Add Blackjack game: rules, card rendering, game loop and tests
Implement the playable console game for gateway MIL-002. - rules.py: deal_card, calculate_score (blackjack = 0, aces drop 11 -> 1) and compare with an Outcome enum; no console I/O - display.py: hand rendering as plain-text rank plus suit emoji (keycap emoji rendered as boxes in common terminals), score formatting and outcome messages - art.py: course logo shown at the start of every game - game.py: play_game (hit/stand, dealer draws below 17), play (restart loop, console clear, logo) and ask_yes_no with re-prompt; read, write and draw are injected so games can be scripted - __main__.py: python -m blackjack entry point, exits cleanly on Ctrl+C/Ctrl+D - constants.py: card ranks and suits, prompts and messages - tests: 32 new unit and scripted end-to-end tests (35 in total) - README: run instructions for python -m blackjack Closes #7 Closes #8 Closes #9 Closes #10 Closes #11 Closes #12 Closes #13 Closes #14 Task: MIL-002#1 Task: MIL-002#2 Task: MIL-002#3 Task: MIL-002#4 Task: MIL-002#5 Task: MIL-002#6 Task: MIL-002#7 Task: MIL-002#8
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
"""!
|
||||
@file rules.py
|
||||
@brief Blackjack house rules as pure functions; no console input or output.
|
||||
"""
|
||||
|
||||
import random
|
||||
from collections.abc import Sequence
|
||||
from enum import Enum
|
||||
|
||||
from blackjack.constants import (
|
||||
ACE_HIGH,
|
||||
ACE_LOW,
|
||||
BLACKJACK_MARKER,
|
||||
BLACKJACK_SCORE,
|
||||
CARDS,
|
||||
OPENING_HAND_SIZE,
|
||||
)
|
||||
|
||||
|
||||
class Outcome(Enum):
|
||||
"""!
|
||||
@brief How a game ended, seen from the player.
|
||||
"""
|
||||
|
||||
DRAW = "draw"
|
||||
WIN = "win"
|
||||
WIN_BLACKJACK = "win_blackjack"
|
||||
WIN_DEALER_BUST = "win_dealer_bust"
|
||||
LOSE = "lose"
|
||||
LOSE_DEALER_BLACKJACK = "lose_dealer_blackjack"
|
||||
LOSE_BUST = "lose_bust"
|
||||
|
||||
|
||||
def deal_card() -> int:
|
||||
"""!
|
||||
@brief Draw one card from the unlimited deck.
|
||||
@return A card value; 11 is the ace and 10 covers 10, Jack, Queen and King.
|
||||
"""
|
||||
return random.choice(CARDS)
|
||||
|
||||
|
||||
def calculate_score(cards: Sequence[int]) -> int:
|
||||
"""!
|
||||
@brief Score a hand.
|
||||
@param cards The card values in the hand.
|
||||
@return The total, or 0 for a blackjack (two cards: an ace and a 10).
|
||||
An ace counts 11 unless that would bust the hand, then it counts 1.
|
||||
"""
|
||||
hand = list(cards)
|
||||
total = sum(hand)
|
||||
if len(hand) == OPENING_HAND_SIZE and total == BLACKJACK_SCORE:
|
||||
return BLACKJACK_MARKER
|
||||
while total > BLACKJACK_SCORE and ACE_HIGH in hand:
|
||||
hand[hand.index(ACE_HIGH)] = ACE_LOW
|
||||
total = sum(hand)
|
||||
return total
|
||||
|
||||
|
||||
def compare(user_score: int, computer_score: int) -> Outcome:
|
||||
"""!
|
||||
@brief Decide who won.
|
||||
@param user_score The player's score (0 means blackjack).
|
||||
@param computer_score The dealer's score (0 means blackjack).
|
||||
@return The outcome seen from the player.
|
||||
"""
|
||||
if user_score == computer_score:
|
||||
return Outcome.DRAW
|
||||
if computer_score == BLACKJACK_MARKER:
|
||||
return Outcome.LOSE_DEALER_BLACKJACK
|
||||
if user_score == BLACKJACK_MARKER:
|
||||
return Outcome.WIN_BLACKJACK
|
||||
if user_score > BLACKJACK_SCORE:
|
||||
return Outcome.LOSE_BUST
|
||||
if computer_score > BLACKJACK_SCORE:
|
||||
return Outcome.WIN_DEALER_BUST
|
||||
if user_score > computer_score:
|
||||
return Outcome.WIN
|
||||
return Outcome.LOSE
|
||||
Reference in New Issue
Block a user