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
79 lines
2.1 KiB
Python
79 lines
2.1 KiB
Python
"""!
|
|
@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
|