User's goal:
- The application user wants to play a single-player card game through the command line
- The user wants to practice / memorise "perfect" blackjack strategy
The rules of the game are based on standard casino blackjack.
Upon loading the game, a blank table is shown with a prompt to enter a bet:
After entering a valid bet value, the game will deal 2 cards to the player (both face-up), and 2 cards to the dealer (1 face-up, 1 face-down), with a prompt to take an action (only valid actions are shown):
Opting to hit will deal another card and prompt again for action (if appropriate, i.e. the extra card did not cause the hand value to be > 21). Stand will end the user's play and reveal the dealer cards, while double will double the original bet (if enough chips are available) in return for 1 additional card only. Split allows 2 cards of equal value to be separated into 2 individual hands, with the bet value of each matching the original bet, if enough chips are available.
When play is completed, the game will show the result and update the player's chip stack accordingly:
Play continues until any of the following happen:
- Player has no chips left
- Player's chip stack exceeds 999,999
- The randomly chosen "reshuffle" point is reached
Individual casinos adjust the rules of blackjack to achieve a suitable balance of risk vs. attracting players, since some rules work in favour of the player, while others favour the casino. The optional blackjack rules built into this game are summarised below:
- Blackjack pays 3:2
- Dealer stands on soft 17
- Doubles permitted with any hand value
- One split permitted with any 2 equally-valued cards
- No double after split
- No double-for-less (i.e. double value must match original bet)
- No blackjack after split
- Hit permitted after splitting Aces
- No re-splitting (splitting again after splitting)
- No check for dealer blackjack until player has acted ("no hole card")
- Note: although a face-down card is shown for the dealer, it is not consulted until after the player has acted so this is considered a "no hole card" game
- No Original Bets Only (OBO)
- No insurance
- No surrender
- Dealer cards not revealed on player bust
- Between 1 and 6 decks per shoe (configurable)
- basic command line interface
- simple game control using keyboard
- ascii art table and cards
- message bar to prompt for input or display results
- prompts to assist with playing "perfect" strategy
- standard blackjack rules
- common casino rule variations built-in
- input error handling
- graceful exit on game over
- play multiple hands in each round
- local multiplayer
Each game is transient and state is maintained with in-memory variables only; no persistent data model is used.
Game state is maintained in the Table
class, which acts as a container for all cards, chip stacks and bets, along with methods for controlling gameplay such as play_hand()
and functions to assess the outcome of a hand or to determine the optimal action based on the current game state.
- Entering a valid bet starts the hand
- Bet greater than available chips is rejected
- Entering non-numeric bet prompts that bet must be a number
- User is prompted to press enter to start new hand once each hand ends
- Double is not permitted when remaining chip stack < bet value
- Split is not permitted when remaining chip stack < bet value
- User is shown prompt when action does not match perfect strategy
- User can take action not matching perfect strategy by entering same option again
- Chevrons are shown to identify active hand after split
- Player is dealt 2 cards initially
- Dealer is dealt 1 face-up card and 1 face-down card initially
-
hit
action deals 1 additional card to player -
stand
ends player input and reveals dealer cards -
double
action increments bet by original bet value and deals 1 card only -
split
action moves 1 card to second hand and plays each hand independently - player hand value > 21 ends hand
- dealer cards are not revealed when player is bust
- Game exits when shuffle point reached
- Game exits when chip stack > 999,999
- Game exits when chip stack == 0
- Game displays reason when exiting
- Player hand value <= 21 and > dealer hand value is win
- Player hand value <= 21 and dealer hand value > 21 is win
- Player blackjack and no dealer blackjack is win
- Player blackjack and dealer blackjack is push (draw)
- Player hand value <= 21 and == dealer hand value is push
- Player hand value <= 21 and < dealer hand value is loss
- Player hand value > 21 is loss
- Player hand value <= 21 and dealer blackjack is loss
- Player hand value > 21 and dealer hand value > 21 is not observed
- Message bar shows hand result once complete
- 2 message bars show result of each hand after split hand is complete
-
Bet * 2
is added to chip stack on win -
Bet * 3
is added to chip stack on win after doubling - No value added to chip stack on loss
-
Bet
is added to chip stack on push - Chip stack is updated according to tests above for each hand independently after split
The PEP8 linter returns quite a number of E501
(line too long) errors. While I have cleared these up as much as possible while writing the code, I have opted not to address these remaining errors as they are either interpolated strings that do not lend themselves to being wrapped across multiple lines, or because doing so would significantly reduce the readability of the code.
Additionally, there is conflicting information about whether a requirement that lines must be <=79 chars is applicable in 2021; for example, the Django documentation specifically calls out that this requirement of PEP8 should not be followed.
No errors other than E501
were detected by the linter.
Checklists for testing features
-
2 of clubs is index 0 so is falsy and interpreted as face down card by print function -
Game requests user press any key for next hand, but spacebar does not trigger new hand -
Betting full value of chip stack throws error (stack must be greater than 0) -
Entering invalid key for action during hand causes crash -
User is prompted to split when that is not a valid action -
User is prompted to double when that is not a valid action -
Double is possible after additional cards have been dealt, but should not be -
Double key is active when double is not shown as an available option -
Double is shown as available option when player has insufficient chips to double -
Split does not show additional player hand -
Player stack > 999999 causes print overflow -
Double increases bet but does not remove incremental value from stack -
Double causes crash when doubled bet value > remaining stack - Input is requested when player has blackjack and dealer does not (hand should be over)
-
Status message displays decimal on round number when winning with blackjack -
Player stack shows decimal when round number after winning with blackjack -
Game exits without warning when shoe reshuffle point is hit -
Game crashes if bet > stack is entered -
No input when bet requested results ininvalid literal for int() with base 10: ''
error message -
IndexError: list index out of range
when concatenatingaction_request_string
-
Round ends on player blackjack without checking if dealer has blackjack -
Result is push on player blackjack when dealer does not have blackjack -
Standing requires key to be pressed twice before revealing dealer cards -
Hitting on split hand deals card to main hand -
Table shows split card from previous hand at start of new hand -
Split is not permitted when split was previously done in same shoe -
Stand is permitted when only 1 card dealt to split hand -
Split hand stands after 2nd card dealt without permitting hit -
Player can have blackjack after split, but should only have 21 -
Not obvious which hand messages are referring to after split -
2 cards are dealt when hitting on split hand -
Main hand labelled as blackjack and input stopped after split -
Press Enter for new hand
message duplicated after split -
Optimal strategy advises splitting 5s -
Optimal strategy advises splitting 10s -
Optimal strategy advises hitting with 3s against dealer 5 -
Dealer cards are revealed after player is bust -
List index error when concatenating action string when standing on split hand -
Optimal strategy suggests standing on split hand with 10 vs Ace
Deployed on Heroku
- Design inspiration for the printed card layout from Stack Exchange answer, though the code was re-written entirely.
- Code for joining a string using a different final separator from Stack Overflow
- Code for stripping decimals from floats when round from Stack Overflow
- Optimal blackjack strategy table from Wizard of Odds