Skip to content

Commit 0568c48

Browse files
committed
added set
1 parent b2db318 commit 0568c48

File tree

9 files changed

+295
-0
lines changed

9 files changed

+295
-0
lines changed

OUTLINE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,4 @@
3838
37. **morse**: Encrypt and decrypt text to and from two versions of Morse code.
3939
38. **rot13**: Encode and decode text by rotating the characters through a list.
4040
39. **word_search**: Find all the words hidden in the rows, columns, and diagonals in a block of text.
41+
40. **set**: Program the Set card game.

bin/chapters.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,4 @@ morse
4343
rot13
4444
#transpose
4545
word_search
46+
set

book.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ Ken Youens-Clark is a Sr. Scientific Programmer in the lab of Dr. Bonnie Hurwitz
155155
37. **morse**: Encrypt and decrypt text to and from two versions of Morse code.
156156
38. **rot13**: Encode and decode text by rotating the characters through a list.
157157
39. **word_search**: Find all the words hidden in the rows, columns, and diagonals in a block of text.
158+
40. **set**: Program the Set card game.
158159

159160
\newpage
160161

@@ -10872,6 +10873,127 @@ At line 157, I start the work of printing the revealed puzzle, iterating over th
1087210873

1087310874
\newpage
1087410875

10876+
# Chapter 40: Ready, Set, Go!
10877+
10878+
Write a Python program called `set.py` that plays the Set card game.
10879+
10880+
\newpage
10881+
10882+
## Solution
10883+
10884+
````
10885+
1 #!/usr/bin/env python3
10886+
2 """Set card game"""
10887+
3
10888+
4 import argparse
10889+
5 import os
10890+
6 import random
10891+
7 import sys
10892+
8 from itertools import product, combinations
10893+
9 from card import Card
10894+
10 from typing import List
10895+
11
10896+
12
10897+
13 # --------------------------------------------------
10898+
14 def get_args():
10899+
15 """Get command-line arguments"""
10900+
16
10901+
17 parser = argparse.ArgumentParser(
10902+
18 description='Argparse Python script',
10903+
19 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
10904+
20
10905+
21 parser.add_argument('-s',
10906+
22 '--seed',
10907+
23 help='Random seed',
10908+
24 metavar='int',
10909+
25 type=int,
10910+
26 default=None)
10911+
27
10912+
28 return parser.parse_args()
10913+
29
10914+
30
10915+
31 # --------------------------------------------------
10916+
32 def make_deck() -> List[Card]:
10917+
33 """Make Set deck"""
10918+
34
10919+
35 colors = ['Red', 'Purple', 'Green']
10920+
36 shapes = ['Oval', 'Squiggle', 'Diamond']
10921+
37 number = ['1', '2', '3']
10922+
38 shading = ['Solid', 'Striped', 'Outlined']
10923+
39
10924+
40 return list(
10925+
41 map(lambda t: Card(color=t[0], shape=t[1], number=t[2], shading=t[3]),
10926+
42 product(colors, shapes, number, shading)))
10927+
43
10928+
44
10929+
45 # --------------------------------------------------
10930+
46 def test_make_deck():
10931+
47 """Test make_deck"""
10932+
48
10933+
49 deck = make_deck()
10934+
50 assert len(deck) == 81
10935+
51
10936+
52
10937+
53 # --------------------------------------------------
10938+
54 def add(bits: List[list]) -> int:
10939+
55 """Add the bits"""
10940+
56
10941+
57 assert isinstance(bits, list)
10942+
58 assert isinstance(bits[0], list)
10943+
59
10944+
60 num_recs = len(bits)
10945+
61 num_bits = len(bits[0])
10946+
62
10947+
63 ret = []
10948+
64 for i in range(num_bits):
10949+
65 ret.append(1 if any(map(lambda n: bits[n][i], range(num_recs))) else 0)
10950+
66
10951+
67 return sum(ret)
10952+
68
10953+
69
10954+
70 # --------------------------------------------------
10955+
71 def find_set(cards: List[Card]) -> List[tuple]:
10956+
72 """Find a 'set' in a hand of cards"""
10957+
73
10958+
74 colors = list(map(lambda c: c.encode_color(), cards))
10959+
75 shapes = list(map(lambda c: c.encode_shape(), cards))
10960+
76 numbers = list(map(lambda c: c.encode_number(), cards))
10961+
77 shadings = list(map(lambda c: c.encode_shading(), cards))
10962+
78
10963+
79 sets = []
10964+
80 for combo in combinations(range(len(cards)), 3):
10965+
81 color = add(list(map(lambda i: colors[i], combo)))
10966+
82 shape = add(list(map(lambda i: shapes[i], combo)))
10967+
83 number = add(list(map(lambda i: numbers[i], combo)))
10968+
84 shading = add(list(map(lambda i: shadings[i], combo)))
10969+
85
10970+
86 if all([x in [1, 3] for x in [color, shape, number, shading]]):
10971+
87 sets.append(combo)
10972+
88
10973+
89 return sets
10974+
90
10975+
91 # --------------------------------------------------
10976+
92 def main():
10977+
93 """Make a jazz noise here"""
10978+
94
10979+
95 args = get_args()
10980+
96 deck: List[Card] = make_deck()
10981+
97
10982+
98 random.seed(args.seed)
10983+
99 cards: List[Card] = random.sample(deck, k=12)
10984+
100
10985+
101 for combo in find_set(cards):
10986+
102 print(combo)
10987+
103 print('\n'.join(map(lambda i: str(cards[i]), combo)))
10988+
104
10989+
105
10990+
106 # --------------------------------------------------
10991+
107 if __name__ == '__main__':
10992+
108 main()
10993+
````
10994+
10995+
\newpage
10996+
1087510997
# Appendix 1: argparse
1087610998

1087710999
The `argparse` module will interpret all the command-line arguments to your program. I suggest you use `argparse` for every command-line program you write so that you always have a standard way to get arguments and present help.

playful_python.pdf

3.78 KB
Binary file not shown.

set/Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.PHONY: pdf test
2+
3+
pdf:
4+
pandoc README.md -o README.pdf
5+
6+
test:
7+
pytest -v test.py

set/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Ready, Set, Go!
2+
3+
Write a Python program called `set.py` that plays the Set card game.

set/card.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from dataclasses import dataclass
2+
3+
4+
@dataclass
5+
class Card:
6+
color: str
7+
shape: str
8+
number: str
9+
shading: str
10+
11+
def __str__(self):
12+
return 'Card({})'.format(' '.join(
13+
[self.number, self.color, self.shading, self.shape]))
14+
15+
def encode_color(self):
16+
colors = ['Red', 'Purple', 'Green']
17+
return [1 if self.color == c else 0 for c in colors]
18+
19+
def encode_shape(self):
20+
shapes = ['Oval', 'Squiggle', 'Diamond']
21+
return [1 if self.shape == s else 0 for s in shapes]
22+
23+
def encode_number(self):
24+
numbers = ['1', '2', '3']
25+
return [1 if self.number == n else 0 for n in numbers]
26+
27+
def encode_shading(self):
28+
shadings = ['Solid', 'Striped', 'Outlined']
29+
return [1 if self.shading == s else 0 for s in shadings]

set/solution.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#!/usr/bin/env python3
2+
"""Set card game"""
3+
4+
import argparse
5+
import os
6+
import random
7+
import sys
8+
from itertools import product, combinations
9+
from card import Card
10+
from typing import List
11+
12+
13+
# --------------------------------------------------
14+
def get_args():
15+
"""Get command-line arguments"""
16+
17+
parser = argparse.ArgumentParser(
18+
description='Argparse Python script',
19+
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
20+
21+
parser.add_argument('-s',
22+
'--seed',
23+
help='Random seed',
24+
metavar='int',
25+
type=int,
26+
default=None)
27+
28+
return parser.parse_args()
29+
30+
31+
# --------------------------------------------------
32+
def make_deck() -> List[Card]:
33+
"""Make Set deck"""
34+
35+
colors = ['Red', 'Purple', 'Green']
36+
shapes = ['Oval', 'Squiggle', 'Diamond']
37+
number = ['1', '2', '3']
38+
shading = ['Solid', 'Striped', 'Outlined']
39+
40+
return list(
41+
map(lambda t: Card(color=t[0], shape=t[1], number=t[2], shading=t[3]),
42+
product(colors, shapes, number, shading)))
43+
44+
45+
# --------------------------------------------------
46+
def test_make_deck():
47+
"""Test make_deck"""
48+
49+
deck = make_deck()
50+
assert len(deck) == 81
51+
52+
53+
# --------------------------------------------------
54+
def add(bits: List[list]) -> int:
55+
"""Add the bits"""
56+
57+
assert isinstance(bits, list)
58+
assert isinstance(bits[0], list)
59+
60+
num_recs = len(bits)
61+
num_bits = len(bits[0])
62+
63+
ret = []
64+
for i in range(num_bits):
65+
ret.append(1 if any(map(lambda n: bits[n][i], range(num_recs))) else 0)
66+
67+
return sum(ret)
68+
69+
70+
# --------------------------------------------------
71+
def find_set(cards: List[Card]) -> List[tuple]:
72+
"""Find a 'set' in a hand of cards"""
73+
74+
colors = list(map(lambda c: c.encode_color(), cards))
75+
shapes = list(map(lambda c: c.encode_shape(), cards))
76+
numbers = list(map(lambda c: c.encode_number(), cards))
77+
shadings = list(map(lambda c: c.encode_shading(), cards))
78+
79+
sets = []
80+
for combo in combinations(range(len(cards)), 3):
81+
color = add(list(map(lambda i: colors[i], combo)))
82+
shape = add(list(map(lambda i: shapes[i], combo)))
83+
number = add(list(map(lambda i: numbers[i], combo)))
84+
shading = add(list(map(lambda i: shadings[i], combo)))
85+
86+
if all([x in [1, 3] for x in [color, shape, number, shading]]):
87+
sets.append(combo)
88+
89+
return sets
90+
91+
# --------------------------------------------------
92+
def main():
93+
"""Make a jazz noise here"""
94+
95+
args = get_args()
96+
deck: List[Card] = make_deck()
97+
98+
random.seed(args.seed)
99+
cards: List[Card] = random.sample(deck, k=12)
100+
101+
for combo in find_set(cards):
102+
print(combo)
103+
print('\n'.join(map(lambda i: str(cards[i]), combo)))
104+
105+
106+
# --------------------------------------------------
107+
if __name__ == '__main__':
108+
main()

set/test.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env python3
2+
"""tests for set.py"""
3+
4+
import os
5+
import re
6+
from subprocess import getstatusoutput
7+
8+
prg = './set.py'
9+
10+
11+
# --------------------------------------------------
12+
def test_exists():
13+
"""exists"""
14+
15+
assert os.path.isfile(prg)
16+
17+
# --------------------------------------------------
18+
def test_usage():
19+
"""usage"""
20+
21+
for flag in ['-h', '--help']:
22+
rv, out = getstatusoutput('{} {}'.format(prg, flag))
23+
assert rv == 0
24+
assert re.match("usage", out, re.IGNORECASE)

0 commit comments

Comments
 (0)