Game Development 26 min read

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.

Python Programming Learning Circle
Python Programming Learning Circle
Python Programming Learning Circle
Step‑by‑Step Guide to Building a Tetris Game Using Python Tkinter

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.

GUIPythonGame developmentTutorialTetrisTkinter
Python Programming Learning Circle
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.