Tetris, a classic puzzle game, has remained popular across various platforms due to its simple yet engaging gameplay. Developing a mobile-friendly version of Tetris using Python involves leveraging libraries such as pygame
and potentially integrating kivy
for enhanced touch controls. This guide provides a step-by-step approach to creating a comprehensive Tetris game optimized for mobile devices.
Before diving into coding, ensure that you have the necessary libraries installed. The primary library used for game development in this guide is pygame
. Additionally, for touch controls and mobile optimization, kivy
can be integrated.
Use the following commands to install these libraries:
pip install pygame
pip install kivy
If you're targeting mobile devices, consider using platforms like Pydroid 3 for Android to run Python scripts seamlessly.
Organize your project directory as follows to maintain clarity and manageability:
Directory/File | Description |
---|---|
tetris_mobile/ | Root directory for the Tetris project. |
└── main.py | Main Python script containing the game logic. |
└── assets/ | Directory for storing images, sounds, and other assets. |
Tetrominoes are the fundamental building blocks of Tetris, each consisting of four blocks arranged in different shapes. Defining these shapes and their corresponding colors is essential for differentiating them during gameplay.
# Define colors
BLACK = (0, 0, 0)
CYAN = (0, 255, 255)
YELLOW = (255, 255, 0)
ORANGE = (255, 165, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
PURPLE = (128, 0, 128)
# Tetromino shapes
SHAPES = [
[[1, 5, 9, 13], [4, 5, 6, 7]], # I
[[4, 5, 9, 10], [2, 6, 5, 9]], # Z
[[6, 7, 9, 10], [1, 5, 6, 10]], # S
[[1, 2, 5, 9], [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]], # L
[[1, 2, 5, 6]], # O
[[1, 4, 5, 6], [1, 4, 5, 9], [4, 5, 6, 9], [1, 5, 6, 9]] # T
]
COLORS = [CYAN, YELLOW, ORANGE, BLUE, GREEN, RED, PURPLE]
Creating a Tetromino class encapsulates the properties and behaviors of each Tetromino, including position, shape, color, and rotation.
import random
class Tetromino:
def __init__(self, x, y, shape):
self.x = x
self.y = y
self.shape = shape
self.color = random.choice(COLORS)
self.rotation = 0
def rotate(self):
self.rotation = (self.rotation + 1) % len(self.shape)
The game grid serves as the playfield where Tetrominoes fall and are placed. It is typically a matrix representing rows and columns.
def create_grid(locked_positions={}):
grid = [[BLACK for _ in range(10)] for _ in range(20)]
for (x, y), color in locked_positions.items():
grid[y][x] = color
return grid
Rendering the grid involves drawing each block based on its color and position. Additionally, grid lines can be drawn for better visual separation.
import pygame
def draw_grid(surface, grid):
for y in range(len(grid)):
for x in range(len(grid[y])):
pygame.draw.rect(surface, grid[y][x], (x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE), 0)
for y in range(len(grid)):
pygame.draw.line(surface, WHITE, (0, y * BLOCK_SIZE), (SCREEN_WIDTH, y * BLOCK_SIZE))
for x in range(len(grid[0])):
pygame.draw.line(surface, WHITE, (x * BLOCK_SIZE, 0), (x * BLOCK_SIZE, SCREEN_HEIGHT))
For desktop development, handling keyboard inputs such as arrow keys allows users to move and rotate Tetrominoes.
def handle_input(event, current_piece, grid):
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
current_piece.x -= 1
if not valid_space(current_piece, grid):
current_piece.x += 1
elif event.key == pygame.K_RIGHT:
current_piece.x += 1
if not valid_space(current_piece, grid):
current_piece.x -= 1
elif event.key == pygame.K_DOWN:
current_piece.y += 1
if not valid_space(current_piece, grid):
current_piece.y -= 1
elif event.key == pygame.K_UP:
current_piece.rotate()
if not valid_space(current_piece, grid):
current_piece.rotate()
Adapting to mobile requires replacing keyboard inputs with touch gestures. Libraries like kivy
facilitate implementing touch controls such as swipes and taps.
def handle_touch(pos, current_piece, grid):
x, y = pos
if touch_left.collidepoint(x, y):
current_piece.x -= 1
if not valid_space(current_piece, grid):
current_piece.x += 1
elif touch_right.collidepoint(x, y):
current_piece.x += 1
if not valid_space(current_piece, grid):
current_piece.x -= 1
elif touch_rotate.collidepoint(x, y):
current_piece.rotate()
if not valid_space(current_piece, grid):
current_piece.rotate()
Ensuring that Tetrominoes do not overlap or exceed the grid boundaries is critical. The valid_space
function checks if a Tetromino's position is valid.
def valid_space(piece, grid):
accepted_positions = [[(x, y) for x in range(10) if grid[y][x] == BLACK] for y in range(20)]
accepted_positions = [x for sub in accepted_positions for x in sub]
formatted = convert_shape_format(piece)
for pos in formatted:
if pos not in accepted_positions:
if pos[1] > -1:
return False
return True
def convert_shape_format(piece):
positions = []
format = piece.shape[piece.rotation % len(piece.shape)]
for i, line in enumerate(format):
row = list(map(int, list(f"{line:04b}")))
for j, column in enumerate(row):
if column:
positions.append((piece.x + j, piece.y + i))
return positions
Clearing complete rows and updating the grid accordingly enhances gameplay by providing points and freeing up space for new Tetrominoes.
def clear_rows(grid, locked_positions):
cleared_rows = 0
for y in range(len(grid)-1, -1, -1):
if BLACK not in grid[y]:
cleared_rows += 1
del grid[y]
grid.insert(0, [BLACK for _ in range(10)])
keys_to_remove = [key for key in locked_positions if key[1] == y]
for key in keys_to_remove:
del locked_positions[key]
return cleared_rows
The game concludes when new Tetrominoes cannot be placed on the grid, signaling a game-over scenario.
def check_game_over(locked_positions):
for pos in locked_positions:
_, y = pos
if y < 1:
return True
return False
Rendering involves drawing the grid, active Tetromino, locked positions, and additional UI elements like the score.
def draw_window(surface, grid, score=0):
surface.fill(BLACK)
# Draw Score
font = pygame.font.SysFont('comicsans', 30)
label = font.render(f"Score: {score}", True, WHITE)
surface.blit(label, (SCREEN_WIDTH - 150, 20))
# Draw Grid
draw_grid(surface, grid)
pygame.display.update()
Displaying the current falling Tetromino and the next one enhances user experience by providing foresight into upcoming pieces.
def draw_next_piece(surface, piece):
font = pygame.font.SysFont('comicsans', 30)
label = font.render("Next Piece:", True, WHITE)
surface.blit(label, (SCREEN_WIDTH + 50, 50))
format = piece.shape[piece.rotation % len(piece.shape)]
for i, line in enumerate(format):
row = list(map(int, list(f"{line:04b}")))
for j, column in enumerate(row):
if column:
pygame.draw.rect(surface, piece.color, (SCREEN_WIDTH + 50 + j*BLOCK_SIZE, 100 + i*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE), 0)
Mobile devices have varying screen sizes and resolutions. Adjusting the screen dimensions ensures that the game is displayed correctly across different devices.
# Mobile-optimized screen dimensions
SCREEN_WIDTH = 360
SCREEN_HEIGHT = 640
BLOCK_SIZE = SCREEN_WIDTH // 12
Replacing keyboard inputs with touch gestures involves detecting touch regions for actions like moving left/right and rotating Tetrominoes. The kivy
library facilitates handling multi-touch inputs.
# Define touch control zones
touch_left = pygame.Rect(0, SCREEN_HEIGHT*2//3, SCREEN_WIDTH//3, SCREEN_HEIGHT//3)
touch_right = pygame.Rect(SCREEN_WIDTH*2//3, SCREEN_HEIGHT*2//3, SCREEN_WIDTH//3, SCREEN_HEIGHT//3)
touch_rotate = pygame.Rect(SCREEN_WIDTH//3, SCREEN_HEIGHT*2//3, SCREEN_WIDTH//3, SCREEN_HEIGHT//3)
def handle_touch(pos, current_piece, grid):
x, y = pos
if touch_left.collidepoint(x, y):
current_piece.x -= 1
if not valid_space(current_piece, grid):
current_piece.x += 1
elif touch_right.collidepoint(x, y):
current_piece.x += 1
if not valid_space(current_piece, grid):
current_piece.x -= 1
elif touch_rotate.collidepoint(x, y):
current_piece.rotate()
if not valid_space(current_piece, grid):
current_piece.rotate()
Ensuring that the game's UI elements scale appropriately based on the device's screen size enhances user experience. Utilizing relative sizing and positioning helps maintain consistency across devices.
# Example: Dynamic block size based on screen width
BLOCK_SIZE = SCREEN_WIDTH // 12
# Positioning score and next piece dynamically
score_position = (SCREEN_WIDTH - 150, 20)
next_piece_position = (SCREEN_WIDTH + 50, 50)
The main game loop handles the game's runtime behavior, including updating the game state, handling user input, and rendering the visuals.
def main():
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH + 200, SCREEN_HEIGHT))
pygame.display.set_caption("Mobile Tetris")
clock = pygame.time.Clock()
grid = create_grid()
current_piece = Tetromino(5, 0, random.choice(SHAPES))
next_piece = Tetromino(5, 0, random.choice(SHAPES))
locked_positions = {}
score = 0
fall_time = 0
fall_speed = 0.5 # Seconds
running = True
game_over = False
while running:
grid = create_grid(locked_positions)
fall_time += clock.get_rawtime()
clock.tick()
if fall_time / 1000 >= fall_speed:
fall_time = 0
current_piece.y += 1
if not valid_space(current_piece, grid):
current_piece.y -= 1
change_piece = True
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
handle_input(event, current_piece, grid)
if event.type == pygame.FINGERDOWN:
touch_pos = (event.x * SCREEN_WIDTH, event.y * SCREEN_HEIGHT)
handle_touch(touch_pos, current_piece, grid)
shape_pos = convert_shape_format(current_piece)
for pos in shape_pos:
if pos[1] > -1:
grid[pos[1]][pos[0]] = current_piece.color
if change_piece:
for pos in shape_pos:
locked_positions[pos] = current_piece.color
current_piece = next_piece
next_piece = Tetromino(5, 0, random.choice(SHAPES))
change_piece = False
score += clear_rows(grid, locked_positions) * 100
if check_game_over(locked_positions):
game_over = True
running = False
draw_window(screen, grid, score)
draw_next_piece(screen, next_piece)
pygame.display.update()
pygame.quit()
Developing a mobile-friendly Tetris game in Python involves a blend of game development principles and mobile optimization techniques. By leveraging libraries like pygame
and kivy
, developers can create engaging and responsive gaming experiences tailored for mobile devices. This guide provided a comprehensive overview, from setting up the environment to implementing core game mechanics and optimizing for mobile, ensuring a robust foundation for your Tetris project.