Skip to content

Commit 8ca48e5

Browse files
committed
mostly blackjack discussion
1 parent 627695c commit 8ca48e5

File tree

7 files changed

+665
-145
lines changed

7 files changed

+665
-145
lines changed

bin/compile.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -127,31 +127,31 @@ def main():
127127
for i, dir_name in enumerate(chapter_list, 1):
128128
print('Chapter {}: {}'.format(i, dir_name))
129129
readme = os.path.join(in_dir, dir_name, 'README.md')
130-
if os.path.isfile(readme):
131-
print('\tREADME')
132-
chapter = 'Chapter {}: '.format(i)
133-
text = open(readme).read()
134-
text = re.sub(r'^#\s+', '# ' + chapter, text)
135-
fh.write(text + '\n\\newpage\n\n')
130+
assert os.path.isfile(readme)
131+
132+
print('\tREADME')
133+
chapter = 'Chapter {}: '.format(i)
134+
text = open(readme).read()
135+
text = re.sub(r'^#\s+', '# ' + chapter, text)
136+
fh.write(text + '\n\\newpage\n\n')
136137

137138
solution_py = os.path.join(in_dir, dir_name, 'solution.py')
138-
if os.path.isfile(solution_py):
139-
print('\tSOLUTION')
140-
fh.write('## Solution\n\n')
141-
fh.write('````\n')
142-
numbered = getoutput('cat -n {}'.format(solution_py))
143-
fh.write(numbered)
144-
#fh.write(open(solution_py).read())
145-
fh.write('\n````\n')
146-
fh.write('\n\\newpage\n\n')
147-
else:
148-
print('\t>>>>>>> MISSING SOLUTION <<<<<<<<\n\n')
139+
assert os.path.isfile(solution_py)
140+
141+
print('\tSOLUTION')
142+
fh.write('## Solution\n\n')
143+
fh.write('````\n')
144+
numbered = getoutput('cat -n {}'.format(solution_py))
145+
fh.write(numbered)
146+
#fh.write(open(solution_py).read())
147+
fh.write('\n````\n')
148+
fh.write('\n\\newpage\n\n')
149149

150-
solution_md = os.path.join(in_dir, dir_name, 'discussion.md')
151-
if os.path.isfile(solution_md):
150+
discussion = os.path.join(in_dir, dir_name, 'discussion.md')
151+
if os.path.isfile(discussion):
152152
print('\tDISCUSSION')
153153
fh.write('## Discussion\n\n')
154-
fh.write(open(solution_md).read())
154+
fh.write(open(discussion).read())
155155
fh.write('\n\\newpage\n\n')
156156

157157
if appendix:

blackjack/README.md

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,56 @@ optional arguments:
1616
-s int, --seed int Random seed (default: None)
1717
````
1818

19-
The program will create a deck of cards by combining symbols "H," "D," "S", and "C" for the suites "hearts," "diamonds," "spades," and "clubs," respectively, with the numbers 2-10 and the letters "A", "J", "Q," and "K". In order to pass the tests, you will need to sort your deck and then use the `random.shuffle` method so that your cards will be in the order the tests expect.
19+
If run with a `--stand` value less than 1, it should create an error:
20+
21+
````
22+
$ ./blackjack.py -S 0
23+
usage: blackjack.py [-h] [-d] [-p] [-S int] [-s int]
24+
blackjack.py: error: --stand "0" must be greater than 0
25+
````
26+
27+
The program will create a deck of cards by combining symbols Unicode symbols ♥ (heart), ♠ (club), ♣ (spade), and ♦ (diamond) with the numbers 2-10 and the letters "A", "J", "Q," and "K". In order to pass the tests, you will need to first sort your deck by suite and value before you use the `random.shuffle` method.
28+
29+
Consider making a function called `make_deck` that does nothing but create the 52 cards. You may chose to model a "card" as a simple string as I did, e.g., `♥4`, or you may prefer to use a `tuple`, e.g., `('♥', '4')`, or even a `dict`, e.g., `{'suite': '♥', 'value': '4'}`. However you define a card, add and modify this function `test_make_deck` to ensure you get back a reasonable deck:
30+
31+
````
32+
def test_make_deck():
33+
"""Test for make_deck"""
34+
35+
deck = make_deck()
36+
assert len(deck) == 52
37+
38+
num_card = re.compile(r'\d+$')
39+
for suite in '♥♠♣♦':
40+
cards = list(filter(lambda c: c[0] == suite, deck))
41+
assert len(cards) == 13
42+
num_cards = list(filter(num_card.search, cards))
43+
assert len(num_cards) == 9
44+
````
2045

2146
To deal, keep in mind how cards are actually dealt -- first one card to each of the players, then one to the dealer, then the players, then the dealer, etc. You might be tempted to use `random.choice` or something like that to select your cards, but you need to keep in mind that you are modeling an actual deck and so selected cards should no longer be present in the deck. If the `--player_hits` flag is present, deal an additional card to the player; likewise with the `--dealer_hits` flag.
2247

23-
When the program runs with no arguments, display the dealer and players hand along with a sum of the values of the cards. In Blackjack, number cards are worth their value, face cards are worth 10, and the Ace will be worth 1 for our game (though in the real game it can alternate between 1 and 11).
48+
In Blackjack, number cards are worth their numeric value, face cards are worth 10, and the Ace will be worth 1 for our game (though in the real game it can alternate between 1 and 11). Consider writing a function called `card_value` will return a `int` value of the card's worth. Add this `test_card_value` function:
49+
50+
````
51+
def test_card_value():
52+
"""Test card_value"""
53+
54+
assert card_value('♥A') == 1
55+
56+
for face in 'JQK':
57+
assert card_value('♦' + face) == 10
58+
59+
for num in range(2, 11):
60+
assert card_value('♠' + str(num)) == num
61+
````
62+
63+
When the program runs with no arguments, display the dealer and players hand along with a sum of the values of the cards.
2464

2565
````
2666
$ ./blackjack.py -s 1
27-
Dealer [15]: HJ C5
28-
Player [10]: C9 SA
67+
Dealer [15]: ♥J ♠5
68+
Player [10]: ♠9 ♦A
2969
Dealer should hit.
3070
Player should hit.
3171
````
@@ -34,8 +74,8 @@ Here we see that both the dealer and player fall below the `--stand` value of `1
3474

3575
````
3676
$ ./blackjack.py -s 1 -d -p
37-
Dealer [23]: HJ C5 C8
38-
Player [14]: C9 SA D4
77+
Dealer [23]: ♥J ♠5 ♠8
78+
Player [14]: ♠9 ♦A ♣4
3979
Dealer busts.
4080
````
4181

@@ -45,24 +85,23 @@ If we run with a different seed, we see different results:
4585

4686
````
4787
$ ./blackjack.py -s 3
48-
Dealer [19]: HK C9
49-
Player [12]: D3 H9
88+
Dealer [19]: ♥K ♠9
89+
Player [12]: ♣3 ♥9
5090
Player should hit.
5191
````
5292

5393
Here the dealer is recommended to stand because they have more than 18. Run with a higher `--stand` to change that:
5494

5595
````
5696
$ ./blackjack.py -s 3 -S 20
57-
Dealer [19]: HK C9
58-
Player [12]: D3 H9
97+
Dealer [19]: ♥K ♠9
98+
Player [12]: ♣3 ♥9
5999
Dealer should hit.
60100
Player should hit.
61101
````
62102

63103
Now the dealer is recommended to hit, which seems unwise.
64104

65-
66105
After dealing all the required cards and displaying the hands, the code should do (in order):
67106

68107
1. Check if the player has more than 21; if so, print 'Player busts! You lose, loser!' and `exit(0)`
@@ -73,4 +112,5 @@ After dealing all the required cards and displaying the hands, the code should d
73112

74113
Hints:
75114

76-
* Use `itertools.product` to combine the suites and cards to make your deck.
115+
* Use `parser.error` in `argparse` to create the error for a bad `--stand` value
116+
* Use `itertools.product` to combine the suites and cards to make your deck.

blackjack/discussion.md

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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

Comments
 (0)