Tetrimino   B
last analyzed

Complexity

Total Complexity 49

Size/Duplication

Total Lines 169
Duplicated Lines 0 %

Importance

Changes 38
Bugs 1 Features 7
Metric Value
c 38
b 1
f 7
dl 0
loc 169
rs 8.5454
wmc 49

14 Methods

Rating   Name   Duplication   Size   Complexity  
A moveLeft() 0 3 1
A moveRight() 0 3 1
A moveDown() 0 3 1
A __init__() 0 16 1
F rotate() 0 44 15
B center() 0 12 5
C isColliding() 0 21 9
A setName() 0 3 1
A clear() 0 2 1
A moveUp() 0 3 1
C _move() 0 13 9
A buildImage() 0 10 1
A setBlocks() 0 18 2
A setColor() 0 4 1

How to fix   Complexity   

Complex Class

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
2
import math
3
from itertools import cycle
4
5
white = (255, 255, 255)
6
7
D_UP = 0
8
9
10
class Tetrimino(pygame.sprite.OrderedUpdates):
11
    def __init__(self, definition, size, background, zone):
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.zone = zone
27
28
    def setName(self, name):
29
        self.name = name
30
        return self
31
32
    def setBlocks(self, blocks):
33
        """Build sprites group"""
34
        # use first block to draw an image
35
        self.blocks = blocks
36
        block = pygame.Rect(blocks[0][0] * self.size,
37
                            blocks[0][1] * self.size,
38
                            self.size,
39
                            self.size)
40
        image = self.buildImage(block)
41
        for block in blocks:
42
            sprite = pygame.sprite.Sprite()
43
            sprite.rect = pygame.Rect(block[0] * self.size + self.pivot[0],
44
                                      block[1] * self.size + self.pivot[1],
45
                                      self.size,
46
                                      self.size)
47
            sprite.image = image
48
            sprite.add(self)
49
        return self
50
51
    def buildImage(self, block):
52
        block.top, block.left = 0, 0
53
        image = pygame.Surface(block.size)
54
        # inner block is a little smaller
55
        innerblock = pygame.Rect(self.outline,
56
                                 self.outline,
57
                                 block.size[0] - self.outline * 2,
58
                                 block.size[1] - self.outline * 2)
59
        pygame.draw.rect(image, self.color, innerblock, self.outline)
60
        return image
61
62
    def setColor(self, color):
63
        """Set the color of the sprites"""
64
        self.color = color
65
        return self
66
67
    def clear(self, zone):
68
        pygame.sprite.OrderedUpdates.clear(self, zone, self.background)
69
70
    def moveUp(self):
71
        self._move('up')
72
        return self
73
74
    def moveDown(self):
75
        self._move('down')
76
        return self
77
78
    def moveLeft(self):
79
        self._move('left')
80
        return self
81
82
    def moveRight(self):
83
        self._move('right')
84
        return self
85
86
    def _move(self, direction):
87
        if direction == 'up':
88
            for sprite in self.sprites():
89
                sprite.rect.top -= self.size
90
        elif direction == 'down':
91
            for sprite in self.sprites():
92
                sprite.rect.top += self.size
93
        elif direction == 'left':
94
            for sprite in self.sprites():
95
                sprite.rect.left -= self.size
96
        elif direction == 'right':
97
            for sprite in self.sprites():
98
                sprite.rect.left += self.size
99
100
    def rotate(self, test_collision=True):
101
        # previous set of blocks base positions
102
        previous_positions = self.blocks
103
        # get current position
104
        positions = list()
105
        for sprite in self.sprites():
106
            positions.append((sprite.rect.left / self.size,
107
                              sprite.rect.top / self.size))
108
        # empty sprite list
109
        self.empty()
110
        # get next set of blocks
111
        self.setBlocks(next(self.blocks_cycle))
112
        new_positions = self.blocks
113
        # set new calculated positions
114
        for index, sprite in enumerate(self.sprites()):
115
            sprite.rect.left = (positions[index][0] -
116
                                previous_positions[index][0] +
117
                                new_positions[index][0]) * self.size
118
            sprite.rect.top = (positions[index][1] -
119
                               previous_positions[index][1] +
120
                               new_positions[index][1]) * self.size
121
        if test_collision and self.isColliding():
122
            # handle zone collisions
123
            if self.colliding == 'left':
124
                left = min(sprite.rect.left for sprite
125
                           in self.sprites()) / self.size
126
                if left < 0:
127
                    for _ in range(0, left * - 1):
128
                        self.moveRight()
129
            elif self.colliding == 'right':
130
                right = max(sprite.rect.right for sprite
131
                            in self.sprites()) / self.size
132
                if right > 10:
133
                    for _ in range(0, right - 10):
134
                        self.moveLeft()
135
            # handle sprite group collisions
136
            for _ in range(1, len(self.all_blocks)):
137
                self.rotate(False)
138
            # use previous positions
139
            for index, sprite in enumerate(self.sprites()):
140
                sprite.rect.left = positions[index][0] * self.size
141
                sprite.rect.top = positions[index][1] * self.size
142
            return self.isColliding()
143
        return True
144
145
    def isColliding(self):
146
        # Test collisions between sprites
147
        for group in self.zone.sprites:
148
            if pygame.sprite.groupcollide(group, self, False, False):
149
                return True
150
        # Test collisions with boundaries
151
        zone = self.zone.get_rect()
152
        bottom = max(sprite.rect.bottom for sprite in self.sprites())
153
        if bottom > zone.bottom:
154
            self.colliding = 'bottom'
155
            return True
156
        left = min(sprite.rect.left for sprite in self.sprites())
157
        if left < zone.left:
158
            self.colliding = 'left'
159
            return True
160
        right = max(sprite.rect.right for sprite in self.sprites())
161
        if right > zone.right:
162
            self.colliding = 'right'
163
            return True
164
        self.colliding = None
165
        return False
166
167
    def center(self, width, height=0):
168
        top = min(sprite.rect.top for sprite in self.sprites()) / self.size
169
        if top > 0:
170
            self.moveUp()
171
        groupwidth = max(sprite.rect.right for sprite
172
                         in self.sprites()) / self.size
173
        zonecenter = (width / self.size) / 2
174
        start = zonecenter - int(math.ceil(float(groupwidth) / 2))
175
        for sprite in self.sprites():
176
            sprite.rect.left += (start * self.size)
177
            sprite.rect.top += height
178
        return self
179