Last active
May 1, 2020 06:16
-
-
Save louisswarren/6da74b05f4f4ece6f10f2e37d55e0bae to your computer and use it in GitHub Desktop.
Blackjack
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from collections import namedtuple | |
from enum import Enum | |
import random | |
compose = lambda f: lambda g: lambda *a, **k: f(g(*a, **k)) | |
class Face(Enum): | |
ACE = 'A' | |
TWO = '2' | |
THREE = '3' | |
FOUR = '4' | |
FIVE = '5' | |
SIX = '6' | |
SEVEN = '7' | |
EIGHT = '8' | |
NINE = '9' | |
TEN = 'X' | |
JACK = 'J' | |
QUEEN = 'Q' | |
KING = 'K' | |
class Suit(Enum): | |
SPADE = '♠' | |
HEART = '♥' | |
DIAMOND = '♦' | |
CLUB = '♣' | |
class Card(namedtuple('Card', 'face suit')): | |
def __str__(self): | |
return self.face.value + self.suit.value | |
@property | |
def score(self): | |
if self.face == Face.ACE: | |
return 11 | |
elif self.face in (Face.TEN, Face.JACK, Face.QUEEN, Face.KING): | |
return 10 | |
else: | |
return int(self.face.value) | |
full_deck = {f.name.lower() + '_of_' + s.name.lower() + 's' : Card(f, s) | |
for f in Face for s in Suit} | |
class Move(Enum): | |
HIT = 'hit' | |
STAY = 'stay' | |
class Result(Enum): | |
BLACKJACK = 'blackjack' | |
PUSH = 'push' | |
BUST = 'bust' | |
WIN = 'win' | |
LOSE = 'lose' | |
class Gamestate(namedtuple('Gamestate', 'dealer player')): | |
@compose(''.join) | |
def __str__(self): | |
if self.dealer[0] is None: | |
yield '-- ' | |
yield ' '.join(str(card) for card in self.dealer[1:]) | |
else: | |
yield ' '.join(str(card) for card in self.dealer) | |
yield ' ({})'.format(hand_score(self.dealer)) | |
yield '\n' | |
yield ' '.join(str(card) for card in self.player) | |
yield ' ({})'.format(hand_score(self.player)) | |
def deck_stream(num_decks=1): | |
deck = list(full_deck.values()) * num_decks | |
while True: | |
random.shuffle(deck) | |
yield from deck | |
def hand_score(hand): | |
have_ace = False | |
total = 0 | |
for card in hand: | |
if card.face == Face.ACE: | |
have_ace = True | |
total += 1 | |
else: | |
total += card.score | |
if have_ace and total <= 11: | |
total += 10 | |
return total | |
def blackjack(deck): | |
player = [next(deck), next(deck)] | |
dealer = [next(deck), next(deck)] | |
if hand_score(player) == 21: | |
yield Gamestate(dealer, player), () | |
if hand_score(dealer) == 21: | |
return Result.PUSH | |
else: | |
return Result.BLACKJACK | |
while hand_score(player) < 21: | |
move = yield Gamestate((None, *dealer[1:]), player), tuple(Move) | |
if move == Move.HIT: | |
player.append(next(deck)) | |
else: | |
assert(move == Move.STAY) | |
break | |
if hand_score(player) > 21: | |
yield Gamestate((None, *dealer[1:]), player), () | |
return Result.BUST | |
yield Gamestate(dealer, player), () | |
if hand_score(dealer) == 21: | |
return Result.LOSE | |
while hand_score(dealer) < 17: | |
dealer.append(next(deck)) | |
yield Gamestate(dealer, player), () | |
if hand_score(dealer) > 21: | |
return Result.WIN | |
if hand_score(player) > hand_score(dealer): | |
return Result.WIN | |
elif hand_score(player) < hand_score(dealer): | |
return Result.LOSE | |
else: | |
return Result.PUSH | |
# Move this | |
move_keys = {Move.HIT: 'h', Move.STAY: 's'} | |
key_binds = {v: k for k, v in move_keys.items()} | |
key_binds[''] = Move.STAY | |
def input_move(choices): | |
prompt = ''.join('[{}] {:<8s}'.format(move_keys[choice], choice.value) | |
for choice in choices) + '> ' | |
while True: | |
response = input(prompt) | |
if response in key_binds: | |
return key_binds[response] | |
def play(num_decks=1): | |
deck = deck_stream(num_decks) | |
g = blackjack(deck) | |
state, moves = next(g) | |
try: | |
while True: | |
print() | |
print(state) | |
if moves: | |
move = input_move(moves) | |
assert(move in moves) | |
else: | |
move = None | |
state, moves = g.send(move) | |
except StopIteration as e: | |
print(e) | |
if __name__ == '__main__': | |
try: | |
while True: | |
play() | |
print() | |
print('-' * 80) | |
print() | |
except EOFError: | |
pass |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment