Skip to content

Commit 54ddfe9

Browse files
committed
init - Structure and Rule classes with tests
0 parents  commit 54ddfe9

File tree

6 files changed

+350
-0
lines changed

6 files changed

+350
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
__pycache__/
2+
.venv

constants.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# colors
2+
RED = "RED"
3+
BLUE = "BLUE"
4+
YELLOW = "YELLOW"
5+
6+
# quantities
7+
EXACTLY = "EXACTLY"
8+
AT_LEAST = "AT_LEAST"
9+
10+
# shapes
11+
PYRAMIDS = "PYRAMIDS"
12+
WEDGES = "WEDGES"
13+
BLOCKS = "BLOCKS"
14+

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pytest==5.4.1
2+
pytest-xdist==1.31.0

rule.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
"""
2+
Class to represent a zendo rules. Specifications can be found here:
3+
https://www.looneylabs.com/sites/default/files/literature/Zendo%20Rules%20Book%201.pdf
4+
"""
5+
6+
from structure import Structure
7+
from constants import (
8+
RED, BLUE, YELLOW, EXACTLY, AT_LEAST, PYRAMIDS, WEDGES, BLOCKS
9+
)
10+
11+
class Rule:
12+
"""
13+
Rules can be compromised of color, quantity, and shape.
14+
Rules must be in all caps, space separated, and follow the spelling conventions found in constants.py
15+
"""
16+
17+
def __init__(self, rule: str) -> None:
18+
"""
19+
:param rule: a string rule. Must be in format "quantity color and/or shape"
20+
21+
Examples:
22+
EXACTLY 2 RED BLOCKS
23+
EXACTLY 0 YELLOW
24+
AT_LEAST 3 PYRAMIDS
25+
"""
26+
self._is_exactly = False
27+
self._is_at_least = False
28+
self._quantity = None
29+
self._color = None
30+
self._shape = None
31+
32+
try:
33+
rule_list = rule.upper().split(" ")
34+
35+
# parse quantity
36+
if rule_list[0] not in [EXACTLY, AT_LEAST]:
37+
raise ValueError("Rules must begin with a quantity")
38+
if rule_list[0] == EXACTLY: self._is_exactly = True
39+
elif rule_list[0] == AT_LEAST: self._is_at_least = True
40+
self._quantity = int(rule_list[1])
41+
42+
# parse the color and shape
43+
if rule_list[2] in [RED, BLUE, YELLOW]:
44+
self._color = rule_list[2]
45+
if len(rule_list) == 4:
46+
self._shape = rule_list[3]
47+
elif rule_list[2] in [PYRAMIDS, WEDGES, BLOCKS]:
48+
self._shape = rule_list[2]
49+
except Exception:
50+
raise ValueError("Invalid rule")
51+
52+
@property
53+
def is_exactly(self):
54+
return self._is_exactly
55+
56+
@property
57+
def is_at_least(self):
58+
return self._is_at_least
59+
60+
@property
61+
def quantity(self):
62+
return self._quantity
63+
64+
@property
65+
def color(self):
66+
return self._color
67+
68+
@property
69+
def shape(self):
70+
return self._shape
71+
72+
def does_structure_fit_rule(self, structure: Structure) -> bool:
73+
"""
74+
Returns a boolean whether a structure matches the rule
75+
"""
76+
structure_qty = 0
77+
78+
# only specifies a color
79+
if self._color and not self._shape:
80+
if self._color == RED: structure_qty = structure.num_red
81+
elif self._color == BLUE: structure_qty = structure.num_blue
82+
elif self._color == YELLOW: structure_qty = structure.num_yellow
83+
84+
# only specifies a shape
85+
elif self._shape and not self._color:
86+
if self._shape == PYRAMIDS: structure_qty = structure.num_pyramids
87+
elif self._shape == WEDGES: structure_qty = structure.num_wedges
88+
elif self._shape == BLOCKS: structure_qty = structure.num_blocks
89+
90+
# both color and shape are given
91+
else:
92+
if self._shape == PYRAMIDS:
93+
if self._color == RED: structure_qty = structure.num_red_pyramids
94+
elif self._color == BLUE: structure_qty = structure.num_blue_pyramids
95+
elif self._color == YELLOW: structure_qty = structure.num_yellow_pyramids
96+
elif self._shape == WEDGES:
97+
if self._color == RED: structure_qty = structure.num_red_wedges
98+
elif self._color == BLUE: structure_qty = structure.num_blue_wedges
99+
elif self._color == YELLOW: structure_qty = structure.num_yellow_wedges
100+
elif self._shape == BLOCKS:
101+
if self._color == RED: structure_qty = structure.num_red_blocks
102+
elif self._color == BLUE: structure_qty = structure.num_blue_blocks
103+
elif self._color == YELLOW: structure_qty = structure.num_yellow_blocks
104+
105+
# validate quantity
106+
if (self._is_exactly and structure_qty != self._quantity) or \
107+
(self._is_at_least and structure_qty < self._quantity): return False
108+
return True

structure.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""
2+
Class to represent a zendo structure. Specifications can be found here:
3+
https://www.looneylabs.com/sites/default/files/literature/Zendo%20Rules%20Book%201.pdf
4+
"""
5+
6+
class Structure:
7+
"""
8+
Pieces: 27 pyramids, 27 wedges, 27 blocks (9 per color). Possible colors are: red, blue, yellow.
9+
"""
10+
11+
def __init__(
12+
self,
13+
num_red_pyramids: int = 0,
14+
num_red_wedges: int = 0,
15+
num_red_blocks: int = 0,
16+
num_blue_pyramids: int = 0,
17+
num_blue_wedges: int = 0,
18+
num_blue_blocks: int = 0,
19+
num_yellow_pyramids: int = 0,
20+
num_yellow_wedges: int = 0,
21+
num_yellow_blocks: int = 0,
22+
) -> None:
23+
24+
self._num_red_pyramids = num_red_pyramids
25+
self._num_red_wedges = num_red_wedges
26+
self._num_red_blocks = num_red_blocks
27+
28+
self._num_blue_pyramids = num_blue_pyramids
29+
self._num_blue_wedges = num_blue_wedges
30+
self._num_blue_blocks = num_blue_blocks
31+
32+
self._num_yellow_pyramids = num_yellow_pyramids
33+
self._num_yellow_wedges = num_yellow_wedges
34+
self._num_yellow_blocks = num_yellow_blocks
35+
36+
self._num_red = self._num_red_pyramids + self._num_red_wedges + self._num_red_blocks
37+
self._num_blue = self._num_blue_pyramids + self._num_blue_wedges + self._num_blue_blocks
38+
self._num_yellow = self._num_yellow_pyramids + self._num_yellow_wedges + self._num_yellow_blocks
39+
40+
self._num_pyramids = self._num_red_pyramids + self._num_blue_pyramids + self._num_yellow_pyramids
41+
self._num_wedges = self._num_red_wedges + self._num_blue_wedges + self._num_yellow_wedges
42+
self._num_blocks = self._num_red_blocks + self._num_blue_blocks + self._num_yellow_blocks
43+
44+
@property
45+
def num_red_pyramids(self):
46+
return self._num_red_pyramids
47+
48+
@property
49+
def num_red_wedges(self):
50+
return self._num_red_wedges
51+
52+
@property
53+
def num_red_blocks(self):
54+
return self._num_red_blocks
55+
56+
@property
57+
def num_blue_pyramids(self):
58+
return self._num_blue_pyramids
59+
60+
@property
61+
def num_blue_wedges(self):
62+
return self._num_blue_wedges
63+
64+
@property
65+
def num_blue_blocks(self):
66+
return self._num_blue_blocks
67+
68+
@property
69+
def num_yellow_pyramids(self):
70+
return self._num_yellow_pyramids
71+
72+
@property
73+
def num_yellow_wedges(self):
74+
return self._num_yellow_wedges
75+
76+
@property
77+
def num_yellow_blocks(self):
78+
return self._num_yellow_blocks
79+
80+
@property
81+
def num_red(self):
82+
return self._num_red
83+
84+
@property
85+
def num_blue(self):
86+
return self._num_blue
87+
88+
@property
89+
def num_yellow(self):
90+
return self._num_yellow
91+
92+
@property
93+
def num_pyramids(self):
94+
return self._num_pyramids
95+
96+
@property
97+
def num_wedges(self):
98+
return self._num_wedges
99+
100+
@property
101+
def num_blocks(self):
102+
return self._num_blocks

unittests.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
"""
2+
Unittests for zendo project
3+
"""
4+
from constants import BLOCKS, PYRAMIDS, RED, YELLOW
5+
import unittest
6+
7+
from structure import Structure
8+
from rule import Rule
9+
10+
11+
class TestStructure(unittest.TestCase):
12+
13+
def test_creating_structures(self):
14+
"""
15+
Tests creating a structure with various parameters
16+
"""
17+
structure = Structure(
18+
num_red_pyramids=1,
19+
num_red_wedges=2,
20+
num_red_blocks=3,
21+
num_blue_pyramids=4,
22+
num_blue_wedges=5,
23+
num_blue_blocks=6,
24+
num_yellow_pyramids=7,
25+
num_yellow_wedges=8,
26+
num_yellow_blocks=9,
27+
)
28+
self.assertEqual(structure.num_red_pyramids, 1)
29+
self.assertEqual(structure.num_red_wedges, 2)
30+
self.assertEqual(structure.num_red_blocks, 3)
31+
self.assertEqual(structure.num_blue_pyramids, 4)
32+
self.assertEqual(structure.num_blue_wedges, 5)
33+
self.assertEqual(structure.num_blue_blocks, 6)
34+
self.assertEqual(structure.num_yellow_pyramids, 7)
35+
self.assertEqual(structure.num_yellow_wedges, 8)
36+
self.assertEqual(structure.num_yellow_blocks, 9)
37+
self.assertEqual(structure.num_red, 6)
38+
self.assertEqual(structure.num_blue, 15)
39+
self.assertEqual(structure.num_yellow, 24)
40+
self.assertEqual(structure.num_pyramids, 12)
41+
self.assertEqual(structure.num_wedges, 15)
42+
self.assertEqual(structure.num_blocks, 18)
43+
44+
45+
class TestRule(unittest.TestCase):
46+
47+
def test_creating_rules(self):
48+
"""
49+
Test creating valid rules
50+
"""
51+
rule1 = Rule(rule="EXACTLY 2 RED BLOCKS")
52+
rule2 = Rule(rule="EXACTLY 0 YELLOW")
53+
rule3 = Rule(rule="AT_LEAST 3 PYRAMIDS")
54+
55+
self.assertEqual(rule1.is_exactly, True)
56+
self.assertEqual(rule2.is_exactly, True)
57+
self.assertEqual(rule3.is_exactly, False)
58+
59+
self.assertEqual(rule1.is_at_least, False)
60+
self.assertEqual(rule2.is_at_least, False)
61+
self.assertEqual(rule3.is_at_least, True)
62+
63+
self.assertEqual(rule1.quantity, 2)
64+
self.assertEqual(rule2.quantity, 0)
65+
self.assertEqual(rule3.quantity, 3)
66+
67+
self.assertEqual(rule1.color, RED)
68+
self.assertEqual(rule2.color, YELLOW)
69+
self.assertEqual(rule3.color, None)
70+
71+
self.assertEqual(rule1.shape, BLOCKS)
72+
self.assertEqual(rule2.shape, None)
73+
self.assertEqual(rule3.shape, PYRAMIDS)
74+
75+
def test_creating_invalid_rules(self):
76+
"""
77+
Test creating invalid rules
78+
"""
79+
with self.assertRaises(ValueError):
80+
Rule(rule="EXACTLY TWO RED BLOCKS")
81+
82+
with self.assertRaises(ValueError):
83+
Rule(rule="INVALID 2 RED BLOCKS")
84+
85+
def test_does_structure_fit_rule(self):
86+
"""
87+
Test if structures match a rule
88+
"""
89+
rule1 = Rule("AT_LEAST 2 BLOCKS")
90+
structure1 = Structure(
91+
num_red_blocks=0,
92+
num_blue_blocks=1,
93+
num_yellow_blocks=1,
94+
)
95+
structure2 = Structure(
96+
num_red_pyramids=5,
97+
num_red_blocks=1
98+
)
99+
self.assertTrue(rule1.does_structure_fit_rule(structure1))
100+
self.assertFalse(rule1.does_structure_fit_rule(structure2))
101+
102+
rule2 = Rule("EXACTLY 4 BLUE")
103+
structure3 = Structure(
104+
num_blue_pyramids=3,
105+
num_blue_wedges=1,
106+
num_blue_blocks=0,
107+
)
108+
structure4 = Structure(
109+
num_blue_blocks=5,
110+
)
111+
self.assertTrue(rule2.does_structure_fit_rule(structure3))
112+
self.assertFalse(rule2.does_structure_fit_rule(structure4))
113+
114+
rule3 = Rule("EXACTLY 1 RED PYRAMIDS")
115+
structure5 = Structure(
116+
num_red_pyramids=1,
117+
num_red_blocks=5,
118+
num_blue_pyramids=3,
119+
)
120+
structure6 = Structure()
121+
self.assertTrue(rule3.does_structure_fit_rule(structure5))
122+
self.assertFalse(rule3.does_structure_fit_rule(structure6))

0 commit comments

Comments
 (0)