# 04d89ce8229216d73214c86390f7a0e838a04c2692a4cfdddde177179a6aa104 Elan 2.0.0-alpha5 valid
main
variable points set to 1000
variable rounds set to 1
variable game set to new Game(points)
call game.addPlayer(new AutomatedPlayerA(points))
call game.addPlayer(new AutomatedPlayerB(points))
while rounds > 0
call playOneRound(game)
call clearKeyBuffer()
call display(game)
reassign rounds to rounds - 1
end while
end main
function strategyA(p as Player, dealerFaceUp as Card) returns Choice
return Choice.stand
end function
function strategyB(p as Player, dealerFaceUp as Card) returns Choice
return Choice.draw
end function
procedure playOneRound(game as Game)
call game.newRound()
call display(game)
variable dealer set to game.dealer
for player in game.players
call player.startTurn()
call display(game)
while player.status is Status.active
call player.nextAction(dealer.faceCard)
call display(game)
end while
end for
call dealer.play()
call display(game)
while dealer.status is Status.active
call dealer.nextAction(dealer.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
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
reassign playerOutcome to lose
elif (p is bj) and (d isnt bj) then
reassign playerOutcome to winDouble
elif d is bust then
reassign playerOutcome to win
elif (d is bj) and (p is bj) then
reassign playerOutcome to draw
elif p is bj then
reassign playerOutcome to winDouble
elif d is bj then
reassign playerOutcome to lose
elif pTotal > dTotal then
reassign playerOutcome to win
elif pTotal < dTotal then
reassign playerOutcome to lose
else
# strictly, this 'else' clause is redundant - as the variable was initialised to 'draw' - but added for clarity
reassign playerOutcome to draw
end if
return playerOutcome
end function
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
function intAsSuit(n as Int) returns Suit
variable suit set to Suit.clubs
if n is 1 then
reassign suit to Suit.diamonds
elif n is 2 then
reassign suit to Suit.hearts
elif n is 3 then
reassign suit to Suit.spades
end if
return suit
end function
function htmlForGame(game as Game) returns String
variable html set to "<div class='game'>"
reassign html to html + htmlForPlayer(game.dealer)
for player in game.players
reassign html to html + htmlForPlayer(player)
end for
reassign html to html + $"<div class='message'>{game.message}</div>"
return html + "</div>"
end function
function htmlForPlayer(player as Player) returns String
variable html set to "<div class='player'>"
reassign html to html + $"<div class='details'>{player.name} - {player.points} points {player.getMessage()}</div>"
reassign html to html + "<div class='hand'>"
for card in player.cards
variable suit set to card.suit
variable rank set to card.rank
reassign html to html + htmlForCard(card)
end for
return html + "</div></div>"
end function
function htmlForCard(card as Card) returns String
variable html set to ""
if card.faceDown then
reassign 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)
reassign 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
reassign grid to grid + htmlForSpot(location, rank)
else
reassign grid to grid + htmlForSpot(location, symbol)
end if
end for
reassign html to html + $"{u}{v}{grid}"
end if
return html + "</div>"
end function
function htmlForSpot(id as String, content as String) returns String
return $"<div class='{id}'>{content}</div>"
end function
class Game
constructor(dealerStartPoints as Int)
reassign this.dealer to new Dealer(dealerStartPoints)
reassign this.players to new List<of Player>()
reassign this.message to ""
end constructor
property dealer as Dealer
property players as List<of Player>
property message as String
function withPlayers(p as List<of Player>) returns Game
let copyOfThis be copy(this)
reassign copyOfThis.players to p
return copyOfThis
end function
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
call player.determineOutcomeAndUpdatePoints(this.dealer)
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)
reassign this.message to message
end procedure
function toString() returns String
return "a Game"
end function
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)
reassign this.rank to rank
reassign this.suit to suit
reassign this.faceDown to facedown
end constructor
procedure turnFaceUp()
reassign this.faceDown to false
end procedure
procedure turnFaceDown()
reassign this.faceDown to true
end procedure
function toString() returns String
return $"{this.rank}{symbolForSuit(this.suit)}"
end function
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
reassign this.hasTurn to true
end if
end procedure
procedure determineOutcomeAndUpdatePoints(dealer as Dealer)
variable playerOutcome set to determinePlayerOutcome(dealer, this)
if playerOutcome is Outcome.winDouble then
call this.changePointsBy(2)
call dealer.changePointsBy(-2)
elif playerOutcome is Outcome.win then
call this.changePointsBy(1)
call dealer.changePointsBy(-1)
elif playerOutcome is Outcome.lose then
call this.changePointsBy(-1)
call dealer.changePointsBy(1)
end if
end procedure
procedure evaluateStatus(newCard as Card)
if (this.cardCount() is 2) and (this.handTotal is 21) then
reassign this.status to Status.blackjack
elif (this.handTotal > 21) and (this.softAce) then
reassign this.handTotal to this.handTotal - 10
reassign this.softAce to false
elif this.handTotal > 21 then
reassign this.status to Status.bust
elif this.handTotal is 21 then
reassign this.status to Status.standing
end if
if this.status isnt Status.active then
reassign this.hasTurn to false
end if
end procedure
procedure stand()
reassign this.status to Status.standing
reassign 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 this.addAce()
else
reassign this.handTotal to this.handTotal + rankValue()[newCard.rank]
end if
call this.evaluateStatus(newCard)
end procedure
procedure addAce()
if this.softAce then
reassign this.handTotal to this.handTotal + 1
else
reassign this.handTotal to this.handTotal + 11
reassign this.softAce to true
end if
end procedure
function cardCount() returns Int
return this.cards.length()
end function
procedure changePointsBy(amount as Int)
reassign this.points to this.points + amount
end procedure
abstract procedure newHand()
procedure newHandHelper()
reassign this.hasTurn to false
reassign this.softAce to false
reassign this.cards to new List<of Card>()
reassign this.handTotal to 0
reassign this.status to Status.active
call this.draw()
call this.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
reassign msg to msg + " - PLAYING"
elif status is Status.standing then
reassign msg to msg + " - STANDING"
elif status is Status.blackjack then
reassign msg to msg + " - BLACKJACK"
elif status is Status.bust then
reassign 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)
reassign this.name to "Dealer"
reassign this.points to startingPoints
reassign this.cards to new List<of Card>()
reassign this.faceCard to new Card("2", Suit.clubs, true)
end constructor
property faceCard as Card
property hasPlayed as Boolean
function withStatus(status as Status) returns Dealer
let copyOfThis be copy(this)
reassign copyOfThis.status to status
return copyOfThis
end function
function withHandTotal(ht as Int) returns Dealer
let copyOfThis be copy(this)
reassign copyOfThis.handTotal to ht
return copyOfThis
end function
procedure play()
call this.startTurn()
variable hiddenCard set to this.cards[1]
call hiddenCard.turnFaceUp()
reassign this.hasPlayed to true
end procedure
procedure newHand()
reassign this.hasPlayed to false
call this.newHandHelper()
reassign this.faceCard to 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 this.draw()
else
call this.stand()
end if
end procedure
function getMessage() returns String
variable msg set to ""
if this.hasPlayed then
reassign msg to this.statusAsString() + $" - hand total: {this.handTotal}"
end if
return msg
end function
function toString() returns String
return "the Dealer"
end function
end class
class HumanPlayer inherits Player
constructor(name as String, startingPoints as Int)
reassign this.name to name
reassign this.points to startingPoints
reassign this.cards to new List<of Card>()
end constructor
function withStatus(status as Status) returns HumanPlayer
let copyOfThis be copy(this)
reassign copyOfThis.status to status
return copyOfThis
end function
function withHandTotal(ht as Int) returns HumanPlayer
let copyOfThis be copy(this)
reassign copyOfThis.handTotal to ht
return copyOfThis
end function
function withCards(c as List<of Card>) returns HumanPlayer
let copyOfThis be copy(this)
reassign copyOfThis.cards to c
return copyOfThis
end function
procedure newHand()
call this.newHandHelper()
end procedure
procedure nextAction(dealerFaceCard as Card)
variable key set to ""
call clearKeyBuffer()
while key.equals("")
reassign key to waitForKey()
if key.equals("d") then
call this.draw()
elif key.equals("s") then
call this.stand()
else
reassign key to ""
end if
end while
end procedure
function getMessage() returns String
variable msg set to this.statusAsString() + $"- hand total: {this.handTotal}"
if this.hasTurn then
reassign msg to msg + " - press 'd' to draw, 's' to stand"
end if
return msg
end function
function toString() returns String
return $"Player: {this.name}"
end function
end class
abstract class AutomatedPlayer inherits Player
procedure newHand()
call this.newHandHelper()
end procedure
procedure nextAction(dealerFaceCard as Card)
variable choice set to this.getChoice(dealerFaceCard)
if choice is Choice.draw then
call this.draw()
else
call this.stand()
end if
end procedure
abstract function getChoice(dealerFaceCard as Card) returns Choice
function getMessage() returns String
return this.statusAsString() + $" - hand total: {this.handTotal}"
end function
end class
class AutomatedPlayerA inherits AutomatedPlayer
constructor(startingPoints as Int)
reassign this.name to "Player A"
reassign this.points to startingPoints
reassign this.cards to new List<of Card>()
end constructor
function getChoice(dealerFaceCard as Card) returns Choice
return strategyA(this, dealerFaceCard)
end function
function toString() returns String
return this.name
end function
end class
class AutomatedPlayerB inherits AutomatedPlayer
constructor(startingPoints as Int)
reassign this.name to "Player B"
reassign this.points to startingPoints
reassign this.cards to new List<of Card>()
end constructor
function getChoice(dealerFaceCard as Card) returns Choice
return strategyB(this, dealerFaceCard)
end function
function toString() returns String
return this.name
end function
end class
enum Choice 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 dc set to [Suit.clubs:"♣", Suit.diamonds:"♦", Suit.hearts:"♥", Suit.spades:"♠"]
return dc[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 dc 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 dc[rank]
end function
function colourForSuit(suit as Suit) returns String
variable dc set to [Suit.clubs:"black", Suit.diamonds:"red", Suit.hearts:"red", Suit.spades:"black"]
return dc[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);}"
Immediately file > auto save it to local file storage,
so that changes you make are preserved.
Move onto the next step before running the program.
Notes
Total hints used: /
Step 2: exploring the code
Briefly describe the meaning of 'abstraction' in programming:
Notes
Total hints used: /
Step 3: automated players
Having run the program 3-4 times, which of the two players is operating the very 'high risk' strategy, and which the very 'cautious' strategy?
Notes
Total hints used: /
Step 4: identifying the better of the strategies
Having run the program to play 10 rounds automatically, how many points did the Dealer, and each of the players ended up with?
Run the program a second time, and record how many points ended up with this time
Notes
Total hints used: /
Step 5: speeding up the program
Make sure that you have ghosted the sleep_ms (instruction 31) and all five occurrences of call display(game) within
the playOneRound procedure. Then run the program. You will see that both of the two players have lost points -
only the Dealer is ahead.
Which of the two players has lost the least points?
Given that the result is from playing 100 rounds, and each participant started with 1000 points, calculate the average points lost or gained per round expressed as a percentage,
for the Dealer and each Player (where a loss is a negative percentage and a gain is positive). Show how you worked this out.
Notes
Total hints used: /
Step 6: the default strategies
What can you say that every function does?
Notes
Total hints used: /
Step 7:implement a simple strategy with a decision
With the changes, how many points does each have after running 100 games?
Notes
Total hints used: /
Step 8: simulating 10,000 rounds
After making the changes such that the display now only updated after the last round, approximately how long did it take to run 10,000 rounds of Blackjack?
How many points did the Dealer, and each Player end up with?
Given that Player B is mimicking the strategy of the Dealer,
why are they still losing points to the Dealer? (If you can't see why, say so.)
Notes
Total hints used: /
Step 9: making use of the soft ace
After making the changes and running the new version, what were the points for Player A and Player B?
Notes
Total hints used: /
Step 10: paying attention to the Dealer's 'face up card'
After making the changes and running the new version, what were the points for Player A and Player B?
Notes
Total hints used: /
Step 11: expanding the scope of the previous rule
After making the changes and running the new version, what were the points for Player A and Player B?
Notes
Total hints used: /
Step 12: testing what you have learned
What does defining a named value as a 'variable' allow you to then do?
When defining a variable, what two things must you specify?
What kind of instruction is used to change the value of a variable?
What must the code written into the 'condition' field of an 'if' instruction evaluate to?
Some types offer 'properties'. What is a property?
If you have a named value and you want to see what properties (if any) you can
access from it, how do you do that?
What happens if you try to compare two values that are of different types?
If an 'if instruction' is given an 'else' clause, under what condition is that else clause executed?
If you combine more than one operator within a condition, what must you be careful about?
And what is the best way to ensure that you don't run into a problem when combining operators?
Notes
Total hints used: /
Congratulations! You have finished this worksheet
Answers to the questions in the previous step:
Q: What does defining a named value as a 'variable' allow you to then do?
A: It allows you to change the value subsequently.
When defining a variable, what two things must you specify?
You must specify the name for the variable and its initial value.
What kind of instruction is used to change the value of a variable?
A: a set instruction.
Q: What must the code written into the 'condition' field of an 'if' instruction evaluate to?
A: It must evaluate to 'true' or 'false'
Q: Some types offer 'properties'. What is a property?
A: a piece of information
Q: If you have a named value and you want to see what properties (if any) you can
access from it, how do you do that?
A: by typing a dot after the named value and selecting an option from the pop-up list
Q: What happens if you try to compare two values that are of different types?
A: You get a 'type incompatibility' error (like comparing apples and oranges)
Q: If you combine more than one operator within a condition, what must you be careful about?
A: operator precedence
Q: And what is the best way to ensure that you don't run into a problem when combining operators?
A: by using brackets to ensure that there is no ambiguity between which argument is applied to which operator.
If you are interested in taking this project further, watch this video first: