# 956aaca44652d46585b5a038d21a6ae2e868be45146d6fc99f1f0550d8f814a5 Elan 2.0.0-alpha valid
main
variable points set to 1000
variable rounds set to 1
variable game set to new Game(points)
call game.addPlayer(new AutomatedPlayer("Player A", strategyA, points))
call game.addPlayer(new AutomatedPlayer("Player B", strategyB, points))
while rounds > 0
call playOneRound(game)
call clearKeyBuffer()
call display(game)
set rounds to rounds - 1
end while
end main
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 strategyA(p as Player, dealerFaceUp as Card) returns Action
return Action.stand
end function
function strategyB(p as Player, dealerFaceUp as Card) returns Action
return Action.draw
end function
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
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 intAsSuit(number mod 4)
return new Card(rank, suit)
end function
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
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
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
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
function htmlForSpot(id as String, content as String) returns String
return $"<div class='{id}'>{content}</div>"
end function
class Game
constructor(dealerStartPoints as Int)
set this.dealer to new Dealer(dealerStartPoints)
end constructor
property dealer as Dealer
property players as List<of Player>
property message as String
procedure newRound()
call this.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)
call this.players.append(player)
end procedure
procedure setMessage(message as String)
set this.message to message
end procedure
end class
class Card
constructor(rank as String, suit as Suit)
set this.rank to rank
set this.suit to suit
set this.value to rankValue()[rank]
end constructor
property suit as Suit
property rank as String
property value as Int
property faceDown as Boolean
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 hasSoftAce 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.hasSoftAce) then
set this.handTotal to this.handTotal - 10
set this.hasSoftAce 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())
call this.cards.append(newCard)
if newCard.rank.equals("A") then
call addAce()
else
set this.handTotal to this.handTotal + newCard.value
end if
call evaluateStatus(newCard)
end procedure
procedure addAce()
if this.hasSoftAce then
set this.handTotal to this.handTotal + 1
else
set this.handTotal to this.handTotal + 11
set this.hasSoftAce 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.hasSoftAce 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
end constructor
property faceCard as 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()
set 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 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
class AutomatedPlayer inherits Player
constructor(name as String, decisionFunc as Func<of Player, Card => Action>, startingPoints as Int)
set this.name to name
set this.decisionFunc to decisionFunc
set this.points to startingPoints
end constructor
property decisionFunc as Func<of Player, Card => Action>
procedure newHand()
call newHandHelper()
end procedure
procedure nextAction(dealerFaceCard as Card)
variable act set to decisionFunc(this, dealerFaceCard)
if act is Action.draw then
call draw()
else
call stand()
end if
end procedure
function getMessage() returns String
return statusAsString() + $" - hand total: {this.handTotal}"
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:"♣", Suit.diamonds:"♦", Suit.hearts:"♥", Suit.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 {\n background-color: darkgreen;\n padding-left: 5px;\n}\n\n.game {\n padding: 5px;\n}\n\n.message, .details {\n color: white;\n font-family: Arial, Helvetica, sans-serif;\n}\n\n.hand {\n margin-top: 5px;\n height: 150px;\n padding-bottom: 10px;\n }\n \n.card {\n position: relative;\n float: left;\n background-color: white;\n width: 95px;\n height:140px;\n margin-right:10px;\n padding: 5px;\n border-radius: 5px;\n font-family: Helvetica, sans-serif; \n}\n.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;}\n\n/* Standard spots */ \n .a,.b,.c,.d,.e,.f,.g,.h,.i,.l,.m,.n,.o,.p,.r,.s,.t {font-size: 30px;}\n\n /* columns */\n .d,.n,.h,.p,.f {left: 18px }\n .a,.b,.c,.l,.m,.s,.t {left: 43px;}\n .e,.o,.i,.r,.g {left: 68px}\n\n /* rows */\n .d,.b,.e {top: 0px}\n .s {top: 20px;}\n .l {top: 28px;}\n .n,.o {top: 37px;}\n .h,.a,.i {top: 57px}\n .p,.r {top: 75px;}\n .m {top: 86px;}\n .t {top: 93px;}\n .f,.c,.g {top: 114px;}\n\n/* royals */\n .royal {\n position: absolute;\n z-index: 1;\n width: 95px;\n height: 140px;\n line-height: 140px;\n font-size: 100px;\n }\n\n/* corner summary */\n .u {font-size: 15px; width: 15px; text-align: center; left: 0px; top: 2px;}\n .v {font-size: 20px; width: 15px; text-align: center; left: 0px; top: 12px;}\n\n/* suit colors */\n .red {color: red}\n .black {color: black}\n\n/* back */\n .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: