【Python】用Python写一个俄罗斯方块玩玩
- 一、引言
- 1.成品效果展示
- 二、思考准备
- 1.思考设计
- 2.代码设计
- 2.1 游戏页面
- 2.2 控件设计
- 2.2.1 方块生成
- 2.2.2 方块碰撞
- 2.2.3 方块消融
- 2.2.4 游戏主循环
- 2.2.5 游戏窗口
- 三、游戏完整版
一、引言
- 今日看到侄子在玩游戏,凑近一看,原来是俄罗斯方块。熟悉又怀念,童年的记忆瞬间涌上心头。小时候觉得这游戏老厉害了,现在想想,好像就是数组的组合和消融,便想着自己写一个试试。说干就干,冲!
1.成品效果展示
俄罗斯方块实现过程
二、思考准备
1.思考设计
- 俄罗斯方块作为风靡一时的游戏,由俄罗斯人阿列克谢·帕基特诺夫于1984年6月发明的休闲游戏,主要是通过方块的组合、消融完成的;
- 所以得先设计有哪些方块、方块如何组合、完成一行时方块消融并下降一行、方块组合还得回旋转,单次90°等等,考虑中ing…
2.代码设计
2.1 游戏页面
- 游戏首先得有个操控页面,所以得设计一个
# 设计游戏窗口尺寸
SCREEN_WIDTH, SCREEN_HEIGHT = 300, 600
# 方块大小
GRID_SIZE = 30
# 各种图形颜色 可自定义调整
COLORS = [ # 颜色配置(含背景色+7种方块色)
(0, 0, 0), # 0: 黑色背景
(255, 0, 0), # 1: 红色-I型
(0, 255, 0), # 2: 绿色-T型
(0, 0, 255), # 3: 蓝色-J型
(255, 165, 0), # 4: 橙色-L型
(255, 255, 0), # 5: 黄色-O型
(128, 0, 128), # 6: 紫色-S型
(0, 255, 255) # 7: 青色-Z型
]
2.2 控件设计
2.2.1 方块生成
- 游戏开始最上方会有方块掉落,随机形状和颜色
def new_piece(self):
"""生成新方块,随机形状和颜色"""
shape = random.choice(SHAPES)
return {
'shape': shape, # 方块形状矩阵
'x': (SCREEN_WIDTH//GRID_SIZE - len(shape[0])) // 2, # 初始水平居中
'y': 0, # 初始垂直位置
'color': random.randint(1, len(COLORS)-1) # 随机颜色(排除背景色)
}
2.2.2 方块碰撞
- 方块会一直下降,碰撞,下降时还要符合可以多次旋转
def check_collision(self, dx=0, dy=0, rotate=False):
"""
碰撞检测函数
:param dx: 水平移动偏移量
:param dy: 垂直移动偏移量
:param rotate: 是否正在旋转
:return: 是否发生碰撞
"""
shape = self.current_piece['shape']
# 旋转时生成临时形状
if rotate:
shape = [list(row[::-1]) for row in zip(*shape)] # 矩阵旋转算法:先转置再反转每行(顺时针90度)
for y, row in enumerate(shape):
for x, cell in enumerate(row):
if cell: # 仅检测实体方块
new_x = self.current_piece['x'] + x + dx
new_y = self.current_piece['y'] + y + dy
# 边界检测(左右越界/触底/与其他方块重叠)
if not (0 <= new_x < len(self.grid[0])) or new_y >= len(self.grid):
return True
if new_y >=0 and self.grid[new_y][new_x]:
return True
return False
2.2.3 方块消融
- 方块接触时,合适的会消融、不合适的会叠加,消融了计算分数,消融的还要剔除
def merge_piece(self):
"""将当前方块合并到游戏网格,并触发消行检测"""
for y, row in enumerate(self.current_piece['shape']):
for x, cell in enumerate(row):
if cell:
# 将方块颜色写入网格对应位置
self.grid[self.current_piece['y']+y][self.current_piece['x']+x] = self.current_piece['color']
# 消行并更新分数
lines = self.clear_lines()
self.score += lines * 100
def clear_lines(self):
"""消除满行并返回消除行数"""
lines = 0
# 从底部向上扫描
for i, row in enumerate(self.grid):
if all(cell !=0 for cell in row): # 检测整行填满
del self.grid[i] # 删除该行
self.grid.insert(0, [0]*len(row)) # 在顶部插入新空行
lines +=1
return lines
2.2.4 游戏主循环
- 游戏主体逻辑写入,方块的处理时间、操作事项和刷新等
def run(self):
"""游戏主循环"""
fall_time = 0 # 下落时间累计器
while True:
self.screen.fill(COLORS[0]) # 用背景色清屏
# 计时系统(控制自动下落速度)
fall_time += self.clock.get_rawtime()
self.clock.tick() # 保持帧率稳定
# 自动下落逻辑(每800ms下落一格)
if fall_time >= 800:
if not self.check_collision(dy=1):
self.current_piece['y'] +=1 # 正常下落
else:
self.merge_piece() # 触底合并
self.current_piece = self.new_piece() # 生成新方块
# 游戏结束检测(新方块无法放置)
if self.check_collision():
print("Game Over 你完蛋啦! Score:", self.score)
return
fall_time =0 # 重置计时器
# 事件处理(适配Mac键盘布局)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
return
if event.type == KEYDOWN:
# 左右移动(带碰撞检测)
if event.key == K_LEFT and not self.check_collision(dx=-1):
self.current_piece['x'] -=1
elif event.key == K_RIGHT and not self.check_collision(dx=1):
self.current_piece['x'] +=1
# 软下落(手动加速)
elif event.key == K_DOWN:
if not self.check_collision(dy=1):
self.current_piece['y'] +=1
# 旋转方块(带碰撞检测)
elif event.key == K_UP and not self.check_collision(rotate=True):
self.current_piece['shape'] = [list(row[::-1]) for row in zip(*self.current_piece['shape'])]
# 硬下落(空格键一键到底)
elif event.key == K_SPACE:
while not self.check_collision(dy=1):
self.current_piece['y'] +=1
# 绘制逻辑,游戏网格
for y, row in enumerate(self.grid):
for x, color in enumerate(row):
if color:
# 绘制已落下方块(留1像素间隙)
pygame.draw.rect(self.screen, COLORS[color],
(x*GRID_SIZE, y*GRID_SIZE, GRID_SIZE-1, GRID_SIZE-1))
# 绘制当前操作方块
for y, row in enumerate(self.current_piece['shape']):
for x, cell in enumerate(row):
if cell:
pygame.draw.rect(self.screen, COLORS[self.current_piece['color']],
((self.current_piece['x']+x)*GRID_SIZE,
(self.current_piece['y']+y)*GRID_SIZE,
GRID_SIZE-1, GRID_SIZE-1))
# 刷新画面
pygame.display.flip()
2.2.5 游戏窗口
- 最后,游戏设计好了,必须有个游戏窗口来展示
def __init__(self):
# 初始化游戏窗口
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Mac M1俄罗斯方块")
self.clock = pygame.time.Clock() # 游戏时钟控制帧率
# 游戏状态初始化
self.grid = [[0]*(SCREEN_WIDTH//GRID_SIZE) for _ in range(SCREEN_HEIGHT//GRID_SIZE)] # 20x10游戏网格
self.current_piece = self.new_piece() # 当前操作方块
self.score = 0
三、游戏完整版
# 俄罗斯方块设计
# -*- coding: utf-8 -*-
"""
功能:俄罗斯方块,童年的回忆
作者:看海的四叔
最后更新:2025-04-16
"""
import pygame
import random
from pygame.locals import *
# 初始化配置
pygame.init()
SCREEN_WIDTH, SCREEN_HEIGHT = 300, 600
GRID_SIZE = 30
COLORS = [
(0, 0, 0),
(255, 0, 0),
(0, 255, 0),
(0, 0, 255),
(255, 165, 0),
(255, 255, 0),
(128, 0, 128),
(0, 255, 255)
]
SHAPES = [
[[1,1,1,1]],
[[1,1],[1,1]],
[[0,1,0], [1,1,1]],
[[1,1,1], [1,0,0]],
[[1,1,1], [0,0,1]],
[[0,1,1], [1,1,0]],
[[1,1,0], [0,1,1]]
]
class Tetris:
"""游戏主控制类,处理游戏逻辑与渲染"""
def __init__(self):
self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Mac M1俄罗斯方块")
self.clock = pygame.time.Clock()
self.grid = [[0]*(SCREEN_WIDTH//GRID_SIZE) for _ in range(SCREEN_HEIGHT//GRID_SIZE)]
self.current_piece = self.new_piece()
self.score = 0
def new_piece(self):
"""生成新方块(随机形状和颜色)"""
shape = random.choice(SHAPES)
return {
'shape': shape,
'x': (SCREEN_WIDTH//GRID_SIZE - len(shape[0])) // 2,
'y': 0,
'color': random.randint(1, len(COLORS)-1)
}
def check_collision(self, dx=0, dy=0, rotate=False):
"""
碰撞检测函数
:param dx: 水平移动偏移量
:param dy: 垂直移动偏移量
:param rotate: 是否正在旋转
:return: 是否发生碰撞
"""
shape = self.current_piece['shape']
if rotate:
shape = [list(row[::-1]) for row in zip(*shape)]
for y, row in enumerate(shape):
for x, cell in enumerate(row):
if cell:
new_x = self.current_piece['x'] + x + dx
new_y = self.current_piece['y'] + y + dy
if not (0 <= new_x < len(self.grid[0])) or new_y >= len(self.grid):
return True
if new_y >=0 and self.grid[new_y][new_x]:
return True
return False
def merge_piece(self):
"""将当前方块合并到游戏网格,并触发消行检测"""
for y, row in enumerate(self.current_piece['shape']):
for x, cell in enumerate(row):
if cell:
self.grid[self.current_piece['y']+y][self.current_piece['x']+x] = self.current_piece['color']
lines = self.clear_lines()
self.score += lines * 100
def clear_lines(self):
"""消除满行并返回消除行数"""
lines = 0
for i, row in enumerate(self.grid):
if all(cell !=0 for cell in row):
del self.grid[i]
self.grid.insert(0, [0]*len(row))
lines +=1
return lines
def run(self):
"""游戏主循环"""
fall_time = 0
while True:
self.screen.fill(COLORS[0])
fall_time += self.clock.get_rawtime()
self.clock.tick()
if fall_time >= 800:
if not self.check_collision(dy=1):
self.current_piece['y'] +=1 # 正常下落
else:
self.merge_piece()
self.current_piece = self.new_piece()
# 游戏结束检测(新方块无法放置)
if self.check_collision():
print("Game Over 你完蛋啦! Score:", self.score)
return
fall_time =0 # 重置计时器
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
return
if event.type == KEYDOWN:
if event.key == K_LEFT and not self.check_collision(dx=-1):
self.current_piece['x'] -=1
elif event.key == K_RIGHT and not self.check_collision(dx=1):
self.current_piece['x'] +=1
elif event.key == K_DOWN:
if not self.check_collision(dy=1):
self.current_piece['y'] +=1
elif event.key == K_UP and not self.check_collision(rotate=True):
self.current_piece['shape'] = [list(row[::-1]) for row in zip(*self.current_piece['shape'])]
elif event.key == K_SPACE:
while not self.check_collision(dy=1):
self.current_piece['y'] +=1
for y, row in enumerate(self.grid):
for x, color in enumerate(row):
if color:
pygame.draw.rect(self.screen, COLORS[color],
(x*GRID_SIZE, y*GRID_SIZE, GRID_SIZE-1, GRID_SIZE-1))
for y, row in enumerate(self.current_piece['shape']):
for x, cell in enumerate(row):
if cell:
pygame.draw.rect(self.screen, COLORS[self.current_piece['color']],
((self.current_piece['x']+x)*GRID_SIZE,
(self.current_piece['y']+y)*GRID_SIZE,
GRID_SIZE-1, GRID_SIZE-1))
pygame.display.flip()
if __name__ == "__main__":
game = Tetris()
game.run()