|
| 1 | +By using `argparse`, we can define all the parameters for the program along with reasonable defaults. The two flags for `--dealer_hits` and `--player_hits` will be `False` by default. The defaults for `--stand` and `--seed` will be 18 and `None`, respetively, and any values the user provides must be `int` values. |
| 2 | + |
| 3 | +## Making a deck |
| 4 | + |
| 5 | +As mentioned in the intro, I chose to model my cards as strings where the suite is the first character and the value is the rest of the string, e.g., `♣10` is the 10 of clubs. I can create a `list` with the four Unicode strings for the suites: |
| 6 | + |
| 7 | +```` |
| 8 | +>>> suites = list('♥♠♣♦') |
| 9 | +>>> suites |
| 10 | +['♥', '♠', '♣', '♦'] |
| 11 | +```` |
| 12 | + |
| 13 | +For the number values 2-10, I can use `range(2, 11)` (remembering that the upper limit is not inclusive), but I need to turn all these into `str` values, so I can `map` them into that function: |
| 14 | + |
| 15 | +```` |
| 16 | +>>> list(map(str, range(2, 11))) |
| 17 | +['2', '3', '4', '5', '6', '7', '8', '9', '10'] |
| 18 | +```` |
| 19 | + |
| 20 | +I can use the `+` operator to join that `list` to one with the face cards "A" (Ace), "J" (Jack), "Q" (Queen), and "K" ("King"): |
| 21 | + |
| 22 | + |
| 23 | +This will produce a `list` of 52 tuples like () |
| 24 | + |
| 25 | +```` |
| 26 | +>>> values = list(map(str, range(2, 11))) + list('AJQK') |
| 27 | +>>> values |
| 28 | +['2', '3', '4', '5', '6', '7', '8', '9', '10', 'A', 'J', 'Q', 'K'] |
| 29 | +```` |
| 30 | + |
| 31 | +I use `itertools.product` to cross the `suites` and `values` and get a `list` of 52 (4 suites times 13 values) which are tuples containing the suite and value: |
| 32 | + |
| 33 | +```` |
| 34 | +>>> from itertools import product |
| 35 | +>>> cards = list(product(suites, values)) |
| 36 | +>>> len(cards) |
| 37 | +52 |
| 38 | +>>> cards[0] |
| 39 | +('♥', '2') |
| 40 | +```` |
| 41 | + |
| 42 | +Since I want those to be strings, I can `map` them into the function `''.join` to make them strings: |
| 43 | + |
| 44 | +```` |
| 45 | +>>> cards = list(map(''.join, product(suites, values))) |
| 46 | +>>> cards[0] |
| 47 | +'♥2' |
| 48 | +```` |
| 49 | + |
| 50 | +Finally I can put `sorted` around all this to order them before shuffling. (This is only for the purposes of testing so that our decks will be in the same order so that we will draw the same cards.) |
| 51 | + |
| 52 | +```` |
| 53 | +>>> cards = list(sorted(map(''.join, product(suites, values)))) |
| 54 | +>>> cards[0] |
| 55 | +'♠10' |
| 56 | +```` |
| 57 | + |
| 58 | +I chose to call `random.shuffle` in my `make_deck` function. It's important to note the difference between how `sorted` *returns a new list* and `random.shuffle` *mutates the list in-place |
| 59 | + |
| 60 | +```` |
| 61 | +>>> random.shuffle(cards) |
| 62 | +>>> cards[0] |
| 63 | +'♥J' |
| 64 | +```` |
| 65 | + |
| 66 | +As suggested, all this code goes into a function called `make_deck` and I use the `test_make_deck` function in the intro to check it with `pytest`. |
| 67 | + |
| 68 | +## Playing the game |
| 69 | + |
| 70 | +To start the game, I need to deal one card to the player, one to the dealer, one to the player, one to the dealer. Since the `cards` is a `list`, will use `pop` to remove a card from the deck. You cannot use the `random` module's `choice` or `sample` functions because the card would not be removed from the deck and so could be dealt again. |
| 71 | + |
| 72 | +```` |
| 73 | +>>> p1, d1, p2, d2 = cards.pop(), cards.pop(), cards.pop(), cards.pop() |
| 74 | +```` |
| 75 | + |
| 76 | +Note that `list.pop` by default removes elements from the *end* of the list. If you want to remove those at another position, you can provide an index, e.g., `0` to indicate the beginning of the list. It doesn't matter which end of `cards` we draw from, just so long as we both agree to draw from the same side. |
| 77 | + |
| 78 | +I use a `list` to model each players "hand": |
| 79 | + |
| 80 | +```` |
| 81 | +>>> player = [p1, p2] |
| 82 | +>>> dealer = [d1, d2] |
| 83 | +>>> player |
| 84 | +['♦10', '♣6'] |
| 85 | +>>> dealer |
| 86 | +['♥2', '♠10'] |
| 87 | +```` |
| 88 | + |
| 89 | +If the flags to hit the player or dealer are present, I `append` the results of using `pop` on the deck |
| 90 | + |
| 91 | +```` |
| 92 | +>>> player_hits = True |
| 93 | +>>> dealer_hits = False |
| 94 | +>>> if player_hits: |
| 95 | +... player.append(cards.pop()) |
| 96 | +... |
| 97 | +>>> if dealer_hits: |
| 98 | +... dealer.append(cards.pop()) |
| 99 | +... |
| 100 | +>>> player |
| 101 | +['♦10', '♣6', '♦7'] |
| 102 | +>>> dealer |
| 103 | +['♥2', '♠10'] |
| 104 | +```` |
| 105 | + |
| 106 | +## Figuring hand value |
| 107 | + |
| 108 | +So, given a list like `['♦10', '♣6', '♦7']` how do we add up the values of the cards? I chose to create a `card_value` function that gives me the value of any one card. I start off using a dictionary comprehension to associtate the string value of the integer cards to their integer values: |
| 109 | + |
| 110 | +```` |
| 111 | +>>> vals = {str(i): i for i in range(2, 11)} |
| 112 | +>>> vals |
| 113 | +{'2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '10': 10} |
| 114 | +```` |
| 115 | + |
| 116 | +I can then `update` the dictionary with *another dictionary* of the face values, which is pretty cool: |
| 117 | + |
| 118 | +```` |
| 119 | +>>> vals.update({'A': 1, 'J': 10, 'Q': 10, 'K': 10}) |
| 120 | +>>> from pprint import pprint as pp |
| 121 | +>>> pp(vals) |
| 122 | +{'10': 10, |
| 123 | + '2': 2, |
| 124 | + '3': 3, |
| 125 | + '4': 4, |
| 126 | + '5': 5, |
| 127 | + '6': 6, |
| 128 | + '7': 7, |
| 129 | + '8': 8, |
| 130 | + '9': 9, |
| 131 | + 'A': 1, |
| 132 | + 'J': 10, |
| 133 | + 'K': 10, |
| 134 | + 'Q': 10} |
| 135 | +```` |
| 136 | + |
| 137 | +I expect the card has the suite as the first character, so the "value" of the card is anything *after* the first character: |
| 138 | + |
| 139 | +```` |
| 140 | +>>> card = '♦10' |
| 141 | +>>> val = card[1:] |
| 142 | +>>> val |
| 143 | +'10' |
| 144 | +```` |
| 145 | + |
| 146 | +I then `assert` that the value is something in my `vals` dict: |
| 147 | + |
| 148 | +```` |
| 149 | +>>> assert val in vals |
| 150 | +```` |
| 151 | + |
| 152 | +The `assert` function returns nothing when it works; otherwise it throws an exception that would halt the program completely, which I actually want. If I were to have passed something that's not actually a "card" to the program, something is definitely wrong and needs to be fixed! |
| 153 | + |
| 154 | +```` |
| 155 | +>>> assert 'X' in vals |
| 156 | +Traceback (most recent call last): |
| 157 | + File "<stdin>", line 1, in <module> |
| 158 | +AssertionError |
| 159 | +```` |
| 160 | + |
| 161 | +If all goes well, I can `return` the value: |
| 162 | + |
| 163 | +```` |
| 164 | +>>> def card_value(card): |
| 165 | +... vals = {str(i): i for i in range(2, 11)} |
| 166 | +... vals.update({'A': 1, 'J': 10, 'Q': 10, 'K': 10}) |
| 167 | +... val = card[1:] |
| 168 | +... assert val in vals |
| 169 | +... return vals[val] |
| 170 | +... |
| 171 | +```` |
| 172 | + |
| 173 | +I can test it manually and with the `test_card_value` provided in the intro: |
| 174 | + |
| 175 | +```` |
| 176 | +>>> card_value('♦10') |
| 177 | +10 |
| 178 | +>>> card_value('♦A') |
| 179 | +1 |
| 180 | +```` |
| 181 | + |
| 182 | +## Adding the card values |
| 183 | + |
| 184 | +So to return to our question of how to add the values in a hand like `['♦10', '♣6', '♦7']`, we can `map` each card into our `card_value` function and then `sum` them: |
| 185 | + |
| 186 | +```` |
| 187 | +>>> player |
| 188 | +['♦10', '♣6', '♦7'] |
| 189 | +>>> sum(map(card_value, player)) |
| 190 | +23 |
| 191 | +```` |
| 192 | + |
| 193 | +## Printing the hands and outcomes |
| 194 | + |
| 195 | +I can print out each of the hands and their values: |
| 196 | + |
| 197 | +```` |
| 198 | +>>> player_hand = sum(map(card_value, player)) |
| 199 | +>>> dealer_hand = sum(map(card_value, dealer)) |
| 200 | +>>> print('Dealer [{:2}]: {}'.format(dealer_hand, ' '.join(dealer))) |
| 201 | +Dealer [12]: ♥2 ♠10 |
| 202 | +>>> print('Player [{:2}]: {}'.format(player_hand, ' '.join(player))) |
| 203 | +Player [23]: ♦10 ♣6 ♦7 |
| 204 | +```` |
| 205 | + |
| 206 | +After printing the hands, I go through the checklist in the intro, checking who went "bust," who got 21, who should hit, and so forth. |
0 commit comments