# f9dcff83372dc8408276b3d13e4d94c152045baea4e93c37ee2cdafa18102b34 Elan 2.0.0-alpha valid
main
variable game set to new Game(1000)
call game.addPlayer(new HumanPlayer("Player A", 1000))
variable anotherRound set to true
while anotherRound
call playOneRound(game)
call game.setMessage("Points updated. Do you want to play another round? (press y or n)")
call clearKeyBuffer()
call display(game)
variable k set to waitForKey().lowerCase()
if k.equals("y") then
call game.setMessage("")
else
set anotherRound to false
end if
end while
end main
procedure playOneRound(game as Game)
call game.newRound()
call display(game)
variable dealer set to game.dealer
variable faceCard set to dealer.faceCard.getValue()
for player in game.players
call player.startTurn()
call display(game)
while player.status is Status.active
call player.nextAction(faceCard)
call display(game)
end while
end for
call dealer.play()
call display(game)
while dealer.status is Status.active
call dealer.nextAction(faceCard)
call display(game)
end while
call game.updatePoints()
end procedure
procedure display(game as Game)
variable html set to $"<style>{styleSheet}</style>{htmlForGame(game)}"
call displayHtml(html)
call sleep(1.5)
end procedure
procedure updatePoints(dealer as Dealer, player as Player, playerOutcome as Outcome)
if playerOutcome is Outcome.winDouble then
call player.changePointsBy(2)
call dealer.changePointsBy(-2)
elif playerOutcome is Outcome.win then
call player.changePointsBy(1)
call dealer.changePointsBy(-1)
elif playerOutcome is Outcome.lose then
call player.changePointsBy(-1)
call dealer.changePointsBy(1)
end if
end procedure
function determinePlayerOutcome(dealer as Dealer, player as Player) returns Outcome
# These 'let' definitions are just to make the logic that follows them simpler to read
variable d set to dealer.status
variable dTotal set to dealer.handTotal
variable p set to player.status
variable pTotal set to player.handTotal
variable bust set to Status.bust
variable bj set to Status.blackjack
variable win set to Outcome.win
variable winDouble set to Outcome.winDouble
variable lose set to Outcome.lose
variable draw set to Outcome.draw
variable playerOutcome set to draw
if p is bust then
set playerOutcome to lose
elif (p is bj) and (d isnt bj) then
set playerOutcome to winDouble
elif d is bust then
set playerOutcome to win
elif (d is bj) and (p is bj) then
set playerOutcome to draw
elif p is bj then
set playerOutcome to winDouble
elif d is bj then
set playerOutcome to lose
elif pTotal > dTotal then
set playerOutcome to win
elif pTotal < dTotal then
set playerOutcome to lose
else
# strictly, this 'else' clause is redundant - as the variable was initialised to 'draw' - but added for clarity
set playerOutcome to draw
end if
return playerOutcome
end function
test test_determinePlayerOutcome
variable dbj set to new Dealer(0) with status set to Status.blackjack
variable d21 set to new Dealer(0) with status set to Status.standing, handTotal set to 21
variable d17 set to new Dealer(0) with status set to Status.standing, handTotal set to 17
variable dbu set to new Dealer(0) with status set to Status.bust
variable pbj set to new HumanPlayer("", 0) with status set to Status.blackjack
variable p21 set to new HumanPlayer("", 0) with status set to Status.standing, handTotal set to 21
variable p17 set to new HumanPlayer("", 0) with status set to Status.standing, handTotal set to 17
variable pbu set to new HumanPlayer("", 0) with status set to Status.bust
assert determinePlayerOutcome(dbj, pbj) is Outcome.draw
assert determinePlayerOutcome(dbj, p21) is Outcome.lose
assert determinePlayerOutcome(dbj, pbu) is Outcome.lose
assert determinePlayerOutcome(d21, pbj) is Outcome.winDouble
assert determinePlayerOutcome(d21, p21) is Outcome.draw
assert determinePlayerOutcome(d21, p17) is Outcome.lose
assert determinePlayerOutcome(d21, pbu) is Outcome.lose
assert determinePlayerOutcome(dbu, pbj) is Outcome.winDouble
assert determinePlayerOutcome(dbu, p17) is Outcome.win
assert determinePlayerOutcome(dbu, pbu) is Outcome.lose
end test
function dealCard(random as Float) returns Card
variable number set to (random*52).floor()
variable rank set to rankValue().keys()[divAsInt(number, 4)]
variable suit set to number mod 4
return new Card(rank, intAsSuit(suit), false)
end function
test test_dealCard
variable c1 set to dealCard(0)
assert c1.rank is "2"
assert c1.suit is Suit.clubs
variable c2 set to dealCard(0.9999999)
assert c2.rank is "A"
assert c2.suit is Suit.spades
variable c3 set to dealCard(0.5)
assert c3.rank is "8"
assert c3.suit is Suit.hearts
variable c4 set to dealCard(0.24)
assert c4.rank is "5"
assert c4.suit is Suit.clubs
end test
function intAsSuit(n as Int) returns Suit
variable suit set to Suit.clubs
if n is 1 then
set suit to Suit.diamonds
elif n is 2 then
set suit to Suit.hearts
elif n is 3 then
set suit to Suit.spades
end if
return suit
end function
test test_intAsSuit
assert intAsSuit(0) is Suit.clubs
assert intAsSuit(1) is Suit.diamonds
assert intAsSuit(2) is Suit.hearts
assert intAsSuit(3) is Suit.spades
end test
function htmlForGame(game as Game) returns String
variable html set to "<div class='game'>"
set html to html + htmlForPlayer(game.dealer)
for player in game.players
set html to html + htmlForPlayer(player)
end for
set html to html + $"<div class='message'>{game.message}</div>"
return html + "</div>"
end function
test test_htmlForGame
variable c1 set to new Card("3", Suit.clubs, false)
variable c2 set to new Card("K", Suit.spades, true)
variable p set to new HumanPlayer("fred", 10) with cards set to [c1, c2]
variable players set to (new List<of Player>()).withAppend(p)
variable g2 set to new Game(1) with players set to players
assert htmlForGame(g2) is "<div class='game'><div class='player'><div class='details'>Dealer - 1 points </div><div class='hand'></div></div><div class='player'><div class='details'>fred - 10 points - hand total: 0</div><div class='hand'><div class='card black'><div class='u'>3</div><div class='v'>&clubs;</div><div class='a'>&clubs;</div><div class='b'>&clubs;</div><div class='c'>&clubs;</div></div><div class='card reversed'></div></div></div><div class='message'></div></div>"
end test
function htmlForPlayer(player as Player) returns String
variable html set to "<div class='player'>"
set html to html + $"<div class='details'>{player.name} - {player.points} points {player.getMessage()}</div>"
set html to html + "<div class='hand'>"
for card in player.cards
variable suit set to card.suit
variable rank set to card.rank
set html to html + htmlForCard(card)
end for
return html + "</div></div>"
end function
test test_htmlForPlayer
variable c1 set to new Card("3", Suit.clubs, false)
variable c2 set to new Card("K", Suit.spades, true)
variable p set to new HumanPlayer("charlie", 10) with cards set to [c1, c2]
assert htmlForPlayer(p) is "<div class='player'><div class='details'>charlie - 10 points - hand total: 0</div><div class='hand'><div class='card black'><div class='u'>3</div><div class='v'>&clubs;</div><div class='a'>&clubs;</div><div class='b'>&clubs;</div><div class='c'>&clubs;</div></div><div class='card reversed'></div></div></div>"
end test
function htmlForCard(card as Card) returns String
variable html set to ""
if card.faceDown then
set html to "<div class='card reversed'>"
else
variable rank set to card.rank
variable suit set to card.suit
variable colour set to colourForSuit(suit)
variable symbol set to symbolForSuit(suit)
set html to $"<div class='card {colour}'>"
variable u set to htmlForSpot("u", rank)
variable v set to htmlForSpot("v", symbol)
variable grid set to ""
for location in gridForRank(rank)
if location.equals("royal") then
set grid to grid + htmlForSpot(location, rank)
else
set grid to grid + htmlForSpot(location, symbol)
end if
end for
set html to html + $"{u}{v}{grid}"
end if
return html + "</div>"
end function
test test_htmlForCard
variable c1 set to new Card("3", Suit.clubs, false)
assert htmlForCard(c1) is "<div class='card black'><div class='u'>3</div><div class='v'>&clubs;</div><div class='a'>&clubs;</div><div class='b'>&clubs;</div><div class='c'>&clubs;</div></div>"
variable c2 set to new Card("K", Suit.spades, true)
assert htmlForCard(c2) is "<div class='card reversed'></div>"
end test
function htmlForSpot(id as String, content as String) returns String
return $"<div class='{id}'>{content}</div>"
end function
test test_htmlForSpot
assert htmlForSpot("c", "&hearts;") is "<div class='c'>&hearts;</div>"
assert htmlForSpot("u", "10") is "<div class='u'>10</div>"
end test
class Game
constructor(dealerStartPoints as Int)
set this.dealer to new Dealer(dealerStartPoints)
set this.players to new List<of Player>()
set this.message to ""
end constructor
property dealer as Dealer
property players as List<of Player>
property message as String
procedure newRound()
variable dealer set to this.dealer
call dealer.newHand()
for player in this.players
call player.newHand()
end for
end procedure
procedure updatePoints()
for player in this.players
variable playerOutcome set to determinePlayerOutcome(this.dealer, player)
call global.updatePoints(this.dealer, player, playerOutcome)
end for
end procedure
procedure addPlayer(player as Player)
variable players set to this.players
call players.append(player)
end procedure
procedure setMessage(message as String)
set this.message to message
end procedure
end class
class Card
property suit as Suit
property rank as String
property faceDown as Boolean
constructor(rank as String, suit as Suit, facedown as Boolean)
set this.rank to rank
set this.suit to suit
set this.faceDown to facedown
end constructor
procedure turnFaceUp()
set this.faceDown to false
end procedure
procedure turnFaceDown()
set this.faceDown to true
end procedure
end class
abstract class Player
property name as String
property points as Int
property cards as List<of Card>
property handTotal as Int
property softAce as Boolean
property status as Status
property hasTurn as Boolean
procedure startTurn()
if this.status is Status.active then
set this.hasTurn to true
end if
end procedure
procedure evaluateStatus(newCard as Card)
if (cardCount() is 2) and (this.handTotal is 21) then
set this.status to Status.blackjack
elif (this.handTotal > 21) and (this.softAce) then
set this.handTotal to this.handTotal - 10
set this.softAce to false
elif this.handTotal > 21 then
set this.status to Status.bust
elif this.handTotal is 21 then
set this.status to Status.standing
end if
if this.status isnt Status.active then
set this.hasTurn to false
end if
end procedure
procedure stand()
set this.status to Status.standing
set this.hasTurn to false
end procedure
procedure draw()
variable newCard set to dealCard(random())
variable cards set to this.cards
call cards.append(newCard)
if newCard.rank.equals("A") then
call addAce()
else
set this.handTotal to this.handTotal + rankValue()[newCard.rank]
end if
call evaluateStatus(newCard)
end procedure
procedure addAce()
if this.softAce then
set this.handTotal to this.handTotal + 1
else
set this.handTotal to this.handTotal + 11
set this.softAce to true
end if
end procedure
function cardCount() returns Int
return this.cards.length()
end function
procedure changePointsBy(amount as Int)
set this.points to this.points + amount
end procedure
abstract procedure newHand()
procedure newHandHelper()
set this.hasTurn to false
set this.softAce to false
set this.cards to new List<of Card>()
set this.handTotal to 0
set this.status to Status.active
call draw()
call draw()
end procedure
abstract function getMessage() returns String
function statusAsString() returns String
variable msg set to ""
variable status set to this.status
if this.hasTurn then
set msg to msg + " - PLAYING"
elif status is Status.standing then
set msg to msg + " - STANDING"
elif status is Status.blackjack then
set msg to msg + " - BLACKJACK"
elif status is Status.bust then
set msg to msg + " - BUST"
end if
return msg
end function
abstract procedure nextAction(dealerFaceCard as Card)
end class
class Dealer inherits Player
constructor(startingPoints as Int)
set this.name to "Dealer"
set this.points to startingPoints
set this.faceCard to new Maybe<of Card>()
end constructor
property faceCard as Maybe<of Card>
property hasPlayed as Boolean
procedure play()
call startTurn()
variable hiddenCard set to this.cards[1]
call hiddenCard.turnFaceUp()
set this.hasPlayed to true
end procedure
procedure newHand()
set this.hasPlayed to false
call newHandHelper()
variable faceCard set to this.faceCard
call faceCard.set(this.cards[0])
variable hiddenCard set to this.cards[1]
call hiddenCard.turnFaceDown()
end procedure
procedure nextAction(faceCard as Card)
if this.handTotal < 17 then
call draw()
else
call stand()
end if
end procedure
function getMessage() returns String
variable msg set to ""
if this.hasPlayed then
set msg to statusAsString() + $" - hand total: {this.handTotal}"
end if
return msg
end function
end class
class HumanPlayer inherits Player
constructor(name as String, startingPoints as Int)
set this.name to name
set this.points to startingPoints
end constructor
procedure newHand()
call newHandHelper()
end procedure
procedure nextAction(dealerFaceCard as Card)
variable key set to ""
call clearKeyBuffer()
while key.equals("")
set key to waitForKey()
if key.equals("d") then
call draw()
elif key.equals("s") then
call stand()
else
set key to ""
end if
end while
end procedure
function getMessage() returns String
variable msg set to statusAsString() + $"- hand total: {this.handTotal}"
if this.hasTurn then
set msg to msg + " - press 'd' to draw, 's' to stand"
end if
return msg
end function
end class
enum Action stand, draw
enum Outcome undecided, lose, draw, win, winDouble
enum Status active, standing, blackjack, bust
enum Suit clubs, diamonds, hearts, spades
function symbolForSuit(suit as Suit) returns String
variable dict set to [Suit.clubs:"&clubs;", Suit.diamonds:"&diams;", Suit.hearts:"&hearts;", Suit.spades:"&spades;"]
return dict[suit]
end function
function rankValue() returns Dictionary<of String, Int>
return ["2":2, "3":3, "4":4, "5":5, "6":6, "7":7, "8":8, "9":9, "10":10, "J":10, "Q":10, "K":10, "A":11]
end function
function gridForRank(rank as String) returns List<of String>
variable dict set to ["A":["royal"], "2":["b", "c"], "3":["a", "b", "c"], "4":["d", "e", "f", "g"], "5":["a", "d", "e", "f", "g"], "6":["d", "e", "f", "g", "h", "i"], "7":["d", "e", "f", "g", "h", "i", "l"], "8":["d", "e", "f", "g", "h", "i", "l", "m"], "9":["a", "d", "e", "f", "g", "n", "o", "p", "r"], "10":["d", "e", "f", "g", "n", "o", "p", "r", "s", "t"], "J":["royal"], "Q":["royal"], "K":["royal"]]
return dict[rank]
end function
function colourForSuit(suit as Suit) returns String
variable dict set to [Suit.clubs:"black", Suit.diamonds:"red", Suit.hearts:"red", Suit.spades:"black"]
return dict[suit]
end function
constant styleSheet set to ':root { background-color: darkgreen; padding-left: 5px;}.game { padding: 5px;}.message, .details { color: white; font-family: Arial, Helvetica, sans-serif;}.hand { margin-top: 5px; height: 150px; padding-bottom: 10px; } .card { position: relative; float: left; background-color: white; width: 95px; height:140px; margin-right:10px; padding: 5px; border-radius: 5px; font-family: Helvetica, sans-serif; }.royal,.a,.b,.c,.d,.e,.f,.g,.h,.i,.j,.k,.l,.m,.n,.o,.p,.q,.r,.s,.t,.u,.v,.w,.x,.y,.z {position: absolute; text-align:center;}/* Standard spots */ .a,.b,.c,.d,.e,.f,.g,.h,.i,.l,.m,.n,.o,.p,.r,.s,.t {font-size: 30px;} /* columns */ .d,.n,.h,.p,.f {left: 18px } .a,.b,.c,.l,.m,.s,.t {left: 43px;} .e,.o,.i,.r,.g {left: 68px} /* rows */ .d,.b,.e {top: 0px} .s {top: 20px;} .l {top: 28px;} .n,.o {top: 37px;} .h,.a,.i {top: 57px} .p,.r {top: 75px;} .m {top: 86px;} .t {top: 93px;} .f,.c,.g {top: 114px;}/* royals */ .royal { position: absolute; z-index: 1; width: 95px; height: 140px; line-height: 140px; font-size: 100px; }/* corner summary */ .u {font-size: 15px; width: 15px; text-align: center; left: 0px; top: 2px;} .v {font-size: 20px; width: 15px; text-align: center; left: 0px; top: 12px;}/* suit colors */ .red {color: red} .black {color: black}/* back */ .card.reversed { background-color: rgba(0, 0, 255, 0.607);}'
and immediately use file > auto save the
code to your local file storage: because you will be making changes and want to ensure that your changes are saved.
When you run the program, the display tab will be shown in place
of this worksheet, you can flip between them by clicking on the tab headings.
So, run the program now, either:
using the mouse, by clicking on the run button at the top of the screen, or
using the keyboard, by pressing Ctrl+r
and play exactly 10 rounds of Blackjack.
Make sure that you have grasped the following points:
Most cards have a obvious numeric value. The Royal cards - Jack, Queen, King - each have
the value 10. An Ace is special: it can be valued at 11, or at 1.
An Ace valued at 11 is known as a 'soft Ace'.
If a dealt card takes the hand total over 21 then a soft Ace is automatically devalued to 1.
The aim is to get the value of your hand as close to 21 as you can - but if drawing a card causes
the total to go over 21 then (unless you have a 'soft Ace') you will be 'BUST'
and your turn is over.
If the first two cards of any player total exactly 21 - which would require one Ace, and one Royal or 10 card -
then you have 'BLACKJACK' - the best hand possible - so no further action is needed from you.
When all hands have finished playing, the outcome is determined, points swapped as follows:
If a player is BUST then 1 point is passed from them to the dealer even the dealer is also BUST.
If a player has BLACKJACK then they gain 2 points from the dealer unless the dealer
also has BLACKJACK, in which case it is a draw.
else, if the player is STANDING and the dealer has BLACKJACK the dealer gains one point from the player.
else, if the player is STANDING and their hand total is greater than the dealer's, the player gains one point from the dealer.
else, if the player is STANDING and their hand total is the same as the dealer's, it is a draw.
else, if the player's total is less than the dealer's, the dealer gains one point from the player.
This program uses deck of multiple packs of cards: so there is a small chance that the same card may appear on
screen more than once.
After playing 10 rounds, how many points did you, and the dealer, end up with?
If you have not already exited the program, click stop.
Notes
Total hints used: /
Step 2: start to explore the code
Watch the next video:
How many instructions are there in total within the Blackjack program (just find the largest instruction number)?
The initial keyword in an instruction defines the type of instruction.
Find and list here at least five initial keywords not mentioned explicitly in the video:
Notes
Total hints used: /
Step 3: the different elements within instructions
Watch the next video:
List below the different colours that you can see being used within the code (they are all shown within the first 15 instructions):
What is the significance of the colour dark-blue, and black?
Notes
Total hints used: /
Step 4: comments
Watch the next video:
Scrolling through the code, find two comments, not including the one at the top showing the Elan version,
and write down the instruction number immediately below each comment:
Notes
Total hints used: /
Step 5: identifiers
Watch the next video:
Find five identifiers used in the program - not including any that were mentioned in the video - and list them here:
Notes
Total hints used: /
Step 6: literals
Watch the next video:
Find an example of a literal number, and a literal string - not including any specifically mentioned in the video -
and note their instruction numbers below:
Notes
Total hints used: /
Step 7: methods
Watch the next video:
Find five method names - not including any specifically mentioned in the video - and write
them here:
Notes
Total hints used: /
Step 8: types
Which of the types mentioned in the video are defined within the standard Elan language
(if necessary, watch the video again)?
Notes
Total hints used: /
Step 9: adding a new instruction
Watch the next video:
Your first task is to turn the program into a two-player game (not counting the dealer, played by the computer):
and immediately below it insert a new call instruction.
Into the two editable fields type in the same code that you see in the instruction immediately above it,
except that instead of "Player A", write "Player B". Note that:
your text will initially be black, but it will be coloured once you leave the field
a drop-down list will offer you some 'auto-completion' options, which can save typing and
errors. You can select an option with the mouse or with the cursor ↑↓ keys and Enter.
You might see a message appear on the right, but you can ignore it for now, unless it is coloured red
(you will also then see the word invalid or error appear) in which case you should Backspace
to correct the mistake.
When you have completed the new instruction, run the program, and take responsibility for both Player A
and Player B (or, if you are pairing with another person, control one each).
When does the dealer play, now ?
Notes
Total hints used: /
Step 10: modifying an existing instruction
Go back to the instruction that creates Player A and above itinsert a let instruction
and note that this features two editable fields.
In the first field enter name1 and in the second field type in the following code
using the auto-completions to help input("Enter name for first player")
Going back to the first field, try deleting the contents and typing Name1
What message do you see within the code editor?
And what do you observe in the 'status panel' - at the top of the screen, just to the right of the code editor?
Then revert the field back to name2.
Select the instruction below this - in other words the one that you added previously - and Tab to the second
field. Edit the contents, replacing the literal string "Player A" with name2
(no quotation marks because this is an identifier that holds a string, not a literal string).
Run the program. When asked, type in your own first name, finishing with the Enter key.
Has the name you entered made a difference?
There is an unwanted 'side effect' on the screen - what is it?
To fix the unwanted side effect, insert the following new instruction immediatelyafter the second player is created and added to the game:
call clearPrintedText()
Now change the program so that it asks the user to enter a name for the second player, this time using
the identifier name2.
Run the program again, and confirm that you can now name both players.
What would happen if you define the second identifier as name2, but in the next line
you were to write name1? (If you need to check, run the program)
And what if the the second identifier was still name2 but you used name3
in the following instruction? This time, can you even run the program to try it?
Notes
Total hints used: /
Congratulations! You have completed this worksheet
You have learned:
How to load, run, and stop a program
How to recognise the different kinds of instructions that make up a program, including compound instructions, which contain other instructions.
How to navigate around the program, selecting different instructions in order to draw attention to them and as a prelude to modifying them.
How to insert new instructions, delete instructions, and to undo changes when they didn't do what you wanted.
In the next worksheet - Blackjack 2 - you'll start to do some real programming, by defining and implementing the logic
for a fully-automated Blackjack player - and then in the Blackjack 3 worksheet, pitting different automated players
against each other for thousands of games, and using the outcome to refine the strategies.
In the meantime, if you have time, we recommend that you watch the following additional videos - that offer
further techniques for navigating and editing instructions. Practice the techniques shown in each video, either while it is running,
or immediately afterwards.
All the techniques for navigating and modifying instructions may be looked up in the Help tab. For example: show in Help