-
Notifications
You must be signed in to change notification settings - Fork 19
/
tetris.py
289 lines (267 loc) · 10 KB
/
tetris.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
#pip install pygame
import sys
import pygame
import random
pygame.init()
# Screen dimensions
WIDTH, HEIGHT = 800, 600
GRID_SIZE = 25 #size of each grid cell
# Colors as defined by rgb values
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
COLORS = [RED, BLUE, GREEN]
# Tetromino shapes - it represents the shapes of the tetris - "J", "L" , "I", "O", "S", "T", "Z".
# The '0' represents a filled cell and '.' represents an empty cell within the Tetromino piece.
# The pieces can be rotated and moved within the game grid.
SHAPES = [
[
['.....',
'.....',
'.....',
'OOOO.',
'.....'],
['.....',
'..O..',
'..O..',
'..O..',
'..O..']
],
[
['.....',
'.....',
'..O..',
'.OOO.',
'.....'],
['.....',
'..O..',
'.OO..',
'..O..',
'.....'],
['.....',
'.....',
'.OOO.',
'..O..',
'.....'],
['.....',
'..O..',
'..OO.',
'..O..',
'.....']
],
[
[
'.....',
'.....',
'..OO.',
'.OO..',
'.....'],
['.....',
'.....',
'.OO..',
'..OO.',
'.....'],
['.....',
'.O...',
'.OO..',
'..O..',
'.....'],
['.....',
'..O..',
'.OO..',
'.O...',
'.....']
],
[
['.....',
'..O..',
'..O.',
'..OO.',
'.....'],
['.....',
'...O.',
'.OOO.',
'.....',
'.....'],
['.....',
'.OO..',
'..O..',
'..O..',
'.....'],
['.....',
'.....',
'.OOO.',
'.O...',
'.....']
],
]
#Tetromino class to represent individual pieces
class Tetromino:
def __init__(self, x, y, shape):
#Initialize tetromino properties
self.x = x
self.y = y
self.shape = shape
self.color = random.choice(COLORS) # You can choose different colors for each shape
self.rotation = 0
#tetris class to manage the game
class Tetris:
def __init__(self, width, height):
#intitialize the tetris game
self.width = width
self.height = height
self.grid = [[0 for _ in range(width)] for _ in range(height)]
self.current_piece = self.new_piece() #Intitialize first tetromino
self.game_over = False
self.score = 0 # Keep track of the player's score
def new_piece(self):
# Choose a random shape
shape = random.choice(SHAPES)
# Return a new Tetromino object
return Tetromino(self.width // 2, 0, shape)
#Check if the current cell in the shape can be placed at a target position in the game grid
#Try catch block is to handle potential IndexErrors that may occur if the target position is out of bounds.
def valid_move(self, piece, x, y, rotation):
"""Check if the piece can move to the given position"""
#Iterate over the cells of the Tetromino shape
for i, row in enumerate(piece.shape[(piece.rotation + rotation) % len(piece.shape)]):
for j, cell in enumerate(row):
try:
if cell == 'O' and (self.grid[piece.y + i + y][piece.x + j + x] != 0):
return False
except IndexError:
return False
return True #valid if all cells in the Tetromino shape can be placed in the grid
#Clear full lines in the game grid and return the number of lines cleared.
def clear_lines(self):
"""Clear the lines that are full and return the number of cleared lines"""
# Iterate through the rows in the game grid, excluding the last row.
lines_cleared = 0
for i, row in enumerate(self.grid[:-1]):
if all(cell != 0 for cell in row):
lines_cleared += 1
del self.grid[i]
self.grid.insert(0, [0 for _ in range(self.width)]) #insert a new line at the top to replace the cleared line.
return lines_cleared
#Lock the current piece in its current position and create a new piece to continue
def lock_piece(self, piece):
"""Lock the piece in place and create a new piece"""
# Iterate over the cells of the Tetromino piece's shape in its current rotation.
for i, row in enumerate(piece.shape[piece.rotation % len(piece.shape)]):
for j, cell in enumerate(row):
if cell == 'O':
self.grid[piece.y + i][piece.x + j] = piece.color
# Clear the lines and update the score
lines_cleared = self.clear_lines()
self.score += lines_cleared * 100 # Update the score based on the number of cleared lines
# Create a new piece
self.current_piece = self.new_piece()
# Check if the game is over
if not self.valid_move(self.current_piece, 0, 0, 0):
self.game_over = True
return lines_cleared #return number of lines cleared in this move.
#Move current Tetromino piece down by one cell if game is not over.
def update(self):
"""Move the tetromino down one cell"""
if not self.game_over:
if self.valid_move(self.current_piece, 0, 1, 0):
self.current_piece.y += 1
else:
self.lock_piece(self.current_piece)
#Draw game grid and the current Tetromino piece on the game screen.
def draw(self, screen):
"""Draw the grid and the current piece"""
for y, row in enumerate(self.grid):
for x, cell in enumerate(row):
if cell:
#draw a rectangle representing the filled cell at appropriate position and size.
pygame.draw.rect(screen, cell, (x * GRID_SIZE, y * GRID_SIZE, GRID_SIZE - 1, GRID_SIZE - 1))
#If there is current piece (game is not over), draw it on game screen.
if self.current_piece:
for i, row in enumerate(
self.current_piece.shape[self.current_piece.rotation % len(self.current_piece.shape)]):
for j, cell in enumerate(row):
if cell == 'O':
#draw a rectangle representing the filled cell at appropriate position and size.
pygame.draw.rect(screen, self.current_piece.color, (
(self.current_piece.x + j) * GRID_SIZE, (self.current_piece.y + i) * GRID_SIZE, GRID_SIZE - 1,
GRID_SIZE - 1))
#Function to draw the score with the given font
def draw_score(screen, score, x, y):
"""Draw the score on the screen"""
font = pygame.font.Font(None, 36)
text = font.render(f"Score: {score}", True, WHITE)
screen.blit(text, (x, y))
#Function to draw game over with the given font
def draw_game_over(screen, x, y):
"""Draw the game over text on the screen"""
font = pygame.font.Font(None, 48)
text = font.render("Game Over", True, RED)
screen.blit(text, (x, y))
#Main function
def main():
# Initialize pygame
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('Tetris')
# Create a clock object
clock = pygame.time.Clock()
# Create a Tetris object
game = Tetris(WIDTH // GRID_SIZE, HEIGHT // GRID_SIZE)
fall_time = 0
fall_speed = 50 #Adjust this value to change the falling speed, it's in milliseconds
while True:
# Fill the screen with black
screen.fill(BLACK)
for event in pygame.event.get():
# Check for the QUIT event
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# Check for the KEYDOWN event
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
if game.valid_move(game.current_piece, -1, 0, 0):
game.current_piece.x -= 1 # Move the piece to the left
if event.key == pygame.K_RIGHT:
if game.valid_move(game.current_piece, 1, 0, 0):
game.current_piece.x += 1 # Move the piece to the right
if event.key == pygame.K_DOWN:
if game.valid_move(game.current_piece, 0, 1, 0):
game.current_piece.y += 1 # Move the piece down
if event.key == pygame.K_UP:
if game.valid_move(game.current_piece, 0, 0, 1):
game.current_piece.rotation += 1 # Rotate the piece
if event.key == pygame.K_SPACE:
while game.valid_move(game.current_piece, 0, 1, 0):
game.current_piece.y += 1 # Move the piece down until it hits the bottom
game.lock_piece(game.current_piece) # Lock the piece in place
# Get the number of milliseconds since the last frame
delta_time = clock.get_rawtime()
# Add the delta time to the fall time
fall_time += delta_time
if fall_time >= fall_speed:
# Move the piece down
game.update()
# Reset the fall time
fall_time = 0
# Draw the score on the screen
draw_score(screen, game.score, 10, 10)
# Draw the grid and the current piece
game.draw(screen)
if game.game_over:
# Draw the "Game Over" message
draw_game_over(screen, WIDTH // 2 - 100, HEIGHT // 2 - 30) # Draw the "Game Over" message
# You can add a "Press any key to restart" message here
# Check for the KEYDOWN event
if event.type == pygame.KEYDOWN:
# Create a new Tetris object
game = Tetris(WIDTH // GRID_SIZE, HEIGHT // GRID_SIZE)
# Update the display
pygame.display.flip()
# Set the framerate
clock.tick(60)
#Call main function to start the Tetris game when this script is executed.
if __name__ == "__main__":
main()