- This Free Friday I am working on a project on YouTube a Tetris game.
import pygame
import random
import sys
pygame.init()
WIDTH, HEIGHT = 300, 500
FPS = 35
CELL = 20
ROWS = (HEIGHT - 120) // CELL
COLS = WIDTH // CELL
SCREEN = pygame.display.set_mode((WIDTH, HEIGHT), pygame.NOFRAME)
clock = pygame.time.Clock()
pygame.display.set_caption("Tetris")
BLACK = (0,0,0)
WHITE = (255,255, 255)
BG_COLOR = (31, 25, 76)
GRID = (31, 25, 132)
WIN = (50, 230, 50)
LOSE = (252, 91, 122)
ASSETS ={
1: pygame.image.load("Assets/1.png"),
2: pygame.image.load("Assets/2.png"),
3: pygame.image.load("Assets/3.png"),
4: pygame.image.load("Assets/4.png")
}
font = pygame.font.SysFont("verdana", 50)
font2 = pygame.font.SysFont("verdana", 15)
- Initializes Pygame and creates a 300×500 window with no frame.
- Defines constants:
- Grid cell size
- Number of rows/columns
- Colors
- Block images (1.png, 2.png, etc.)
- Creates fonts for text.
- This section prepares everything the game needs before running.
class Shape:
VERSION = {
'I': [[1,5,9,13], [4,5,6,7]],
'Z': [[4,5,9,10], [2,6,5,9]],
'S': [[6,7,9,10], [1,5,6,10]],
'L': [[1,2,6, 10], [0,4,5,6], [1,5,9,8], [4,5,6,10]],
'J': [[1,2,6,10], [5,6,7,9], [2,6,10,11], [3,5,6,7]],
'T': [[1,4,5,6], [1,4,5,9], [ 4,5,6,9], [1,5,6,9]],
'O': [[1,2,5,6]]
}
SHAPES = ['I', 'Z', 'S', 'L', 'J', 'T', 'O']
def __init__(self,x,y):
self.x = x
self.y = y
self.type = random.choice(self.SHAPES)
self.shape = self.VERSION[self.type]
self.color = random.randint(1,4)
self.orientation = 0
def image(self):
return self.shape[self.orientation]
def rotate(self):
self.orientation = (self.orientation + 1) % len(self.shape)
- This class represents a single falling Tetris piece.
- Each shape type (
I, Z, S, L, J, T, O) has predefined rotation patterns.
- A shape has:
x, y position on the grid
- A random type
- A random color (1–4)
- A rotation index (
orientation)
image() returns the current rotation pattern.
rotate() cycles through the available rotations.
- This class handles the behavior of individual tetrominoes.
class Tetris:
def __init__(self, rows, cols):
self.rows = rows
self.cols = cols
self.score = 0
self.level = 1
self.grid = [[0 for j in range(cols)] for i in range(rows)]
self.next = None
self.end = False
self.new_shape()
def make_grid(self):
for i in range(self.rows + 1):
pygame.draw.line(SCREEN, GRID, (0,CELL*i), (WIDTH,CELL*i))
for j in range(self.rows + 1):
pygame.draw.line(SCREEN, GRID, (CELL*j, 0), (CELL*j, HEIGHT-120))
def new_shape(self):
if not self.next:
self.next = Shape(5,0)
self.figure = self.next
self.next = Shape(5,0)
def collision(self) -> bool:
for i in range(4):
for j in range(4):
if (i*4 *j) in self.figure.image():
block_row = i + self.figure.y
block_col = j + self.figure.x
if(block_row >= self.rows or block_col >= self.cols or block_col < 0 or self.grid(block_row) (block_col) > 0):
return True
return False
def freeze(self):
for i in range(4):
for j in range(4):
if (i*4+j) in self.figure.image():
self.grid[i+self.figure.y][j+self.figure.x] = self.figure.color
self.new_shape()
if self.collision():
self.end = True
def move_down(self):
self.figure.y += 1
if self.collision():
self.figure.y -= 1
self.freeze()
def left(self):
self.figure.x -= 1
if self.collision():
self.figure.x += 1
def right(self):
self.figure.y += 1
if self.collision():
self.figure.y -= 1
def freefall(self):
while not self.collision():
self.figure.y += 1
self.figure.y -= 1
self.freeze()
def rotate(self):
orientation = self.figure.orientation
self.figure.rotate()
if self.collision():
self.figure.orientation = orientation
- This class manages the entire game board.
- It stores:
- The grid (2D list of rows × columns)
- The current falling shape
- The next shape
- Score, level, and game-over state
- It handles:
- Drawing the grid
- Spawning new shapes
- Collision detection
- Freezing pieces into the grid
- Movement (left, right, down)
- Rotation
- Instant drop (space bar)
- This is the core engine of the game.
def main():
tetris = Tetris(ROWS, COLS)
counter = 0
move = True
run = True
space_pressed = False
while run:
SCREEN.fill(BG_COLOR)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
sys.exit()
keys = pygame.key.get_pressed()
if not tetris.end:
if keys[pygame.K_LEFT]:
tetris.left()
elif keys[pygame.K_RIGHT]:
tetris.right()
elif keys[pygame.K_DOWN]:
tetris.move_down()
elif keys[pygame.K_UP]:
tetris.rotate()
elif keys[pygame.K_SPACE]:
space_pressed = True
if keys[pygame.K_ESCAPE] or keys[pygame.K_q]:
run = False
counter += 1
if counter >= 15000:
counter = 0
if move:
if counter % (FPS //(tetris.level*2)) == 0:
if not tetris.end:
if space_pressed:
tetris.freefall()
space_pressed = False
else:
tetris.move_down()
tetris.make_grid()
for x in range(ROWS):
for y in range(COLS):
if tetris.grid[x][y] > 0:
value = tetris.grid[x][y]
image = ASSETS[value]
SCREEN.blit(image, (x*CELL, y*CELL))
pygame.draw.rect(SCREEN, WHITE, (y*CELL, x*CELL, CELL, CELL),1)
if tetris.figure:
for i in range(4):
for j in range(4):
if (i *4 + j) in tetris.figure.image():
shape = ASSETS[tetris.figure.color]
x = CELL * (tetris.figure.x + j)
y = CELL * (tetris.figure.y + i)
SCREEN.blit(shape, (x,y))
pygame.draw.rect(SCREEN, WHITE, (x,y,CELL,CELL),1)
pygame.display.update()
clock.tick(FPS)
if __name__ == "__main__":
main()
- Runs continuously until the player quits.
- It handles:
- Keyboard input:
- Arrow keys to move/rotate
- Space for instant drop
- Escape/Q to quit
- Automatic falling based on a timer
- Drawing:
- Background
- Grid lines
- Frozen blocks
- Current falling piece
- Updating the display each frame
- This loop is the heartbeat of the game.