Python Tetris Game – Develop Tetris using PyGame

Python course with 57 real-time projects - Learn Python

Have you ever played the Tetris puzzle? It is a video game that is developed with a motive to get an order out of chaos. It helps in improving both thinking and learning skills. Wouldn’t it be interesting to build this game in Python? So, let’s start.

What is Tetris?

The Tetris game consists of blocks/tetriminos, of different shapes. that appear one after another. And the objective is to place these blocks on the screen such that we form rows at the bottom of the board.

Every completed row increases the score. To do so, the player will be allowed to move the falling block to left, right, down and also rotate it using the keyboard buttons.

Python Tetris – Project details

To build this game we will be using the Pygame module in Python. We will also use the random module to select the shape and color of the Tetrimino. And we consider the whole game board and the blocks as matrices. This makes it easy to do the operations, shifting, and rotation.

Download Python Tetris Code

Please download the code for the Tetris game here: Python Tetris Game Project Source Code

Project Prerequisites

It is suggested to have prior knowledge on Python and PyGame. If you don’t have the PyGame module, then you can install it using the following command.

pip install pygame

Steps to build the Python Tetris Game

We are done discussing the necessities of the project, let us look into the steps to be followed to build the project.

1. Create a matrix storing the information about the block and write a class to handle these details.

2. Create a class to handle the creation of blocks, their movement, and placement.

3. Create the main window for the game.

4. Keep checking the button pressed and the status of the blocks in the board.

5. End the game when the blocks touch the top of the board.

This game is written in an infinite while loop that runs till the fifth condition above is met.

1. Importing the required modules

We are importing the pygame module which we use to build the Tetris game and then importing the random module to get the shapes of the blocks in a random manner.

import pygame
import random

2. Creating variables to store shapes and colors of the blocks

In the below code, the shapes variable holds the matrix that contains information about the shape of the block. Assume a 4×4 block and give each cell indices as shown below

0123
4567
891011
12131415

Then, [1, 5, 9, 13] represents the vertical line (second column),  [1, 5, 9, 8] represents L shape, etc. And the shapeColors hold the RGB values of different colors from which we randomly select a color for a block.

#Shapes of the blocks
shapes = [
        [[1, 5, 9, 13], [4, 5, 6, 7]],
        [[4, 5, 9, 10], [2, 6, 5, 9]],
        [[6, 7, 9, 10], [1, 5, 6, 10]],
        [[1, 2, 5, 9], [0, 4, 5, 6], [1, 5, 9, 8], [4, 5, 6, 10]],
        [[1, 2, 6, 10], [5, 6, 7, 9], [2, 6, 10, 11], [3, 5, 6, 7]],
        [[1, 4, 5, 6], [1, 4, 5, 9], [4, 5, 6, 9], [1, 5, 6, 9]],
        [[1, 2, 5, 6]],
    ]
#Colors of the blocks
shapeColors = [(0, 255, 0), (255, 0, 0), (0, 255, 255), (255, 255, 0), (255, 165, 0), (0, 0, 255), (128, 0, 128)]

3. Creating a class Blocks to hold information about the current block

This class assigns the coordinates, shape, color and rotation to the block object created. Using the function random.randint(), which generates a random integer in the given range, a random shape and color are assigned to a block.

The function rotate() gives the index of the rotation variant of a given shape from the matrix ‘shapes’. And the function image() returns the rotated variant block to the Block object.

# GLOBALS VARS
width = 700
height = 600
gameWidth = 100  
gameHeight = 400 
blockSize = 20
 
topLeft_x = (width - gameWidth) // 2
topLeft_y = height - gameHeight - 50


class Block:
    x = 0
    y = 0
    n = 0
    def __init__(self, x, y,n):
        self.x = x
        self.y = y
        self.type = n
        self.color = n
        self.rotation = 0
    def image(self):
        return shapes[self.type][self.rotation]

    def rotate(self):
        self.rotation = (self.rotation + 1) % len(shapes[self.type])

4. Creating a class Tetris for creation of blocks, controlling their movement and placement

In this class, we first create variables that set the properties of the board and also set the score to 0 and state to “start”. The field variable stores the board in the form of a 2D matrix.

# GLOBALS VARS
width = 700
height = 600
gameWidth = 100  
gameHeight = 400 
blockSize = 20
 
topLeft_x = (width - gameWidth) // 2
topLeft_y = height - gameHeight - 50


class Block:
    x = 0
    y = 0
    n = 0
    def __init__(self, x, y,n):
        self.x = x
        self.y = y
        self.type = n
        self.color = n
        self.rotation = 0
    def image(self):
        return shapes[self.type][self.rotation]

    def rotate(self):
        self.rotation = (self.rotation + 1) % len(shapes[self.type])

In the following code, which is a part of the Tetris class, has the below functions

1. The new_block() function creates a new block using the Block() class.

2. The function next_block() creates the next block that would be appearing on the screen.  

3. The intersects() function checks if the blocks touch the top of the board. This decides the end of the game.

4. The break_lines() function checks if the blocks form any row. If the condition is met, then it increases the score and deletes the line.

5. And the function draw_next_block() places the next block beside the main game to show it to the player.

#Creates a new block
    def new_block(self):
        self.block = Block(3, 0,random.randint(0, len(shapes) - 1))
                           
    def next_block(self):
        self.nextBlock=Block(3,0,random.randint(0, len(shapes) - 1))
    #Checks if the blocks touch the top of the board
    def intersects(self):
        intersection = False
        for i in range(4):
            for j in range(4):
                if i * 4 + j in self.block.image():
                    if i + self.block.y > self.height - 1 or \
                            j + self.block.x > self.width - 1 or \
                            j + self.block.x < 0 or \
                            self.field[i + self.block.y][j + self.block.x] > 0:
                        intersection = True
        return intersection

    #Checks if a row is formed and destroys that line
    def break_lines(self):
        lines = 0
        for i in range(1, self.height):
            zeros = 0
            for j in range(self.width):
                if self.field[i][j] == 0:
                    zeros += 1
            if zeros == 0:
                lines += 1
                for i1 in range(i, 1, -1):
                    for j in range(self.width):
                        self.field[i1][j] = self.field[i1 - 1][j]
        self.score += lines ** 2

def draw_next_block(self,screen):
    
        font = pygame.font.SysFont("Calibri", 30)
        label = font.render("Next Shape", 1, (128,128,128))

        sx = topLeft_x + gameWidth + 50
        sy = topLeft_y + gameHeight/2 - 100
        format = self.nextBlock.image()
        for i in range(4):
                for j in range(4):
                    p = i * 4 + j
                    if p in self.nextBlock.image():
                        pygame.draw.rect(screen, shapeColors[self.nextBlock.color],(sx + j*30, sy + i*30, 30, 30), 0)

Now it’s time to write the functions to move the blocks.

1. The go_down() function moves the block down by a unit

2. The go_space() function is similar to go_down(), but it moves the block down to the bottom

3. The freez() function runs once the block reaches the bottom. And it checks if any row is formed, forms a new block and if it touches the top, it ends the game.

4. The go_side() function moves the block to either right or left

5. And the rotate() function rotates the block by 90 degrees in clockwise or anticlockwise direction

#Moves the block down by a unit
def go_down(self):
    self.block.y += 1
    if self.intersects():
        self.block.y -= 1
        self.freeze()

#Moves the block to the bottom
def go_space(self):
    while not self.intersects():
        self.block.y += 1
    self.block.y -= 1
    self.freeze()

# This function runs once the block reaches the bottom. 
def freeze(self):
    for i in range(4):
        for j in range(4):
            if i * 4 + j in self.block.image():
                self.field[i + self.block.y][j + self.block.x] = self.block.color
    self.break_lines() #Checking if any row is formed
    self.new_block() #Creating a new block
    if self.intersects(): #If blocks touch the top of the board, then ending the game by setting status as gameover
        self.state = "gameover"
#This function moves the block horizontally
def go_side(self, dx):
    old_x = self.block.x
    self.block.x += dx
    if self.intersects():
        self.block.x = old_x
#This function rotates the block 
def rotate(self):
    old_rotation = self.block.rotation
    self.block.rotate()
    if self.intersects():
        self.block.rotation = old_rotation

Main game

It’s time to create the main window. We do this by using the init() function in PyGame. We also set the other properties like size, and title. When the user presses any of the keyboard buttons, the main game starts.

pygame.font.init()
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Tetris by DataFlair")
run = True
while run:
    screen.fill((16, 57, 34 ))
    font = pygame.font.SysFont("Calibri", 70, bold=True)
    label = font.render("Press any key to begin!", True, '#FFFFFF')

    screen.blit(label, (10, 300 ))
    pygame.display.update()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        if event.type == pygame.KEYDOWN:
            startGame()
pygame.quit()

Now, write the function startGame()  with a for loop that runs infinitely till the game end condition is met. This loop includes the following operations:

1. Creating a new block if there is no moving block.

2. Moving the block down by a unit continuously or when the down key is pressed.

3. Checking the key pressed, if any, and doing the corresponding function discussed above.

4. Updating the board with the previous blocks and the moving block.

5. Checking the status and ending the game if the status is gameover

def startGame():
    done = False
    clock = pygame.time.Clock()
    fps = 25
    game = Tetris(20, 10)
    counter = 0

    pressing_down = False
    
    while not done:
        #Create a new block if there is no moving block
        if game.block is None:
            game.new_block()
        if game.nextBlock is None:
            game.next_block()
        counter += 1 #Keeping track of the time 
        if counter > 100000:
            counter = 0

        #Moving the block continuously with time or when down key is pressed
        if counter % (fps // game.level // 2) == 0 or pressing_down:
            if game.state == "start":
                game.go_down()
        #Checking which key is pressed and running corresponding function
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_UP:
                    game.rotate()
                if event.key == pygame.K_DOWN:
                    game.moveDown()
                if event.key == pygame.K_LEFT:
                    game.moveHoriz(-1)
                if event.key == pygame.K_RIGHT:
                    game.moveHoriz(1)
                if event.key == pygame.K_SPACE:
                    game.moveBottom()
                if event.key == pygame.K_ESCAPE:
                    game.__init__(20, 10)

        screen.fill('#FFFFFF')

        #Updating the game board regularly
        for i in range(game.height):
            for j in range(game.width):
                pygame.draw.rect(screen, '#B2BEB5', [game.x + game.zoom * j, game.y + game.zoom * i, game.zoom, game.zoom], 1)
                if game.field[i][j] > 0:
                    pygame.draw.rect(screen, shapeColors[game.field[i][j]],
                                     [game.x + game.zoom * j + 1, game.y + game.zoom * i + 1, game.zoom - 2, game.zoom - 1])

        #Updating the board with the moving block
        if game.block is not None:
            for i in range(4):
                for j in range(4):
                    p = i * 4 + j
                    if p in game.block.image():
                        pygame.draw.rect(screen, shapeColors[game.block.color],
                                         [game.x + game.zoom * (j + game.block.x) + 1,
                                          game.y + game.zoom * (i + game.block.y) + 1,
                                          game.zoom - 2, game.zoom - 2])

        #Showing the score
        font = pygame.font.SysFont('Calibri', 40, True, False)
        font1 = pygame.font.SysFont('Calibri', 25, True, False)
        text = font.render("Score: " + str(game.score), True, '#000000')
        text_game_over = font.render("Game Over", True, '#000000')
        text_game_over1 = font.render("Press ESC", True, '#000000')

        #Ending the game if state is gameover
        screen.blit(text, [300, 0])
        if game.state == "gameover":
            screen.blit(text_game_over, [300, 200])
            screen.blit(text_game_over1, [300, 265])
       
        game.draw_next_block(screen)

        pygame.display.flip()
        clock.tick(fps)    

Python Tetris Game Output

python tetris game output

Summary

With this Python project, we have successfully built the python tetris game. For this, we used the pygame and random libraries. We learned how to create shapes for blocks, capture events from the keyboard and trigger a function. We could also check the status of the Tetris board. Hoping that you enjoyed developing this with us!

Your opinion matters
Please write your valuable feedback about DataFlair on Google

follow dataflair on YouTube

4 Responses

  1. Márton says:

    whenever i try to run the terminal says ‘Tetris’ object has no attribute ‘go_down’

  2. Mohammad Huzaif says:

    when i try to run it says Tetris object has no attribute go_down

  3. Barry Waldner says:

    change line 187 from “go_down()” to “moveDown()

    I got it working, butt wasn’t able to get the 1×4 (light green) to stay in the game, it would disappear whenever it was placed without removing any lines, it just disappeared.

Leave a Reply

Your email address will not be published. Required fields are marked *