-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmodels.py
355 lines (324 loc) · 12.6 KB
/
models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
from enum import Enum, auto
from colors import Color
class Pytromino:
"""An object to represent a block of squares
"""
class Types(Enum):
I = auto()
O = auto()
L = auto()
S = auto()
T = auto()
J = auto()
Z = auto()
def __init__(self, block_rel_pos, color, pytromino_type, center_rot=(0, 0)):
""" Create a new Pytromino instance. A pytromino consists of a list of
coordinates for the center points of blocks. One of these blocks should
have coordinate (0, 0), this is the reference block. All other blocks'
coordinates are relatively the reference block's coordinate. Additionally,
the center of rotation can be any point that's relative to center of reference;
it does not have to be (0, 0).
Parameters
----------
block_rel_pos (list[tuple[int, int]]): A list of tuples (x, y) that
represent a block's relative position to the center
color (tuple[int, int, int]): RGB colors of this Pytromino
pytromino_type (Pytromino.Types): Type of Pytromino
center_rot (tuple[number, number], optional): Center of rotation coordinate
relative to the (0, 0) reference block. Defaults to (0, 0).
"""
assert isinstance(pytromino_type, Pytromino.Types)
assert type(color) == tuple
self._blocks_pos = block_rel_pos
self._color = color
self._type = pytromino_type
self._center_rot = center_rot
self._placed = False
# ---------------------------------------------------------------------------- #
# ----------------------------- Required Methods ----------------------------- #
# ---------------------------------------------------------------------------- #
def rotate_block_90_cw(self, pos):
"""Rotate pos e.g. (x, y) by 90 degree clockwise
Parameters
----------
pos (tuple[int, int]):
A tuple coordinate (x, y)
Returns
-------
tuple[int, int]:
A new tuple coordinate after rotating input tuple coordinate
90 degrees clockwise
>>> T = Pytromino([(0, 0), (0, -1), (-1, 0), (1, 0)], Color.PURPLE.value, Pytromino.Types.T) # type T
>>> T.rotate_block_90_cw((-1, 0))
(0, -1)
>>> T.rotate_block_90_cw((0, 0))
(0, 0)
>>> T.rotate_block_90_cw((1, 0))
(0, 1)
"""
# TODO: your solution here
# Hint:
# The new x value is: center_rot.y - pos.y + center_rot.x
# The new y value is: pos.x - center_rot.x + center_rot.y
# You need to translate the above equations to code and
# return the right solution.
def filter_blocks_pos(self, fn):
"""Use a function to filter out blocks positions
Parameters
----------
fn ((tuple[int, int]) -> bool):
A function that takes in a tuple coordinate and returns boolean
Returns
-------
list[tuple[int, int]]:
A list of tuple coordinates that satisfy fn
>>> S = Pytromino([(0, 0), (-1, 0), (0, -1), (1, -1)], Color.GREEN.value, Pytromino.Types.S) # type S
>>> f = lambda pos: pos[0] == 0
>>> S.filter_blocks_pos(f)
[(0, 0), (0, -1)]
>>> S.filter_blocks_pos(lambda pos: pos[0] * pos[1] < 0)
[(1, -1)]
"""
# TODO: your solution here
@staticmethod
def shift_down_fn(steps):
"""Create a function that will shift *this* pytromino
down number of steps
Parameters
----------
steps (int):
number of steps to shift down
Returns
-------
tuple[int, int] -> tuple[int, int]:
A function that takes in a tuple coordinate and returns
a new tuple coordinate
>>> f = Pytromino.shift_down_fn(1)
>>> f((0, 0))
(0, 1)
>>> m = Pytromino.shift_down_fn(3)
>>> m((0, 0))
(0, 3)
"""
# TODO: your solution here
@staticmethod
def shift_left_fn(steps):
"""Create a function that will shift *this* pytromino
left number of steps
Parameters
----------
steps (int):
number of steps to shift left
Returns
-------
tuple[int, int] -> tuple[int, int]:
A function that takes in a tuple coordinate and returns
a new tuple coordinate
>>> f = Pytromino.shift_left_fn(1)
>>> f((0, 0))
(-1, 0)
>>> m = Pytromino.shift_left_fn(3)
>>> m((0, 0))
(-3, 0)
"""
# TODO: your solution here
@staticmethod
def shift_right_fn(steps):
"""Create a function that will shift *this* pytromino
right number of steps
Parameters
----------
steps (int):
number of steps to shift right
Returns
-------
tuple[int, int] -> tuple[int, int]:
A function that takes in a tuple coordinate and returns
a new tuple coordinate
>>> f = Pytromino.shift_right_fn(1)
>>> f((0, 0))
(1, 0)
>>> m = Pytromino.shift_right_fn(3)
>>> m((0, 0))
(3, 0)
"""
# TODO: your solution here
def validated_apply(self, fn, is_rotation=False, validator=lambda pos: True):
""" Apply fn on all block coordinates of the pytromino, and check the
validity of each resulting coordinate using a validator function.
A side effect will only occur when ALL resulting coordinates pass
the validator check. Else, no effect will occur and False will be
returned. If is_rotation, fn is not applied to self._center_rot.
Parameters
----------
fn ((tuple[int, int])) -> tuple[int, int]):
A function that takes in a tuple of 2 int, then does some
transformation, and return a new tuple of 2 int.
is_rotation (bool):
If fn is a rotational transfermation, self.center_rot will not
be applied with fn
validator ((tuple[int, int]) -> bool):
A function that takes in the result of fn, a tuple of 2 int,
does some check, then return a boolean of the result. By default,
there is no meaningful check.
Returns
-------
bool
True when fn has been applied to ALL blocks, False for NONE
>>> T = Pytromino([(0, 0), (0, -1), (-1, 0), (1, 0)], Color.PURPLE.value, Pytromino.Types.T) # type T
>>> T # Checkout the __repr__(self) below if you're curious
<Pytromino [(0, 0), (0, -1), (-1, 0), (1, 0)], (146, 44, 140), Types.T, (0, 0) >
>>> right_shift_1 = Pytromino.shift_right_fn(1)
>>> positive_x = lambda pos: pos[0] > 0
>>> T.validated_apply(right_shift_1, False, positive_x)
False
>>> T # No change!
<Pytromino [(0, 0), (0, -1), (-1, 0), (1, 0)], (146, 44, 140), Types.T, (0, 0) >
>>> always_true = lambda pos: True
>>> T.validated_apply(right_shift_1, False, always_true)
True
>>> T # Notice the change in center_pos ------------------------------ below
<Pytromino [(1, 0), (1, -1), (0, 0), (2, 0)], (146, 44, 140), Types.T, (1, 0) >
>>> I = Pytromino([(0, 0), (-1, 0), (1, 0), (2, 0)], Color.CYAN.value, Pytromino.Types.I, center_rot=(0.5, 0.5)) #Type I
>>> I
<Pytromino [(0, 0), (-1, 0), (1, 0), (2, 0)], (43, 172, 226), Types.I, (0.5, 0.5) >
>>> I.validated_apply(I.rotate_block_90_cw, True, always_true) # This is a rotation
True
>>> I # Notice center_pos is NOT changed --------------------------------------------- below
<Pytromino [(1.0, 0.0), (1.0, -1.0), (1.0, 1.0), (1.0, 2.0)], (43, 172, 226), Types.I, (0.5, 0.5) >
"""
# TODO: your solution here
# ---------------------------------------------------------------------------- #
# --------------------------- Helpers: Not Required -------------------------- #
# ---------------------------------------------------------------------------- #
def get_unique_rows(self):
""" Returns a list of rows spanned by this pytromino
"""
s = set()
for pos in self._blocks_pos:
s.add(pos[1])
return list(s)
def place_at(self, coordinate):
""" Place this Pytromino at coordinate, can only be called ONCE
in an instance's lifetime
coordinate: (x, y) coordinate
"""
if not self._placed:
self.validated_apply(
lambda pos: (
pos[0] + coordinate[0],
pos[1] + coordinate[1]
),
False
)
self._placed = True
def is_placed(self):
return self._placed
def get_blocks_pos(self):
""" Returns a COPY of blocks_pos
"""
return self._blocks_pos[:]
def get_color(self):
""" Returns the color of the Pytromino
"""
return self._color
def get_type(self):
return self._type
def __repr__(self):
return f"<Pytromino {self._blocks_pos}, {self._color}, {self._type}, {self._center_rot} >"
def pytromino_factory(pytromino_type):
if pytromino_type == Pytromino.Types.I: # cyan
return Pytromino([(0, 0), (-1, 0), (1, 0), (2, 0)],
Color.CYAN.value,
pytromino_type,
center_rot=(0.5, 0.5))
elif pytromino_type == Pytromino.Types.O: # yellow
return Pytromino([(0, 0), (0, -1), (1, -1), (1, 0)],
Color.YELLOW.value,
pytromino_type,
center_rot=(0.5, -0.5))
elif pytromino_type == Pytromino.Types.L: # orange
return Pytromino([(0, 0), (-1, 0), (1, 0), (1, -1)],
Color.ORANGE.value,
pytromino_type)
elif pytromino_type == Pytromino.Types.S: # green
return Pytromino([(0, 0), (-1, 0), (0, -1), (1, -1)],
Color.GREEN.value,
pytromino_type)
elif pytromino_type == Pytromino.Types.T: # purple
return Pytromino([(0, 0), (0, -1), (-1, 0), (1, 0)],
Color.PURPLE.value,
pytromino_type)
elif pytromino_type == Pytromino.Types.J: # blue
return Pytromino([(0, 0), (-1, -1), (-1, 0), (1, 0)],
Color.BLUE.value,
pytromino_type)
elif pytromino_type == Pytromino.Types.Z: # red
return Pytromino([(0, 0), (0, -1), (-1, -1), (1, 0)],
Color.RED.value,
pytromino_type)
else:
raise ValueError(f'Unknown block type: "{pytromino_type}"')
class Holder:
"""An object that can hold 1 item at a time,
when closed, the item can not be stored or replaced
"""
def __init__(self):
"""Create an instance of Holder
>>> holder = Holder()
>>> holder.is_open()
True
>>> holder.store(1)
>>> holder.get_item()
1
>>> holder.close()
>>> holder.get_item()
1
>>> holder.is_open()
False
>>> holder.open()
>>> holder.is_open()
True
"""
self._item = None
self._can_store = True
# ---------------------------------------------------------------------------- #
# --------------------------------- Required --------------------------------- #
# ---------------------------------------------------------------------------- #
def store(self, item):
"""hold an item, or replace existing item.
Parameters
----------
item (any):
the item to hold
"""
assert self._can_store, "holder is closed"
# TODO: your solution here
def open(self):
"""Open *this* holder to be able to store/replace item
"""
# TODO: your solution here
def close(self):
"""Close *this* holder so that no new item can be stored,
or the existing item cannot be replaced.
"""
# TODO: your solution here
def get_item(self):
"""Get the item currently being held,
regardless whether the holder is closed
Returns
-------
any:
the item currently being held
"""
# TODO: your solution here
def is_open(self):
"""Check if *this* holder is currently open so that it can
store item, or replace existing item.
Returns
-------
bool:
True if the holder can accept store/replace item,
False otherwise
"""
# TODO: your solution here