Step‑by‑Step Guide to Building a Tetris Game Using Python Tkinter
This tutorial walks through creating a Tetris game in Python using the built‑in Tkinter library, covering window setup, drawing the board, implementing block movement, rotation, line clearing, scoring, and full source code with explanations.
1. Basic Interface
The project uses Python's built‑in tkinter library, requiring no third‑party packages and runs on Python 3. A simple window is created with three lines of code:
<code>import tkinter as tk
win = tk.Tk()
win.mainloop()</code>Next a Canvas is added to serve as the Tetris playfield. Parameters such as rows (R = 20), columns (C = 12) and cell size (30 px) are defined, and the canvas is created accordingly.
<code>import tkinter as tk
cell_size = 30
C = 12
R = 20
height = R * cell_size
width = C * cell_size
win = tk.Tk()
canvas = tk.Canvas(win, width=width, height=height)
canvas.pack()
win.mainloop()</code>A function draw_cell_by_cr draws a single square on the canvas, and draw_blank_board fills the whole board with light‑gray cells.
<code>def draw_cell_by_cr(canvas, c, r, color="#CCCCCC"):
x0 = c * cell_size
y0 = r * cell_size
x1 = x0 + cell_size
y1 = y0 + cell_size
canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2)</code>2. Making the Interface Dynamic
Two tasks are needed: periodic screen refresh and moving the falling block. A simple game loop updates the window every FPS milliseconds.
<code>FPS = 500 # refresh interval in ms
def game_loop():
win.update()
win.after(FPS, game_loop)
win.after(FPS, game_loop)
win.mainloop()</code>Block movement is handled by draw_block_move , which clears the block at its old position and redraws it at the new coordinates.
<code>def draw_block_move(canvas, block, direction=[0, 0]):
shape_type = block['kind']
c, r = block['cr']
cell_list = block['cell_list']
draw_cells(canvas, c, r, cell_list) # erase old
dc, dr = direction
new_c, new_r = c + dc, r + dr
block['cr'] = [new_c, new_r]
draw_cells(canvas, new_c, new_r, cell_list, SHAPESCOLOR[shape_type])</code>3. Generating, Moving, Fixing, and Clearing Blocks
Random block generation uses random.choice on the SHAPES dictionary. Each block is stored as a dictionary with its type, cell list, and top‑left coordinate.
<code>def generate_new_block():
kind = random.choice(list(SHAPES.keys()))
cr = [C // 2, 0]
return {'kind': kind, 'cell_list': SHAPES[kind], 'cr': cr}</code>The check_move function verifies whether a block can move in a given direction, considering board boundaries and already fixed cells stored in block_list .
<code>def check_move(block, direction=[0, 0]):
cc, cr = block['cr']
for cell_c, cell_r in block['cell_list']:
c = cell_c + cc + direction[0]
r = cell_r + cr + direction[1]
if c < 0 or c >= C or r >= R:
return False
if r >= 0 and block_list[r][c]:
return False
return True</code>When a block can no longer move down, it is saved to block_list via save_block_to_list . Horizontal movement is bound to the left/right arrow keys, and rotation to the up arrow.
<code>def horizontal_move_block(event):
direction = [0, 0]
if event.keysym == 'Left':
direction = [-1, 0]
elif event.keysym == 'Right':
direction = [1, 0]
else:
return
if current_block and check_move(current_block, direction):
draw_block_move(canvas, current_block, direction)
canvas.bind("<KeyPress-Left>", horizontal_move_block)
canvas.bind("<KeyPress-Right>", horizontal_move_block)</code>Rotation creates a new cell list by swapping coordinates and checks validity before applying.
<code>def rotate_block(event):
global current_block
if not current_block:
return
rotate_list = []
for c, r in current_block['cell_list']:
rotate_list.append([r, -c])
rotated = {'kind': current_block['kind'], 'cell_list': rotate_list, 'cr': current_block['cr']}
if check_move(rotated):
draw_cells(canvas, *current_block['cr'], current_block['cell_list'])
draw_cells(canvas, *current_block['cr'], rotate_list, SHAPESCOLOR[current_block['kind']])
current_block = rotated</code>Landing (hard drop) computes the maximum downward distance without collision and moves the block accordingly.
<code>def land(event):
global current_block
if not current_block:
return
# compute min height to drop
# ... (logic omitted for brevity) ...
if check_move(current_block, down):
draw_block_move(canvas, current_block, down)</code>4. Line Clearing and Scoring
When a row is completely filled, check_row_complete returns True . check_and_clear removes full rows, shifts above rows down, updates the score, and redraws the board.
<code>def check_row_complete(row):
return all(cell != '' for cell in row)
score = 0
win.title(f"SCORES: {score}")
def check_and_clear():
global score
for ri in range(len(block_list)):
if check_row_complete(block_list[ri]):
# shift rows down
for cur in range(ri, 0, -1):
block_list[cur] = block_list[cur-1][:]
block_list[0] = ['' for _ in range(C)]
score += 10
draw_board(canvas, block_list)
win.title(f"SCORES: {score}")</code>5. Complete Source Code
The final script combines all functions, initializes the board, binds key events, starts the game loop, and runs mainloop() . The full code is available on GitHub.
<code>import tkinter as tk
from tkinter import messagebox
import random
cell_size = 30
C = 12
R = 20
height = R * cell_size
width = C * cell_size
FPS = 200
SHAPES = {
"O": [(-1,-1),(0,-1),(-1,0),(0,0)],
"S": [(-1,0),(0,0),(0,-1),(1,-1)],
"T": [(-1,0),(0,0),(0,-1),(1,0)],
"I": [(0,1),(0,0),(0,-1),(0,-2)],
"L": [(-1,0),(0,0),(-1,-1),(-1,-2)],
"J": [(-1,0),(0,0),(0,-1),(0,-2)],
"Z": [(-1,-1),(0,-1),(0,0),(1,0)]
}
SHAPESCOLOR = {
"O": "blue",
"S": "red",
"T": "yellow",
"I": "green",
"L": "purple",
"J": "orange",
"Z": "Cyan"
}
# ... (all functions from the tutorial) ...
win = tk.Tk()
canvas = tk.Canvas(win, width=width, height=height)
canvas.pack()
block_list = [['' for _ in range(C)] for _ in range(R)]
draw_board(canvas, block_list)
canvas.focus_set()
canvas.bind("<KeyPress-Left>", horizontal_move_block)
canvas.bind("<KeyPress-Right>", horizontal_move_block)
canvas.bind("<KeyPress-Up>", rotate_block)
canvas.bind("<KeyPress-Down>", land)
current_block = None
win.after(FPS, game_loop)
win.mainloop()</code>This guide provides a complete, runnable example of a classic Tetris game built entirely with Python's standard library.
Python Programming Learning Circle
A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.