俄罗斯方块是一个脍炙人口、老少咸宜,同时也是可以简单实现的游戏。
代码:
import pygame
import random
import numpy as np
pygame.init()
# 游戏参数
WIDTH, HEIGHT = 300, 600
GRID_SIZE = 30
GRID_WIDTH = WIDTH // GRID_SIZE
GRID_HEIGHT = HEIGHT // GRID_SIZE
FPS = 3
# 颜色定义
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
CYAN = (0, 255, 255)
MAGENTA = (255, 0, 255)
YELLOW = (255, 255, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
PURPLE = (148, 0, 211)
# 俄罗斯方块形状定义
SHAPES = [
[[1, 1, 1, 1]],
[[0, 1, 0],
[1, 1, 1]],
[[1, 1, 1],
[0, 0, 1]],
[[1, 1, 1],
[1, 0, 0]],
[[1, 1],
[1, 1]],
[[1, 1, 0],
[0, 1, 1]],
[[0, 1, 1],
[1, 1, 0]],
]
class TetrisGame:
def __init__(self):
self.grid = [[0] * GRID_WIDTH for _ in range(GRID_HEIGHT)]
self.current_shape = None
self.current_color = None
self.current_x = 0
self.current_y = 0
self.score = 0
self.game_over = False
self.current_shape_index = 0
self.colors = [RED, CYAN, MAGENTA, YELLOW, GREEN, BLUE, PURPLE]
self.current_color_index = 0
def new_shape(self):
while True:
self.current_shape = random.choice(SHAPES)
self.current_shape_index = SHAPES.index(self.current_shape)
self.current_color_index = self.current_shape_index
self.current_color = self.colors[self.current_color_index]
if self.current_shape is not None:
break
self.current_x = GRID_WIDTH // 2 - len(self.current_shape[0]) // 2
self.current_y = 0
return self.current_shape
def rotate(self):
if self.current_shape is not None:
rotated_shape = np.rot90(self.current_shape)
if not self.collision(rotated_shape):
self.current_shape = rotated_shape
def move_down(self):
self.current_y += 1
if self.collision():
self.current_y -= 1
self.freeze()
self.clear_lines()
if self.current_y <= 0:
# 如果当前方块堆积到游戏区域的顶部,触发游戏结束
self.game_over = True
else:
self.new_shape()
def move_left(self):
self.current_x -= 1
if self.collision():
self.current_x += 1
def move_right(self):
self.current_x += 1
if self.collision():
self.current_x -= 1
def collision(self, shape=None):
shape = shape if shape is not None else self.current_shape
if shape is not None:
for y, row in enumerate(shape):
for x, cell in enumerate(row):
if cell and (
self.current_y + y >= GRID_HEIGHT
or self.current_x + x < 0
or self.current_x + x >= GRID_WIDTH
or self.grid[self.current_y + y][self.current_x + x]
):
return True
return False
def freeze(self):
for y, row in enumerate(self.current_shape):
for x, cell in enumerate(row):
if cell:
self.grid[self.current_y + y][self.current_x + x] = self.current_color
def clear_lines(self):
lines_to_clear = [i for i, row in enumerate(self.grid) if all(row)]
for line in reversed(lines_to_clear):
del self.grid[line]
self.grid.insert(0, [0] * GRID_WIDTH)
self.score += 10
def draw_grid(self, surface):
for y, row in enumerate(self.grid):
for x, cell in enumerate(row):
if cell:
pygame.draw.rect(
surface,
cell,
(x * GRID_SIZE, y * GRID_SIZE, GRID_SIZE, GRID_SIZE),
)
def draw_current_shape(self, surface):
if self.current_shape is not None:
for y, row in enumerate(self.current_shape):
for x, cell in enumerate(row):
if cell:
pygame.draw.rect(
surface,
self.current_color,
((self.current_x + x) * GRID_SIZE, (self.current_y + y) * GRID_SIZE, GRID_SIZE, GRID_SIZE),
)
def draw_score(self, surface):
font = pygame.font.Font(None, 36)
score_text = font.render(f"Score: {self.score}", True, WHITE)
surface.blit(score_text, (10, 10))
def draw_game_over(self, surface):
font = pygame.font.Font(None, 72)
game_over_text = font.render("Game Over", True, WHITE)
surface.blit(game_over_text, (WIDTH // 4, HEIGHT // 3))
score_text = font.render(f"Score: {self.score}", True, WHITE)
surface.blit(score_text, (WIDTH // 3, HEIGHT // 2))
def main():
pygame.display.set_caption("Tetris")
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
game = TetrisGame()
# 在循环外部创建第一个形状
game.new_shape()
while not game.game_over:
for event in pygame.event.get():
if event.type == pygame.QUIT:
game.game_over = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_DOWN:
game.move_down()
elif event.key == pygame.K_LEFT:
game.move_left()
elif event.key == pygame.K_RIGHT:
game.move_right()
elif event.key == pygame.K_UP:
game.rotate()
# 移动下移移到事件处理外部
game.move_down()
screen.fill(BLACK)
game.draw_grid(screen)
game.draw_current_shape(screen)
game.draw_score(screen)
pygame.display.flip()
clock.tick(FPS)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
return
if __name__ == "__main__":
main()
运行效果:
俄罗斯方块
失败:
核心代码解释:
def freeze(self):
for y, row in enumerate(self.current_shape):
for x, cell in enumerate(row):
if cell:
self.grid[self.current_y + y][self.current_x + x] = self.current_color
这段代码位于 TetrisGame
类中的 freeze
方法中,其作用是将当前正在下落的方块 "冻结" 到游戏区域中。具体来说,它将方块的颜色信息赋值给游戏区域中对应位置的格子,从而将方块的形状固定在游戏区域中。
-
for y, row in enumerate(self.current_shape):
使用enumerate
函数遍历当前正在下落的方块的每一行,其中y
是行索引,row
是行的内容。 -
for x, cell in enumerate(row):
在每一行中,再次使用enumerate
函数遍历该行的每个单元格,其中x
是列索引,cell
是单元格的值。 -
if cell:
检查当前单元格是否包含方块的一部分,即是否为非零值。如果是,表示这个单元格是方块的一部分。 -
self.grid[self.current_y + y][self.current_x + x] = self.current_color
将当前方块的颜色信息self.current_color
赋值给游戏区域中对应位置的格子。self.current_y + y
表示方块在游戏区域中的纵坐标,self.current_x + x
表示方块在游戏区域中的横坐标。self.current_x
表示当前方块左上角单元格在游戏区域中的横坐标,x是当前正在遍历的方块的一行中的相对横坐标。通过相加,可以得到方块中每个单元格在游戏区域中的绝对坐标。y也是类似。
可以改进的地方:
- 方块可以更加立体,界面可以更加美化
- 方块控制可以支持长按
- 空格键可以支持方块快速下落
- 增加音乐
- 其它