Skip to content

Instantly share code, notes, and snippets.

@louisswarren
Last active May 1, 2020 06:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save louisswarren/6da74b05f4f4ece6f10f2e37d55e0bae to your computer and use it in GitHub Desktop.
Save louisswarren/6da74b05f4f4ece6f10f2e37d55e0bae to your computer and use it in GitHub Desktop.
Blackjack
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