# Tetris¶

tetris.py
```  1"""
2Tetris
3
4Tetris clone, with some ideas from silvasur's code:
5https://gist.github.com/silvasur/565419/d9de6a84e7da000797ac681976442073045c74a4
6
7If Python and Arcade are installed, this example can be run from the command line with:
9"""
10# flake8: noqa: E241
12import random
13import PIL
14
15# Set how many rows and columns we will have
16ROW_COUNT = 24
17COLUMN_COUNT = 10
18
19# This sets the WIDTH and HEIGHT of each grid location
20WIDTH = 30
21HEIGHT = 30
22
23# This sets the margin between each cell
24# and on the edges of the screen.
25MARGIN = 5
26
27# Do the math to figure out our screen dimensions
28SCREEN_WIDTH = (WIDTH + MARGIN) * COLUMN_COUNT + MARGIN
29SCREEN_HEIGHT = (HEIGHT + MARGIN) * ROW_COUNT + MARGIN
30SCREEN_TITLE = "Tetris"
31
32colors = [
33    (0,   0,   0, 255),
34    (255, 0,   0, 255),
35    (0,   150, 0, 255),
36    (0,   0,   255, 255),
37    (255, 120, 0, 255),
38    (255, 255, 0, 255),
39    (180, 0,   255, 255),
40    (0,   220, 220, 255)
41]
42
43# Define the shapes of the single parts
44tetris_shapes = [
45    [[1, 1, 1],
46     [0, 1, 0]],
47
48    [[0, 2, 2],
49     [2, 2, 0]],
50
51    [[3, 3, 0],
52     [0, 3, 3]],
53
54    [[4, 0, 0],
55     [4, 4, 4]],
56
57    [[0, 0, 5],
58     [5, 5, 5]],
59
60    [[6, 6, 6, 6]],
61
62    [[7, 7],
63     [7, 7]]
64]
65
66
67def create_textures():
68    """ Create a list of images for sprites based on the global colors. """
69    new_textures = []
70    for color in colors:
71        image = PIL.Image.new('RGBA', (WIDTH, HEIGHT), color)
73    return new_textures
74
75
76texture_list = create_textures()
77
78
79def rotate_counterclockwise(shape):
80    """ Rotates a matrix clockwise """
81    return [[shape[y][x] for y in range(len(shape))]
82            for x in range(len(shape[0]) - 1, -1, -1)]
83
84
85def check_collision(board, shape, offset):
86    """
87    See if the matrix stored in the shape will intersect anything
88    on the board based on the offset. Offset is an (x, y) coordinate.
89    """
90    off_x, off_y = offset
91    for cy, row in enumerate(shape):
92        for cx, cell in enumerate(row):
93            if cell and board[cy + off_y][cx + off_x]:
94                return True
95    return False
96
97
98def remove_row(board, row):
99    """ Remove a row from the board, add a blank row on top. """
100    del board[row]
101    return [[0 for _ in range(COLUMN_COUNT)]] + board
102
103
104def join_matrixes(matrix_1, matrix_2, matrix_2_offset):
105    """ Copy matrix 2 onto matrix 1 based on the passed in x, y offset coordinate """
106    offset_x, offset_y = matrix_2_offset
107    for cy, row in enumerate(matrix_2):
108        for cx, val in enumerate(row):
109            matrix_1[cy + offset_y - 1][cx + offset_x] += val
110    return matrix_1
111
112
113def new_board():
114    """ Create a grid of 0's. Add 1's to the bottom for easier collision detection. """
115    # Create the main board of 0's
116    board = [[0 for _x in range(COLUMN_COUNT)] for _y in range(ROW_COUNT)]
117    # Add a bottom border of 1's
118    board += [[1 for _x in range(COLUMN_COUNT)]]
119    return board
120
121
123    """ Main application class. """
124
125    def __init__(self, width, height, title):
126        """ Set up the application. """
127
128        super().__init__(width, height, title)
129
131
132        self.board = None
133        self.frame_count = 0
134        self.game_over = False
135        self.paused = False
136        self.board_sprite_list = None
137
138        self.stone = None
139        self.stone_x = 0
140        self.stone_y = 0
141
142    def new_stone(self):
143        """
144        Randomly grab a new stone and set the stone location to the top.
145        If we immediately collide, then game-over.
146        """
147        self.stone = random.choice(tetris_shapes)
148        self.stone_x = int(COLUMN_COUNT / 2 - len(self.stone[0]) / 2)
149        self.stone_y = 0
150
151        if check_collision(self.board, self.stone, (self.stone_x, self.stone_y)):
152            self.game_over = True
153
154    def setup(self):
155        self.board = new_board()
156
158        for row in range(len(self.board)):
159            for column in range(len(self.board[0])):
161                sprite.textures = texture_list
162                sprite.center_x = (MARGIN + WIDTH) * column + MARGIN + WIDTH // 2
163                sprite.center_y = SCREEN_HEIGHT - (MARGIN + HEIGHT) * row + MARGIN + HEIGHT // 2
164
165                self.board_sprite_list.append(sprite)
166
167        self.new_stone()
168        self.update_board()
169
170    def drop(self):
171        """
172        Drop the stone down one place.
173        Check for collision.
174        If collided, then
175          join matrixes
176          Check for rows we can remove
177          Update sprite list with stones
178          Create a new stone
179        """
180        if not self.game_over and not self.paused:
181            self.stone_y += 1
182            if check_collision(self.board, self.stone, (self.stone_x, self.stone_y)):
183                self.board = join_matrixes(self.board, self.stone, (self.stone_x, self.stone_y))
184                while True:
185                    for i, row in enumerate(self.board[:-1]):
186                        if 0 not in row:
187                            self.board = remove_row(self.board, i)
188                            break
189                    else:
190                        break
191                self.update_board()
192                self.new_stone()
193
194    def rotate_stone(self):
195        """ Rotate the stone, check collision. """
196        if not self.game_over and not self.paused:
197            new_stone = rotate_counterclockwise(self.stone)
198            if self.stone_x + len(new_stone[0]) >= COLUMN_COUNT:
199                self.stone_x = COLUMN_COUNT - len(new_stone[0])
200            if not check_collision(self.board, new_stone, (self.stone_x, self.stone_y)):
201                self.stone = new_stone
202
203    def on_update(self, dt):
204        """ Update, drop stone if warranted """
205        self.frame_count += 1
206        if self.frame_count % 10 == 0:
207            self.drop()
208
209    def move(self, delta_x):
210        """ Move the stone back and forth based on delta x. """
211        if not self.game_over and not self.paused:
212            new_x = self.stone_x + delta_x
213            if new_x < 0:
214                new_x = 0
215            if new_x > COLUMN_COUNT - len(self.stone[0]):
216                new_x = COLUMN_COUNT - len(self.stone[0])
217            if not check_collision(self.board, self.stone, (new_x, self.stone_y)):
218                self.stone_x = new_x
219
220    def on_key_press(self, key, modifiers):
221        """
222        Handle user key presses
223        User goes left, move -1
224        User goes right, move 1
225        Rotate stone,
226        or drop down
227        """
229            self.move(-1)
231            self.move(1)
233            self.rotate_stone()
235            self.drop()
236
237    # noinspection PyMethodMayBeStatic
238    def draw_grid(self, grid, offset_x, offset_y):
239        """
240        Draw the grid. Used to draw the falling stones. The board is drawn
241        by the sprite list.
242        """
243        # Draw the grid
244        for row in range(len(grid)):
245            for column in range(len(grid[0])):
246                # Figure out what color to draw the box
247                if grid[row][column]:
248                    color = colors[grid[row][column]]
249                    # Do the math to figure out where the box is
250                    x = (MARGIN + WIDTH) * (column + offset_x) + MARGIN + WIDTH // 2
251                    y = SCREEN_HEIGHT - (MARGIN + HEIGHT) * (row + offset_y) + MARGIN + HEIGHT // 2
252
253                    # Draw the box
255
256    def update_board(self):
257        """
258        Update the sprite list to reflect the contents of the 2d grid
259        """
260        for row in range(len(self.board)):
261            for column in range(len(self.board[0])):
262                v = self.board[row][column]
263                i = row * COLUMN_COUNT + column
264                self.board_sprite_list[i].set_texture(v)
265
266    def on_draw(self):
267        """ Render the screen. """
268
269        # This command has to happen before we start drawing
270        self.clear()
271        self.board_sprite_list.draw()
272        self.draw_grid(self.stone, self.stone_x, self.stone_y)
273
274
275def main():
276    """ Create the game window, setup, run """
277    my_game = MyGame(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
278    my_game.setup()