Tetris¶

../_images/tetris.png

In tetris.py, you find a complete Tetris game. It makes heavy use of the Numpy library. The code was created in a coding speedrun; it almost has no structure.

Note

There is also a bug when dropping long bricks to the ground. Maybe some refactoring makes this issue easier to debug.

"""
Tetris

Install dependencies:

    pip install opencv-python
"""
import numpy as np
import cv2
import random

ROWS, COLS = 17, 10
TILE_SIZE = 32
SCREEN_SIZE_X, SCREEN_SIZE_Y = COLS * TILE_SIZE, ROWS * TILE_SIZE

# box with three extra layers of wall for rotations and moves
box = np.ones((ROWS + 2, COLS + 4), dtype=np.uint8)
box[:-3, 3:-3] = 0

# definition of bricks
BRICKS = [
    """
.#.
###
...
""", """
.#.
##.
#..
""", """
.#.
.##
..#
""", """
.##
.#.
.#.
""", """
##.
.#.
.#.
""", """
##
##
""", """
..#.
..#.
..#.
..#.
""", """
#.
#.
"""
]
BRICKS = [
    (np.array([list(row) for row in b.strip().split()]) == "#").astype(np.uint8)
    for b in BRICKS
]


# create first brick
brick = random.choice(BRICKS)
x, y = 5, 0

timer = 150
box_full = False
while not box_full:

    # draw everything
    frame = np.zeros((SCREEN_SIZE_Y, SCREEN_SIZE_X, 3), np.uint8)
    box_cut = box[:-2, 2:-2]
    bb = np.kron(box_cut, np.ones((TILE_SIZE, TILE_SIZE), dtype=np.uint8)) * 192
    frame[:,:,0] = bb
    frame[:,:,1] = bb
    frame[:,:,2] = bb

    br = np.kron(brick, np.ones((TILE_SIZE, TILE_SIZE), dtype=np.uint8)) * 255
    frame[y*TILE_SIZE:y*TILE_SIZE+br.shape[0], (x-2)*TILE_SIZE:(x-2)*TILE_SIZE+br.shape[1],0] += br

    cv2.imshow("Tetris", frame)

    # handle keyboard input
    key = chr(cv2.waitKey(1) & 0xFF)
    newbrick = brick
    newx, newy = x, y
    if key == "d":
        newx += 1
    elif key == "a":
        newx -= 1
    elif key == "s":
        newy += 1
    elif key == "w":
        newbrick = brick.T[::-1]  # swap x & y, then mirror y
    elif key == "q":
        box_full = True

    # place brick in new position if it does not collide
    check = box.copy()
    check[newy:newy+newbrick.shape[0], newx:newx+newbrick.shape[1]] += newbrick
    if not (check == 2).any():
        brick = newbrick
        x, y = newx, newy

    timer -= 1
    if timer == 0:
        # brick drops automatically
        timer = 150
        y += 1
        check = box.copy()
        check[y:y+brick.shape[0], newx:newx+brick.shape[1]] += brick
        if (check == 2).any():
            # brick stops moving, place it in the box
            if y == 1:
                box_full = True
            y -= 1
            box[y:y+brick.shape[0], newx:newx+brick.shape[1]] += brick

            # remove rows
            for row in range(box.shape[0] - 3):
                if box[row].sum() == box.shape[1]:
                    box[1:row + 1] = box[:row]
                    box[0, 3:-3] = 0

            x, y = 4, 0
            brick = random.choice(BRICKS)

cv2.destroyAllWindows()