俄罗斯方块#

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:
8python -m arcade.examples.tetris
9"""
10# flake8: noqa: E241
11import arcade
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)
72 new_textures.append(arcade.Texture(image))
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
122class MyGame(arcade.Window):
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
130 self.background_color = arcade.color.WHITE
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
157 self.board_sprite_list = arcade.SpriteList()
158 for row in range(len(self.board)):
159 for column in range(len(self.board[0])):
160 sprite = arcade.Sprite(texture_list[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 """
228 if key == arcade.key.LEFT:
229 self.move(-1)
230 elif key == arcade.key.RIGHT:
231 self.move(1)
232 elif key == arcade.key.UP:
233 self.rotate_stone()
234 elif key == arcade.key.DOWN:
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
254 arcade.draw_rectangle_filled(x, y, WIDTH, HEIGHT, color)
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()
279 arcade.run()
280
281
282if __name__ == "__main__":
283 main()