前言
在之前的俄罗斯方块的小游戏中进行了修改,但是一定还存在着一些问题,欢迎大家在评论区留言。
目前是进行了以下的一些优化:
- 普通方块颜色除了选用马卡龙配色还增加了一些其他好看的颜色,对于特殊方块的颜色使用红橙黄绿蓝靛紫,并且分别对应一种特殊的功能(对应特殊方块本来是想通过添加边框来标记的,但是实在太丑了,所以干脆用颜色来进行区分)。
- 对不同特殊方块设计不同的功能(红-3*3爆炸;橙-清除3行;黄-清当前+底行;绿-奇偶列交换;蓝-隐藏底5行;靛-镜像翻转列;紫-十字清除)(这个本来是靛色想设计成重力翻转的,就是让界面的方块翻转,然后新方块是从底部上升到顶部,游戏判断也是反的,但是没有实现成功,还是边界判断的问题,不论是先翻转游戏界面的方块还是先修改游戏结束的判断逻辑好像都会直接导致游戏结束)(后续是想把靛色设计成隐藏顶部的5行,发现他是设置了一个数组,把要隐藏的行添加进去,但是我想实现的是方块降落在前5行不显示,也就是不知道方块在哪个位置这种效果,所以还是没有实现成功)(最后想说的是这方块的消除决定还有问题,虽然代码我没看出有啥问题,但是他清除的真的是莫名其妙,感觉一下子就全部清除了)。
- 增加暂停功能,可能就是在玩的过程中需要回复信息之类的,用户可以通过按下Esc键来进行暂停。
- 增加了警戒线,超过警戒线游戏结束,之前是超过游戏界面结束,这里相当于下移了一格,这个只能说在最后用AI进行修改,他两个方案改来改去,让我增加代码之后游戏直接一开始就结束了,然后又让我删了增加的代码。
- 通过按下空格键可以实现方块加速下降的功能(之前也有但是我没发现)。
- 优化方块的旋转和下落(尽量让他下降的速度不是突然变得飞快,还有方块旋转的逻辑)。
使用DeepSeek定制Python小游戏——以“俄罗斯方块”为例_deepseek制作小游戏-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/147686449?spm=1001.2014.3001.55012048游戏(含Python源码)-CSDN博客
https://blog.csdn.net/weixin_64066303/article/details/147711281?spm=1001.2014.3001.5501像素飞机大战(含Python源码)_像素飞机python-CSDN博客
https://blog.csdn.net/weixin_64066303/article/details/147693018?spm=1001.2014.3001.5501贪吃蛇(含Python源码)_python写贪吃蛇源码-CSDN博客
https://blog.csdn.net/weixin_64066303/article/details/147660127?spm=1001.2014.3001.5501外星人入侵(python)_外星人入侵python源代码-CSDN博客
https://blog.csdn.net/weixin_64066303/article/details/135963588?spm=1001.2014.3001.5501
代码
# -*- coding: utf-8 -*-
import pygame
import random
import os
import math# 初始化 Pygame
pygame.init()
os.environ['PYGAME_FREETYPE'] = '1'# 常量定义
HIGHSCORE_FILE = "tetris_highscore.txt"
WINDOW_HEIGHT = 600
GRID_SIZE = 30
GRID_WIDTH = 10
GRID_HEIGHT = 20
INFO_PANEL_WIDTH = 200
GAME_PANEL_WIDTH = GRID_WIDTH * GRID_SIZE
WINDOW_WIDTH = GAME_PANEL_WIDTH + INFO_PANEL_WIDTHSPECIAL_COLORS = [(255, 0, 0), # 红(255, 165, 0), # 橙(255, 255, 0), # 黄(0, 255, 0), # 绿(0, 0, 255), # 蓝(75, 0, 130), # 靛(148, 0, 211) # 紫
]
# 颜色定义(马卡龙)
COLORS = [(255, 209, 220), # 淡粉色(189, 236, 204), # 薄荷绿(255, 241, 187), # 奶油黄(174, 221, 255), # 浅蓝色(207, 186, 240), # 薰衣草紫(255, 183, 162), # 珊瑚橙(255, 248, 237), # 奶油白(230, 230, 230), # 浅灰色(196, 221, 235), # 雾霾蓝(255, 240, 214), # 杏仁奶油(205, 227, 205), # 灰豆绿(222, 200, 228), # 香芋紫(255, 198, 193), # 珊瑚粉(253, 245, 178), # 浅鹅黄(174, 224, 238), # 冰川蓝(215, 232, 186), # 抹茶奶绿(237, 210, 210), # 烟灰粉(233, 213, 232), # 丁香紫(234, 224, 210), # 燕麦色(224, 232, 240), # 浅灰蓝
]BLACK = (0, 0, 0)
DARK_GRAY = (40, 40, 40)
WHITE = (255, 255, 255)# 方块形状定义
SHAPES = [[[1, 1, 1, 1]],[[1, 0, 0], [1, 1, 1]],[[0, 0, 1], [1, 1, 1]],[[1, 1], [1, 1]],[[0, 1, 1], [1, 1, 0]],[[0, 1, 0], [1, 1, 1]],[[1, 1, 0], [0, 1, 1]]
]# 初始化窗口
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("俄罗斯方块")def load_font(size):try:return pygame.font.SysFont("simhei", size)except:return pygame.font.Font(None, size)def load_highscore():try:if os.path.exists(HIGHSCORE_FILE):with open(HIGHSCORE_FILE, 'r') as f:return int(f.read().strip() or 0)return 0except:return 0def save_highscore(score):with open(HIGHSCORE_FILE, 'w') as f:f.write(str(score))class GameState:def __init__(self):# 新增速度相关常量self.BASE_FALL_SPEED = 500self.MIN_FALL_SPEED = 50self.MAX_ACCELERATION = 200 # 最快速度(最小间隔)self.SPEED_DECAY_FACTOR = 35self.paused = False # 新增暂停状态self.special_effects = {(255, 0, 0): self.red_effect, # 红(255, 165, 0): self.orange_effect, # 橙(255, 255, 0): self.yellow_effect, # 黄(0, 255, 0): self.green_effect, # 绿(0, 0, 255): self.blue_effect, # 蓝(75, 0, 130): self.indigo_effect, # 靛(148, 0, 211): self.purple_effect # 紫}self.rotation_angle = 0 # 当前旋转角度(0-90)self.is_rotating = Falseself.hidden_rows = [] # 存储被隐藏的行索引self.hide_row_start_time = None # 隐藏开始时间self.reset()def red_effect(self, origin_x, origin_y):"""红色方块3x3爆炸效果"""# 计算爆炸范围边界start_x = max(0, origin_x - 1)end_x = min(GRID_WIDTH - 1, origin_x + 1)start_y = max(0, origin_y - 1)end_y = min(GRID_HEIGHT - 1, origin_y + 1)# 遍历3x3区域for y in range(start_y, end_y + 1):for x in range(start_x, end_x + 1):# 如果方块存在,则将其清除并增加分数if self.game_area[y][x] is not None:self.score += 1 # 每个被炸方块加1分self.game_area[y][x] = Nonedef orange_effect(self, origin_x, origin_y):"""橙色方块效果:清除当前行及其上下相邻的两行"""# print(f"\n[Orange Effect] 触发于位置 (x={origin_x}, y={origin_y})")# 清除当前行及其上下相邻的两行for y in [origin_y - 1, origin_y, origin_y + 1]: # 上一行、当前行、下一行if 0 <= y < GRID_HEIGHT: # 确保不越界for x in range(GRID_WIDTH):if self.game_area[y][x] is not None:self.score += 1 # 每个被清除的方块加10分self.game_area[y][x] = None# 更新游戏区域(删除空行并补充新行)new_game_area = [row for row in self.game_area if not all(cell is None for cell in row)]new_rows = [[None] * GRID_WIDTH for _ in range(GRID_HEIGHT - len(new_game_area))]self.game_area = new_rows + new_game_areadef yellow_effect(self, origin_x, origin_y):"""黄色方块效果:非底部清当前行+底行,底部只清当前行"""bottom_row = GRID_HEIGHT - 1# print(f"\n[Yellow Effect] 触发于 (x={origin_x}, y={origin_y})")# 需要清除的行列表target_rows = []if origin_y == bottom_row:# 底部情况:只清除当前行target_rows.append(origin_y)else:# 非底部情况:清除当前行和底行target_rows.extend([origin_y, bottom_row])# 执行清除操作for y in target_rows:if 0 <= y < GRID_HEIGHT: # 安全边界检查for x in range(GRID_WIDTH):if self.game_area[y][x] is not None:self.score += 1self.game_area[y][x] = None# 更新游戏区域(直接补充新行到顶部)new_game_area = [row for row in self.game_area if not all(cell is None for cell in row)]new_rows = [[None] * GRID_WIDTH for _ in range(GRID_HEIGHT - len(new_game_area))]self.game_area = new_rows + new_game_areadef green_effect(self, origin_x, origin_y):"""绿色方块效果:清除当前行,并将所有列按奇偶交换"""# print(f"\n[Green Effect] 触发于位置 (x={origin_x}, y={origin_y})")# 1. 清除当前行for x in range(GRID_WIDTH):if self.game_area[origin_y][x] is not None:self.score += 1self.game_area[origin_y][x] = None# 2. 按列奇偶互换(0<->1, 2<->3, ...)for y in range(GRID_HEIGHT):for x in range(0, GRID_WIDTH - 1, 2): # 每次处理两个相邻的列# 如果不是在最后一列if x + 1 < GRID_WIDTH:# 交换两列中的方块self.game_area[y][x], self.game_area[y][x + 1] = self.game_area[y][x + 1], self.game_area[y][x]# 3. 更新游戏区域(删除空行并补充新行)new_game_area = [row for row in self.game_area if not all(cell is None for cell in row)]new_rows = [[None] * GRID_WIDTH for _ in range(GRID_HEIGHT - len(new_game_area))]self.game_area = new_rows + new_game_areadef blue_effect(self, origin_x, origin_y):"""蓝色方块效果:隐藏底部5行,持续30s"""# print("\n[Blue Effect] 隐藏底部5行")# 确定底部5行的索引bottom_rows = range(GRID_HEIGHT - 5, GRID_HEIGHT)# 设置隐藏行self.hidden_rows = list(bottom_rows)self.hide_row_start_time = pygame.time.get_ticks()def indigo_effect(self, origin_x, origin_y):"""靛色方块效果:镜像翻转所有列"""# print("\n[Indigo Effect] 镜像翻转所有列")# 逐行进行列镜像交换for y in range(GRID_HEIGHT):# 交换该行的所有对称列(0<->9, 1<->8...)for x in range(GRID_WIDTH // 2):left_col = xright_col = GRID_WIDTH - 1 - x# 交换左右列内容self.game_area[y][left_col], self.game_area[y][right_col] = \self.game_area[y][right_col], self.game_area[y][left_col]def purple_effect(self, origin_x, origin_y):# print(f"清除行{origin_y}和列{origin_x}") # 调试输出"""紫色方块效果:清除当前行和当前列"""# 清除当前行for x in range(GRID_WIDTH):if self.game_area[origin_y][x] is not None:self.score += 1 # 每个被清除的方块加10分self.game_area[origin_y][x] = None# 清除当前列for y in range(GRID_HEIGHT):if self.game_area[y][origin_x] is not None:self.score += 1 # 每个被清除的方块加10分self.game_area[y][origin_x] = Nonedef reset(self):self.game_area = [[None] * GRID_WIDTH for _ in range(GRID_HEIGHT)]self.score = 0self.highscore = load_highscore()self.next_shape = random.choice(SHAPES)self.next_color = random.choice(COLORS)self.fall_speed = self.BASE_FALL_SPEED # 使用常量初始化self.running = Trueself.initialize_shapes()def initialize_shapes(self):# 保存当前的next_shape作为新的current_shapeself.current_shape = self.next_shapeself.current_color = self.next_color# 特殊方块生成概率(15%)(测试设置成15%)if random.random() < 0.15:self.next_shape = [[1]] # 固定1x1形状self.next_color = random.choice(SPECIAL_COLORS)else:self.next_shape = random.choice(SHAPES)self.next_color = random.choice(COLORS)self.reset_position()def reset_position(self):shape_height = len(self.current_shape)# 正常生成在缓冲区上方(不要额外偏移)self.current_pos = [GRID_WIDTH // 2 - len(self.current_shape[0]) // 2,-shape_height # 原始生成高度]def check_collision(self, shape, pos):for y in range(len(shape)):for x in range(len(shape[y])):if shape[y][x]:px = pos[0] + xpy = pos[1] + y# 允许进入缓冲区(前1行)if px < 0 or px >= GRID_WIDTH: # 仅横向边界检测return True# 当方块进入可视区域(第1行及以下)时检测碰撞if py >= 1 and (py >= GRID_HEIGHT or self.game_area[py][px] is not None):return Truereturn Falsedef rotate_shape(self, clockwise=True):if self.current_shape == [[1]]: # 特殊方块不旋转returnoriginal_shape = self.current_shapeoriginal_pos = self.current_pos.copy()# 标准旋转实现(支持顺/逆时针)if clockwise:rotated = [list(row)[::-1] for row in zip(*original_shape)]else:rotated = [list(row) for row in zip(*original_shape)][::-1]# 标准墙踢测试位置(5种偏移)test_offsets = [[0, 0], # 原始位置[-1, 0], # 左移1格[1, 0], # 右移1格[0, -1], # 上移1格[-2, 0], # 左移2格(针对I型方块)[2, 0] # 右移2格(针对I型方块)]# 特殊处理I型方块的墙踢if len(original_shape) == 1 or len(original_shape[0]) == 4: # I型test_offsets.extend([[1, -1], [-1, -1], # 特殊偏移[1, 1], [-1, 1]])for offset in test_offsets:test_pos = [original_pos[0] + offset[0],original_pos[1] + offset[1]]if not self.check_collision(rotated, test_pos):self.current_shape = rotatedself.current_pos = test_posreturn# 所有测试位置都失败则恢复原状self.current_shape = original_shapeself.current_pos = original_posdef draw_rotating_shape(self):if not self.is_rotating:return# 创建旋转后的表面shape_width = len(self.current_shape[0]) * GRID_SIZEshape_height = len(self.current_shape) * GRID_SIZEshape_surface = pygame.Surface((shape_width, shape_height), pygame.SRCALPHA)# 绘制形状到临时表面for y, row in enumerate(self.current_shape):for x, cell in enumerate(row):if cell:pygame.draw.rect(shape_surface, self.current_color,(x * GRID_SIZE, y * GRID_SIZE,GRID_SIZE - 1, GRID_SIZE - 1))# 旋转并绘制rotated_surface = pygame.transform.rotate(shape_surface, self.rotation_angle)screen.blit(rotated_surface,(self.current_pos[0] * GRID_SIZE,self.current_pos[1] * GRID_SIZE))def merge_shape(self):should_game_over = Falsefor y, row in enumerate(self.current_shape):for x, cell in enumerate(row):if cell:px = self.current_pos[0] + xpy = self.current_pos[1] + y# 转换为实际坐标时检查是否超出缓冲区if py < 1: # 如果固定到缓冲区(前1行)should_game_over = Trueif 0 <= px < GRID_WIDTH and 0 <= py < GRID_HEIGHT:self.game_area[py][px] = self.current_colorif should_game_over:self.game_over()def clear_lines(self):lines_to_clear = [i for i, row in enumerate(self.game_area) if all(cell is not None for cell in row)]# 统计被消除的方块总数blocks_cleared = 0if lines_to_clear:# 检查被消除的行中是否有特殊方块for line_idx in lines_to_clear:blocks_cleared += GRID_WIDTH # 每行有GRID_WIDTH个方块# 检查被消除的行中是否有特殊方块for x in range(GRID_WIDTH):color = self.game_area[line_idx][x]if color in SPECIAL_COLORS:# 传递方块坐标给特效方法effect = self.special_effects.get(color)if effect:effect(x, line_idx) # 新增坐标参数# 统一清除行并生成新行for idx in reversed(lines_to_clear):del self.game_area[idx]new_rows = [[None] * GRID_WIDTH for _ in lines_to_clear]self.game_area = new_rows + self.game_areaself.score += blocks_cleared * 1self.update_game_speed() # 改为调用独立方法def update_game_speed(self):"""根据分数更新下落速度(非线性变化)"""raw_speed = self.BASE_FALL_SPEED - int(math.sqrt(self.score)) * self.SPEED_DECAY_FACTOR# 同时设置上下限self.fall_speed = max(self.MAX_ACCELERATION, # 最快不能超过200ms/格min(raw_speed, self.BASE_FALL_SPEED) # 最慢不低于初始速度)def draw_grid(self):# 在顶部绘制红色警戒线pygame.draw.line(screen, (255, 0, 0),(0, GRID_SIZE),(GAME_PANEL_WIDTH, GRID_SIZE),3)current_time = pygame.time.get_ticks()# 检查是否需要取消隐藏if self.hide_row_start_time is not None:elapsed = (current_time - self.hide_row_start_time) / 1000if elapsed >= 30: # 超过60秒则清除隐藏self.hidden_rows = []self.hide_row_start_time = None# 绘制所有非隐藏行for y in range(GRID_HEIGHT):if y in self.hidden_rows:continue # 跳过隐藏行for x in range(GRID_WIDTH):color = self.game_area[y][x]# 添加颜色类型安全检查if isinstance(color, tuple) and len(color) == 3:# 绘制方块pygame.draw.rect(screen, color,(x * GRID_SIZE, y * GRID_SIZE,GRID_SIZE - 1, GRID_SIZE - 1))def draw_current_shape(self):# 特殊方块闪烁效果if self.current_shape == [[1]]:alpha = 128 + int(127 * math.sin(pygame.time.get_ticks() / 200))color = (*self.current_color, alpha)surface = pygame.Surface((GRID_SIZE, GRID_SIZE), pygame.SRCALPHA)surface.fill(color)# 绘制特殊方块for y, row in enumerate(self.current_shape):for x, cell in enumerate(row):if cell:px = (self.current_pos[0] + x) * GRID_SIZEpy = (self.current_pos[1] + y) * GRID_SIZE# 先绘制方块本体pygame.draw.rect(screen, self.current_color,(px, py, GRID_SIZE - 1, GRID_SIZE - 1))else:surface = pygame.Surface((GRID_SIZE, GRID_SIZE))surface.fill(self.current_color)for y, row in enumerate(self.current_shape):for x, cell in enumerate(row):if cell:px = (self.current_pos[0] + x) * GRID_SIZEpy = (self.current_pos[1] + y) * GRID_SIZEpygame.draw.rect(screen, self.current_color,(px, py, GRID_SIZE - 1, GRID_SIZE - 1))def apply_speed_buffer(self):"""底部缓冲减速系统"""shape_height = len(self.current_shape)bottom_pos = self.current_pos[1] + shape_heightbuffer_zone = 3 # 距离底部3格开始缓冲if (GRID_HEIGHT - bottom_pos) < buffer_zone:buffer_factor = (GRID_HEIGHT - bottom_pos) / buffer_zonereturn buffer_factor * 0.5 + 0.5 # 返回0.5-1.0的系数return 1.0def draw_info_panel(self):font = load_font(24)pygame.draw.rect(screen, BLACK, (GAME_PANEL_WIDTH, 0, INFO_PANEL_WIDTH, WINDOW_HEIGHT))effect_text = ["红: 3x3爆炸","橙: 清除三行","黄: 清当前+底行","绿: 奇偶列交换","蓝: 隐藏底5行","靛: 镜像翻转列","紫: 十字清除"]y_pos = 300for text in effect_text:label = font.render(text, True, WHITE)screen.blit(label, (GAME_PANEL_WIDTH + 10, y_pos))y_pos += 30# 下一个方块title = font.render("下一个方块", True, WHITE)screen.blit(title, (GAME_PANEL_WIDTH + 10, 30))# 预览方块start_x = GAME_PANEL_WIDTH + (INFO_PANEL_WIDTH - len(self.next_shape[0]) * GRID_SIZE) // 2start_y = 100for y, row in enumerate(self.next_shape):for x, cell in enumerate(row):if cell:# 如果是特殊方块(1x1)if self.next_shape == [[1]]:# 绘制方块本体pygame.draw.rect(screen, self.next_color,(start_x + x * GRID_SIZE + 2, start_y + y * GRID_SIZE + 2,GRID_SIZE - 4, GRID_SIZE - 4))else:pygame.draw.rect(screen, self.next_color,(start_x + x * GRID_SIZE, start_y + y * GRID_SIZE, GRID_SIZE - 1,GRID_SIZE - 1))# 分数显示score_text = font.render(f"分数: {self.score}", True, WHITE)screen.blit(score_text, (GAME_PANEL_WIDTH + 10, 200))highscore_text = font.render(f"最高分: {self.highscore}", True, WHITE)screen.blit(highscore_text, (GAME_PANEL_WIDTH + 10, 250))# 添加暂停状态显示if self.paused:font = load_font(36)pause_text = font.render("已暂停 (ESC继续)", True, (255, 0, 0))text_rect = pause_text.get_rect(center=(WINDOW_WIDTH // 2, WINDOW_HEIGHT // 2))screen.blit(pause_text, text_rect)def handle_input(self):for event in pygame.event.get():if event.type == pygame.QUIT:self.running = Falseif event.type == pygame.KEYDOWN:# 暂停状态下仍处理ESC键if event.key == pygame.K_ESCAPE:self.paused = not self.paused# 非暂停状态处理游戏操作if not self.paused:if event.key == pygame.K_LEFT:self.current_pos[0] -= 1if self.check_collision(self.current_shape, self.current_pos):self.current_pos[0] += 1elif event.key == pygame.K_RIGHT:self.current_pos[0] += 1if self.check_collision(self.current_shape, self.current_pos):self.current_pos[0] -= 1elif event.key == pygame.K_DOWN:self.current_pos[1] += 1if self.check_collision(self.current_shape, self.current_pos):self.current_pos[1] -= 1elif event.key == pygame.K_UP:self.rotate_shape()# 空格键加速功能elif event.key == pygame.K_SPACE:while not self.check_collision(self.current_shape, self.current_pos):self.current_pos[1] += 1self.current_pos[1] -= 1def game_over(self):if self.score > self.highscore:save_highscore(self.score)screen.fill(BLACK)font = load_font(48)text = font.render("游戏结束", True, (255, 0, 0))text_rect = text.get_rect(center=(WINDOW_WIDTH // 2, WINDOW_HEIGHT // 2 - 30))screen.blit(text, text_rect)score_text = font.render(f"得分: {self.score}", True, WHITE)score_rect = score_text.get_rect(center=(WINDOW_WIDTH // 2, WINDOW_HEIGHT // 2 + 30))screen.blit(score_text, score_rect)pygame.display.update()pygame.time.wait(3000)self.running = Falsedef run(self):clock = pygame.time.Clock()fall_time = pygame.time.get_ticks()while self.running:current_time = pygame.time.get_ticks()# 处理输入(即使暂停也需要响应)self.handle_input()if not self.paused: # 只在非暂停状态更新游戏# 根据重力方向计算移动步长move_step = 1 # 固定向下移动# 自动下落if current_time - fall_time > self.fall_speed:self.current_pos[1] += move_step# 碰撞检测与处理if self.check_collision(self.current_shape, self.current_pos):self.current_pos[1] -= move_step # 回退位置self.merge_shape()self.clear_lines()self.initialize_shapes() # 生成新方块# 游戏结束检测if self.check_collision(self.current_shape, self.current_pos):self.game_over()fall_time = current_time# 绘制界面(始终执行)screen.fill(DARK_GRAY)self.draw_grid()self.draw_current_shape()self.draw_info_panel()pygame.display.update()clock.tick(60)if __name__ == "__main__":game = GameState()game.run()pygame.quit()
总结
本游戏绝对还存在着一些问题, 希望各位小伙伴在玩了之后可以提供一些建议,可以在评论区进行留言,游戏的话因为增加了一些特殊的方块,所以难度还是有所下降,祝各位玩的开心!!!