├── dealer.py ├── deck.py ├── README.md ├── card.py ├── hand.py ├── player.py ├── strategy.py └── game.py /dealer.py: -------------------------------------------------------------------------------- 1 | from hand import Hand 2 | from player import Player 3 | 4 | 5 | class Dealer(Player): 6 | def __init__(self): 7 | self.hand = Hand() 8 | self.name = "Dealer" 9 | self.bet = 0 10 | self.bankroll = 1e7 11 | 12 | def play_hand(self): 13 | if self.hand.value() < 17: 14 | return 'hit' 15 | return 'stand' 16 | 17 | def __str__(self): 18 | return "Dealer" 19 | 20 | 21 | -------------------------------------------------------------------------------- /deck.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from card import Card 4 | 5 | class Deck(): 6 | def __init__(self): 7 | self.cards = [] 8 | 9 | for suit in Card.SUITS: 10 | for face in Card.FACES: 11 | self.cards.append(Card(suit, face)) 12 | 13 | random.shuffle(self.cards) 14 | 15 | def draw(self): 16 | if self.cards: 17 | return self.cards.pop() 18 | # reshuffle. 19 | newdeck = Deck() 20 | 21 | self.cards = newdeck.cards 22 | return self.draw() 23 | 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Blackjack with Basic Strategy 2 | ============================= 3 | 4 | This is a simple console version of Blackjack written in Python. 5 | 6 | * Supports 1-6 players 7 | * Hit / Stand / Double implemented correctly. 8 | * No Splitting / Surrender 9 | * Aces can be soft or hard. 10 | * The application will advise you on what move you should make based on [basic strategy in Blackjack](http://wizardofodds.com/games/blackjack/strategy/4-decks/). 11 | 12 | This was created for [Insight's Data Engineering Fellows Program](http://insightdataengineering.com/). 13 | 14 | ## Instructions 15 | 16 | Run game.py from the Python shell. Works with Python 2.7 17 | -------------------------------------------------------------------------------- /card.py: -------------------------------------------------------------------------------- 1 | class Card(): 2 | # these unicode code points are the suits. 3 | SUITS_HUMAN = [u"\u2660", u"\u2665", u"\u2666", u"\u2663"] 4 | FACES_HUMAN = ['A'] + range(2, 11) + ['J', 'Q', 'K'] 5 | 6 | SUITS = range(0, 4) 7 | FACES = range(0, 13) 8 | 9 | def __init__(self, suit, face): 10 | self.suit = suit 11 | self.face = face 12 | 13 | def is_ace(self): 14 | return self.face == 0 15 | 16 | def value(self): 17 | # faces are 0 indexed 18 | return min(self.face, 9) + 1 19 | 20 | def __unicode__(self): 21 | return u"%s%s" % (self.FACES_HUMAN[self.face], self.SUITS_HUMAN[self.suit]) 22 | 23 | def __str__(self): 24 | return unicode(self).encode('utf-8') 25 | 26 | 27 | -------------------------------------------------------------------------------- /hand.py: -------------------------------------------------------------------------------- 1 | class Hand(): 2 | def __init__(self): 3 | self.cards = [] 4 | 5 | def is_soft(self): 6 | value = 0 7 | has_ace = False 8 | 9 | for card in self.cards: 10 | if card.is_ace(): 11 | has_ace = True 12 | 13 | value += card.value() 14 | 15 | return has_ace and value <= 11 16 | 17 | def value(self): 18 | value = 0 19 | has_ace = False 20 | 21 | for card in self.cards: 22 | value += card.value() 23 | 24 | if card.is_ace(): 25 | has_ace = True 26 | 27 | if has_ace and value <= 11: 28 | value += 10 29 | 30 | return value 31 | 32 | def __unicode__(self): 33 | soft = ' soft ' if self.is_soft() else ' ' 34 | 35 | return u"[%s] (value:%s%s)" % (', '.join(map(unicode, self.cards)), soft, self.value()) 36 | 37 | def __str__(self): 38 | return unicode(self).encode('utf-8') 39 | 40 | 41 | -------------------------------------------------------------------------------- /player.py: -------------------------------------------------------------------------------- 1 | from hand import Hand 2 | from strategy import basic_strategy 3 | 4 | class Player(): 5 | def __init__(self, name, bankroll): 6 | self.hand = Hand() 7 | self.bankroll = bankroll 8 | self.name = name 9 | self.bet = 0 10 | 11 | def play_hand(self, dealer_card): 12 | while True: 13 | recommend = basic_strategy(self.hand.value(), dealer_card.value(), self.hand.is_soft()) 14 | 15 | if len(self.hand.cards) == 2: 16 | move = raw_input("%s: %s - hit/stand/double? (we recommend %s) " % (self.name, self.hand, recommend)).lower() 17 | else: 18 | # for simplicity. This is not true for soft 18. 19 | if recommend == 'double': 20 | recommend = 'hit' 21 | 22 | move = raw_input("%s: %s - hit/stand? (we recommend %s) " % (self.name, self.hand, recommend)).lower() 23 | 24 | if move.startswith('h'): 25 | return 'hit' 26 | elif move.startswith('s'): 27 | return 'stand' 28 | elif move.startswith('d'): 29 | return 'double' 30 | 31 | print "Invalid move" 32 | 33 | def __str__(self): 34 | return '(%s, $%s)' % (self.name, self.bankroll) 35 | 36 | def is_bust(self): 37 | return self.hand.value() > 21 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /strategy.py: -------------------------------------------------------------------------------- 1 | def basic_strategy(player_total, dealer_value, soft): 2 | """ This is a simple implementation of Blackjack's 3 | basic strategy. It is used to recommend actions 4 | for the player. """ 5 | 6 | if 4 <= player_total <= 8: 7 | return 'hit' 8 | if player_total == 9: 9 | if dealer_value in [1,2,7,8,9,10]: 10 | return 'hit' 11 | return 'double' 12 | if player_total == 10: 13 | if dealer_value in [1, 10]: 14 | return 'hit' 15 | return 'double' 16 | if player_total == 11: 17 | if dealer_value == 1: 18 | return 'hit' 19 | return 'double' 20 | if soft: 21 | #we only double soft 12 because there's no splitting 22 | if player_total in [12, 13, 14]: 23 | if dealer_value in [5, 6]: 24 | return 'double' 25 | return 'hit' 26 | if player_total in [15, 16]: 27 | if dealer_value in [4, 5, 6]: 28 | return 'double' 29 | return 'hit' 30 | if player_total == 17: 31 | if dealer_value in [3, 4, 5, 6]: 32 | return 'double' 33 | return 'hit' 34 | if player_total == 18: 35 | if dealer_value in [3, 4, 5, 6]: 36 | return 'double' 37 | if dealer_value in [2, 7, 8]: 38 | return 'stand' 39 | return 'hit' 40 | if player_total >= 19: 41 | return 'stand' 42 | 43 | else: 44 | if player_total == 12: 45 | if dealer_value in [1, 2, 3, 7, 8, 9, 10]: 46 | return 'hit' 47 | return 'stand' 48 | if player_total in [13, 14, 15, 16]: 49 | if dealer_value in [2, 3, 4, 5, 6]: 50 | return 'stand' 51 | return 'hit' 52 | 53 | if player_total >= 17: 54 | return 'stand' 55 | 56 | -------------------------------------------------------------------------------- /game.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from dealer import Dealer 4 | from deck import Deck 5 | from player import Player 6 | 7 | class Game(): 8 | def __init__(self): 9 | self.deck = Deck() 10 | 11 | self.players = [] 12 | 13 | while True: 14 | try: 15 | num_players = int(raw_input('Enter number of players (1-6): ')) 16 | except ValueError: 17 | continue 18 | 19 | if num_players < 1 or num_players > 6: 20 | print "Invalid number of players" 21 | continue 22 | 23 | for i in range(num_players): 24 | name = raw_input('Enter name of player %d: ' % (i + 1)) 25 | bankroll = random.choice(range(100, 1001, 100)) 26 | 27 | self.players.append(Player(name, bankroll)) 28 | 29 | print "Welcome %s, you have a starting bankroll of $%s" % (name, bankroll) 30 | break 31 | 32 | 33 | self.dealer = Dealer() 34 | 35 | def start_round(self): 36 | """ place bets 37 | deal cards 38 | print cards """ 39 | 40 | for player in self.players: 41 | if player.bankroll > 0: 42 | while True: 43 | try: 44 | player.bet = int(raw_input("%s: Your bankroll is $%s. Enter your bet: " % (player.name, player.bankroll))) 45 | except ValueError: 46 | continue 47 | 48 | if player.bet <= 0: 49 | # abort the game. 50 | player.bet = 0 51 | break 52 | 53 | if player.bet <= player.bankroll: 54 | break 55 | 56 | print "Your bet cannot be larger than %s" % (player.bankroll) 57 | else: 58 | print "%s you have no more bankroll" % (player.name) 59 | 60 | 61 | for i in range(2): 62 | for player in self.players: 63 | if player.bet: 64 | player.hand.cards.append(self.deck.draw()) 65 | 66 | self.dealer.hand.cards.append(self.deck.draw()) 67 | 68 | for player in self.players: 69 | if player.bet: 70 | print "%s - $%s bet: %s" % (player.name, player.bet, player.hand) 71 | 72 | print "Dealer - %s, [face down card]" % (self.dealer.hand.cards[0]) 73 | 74 | 75 | def play_round(self): 76 | """ play out each player & dealer's hand. 77 | give out rewards. """ 78 | 79 | for player in self.players: 80 | while player.bet and not player.is_bust(): 81 | move = player.play_hand(self.dealer.hand.cards[0]) 82 | 83 | if move in ['hit', 'double']: 84 | if move == 'double': 85 | if len(player.hand.cards) != 2: 86 | print 'You cannot double now!' 87 | continue 88 | 89 | if player.bankroll < 2 * player.bet: 90 | print '%s, your bankroll was too small, so you doubled for $%s' % (player.name, player.bankroll - player.bet) 91 | 92 | player.bet += player.bankroll - player.bet 93 | else: 94 | player.bet *= 2 95 | 96 | card = self.deck.draw() 97 | player.hand.cards.append(card) 98 | 99 | print "%s drew a %s." % (player.name, card) 100 | 101 | if player.is_bust(): 102 | player.bankroll -= player.bet 103 | self.dealer.bankroll += player.bet 104 | 105 | player.bet = 0 106 | 107 | print "Sorry %s, you busted! %s . Your bankroll is now $%s" % (player.name, player.hand, player.bankroll) 108 | break 109 | 110 | 111 | 112 | elif move == 'stand': 113 | break 114 | 115 | print "%s - $%s bet: %s" % (player.name, player.bet, player.hand) 116 | 117 | # you only get one card on a double. 118 | if move == 'double': 119 | break 120 | 121 | print "Dealer reveals - %s" % (self.dealer.hand) 122 | 123 | while not self.dealer.is_bust(): 124 | move = self.dealer.play_hand() 125 | 126 | if move == 'hit': 127 | card = self.deck.draw() 128 | self.dealer.hand.cards.append(card) 129 | print "Dealer drew a %s." % (card) 130 | elif move == 'stand': 131 | break 132 | 133 | print "Dealer - %s" % (self.dealer.hand) 134 | 135 | if self.dealer.is_bust(): 136 | print "The dealer busted!" 137 | for player in self.players: 138 | if player.bet: 139 | player.bankroll += player.bet 140 | self.dealer.bankroll -= player.bet 141 | 142 | print "%s wins $%s!" % (player.name, player.bet) 143 | 144 | else: 145 | for player in self.players: 146 | if player.bet: 147 | if player.hand.value() > self.dealer.hand.value(): 148 | print "%s wins $%s!" % (player.name, player.bet) 149 | player.bankroll += player.bet 150 | self.dealer.bankroll -= player.bet 151 | 152 | elif player.hand.value() < self.dealer.hand.value(): 153 | print "%s loses $%s." % (player.name, player.bet) 154 | player.bankroll -= player.bet 155 | self.dealer.bankroll += player.bet 156 | else: 157 | print "%s splits with the dealer." % (player.name) 158 | 159 | def end_round(self): 160 | """ reset player bets, cards and check if game continues """ 161 | # reset round. 162 | for player in self.players: 163 | player.bet = 0 164 | player.hand.cards = [] 165 | 166 | self.dealer.hand.cards = [] 167 | 168 | 169 | while True: 170 | move = raw_input('Keep Going? (y/n): ').lower() 171 | 172 | if move.startswith('y'): 173 | return True 174 | elif move.startswith('n'): 175 | return False 176 | 177 | 178 | def main(): 179 | print ">>> Welcome to V's Blackjack Table. Advice is given out for free <<< \n" 180 | 181 | game = Game() 182 | 183 | while True: 184 | game.start_round() 185 | game.play_round() 186 | keep_going = game.end_round() 187 | 188 | if not keep_going: 189 | break 190 | print "\n>>> Thanks for playing at V\'s Casino! <<<" 191 | 192 | 193 | if __name__ == '__main__': 194 | main() 195 | --------------------------------------------------------------------------------