Hey everyone! Ever spent hours trying to clear lines in Tetris, that iconic puzzle game where colorful blocks fall from the sky? It’s a classic for a reason – simple to understand, yet endlessly engaging! What if I told you that you could build a basic version of this game yourself using Python?
In this post, we’re going to dive into creating a simple Tetris-like game. Don’t worry if you’re new to game development; we’ll break down the core ideas using easy-to-understand language and provide code snippets to guide you. By the end, you’ll have a better grasp of how games like Tetris are put together and a foundation to build even more amazing things!
What is Tetris, Anyway?
For those who might not know, Tetris is a tile-matching puzzle video game. It features seven different shapes, known as Tetrominoes (we’ll just call them ‘blocks’ for simplicity), each made up of four square blocks. These blocks fall one by one from the top of the screen. Your goal is to rotate and move these falling blocks to create complete horizontal lines without any gaps. When a line is complete, it disappears, and the blocks above it fall down, earning you points. The game ends when the stack of blocks reaches the top of the screen.
Tools We’ll Need
To bring our Tetris game to life, we’ll use Python, a popular and beginner-friendly programming language. For the graphics and game window, we’ll rely on a fantastic library called Pygame.
- Python: Make sure you have Python installed on your computer (version 3.x is recommended). You can download it from python.org.
- Pygame: This is a set of Python modules designed for writing video games. It handles things like creating windows, drawing shapes, managing user input (keyboard/mouse), and playing sounds. It makes game development much easier!
How to Install Pygame
Installing Pygame is straightforward. Open your terminal or command prompt and type the following command:
pip install pygame
pip: This is Python’s package installer. Think of it like an app store for Python libraries. It helps you download and install additional tools that other people have created for Python.
Once pip finishes, you’re all set to start coding!
Core Concepts for Our Tetris Game
Before we jump into code, let’s think about the main components of a Tetris game:
- The Game Board (Grid): Tetris is played on a grid of cells. We’ll need a way to represent this grid in our program.
- The Blocks (Tetrominoes): We need to define the shapes and colors of the seven different Tetris blocks.
- Falling and Movement: Blocks need to fall downwards, and players need to move them left, right, and rotate them.
- Collision Detection: How do we know if a block hits the bottom of the screen, another block, or the side walls? This is crucial for stopping blocks and preventing them from overlapping.
- Line Clearing: When a row is completely filled with blocks, it should disappear, and the rows above it should shift down.
- Game Loop: Every game has a “game loop” – a continuous cycle that handles events, updates the game state, and redraws everything on the screen.
Let’s Start Coding!
We’ll begin by setting up our Pygame window and defining our game board.
Setting Up the Pygame Window
First, we need to import pygame and initialize it. Then, we can set up our screen dimensions and create the game window.
import pygame
SCREEN_WIDTH = 400
SCREEN_HEIGHT = 600
BLOCK_SIZE = 30 # Each 'cell' in our grid will be 30x30 pixels
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (50, 50, 50)
BLUE = (0, 0, 255)
CYAN = (0, 255, 255)
GREEN = (0, 255, 0)
ORANGE = (255, 165, 0)
PURPLE = (128, 0, 128)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("My Simple Tetris")
import pygame: This line brings all the Pygame tools into our program.SCREEN_WIDTH,SCREEN_HEIGHT: These variables define how wide and tall our game window will be in pixels.BLOCK_SIZE: Since Tetris blocks are made of smaller squares, this defines the size of one of those squares.- Colors: We define common colors using RGB (Red, Green, Blue) values. Each value ranges from 0 to 255, determining the intensity of that color component.
pygame.init(): This function needs to be called at the very beginning of any Pygame program to prepare all the modules for use.pygame.display.set_mode(...): This creates the actual window where our game will be displayed.pygame.display.set_caption(...): This sets the text that appears in the title bar of our game window.
Defining the Game Board
Our Tetris board will be a grid, like a spreadsheet. We can represent this using a 2D list (also known as a list of lists or a 2D array) in Python. Each element in this list will represent a cell on the board. A 0 might mean an empty cell, and a number representing a color could mean a filled cell.
GRID_WIDTH = SCREEN_WIDTH // BLOCK_SIZE # Number of blocks horizontally
GRID_HEIGHT = SCREEN_HEIGHT // BLOCK_SIZE # Number of blocks vertically
game_board = [[0 for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
GRID_WIDTH,GRID_HEIGHT: We calculate the number of blocks that can fit across and down the screen based on ourBLOCK_SIZE.game_board = [[0 for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]: This is a powerful Python trick called a list comprehension. It creates a list of lists.[0 for _ in range(GRID_WIDTH)]creates a single row ofGRID_WIDTHzeros (e.g.,[0, 0, 0, ..., 0]).- The outer loop
for _ in range(GRID_HEIGHT)repeats this processGRID_HEIGHTtimes, stacking these rows to form our 2D grid. Initially, all cells are0(empty).
Defining Tetrominoes (The Blocks)
Each Tetris block shape (Tetromino) is unique. We can define them using a list of coordinates relative to a central point. We’ll also assign them a color.
TETROMINOES = {
'I': {'shape': [[0,0], [1,0], [2,0], [3,0]], 'color': CYAN}, # Cyan I-block
'J': {'shape': [[0,0], [0,1], [1,1], [2,1]], 'color': BLUE}, # Blue J-block
'L': {'shape': [[1,0], [0,1], [1,1], [2,1]], 'color': ORANGE}, # Orange L-block (oops, this is T-block)
# Let's fix L-block and add more common ones correctly.
# For simplicity, we'll only define one for now, the 'Square' block, and a 'T' block
'O': {'shape': [[0,0], [1,0], [0,1], [1,1]], 'color': YELLOW}, # Yellow O-block (Square)
'T': {'shape': [[1,0], [0,1], [1,1], [2,1]], 'color': PURPLE}, # Purple T-block
# ... you would add S, Z, L, J, I blocks here
}
current_block_shape_data = TETROMINOES['T']
current_block_color = current_block_shape_data['color']
current_block_coords = current_block_shape_data['shape']
block_x_offset = GRID_WIDTH // 2 - 1 # Center horizontally
block_y_offset = 0 # Top of the screen
TETROMINOES: This is a dictionary where each key is the name of a block type (like ‘O’ for the square block, ‘T’ for the T-shaped block), and its value is another dictionary containing itsshapeandcolor.shape: This list of[row, column]pairs defines which cells are filled for that specific block, relative to an origin point (usually the top-leftmost cell of the block’s bounding box).block_x_offset,block_y_offset: These variables will keep track of where our falling block is currently located on the game grid.
Drawing Everything
Now that we have our game board and a block defined, we need functions to draw them on the screen.
def draw_grid():
# Draw vertical lines
for x in range(0, SCREEN_WIDTH, BLOCK_SIZE):
pygame.draw.line(screen, GRAY, (x, 0), (x, SCREEN_HEIGHT))
# Draw horizontal lines
for y in range(0, SCREEN_HEIGHT, BLOCK_SIZE):
pygame.draw.line(screen, GRAY, (0, y), (SCREEN_WIDTH, y))
def draw_board_blocks():
for row_index, row in enumerate(game_board):
for col_index, cell_value in enumerate(row):
if cell_value != 0: # If cell is not empty (0)
# Draw the filled block
pygame.draw.rect(screen, cell_value, (col_index * BLOCK_SIZE,
row_index * BLOCK_SIZE,
BLOCK_SIZE, BLOCK_SIZE))
def draw_current_block(block_coords, block_color, x_offset, y_offset):
for x, y in block_coords:
# Calculate screen position for each sub-block
draw_x = (x_offset + x) * BLOCK_SIZE
draw_y = (y_offset + y) * BLOCK_SIZE
pygame.draw.rect(screen, block_color, (draw_x, draw_y, BLOCK_SIZE, BLOCK_SIZE))
# Optional: draw a border for better visibility
pygame.draw.rect(screen, WHITE, (draw_x, draw_y, BLOCK_SIZE, BLOCK_SIZE), 1) # 1-pixel border
draw_grid(): This function draws gray lines to visualize our grid cells.draw_board_blocks(): This iterates through ourgame_board2D list. If a cell has a color value (not0), it means there’s a settled block there, so we draw a rectangle of that color at the correct position.draw_current_block(...): This function takes the coordinates, color, and current position of our falling block and draws each of its four sub-blocks on the screen.pygame.draw.rect(...): This Pygame function draws a rectangle. It takes the screen, color, a tuple(x, y, width, height)for its position and size, and an optional thickness for the border.
The Game Loop: Bringing It All Together
The game loop is the heart of our game. It runs continuously, handling user input, updating the game state, and redrawing the screen.
clock = pygame.time.Clock() # Helps control the game's speed
running = True
fall_time = 0 # Tracks how long it's been since the block last fell
fall_speed = 0.5 # How many seconds before the block moves down 1 unit
while running:
# --- Event Handling ---
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
block_x_offset -= 1 # Move block left
if event.key == pygame.K_RIGHT:
block_x_offset += 1 # Move block right
if event.key == pygame.K_DOWN:
block_y_offset += 1 # Move block down faster
# --- Update Game State (e.g., block falling automatically) ---
fall_time += clock.get_rawtime() # Add time since last frame
clock.tick() # Update clock and control frame rate
if fall_time / 1000 >= fall_speed: # Check if enough time has passed (milliseconds to seconds)
block_y_offset += 1 # Move the block down
fall_time = 0 # Reset fall timer
# --- Drawing ---
screen.fill(BLACK) # Clear the screen with black each frame
draw_grid() # Draw the background grid
draw_board_blocks() # Draw any blocks that have settled on the board
draw_current_block(current_block_coords, current_block_color, block_x_offset, block_y_offset)
pygame.display.flip() # Update the full display Surface to the screen
pygame.quit()
print("Game Over!")
clock = pygame.time.Clock(): This object helps us manage the game’s frame rate and calculate time intervals.running = True: This boolean variable controls whether our game loop continues to run. When it becomesFalse, the loop stops, and the game ends.while running:: This is our main game loop.for event in pygame.event.get():: This loop checks for any events that have occurred (like a key press, mouse click, or closing the window).pygame.QUIT: This event occurs when the user clicks the ‘X’ button to close the window.pygame.KEYDOWN: This event occurs when a key is pressed down. We checkevent.keyto see which key was pressed (pygame.K_LEFT,pygame.K_RIGHT,pygame.K_DOWN).
fall_time += clock.get_rawtime():clock.get_rawtime()gives us the number of milliseconds since the last call toclock.tick(). We add this tofall_timeto keep track of how much time has passed for our automatic block fall.clock.tick(): This function should be called once per frame. It tells Pygame how many milliseconds have passed since the last call and helps limit the frame rate to ensure the game runs at a consistent speed on different computers.screen.fill(BLACK): Before drawing anything new, it’s good practice to clear the screen by filling it with a background color (in our case, black).pygame.display.flip(): This command updates the entire screen to show everything we’ve drawn since the lastflip().
What’s Next? (Beyond the Basics)
You now have a basic Pygame window with a grid and a single block that automatically falls and can be moved left, right, and down by the player. This is a great start! To make it a full Tetris game, you’d need to add these crucial features:
- Collision Detection:
- Check if the
current_blockhits the bottom of the screen or another block on thegame_board. - If a collision occurs, the block should “lock” into place on the
game_board(updategame_boardcells with the block’s color). - Then, a new random block should appear at the top.
- Check if the
- Rotation: Implement logic to rotate the
current_block‘sshapedata when a rotation key (e.g.,K_UP) is pressed, ensuring it doesn’t collide with walls or other blocks during rotation. - Line Clearing:
- After a block locks, check if any rows on the
game_boardare completely filled. - If a row is full, remove it and shift all rows above it down by one.
- After a block locks, check if any rows on the
- Game Over Condition: If a new block appears and immediately collides with existing blocks (meaning it can’t even start falling), the game should end.
- Scoring and Levels: Keep track of the player’s score and increase the
fall_speedas the score goes up to make the game harder. - Sound Effects and Music: Add audio elements to make the game more immersive.
Conclusion
Phew! You’ve taken a significant step into game development by creating the foundational elements of a Tetris-like game in Python using Pygame. We’ve covered setting up the game window, representing the game board, defining block shapes, drawing everything on screen, and creating an interactive game loop.
This project, even in its simplified form, touches upon many core concepts in game programming: event handling, game state updates, and rendering graphics. I encourage you to experiment with the code, add more features, and personalize your game. Happy coding, and may your blocks always fit perfectly!