Blackjack 2
1

First, set the browser to Full Screen view, to give this worksheet and your code as much space as possible.

Then choose either one of the two options below:

Option 1: Start this worksheet from scratch

- First Enter your name:
- Then
  .
- All answers that you subsequently enter/edit will be auto-saved to this sheet.

Option 2: Continue, or view, previous work

- All answers that you subsequently enter/edit will be auto-saved to this sheet.
- The first time you re-load an answers sheet - within a single session on Elan - you will be asked to confirm that you understand this.

This worksheet is copyright © Richard Pawson 2025, and protected by the Creative Commons license: Attribution-NonCommercial-NoDerivatives 4.0 International. You may freely make and distribute copies of this worksheet as is, but if you modify this worksheet you may not distribute your modified version (outside your own teaching institution) without the author's permission. Please email the author to report errors or suggest improvements.

# Step 1: load the starting code

# 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:"&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 {\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

Step Completed Total hints used: /

# Step 2: exploring the code

Briefly describe the meaning of 'abstraction' in programming:

#### Notes

Step Completed 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

Step Completed 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

Step Completed 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

Step Completed Total hints used: /

# Step 6: the default strategies

What can you say that every function does?

#### Notes

Step Completed 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

Step Completed 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

Step Completed 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

Step Completed 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

Step Completed 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

Step Completed 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

Step Completed 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: