• Skip to primary navigation
  • Skip to main content
  • Skip to primary sidebar
  • Skip to footer

Learn OpenCV

OpenCV, PyTorch, Keras, Tensorflow examples and tutorials

  • Home
  • Getting Started
    • Installation
    • PyTorch
    • Keras & Tensorflow
    • Resource Guide
  • Courses
    • Opencv Courses
    • CV4Faces (Old)
  • Resources
  • AI Consulting
  • About

Tetris with OpenCV Python

Rohan Nayak Mallick
November 30, 2020 Leave a Comment
Application OpenCV 3 OpenCV 4 Tutorial

November 30, 2020 By Leave a Comment

In this blog post, we’ll create the game of Tetris as shown in the video above.

Tetris

Most readers are probably familiar with Tetris – a popular and addictive video game created by Russian software engineer Alexey Pajitnov in 1984.

Let’s look at the parts of the game and the rules.

Board

The game consists of a board that is 10 cells across and 20 cells high as shown below.

Tetris board
Figure 1 : The Tetris board is a 10 x 20 grid of cells.

Tetris Pieces (a.k.a tetrominoes)

In Tetris, blocks fall from the top of the board vertically down in chunks of 4. The chunks are called tetrominoes but in this post we will simply call them “tetris pieces.”

In Figure 1, we can see a number of Tetris pieces on the bottom of the board (each colored differently), and one blue colored line piece falling down.

There are seven different kinds of pieces. We denote them using the letters “O”, “I”, “S”, “Z”, “L”, “J”, and “T” in our code.

Tetris pieces
Figure 2 : Tetris Pieces
I've partnered with OpenCV.org to bring you official courses in Computer Vision, Machine Learning, and AI! Sign up now and take your skills to the next level!

Official Courses by OpenCV.org

Keyboard Controls

The tetris pieces fall from the top to the bottom of the board.

You can move the pieces by pressing keyboard control keys. In this post, we are using the following keys for controlling the motion of the pieces

  1. Pressing A moves the piece left
  2. D moves the piece right
  3. J rotates piece left
  4. L rotates piece right
  5. I holds the current piece for future use
  6. S moves the piece down by 1 cell. This is also called “soft drop.”
  7. W drops the piece vertically down to the lowest possible cell. It is also called “hard drop.”

Rules of the game

If you make all the cells in one row full by moving and placing the pieces intelligently as they fall, the full line clears and you get points based on how many lines you clear.

If a single line is cleared by an action, you receive 40 points. If two lines are cleared in one shot, you receive 100 points, and if three lines are cleared you get 300 points.

Figure 3: Tetris line clear example. On the left we show four lines are completely full because of the blue block on the right most column. This state changes to the one shown on the right giving the user a TETRIS or 1200 points.

Clearing 4 lines in a single shot gets you 1200 points! This is called a TETRIS and is the highest score you can get in one shot. This is shown in Figure 3.

Your objective is to score as many points as possible before the pile of tetris pieces grows too high.

Creating Tetris using OpenCV and numpy

Let’s see how we can use OpenCV’s drawing functions and keyboard handler along with numpy to create the game of Tetris.

Download Code To easily follow along this tutorial, please download code by clicking on the button below. It's FREE!

Download Code

First, we will import some standard libraries.

import cv2
import numpy as np
from random import choice

Now, we will make a board, initialize some other variables, and define the parameter SPEED to be the speed at which the tetris pieces fall.

SPEED = 1 # Controls the speed of the tetris pieces

# Make a board

board = np.uint8(np.zeros([20, 10, 3]))

# Initialize some variables

quit = False
place = False
drop = False
switch = False
held_piece = ""
flag = 0
score = 0

Tetris pieces can have one of seven different shapes.

Seven Kinds of Tetris Pieces

# All the tetris pieces
next_piece = choice(["O", "I", "S", "Z", "L", "J", "T"])

A new Tetris piece always appears in a specific location on the screen.

Next, we will write a function that

  1. Creates a tetris piece
  2. Assigns a color to the piece.

Below we have a function that gets the spawn location and color of a given tetris piece.

def get_info(piece):
    if piece == "I":
        coords = np.array([[0, 3], [0, 4], [0, 5], [0, 6]])
        color = [255, 155, 15]
    elif piece == "T":
        coords = np.array([[1, 3], [1, 4], [1, 5], [0, 4]])
        color = [138, 41, 175]
    elif piece == "L":
        coords = np.array([[1, 3], [1, 4], [1, 5], [0, 5]])
        color = [2, 91, 227]
    elif piece == "J":
        coords = np.array([[1, 3], [1, 4], [1, 5], [0, 3]])
        color = [198, 65, 33]
    elif piece == "S":
        coords = np.array([[1, 5], [1, 4], [0, 3], [0, 4]])
        color = [55, 15, 215]
    elif piece == "Z":
        coords = np.array([[1, 3], [1, 4], [0, 4], [0, 5]])
        color = [1, 177, 89]
    else:
        coords = np.array([[0, 4], [0, 5], [1, 4], [1, 5]])
        color = [2, 159, 227]
    
    return coords, color

Display board

Let’s now write a function for displaying the board and capturing keyboard events.

def display(board, coords, color, next_info, held_info, score, SPEED):
    # Generates the display
    
    border = np.uint8(127 - np.zeros([20, 1, 3]))
    border_ = np.uint8(127 - np.zeros([1, 34, 3]))
    
    dummy = board.copy()
    dummy[coords[:,0], coords[:,1]] = color
    
    right = np.uint8(np.zeros([20, 10, 3]))
    right[next_info[0][:,0] + 2, next_info[0][:,1]] = next_info[1]
    left = np.uint8(np.zeros([20, 10, 3]))
    left[held_info[0][:,0] + 2, held_info[0][:,1]] = held_info[1]
    
    dummy = np.concatenate((border, left, border, dummy, border, right, border), 1)
    dummy = np.concatenate((border_, dummy, border_), 0)
    dummy = dummy.repeat(20, 0).repeat(20, 1)
    dummy = cv2.putText(dummy, str(score), (520, 200), cv2.FONT_HERSHEY_DUPLEX, 1, [0, 0, 255], 2)
    
    # Instructions for the player
    
    dummy = cv2.putText(dummy, "A - move left", (45, 200), cv2.FONT_HERSHEY_DUPLEX, 0.6, [0, 0, 255])
    dummy = cv2.putText(dummy, "D - move right", (45, 225), cv2.FONT_HERSHEY_DUPLEX, 0.6, [0, 0, 255])
    dummy = cv2.putText(dummy, "S - move down", (45, 250), cv2.FONT_HERSHEY_DUPLEX, 0.6, [0, 0, 255])
    dummy = cv2.putText(dummy, "W - hard drop", (45, 275), cv2.FONT_HERSHEY_DUPLEX, 0.6, [0, 0, 255])
    dummy = cv2.putText(dummy, "J - rotate left", (45, 300), cv2.FONT_HERSHEY_DUPLEX, 0.6, [0, 0, 255])
    dummy = cv2.putText(dummy, "L - rotate right", (45, 325), cv2.FONT_HERSHEY_DUPLEX, 0.6, [0, 0, 255])
    dummy = cv2.putText(dummy, "I - hold", (45, 350), cv2.FONT_HERSHEY_DUPLEX, 0.6, [0, 0, 255])
    
    cv2.imshow("Tetris", dummy)
    key = cv2.waitKey(int(1000/SPEED))
    
    return key

Main Loop

This is the main part of the code. We have a while loop where at every iteration we place a new piece in the game.

In Tetris, you can press a certain key to hold a piece. A piece that is held is such a way is available to be used in the future by swapping it with the current piece.

In the code below, we first check if the user wants to swap the current piece with the held piece using the switch variable.

if __name__ == "__main__":
    while not quit:
        # Check if user wants to swap held and current pieces
        if switch:
           # swap held_piece and current_piece
            held_piece, current_piece = current_piece, held_piece
            switch = False

If the switch variable is set to false, we assign the current_piece to the next_piece and randomly choose a new next_piece.

        else:
            # Generates the next piece and updates the current piece
            current_piece = next_piece
            next_piece = choice(["I", "T", "L", "J", "Z", "S", "O"])
        
        if flag > 0:
            flag -= 1

Next, we determine the color and position of the current_piece, next_piece, and the held_piece.

        # Determines the color and position of the current, next, and held pieces
        if held_piece == "":
            held_info = np.array([[0, 0]]), [0, 0, 0]
        else:
           held_info = get_info(held_piece)
        
        next_info = get_info(next_piece)

        coords, color = get_info(current_piece)
        if current_piece == "I":
            top_left = [-2, 3]

This if statement just checks if the game needs to be terminated (i.e., the tetris pieces have stacked too high), and we do this by checking if the next piece’s spawn location doesn’t overlap with another piece.

if not np.all(board[coords[:,0], coords[:,1]] == 0):
    break

Next, we add another while loop inside of the main one. Each iteration of this new loop corresponds to the piece moving down by one block.

First, we show the board using our display() function and receive the keyboard input.

We also make a copy of the original position.

while True:
    # Shows the board and gets the key press
    key = display(board, coords, color, next_info, held_info, score, SPEED)
    # Create a copy of the position
    dummy = coords.copy()

The key variable above stores the ASCII code for the pressed keyboard entry. Depending on which key was pressed, we take different actions.

The a and d keys control the left and right movement of the piece.

if key == ord("a"):
    # Moves the piece left if it isn't against the left wall
    if np.min(coords[:,1]) > 0:
        coords[:,1] -= 1
    if current_piece == "I":
        top_left[1] -= 1
elif key == ord("d"):
    # Moves the piece right if it isn't against the right wall
    if np.max(coords[:,1]) < 9:
        coords[:,1] += 1
        if current_piece == "I":
            top_left[1] += 1

The keys j and l are used to rotate the pieces.

To code rotation, we have three kinds of pieces to deal with – the square piece, the line piece, and all others.

For the square piece, rotating is simple; you don’t do anything!

For anything that’s not a square piece, we can inscribe the piece in a square, which we can rotate.

The line piece is inscribed in a 4×4 square and not a 3×3 square, and therefore we need to treat it differently.

elif key == ord("j") or key == ord("l"):
    # Rotation mechanism
    # arr is the array of nearby points which get rotated and pov is the indexes of the blocks within arr
    
    if current_piece != "I" and current_piece != "O":
        if coords[1,1] > 0 and coords[1,1] < 9:
            arr = coords[1] - 1 + np.array([[[x, y] for y in range(3)] for x in range(3)])
            pov = coords - coords[1] + 1
            
    elif current_piece == "I":
        # The straight piece has a 4x4 array, so it needs seperate code
        
        arr = top_left + np.array([[[x, y] for y in range(4)] for x in range(4)])
        pov = np.array([np.where(np.logical_and(arr[:,:,0] == pos[0], arr[:,:,1] == pos[1])) for pos in coords])
        pov = np.array([k[0] for k in np.swapaxes(pov, 1, 2)])
    
    # Rotates the array and repositions the piece to where it is now
    
    if current_piece != "O":
        if key == ord("j"):
            arr = np.rot90(arr, -1)
        else:
            arr = np.rot90(arr)
        coords = arr[pov[:,0], pov[:,1]]

Lastly, we will handle the w, i, DELETE, and ESC keys.

Pressing w implements a hard drop. Pressing i holds the piece.

The DELETE and ESC keys end the program.

elif key == ord("w"):
    # Hard drop set to true
    drop = True
elif key == ord("i"):
    # Goes out of the loop and tells the program to switch held and current pieces
    if flag == 0:
        if held_piece == "":
            held_piece = current_piece
        else:
            switch = True
        flag = 2
        break
elif key == 8 or key == 27:
    quit = True
    break

We next need to check for collisions with other pieces and prevent the piece from moving into or rotating into another piece.

If such a collision occurs, we change the new position back to the original one using our copy of the coords stored in the dummy variable.

# Checks if the piece is overlapping with other pieces or if it's outside the board, and if so, changes the position to the position before anything happened
            
if np.max(coords[:,0]) < 20 and np.min(coords[:,0]) >= 0:
    if not (current_piece == "I" and (np.max(coords[:,1]) >= 10 or np.min(coords[:,1]) < 0)):
        if not np.all(board[coords[:,0], coords[:,1]] == 0):
            coords = dummy.copy()
    else:
        coords = dummy.copy()
else:
    coords = dummy.copy()

Finally, we code the “hard drop.” We use a while loop to check if the piece can move one step down, and stop moving down if it collides with an existing piece or reaches the bottom of the board.

if drop:
    # Every iteration of the loop moves the piece down by 1 and if the piece is resting on the ground or another piece, then it stops and places it
    
    while not place:
        if np.max(coords[:,0]) != 19:
            # Checks if the piece is resting on something
            for pos in coords:
                if not np.array_equal(board[pos[0] + 1, pos[1]], [0, 0, 0]):
                    place = True
                    break
        else:
            # If the position of the piece is at the ground level, then it places
            place = True
        
        if place:
            break
        
        # Keeps going down and checking when the piece needs to be placed
        
        coords[:,0] += 1
        score += 1
        if current_piece == "I":
            top_left[0] += 1
            
    drop = False

If we don’t hard drop, then we just need to check if the piece needs to be placed (i.e. stop moving). A piece is placed when the piece either reaches the bottom of the board or hits another piece.

If none of the above cases apply, we move the piece down by one.

else:
    # Checks if the piece needs to be placed
    if np.max(coords[:,0]) != 19:
        for pos in coords:
            if not np.array_equal(board[pos[0] + 1, pos[1]], [0, 0, 0]):
                place = True
                break
    else:
        place = True
    
if place:
    # Places the piece where it is on the board
    for pos in coords:
        board[tuple(pos)] = color
        
    # Resets place to False
    place = False
    break

# Moves down by 1

coords[:,0] += 1
if key == ord("s"):
    score += 1
if current_piece == "I":
    top_left[0] += 1

Finally, for each iteration of the outer while loop, (aka each time a piece is placed,) we check if any lines were scored and we update the points.

# Clears lines and also counts how many lines have been cleared and updates the score
        
lines = 0
        
for line in range(20):
    if np.all([np.any(pos != 0) for pos in board[line]]):
        lines += 1
        board[1:line+1] = board[:line]
                
if lines == 1:
    score += 40
elif lines == 2:
    score += 100
elif lines == 3:
    score += 300
elif lines == 4:
    score += 1200

So, that’s it! Thank you for reading this post and I hope you enjoyed.

Subscribe & Download Code

If you liked this article and would like to download code (C++ and Python) and example images used in this post, please subscribe to our newsletter. You will also receive a free Computer Vision Resource Guide. In our newsletter, we share OpenCV tutorials and examples written in C++/Python, and Computer Vision and Machine Learning algorithms and news.

Subscribe Now


Tags: OpenCV Python tetris

Filed Under: Application, OpenCV 3, OpenCV 4, Tutorial

About

I am an entrepreneur with a love for Computer Vision and Machine Learning with a dozen years of experience (and a Ph.D.) in the field.

In 2007, right after finishing my Ph.D., I co-founded TAAZ Inc. with my advisor Dr. David Kriegman and Kevin Barnes. The scalability, and robustness of our computer vision and machine learning algorithms have been put to rigorous test by more than 100M users who have tried our products. Read More…

Getting Started

  • Installation
  • PyTorch
  • Keras & Tensorflow
  • Resource Guide

Resources

Download Code (C++ / Python)

ENROLL IN OFFICIAL OPENCV COURSES

I've partnered with OpenCV.org to bring you official courses in Computer Vision, Machine Learning, and AI.
Learn More

Recent Posts

  • RAFT: Optical Flow estimation using Deep Learning
  • Making A Low-Cost Stereo Camera Using OpenCV
  • Optical Flow in OpenCV (C++/Python)
  • Introduction to Epipolar Geometry and Stereo Vision
  • Depth Estimation using Stereo matching

Disclaimer

All views expressed on this site are my own and do not represent the opinions of OpenCV.org or any entity whatsoever with which I have been, am now, or will be affiliated.

GETTING STARTED

  • Installation
  • PyTorch
  • Keras & Tensorflow
  • Resource Guide

COURSES

  • Opencv Courses
  • CV4Faces (Old)

COPYRIGHT © 2020 - BIG VISION LLC

Privacy Policy | Terms & Conditions

We use cookies to ensure that we give you the best experience on our website. If you continue to use this site we will assume that you are happy with it.AcceptPrivacy policy