Playing with AI - Tetris Game
Creating Tetris with Pygame
Introduction
This blog will go over how I went about creating the Tetris game in Python and what I learnt. I have made games in Python in the past and remember Pygame being a useful library so I will base my game around this. Everything else will come from a mixture of ChatGPT and general research. Let's begin.
Process
Project Overview
After booting up Pycharm and installing Pygame into the venv, I couldn't decide where to start. To get my brain organised along with the project, I asked ChatGPT to create a directory for a Tetris game and this is what it came up:
TetrisGame/
├── assets/
│ ├── fonts/
│ │ └── (store fonts here)
│ ├── images/
│ │ ├── blocks/
│ │ │ └── (store block images here)
│ │ ├── background.png
│ │ └── (other game images)
├── src/
│ ├── __init__.py
│ ├── main.py
│ ├── tetris.py
│ ├── block.py
│ ├── board.py
│ ├── utils.py
├── README.md
├── requirements.txt
I think that looks like a good enough start, so this is what I went for. The files are pretty straight-forward, where main.py will be the entry point of the program, tetris.py will contain most of the game rules, block.py will define the Block class, board.py will define the Board, and utils.py will contain any functions that are used across the program.
My First Game
Now where? Well, why not Pygame's documentation? Their front game offers an example of having a ball on the screen you can move with the WASD keys. So that's what I did:
# Example file showing a circle moving on screen
import pygame
# pygame setup
pygame.init()
screen = pygame.display.set_mode((1280, 720))
clock = pygame.time.Clock()
running = True
dt = 0
player_pos = pygame.Vector2(screen.get_width() / 2, screen.get_height() / 2)
while running:
# poll for events
# pygame.QUIT event means the user clicked X to close your window
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# fill the screen with a color to wipe away anything from last frame
screen.fill("purple")
pygame.draw.circle(screen, "red", player_pos, 40)
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
player_pos.y -= 300 * dt
if keys[pygame.K_s]:
player_pos.y += 300 * dt
if keys[pygame.K_a]:
player_pos.x -= 300 * dt
if keys[pygame.K_d]:
player_pos.x += 300 * dt
# flip() the display to put your work on screen
pygame.display.flip()
# limits FPS to 60
# dt is delta time in seconds since last frame, used for framerate-
# independent physics.
dt = clock.tick(60) / 1000
pygame.quit()
Very exciting stuff. There are several things to learn from the example though. Firstly, we need to initialize the Pygame modules with the init()
call. Then, we need to set up the screen and the clock. Finally, we need a game loop that will check each frame for the logic of the game - in this case, it draws the circle and checks for keyboard inputs to update the position of the player.
Tetris Demo
After some playing around with the code and reading more of the documentation, I was able to get a demo going of a T Tetromino falling automatically, moving left and right with arrow keys, and rotating with the up arrow.
# Example file showing a circle moving on screen
import pygame
class BlockSprite(pygame.sprite.Sprite):
def __init__(self):
super().__init__() # Call the constructor of the parent class
self.image = pygame.image.load('assets/images/blocks/t_block.webp') # Set the image of the sprite
self.rect = self.image.get_rect() # Get the rectangular shape of the sprite
def get_mask(self):
return pygame.mask.from_surface(self.image)
def rotate_right(self):
# Rotate the sprite by 90 degrees
self.image = pygame.transform.rotate(self.image, -90)
self.rect = self.image.get_rect(center=self.rect.center)
# pygame setup
pygame.init()
screen = pygame.display.set_mode((1280, 720))
clock = pygame.time.Clock()
running = True
dt = 0
player_pos = pygame.Vector2(screen.get_width() / 2, screen.get_height() / 2)
block_sprite = BlockSprite()
all_sprites = pygame.sprite.Group()
all_sprites.add(block_sprite)
# Set the initial movement speed
move_speed = 5
while running:
# poll for events
# pygame.QUIT event means the user clicked X to close your window
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP: # Rotate the sprite when 'R' is pressed
block_sprite.rotate_right()
# fill the screen with a color to wipe away anything from last frame
screen.fill("black")
# Check for key presses to move the sprite
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
block_sprite.rect.x -= move_speed
if keys[pygame.K_RIGHT]:
block_sprite.rect.x += move_speed
block_sprite.rect.y += move_speed
block_mask = block_sprite.get_mask()
if block_sprite.rect.y > screen.get_height() - block_mask.get_size()[1]:
block_sprite.rect.y = 0 # Wrap to the top
# Update and draw all sprites in the group
all_sprites.update()
all_sprites.draw(screen)
# flip() the display to put your work on screen
pygame.display.flip()
# limits FPS to 60
# dt is delta time in seconds since last frame, used for framerate-
# independent physics.
dt = clock.tick(60) / 1000
pygame.quit()
A little more exciting, but not much.
Conclusion
Unfortunately, that's all I had time for. It was good getting the foundations going and having a play around and I can already see what the next steps will be. Taking the code out of main.py and starting to compartmentalise the code and move it into its correct files is a must. I also need to figure out how to make the movement more akin to Tetris and not the frame-by-frame movement seen in the GIF.
What I did take out of this was how quick Python projects are to get up and running. I did all of this in around half an hour and had the basis of a game ready to go (a bit of a stretch but you get what I mean). I imagine this is going to contrast greatly with the process of setting up the AI.