Total Complexity | 55 |
Total Lines | 200 |
Duplicated Lines | 0 % |
Complex classes like Tetrimino often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | import pygame |
||
10 | class Tetrimino(pygame.sprite.OrderedUpdates): |
||
11 | def __init__(self, definition, size, background, matrix): |
||
12 | pygame.sprite.OrderedUpdates.__init__(self) |
||
13 | self.blocks = list() |
||
14 | self.color = white |
||
15 | self.outline = 1 |
||
16 | self.direction = D_UP |
||
17 | # XXX maybe have 3 separate parameters in init |
||
18 | self.setName(definition['name']) |
||
19 | self.setColor(definition['color']) |
||
20 | self.all_blocks = definition['blocks'] |
||
21 | self.blocks_cycle = cycle(self.all_blocks) |
||
22 | self.size = size |
||
23 | self.pivot = (0, 0) |
||
24 | self.setBlocks(next(self.blocks_cycle)) |
||
25 | self.background = background |
||
26 | self.matrix = matrix |
||
27 | self.isLocked = False |
||
28 | |||
29 | def setName(self, name): |
||
30 | self.name = name |
||
31 | return self |
||
32 | |||
33 | def setBlocks(self, blocks): |
||
34 | """Build sprites group""" |
||
35 | # use first block to draw an image |
||
36 | self.blocks = blocks |
||
37 | block = pygame.Rect(blocks[0][0] * self.size, |
||
38 | blocks[0][1] * self.size, |
||
39 | self.size, |
||
40 | self.size) |
||
41 | image = self.buildImage(block) |
||
42 | for block in blocks: |
||
43 | sprite = pygame.sprite.Sprite() |
||
44 | sprite.rect = pygame.Rect(block[0] * self.size + self.pivot[0], |
||
45 | block[1] * self.size + self.pivot[1], |
||
46 | self.size, |
||
47 | self.size) |
||
48 | sprite.image = image |
||
49 | sprite.add(self) |
||
50 | return self |
||
51 | |||
52 | def buildImage(self, block): |
||
53 | block.top, block.left = 0, 0 |
||
54 | image = pygame.Surface(block.size) |
||
55 | # inner block is a little smaller |
||
56 | innerblock = pygame.Rect(self.outline, |
||
57 | self.outline, |
||
58 | block.size[0] - self.outline * 2, |
||
59 | block.size[1] - self.outline * 2) |
||
60 | pygame.draw.rect(image, self.color, innerblock, self.outline) |
||
61 | return image |
||
62 | |||
63 | def setColor(self, color): |
||
64 | """Set the color of the sprites""" |
||
65 | self.color = color |
||
66 | return self |
||
67 | |||
68 | def clear(self, matrix): |
||
69 | pygame.sprite.OrderedUpdates.clear(self, matrix, self.background) |
||
70 | |||
71 | def moveUp(self): |
||
72 | self._move('up') |
||
73 | return self |
||
74 | |||
75 | def moveDown(self): |
||
76 | moved = False |
||
77 | self._move('down') |
||
78 | if self.isColliding(): |
||
79 | self._move('up') |
||
80 | self.isLocked = True |
||
81 | else: |
||
82 | moved = True |
||
83 | self._redraw() |
||
84 | return moved |
||
85 | |||
86 | def moveLeft(self): |
||
87 | self._move('left') |
||
88 | if self.isColliding(): |
||
89 | self._move('right') |
||
90 | else: |
||
91 | self._redraw() |
||
92 | return self |
||
93 | |||
94 | def moveRight(self): |
||
95 | self._move('right') |
||
96 | if self.isColliding(): |
||
97 | self._move('left') |
||
98 | else: |
||
99 | self._redraw() |
||
100 | return self |
||
101 | |||
102 | def _move(self, direction): |
||
103 | if direction == 'up': |
||
104 | for sprite in self.sprites(): |
||
105 | sprite.rect.top -= self.size |
||
106 | elif direction == 'down': |
||
107 | for sprite in self.sprites(): |
||
108 | sprite.rect.top += self.size |
||
109 | elif direction == 'left': |
||
110 | for sprite in self.sprites(): |
||
111 | sprite.rect.left -= self.size |
||
112 | elif direction == 'right': |
||
113 | for sprite in self.sprites(): |
||
114 | sprite.rect.left += self.size |
||
115 | |||
116 | def harddrop(self): |
||
117 | harddrops = 0 |
||
118 | while not self.isColliding(): |
||
119 | self._move('down') |
||
120 | harddrops += 1 |
||
121 | self._move('up') |
||
122 | self._redraw() |
||
123 | self.isLocked = True |
||
124 | harddrops -= 1 |
||
125 | return harddrops |
||
126 | |||
127 | def _redraw(self): |
||
128 | self.clear(self.matrix) |
||
129 | self.draw(self.matrix) |
||
130 | |||
131 | def rotate(self, test_collision=True): |
||
132 | # previous set of blocks base positions |
||
133 | previous_positions = self.blocks |
||
134 | # get current position |
||
135 | positions = list() |
||
136 | for sprite in self.sprites(): |
||
137 | positions.append((sprite.rect.left / self.size, |
||
138 | sprite.rect.top / self.size)) |
||
139 | # empty sprite list |
||
140 | self.empty() |
||
141 | # get next set of blocks |
||
142 | self.setBlocks(next(self.blocks_cycle)) |
||
143 | new_positions = self.blocks |
||
144 | # set new calculated positions |
||
145 | for index, sprite in enumerate(self.sprites()): |
||
146 | sprite.rect.left = (positions[index][0] - |
||
147 | previous_positions[index][0] + |
||
148 | new_positions[index][0]) * self.size |
||
149 | sprite.rect.top = (positions[index][1] - |
||
150 | previous_positions[index][1] + |
||
151 | new_positions[index][1]) * self.size |
||
152 | if test_collision and self.isColliding(): |
||
153 | # handle matrix collisions |
||
154 | if self.colliding == 'left': |
||
155 | left = min(sprite.rect.left for sprite |
||
156 | in self.sprites()) / self.size |
||
157 | if left < 0: |
||
158 | for _ in range(0, left * - 1): |
||
159 | self.moveRight() |
||
160 | elif self.colliding == 'right': |
||
161 | right = max(sprite.rect.right for sprite |
||
162 | in self.sprites()) / self.size |
||
163 | if right > 10: |
||
164 | for _ in range(0, right - 10): |
||
165 | self.moveLeft() |
||
166 | # handle sprite group collisions |
||
167 | for _ in range(1, len(self.all_blocks)): |
||
168 | self.rotate(False) |
||
169 | # use previous positions |
||
170 | for index, sprite in enumerate(self.sprites()): |
||
171 | sprite.rect.left = positions[index][0] * self.size |
||
172 | sprite.rect.top = positions[index][1] * self.size |
||
173 | return self.isColliding() |
||
174 | return True |
||
175 | |||
176 | def isColliding(self): |
||
177 | # Test collisions between sprites |
||
178 | for group in self.matrix.sprites: |
||
179 | if pygame.sprite.groupcollide(group, self, False, False): |
||
180 | return True |
||
181 | # Test collisions with boundaries |
||
182 | matrix = self.matrix.get_rect() |
||
183 | bottom = max(sprite.rect.bottom for sprite in self.sprites()) |
||
184 | if bottom > matrix.bottom: |
||
185 | self.colliding = 'bottom' |
||
186 | return True |
||
187 | left = min(sprite.rect.left for sprite in self.sprites()) |
||
188 | if left < matrix.left: |
||
189 | self.colliding = 'left' |
||
190 | return True |
||
191 | right = max(sprite.rect.right for sprite in self.sprites()) |
||
192 | if right > matrix.right: |
||
193 | self.colliding = 'right' |
||
194 | return True |
||
195 | self.colliding = None |
||
196 | return False |
||
197 | |||
198 | def center(self, width, height=0): |
||
199 | top = min(sprite.rect.top for sprite in self.sprites()) / self.size |
||
200 | if top > 0: |
||
201 | self.moveUp() |
||
202 | groupwidth = max(sprite.rect.right for sprite |
||
203 | in self.sprites()) / self.size |
||
204 | matrixcenter = (width / self.size) / 2 |
||
205 | start = matrixcenter - int(math.ceil(float(groupwidth) / 2)) |
||
206 | for sprite in self.sprites(): |
||
207 | sprite.rect.left += (start * self.size) |
||
208 | sprite.rect.top += height |
||
209 | return self |
||
210 |