Nanfeng

Notes on software development, code, and curious ideas

Building Tetris with Python and Pygame

This Pygame project implements the essential parts of Tetris: seven tetromino shapes, rotation states, grid collision, falling pieces, line removal, scoring, increasing speed, and keyboard controls.

Install Pygame:

1
python -m pip install pygame

Board representation

Represent the fixed blocks as a matrix. None means empty; a color tuple means occupied:

1
2
3
4
5
6
7
8
GRID_SIZE = 20
BOARD_COLUMNS = 15
BOARD_ROWS = 25

board = [
[None] * BOARD_COLUMNS
for _ in range(BOARD_ROWS)
]

Tetromino definitions

Each rotation is a list of row and column offsets from a center point:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SHAPES = {
'I': [
[(0, -1), (0, 0), (0, 1), (0, 2)],
[(-1, 0), (0, 0), (1, 0), (2, 0)]
],
'O': [
[(0, 0), (0, 1), (1, 0), (1, 1)]
],
'T': [
[(0, -1), (0, 0), (0, 1), (-1, 0)],
[(-1, 0), (0, 0), (1, 0), (0, 1)],
[(0, -1), (0, 0), (0, 1), (1, 0)],
[(-1, 0), (0, 0), (1, 0), (0, -1)]
]
# Define J, L, S, and Z in the same format.
}

Collision and movement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def occupied_cells(shape_name, rotation, center):
row, column = center
return [
(row + row_offset, column + column_offset)
for row_offset, column_offset in SHAPES[shape_name][rotation]
]


def conflicts(cells):
for row, column in cells:
if not (0 <= row < BOARD_ROWS and 0 <= column < BOARD_COLUMNS):
return True
if board[row][column] is not None:
return True
return False

To move, calculate the candidate cells first and accept the new center only if conflicts() returns false. Rotation follows the same rule and restores the previous rotation when blocked.

Lock a piece and remove full lines

1
2
3
4
5
6
7
8
9
10
11
def lock_piece(cells, color):
for row, column in cells:
board[row][column] = color


def remove_full_lines():
global board
remaining = [row for row in board if any(cell is None for cell in row)]
removed = BOARD_ROWS - len(remaining)
board = [[None] * BOARD_COLUMNS for _ in range(removed)] + remaining
return removed

When downward movement is blocked, lock the current cells, clear full lines, update the score, and create the next random piece. The game ends if the new piece already conflicts at its spawn position.

Controls and timing

Handle K_LEFT, K_RIGHT, K_DOWN, and K_UP for movement and rotation. Space can repeatedly move the piece down for a hard drop. In the main loop, use pygame.time.Clock() and a time accumulator rather than a blocking delay so input and rendering remain responsive.

The original project used a custom font.ttf for Chinese labels. Either provide that file beside the script or use pygame.font.Font(None, size) for the default font.

+