1
+ from settings import *
2
+ from random import choice
3
+ from sys import exit
4
+ from os .path import join
5
+
6
+ from timer import Timer
7
+
8
+ class Game :
9
+ def __init__ (self , get_next_shape , update_score ):
10
+
11
+ # general
12
+ self .surface = pygame .Surface ((GAME_WIDTH , GAME_HEIGHT ))
13
+ self .display_surface = pygame .display .get_surface ()
14
+ self .rect = self .surface .get_rect (topleft = (PADDING , PADDING ))
15
+ self .sprites = pygame .sprite .Group ()
16
+
17
+ # game connection
18
+ self .get_next_shape = get_next_shape
19
+ self .update_score = update_score
20
+
21
+ # lines
22
+ self .line_surface = self .surface .copy ()
23
+ self .line_surface .fill ((0 ,255 ,0 ))
24
+ self .line_surface .set_colorkey ((0 ,255 ,0 ))
25
+ self .line_surface .set_alpha (120 )
26
+
27
+ # tetromino
28
+ self .field_data = [[0 for x in range (COLUMNS )] for y in range (ROWS )]
29
+ self .tetromino = Tetromino (
30
+ choice (list (TETROMINOS .keys ())),
31
+ self .sprites ,
32
+ self .create_new_tetromino ,
33
+ self .field_data )
34
+
35
+ # timer
36
+ self .down_speed = UPDATE_START_SPEED
37
+ self .down_speed_faster = self .down_speed * 0.3
38
+ self .down_pressed = False
39
+ self .timers = {
40
+ 'vertical move' : Timer (self .down_speed , True , self .move_down ),
41
+ 'horizontal move' : Timer (MOVE_WAIT_TIME ),
42
+ 'rotate' : Timer (ROTATE_WAIT_TIME )
43
+ }
44
+ self .timers ['vertical move' ].activate ()
45
+
46
+ # score
47
+ self .current_level = 1
48
+ self .current_score = 0
49
+ self .current_lines = 0
50
+
51
+ # sound
52
+ self .landing_sound = pygame .mixer .Sound (join ('..' ,'sound' , 'landing.wav' ))
53
+ self .landing_sound .set_volume (0.1 )
54
+
55
+ def calculate_score (self , num_lines ):
56
+ self .current_lines += num_lines
57
+ self .current_score += SCORE_DATA [num_lines ] * self .current_level
58
+
59
+ if self .current_lines / 10 > self .current_level :
60
+ self .current_level += 1
61
+ self .down_speed *= 0.75
62
+ self .down_speed_faster = self .down_speed * 0.3
63
+ self .timers ['vertical move' ].duration = self .down_speed
64
+
65
+ self .update_score (self .current_lines , self .current_score , self .current_level )
66
+
67
+ def check_game_over (self ):
68
+ for block in self .tetromino .blocks :
69
+ if block .pos .y < 0 :
70
+ exit ()
71
+
72
+ def create_new_tetromino (self ):
73
+ self .landing_sound .play ()
74
+ self .check_game_over ()
75
+ self .check_finished_rows ()
76
+ self .tetromino = Tetromino (
77
+ self .get_next_shape (),
78
+ self .sprites ,
79
+ self .create_new_tetromino ,
80
+ self .field_data )
81
+
82
+ def timer_update (self ):
83
+ for timer in self .timers .values ():
84
+ timer .update ()
85
+
86
+ def move_down (self ):
87
+ self .tetromino .move_down ()
88
+
89
+ def draw_grid (self ):
90
+
91
+ for col in range (1 , COLUMNS ):
92
+ x = col * CELL_SIZE
93
+ pygame .draw .line (self .line_surface , LINE_COLOR , (x ,0 ), (x ,self .surface .get_height ()), 1 )
94
+
95
+ for row in range (1 , ROWS ):
96
+ y = row * CELL_SIZE
97
+ pygame .draw .line (self .line_surface , LINE_COLOR , (0 ,y ), (self .surface .get_width (),y ))
98
+
99
+ self .surface .blit (self .line_surface , (0 ,0 ))
100
+
101
+ def input (self ):
102
+ keys = pygame .key .get_pressed ()
103
+
104
+ # checking horizontal movement
105
+ if not self .timers ['horizontal move' ].active :
106
+ if keys [pygame .K_LEFT ]:
107
+ self .tetromino .move_horizontal (- 1 )
108
+ self .timers ['horizontal move' ].activate ()
109
+ if keys [pygame .K_RIGHT ]:
110
+ self .tetromino .move_horizontal (1 )
111
+ self .timers ['horizontal move' ].activate ()
112
+
113
+ # check for rotation
114
+ if not self .timers ['rotate' ].active :
115
+ if keys [pygame .K_UP ]:
116
+ self .tetromino .rotate ()
117
+ self .timers ['rotate' ].activate ()
118
+
119
+ # down speedup
120
+ if not self .down_pressed and keys [pygame .K_DOWN ]:
121
+ self .down_pressed = True
122
+ self .timers ['vertical move' ].duration = self .down_speed_faster
123
+
124
+ if self .down_pressed and not keys [pygame .K_DOWN ]:
125
+ self .down_pressed = False
126
+ self .timers ['vertical move' ].duration = self .down_speed
127
+
128
+ def check_finished_rows (self ):
129
+
130
+ # get the full row indexes
131
+ delete_rows = []
132
+ for i , row in enumerate (self .field_data ):
133
+ if all (row ):
134
+ delete_rows .append (i )
135
+
136
+ if delete_rows :
137
+ for delete_row in delete_rows :
138
+
139
+ # delete full rows
140
+ for block in self .field_data [delete_row ]:
141
+ block .kill ()
142
+
143
+ # move down blocks
144
+ for row in self .field_data :
145
+ for block in row :
146
+ if block and block .pos .y < delete_row :
147
+ block .pos .y += 1
148
+
149
+ # rebuild the field data
150
+ self .field_data = [[0 for x in range (COLUMNS )] for y in range (ROWS )]
151
+ for block in self .sprites :
152
+ self .field_data [int (block .pos .y )][int (block .pos .x )] = block
153
+
154
+ # update score
155
+ self .calculate_score (len (delete_rows ))
156
+
157
+ def run (self ):
158
+
159
+ # update
160
+ self .input ()
161
+ self .timer_update ()
162
+ self .sprites .update ()
163
+
164
+ # drawing
165
+ self .surface .fill (GRAY )
166
+ self .sprites .draw (self .surface )
167
+
168
+ self .draw_grid ()
169
+ self .display_surface .blit (self .surface , (PADDING ,PADDING ))
170
+ pygame .draw .rect (self .display_surface , LINE_COLOR , self .rect , 2 , 2 )
171
+
172
+ class Tetromino :
173
+ def __init__ (self , shape , group , create_new_tetromino , field_data ):
174
+
175
+ # setup
176
+ self .shape = shape
177
+ self .block_positions = TETROMINOS [shape ]['shape' ]
178
+ self .color = TETROMINOS [shape ]['color' ]
179
+ self .create_new_tetromino = create_new_tetromino
180
+ self .field_data = field_data
181
+
182
+ # create blocks
183
+ self .blocks = [Block (group , pos , self .color ) for pos in self .block_positions ]
184
+
185
+ # collisions
186
+ def next_move_horizontal_collide (self , blocks , amount ):
187
+ collision_list = [block .horizontal_collide (int (block .pos .x + amount ), self .field_data ) for block in self .blocks ]
188
+ return True if any (collision_list ) else False
189
+
190
+ def next_move_vertical_collide (self , blocks , amount ):
191
+ collision_list = [block .vertical_collide (int (block .pos .y + amount ), self .field_data ) for block in self .blocks ]
192
+ return True if any (collision_list ) else False
193
+
194
+ # movement
195
+ def move_horizontal (self , amount ):
196
+ if not self .next_move_horizontal_collide (self .blocks , amount ):
197
+ for block in self .blocks :
198
+ block .pos .x += amount
199
+
200
+ def move_down (self ):
201
+ if not self .next_move_vertical_collide (self .blocks , 1 ):
202
+ for block in self .blocks :
203
+ block .pos .y += 1
204
+ else :
205
+ for block in self .blocks :
206
+ self .field_data [int (block .pos .y )][int (block .pos .x )] = block
207
+ self .create_new_tetromino ()
208
+
209
+ # rotate
210
+ def rotate (self ):
211
+ if self .shape != 'O' :
212
+
213
+ # 1. pivot point
214
+ pivot_pos = self .blocks [0 ].pos
215
+
216
+ # 2. new block positions
217
+ new_block_positions = [block .rotate (pivot_pos ) for block in self .blocks ]
218
+
219
+ # 3. collision check
220
+ for pos in new_block_positions :
221
+ # horizontal
222
+ if pos .x < 0 or pos .x >= COLUMNS :
223
+ return
224
+
225
+ # field check -> collision with other pieces
226
+ if self .field_data [int (pos .y )][int (pos .x )]:
227
+ return
228
+
229
+ # vertical / floor check
230
+ if pos .y > ROWS :
231
+ return
232
+
233
+ # 4. implement new positions
234
+ for i , block in enumerate (self .blocks ):
235
+ block .pos = new_block_positions [i ]
236
+
237
+ class Block (pygame .sprite .Sprite ):
238
+ def __init__ (self , group , pos , color ):
239
+
240
+ # general
241
+ super ().__init__ (group )
242
+ self .image = pygame .Surface ((CELL_SIZE ,CELL_SIZE ))
243
+ self .image .fill (color )
244
+
245
+ # position
246
+ self .pos = pygame .Vector2 (pos ) + BLOCK_OFFSET
247
+ self .rect = self .image .get_rect (topleft = self .pos * CELL_SIZE )
248
+
249
+ def rotate (self , pivot_pos ):
250
+
251
+ return pivot_pos + (self .pos - pivot_pos ).rotate (90 )
252
+
253
+ def horizontal_collide (self , x , field_data ):
254
+ if not 0 <= x < COLUMNS :
255
+ return True
256
+
257
+ if field_data [int (self .pos .y )][x ]:
258
+ return True
259
+
260
+ def vertical_collide (self , y , field_data ):
261
+ if y >= ROWS :
262
+ return True
263
+
264
+ if y >= 0 and field_data [y ][int (self .pos .x )]:
265
+ return True
266
+
267
+ def update (self ):
268
+
269
+ self .rect .topleft = self .pos * CELL_SIZE
0 commit comments