# a15f20d097542126cc9e83305716a364b9fb2c0c0fd30c28172424dfc2c14722 Elan 1.8.2 guest default_profile valid
main
let points be 1000
variable rounds set to 1
let game be 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)
let dealer be game.dealer
each 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 each
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)
let html be "<style>{styleSheet}</style>{htmlForGame(game)}"
call displayHtml(html)
call pause(1500)
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
[imported] 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)
else if playerOutcome is Outcome.win then
call player.changePointsBy(1)
call dealer.changePointsBy(-1)
else if playerOutcome is Outcome.lose then
call player.changePointsBy(-1)
call dealer.changePointsBy(1)
end if
end procedure
[imported] 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
let d be dealer.status
let dTotal be dealer.handTotal
let p be player.status
let pTotal be player.handTotal
let bust be Status.bust
let bj be Status.blackjack
let win be Outcome.win
let winDouble be Outcome.winDouble
let lose be Outcome.lose
let draw be Outcome.draw
variable playerOutcome set to draw
if p is bust then
set playerOutcome to lose
else if (p is bj) and (d isnt bj) then
set playerOutcome to winDouble
else if d is bust then
set playerOutcome to win
else if (d is bj) and (p is bj) then
set playerOutcome to draw
else if p is bj then
set playerOutcome to winDouble
else if d is bj then
set playerOutcome to lose
else if pTotal > dTotal then
set playerOutcome to win
else if 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
[imported] function dealCard(random as Float) returns Card
let number be (random*52).floor()
let rank be rankValue.keys()[(number/4).floor()]
let suit be intAsSuit(number mod 4)
return new Card(rank, suit)
end function
[imported] function intAsSuit(n as Int) returns Suit
variable suit set to Suit.clubs
if n is 1 then
set suit to Suit.diamonds
else if n is 2 then
set suit to Suit.hearts
else if n is 3 then
set suit to Suit.spades
end if
return suit
end function
[imported] function htmlForGame(game as Game) returns String
variable html set to "<div class='game'>"
set html to html + htmlForPlayer(game.dealer)
each player in game.players
set html to html + htmlForPlayer(player)
end each
set html to html + "<div class='message'>{game.message}</div>"
return html + "</div>"
end function
[imported] 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'>"
each card in player.cards
let suit be card.suit
let rank be card.rank
set html to html + htmlForCard(card)
end each
return html + "</div></div>"
end function
[imported] function htmlForCard(card as Card) returns String
variable html set to ""
if card.faceDown then
set html to "<div class='card reversed'>"
else
let rank be card.rank
let suit be card.suit
let colour be colours[suit]
let symbol be symbols[suit]
set html to "<div class='card {colour}'>"
let u be htmlForSpot("u", rank)
let v be htmlForSpot("v", symbol)
variable grid set to ""
each location in gridByRank[rank]
if location is "royal" then
set grid to grid + htmlForSpot(location, rank)
else
set grid to grid + htmlForSpot(location, symbol)
end if
end each
set html to html + "{u}{v}{grid}"
end if
return html + "</div>"
end function
[imported] function htmlForSpot(id as String, content as String) returns String
return "<div class='{id}'>{content}</div>"
end function
[imported] class Game
constructor(dealerStartPoints as Int)
set property.dealer to new Dealer(dealerStartPoints)
end constructor
property dealer as Dealer
property players as List<of Player>
property message as String
procedure newRound()
call property.dealer.newHand()
each player in property.players
call player.newHand()
end each
end procedure
procedure updatePoints()
each player in property.players
let playerOutcome be determinePlayerOutcome(property.dealer, player)
call global.updatePoints(property.dealer, player, playerOutcome)
end each
end procedure
procedure addPlayer(player as Player)
call property.players.append(player)
end procedure
procedure setMessage(message as String)
set property.message to message
end procedure
end class
[imported] class Card
constructor(rank as String, suit as Suit)
set property.rank to rank
set property.suit to suit
set property.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 property.faceDown to false
end procedure
procedure turnFaceDown()
set property.faceDown to true
end procedure
end class
[imported] 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 property.status is Status.active then
set property.hasTurn to true
end if
end procedure
procedure evaluateStatus(newCard as Card)
if (cardCount() is 2) and (property.handTotal is 21) then
set property.status to Status.blackjack
else if (property.handTotal > 21) and (property.hasSoftAce) then
set property.handTotal to property.handTotal - 10
set property.hasSoftAce to false
else if property.handTotal > 21 then
set property.status to Status.bust
else if property.handTotal is 21 then
set property.status to Status.standing
end if
if property.status isnt Status.active then
set property.hasTurn to false
end if
end procedure
procedure stand()
set property.status to Status.standing
set property.hasTurn to false
end procedure
procedure draw()
let newCard be dealCard(random())
call property.cards.append(newCard)
if newCard.rank is "A" then
call addAce()
else
set property.handTotal to property.handTotal + newCard.value
end if
call evaluateStatus(newCard)
end procedure
procedure addAce()
if property.hasSoftAce then
set property.handTotal to property.handTotal + 1
else
set property.handTotal to property.handTotal + 11
set property.hasSoftAce to true
end if
end procedure
function cardCount() returns Int
return property.cards.length()
end function
procedure changePointsBy(amount as Int)
set property.points to property.points + amount
end procedure
abstract procedure newHand()
procedure newHandHelper()
set property.hasTurn to false
set property.hasSoftAce to false
set property.cards to empty List<of Card>
set property.handTotal to 0
set property.status to Status.active
call draw()
call draw()
end procedure
abstract function getMessage() returns String
function statusAsString() returns String
variable msg set to ""
let status be property.status
if property.hasTurn then
set msg to msg + " - PLAYING"
else if status is Status.standing then
set msg to msg + " - STANDING"
else if status is Status.blackjack then
set msg to msg + " - BLACKJACK"
else if status is Status.bust then
set msg to msg + " - BUST"
end if
return msg
end function
abstract procedure nextAction(dealerFaceCard as Card)
end class
[imported] class Dealer inherits Player
constructor(startingPoints as Int)
set property.name to "Dealer"
set property.points to startingPoints
end constructor
property faceCard as Card
property hasPlayed as Boolean
procedure play()
call startTurn()
let hiddenCard be property.cards[1]
call hiddenCard.turnFaceUp()
set property.hasPlayed to true
end procedure
procedure newHand()
set property.hasPlayed to false
call newHandHelper()
set property.faceCard to property.cards[0]
let hiddenCard be property.cards[1]
call hiddenCard.turnFaceDown()
end procedure
procedure nextAction(faceCard as Card)
if property.handTotal < 17 then
call draw()
else
call stand()
end if
end procedure
function getMessage() returns String
variable msg set to ""
if property.hasPlayed then
set msg to statusAsString() + " - hand total: {property.handTotal}"
end if
return msg
end function
end class
[imported] class HumanPlayer inherits Player
constructor(name as String, startingPoints as Int)
set property.name to name
set property.points to startingPoints
end constructor
procedure newHand()
call newHandHelper()
end procedure
procedure nextAction(dealerFaceCard as Card)
variable key set to ""
call clearKeyBuffer()
while key is ""
set key to waitForKey()
if key is "d" then
call draw()
else if key is "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: {property.handTotal}"
if property.hasTurn then
set msg to msg + " - press 'd' to draw, 's' to stand"
end if
return msg
end function
end class
[imported] class AutomatedPlayer inherits Player
constructor(name as String, decisionFunc as Func<of Player, Card => Action>, startingPoints as Int)
set property.name to name
set property.decisionFunc to decisionFunc
set property.points to startingPoints
end constructor
property decisionFunc as Func<of Player, Card => Action>
procedure newHand()
call newHandHelper()
end procedure
procedure nextAction(dealerFaceCard as Card)
let act be 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: {property.handTotal}"
end function
end class
[imported] enum Action stand, draw
[imported] enum Outcome undecided, lose, draw, win, winDouble
[imported] enum Status active, standing, blackjack, bust
[imported] enum Suit clubs, diamonds, hearts, spades
[imported] constant symbols set to {Suit.clubs:"♣", Suit.diamonds:"♦", Suit.hearts:"♥", Suit.spades:"♠"}
[imported] constant rankValue set to {"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}
[imported] constant gridByRank 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"}}
[imported] constant colours set to {Suit.clubs:"black", Suit.diamonds:"red", Suit.hearts:"red", Suit.spades:"black"}
[imported] 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 pause (instruction 31) and all five occurrences of call displayprocedureName?(gamearguments?)0 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: