Blackjack 2
1

First, reassign 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

# 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 "undefined" 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 "undefined" 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 "undefined" 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 "undefined" 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:"&clubs;", Suit.diamonds:"&diams;", Suit.hearts:"&hearts;", Suit.spades:"&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

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: