-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPit.py
164 lines (136 loc) · 5.25 KB
/
Pit.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
from contextlib import contextmanager
from PyQt4 import QtCore
from Tetracube import CubeBank
from MathHelper import *
class Pit(QtCore.QObject):
modified = QtCore.pyqtSignal()
def __init__(self, width, height, depth):
"width: X-axis, height: Y-axis, depth: Z-axis"
super(Pit, self).__init__()
self.width, self.height, self.depth = width, height, depth
self.empty()
self.cur_cube = None
self.cur_pos = None
def empty(self):
width, height, depth = self.width, self.height, self.depth
# Is copy.deepcopy() faster?
self.empty_layer_factory = lambda: [a[:] for a in [[0] * width] * height]
self.layers = [self.empty_layer_factory() for d in range(depth)]
self.modified.emit()
def spawn_cube(self):
"""
Creates a new tetracube in the pit.
Returns False if there's no room for the new tetracube
(this basically means game over), and True if
everything is OK and the tetracube has been created.
"""
tc = CubeBank.get_random()
half_width, half_height = self.width / 2, self.height / 2
pos = [half_width, half_height, self.depth - int(tc.three_d) - 1]
if self.is_conflict(tc, pos):
return False
self.cur_cube = tc
self.cur_pos = pos
self.modified.emit()
return True
def is_conflict(self, tetracube, pos):
for x, y, z in tetracube.coords:
x_, y_, z_ = x+pos[0], y+pos[1], z+pos[2]
if 0 <= x_ < self.width and \
0 <= y_ < self.height and \
0 <= z_ < self.depth and \
not self.layers[z_][y_][x_]:
continue
else:
return True
return False
def get_offset(self, tetracube, pos):
"""
After adding `offset` to `cur_pos`, the cube will be moved inside
the pit (if possible).
"""
offset = [0, 0, 0]
sizes = [self.width, self.height, self.depth]
for coord in tetracube.coords:
coord = add(coord, pos)
for i in range(3):
off = 0
if coord[i] < 0:
off = -coord[i]
elif coord[i] >= sizes[i]:
off = -(coord[i] - sizes[i] + 1)
if abs(off) > abs(offset[i]):
offset[i] = off
return offset
def move_cube(self, offset) :
"""
Moves the current tetracube by the given offset. The
offset is a tuple (X, Y, Z).
Returns True if the figure was successfully moved,
and False if the move introduces a conflict.
"""
if not self.cur_pos:
return False
new_pos = add(self.cur_pos, offset)
if self.is_conflict(self.cur_cube, new_pos):
return False
self.cur_pos = new_pos
self.modified.emit()
return True
def move_down(self):
return self.move_cube(NEGATIVE_Z)
def rotate_cube(self, axis):
tmp_cube = self.cur_cube.copy()
tmp_cube.rotate(axis)
if self.is_conflict(tmp_cube, self.cur_pos):
offset = self.get_offset(tmp_cube, self.cur_pos)
if offset == [0, 0, 0] or \
self.is_conflict(tmp_cube, add(self.cur_pos, offset)):
return False
else:
self.cur_pos = add(self.cur_pos, offset)
self.cur_cube = tmp_cube
self.modified.emit()
return True
def can_move_down(self):
down_pos = add(self.cur_pos, NEGATIVE_Z)
return not self.is_conflict(self.cur_cube, down_pos)
def drop_cube(self):
while(self.can_move_down()):
self.move_down()
@staticmethod
def _is_layer_incomplete(layer):
for row in layer:
for blk in row:
if not blk:
return True
return False
def remove_completed_layers(self):
complete = [not Pit._is_layer_incomplete(lay) for lay in self.layers]
self.layers = [lay for (lay, flag_complete) in
zip(self.layers, complete) if not flag_complete]
self.layers += [self.empty_layer_factory() for i in range(sum(complete))]
self.modified.emit()
return complete
@contextmanager
def with_active_cube(self):
self.put_active_cube()
yield self
self.remove_active_cube()
def _manipulate_active_cube(self, add_cube):
"Puts the active cube into the pit, or removes it from the pit."
cur_cube = self.cur_cube
if not cur_cube:
return
for x, y, z in cur_cube.coords:
x_, y_, z_ = x+self.cur_pos[0], y+self.cur_pos[1], z+self.cur_pos[2]
self.layers[z_][y_][x_] = cur_cube.color_index if add_cube else 0
def put_active_cube(self):
self._manipulate_active_cube(True)
def remove_active_cube(self):
self._manipulate_active_cube(False)
def add_active_cube(self):
self.put_active_cube()
# Sets current cube to None so the added cube will not be taken away
# when calling `remove_active_cube`
self.cur_cube = None