Begin by installing Pygame, initializing the library, and setting up the game window. Define essential constants such as screen dimensions, grid size, and colors for different elements.
Ensure you have Python installed. Install Pygame using pip:
pip install pygame
Import necessary modules, initialize Pygame, and create the main game window:
import pygame
import random
# Initialize Pygame
pygame.init()
# Constants
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
GRID_SIZE = 30
GRID_WIDTH = 10
GRID_HEIGHT = 20
SIDEBAR_WIDTH = 150
FPS = 60
# Colors (RGB)
BLACK = (0, 0, 0)
WHITE = (255,255,255)
GRAY = (128,128,128)
RED = (255, 0, 0)
GREEN = (0,255, 0)
BLUE = (0, 0,255)
CYAN = (0,255,255)
MAGENTA = (255, 0,255)
YELLOW = (255,255, 0)
ORANGE = (255,165, 0)
# Set up display
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Tetris")
Define the seven standard tetromino shapes (I, O, T, S, Z, J, L) using 2D lists. Assign distinct colors for each shape for visual differentiation.
Each tetromino is represented as a list of lists, indicating occupied cells:
Tetromino | Shape Matrix | Color |
---|---|---|
I | [[1, 1, 1, 1]] | |
O | [[1, 1], [1, 1]] | |
T | [[1, 1, 1], [0, 1, 0]] | |
S | [[0, 1, 1], [1, 1, 0]] | |
Z | [[1, 1, 0], [0, 1, 1]] | |
J | [[1, 0, 0], [1, 1, 1]] | |
L | [[0, 0, 1], [1, 1, 1]] |
Implement rotation logic to rotate tetromino shapes clockwise:
def rotate_shape(shape):
return [list(row)[::-1] for row in zip(*shape)]
Manage the falling of tetrominoes, detect collisions with the grid boundaries or other settled blocks, and lock pieces in place when they can no longer move down.
Implement a function to make the current tetromino fall at a constant rate, increasing speed as the game progresses:
def update_game():
global fall_time, fall_speed, current_tetromino
fall_time += clock.get_rawtime()
clock.tick()
if fall_time / 1000 > fall_speed:
if not move(current_tetromino, 0, 1):
lock_tetromino(current_tetromino, grid)
cleared = clear_lines(grid)
score += cleared * 100
current_tetromino = next_tetromino
next_tetromino = Tetromino(random.choice(SHAPES))
if not can_move(current_tetromino, grid, 0, 0):
game_over = True
fall_time = 0
Check if the tetromino can move to a new position without colliding:
def can_move(tetromino, grid, dx, dy, rotated_shape=None):
shape = rotated_shape if rotated_shape else tetromino.shape
for y, row in enumerate(shape):
for x, cell in enumerate(row):
if cell:
new_x = tetromino.x + x + dx
new_y = tetromino.y + y + dy
if new_x < 0 or new_x >= GRID_WIDTH or new_y >= GRID_HEIGHT:
return False
if new_y >= 0 and grid[new_y][new_x]:
return False
return True
Once a tetromino cannot move further down, lock it into the grid:
def lock_tetromino(tetromino, grid):
for y, row in enumerate(tetromino.shape):
for x, cell in enumerate(row):
if cell:
grid[tetromino.y + y][tetromino.x + x] = tetromino.color
Implement controls to allow the user to move and rotate tetrominoes using arrow keys. Also, add functionality to perform a hard drop.
Handle user events for movement and rotation:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT and can_move(current_tetromino, grid, -1, 0):
current_tetromino.x -= 1
elif event.key == pygame.K_RIGHT and can_move(current_tetromino, grid, 1, 0):
current_tetromino.x += 1
elif event.key == pygame.K_DOWN and can_move(current_tetromino, grid, 0, 1):
current_tetromino.y += 1
elif event.key == pygame.K_UP:
rotated = rotate_shape(current_tetromino.shape)
if can_move(current_tetromino, grid, 0, 0, rotated):
current_tetromino.shape = rotated
elif event.key == pygame.K_SPACE:
while can_move(current_tetromino, grid, 0, 1):
current_tetromino.y += 1
lock_tetromino(current_tetromino, grid)
cleared = clear_lines(grid)
score += cleared * 100
current_tetromino = next_tetromino
next_tetromino = Tetromino(random.choice(SHAPES))
if not can_move(current_tetromino, grid, 0, 0):
game_over = True
Show the current score and the next tetromino to give players foresight:
Implement a scoring system that rewards line clears:
score += cleared * 100
Display the next tetromino in a sidebar:
def draw_next_piece(screen, tetromino):
font = pygame.font.SysFont('comicsans', 30)
label = font.render('Next Piece', 1, WHITE)
screen.blit(label, (GRID_WIDTH * GRID_SIZE + 20, 20))
for y, row in enumerate(tetromino.shape):
for x, cell in enumerate(row):
if cell:
pygame.draw.rect(screen, tetromino.color,
(GRID_WIDTH * GRID_SIZE + 20 + x * GRID_SIZE,
60 + y * GRID_SIZE, GRID_SIZE, GRID_SIZE))
Add a start button to begin the game and display a game over message when the game ends:
start_button = pygame.Rect(GRID_WIDTH * GRID_SIZE + 50, GRID_HEIGHT * GRID_SIZE // 2 - 25, 100, 50)
# Within the event loop
if event.type == pygame.MOUSEBUTTONDOWN and show_start_screen:
if start_button.collidepoint(event.pos):
show_start_screen = False
# Drawing the start button
pygame.draw.rect(screen, WHITE, start_button)
font = pygame.font.SysFont('comicsans', 30)
text = font.render('Start Game', True, BLACK)
screen.blit(text, (start_button.x + 10, start_button.y + 10))
Use Pygame's drawing functions to render the grid, tetrominoes, next piece, and score on the screen:
def draw_grid(screen, grid):
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
cell = grid[y][x]
if cell:
pygame.draw.rect(screen, cell, (x * GRID_SIZE, y * GRID_SIZE, GRID_SIZE, GRID_SIZE))
pygame.draw.rect(screen, GRAY, (x * GRID_SIZE, y * GRID_SIZE, GRID_SIZE, GRID_SIZE), 1)
def draw_tetromino(screen, tetromino):
for y, row in enumerate(tetromino.shape):
for x, cell in enumerate(row):
if cell:
pygame.draw.rect(screen, tetromino.color,
((tetromino.x + x) * GRID_SIZE,
(tetromino.y + y) * GRID_SIZE, GRID_SIZE, GRID_SIZE))
Continuously update the display within the game loop:
pygame.display.flip()
clock.tick(FPS)
Below is the full Python code for a Tetris game using Pygame that incorporates all the necessary features:
import pygame
import random
# Initialize Pygame
pygame.init()
# Constants
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
GRID_SIZE = 30
GRID_WIDTH = 10
GRID_HEIGHT = 20
SIDEBAR_WIDTH = 150
FPS = 60
# Colors (RGB)
BLACK = (0, 0, 0)
WHITE = (255,255,255)
GRAY = (128,128,128)
RED = (255, 0, 0)
GREEN = (0,255, 0)
BLUE = (0, 0,255)
CYAN = (0,255,255)
MAGENTA = (255, 0,255)
YELLOW = (255,255, 0)
ORANGE = (255,165, 0)
# Tetromino shapes
SHAPES = [
[[1, 1, 1, 1]], # I
[[1, 1], [1, 1]], # O
[[1, 1, 1], [0,1,0]], # T
[[0,1,1], [1,1,0]], # S
[[1,1,0], [0,1,1]], # Z
[[1,0,0], [1,1,1]], # J
[[0,0,1], [1,1,1]] # L
]
COLORS = [CYAN, YELLOW, MAGENTA, GREEN, RED, BLUE, ORANGE]
# Function to rotate shapes
def rotate_shape(shape):
return [list(row)[::-1] for row in zip(*shape)]
class Tetromino:
def __init__(self, shape):
self.shape = shape
self.color = COLORS[SHAPES.index(shape)]
self.x = GRID_WIDTH // 2 - len(shape[0]) // 2
self.y = 0
self.rotation = 0
def rotate(self):
self.shape = rotate_shape(self.shape)
def create_grid():
return [[0 for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
def can_move(tetromino, grid, dx, dy, rotated_shape=None):
shape = rotated_shape if rotated_shape else tetromino.shape
for y, row in enumerate(shape):
for x, cell in enumerate(row):
if cell:
new_x = tetromino.x + x + dx
new_y = tetromino.y + y + dy
if new_x < 0 or new_x >= GRID_WIDTH or new_y >= GRID_HEIGHT:
return False
if new_y >= 0 and grid[new_y][new_x]:
return False
return True
def lock_tetromino(tetromino, grid):
for y, row in enumerate(tetromino.shape):
for x, cell in enumerate(row):
if cell:
grid[tetromino.y + y][tetromino.x + x] = tetromino.color
def clear_lines(grid):
lines_cleared = 0
for i in range(len(grid)-1, -1, -1):
if 0 not in grid[i]:
del grid[i]
grid.insert(0, [0 for _ in range(GRID_WIDTH)])
lines_cleared +=1
return lines_cleared
def draw_grid(screen, grid):
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
cell = grid[y][x]
if cell:
pygame.draw.rect(screen, cell, (x * GRID_SIZE, y * GRID_SIZE, GRID_SIZE, GRID_SIZE))
pygame.draw.rect(screen, GRAY, (x * GRID_SIZE, y * GRID_SIZE, GRID_SIZE, GRID_SIZE), 1)
def draw_tetromino(screen, tetromino):
for y, row in enumerate(tetromino.shape):
for x, cell in enumerate(row):
if cell:
pygame.draw.rect(screen, tetromino.color,
((tetromino.x + x) * GRID_SIZE,
(tetromino.y + y) * GRID_SIZE, GRID_SIZE, GRID_SIZE))
def draw_next_piece(screen, tetromino):
font = pygame.font.SysFont('comicsans', 30)
label = font.render('Next Piece', 1, WHITE)
screen.blit(label, (GRID_WIDTH * GRID_SIZE + 20, 20))
for y, row in enumerate(tetromino.shape):
for x, cell in enumerate(row):
if cell:
pygame.draw.rect(screen, tetromino.color,
(GRID_WIDTH * GRID_SIZE + 20 + x * GRID_SIZE,
60 + y * GRID_SIZE, GRID_SIZE, GRID_SIZE))
def main():
screen = pygame.display.set_mode((GRID_WIDTH * GRID_SIZE + SIDEBAR_WIDTH, GRID_HEIGHT * GRID_SIZE))
pygame.display.set_caption('Tetris')
clock = pygame.time.Clock()
grid = create_grid()
current_tetromino = Tetromino(random.choice(SHAPES))
next_tetromino = Tetromino(random.choice(SHAPES))
fall_time = 0
fall_speed = 0.5 # seconds
score = 0
game_over = False
show_start_screen = True
# Start button
start_button = pygame.Rect(GRID_WIDTH * GRID_SIZE + 50, GRID_HEIGHT * GRID_SIZE // 2 - 25, 100, 50)
while True:
dt = clock.tick(FPS) / 1000 # Delta time in seconds
if not show_start_screen:
fall_time += dt
# Piece falling
if fall_time > fall_speed:
fall_time = 0
if not can_move(current_tetromino, grid, 0, 1):
lock_tetromino(current_tetromino, grid)
cleared = clear_lines(grid)
score += cleared * 100
current_tetromino = next_tetromino
next_tetromino = Tetromino(random.choice(SHAPES))
if not can_move(current_tetromino, grid, 0, 0):
game_over = True
else:
current_tetromino.y +=1
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
return
if event.type == pygame.MOUSEBUTTONDOWN and show_start_screen:
if start_button.collidepoint(event.pos):
show_start_screen = False
if event.type == pygame.KEYDOWN and not show_start_screen and not game_over:
if event.key == pygame.K_LEFT:
if can_move(current_tetromino, grid, -1, 0):
current_tetromino.x -=1
elif event.key == pygame.K_RIGHT:
if can_move(current_tetromino, grid, 1, 0):
current_tetromino.x +=1
elif event.key == pygame.K_DOWN:
if can_move(current_tetromino, grid, 0,1):
current_tetromino.y +=1
elif event.key == pygame.K_UP:
rotated = rotate_shape(current_tetromino.shape)
if can_move(current_tetromino, grid, 0,0, rotated_shape=rotated):
current_tetromino.shape = rotated
elif event.key == pygame.K_SPACE:
while can_move(current_tetromino, grid, 0,1):
current_tetromino.y +=1
lock_tetromino(current_tetromino, grid)
cleared = clear_lines(grid)
score += cleared * 100
current_tetromino = next_tetromino
next_tetromino = Tetromino(random.choice(SHAPES))
if not can_move(current_tetromino, grid, 0, 0):
game_over = True
screen.fill(BLACK)
if show_start_screen:
pygame.draw.rect(screen, WHITE, start_button)
font = pygame.font.SysFont('comicsans', 30)
text = font.render('Start Game', True, BLACK)
screen.blit(text, (start_button.x + 10, start_button.y + 10))
else:
draw_grid(screen, grid)
draw_tetromino(screen, current_tetromino)
draw_next_piece(screen, next_tetromino)
font = pygame.font.SysFont('comicsans', 30)
score_text = font.render(f"Score: {score}", True, WHITE)
screen.blit(score_text, (GRID_WIDTH * GRID_SIZE + 20, 200))
if game_over:
font = pygame.font.SysFont('comicsans', 60)
over_text = font.render("GAME OVER", True, RED)
screen.blit(over_text, (50, GRID_HEIGHT * GRID_SIZE // 2 - 30))
pygame.display.flip()
if __name__ == "__main__":
main()
pygame.quit()
By following this comprehensive guide, you can build a fully functional Tetris game in Python using Pygame. The implementation covers game setup, tetromino management, user controls, scoring, and user interface elements like the next piece preview and start button. Further enhancements can include adding sound effects, increasing difficulty levels, and tracking high scores to enrich the gaming experience.