鱼C论坛

 找回密码
 立即注册
查看: 157|回复: 19

[作品展示] 俄罗斯方块Tetris(tkinter)

[复制链接]
回帖奖励 140 鱼币 回复本帖可获得 5 鱼币奖励! 每人限 1 次(中奖概率 70%)
发表于 5 天前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 pyzyd 于 2025-7-21 12:35 编辑


代码实现:
动画3.gif

感觉写了一坨屎山


源代码:

  1. import tkinter as tk
  2. from tkinter import messagebox
  3. import random
  4. import time


  5. class Game(tk.Tk):
  6.     def __init__(self):
  7.         tk.Tk.__init__(self)
  8.         self.title('Tetris')
  9.         # 固定边框
  10.         self.resizable(False,False)
  11.         # 设置边长、像素
  12.         self.width = 12
  13.         self.height = 20
  14.         self.px = 30

  15.         # 设置不同形状的格子
  16.         self.shapes = {
  17.             'O': [(-1, -1), (0, -1), (-1, 0), (0, 0)],
  18.             'I': [(0, 1), (0, 0), (0, -1), (0, -2)],
  19.             'T': [(-1, -1), (0, -1), (0, 0), (1, -1)],
  20.             'Z': [(-1, 0), (0, 0), (0, 1), (1, 1)],
  21.             'S': [(-1, 1), (0, 1), (0, 0), (1, 0)],
  22.             'L': [(1, 1), (0, 1), (0, 0), (0, -1)],
  23.             'J': [(-1, 1), (0, 1), (0, 0), (0, -1)],
  24.         }

  25.         # 设置不同格子的颜色
  26.         self.colors = ['#FF0000', '#FF7F00', "#D4D432", "#00FF00", "#0000FF", '#4B008B', "#D3007F"]

  27.         self.shape_index = random.choice(list(self.shapes.keys()))
  28.         self.shape_color = random.choice(self.colors)
  29.         self.shape = self.shapes[self.shape_index]

  30.         # 表示固定的格子
  31.         self.fixed_shape = {}
  32.         self.fixed_set = set()

  33.         # 创建画布
  34.         self.canvas = tk.Canvas(
  35.             self, width=self.width*self.px,
  36.             height=self.height*self.px)
  37.         self.canvas.grid(row=0,column=0,columnspan=3,rowspan=4, padx=10, pady=10)

  38.         self.btnr = tk.Button(self, text='<', command=lambda:self.move((-1, 0)))
  39.         self.btnr.grid(row=4, column=0, padx=10, pady=10, sticky='nsew')

  40.         self.btn_rotate = tk.Button(self, text='-', command=self.rotate_shape)
  41.         self.btn_rotate.grid(row=4, column=1, padx=10, pady=10, sticky='nsew')

  42.         self.btnd = tk.Button(self, text='v', command=lambda:self.move((0, 1)))
  43.         self.btnd.grid(row=5, column=1, padx=10, pady=10, sticky='nsew')

  44.         self.btnl = tk.Button(self, text='>', command=lambda:self.move((1, 0)))
  45.         self.btnl.grid(row=4, column=2, padx=10, pady=10, sticky='nsew')

  46.         self.score = 0
  47.         self.v = tk.StringVar()
  48.         self.v.set(f'当前分数:{self.score}')

  49.         self.label1 = tk.Label(self, textvariable=self.v,font=('微软雅黑', 24))
  50.         self.label1.grid(row=0, column=3, padx=10, pady=10, sticky='nsew')

  51.         self.best_score = self.read_best_score()
  52.         self.v2 = tk.StringVar()
  53.         self.v2.set(f'最高分:{self.best_score}')
  54.         self.label2 = tk.Label(self, textvariable=self.v2,font=('微软雅黑', 24))
  55.         self.label2.grid(row=1, column=3, padx=10, pady=10, sticky='nsew')

  56.         self.text = tk.Text(self, height=10, width=20, font=('微软雅黑', 18))
  57.         self.text.grid(row=2, column=3, padx=10, pady=10, sticky='nsew')

  58.         self.text.insert(tk.END, '游戏规则:\n')
  59.         self.text.insert(tk.END, '1. 使用方向键控制方块的移动\n')
  60.         self.text.insert(tk.END, '2. 使用方向上键(Up键)旋转方块\n')
  61.         self.text.insert(tk.END, '3. 消除满行即可获得分数\n')
  62.         self.text.insert(tk.END, '4. 超出画布即游戏结束\n')

  63.         self.bind('<Left>', lambda event:self.move((-1, 0)))
  64.         self.bind('<Right>', lambda event:self.move((1, 0)))
  65.         self.bind('<Up>', lambda event:self.rotate_shape())
  66.         self.bind('<Down>', lambda event:self.move((0, 1)))
  67.         
  68.         # 设置画布的点集(范围)
  69.         self.canvas_set = set()
  70.         for i in range(0, self.width):
  71.             for j in range(0, self.height):
  72.                 self.canvas_set.add((i,j))
  73.         
  74.         # 运行游戏
  75.         self.run_game()


  76.     # 读取最佳分数
  77.     def read_best_score(self):
  78.         """读取最高分"""
  79.         try:
  80.             # 打开best_score.txt文件,以只读模式
  81.             with open('best_score.txt', 'r') as f:
  82.                 # 读取文件内容,并转换为整数
  83.                 return int(f.read())
  84.         except:
  85.             # 如果出现异常,返回0
  86.             return 0
  87.         
  88.     # 定义一个方法,用于将最高分写入文件
  89.     def write_best_score(self, score):
  90.         """写入最高分"""
  91.         # 打开文件best_score.txt,以写入模式
  92.         with open('best_score.txt', 'w') as f:
  93.             # 将最高分转换为字符串,并写入文件
  94.             f.write(str(score))

  95.     # 运行游戏
  96.     def run_game(self):
  97.         # 初始化游戏
  98.         self.initialize()
  99.         # 自动下落
  100.         self.auto_down()
  101.         # 进入主循环
  102.         self.mainloop()

  103.     def initialize(self):
  104.         """初始化界面"""
  105.         # 绘制背景
  106.         self.draw_bg()
  107.         # 生成初始格子
  108.         self.generate_shape()
  109.         # 绘制初始
  110.         self.draw_shape(self.shape, self.x, self.y, self.shape_color)

  111.     def generate_shape(self):
  112.         """生成新的格子"""
  113.         self.shape_index = random.choice(list(self.shapes.keys()))
  114.         self.shape_color = random.choice(self.colors)
  115.         self.shape = self.shapes[self.shape_index]
  116.         # 设置初始位置
  117.         self.x = random.randint(self.width//4, self.width//4*3)
  118.         self.y = -4
  119.         

  120.     def auto_down(self):
  121.         """自动下移"""
  122.         # 翻转方块
  123.         self.flip()
  124.         # 移动方块
  125.         self.move()
  126.         # 每隔500毫秒调用一次auto_down方法
  127.         self.after(500, self.auto_down)

  128.    
  129.     def move(self, direction=(0, 1)):
  130.         """移动格子"""
  131.         # 获取移动方向
  132.         dx, dy = direction
  133.         
  134.         # 更新格子的坐标
  135.         self.x += dx
  136.         self.y += dy
  137.         
  138.         # 如果格子超出边界,则回退到原来的位置
  139.         if self.is_out():
  140.             self.x -= dx
  141.             self.y -= dy

  142.         # 如果格子到达底部,则将格子固定在底部,并生成新的格子
  143.         if self.is_bottom():
  144.             # 如果格子颜色已经在固定格子的集合中,则更新该格子的坐标
  145.             if self.shape_color in self.fixed_shape:
  146.                 self.fixed_shape[self.shape_color].update([(self.x+dx,self.y+dy) for dx,dy in self.shape])
  147.             # 如果格子颜色不在固定格子的集合中,则将格子添加到固定格子的集合中
  148.             else:
  149.                 self.fixed_shape[self.shape_color] = set([(self.x+dx,self.y+dy) for dx,dy in self.shape])
  150.             # 将格子的坐标添加到固定格子的集合中
  151.             for x, y in self.shape:
  152.                 self.fixed_set.add((self.x + x, self.y + y))
  153.             # 生成新的格子
  154.             self.generate_shape()
  155.             
  156.    

  157.     def is_over(self):
  158.         """判断游戏是否结束"""
  159.         for x, y in self.fixed_set:
  160.             if y < 0:
  161.                 return True
  162.         return False

  163.     def rotate_shape(self):
  164.         """旋转格子"""
  165.         if self.shape != self.shapes['O']:
  166.             temp = [(-dy, dx) for dx, dy in self.shape]
  167.             for dx, dy in temp:
  168.                 if self.x + dx < 0:
  169.                     self.x += 1
  170.                 elif self.x + dx >= self.width:
  171.                     self.x -= 1
  172.             # 检查旋转后的形状是否与已经固定的方块重叠
  173.             if not self.is_overlap():
  174.                 self.shape = temp


  175.     def is_overlap(self):
  176.         """检查新的格子是否与已经固定的格子重叠"""
  177.         for dx, dy in self.shape:
  178.             if (self.x + dx, self.y + dy) in self.fixed_set:
  179.                 return True
  180.         return False
  181.         

  182.     def is_out(self):
  183.         """判断是否越界"""
  184.         for dx, dy in self.shape:
  185.             if self.x + dx < 0 or self.x + dx >= self.width or self.y + dy >= self.height:
  186.                 return True
  187.         if self.is_overlap():
  188.             return True
  189.         return False
  190.         
  191.     def is_bottom(self):
  192.         """判断是否到底"""
  193.         for dx, dy in self.shape:
  194.             if self.y + dy == self.height - 1:
  195.                 return True
  196.             if (self.x + dx, self.y + dy + 1) in self.fixed_set:
  197.                 return True
  198.         return False
  199.    
  200.     def flip(self):
  201.         """更新画布"""
  202.         # 删除画布上所有元素
  203.         self.canvas.delete("all")
  204.         # 重绘元素
  205.         self.draw_bg()

  206.         y_s = self.is_delete()
  207.         if y_s:
  208.             index_y = y_s[0]
  209.             l = len(y_s)
  210.             self.score += l * 10
  211.             self.v.set(f'当前分数:{self.score}')
  212.             if self.score > self.best_score:
  213.                 self.best_score = self.score
  214.                 self.v2.set(f'最高分:{self.best_score}')
  215.                 self.write_best_score(self.best_score)
  216.             for shape_color, _ in self.fixed_shape.items():
  217.                 temp_list = list(_)
  218.                 for x, y in _:
  219.                     if y < index_y:
  220.                         temp_list.remove((x, y))
  221.                         temp_list.append((x, y + l))
  222.                         self.fixed_set.discard((x, y))
  223.                         self.fixed_set.add((x, y + l))
  224.                 self.fixed_shape[shape_color] = set(temp_list)

  225.         for shape_color, _ in self.fixed_shape.items():
  226.             for x, y in _:
  227.                 self.draw_rect(x, y, shape_color)

  228.         self.draw_shape(self.shape, self.x, self.y, self.shape_color)

  229.         # 判断是否结束
  230.         if self.is_over():
  231.             # 游戏结束
  232.             self.game_over()

  233.         


  234.     def draw_bg(self):
  235.         """"绘制像素"""
  236.         # 设置背景像素颜色
  237.         fill = '#CCCCCC'
  238.         outline = 'white'
  239.         px = self.px
  240.         for x, y in list(self.canvas_set):
  241.             self.draw_rect(x, y, fill, outline)

  242.     def draw_rect(self, x, y, color, outline="white"):
  243.         """"绘制像素"""
  244.         px = self.px
  245.         # 绘制像素
  246.         self.canvas.create_rectangle(
  247.             x * px, y * px,
  248.             (x + 1) * px, (y + 1) * px,
  249.             fill=color, outline=outline
  250.         )

  251.     def draw_shape(self, shape, x, y, color):
  252.         """绘制不同的格子"""
  253.         for dx, dy in shape:
  254.             self.draw_rect(x + dx, y + dy, color)


  255.     def is_delete(self):
  256.         """判断是否可以删除"""
  257.         y_s = []
  258.         for y in range(self.height):
  259.             if all((x, y) in self.fixed_set for x in range(self.width)):
  260.                 for x in range(self.width):
  261.                     self.fixed_set.remove((x, y))
  262.                     for _ in self.fixed_shape.values():
  263.                         _.discard((x, y))
  264.                 y_s.append(y)
  265.         return y_s

  266.     def game_over(self):
  267.         """游戏结束"""
  268.         self.canvas.create_text(
  269.             self.width * self.px / 2,
  270.             self.height * self.px / 2,
  271.             text="Game Over",
  272.             font=("Arial", 36),
  273.             fill="red"
  274.         )
  275.         self.canvas.update()
  276.         time.sleep(1)
  277.         yesno = messagebox.askyesno("提示", "是否重新开始游戏?")
  278.         self.destroy()
  279.         if yesno:
  280.             self.__init__()


  281. if __name__ == '__main__':
  282.     game = Game()
复制代码




打包好的文件:
https://wwrx.lanzoum.com/iAsuh31hj84d
密码:b5pi

评分

参与人数 4荣誉 +16 鱼币 +16 贡献 +16 收起 理由
~风介~ + 3 + 3 + 5 鱼C有你更精彩^_^
当下的力量 + 3 + 3 + 3 鱼C有你更精彩^_^
青出于蓝 + 5 + 5 + 5 鱼C有你更精彩^_^
cjjJasonchen + 5 + 5 + 3 狠狠支持!

查看全部评分

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 5 天前 | 显示全部楼层
我仔细分析了你的俄罗斯方块代码,发现几个关键问题需要修复。以下是主要问题和解决方案:

### 主要问题及修复方案
1. **形状定义缺失**:`self.shapes` 字典内容未定义
2. **颜色列表缺失**:`self.colors` 未初始化
3. **方块旋转逻辑错误**:旋转函数不完整
4. **固定方块存储结构混乱**:`fixed_shape` 和 `fixed_set` 使用混乱
5. **行消除逻辑缺陷**:`is_delete()` 函数未正确实现
6. **按键绑定错误**:方向键事件字符串缺失

### 修复后的核心代码

  1. import tkinter as tk
  2. from tkinter import messagebox
  3. import random
  4. import time

  5. class Game(tk.Tk):
  6.     def __init__(self):
  7.         tk.Tk.__init__(self)
  8.         self.title('Tetris')
  9.         self.resizable(False, False)
  10.         self.width = 12
  11.         self.height = 20
  12.         self.px = 30
  13.         
  14.         # 1. 补全方块形状定义
  15.         self.shapes = {
  16.             'O': [(0,0), (0,1), (1,0), (1,1)],
  17.             'I': [(0,-1), (0,0), (0,1), (0,2)],
  18.             'T': [(0,0), (-1,0), (1,0), (0,1)],
  19.             'Z': [(0,0), (-1,0), (0,1), (1,1)],
  20.             'S': [(0,0), (1,0), (0,1), (-1,1)],
  21.             'L': [(0,0), (0,1), (0,2), (1,2)],
  22.             'J': [(0,0), (0,1), (0,2), (-1,2)]
  23.         }
  24.         
  25.         # 2. 添加颜色列表
  26.         self.colors = ['red', 'yellow', 'blue', 'green', 'purple', 'orange', 'cyan']
  27.         
  28.         # 3. 初始化当前方块
  29.         self.shape_index = random.choice(list(self.shapes.keys()))
  30.         self.shape_color = random.choice(self.colors)
  31.         self.shape = self.shapes[self.shape_index]
  32.         
  33.         # 4. 简化固定方块存储结构
  34.         self.fixed_blocks = {}  # 格式: {(x,y): color}
  35.         
  36.         # 画布初始化(保持不变)
  37.         self.canvas = tk.Canvas(
  38.             self, width=self.width*self.px,
  39.             height=self.height*self.px)
  40.         self.canvas.grid(row=0,column=0,columnspan=3,rowspan=4, padx=10, pady=10)
  41.         
  42.         # 5. 修复按钮和按键绑定
  43.         self.btn_left = tk.Button(self, text='←', command=lambda:self.move((-1, 0)))
  44.         self.btn_left.grid(row=4, column=0, padx=10, pady=10)
  45.         
  46.         self.btn_right = tk.Button(self, text='→', command=lambda:self.move((1, 0)))
  47.         self.btn_right.grid(row=4, column=2, padx=10, pady=10)
  48.         
  49.         # 绑定键盘事件
  50.         self.bind('<Left>', lambda event:self.move((-1, 0)))
  51.         self.bind('<Right>', lambda event:self.move((1, 0)))
  52.         self.bind('<Up>', lambda event:self.rotate_shape())
  53.         self.bind('<Down>', lambda event:self.move((0, 1)))
  54.         
  55.         # 其余初始化代码保持不变...
  56.         self.run_game()

  57.     # 6. 修复旋转函数
  58.     def rotate_shape(self):
  59.         if self.shape_index == 'O':  # O型方块不旋转
  60.             return
  61.             
  62.         # 计算旋转后的新形状 (90度顺时针)
  63.         new_shape = [(y, -x) for (x, y) in self.shape]
  64.         
  65.         # 备份当前状态
  66.         old_shape = self.shape
  67.         old_x = self.x
  68.         
  69.         # 尝试旋转
  70.         self.shape = new_shape
  71.         
  72.         # 旋转后越界则恢复
  73.         if self.is_out():
  74.             self.shape = old_shape

  75.     # 7. 修复方块固定逻辑
  76.     def move(self, direction=(0, 1)):
  77.         dx, dy = direction
  78.         self.x += dx
  79.         self.y += dy
  80.         
  81.         if self.is_out() or self.is_overlap():
  82.             self.x -= dx
  83.             self.y -= dy
  84.             if dy > 0:  # 如果是向下移动被阻挡
  85.                 self.fix_block()
  86.                 self.check_lines()
  87.                 self.generate_shape()
  88.                 if self.is_overlap():  # 新方块重叠即结束
  89.                     self.game_over()
  90.         
  91.         self.update_display()

  92.     def fix_block(self):
  93.         for dx, dy in self.shape:
  94.             x, y = self.x + dx, self.y + dy
  95.             if 0 <= y < self.height:
  96.                 self.fixed_blocks[(x, y)] = self.shape_color

  97.     # 8. 重写行消除逻辑
  98.     def check_lines(self):
  99.         lines_cleared = 0
  100.         for y in range(self.height-1, -1, -1):
  101.             if all((x, y) in self.fixed_blocks for x in range(self.width)):
  102.                 lines_cleared += 1
  103.                 # 删除该行
  104.                 for x in range(self.width):
  105.                     del self.fixed_blocks[(x, y)]
  106.                 # 上方方块下移
  107.                 for yy in range(y-1, -1, -1):
  108.                     for x in range(self.width):
  109.                         if (x, yy) in self.fixed_blocks:
  110.                             color = self.fixed_blocks.pop((x, yy))
  111.                             self.fixed_blocks[(x, yy+1)] = color
  112.         
  113.         if lines_cleared:
  114.             self.score += lines_cleared * 10
  115.             self.v.set(f'当前分数:{self.score}')
  116.             if self.score > self.best_score:
  117.                 self.best_score = self.score
  118.                 self.v2.set(f'最高分:{self.best_score}')
  119.                 self.write_best_score(self.best_score)

  120.     # 9. 简化碰撞检测
  121.     def is_overlap(self):
  122.         for dx, dy in self.shape:
  123.             x, y = self.x + dx, self.y + dy
  124.             if (x, y) in self.fixed_blocks:
  125.                 return True
  126.         return False

  127.     # 10. 重绘函数优化
  128.     def update_display(self):
  129.         self.canvas.delete("all")
  130.         self.draw_bg()
  131.         
  132.         # 绘制固定方块
  133.         for (x, y), color in self.fixed_blocks.items():
  134.             self.draw_rect(x, y, color)
  135.         
  136.         # 绘制当前方块
  137.         self.draw_shape(self.shape, self.x, self.y, self.shape_color)

  138. # 其余函数保持基本结构,根据上述调整优化
复制代码


### 主要改进说明:
1. **形状和颜色**:补全了7种标准俄罗斯方块形状定义和对应颜色
2. **数据结构**:使用单一字典 `fixed_blocks` 存储固定方块,键为坐标,值为颜色
3. **旋转逻辑**:实现90度顺时针旋转,O型方块不旋转
4. **行消除**:重写为逐行检测,消除后上方方块自动下落
5. **碰撞检测**:合并边界检测和方块重叠检测
6. **按键绑定**:修复方向键事件绑定

### 优化建议:
1. 增加游戏速度随分数提升的机制
2. 添加下一个方块预览功能
3. 实现墙壁反弹(Wall kick)旋转机制
4. 使用面向对象重构方块类

这个版本解决了原始代码的主要问题,结构更清晰。建议先测试基础功能(移动、旋转、消行),再逐步添加高级功能。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

评分

参与人数 1荣誉 +6 鱼币 +6 贡献 +6 C币 +6 收起 理由
小甲鱼 + 6 + 6 + 6 + 6 鱼C有你更精彩^_^

查看全部评分

小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 5 天前 | 显示全部楼层

回帖奖励 +5 鱼币

挺好的
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 5 天前 | 显示全部楼层

回帖奖励 +5 鱼币

古德
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 5 天前 | 显示全部楼层

回帖奖励 +5 鱼币

666
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 5 天前 | 显示全部楼层
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 5 天前 | 显示全部楼层

回帖奖励 +5 鱼币

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 5 天前 | 显示全部楼层

回帖奖励 +5 鱼币

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 5 天前 | 显示全部楼层
不错,支持~
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 5 天前 | 显示全部楼层

回帖奖励 +5 鱼币

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 5 天前 | 显示全部楼层
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 5 天前 | 显示全部楼层

回帖奖励 +5 鱼币

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 4 天前 | 显示全部楼层

回帖奖励 +5 鱼币

狠狠赞!
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 4 天前 | 显示全部楼层

回帖奖励 +5 鱼币

小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 4 天前 | 显示全部楼层
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 4 天前 | 显示全部楼层

回帖奖励 +5 鱼币

历害啊
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 4 天前 | 显示全部楼层
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 4 天前 | 显示全部楼层

回帖奖励 +5 鱼币

gooddddddd
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 4 天前 | 显示全部楼层
厉害
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 4 天前 | 显示全部楼层

回帖奖励 +5 鱼币

支持
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-7-26 17:00

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表