| 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 |