├── imgs └── 1_5_1.png ├── .gitignore ├── 4_love ├── love_t1.txt ├── love3.txt ├── love1.txt └── love2.txt ├── README.md ├── LICENSE ├── 1_BASIC ├── 001.py ├── 002.py ├── 003.py ├── 004.py ├── 005.py └── 4-5.py ├── DOCS └── 1 │ └── 5.md ├── 3_AI ├── util.py ├── multi_tetris.py └── tetris_by_class.py └── 2_MODE ├── 01.py ├── 04.py ├── 02.py └── 03.py /imgs/1_5_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BigShuang/Tetris/HEAD/imgs/1_5_1.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | /Files/ 5 | .idea/ 6 | 7 | temp.py 8 | *demo*.py 9 | *.pyc -------------------------------------------------------------------------------- /4_love/love_t1.txt: -------------------------------------------------------------------------------- 1 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 | 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 6 | 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 7 | 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 8 | 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tetris 2 | 俄罗斯方块教程,基于python tkinter实现 3 | 4 | ## 1 基础版 5 | 对应文件夹:1_BASIC 6 | 本项目基础版最终效果见本人b站投稿[av81480858](https://www.bilibili.com/video/av81480858) 简介部分 7 | 8 | 本文已录制视频教程上传b站:https://www.bilibili.com/video/av81480858 。视频部分讲解的比较详细,觉得文字版不够详细的可以去看视频。 9 | 10 | ## 3 AI自动玩俄罗斯方块 11 | 对应文件夹:3_AI 12 | 运行效果见本人b站投稿[av82337073](https://www.bilibili.com/video/av82337073) 13 | 14 | 其中multi_tetris.py对应宽屏多个 15 | 16 | tetris_by_class.py对应单个竖屏 -------------------------------------------------------------------------------- /4_love/love3.txt: -------------------------------------------------------------------------------- 1 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 | 0 0 0 0 0 1 1 1 0 0 0 0 0 1 1 1 0 0 0 0 7 | 0 0 0 0 1 1 1 1 1 1 0 1 1 1 1 1 1 0 0 0 8 | 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 9 | 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 10 | 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 11 | 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 12 | 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 13 | 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 14 | 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 15 | 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 16 | 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 17 | 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 -------------------------------------------------------------------------------- /4_love/love1.txt: -------------------------------------------------------------------------------- 1 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 | 0 0 0 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 0 0 7 | 0 0 0 0 0 1 1 1 1 1 0 1 1 1 1 1 0 0 0 0 8 | 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 9 | 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 10 | 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 11 | 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 12 | 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 13 | 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 14 | 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 15 | 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 16 | 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 17 | 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 18 | 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 19 | -------------------------------------------------------------------------------- /4_love/love2.txt: -------------------------------------------------------------------------------- 1 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 | 0 0 0 0 0 1 1 1 1 0 0 0 1 1 1 1 0 0 0 0 7 | 0 0 0 0 1 1 1 1 1 1 0 1 1 1 1 1 1 0 0 0 8 | 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 9 | 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 10 | 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 11 | 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 12 | 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 13 | 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 14 | 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 15 | 0 0 0 0 0 0 0 1 1 1 1 1 1 1 0 0 0 0 0 0 16 | 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 0 17 | 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 18 | 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 BigShuang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /1_BASIC/001.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | cell_size = 30 4 | C = 12 5 | R = 20 6 | height = R * cell_size 7 | width = C * cell_size 8 | 9 | # 定义各种形状 10 | SHAPES = { 11 | "O": [(-1, -1), (0, -1), (-1, 0), (0, 0)], 12 | "S": [(-1, 0), (0, 0), (0, -1), (1, -1)], 13 | "T": [(-1, 0), (0, 0), (0, -1), (1, 0)], 14 | "I": [(0, 1), (0, 0), (0, -1), (0, -2)], 15 | "L": [(-1, 0), (0, 0), (-1, -1), (-1, -2)], 16 | "J": [(-1, 0), (0, 0), (0, -1), (0, -2)], 17 | "Z": [(-1, -1), (0, -1), (0, 0), (1, 0)], 18 | } 19 | 20 | # 定义各种形状的颜色 21 | SHAPESCOLOR = { 22 | "O": "blue", 23 | "S": "red", 24 | "T": "yellow", 25 | "I": "green", 26 | "L": "purple", 27 | "J": "orange", 28 | "Z": "Cyan", 29 | } 30 | 31 | 32 | def draw_cell_by_cr(canvas, c, r, color="#CCCCCC"): 33 | """ 34 | :param canvas: 画板,用于绘制一个方块的Canvas对象 35 | :param c: 方块所在列 36 | :param r: 方块所在行 37 | :param color: 方块颜色,默认为#CCCCCC,轻灰色 38 | :return: 39 | """ 40 | x0 = c * cell_size 41 | y0 = r * cell_size 42 | x1 = c * cell_size + cell_size 43 | y1 = r * cell_size + cell_size 44 | canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2) 45 | 46 | 47 | # 绘制空白面板 48 | def draw_blank_board(canvas): 49 | for ri in range(R): 50 | for ci in range(C): 51 | draw_cell_by_cr(canvas, ci, ri) 52 | 53 | 54 | def draw_cells(canvas, c, r, cell_list, color="#CCCCCC"): 55 | """ 56 | 绘制指定形状指定颜色的俄罗斯方块 57 | :param canvas: 画板 58 | :param r: 该形状设定的原点所在的行 59 | :param c: 该形状设定的原点所在的列 60 | :param cell_list: 该形状各个方格相对自身所处位置 61 | :param color: 该形状颜色 62 | :return: 63 | """ 64 | for cell in cell_list: 65 | cell_c, cell_r = cell 66 | ci = cell_c + c 67 | ri = cell_r + r 68 | # 判断该位置方格在画板内部(画板外部的方格不再绘制) 69 | if 0 <= ci < C and 0 <= ri < R: 70 | draw_cell_by_cr(canvas, ci, ri, color) 71 | 72 | 73 | win = tk.Tk() 74 | canvas = tk.Canvas(win, width=width, height=height, ) 75 | canvas.pack() 76 | 77 | draw_blank_board(canvas) 78 | 79 | 80 | draw_cells(canvas, 3, 3, SHAPES['O'], SHAPESCOLOR['O']) 81 | draw_cells(canvas, 3, 8, SHAPES['S'], SHAPESCOLOR['S']) 82 | draw_cells(canvas, 3, 13, SHAPES['T'], SHAPESCOLOR['T']) 83 | draw_cells(canvas, 8, 3, SHAPES['I'], SHAPESCOLOR['I']) 84 | draw_cells(canvas, 8, 8, SHAPES['L'], SHAPESCOLOR['L']) 85 | draw_cells(canvas, 8, 13, SHAPES['J'], SHAPESCOLOR['J']) 86 | draw_cells(canvas, 5, 18, SHAPES['Z'], SHAPESCOLOR['Z']) 87 | 88 | win.mainloop() 89 | -------------------------------------------------------------------------------- /1_BASIC/002.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | 3 | cell_size = 30 4 | C = 12 5 | R = 20 6 | height = R * cell_size 7 | width = C * cell_size 8 | 9 | FPS = 200 # 刷新页面的毫秒间隔 10 | 11 | # 定义各种形状 12 | SHAPES = { 13 | "O": [(-1, -1), (0, -1), (-1, 0), (0, 0)], 14 | "S": [(-1, 0), (0, 0), (0, -1), (1, -1)], 15 | "T": [(-1, 0), (0, 0), (0, -1), (1, 0)], 16 | "I": [(0, 1), (0, 0), (0, -1), (0, -2)], 17 | "L": [(-1, 0), (0, 0), (-1, -1), (-1, -2)], 18 | "J": [(-1, 0), (0, 0), (0, -1), (0, -2)], 19 | "Z": [(-1, -1), (0, -1), (0, 0), (1, 0)], 20 | } 21 | 22 | # 定义各种形状的颜色 23 | SHAPESCOLOR = { 24 | "O": "blue", 25 | "S": "red", 26 | "T": "yellow", 27 | "I": "green", 28 | "L": "purple", 29 | "J": "orange", 30 | "Z": "Cyan", 31 | } 32 | 33 | 34 | def draw_cell_by_cr(canvas, c, r, color="#CCCCCC"): 35 | """ 36 | :param canvas: 画板,用于绘制一个方块的Canvas对象 37 | :param c: 方块所在列 38 | :param r: 方块所在行 39 | :param color: 方块颜色,默认为#CCCCCC,轻灰色 40 | :return: 41 | """ 42 | x0 = c * cell_size 43 | y0 = r * cell_size 44 | x1 = c * cell_size + cell_size 45 | y1 = r * cell_size + cell_size 46 | canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2) 47 | 48 | 49 | # 绘制空白面板 50 | def draw_blank_board(canvas): 51 | for ri in range(R): 52 | for ci in range(C): 53 | draw_cell_by_cr(canvas, ci, ri) 54 | 55 | 56 | def draw_cells(canvas, c, r, cell_list, color="#CCCCCC"): 57 | """ 58 | 绘制指定形状指定颜色的俄罗斯方块 59 | :param canvas: 画板 60 | :param r: 该形状设定的原点所在的行 61 | :param c: 该形状设定的原点所在的列 62 | :param cell_list: 该形状各个方格相对自身所处位置 63 | :param color: 该形状颜色 64 | :return: 65 | """ 66 | for cell in cell_list: 67 | cell_c, cell_r = cell 68 | ci = cell_c + c 69 | ri = cell_r + r 70 | # 判断该位置方格在画板内部(画板外部的方格不再绘制) 71 | if 0 <= ci < C and 0 <= ri < R: 72 | draw_cell_by_cr(canvas, ci, ri, color) 73 | 74 | 75 | win = tk.Tk() 76 | canvas = tk.Canvas(win, width=width, height=height, ) 77 | canvas.pack() 78 | 79 | draw_blank_board(canvas) 80 | 81 | 82 | def draw_block_move(canvas, block, direction=[0, 0]): 83 | """ 84 | 绘制向指定方向移动后的俄罗斯方块 85 | :param canvas: 画板 86 | :param block: 俄罗斯方块对象 87 | :param direction: 俄罗斯方块移动方向 88 | :return: 89 | """ 90 | shape_type = block['kind'] 91 | c, r = block['cr'] 92 | cell_list = block['cell_list'] 93 | 94 | # 移动前,先清除原有位置绘制的俄罗斯方块,也就是用背景色绘制原有的俄罗斯方块 95 | draw_cells(canvas, c, r, cell_list) 96 | 97 | dc, dr = direction 98 | new_c, new_r = c+dc, r+dr 99 | block['cr'] = [new_c, new_r] 100 | # 在新位置绘制新的俄罗斯方块就好 101 | draw_cells(canvas, new_c, new_r, cell_list, SHAPESCOLOR[shape_type]) 102 | 103 | 104 | a_block = { 105 | 'kind': 'O', # 对应俄罗斯方块的类型 106 | 'cell_list': SHAPES['O'], # 对应俄罗斯方块的各个方格 107 | 'cr': [3, 3] # 对应横纵坐标,以左上角为原点,水平向右为横坐标轴正方向,竖直向下为纵坐标轴正方向 108 | } 109 | 110 | draw_block_move(canvas, a_block) 111 | 112 | 113 | def game_loop(): 114 | win.update() 115 | 116 | down = [0, 1] 117 | draw_block_move(canvas, a_block, down) 118 | 119 | win.after(FPS, game_loop) 120 | 121 | win.update() 122 | win.after(FPS, game_loop) # 在FPS 毫秒后调用 game_loop方法 123 | 124 | 125 | win.mainloop() 126 | -------------------------------------------------------------------------------- /DOCS/1/5.md: -------------------------------------------------------------------------------- 1 | ## python tkinter实现俄罗斯方块基础版 —— 五、后续优化 2 | 3 | ### 1 - 答评论问——卡顿优化 4 | 之前做过python tkinter实现俄罗斯方块的教程: 5 | [https://www.bilibili.com/video/BV1eJ411h7ZV](https://www.bilibili.com/video/BV1eJ411h7ZV) 6 | 对应博客:[https://blog.csdn.net/python1639er/article/details/104069590](https://blog.csdn.net/python1639er/article/details/104069590) 7 | 8 | 后来有一些观众朋友反馈:玩到后面特别的卡顿。 9 | 10 | 由于我自己玩的时候感觉不卡(三五分钟都没啥卡顿感觉) 11 | 所以当时没有细想,以为是他们电脑配置或者改了代码的原因。 12 | 13 | 直到前不久,我直播写代码的时候,有个老哥又提到了这个卡顿的问题。 14 | 当时刚好有空,仔细看了下代码,发现确实有问题会导致卡顿。 15 | 16 | #### 那是什么问题导致了卡顿呢 17 | **是清理方块的方式有问题** 18 | 19 | 游戏里面清理方块的方式: 20 | 是通过绘制一个新的背景色的方格覆盖来实现的。 21 | 22 | 具体的来讲 23 | - 当俄罗斯方块下落(左右移动同理)时 24 | 其过程如示意图 25 | ![](/imgs/1_5_1.png) 26 | 27 | 每一次移动都会新增四个方块进行绘制。 28 | 29 | - 当下落的俄罗斯方块填满一行,要清除的时候 30 | 31 | 代码里面是重新绘制整个面板所有方块。 32 | 33 | 每一次清理都会新增R*C个方块进行绘制 34 | 代码里面行数R为12,列数C为20 35 | 所以总数为240。 36 | 即每一次清理都会新增240个。 37 | 38 | 问题来了,所有这些新增的方块从未被清理过。 39 | 那么我们不难发现,随着时间的推移,方块越来越多,游戏因此越来越卡顿。 40 | 41 | #### 那么这个问题怎么解决呢? 42 | **修改清理方式,之前是通过用背景色覆盖来假清理。** 43 | 44 | 下来我们需要想办法,对要去除的方块,实现真正的清理。 45 | 46 | 补充说明: 47 | 用背景色覆盖来进行清理的方法,在pygame里面经常会用到。 48 | 其在pygame里面应该是不会卡顿的,因为pygame的具体机制有些不同。 49 | 50 | tkinter `Canvas` 类 提供了类方法`delete`来实现这样的清理。 51 | 52 | 该方法的详细解释可以看我最近的博客文章: 53 | [tkinter Canvas delete 方法详解](https://blog.csdn.net/python1639er/article/details/115386039) 54 | 55 | 这里我们用该方法通过tag去删除已绘制的对象 56 | 代码示例如下 57 | ```python 58 | canvas.create_rectangle(50, 50, 100, 150, fill="red", tag="one") # 绘制 59 | canvas.delete("one") # 清除tag为"one"的绘制对象,即上面的绘制 60 | ``` 61 | 62 | #### 代码思路 63 | 64 | 既然要通过tag来删除绘制对象,那么我们就需要思考应该有几种tag。 65 | 或者说,有几种要清理的场景 66 | 67 | 通过上文的分析,不难想到有两种。 68 | 1. 正在下落的俄罗斯方块,移动时要清理移动之前的俄罗斯方块。 69 | 那么对正在下落的俄罗斯方块,不妨记其tag为`"falling"` 70 | 移动正在下落的俄罗斯方块后,先清理掉tag为`"falling"`的,再绘制一遍俄罗斯方块。 71 | 2. 已经落地的俄罗斯方块,凑齐一行后需要按行来清除。 72 | 那么对于这些方块,记其tag为`"row-%s" % r` 73 | 74 | 所以tag有两类 75 | - `"falling"` 76 | - `"row-%s" % r`,记其 tag_kind 为`"row"` 77 | 78 | 而背景色方块只需要游戏开始时绘制一遍,后面不用再绘制,所以不需要清理,不设置tag。 79 | 80 | #### 修改draw_cell_by_cr 81 | 原来的`draw_cell_by_cr`为 82 | ```python 83 | def draw_cell_by_cr(canvas, c, r, color="#CCCCCC"): 84 | x0 = c * cell_size 85 | y0 = r * cell_size 86 | x1 = c * cell_size + cell_size 87 | y1 = r * cell_size + cell_size 88 | canvas.create_rectangle(x0, y0, x1, y1,fill=color, outline="white", width=2) 89 | ``` 90 | 修改后为 91 | ```python 92 | def draw_cell_by_cr(canvas, c, r, color="#CCCCCC", tag_kind=""): 93 | x0 = c * cell_size 94 | y0 = r * cell_size 95 | x1 = c * cell_size + cell_size 96 | y1 = r * cell_size + cell_size 97 | if tag_kind == "falling": 98 | canvas.create_rectangle(x0, y0, x1, y1, fill=color,outline="white", width=2, tag=tag_kind) 99 | elif tag_kind == "row": 100 | canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2, tag="row-%s" % r) 101 | else: 102 | canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2) 103 | ``` 104 | #### 修改对draw_cell_by_cr的调用 105 | - `draw_board` 106 | 原来为 107 | ```python 108 | def draw_board(canvas, block_list): 109 | for ri in range(R): 110 | for ci in range(C): 111 | cell_type = block_list[ri][ci] 112 | if cell_type: 113 | draw_cell_by_cr(canvas, ci, ri, SHAPESCOLOR[cell_type]) 114 | else: 115 | draw_cell_by_cr(canvas, ci, ri) 116 | ``` 117 | 修改后为 118 | ```python 119 | # 绘制面板, 只有在第一次绘制时才绘制背景色方块 120 | def draw_board(canvas, block_list, isFirst=False): 121 | # 删掉原来所有的行 122 | for ri in range(R): 123 | canvas.delete("row-%s" % ri) 124 | 125 | for ri in range(R): 126 | for ci in range(C): 127 | cell_type = block_list[ri][ci] 128 | if cell_type: 129 | draw_cell_by_cr(canvas, ci, ri, SHAPESCOLOR[cell_type], tag_kind="row") 130 | elif isFirst: 131 | draw_cell_by_cr(canvas, ci, ri) 132 | ``` 133 | 同时修改原来,初始调用draw_board的代码(大概在91行左右的地方) 134 | 修改原来的```draw_board(canvas, block_list)``` 135 | 为```draw_board(canvas, block_list, True)``` 136 | - `draw_cells` 137 | 原来为 138 | ```python 139 | def draw_cells(canvas, c, r, cell_list, color="#CCCCCC"): 140 | for cell in cell_list: 141 | cell_c, cell_r = cell 142 | ci = cell_c + c 143 | ri = cell_r + r 144 | # 判断该位置方格在画板内部(画板外部的方格不再绘制) 145 | if 0 <= c < C and 0 <= r < R: 146 | draw_cell_by_cr(canvas, ci, ri, color) 147 | ``` 148 | 修改后为 149 | ```python 150 | def draw_cells(canvas, c, r, cell_list, color="#CCCCCC"): 151 | for cell in cell_list: 152 | cell_c, cell_r = cell 153 | ci = cell_c + c 154 | ri = cell_r + r 155 | # 判断该位置方格在画板内部(画板外部的方格不再绘制) 156 | if 0 <= c < C and 0 <= r < R: 157 | draw_cell_by_cr(canvas, ci, ri, color, tag_kind="falling") 158 | ``` 159 | - `draw_block_move` 160 | 原来为 161 | ```python 162 | def draw_block_move(canvas, block, direction=[0, 0]): 163 | shape_type = block['kind'] 164 | c, r = block['cr'] 165 | cell_list = block['cell_list'] 166 | 167 | # 移动前,先清除原有位置绘制的俄罗斯方块,也就是用背景色绘制原有的俄罗斯方块 168 | draw_cells(canvas, c, r, cell_list) 169 | 170 | dc, dr = direction 171 | new_c, new_r = c+dc, r+dr 172 | block['cr'] = [new_c, new_r] 173 | # 在新位置绘制新的俄罗斯方块就好 174 | draw_cells(canvas, new_c, new_r, cell_list, SHAPESCOLOR[shape_type]) 175 | ``` 176 | 修改后为 177 | ```python 178 | def draw_block_move(canvas, block, direction=[0, 0]): 179 | shape_type = block['kind'] 180 | c, r = block['cr'] 181 | cell_list = block['cell_list'] 182 | 183 | # 移动前,清除原有位置绘制的俄罗斯方块 184 | canvas.delete("falling") 185 | 186 | dc, dr = direction 187 | new_c, new_r = c+dc, r+dr 188 | block['cr'] = [new_c, new_r] 189 | # 在新位置绘制新的俄罗斯方块就好 190 | draw_cells(canvas, new_c, new_r, cell_list, SHAPESCOLOR[shape_type]) 191 | ``` 192 | - `save_block_to_list` 193 | 原来为 194 | ```python 195 | def save_block_to_list(block): 196 | shape_type = block['kind'] 197 | cc, cr = block['cr'] 198 | cell_list = block['cell_list'] 199 | 200 | for cell in cell_list: 201 | cell_c, cell_r = cell 202 | c = cell_c + cc 203 | r = cell_r + cr 204 | # block_list 在对应位置记下其类型draw_cells 205 | block_list[r][c] = shape_type 206 | ``` 207 | 修改后为 208 | ```python 209 | def save_block_to_list(block): 210 | # 清除原有的打上了 falling 标签的方块 211 | canvas.delete("falling") 212 | 213 | shape_type = block['kind'] 214 | cc, cr = block['cr'] 215 | cell_list = block['cell_list'] 216 | 217 | for cell in cell_list: 218 | cell_c, cell_r = cell 219 | c = cell_c + cc 220 | r = cell_r + cr 221 | # block_list 在对应位置记下其类型 222 | block_list[r][c] = shape_type 223 | 224 | draw_cell_by_cr(canvas, c, r, SHAPESCOLOR[shape_type], tag_kind="row") 225 | ``` 226 | 到这里,就改好了。 227 | 此时运行软件,应该就没有越玩越卡顿的问题了。 228 | 229 | 如果还卡,那可能又有其他bug(雾 230 | -------------------------------------------------------------------------------- /3_AI/util.py: -------------------------------------------------------------------------------- 1 | #usr/bin/env python 2 | #-*- coding:utf-8- -*- 3 | 4 | # 0 不加速 5 | # 1 直接落地 6 | JIASU = 0 7 | 8 | def move_block(block, direction): 9 | c, r = block['cr'] 10 | dc, dr = direction 11 | block['cr'] = c+dc, r+dr 12 | 13 | 14 | def get_cell_list_by_angle(cell_list, angle): 15 | angle_dict = { 16 | 0: (1, 0, 0, 1), 17 | 1: (0, 1, -1, 0), 18 | 2: (-1, 0, 0, -1), 19 | 3: (0, -1, 1, 0), 20 | } 21 | a, b, c, d = angle_dict[angle] 22 | if angle == 0: 23 | return cell_list 24 | 25 | rotate_cell_list = [] 26 | for cell in cell_list: 27 | cc, cr = cell 28 | rc, rr = a * cc + b * cr, c*cc+d*cr 29 | rotate_cell_list.append((rc, rr)) 30 | 31 | return rotate_cell_list 32 | 33 | 34 | def save_block_to_list(block, board, isFuture = False): 35 | shape_type = block['kind'] 36 | cc, cr = block['cr'] 37 | cell_list = block['cell_list'] 38 | 39 | if isFuture: 40 | cc, cr = block['best']['cr'] 41 | angle = block['best']['angle'] 42 | cell_list = get_cell_list_by_angle(cell_list,angle) 43 | 44 | for cell in cell_list: 45 | cell_c, cell_r = cell 46 | c = cell_c + cc 47 | r = cell_r + cr 48 | # block_list 在对应位置记下其类型 49 | board[r][c] = shape_type 50 | 51 | 52 | def check_row_complete(row): 53 | for cell in row: 54 | if cell=='': 55 | return False 56 | 57 | return True 58 | 59 | 60 | def check_move(board, cr, cell_list, direction): 61 | board_r = len(board) 62 | board_c = len(board[0]) 63 | cc, cr = cr 64 | cell_list = cell_list 65 | 66 | for cell in cell_list: 67 | cell_c, cell_r = cell 68 | c = cell_c + cc + direction[0] 69 | r = cell_r + cr + direction[1] 70 | # 判断该位置是否超出左右边界,以及下边界 71 | # 一般不判断上边界,因为俄罗斯方块生成的时候,可能有一部分在上边界之上还没有出来 72 | if c < 0 or c >= board_c or r >= board_r: 73 | return False 74 | 75 | # 必须要判断r不小于0才行,具体原因你可以不加这个判断,试试会出现什么效果 76 | if r >= 0 and board[r][c]: 77 | return False 78 | 79 | return True 80 | 81 | 82 | def check_above_empty(board, cell_list, ci, ri): 83 | for cell in cell_list: 84 | cc, cr = cell 85 | c, r = ci + cc, ri + cr 86 | for ir in range(r): 87 | if board[ir][c]: 88 | return False 89 | 90 | return True 91 | 92 | 93 | def get_bottom_r(cell_list, board, ci): 94 | board_r = len(board) 95 | for ri in range(board_r-1, -1, -1): 96 | if check_move(board, (ci, ri), cell_list, (0, 0)): 97 | for dc in [0, 1, -1]: 98 | nci = ci + dc 99 | if check_move(board, (nci, ri), cell_list, (0, 0)) and \ 100 | check_above_empty(board, cell_list, nci, ri): 101 | return ri, dc 102 | 103 | raise Exception("no space to place") 104 | 105 | 106 | def cal_ai_score(blist, board_c, board_r): 107 | aggregate_height = 0 108 | holes = 0 109 | row_hight_list = [] 110 | for ci in range(board_c): 111 | find_first_cell = False 112 | for ri in range(board_r): 113 | if not find_first_cell and blist[ri][ci]: 114 | h = board_r - ri 115 | aggregate_height += h*h 116 | row_hight_list.append(h) 117 | find_first_cell = True 118 | elif find_first_cell and not blist[ri][ci]: 119 | holes += 1 120 | 121 | if not find_first_cell: 122 | row_hight_list.append(0) 123 | 124 | complete_lines = 0 125 | for row in blist: 126 | if check_row_complete(row): 127 | complete_lines += 1 128 | 129 | bumpiness = 0 130 | for ci in range(board_c - 1): 131 | bumpiness += abs(row_hight_list[ci] - row_hight_list[ci+1]) 132 | a = -2.10066 133 | b = 2.760666 134 | c = -0.35663 135 | d = -0.184483 136 | 137 | p = a * aggregate_height + b * complete_lines + c*holes + d*bumpiness 138 | return p 139 | 140 | 141 | def cal_move_order(block): 142 | # 计算出移动到最佳位置的路径 143 | cc, cr = block['cr'] 144 | best = block['best'] 145 | bc, br = best['cr'] 146 | dc = best['dc'] 147 | bdc = bc + dc 148 | angle_change_count = best['angle'] # 需要旋转的次数 149 | horizontal_move_count = abs(bdc - cc) # 需要水平移动的次数 150 | 151 | speed = (angle_change_count + horizontal_move_count + 1) // br + 1 # 每下落一次移动次数 152 | 153 | steps = ['' for _ in range(br+1)] 154 | 155 | for si in range(br): 156 | if angle_change_count >= speed: 157 | steps[si] = 'W'*speed 158 | angle_change_count -= speed 159 | else: 160 | step = 'W'*angle_change_count 161 | for i in range(speed - angle_change_count): 162 | if bdc < cc: 163 | step += 'A' 164 | cc -= 1 165 | elif bdc > cc: 166 | step += 'D' 167 | cc += 1 168 | else: 169 | break 170 | 171 | angle_change_count = 0 172 | steps[si] = step 173 | 174 | if dc == 0: 175 | steps[br] = '' 176 | elif dc == 1: 177 | steps[br] = 'A' 178 | elif dc == -1: 179 | steps[br] = 'D' 180 | 181 | block['move_steps'] = steps 182 | 183 | 184 | def move_block_by_step(block): 185 | step_count = len(block['move_steps']) 186 | si = block['cur_step'] 187 | if si >= step_count: 188 | return False 189 | 190 | step_str = block['move_steps'][si] 191 | c, r = block['cr'] 192 | cell_list = block['cell_list'] 193 | 194 | if step_str == '' and JIASU == 1: 195 | if all(block['move_steps'][next_si] == '' for next_si in range(si,step_count)): 196 | si = step_count 197 | r = step_count - 1 198 | block['cur_step'] = si 199 | block['cr'] = (c, r) 200 | return True 201 | 202 | for step in step_str: 203 | if step == 'A': 204 | c -= 1 205 | elif step == 'D': 206 | c += 1 207 | elif step == 'W': 208 | cell_list = get_cell_list_by_angle(cell_list, 1) 209 | 210 | if si == step_count - 1: 211 | pass 212 | else: 213 | r += 1 214 | # return False 215 | 216 | si += 1 217 | block['cur_step'] = si 218 | block['cr'] = (c, r) 219 | block['cell_list'] = cell_list 220 | return True 221 | 222 | 223 | def check_and_clear(board): 224 | score = 0 225 | board_r = len(board) 226 | board_c = len(board[0]) 227 | for ri in range(board_r): 228 | if check_row_complete(board[ri]): 229 | # 当前行可消除 230 | if ri > 0: 231 | for cur_ri in range(ri, 0, -1): 232 | board[cur_ri] = board[cur_ri-1][:] 233 | board[0] = ['' for j in range(board_c)] 234 | else: 235 | board[ri] = ['' for j in range(board_c)] 236 | 237 | score += 10 238 | 239 | return score 240 | 241 | 242 | def get_range(block_c, board_c, length): 243 | if block_c < length: 244 | return 0, min(board_c, 2*length) 245 | elif block_c > board_c - length: 246 | return max(0, board_c - 2*length), board_c 247 | else: 248 | return block_c-length, block_c +length -------------------------------------------------------------------------------- /1_BASIC/003.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | import random 3 | 4 | cell_size = 30 5 | C = 12 6 | R = 20 7 | height = R * cell_size 8 | width = C * cell_size 9 | 10 | FPS = 200 # 刷新页面的毫秒间隔 11 | 12 | # 定义各种形状 13 | SHAPES = { 14 | "O": [(-1, -1), (0, -1), (-1, 0), (0, 0)], 15 | "S": [(-1, 0), (0, 0), (0, -1), (1, -1)], 16 | "T": [(-1, 0), (0, 0), (0, -1), (1, 0)], 17 | "I": [(0, 1), (0, 0), (0, -1), (0, -2)], 18 | "L": [(-1, 0), (0, 0), (-1, -1), (-1, -2)], 19 | "J": [(-1, 0), (0, 0), (0, -1), (0, -2)], 20 | "Z": [(-1, -1), (0, -1), (0, 0), (1, 0)], 21 | } 22 | 23 | # 定义各种形状的颜色 24 | SHAPESCOLOR = { 25 | "O": "blue", 26 | "S": "red", 27 | "T": "yellow", 28 | "I": "green", 29 | "L": "purple", 30 | "J": "orange", 31 | "Z": "Cyan", 32 | } 33 | 34 | 35 | def draw_cell_by_cr(canvas, c, r, color="#CCCCCC"): 36 | """ 37 | :param canvas: 画板,用于绘制一个方块的Canvas对象 38 | :param c: 方块所在列 39 | :param r: 方块所在行 40 | :param color: 方块颜色,默认为#CCCCCC,轻灰色 41 | :return: 42 | """ 43 | x0 = c * cell_size 44 | y0 = r * cell_size 45 | x1 = c * cell_size + cell_size 46 | y1 = r * cell_size + cell_size 47 | canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2) 48 | 49 | 50 | # 绘制空白面板 51 | def draw_blank_board(canvas): 52 | for ri in range(R): 53 | for ci in range(C): 54 | draw_cell_by_cr(canvas, ci, ri) 55 | 56 | 57 | def draw_cells(canvas, c, r, cell_list, color="#CCCCCC"): 58 | """ 59 | 绘制指定形状指定颜色的俄罗斯方块 60 | :param canvas: 画板 61 | :param r: 该形状设定的原点所在的行 62 | :param c: 该形状设定的原点所在的列 63 | :param cell_list: 该形状各个方格相对自身所处位置 64 | :param color: 该形状颜色 65 | :return: 66 | """ 67 | for cell in cell_list: 68 | cell_c, cell_r = cell 69 | ci = cell_c + c 70 | ri = cell_r + r 71 | # 判断该位置方格在画板内部(画板外部的方格不再绘制) 72 | if 0 <= ci < C and 0 <= ri < R: 73 | draw_cell_by_cr(canvas, ci, ri, color) 74 | 75 | 76 | win = tk.Tk() 77 | canvas = tk.Canvas(win, width=width, height=height, ) 78 | canvas.pack() 79 | 80 | draw_blank_board(canvas) 81 | 82 | block_list = [] 83 | for i in range(R): 84 | i_row = ['' for j in range(C)] 85 | block_list.append(i_row) 86 | 87 | 88 | def draw_block_move(canvas, block, direction=[0, 0]): 89 | """ 90 | 绘制向指定方向移动后的俄罗斯方块 91 | :param canvas: 画板 92 | :param block: 俄罗斯方块对象 93 | :param direction: 俄罗斯方块移动方向 94 | :return: 95 | """ 96 | shape_type = block['kind'] 97 | c, r = block['cr'] 98 | cell_list = block['cell_list'] 99 | 100 | # 移动前,先清除原有位置绘制的俄罗斯方块,也就是用背景色绘制原有的俄罗斯方块 101 | draw_cells(canvas, c, r, cell_list) 102 | 103 | dc, dr = direction 104 | new_c, new_r = c+dc, r+dr 105 | block['cr'] = [new_c, new_r] 106 | # 在新位置绘制新的俄罗斯方块就好 107 | draw_cells(canvas, new_c, new_r, cell_list, SHAPESCOLOR[shape_type]) 108 | 109 | 110 | def generate_new_block(): 111 | # 随机生成新的俄罗斯方块 112 | 113 | kind = random.choice(list(SHAPES.keys())) 114 | # 对应横纵坐标,以左上角为原点,水平向右为x轴正方向, 115 | # 竖直向下为y轴正方向,x对应横坐标,y对应纵坐标 116 | cr = [C // 2, 0] 117 | new_block = { 118 | 'kind': kind, # 对应俄罗斯方块的类型 119 | 'cell_list': SHAPES[kind], 120 | 'cr': cr 121 | } 122 | 123 | return new_block 124 | 125 | 126 | def check_move(block, direction=[0, 0]): 127 | """ 128 | 判断俄罗斯方块是否可以朝制定方向移动 129 | :param block: 俄罗斯方块对象 130 | :param direction: 俄罗斯方块移动方向 131 | :return: boolean 是否可以朝制定方向移动 132 | """ 133 | cc, cr = block['cr'] 134 | cell_list = block['cell_list'] 135 | 136 | for cell in cell_list: 137 | cell_c, cell_r = cell 138 | c = cell_c + cc + direction[0] 139 | r = cell_r + cr + direction[1] 140 | # 判断该位置是否超出左右边界,以及下边界 141 | # 一般不判断上边界,因为俄罗斯方块生成的时候,可能有一部分在上边界之上还没有出来 142 | if c < 0 or c >= C or r >= R: 143 | return False 144 | 145 | # 必须要判断r不小于0才行,具体原因你可以不加这个判断,试试会出现什么效果 146 | if r >= 0 and block_list[r][c]: 147 | return False 148 | 149 | return True 150 | 151 | 152 | def save_block_to_list(block): 153 | shape_type = block['kind'] 154 | cc, cr = block['cr'] 155 | cell_list = block['cell_list'] 156 | 157 | for cell in cell_list: 158 | cell_c, cell_r = cell 159 | c = cell_c + cc 160 | r = cell_r + cr 161 | # block_list 在对应位置记下其类型 162 | block_list[r][c] = shape_type 163 | 164 | 165 | def horizontal_move_block(event): 166 | """ 167 | 左右水平移动俄罗斯方块 168 | """ 169 | direction = [0, 0] 170 | if event.keysym == 'Left': 171 | direction = [-1, 0] 172 | elif event.keysym == 'Right': 173 | direction = [1, 0] 174 | else: 175 | return 176 | 177 | global current_block 178 | if current_block is not None and check_move(current_block, direction): 179 | draw_block_move(canvas, current_block, direction) 180 | 181 | 182 | def rotate_block(event): 183 | global current_block 184 | if current_block is None: 185 | return 186 | 187 | cell_list = current_block['cell_list'] 188 | rotate_list = [] 189 | for cell in cell_list: 190 | cell_c, cell_r = cell 191 | rotate_cell = [cell_r, -cell_c] 192 | rotate_list.append(rotate_cell) 193 | 194 | block_after_rotate = { 195 | 'kind': current_block['kind'], # 对应俄罗斯方块的类型 196 | 'cell_list': rotate_list, 197 | 'cr': current_block['cr'] 198 | } 199 | 200 | if check_move(block_after_rotate): 201 | cc, cr= current_block['cr'] 202 | draw_cells(canvas, cc, cr, current_block['cell_list']) 203 | draw_cells(canvas, cc, cr, rotate_list,SHAPESCOLOR[current_block['kind']]) 204 | current_block = block_after_rotate 205 | 206 | 207 | def land(event): 208 | global current_block 209 | if current_block is None: 210 | return 211 | 212 | cell_list = current_block['cell_list'] 213 | cc, cr = current_block['cr'] 214 | min_height = R 215 | for cell in cell_list: 216 | cell_c, cell_r = cell 217 | c, r = cell_c + cc, cell_r + cr 218 | if block_list[r][c]: 219 | return 220 | h = 0 221 | for ri in range(r+1, R): 222 | if block_list[ri][c]: 223 | break 224 | else: 225 | h += 1 226 | if h < min_height: 227 | min_height = h 228 | 229 | down = [0, min_height] 230 | if check_move(current_block, down): 231 | draw_block_move(canvas, current_block, down) 232 | 233 | 234 | def game_loop(): 235 | win.update() 236 | global current_block 237 | if current_block is None: 238 | new_block = generate_new_block() 239 | # 新生成的俄罗斯方块需要先在生成位置绘制出来 240 | draw_block_move(canvas, new_block) 241 | current_block = new_block 242 | else: 243 | if check_move(current_block, [0, 1]): 244 | draw_block_move(canvas, current_block, [0, 1]) 245 | else: 246 | # 无法移动,记入 block_list 中 247 | save_block_to_list(current_block) 248 | current_block = None 249 | 250 | win.after(FPS, game_loop) 251 | 252 | canvas.focus_set() # 聚焦到canvas画板对象上 253 | canvas.bind("", horizontal_move_block) 254 | canvas.bind("", horizontal_move_block) 255 | canvas.bind("", rotate_block) 256 | canvas.bind("", land) 257 | 258 | 259 | current_block = None 260 | 261 | 262 | win.update() 263 | win.after(FPS, game_loop) # 在FPS 毫秒后调用 game_loop方法 264 | 265 | 266 | win.mainloop() 267 | -------------------------------------------------------------------------------- /1_BASIC/004.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import messagebox 3 | import random 4 | 5 | cell_size = 30 6 | C = 12 7 | R = 20 8 | height = R * cell_size 9 | width = C * cell_size 10 | 11 | FPS = 200 # 刷新页面的毫秒间隔 12 | 13 | # 定义各种形状 14 | SHAPES = { 15 | "O": [(-1, -1), (0, -1), (-1, 0), (0, 0)], 16 | "S": [(-1, 0), (0, 0), (0, -1), (1, -1)], 17 | "T": [(-1, 0), (0, 0), (0, -1), (1, 0)], 18 | "I": [(0, 1), (0, 0), (0, -1), (0, -2)], 19 | "L": [(-1, 0), (0, 0), (-1, -1), (-1, -2)], 20 | "J": [(-1, 0), (0, 0), (0, -1), (0, -2)], 21 | "Z": [(-1, -1), (0, -1), (0, 0), (1, 0)], 22 | } 23 | 24 | # 定义各种形状的颜色 25 | SHAPESCOLOR = { 26 | "O": "blue", 27 | "S": "red", 28 | "T": "yellow", 29 | "I": "green", 30 | "L": "purple", 31 | "J": "orange", 32 | "Z": "Cyan", 33 | } 34 | 35 | 36 | def draw_cell_by_cr(canvas, c, r, color="#CCCCCC"): 37 | """ 38 | :param canvas: 画板,用于绘制一个方块的Canvas对象 39 | :param c: 方块所在列 40 | :param r: 方块所在行 41 | :param color: 方块颜色,默认为#CCCCCC,轻灰色 42 | :return: 43 | """ 44 | x0 = c * cell_size 45 | y0 = r * cell_size 46 | x1 = c * cell_size + cell_size 47 | y1 = r * cell_size + cell_size 48 | canvas.create_rectangle(x0, y0, x1, y1, 49 | fill=color, outline="white", width=2) 50 | 51 | 52 | # 绘制面板 53 | def draw_board(canvas, block_list): 54 | for ri in range(R): 55 | for ci in range(C): 56 | cell_type = block_list[ri][ci] 57 | if cell_type: 58 | draw_cell_by_cr(canvas, ci, ri, SHAPESCOLOR[cell_type]) 59 | else: 60 | draw_cell_by_cr(canvas, ci, ri) 61 | 62 | 63 | def draw_cells(canvas, c, r, cell_list, color="#CCCCCC"): 64 | """ 65 | 绘制指定形状指定颜色的俄罗斯方块 66 | :param canvas: 画板 67 | :param r: 该形状设定的原点所在的行 68 | :param c: 该形状设定的原点所在的列 69 | :param cell_list: 该形状各个方格相对自身所处位置 70 | :param color: 该形状颜色 71 | :return: 72 | """ 73 | for cell in cell_list: 74 | cell_c, cell_r = cell 75 | ci = cell_c + c 76 | ri = cell_r + r 77 | # 判断该位置方格在画板内部(画板外部的方格不再绘制) 78 | if 0 <= ci < C and 0 <= ri < R: 79 | draw_cell_by_cr(canvas, ci, ri, color) 80 | 81 | 82 | win = tk.Tk() 83 | canvas = tk.Canvas(win, width=width, height=height, ) 84 | canvas.pack() 85 | 86 | block_list = [] 87 | for i in range(R): 88 | i_row = ['' for j in range(C)] 89 | block_list.append(i_row) 90 | 91 | draw_board(canvas, block_list) 92 | 93 | 94 | def draw_block_move(canvas, block, direction=[0, 0]): 95 | """ 96 | 绘制向指定方向移动后的俄罗斯方块 97 | :param canvas: 画板 98 | :param block: 俄罗斯方块对象 99 | :param direction: 俄罗斯方块移动方向 100 | :return: 101 | """ 102 | shape_type = block['kind'] 103 | c, r = block['cr'] 104 | cell_list = block['cell_list'] 105 | 106 | # 移动前,先清除原有位置绘制的俄罗斯方块,也就是用背景色绘制原有的俄罗斯方块 107 | draw_cells(canvas, c, r, cell_list) 108 | 109 | dc, dr = direction 110 | new_c, new_r = c+dc, r+dr 111 | block['cr'] = [new_c, new_r] 112 | # 在新位置绘制新的俄罗斯方块就好 113 | draw_cells(canvas, new_c, new_r, cell_list, SHAPESCOLOR[shape_type]) 114 | 115 | 116 | def generate_new_block(): 117 | # 随机生成新的俄罗斯方块 118 | 119 | kind = random.choice(list(SHAPES.keys())) 120 | # 对应横纵坐标,以左上角为原点,水平向右为x轴正方向, 121 | # 竖直向下为y轴正方向,x对应横坐标,y对应纵坐标 122 | cr = [C // 2, 0] 123 | new_block = { 124 | 'kind': kind, # 对应俄罗斯方块的类型 125 | 'cell_list': SHAPES[kind], 126 | 'cr': cr 127 | } 128 | 129 | return new_block 130 | 131 | 132 | def check_move(block, direction=[0, 0]): 133 | """ 134 | 判断俄罗斯方块是否可以朝制定方向移动 135 | :param block: 俄罗斯方块对象 136 | :param direction: 俄罗斯方块移动方向 137 | :return: boolean 是否可以朝制定方向移动 138 | """ 139 | cc, cr = block['cr'] 140 | cell_list = block['cell_list'] 141 | 142 | for cell in cell_list: 143 | cell_c, cell_r = cell 144 | c = cell_c + cc + direction[0] 145 | r = cell_r + cr + direction[1] 146 | # 判断该位置是否超出左右边界,以及下边界 147 | # 一般不判断上边界,因为俄罗斯方块生成的时候,可能有一部分在上边界之上还没有出来 148 | if c < 0 or c >= C or r >= R: 149 | return False 150 | 151 | # 必须要判断r不小于0才行,具体原因你可以不加这个判断,试试会出现什么效果 152 | if r >= 0 and block_list[r][c]: 153 | return False 154 | 155 | return True 156 | 157 | 158 | def check_row_complete(row): 159 | for cell in row: 160 | if cell=='': 161 | return False 162 | 163 | return True 164 | 165 | 166 | score = 0 167 | win.title("SCORES: %s" % score) # 标题中展示分数 168 | 169 | 170 | def check_and_clear(): 171 | has_complete_row = False 172 | for ri in range(len(block_list)): 173 | if check_row_complete(block_list[ri]): 174 | has_complete_row = True 175 | # 当前行可消除 176 | if ri > 0: 177 | for cur_ri in range(ri, 0, -1): 178 | block_list[cur_ri] = block_list[cur_ri-1][:] 179 | block_list[0] = ['' for j in range(C)] 180 | else: 181 | block_list[ri] = ['' for j in range(C)] 182 | global score 183 | score += 10 184 | 185 | if has_complete_row: 186 | draw_board(canvas, block_list) 187 | 188 | win.title("SCORES: %s" % score) 189 | 190 | 191 | def save_block_to_list(block): 192 | shape_type = block['kind'] 193 | cc, cr = block['cr'] 194 | cell_list = block['cell_list'] 195 | 196 | for cell in cell_list: 197 | cell_c, cell_r = cell 198 | c = cell_c + cc 199 | r = cell_r + cr 200 | # block_list 在对应位置记下其类型draw_cells 201 | block_list[r][c] = shape_type 202 | 203 | 204 | def horizontal_move_block(event): 205 | """ 206 | 左右水平移动俄罗斯方块 207 | """ 208 | direction = [0, 0] 209 | if event.keysym == 'Left': 210 | direction = [-1, 0] 211 | elif event.keysym == 'Right': 212 | direction = [1, 0] 213 | else: 214 | return 215 | 216 | global current_block 217 | if current_block is not None and check_move(current_block, direction): 218 | draw_block_move(canvas, current_block, direction) 219 | 220 | 221 | def rotate_block(event): 222 | global current_block 223 | if current_block is None: 224 | return 225 | 226 | cell_list = current_block['cell_list'] 227 | rotate_list = [] 228 | for cell in cell_list: 229 | cell_c, cell_r = cell 230 | rotate_cell = [cell_r, -cell_c] 231 | rotate_list.append(rotate_cell) 232 | 233 | block_after_rotate = { 234 | 'kind': current_block['kind'], # 对应俄罗斯方块的类型 235 | 'cell_list': rotate_list, 236 | 'cr': current_block['cr'] 237 | } 238 | 239 | if check_move(block_after_rotate): 240 | cc, cr= current_block['cr'] 241 | draw_cells(canvas, cc, cr, current_block['cell_list']) 242 | draw_cells(canvas, cc, cr, rotate_list,SHAPESCOLOR[current_block['kind']]) 243 | current_block = block_after_rotate 244 | 245 | 246 | def land(event): 247 | global current_block 248 | if current_block is None: 249 | return 250 | 251 | cell_list = current_block['cell_list'] 252 | cc, cr = current_block['cr'] 253 | min_height = R 254 | for cell in cell_list: 255 | cell_c, cell_r = cell 256 | c, r = cell_c + cc, cell_r + cr 257 | if r>=0 and block_list[r][c]: 258 | return 259 | h = 0 260 | for ri in range(r+1, R): 261 | if block_list[ri][c]: 262 | break 263 | else: 264 | h += 1 265 | if h < min_height: 266 | min_height = h 267 | 268 | down = [0, min_height] 269 | if check_move(current_block, down): 270 | draw_block_move(canvas, current_block, down) 271 | 272 | 273 | def game_loop(): 274 | win.update() 275 | global current_block 276 | if current_block is None: 277 | new_block = generate_new_block() 278 | # 新生成的俄罗斯方块需要先在生成位置绘制出来 279 | draw_block_move(canvas, new_block) 280 | current_block = new_block 281 | if not check_move(current_block, [0, 0]): 282 | messagebox.showinfo("Game Over!", "Your Score is %s" % score) 283 | win.destroy() 284 | return 285 | else: 286 | if check_move(current_block, [0, 1]): 287 | draw_block_move(canvas, current_block, [0, 1]) 288 | else: 289 | # 无法移动,记入 block_list 中 290 | save_block_to_list(current_block) 291 | current_block = None 292 | check_and_clear() 293 | 294 | win.after(FPS, game_loop) 295 | 296 | canvas.focus_set() # 聚焦到canvas画板对象上 297 | canvas.bind("", horizontal_move_block) 298 | canvas.bind("", horizontal_move_block) 299 | canvas.bind("", rotate_block) 300 | canvas.bind("", land) 301 | 302 | 303 | current_block = None 304 | 305 | 306 | win.update() 307 | win.after(FPS, game_loop) # 在FPS 毫秒后调用 game_loop方法 308 | 309 | 310 | win.mainloop() 311 | -------------------------------------------------------------------------------- /1_BASIC/005.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import messagebox 3 | import random 4 | 5 | cell_size = 30 6 | C = 12 7 | R = 20 8 | height = R * cell_size 9 | width = C * cell_size 10 | 11 | FPS = 200 # 刷新页面的毫秒间隔 12 | 13 | 14 | # 定义各种形状 15 | SHAPES = { 16 | "O": [(-1, -1), (0, -1), (-1, 0), (0, 0)], 17 | "S": [(-1, 0), (0, 0), (0, -1), (1, -1)], 18 | "T": [(-1, 0), (0, 0), (0, -1), (1, 0)], 19 | "I": [(0, 1), (0, 0), (0, -1), (0, -2)], 20 | "L": [(-1, 0), (0, 0), (-1, -1), (-1, -2)], 21 | "J": [(-1, 0), (0, 0), (0, -1), (0, -2)], 22 | "Z": [(-1, -1), (0, -1), (0, 0), (1, 0)], 23 | } 24 | 25 | 26 | # 定义各种形状的颜色 27 | SHAPESCOLOR = { 28 | "O": "blue", 29 | "S": "red", 30 | "T": "yellow", 31 | "I": "green", 32 | "L": "purple", 33 | "J": "orange", 34 | "Z": "Cyan", 35 | } 36 | 37 | 38 | def draw_cell_by_cr(canvas, c, r, color="#CCCCCC", tag_kind=""): 39 | """ 40 | :param canvas: 画板,用于绘制一个方块的Canvas对象 41 | :param c: 方块所在列 42 | :param r: 方块所在行 43 | :param color: 方块颜色,默认为#CCCCCC,轻灰色 44 | :return: 45 | """ 46 | x0 = c * cell_size 47 | y0 = r * cell_size 48 | x1 = c * cell_size + cell_size 49 | y1 = r * cell_size + cell_size 50 | if tag_kind == "falling": 51 | canvas.create_rectangle(x0, y0, x1, y1, fill=color,outline="white", width=2, tag=tag_kind) 52 | elif tag_kind == "row": 53 | canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2, tag="row-%s" % r) 54 | else: 55 | canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2) 56 | 57 | 58 | # 绘制面板, 只有在第一次绘制时才绘制背景色方块 59 | def draw_board(canvas, block_list, isFirst=False): 60 | # 删掉原来所有的行 61 | for ri in range(R): 62 | canvas.delete("row-%s" % ri) 63 | 64 | for ri in range(R): 65 | for ci in range(C): 66 | cell_type = block_list[ri][ci] 67 | if cell_type: 68 | draw_cell_by_cr(canvas, ci, ri, SHAPESCOLOR[cell_type], tag_kind="row") 69 | elif isFirst: 70 | draw_cell_by_cr(canvas, ci, ri) 71 | 72 | 73 | 74 | def draw_cells(canvas, c, r, cell_list, color="#CCCCCC"): 75 | """ 76 | 绘制指定形状指定颜色的俄罗斯方块 77 | :param canvas: 画板 78 | :param r: 该形状设定的原点所在的行 79 | :param c: 该形状设定的原点所在的列 80 | :param cell_list: 该形状各个方格相对自身所处位置 81 | :param color: 该形状颜色 82 | :return: 83 | """ 84 | for cell in cell_list: 85 | cell_c, cell_r = cell 86 | ci = cell_c + c 87 | ri = cell_r + r 88 | # 判断该位置方格在画板内部(画板外部的方格不再绘制) 89 | if 0 <= ci < C and 0 <= ri < R: 90 | draw_cell_by_cr(canvas, ci, ri, color, tag_kind="falling") 91 | 92 | 93 | win = tk.Tk() 94 | canvas = tk.Canvas(win, width=width, height=height, ) 95 | canvas.pack() 96 | 97 | block_list = [] 98 | for i in range(R): 99 | i_row = ['' for j in range(C)] 100 | block_list.append(i_row) 101 | 102 | draw_board(canvas, block_list, True) 103 | 104 | 105 | def draw_block_move(canvas, block, direction=[0, 0]): 106 | """ 107 | 绘制向指定方向移动后的俄罗斯方块 108 | :param canvas: 画板 109 | :param block: 俄罗斯方块对象 110 | :param direction: 俄罗斯方块移动方向 111 | :return: 112 | """ 113 | shape_type = block['kind'] 114 | c, r = block['cr'] 115 | cell_list = block['cell_list'] 116 | 117 | # 移动前,清除原有位置绘制的俄罗斯方块 118 | canvas.delete("falling") 119 | 120 | dc, dr = direction 121 | new_c, new_r = c+dc, r+dr 122 | block['cr'] = [new_c, new_r] 123 | # 在新位置绘制新的俄罗斯方块就好 124 | draw_cells(canvas, new_c, new_r, cell_list, SHAPESCOLOR[shape_type]) 125 | 126 | 127 | def generate_new_block(): 128 | # 随机生成新的俄罗斯方块 129 | 130 | kind = random.choice(list(SHAPES.keys())) 131 | # 对应横纵坐标,以左上角为原点,水平向右为x轴正方向, 132 | # 竖直向下为y轴正方向,x对应横坐标,y对应纵坐标 133 | cr = [C // 2, 0] 134 | new_block = { 135 | 'kind': kind, # 对应俄罗斯方块的类型 136 | 'cell_list': SHAPES[kind], 137 | 'cr': cr 138 | } 139 | 140 | return new_block 141 | 142 | 143 | def check_move(block, direction=[0, 0]): 144 | """ 145 | 判断俄罗斯方块是否可以朝制定方向移动 146 | :param block: 俄罗斯方块对象 147 | :param direction: 俄罗斯方块移动方向 148 | :return: boolean 是否可以朝制定方向移动 149 | """ 150 | cc, cr = block['cr'] 151 | cell_list = block['cell_list'] 152 | 153 | for cell in cell_list: 154 | cell_c, cell_r = cell 155 | c = cell_c + cc + direction[0] 156 | r = cell_r + cr + direction[1] 157 | # 判断该位置是否超出左右边界,以及下边界 158 | # 一般不判断上边界,因为俄罗斯方块生成的时候,可能有一部分在上边界之上还没有出来 159 | if c < 0 or c >= C or r >= R: 160 | return False 161 | 162 | # 必须要判断r不小于0才行,具体原因你可以不加这个判断,试试会出现什么效果 163 | if r >= 0 and block_list[r][c]: 164 | return False 165 | 166 | return True 167 | 168 | 169 | def check_row_complete(row): 170 | for cell in row: 171 | if cell=='': 172 | return False 173 | 174 | return True 175 | 176 | 177 | score = 0 178 | win.title("SCORES: %s" % score) # 标题中展示分数 179 | 180 | 181 | def check_and_clear(): 182 | has_complete_row = False 183 | for ri in range(len(block_list)): 184 | if check_row_complete(block_list[ri]): 185 | has_complete_row = True 186 | # 当前行可消除 187 | if ri > 0: 188 | for cur_ri in range(ri, 0, -1): 189 | block_list[cur_ri] = block_list[cur_ri-1][:] 190 | block_list[0] = ['' for j in range(C)] 191 | else: 192 | block_list[ri] = ['' for j in range(C)] 193 | global score 194 | score += 10 195 | 196 | if has_complete_row: 197 | draw_board(canvas, block_list) 198 | 199 | win.title("SCORES: %s" % score) 200 | 201 | 202 | def save_block_to_list(block): 203 | # 清除原有的打上了 falling 标签的方块 204 | canvas.delete("falling") 205 | 206 | shape_type = block['kind'] 207 | cc, cr = block['cr'] 208 | cell_list = block['cell_list'] 209 | 210 | for cell in cell_list: 211 | cell_c, cell_r = cell 212 | c = cell_c + cc 213 | r = cell_r + cr 214 | # block_list 在对应位置记下其类型 215 | block_list[r][c] = shape_type 216 | 217 | draw_cell_by_cr(canvas, c, r, SHAPESCOLOR[shape_type], tag_kind="row") 218 | 219 | 220 | def horizontal_move_block(event): 221 | """ 222 | 左右水平移动俄罗斯方块 223 | """ 224 | direction = [0, 0] 225 | if event.keysym == 'Left': 226 | direction = [-1, 0] 227 | elif event.keysym == 'Right': 228 | direction = [1, 0] 229 | else: 230 | return 231 | 232 | global current_block 233 | if current_block is not None and check_move(current_block, direction): 234 | draw_block_move(canvas, current_block, direction) 235 | 236 | 237 | def rotate_block(event): 238 | global current_block 239 | if current_block is None: 240 | return 241 | 242 | cell_list = current_block['cell_list'] 243 | rotate_list = [] 244 | for cell in cell_list: 245 | cell_c, cell_r = cell 246 | rotate_cell = [cell_r, -cell_c] 247 | rotate_list.append(rotate_cell) 248 | 249 | block_after_rotate = { 250 | 'kind': current_block['kind'], # 对应俄罗斯方块的类型 251 | 'cell_list': rotate_list, 252 | 'cr': current_block['cr'] 253 | } 254 | 255 | if check_move(block_after_rotate): 256 | cc, cr= current_block['cr'] 257 | draw_cells(canvas, cc, cr, current_block['cell_list']) 258 | draw_cells(canvas, cc, cr, rotate_list,SHAPESCOLOR[current_block['kind']]) 259 | current_block = block_after_rotate 260 | 261 | 262 | def land(event): 263 | global current_block 264 | if current_block is None: 265 | return 266 | 267 | cell_list = current_block['cell_list'] 268 | cc, cr = current_block['cr'] 269 | min_height = R 270 | for cell in cell_list: 271 | cell_c, cell_r = cell 272 | c, r = cell_c + cc, cell_r + cr 273 | if r>=0 and block_list[r][c]: 274 | return 275 | h = 0 276 | for ri in range(r+1, R): 277 | if block_list[ri][c]: 278 | break 279 | else: 280 | h += 1 281 | if h < min_height: 282 | min_height = h 283 | 284 | down = [0, min_height] 285 | if check_move(current_block, down): 286 | draw_block_move(canvas, current_block, down) 287 | 288 | 289 | def game_loop(): 290 | win.update() 291 | global current_block 292 | if current_block is None: 293 | new_block = generate_new_block() 294 | # 新生成的俄罗斯方块需要先在生成位置绘制出来 295 | draw_block_move(canvas, new_block) 296 | current_block = new_block 297 | if not check_move(current_block, [0, 0]): 298 | messagebox.showinfo("Game Over!", "Your Score is %s" % score) 299 | win.destroy() 300 | return 301 | else: 302 | if check_move(current_block, [0, 1]): 303 | draw_block_move(canvas, current_block, [0, 1]) 304 | else: 305 | # 无法移动,记入 block_list 中 306 | save_block_to_list(current_block) 307 | current_block = None 308 | check_and_clear() 309 | 310 | win.after(FPS, game_loop) 311 | 312 | 313 | canvas.focus_set() # 聚焦到canvas画板对象上 314 | canvas.bind("", horizontal_move_block) 315 | canvas.bind("", horizontal_move_block) 316 | canvas.bind("", rotate_block) 317 | canvas.bind("", land) 318 | 319 | 320 | current_block = None 321 | 322 | 323 | win.update() 324 | win.after(FPS, game_loop) # 在FPS 毫秒后调用 game_loop方法 325 | 326 | 327 | win.mainloop() 328 | -------------------------------------------------------------------------------- /1_BASIC/4-5.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import messagebox 3 | import random 4 | 5 | cell_size = 30 6 | C = 12 7 | R = 20 8 | height = R * cell_size 9 | width = C * cell_size 10 | 11 | FPS = 200 # 刷新页面的毫秒间隔 12 | 13 | # 定义各种形状 14 | SHAPES = { 15 | "O": [(-1, -1), (0, -1), (-1, 0), (0, 0)], 16 | "S": [(-1, 0), (0, 0), (0, -1), (1, -1)], 17 | "T": [(-1, 0), (0, 0), (0, -1), (1, 0)], 18 | "I": [(0, 1), (0, 0), (0, -1), (0, -2)], 19 | "L": [(-1, 0), (0, 0), (-1, -1), (-1, -2)], 20 | "J": [(-1, 0), (0, 0), (0, -1), (0, -2)], 21 | "Z": [(-1, -1), (0, -1), (0, 0), (1, 0)], 22 | } 23 | 24 | # 定义各种形状的颜色 25 | SHAPESCOLOR = { 26 | "O": "blue", 27 | "S": "red", 28 | "T": "yellow", 29 | "I": "green", 30 | "L": "purple", 31 | "J": "orange", 32 | "Z": "Cyan", 33 | } 34 | 35 | 36 | def draw_cell_by_cr(canvas, c, r, color="#CCCCCC", tag_kind=""): 37 | """ 38 | :param canvas: 画板,用于绘制一个方块的Canvas对象 39 | :param c: 方块所在列 40 | :param r: 方块所在行 41 | :param color: 方块颜色,默认为#CCCCCC,轻灰色 42 | :return: 43 | """ 44 | x0 = c * cell_size 45 | y0 = r * cell_size 46 | x1 = c * cell_size + cell_size 47 | y1 = r * cell_size + cell_size 48 | if tag_kind == "falling": 49 | canvas.create_rectangle(x0, y0, x1, y1, fill=color, 50 | outline="white", width=2, tag=tag_kind) 51 | elif tag_kind == "row": 52 | canvas.create_rectangle(x0, y0, x1, y1, fill=color, 53 | outline="white", width=2, tag="row-%s" % r) 54 | else: 55 | canvas.create_rectangle(x0, y0, x1, y1, fill=color, 56 | outline="white", width=2) 57 | 58 | 59 | # 绘制面板 60 | def draw_board(canvas, block_list, isFirst=False): 61 | for ri in range(R): 62 | canvas.delete("row-%s" % ri) 63 | 64 | for ri in range(R): 65 | for ci in range(C): 66 | cell_type = block_list[ri][ci] 67 | if cell_type: 68 | draw_cell_by_cr(canvas, ci, ri, SHAPESCOLOR[cell_type], tag_kind="row") 69 | elif isFirst: 70 | draw_cell_by_cr(canvas, ci, ri) 71 | 72 | 73 | def draw_cells(canvas, c, r, cell_list, color="#CCCCCC"): 74 | """ 75 | 绘制指定形状指定颜色的俄罗斯方块 76 | :param canvas: 画板 77 | :param r: 该形状设定的原点所在的行 78 | :param c: 该形状设定的原点所在的列 79 | :param cell_list: 该形状各个方格相对自身所处位置 80 | :param color: 该形状颜色 81 | :return: 82 | """ 83 | for cell in cell_list: 84 | cell_c, cell_r = cell 85 | ci = cell_c + c 86 | ri = cell_r + r 87 | # 判断该位置方格在画板内部(画板外部的方格不再绘制) 88 | if 0 <= c < C and 0 <= r < R: 89 | draw_cell_by_cr(canvas, ci, ri, color, tag_kind="falling") 90 | 91 | 92 | win = tk.Tk() 93 | canvas = tk.Canvas(win, width=width, height=height, ) 94 | canvas.pack() 95 | 96 | block_list = [] 97 | for i in range(R): 98 | i_row = ['' for j in range(C)] 99 | block_list.append(i_row) 100 | 101 | draw_board(canvas, block_list, True) 102 | 103 | 104 | def draw_block_move(canvas, block, direction=[0, 0]): 105 | """ 106 | 绘制向指定方向移动后的俄罗斯方块 107 | :param canvas: 画板 108 | :param block: 俄罗斯方块对象 109 | :param direction: 俄罗斯方块移动方向 110 | :return: 111 | """ 112 | shape_type = block['kind'] 113 | c, r = block['cr'] 114 | cell_list = block['cell_list'] 115 | 116 | # 移动前,先清除原有位置绘制的俄罗斯方块,也就是用背景色绘制原有的俄罗斯方块 117 | canvas.delete("falling") 118 | 119 | dc, dr = direction 120 | new_c, new_r = c+dc, r+dr 121 | block['cr'] = [new_c, new_r] 122 | # 在新位置绘制新的俄罗斯方块就好 123 | draw_cells(canvas, new_c, new_r, cell_list, SHAPESCOLOR[shape_type]) 124 | 125 | 126 | def generate_new_block(): 127 | # 随机生成新的俄罗斯方块 128 | 129 | kind = random.choice(list(SHAPES.keys())) 130 | # 对应横纵坐标,以左上角为原点,水平向右为x轴正方向, 131 | # 竖直向下为y轴正方向,x对应横坐标,y对应纵坐标 132 | cr = [C // 2, 0] 133 | new_block = { 134 | 'kind': kind, # 对应俄罗斯方块的类型 135 | 'cell_list': SHAPES[kind], 136 | 'cr': cr 137 | } 138 | 139 | return new_block 140 | 141 | 142 | def check_move(block, direction=[0, 0]): 143 | """ 144 | 判断俄罗斯方块是否可以朝制定方向移动 145 | :param block: 俄罗斯方块对象 146 | :param direction: 俄罗斯方块移动方向 147 | :return: boolean 是否可以朝制定方向移动 148 | """ 149 | cc, cr = block['cr'] 150 | cell_list = block['cell_list'] 151 | 152 | for cell in cell_list: 153 | cell_c, cell_r = cell 154 | c = cell_c + cc + direction[0] 155 | r = cell_r + cr + direction[1] 156 | # 判断该位置是否超出左右边界,以及下边界 157 | # 一般不判断上边界,因为俄罗斯方块生成的时候,可能有一部分在上边界之上还没有出来 158 | if c < 0 or c >= C or r >= R: 159 | return False 160 | 161 | # 必须要判断r不小于0才行,具体原因你可以不加这个判断,试试会出现什么效果 162 | if r >= 0 and block_list[r][c]: 163 | return False 164 | 165 | return True 166 | 167 | 168 | def check_row_complete(row): 169 | for cell in row: 170 | if cell=='': 171 | return False 172 | 173 | return True 174 | 175 | 176 | score = 0 177 | win.title("SCORES: %s" % score) # 标题中展示分数 178 | 179 | 180 | def check_and_clear(): 181 | has_complete_row = False 182 | for ri in range(len(block_list)): 183 | if check_row_complete(block_list[ri]): 184 | has_complete_row = True 185 | # 当前行可消除 186 | if ri > 0: 187 | for cur_ri in range(ri, 0, -1): 188 | block_list[cur_ri] = block_list[cur_ri-1][:] 189 | block_list[0] = ['' for j in range(C)] 190 | else: 191 | block_list[ri] = ['' for j in range(C)] 192 | global score 193 | score += 10 194 | 195 | if has_complete_row: 196 | draw_board(canvas, block_list) 197 | 198 | win.title("SCORES: %s" % score) 199 | 200 | 201 | def save_block_to_list(block): 202 | canvas.delete("falling") 203 | 204 | shape_type = block['kind'] 205 | cc, cr = block['cr'] 206 | cell_list = block['cell_list'] 207 | 208 | for cell in cell_list: 209 | cell_c, cell_r = cell 210 | c = cell_c + cc 211 | r = cell_r + cr 212 | # block_list 在对应位置记下其类型draw_cells 213 | block_list[r][c] = shape_type 214 | 215 | draw_cell_by_cr(canvas, c, r, SHAPESCOLOR[shape_type], tag_kind="row") 216 | 217 | 218 | def horizontal_move_block(event): 219 | """ 220 | 左右水平移动俄罗斯方块 221 | """ 222 | direction = [0, 0] 223 | if event.keysym == 'Left': 224 | direction = [-1, 0] 225 | elif event.keysym == 'Right': 226 | direction = [1, 0] 227 | else: 228 | return 229 | 230 | global current_block 231 | if current_block is not None and check_move(current_block, direction): 232 | draw_block_move(canvas, current_block, direction) 233 | 234 | 235 | def rotate_block(event): 236 | global current_block 237 | if current_block is None: 238 | return 239 | 240 | cell_list = current_block['cell_list'] 241 | rotate_list = [] 242 | for cell in cell_list: 243 | cell_c, cell_r = cell 244 | rotate_cell = [cell_r, -cell_c] 245 | rotate_list.append(rotate_cell) 246 | 247 | block_after_rotate = { 248 | 'kind': current_block['kind'], # 对应俄罗斯方块的类型 249 | 'cell_list': rotate_list, 250 | 'cr': current_block['cr'] 251 | } 252 | 253 | if check_move(block_after_rotate): 254 | cc, cr= current_block['cr'] 255 | draw_cells(canvas, cc, cr, current_block['cell_list']) 256 | draw_cells(canvas, cc, cr, rotate_list,SHAPESCOLOR[current_block['kind']]) 257 | current_block = block_after_rotate 258 | 259 | 260 | def land(event): 261 | global current_block 262 | if current_block is None: 263 | return 264 | 265 | cell_list = current_block['cell_list'] 266 | cc, cr = current_block['cr'] 267 | min_height = R 268 | for cell in cell_list: 269 | cell_c, cell_r = cell 270 | c, r = cell_c + cc, cell_r + cr 271 | if r>=0 and block_list[r][c]: 272 | return 273 | h = 0 274 | for ri in range(r+1, R): 275 | if block_list[ri][c]: 276 | break 277 | else: 278 | h += 1 279 | if h < min_height: 280 | min_height = h 281 | 282 | down = [0, min_height] 283 | if check_move(current_block, down): 284 | draw_block_move(canvas, current_block, down) 285 | 286 | 287 | def game_loop(): 288 | win.update() 289 | global current_block 290 | if current_block is None: 291 | new_block = generate_new_block() 292 | # 新生成的俄罗斯方块需要先在生成位置绘制出来 293 | draw_block_move(canvas, new_block) 294 | current_block = new_block 295 | if not check_move(current_block, [0, 0]): 296 | messagebox.showinfo("Game Over!", "Your Score is %s" % score) 297 | win.destroy() 298 | return 299 | else: 300 | if check_move(current_block, [0, 1]): 301 | draw_block_move(canvas, current_block, [0, 1]) 302 | else: 303 | # 无法移动,记入 block_list 中 304 | save_block_to_list(current_block) 305 | current_block = None 306 | check_and_clear() 307 | 308 | win.after(FPS, game_loop) 309 | 310 | canvas.focus_set() # 聚焦到canvas画板对象上 311 | canvas.bind("", horizontal_move_block) 312 | canvas.bind("", horizontal_move_block) 313 | canvas.bind("", rotate_block) 314 | canvas.bind("", land) 315 | 316 | 317 | current_block = None 318 | 319 | 320 | win.update() 321 | win.after(FPS, game_loop) # 在FPS 毫秒后调用 game_loop方法 322 | 323 | 324 | win.mainloop() 325 | -------------------------------------------------------------------------------- /2_MODE/01.py: -------------------------------------------------------------------------------- 1 | # usr/bin/env python 2 | # -*- coding:utf-8- -*- 3 | # demage mode (残局模式) 4 | import tkinter as tk 5 | from tkinter import messagebox 6 | import random 7 | 8 | cell_size = 30 9 | C = 12 10 | R = 20 11 | height = R * cell_size 12 | width = C * cell_size 13 | 14 | FPS = 200 # 刷新页面的毫秒间隔 15 | demage_line = R // 2 # 残局开始行数 16 | 17 | # 定义各种形状 18 | SHAPES = { 19 | "O": [(-1, -1), (0, -1), (-1, 0), (0, 0)], 20 | "S": [(-1, 0), (0, 0), (0, -1), (1, -1)], 21 | "T": [(-1, 0), (0, 0), (0, -1), (1, 0)], 22 | "I": [(0, 1), (0, 0), (0, -1), (0, -2)], 23 | "L": [(-1, 0), (0, 0), (-1, -1), (-1, -2)], 24 | "J": [(-1, 0), (0, 0), (0, -1), (0, -2)], 25 | "Z": [(-1, -1), (0, -1), (0, 0), (1, 0)], 26 | } 27 | 28 | 29 | # 定义各种形状的颜色 30 | SHAPESCOLOR = { 31 | "O": "blue", 32 | "S": "red", 33 | "T": "yellow", 34 | "I": "green", 35 | "L": "purple", 36 | "J": "orange", 37 | "Z": "Cyan", 38 | "W": "grey", 39 | } 40 | 41 | 42 | def draw_cell_by_cr(canvas, c, r, color="#CCCCCC", tag_kind=""): 43 | """ 44 | :param canvas: 画板,用于绘制一个方块的Canvas对象 45 | :param c: 方块所在列 46 | :param r: 方块所在行 47 | :param color: 方块颜色,默认为#CCCCCC,轻灰色 48 | :return: 49 | """ 50 | x0 = c * cell_size 51 | y0 = r * cell_size 52 | x1 = c * cell_size + cell_size 53 | y1 = r * cell_size + cell_size 54 | if tag_kind == "falling": 55 | canvas.create_rectangle(x0, y0, x1, y1, fill=color,outline="white", width=2, tag=tag_kind) 56 | elif tag_kind == "row": 57 | canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2, tag="row-%s" % r) 58 | else: 59 | canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2) 60 | 61 | 62 | # 绘制面板, 只有在第一次绘制时才绘制背景色方块 63 | def draw_board(canvas, block_list, isFirst=False): 64 | # 删掉原来所有的行 65 | for ri in range(R): 66 | canvas.delete("row-%s" % ri) 67 | 68 | for ri in range(R): 69 | for ci in range(C): 70 | cell_type = block_list[ri][ci] 71 | if cell_type: 72 | draw_cell_by_cr(canvas, ci, ri, SHAPESCOLOR[cell_type], tag_kind="row") 73 | elif isFirst: 74 | draw_cell_by_cr(canvas, ci, ri) 75 | 76 | 77 | def draw_cells(canvas, c, r, cell_list, color="#CCCCCC"): 78 | """ 79 | 绘制指定形状指定颜色的俄罗斯方块 80 | :param canvas: 画板 81 | :param r: 该形状设定的原点所在的行 82 | :param c: 该形状设定的原点所在的列 83 | :param cell_list: 该形状各个方格相对自身所处位置 84 | :param color: 该形状颜色 85 | :return: 86 | """ 87 | for cell in cell_list: 88 | cell_c, cell_r = cell 89 | ci = cell_c + c 90 | ri = cell_r + r 91 | # 判断该位置方格在画板内部(画板外部的方格不再绘制) 92 | if 0 <= c < C and 0 <= r < R: 93 | draw_cell_by_cr(canvas, ci, ri, color, tag_kind="falling") 94 | 95 | 96 | win = tk.Tk() 97 | canvas = tk.Canvas(win, width=width, height=height, ) 98 | canvas.pack() 99 | 100 | block_list = [] 101 | 102 | 103 | for i in range(R): 104 | if i > demage_line: 105 | i_row = ['W' for j in range(C)] 106 | bi = i % C 107 | i_row[bi] = '' 108 | block_list.append(i_row) 109 | else: 110 | i_row = ['' for j in range(C)] 111 | block_list.append(i_row) 112 | 113 | draw_board(canvas, block_list, True) 114 | 115 | 116 | def draw_block_move(canvas, block, direction=[0, 0]): 117 | """ 118 | 绘制向指定方向移动后的俄罗斯方块 119 | :param canvas: 画板 120 | :param block: 俄罗斯方块对象 121 | :param direction: 俄罗斯方块移动方向 122 | :return: 123 | """ 124 | shape_type = block['kind'] 125 | c, r = block['cr'] 126 | cell_list = block['cell_list'] 127 | 128 | # 移动前,清除原有位置绘制的俄罗斯方块 129 | canvas.delete("falling") 130 | 131 | dc, dr = direction 132 | new_c, new_r = c+dc, r+dr 133 | block['cr'] = [new_c, new_r] 134 | # 在新位置绘制新的俄罗斯方块就好 135 | draw_cells(canvas, new_c, new_r, cell_list, SHAPESCOLOR[shape_type]) 136 | 137 | 138 | def generate_new_block(): 139 | # 随机生成新的俄罗斯方块 140 | 141 | kind = random.choice(list(SHAPES.keys())) 142 | # 对应横纵坐标,以左上角为原点,水平向右为x轴正方向, 143 | # 竖直向下为y轴正方向,x对应横坐标,y对应纵坐标 144 | cr = [C // 2, 0] 145 | new_block = { 146 | 'kind': kind, # 对应俄罗斯方块的类型 147 | 'cell_list': SHAPES[kind], 148 | 'cr': cr 149 | } 150 | 151 | return new_block 152 | 153 | 154 | def check_move(block, direction=[0, 0]): 155 | """ 156 | 判断俄罗斯方块是否可以朝制定方向移动 157 | :param block: 俄罗斯方块对象 158 | :param direction: 俄罗斯方块移动方向 159 | :return: boolean 是否可以朝制定方向移动 160 | """ 161 | cc, cr = block['cr'] 162 | cell_list = block['cell_list'] 163 | 164 | for cell in cell_list: 165 | cell_c, cell_r = cell 166 | c = cell_c + cc + direction[0] 167 | r = cell_r + cr + direction[1] 168 | # 判断该位置是否超出左右边界,以及下边界 169 | # 一般不判断上边界,因为俄罗斯方块生成的时候,可能有一部分在上边界之上还没有出来 170 | if c < 0 or c >= C or r >= R: 171 | return False 172 | 173 | # 必须要判断r不小于0才行,具体原因你可以不加这个判断,试试会出现什么效果 174 | if r >= 0 and block_list[r][c]: 175 | return False 176 | 177 | return True 178 | 179 | 180 | def check_row_complete(row): 181 | for cell in row: 182 | if cell=='': 183 | return False 184 | 185 | return True 186 | 187 | 188 | score = 0 189 | win.title("SCORES: %s" % score) # 标题中展示分数 190 | 191 | 192 | def check_and_clear(): 193 | has_complete_row = False 194 | for ri in range(len(block_list)): 195 | if check_row_complete(block_list[ri]): 196 | has_complete_row = True 197 | # 当前行可消除 198 | if ri > 0: 199 | for cur_ri in range(ri, 0, -1): 200 | block_list[cur_ri] = block_list[cur_ri-1][:] 201 | block_list[0] = ['' for j in range(C)] 202 | else: 203 | block_list[ri] = ['' for j in range(C)] 204 | global score 205 | score += 10 206 | 207 | if has_complete_row: 208 | draw_board(canvas, block_list) 209 | 210 | win.title("SCORES: %s" % score) 211 | 212 | 213 | def save_block_to_list(block): 214 | # 清除原有的打上了 falling 标签的方块 215 | canvas.delete("falling") 216 | 217 | shape_type = block['kind'] 218 | cc, cr = block['cr'] 219 | cell_list = block['cell_list'] 220 | 221 | for cell in cell_list: 222 | cell_c, cell_r = cell 223 | c = cell_c + cc 224 | r = cell_r + cr 225 | # block_list 在对应位置记下其类型 226 | block_list[r][c] = shape_type 227 | 228 | draw_cell_by_cr(canvas, c, r, SHAPESCOLOR[shape_type], tag_kind="row") 229 | 230 | 231 | def horizontal_move_block(event): 232 | """ 233 | 左右水平移动俄罗斯方块 234 | """ 235 | direction = [0, 0] 236 | if event.keysym == 'Left': 237 | direction = [-1, 0] 238 | elif event.keysym == 'Right': 239 | direction = [1, 0] 240 | else: 241 | return 242 | 243 | global current_block 244 | if current_block is not None and check_move(current_block, direction): 245 | draw_block_move(canvas, current_block, direction) 246 | 247 | 248 | def rotate_block(event): 249 | global current_block 250 | if current_block is None: 251 | return 252 | 253 | cell_list = current_block['cell_list'] 254 | rotate_list = [] 255 | for cell in cell_list: 256 | cell_c, cell_r = cell 257 | rotate_cell = [cell_r, -cell_c] 258 | rotate_list.append(rotate_cell) 259 | 260 | block_after_rotate = { 261 | 'kind': current_block['kind'], # 对应俄罗斯方块的类型 262 | 'cell_list': rotate_list, 263 | 'cr': current_block['cr'] 264 | } 265 | 266 | if check_move(block_after_rotate): 267 | cc, cr= current_block['cr'] 268 | draw_cells(canvas, cc, cr, current_block['cell_list']) 269 | draw_cells(canvas, cc, cr, rotate_list,SHAPESCOLOR[current_block['kind']]) 270 | current_block = block_after_rotate 271 | 272 | 273 | def land(event): 274 | global current_block 275 | if current_block is None: 276 | return 277 | 278 | cell_list = current_block['cell_list'] 279 | cc, cr = current_block['cr'] 280 | min_height = R 281 | for cell in cell_list: 282 | cell_c, cell_r = cell 283 | c, r = cell_c + cc, cell_r + cr 284 | if r>=0 and block_list[r][c]: 285 | return 286 | h = 0 287 | for ri in range(r+1, R): 288 | if block_list[ri][c]: 289 | break 290 | else: 291 | h += 1 292 | if h < min_height: 293 | min_height = h 294 | 295 | down = [0, min_height] 296 | if check_move(current_block, down): 297 | draw_block_move(canvas, current_block, down) 298 | 299 | 300 | def game_loop(): 301 | win.update() 302 | global current_block 303 | if current_block is None: 304 | new_block = generate_new_block() 305 | # 新生成的俄罗斯方块需要先在生成位置绘制出来 306 | draw_block_move(canvas, new_block) 307 | current_block = new_block 308 | if not check_move(current_block, [0, 0]): 309 | messagebox.showinfo("Game Over!", "Your Score is %s" % score) 310 | win.destroy() 311 | return 312 | else: 313 | if check_move(current_block, [0, 1]): 314 | draw_block_move(canvas, current_block, [0, 1]) 315 | else: 316 | # 无法移动,记入 block_list 中 317 | save_block_to_list(current_block) 318 | current_block = None 319 | check_and_clear() 320 | 321 | win.after(FPS, game_loop) 322 | 323 | 324 | canvas.focus_set() # 聚焦到canvas画板对象上 325 | canvas.bind("", horizontal_move_block) 326 | canvas.bind("", horizontal_move_block) 327 | canvas.bind("", rotate_block) 328 | canvas.bind("", land) 329 | 330 | 331 | current_block = None 332 | 333 | 334 | win.update() 335 | win.after(FPS, game_loop) # 在FPS 毫秒后调用 game_loop方法 336 | 337 | 338 | win.mainloop() 339 | -------------------------------------------------------------------------------- /2_MODE/04.py: -------------------------------------------------------------------------------- 1 | # usr/bin/env python 2 | # -*- coding:utf-8- -*- 3 | # Hidden mode (隐藏模式) 4 | # The box on the bottom is hidden by default, and it is only displayed once when the new block lands 5 | # 底部落地的方块默认隐藏,只在新的方块落地瞬间展示一次 6 | import tkinter as tk 7 | from tkinter import messagebox 8 | import random 9 | 10 | cell_size = 30 11 | C = 12 12 | R = 20 13 | height = R * cell_size 14 | width = C * cell_size 15 | 16 | FPS = 200 # 刷新页面的毫秒间隔 17 | 18 | # 定义各种形状 19 | SHAPES = { 20 | "O": [(-1, -1), (0, -1), (-1, 0), (0, 0)], 21 | "S": [(-1, 0), (0, 0), (0, -1), (1, -1)], 22 | "T": [(-1, 0), (0, 0), (0, -1), (1, 0)], 23 | "I": [(0, 1), (0, 0), (0, -1), (0, -2)], 24 | "L": [(-1, 0), (0, 0), (-1, -1), (-1, -2)], 25 | "J": [(-1, 0), (0, 0), (0, -1), (0, -2)], 26 | "Z": [(-1, -1), (0, -1), (0, 0), (1, 0)], 27 | } 28 | 29 | # 定义各种形状的颜色 30 | SHAPESCOLOR = { 31 | "O": "blue", 32 | "S": "red", 33 | "T": "yellow", 34 | "I": "green", 35 | "L": "purple", 36 | "J": "orange", 37 | "Z": "Cyan", 38 | } 39 | 40 | 41 | def draw_cell_by_cr(canvas, c, r, color="#CCCCCC"): 42 | """ 43 | :param canvas: 画板,用于绘制一个方块的Canvas对象 44 | :param c: 方块所在列 45 | :param r: 方块所在行 46 | :param color: 方块颜色,默认为#CCCCCC,轻灰色 47 | :return: 48 | """ 49 | x0 = c * cell_size 50 | y0 = r * cell_size 51 | x1 = c * cell_size + cell_size 52 | y1 = r * cell_size + cell_size 53 | canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2) 54 | 55 | 56 | # 绘制空白面板 57 | def draw_board(canvas, block_list): 58 | for ri in range(R): 59 | for ci in range(C): 60 | cell_type = block_list[ri][ci] 61 | if cell_type: 62 | draw_cell_by_cr(canvas, ci, ri, SHAPESCOLOR[cell_type]) 63 | else: 64 | draw_cell_by_cr(canvas, ci, ri) 65 | 66 | 67 | def draw_cells(canvas, c, r, cell_list, color="#CCCCCC"): 68 | """ 69 | 绘制指定形状指定颜色的俄罗斯方块 70 | :param canvas: 画板 71 | :param r: 该形状设定的原点所在的行 72 | :param c: 该形状设定的原点所在的列 73 | :param cell_list: 该形状各个方格相对自身所处位置 74 | :param color: 该形状颜色 75 | :return: 76 | """ 77 | for cell in cell_list: 78 | cell_c, cell_r = cell 79 | ci = cell_c + c 80 | ri = cell_r + r 81 | # 判断该位置方格在画板内部(画板外部的方格不再绘制) 82 | if 0 <= c < C and 0 <= r < R: 83 | draw_cell_by_cr(canvas, ci, ri, color) 84 | 85 | 86 | win = tk.Tk() 87 | canvas = tk.Canvas(win, width=width, height=height, ) 88 | canvas.pack() 89 | 90 | block_list = [] 91 | for i in range(R): 92 | i_row = ['' for j in range(C)] 93 | block_list.append(i_row) 94 | 95 | draw_board(canvas, block_list) 96 | 97 | 98 | def draw_block_move(canvas, block, direction=[0, 0]): 99 | """ 100 | 绘制向指定方向移动后的俄罗斯方块 101 | :param canvas: 画板 102 | :param block: 俄罗斯方块对象 103 | :param direction: 俄罗斯方块移动方向 104 | :return: 105 | """ 106 | shape_type = block['kind'] 107 | c, r = block['cr'] 108 | cell_list = block['cell_list'] 109 | 110 | # 移动前,先清除原有位置绘制的俄罗斯方块,也就是用背景色绘制原有的俄罗斯方块 111 | draw_cells(canvas, c, r, cell_list) 112 | 113 | dc, dr = direction 114 | new_c, new_r = c+dc, r+dr 115 | block['cr'] = [new_c, new_r] 116 | # 在新位置绘制新的俄罗斯方块就好 117 | draw_cells(canvas, new_c, new_r, cell_list, SHAPESCOLOR[shape_type]) 118 | 119 | 120 | def generate_new_block(): 121 | # 随机生成新的俄罗斯方块 122 | 123 | kind = random.choice(list(SHAPES.keys())) 124 | # 对应横纵坐标,以左上角为原点,水平向右为x轴正方向, 125 | # 竖直向下为y轴正方向,x对应横坐标,y对应纵坐标 126 | cr = [C // 2, 0] 127 | new_block = { 128 | 'kind': kind, # 对应俄罗斯方块的类型 129 | 'cell_list': SHAPES[kind], 130 | 'cr': cr 131 | } 132 | 133 | return new_block 134 | 135 | 136 | def check_move(block, direction=[0, 0]): 137 | """ 138 | 判断俄罗斯方块是否可以朝制定方向移动 139 | :param block: 俄罗斯方块对象 140 | :param direction: 俄罗斯方块移动方向 141 | :return: boolean 是否可以朝制定方向移动 142 | """ 143 | cc, cr = block['cr'] 144 | cell_list = block['cell_list'] 145 | 146 | for cell in cell_list: 147 | cell_c, cell_r = cell 148 | c = cell_c + cc + direction[0] 149 | r = cell_r + cr + direction[1] 150 | # 判断该位置是否超出左右边界,以及下边界 151 | # 一般不判断上边界,因为俄罗斯方块生成的时候,可能有一部分在上边界之上还没有出来 152 | if c < 0 or c >= C or r >= R: 153 | return False 154 | 155 | # 必须要判断r不小于0才行,具体原因你可以不加这个判断,试试会出现什么效果 156 | if r >= 0 and block_list[r][c]: 157 | return False 158 | 159 | return True 160 | 161 | 162 | def check_row_complete(row): 163 | for cell in row: 164 | if cell=='': 165 | return False 166 | 167 | return True 168 | 169 | 170 | score = 0 171 | win.title("SCORES: %s" % score) # 标题中展示分数 172 | 173 | 174 | def check_and_clear(): 175 | has_complete_row = False 176 | for ri in range(len(block_list)): 177 | if check_row_complete(block_list[ri]): 178 | has_complete_row = True 179 | # 当前行可消除 180 | if ri > 0: 181 | for cur_ri in range(ri, 0, -1): 182 | block_list[cur_ri] = block_list[cur_ri-1][:] 183 | block_list[0] = ['' for j in range(C)] 184 | else: 185 | block_list[ri] = ['' for j in range(C)] 186 | global score 187 | score += 10 188 | 189 | if has_complete_row: 190 | draw_board(canvas, block_list) 191 | 192 | win.title("SCORES: %s" % score) 193 | 194 | return has_complete_row 195 | 196 | 197 | def save_block_to_list(block): 198 | shape_type = block['kind'] 199 | cc, cr = block['cr'] 200 | cell_list = block['cell_list'] 201 | 202 | for cell in cell_list: 203 | cell_c, cell_r = cell 204 | c = cell_c + cc 205 | r = cell_r + cr 206 | # block_list 在对应位置记下其类型 207 | block_list[r][c] = shape_type 208 | 209 | 210 | def horizontal_move_block(event): 211 | """ 212 | 左右水平移动俄罗斯方块 213 | """ 214 | direction = [0, 0] 215 | if event.keysym == 'Left': 216 | direction = [-1, 0] 217 | elif event.keysym == 'Right': 218 | direction = [1, 0] 219 | else: 220 | return 221 | 222 | global current_block 223 | if current_block is not None and check_move(current_block, direction): 224 | draw_block_move(canvas, current_block, direction) 225 | 226 | 227 | def rotate_block(event): 228 | global current_block 229 | if current_block is None: 230 | return 231 | 232 | cell_list = current_block['cell_list'] 233 | rotate_list = [] 234 | for cell in cell_list: 235 | cell_c, cell_r = cell 236 | rotate_cell = [cell_r, -cell_c] 237 | rotate_list.append(rotate_cell) 238 | 239 | block_after_rotate = { 240 | 'kind': current_block['kind'], # 对应俄罗斯方块的类型 241 | 'cell_list': rotate_list, 242 | 'cr': current_block['cr'] 243 | } 244 | 245 | if check_move(block_after_rotate): 246 | cc, cr= current_block['cr'] 247 | draw_cells(canvas, cc, cr, current_block['cell_list']) 248 | draw_cells(canvas, cc, cr, rotate_list,SHAPESCOLOR[current_block['kind']]) 249 | current_block = block_after_rotate 250 | 251 | 252 | def land(event): 253 | global current_block 254 | if current_block is None: 255 | return 256 | 257 | cell_list = current_block['cell_list'] 258 | cc, cr = current_block['cr'] 259 | min_height = R 260 | for cell in cell_list: 261 | cell_c, cell_r = cell 262 | c, r = cell_c + cc, cell_r + cr 263 | if r>=0 and block_list[r][c]: 264 | return 265 | h = 0 266 | for ri in range(r+1, R): 267 | if block_list[ri][c]: 268 | break 269 | else: 270 | h += 1 271 | if h < min_height: 272 | min_height = h 273 | 274 | down = [0, min_height] 275 | if check_move(current_block, down): 276 | draw_block_move(canvas, current_block, down) 277 | 278 | 279 | has_hiden = True 280 | 281 | 282 | def show_block_list(): 283 | for ri in range(R): 284 | for ci in range(C): 285 | if block_list[ri][ci]: 286 | draw_cell_by_cr(canvas, ci, ri, SHAPESCOLOR[block_list[ri][ci]]) 287 | 288 | 289 | def hide_block_list(): 290 | for ri in range(R): 291 | for ci in range(C): 292 | if block_list[ri][ci]: 293 | draw_cell_by_cr(canvas, ci, ri) 294 | 295 | 296 | def game_loop(): 297 | win.update() 298 | global current_block, has_hiden 299 | if current_block is None: 300 | new_block = generate_new_block() 301 | # 新生成的俄罗斯方块需要先在生成位置绘制出来 302 | draw_block_move(canvas, new_block) 303 | current_block = new_block 304 | if not check_move(current_block, [0, 0]): 305 | messagebox.showinfo("Game Over!", "Your Score is %s" % score) 306 | win.destroy() 307 | return 308 | else: 309 | if check_move(current_block, [0, 1]): 310 | draw_block_move(canvas, current_block, [0, 1]) 311 | else: 312 | # 无法移动,记入 block_list 中 313 | if has_hiden: 314 | save_block_to_list(current_block) 315 | show_block_list() 316 | has_hiden = False 317 | elif not check_and_clear(): 318 | hide_block_list() 319 | current_block = None 320 | has_hiden = True 321 | 322 | if not has_hiden: 323 | win.after(2 * FPS, game_loop) 324 | else: 325 | win.after(FPS, game_loop) 326 | 327 | canvas.focus_set() # 聚焦到canvas画板对象上 328 | canvas.bind("", horizontal_move_block) 329 | canvas.bind("", horizontal_move_block) 330 | canvas.bind("", rotate_block) 331 | canvas.bind("", land) 332 | 333 | 334 | current_block = None 335 | 336 | 337 | win.update() 338 | win.after(FPS, game_loop) # 在FPS 毫秒后调用 game_loop方法 339 | 340 | 341 | win.mainloop() 342 | -------------------------------------------------------------------------------- /2_MODE/02.py: -------------------------------------------------------------------------------- 1 | # usr/bin/env python 2 | # -*- coding:utf-8- -*- 3 | # float mode (浮动模式) 4 | 5 | import tkinter as tk 6 | from tkinter import messagebox 7 | import random 8 | 9 | cell_size = 30 10 | C = 12 11 | R = 20 12 | height = R * cell_size 13 | width = C * cell_size 14 | 15 | FPS = 200 # 刷新页面的毫秒间隔 16 | 17 | 18 | # 定义各种形状 19 | SHAPES = { 20 | "O": [(-1, -1), (0, -1), (-1, 0), (0, 0)], 21 | "S": [(-1, 0), (0, 0), (0, -1), (1, -1)], 22 | "T": [(-1, 0), (0, 0), (0, -1), (1, 0)], 23 | "I": [(0, 1), (0, 0), (0, -1), (0, -2)], 24 | "L": [(-1, 0), (0, 0), (-1, -1), (-1, -2)], 25 | "J": [(-1, 0), (0, 0), (0, -1), (0, -2)], 26 | "Z": [(-1, -1), (0, -1), (0, 0), (1, 0)], 27 | } 28 | 29 | 30 | # 定义各种形状的颜色 31 | SHAPESCOLOR = { 32 | "O": "blue", 33 | "S": "red", 34 | "T": "yellow", 35 | "I": "green", 36 | "L": "purple", 37 | "J": "orange", 38 | "Z": "Cyan", 39 | } 40 | 41 | 42 | def draw_cell_by_cr(canvas, c, r, color="#CCCCCC", tag_kind=""): 43 | """ 44 | :param canvas: 画板,用于绘制一个方块的Canvas对象 45 | :param c: 方块所在列 46 | :param r: 方块所在行 47 | :param color: 方块颜色,默认为#CCCCCC,轻灰色 48 | :return: 49 | """ 50 | x0 = c * cell_size 51 | y0 = r * cell_size 52 | x1 = c * cell_size + cell_size 53 | y1 = r * cell_size + cell_size 54 | if tag_kind == "falling": 55 | canvas.create_rectangle(x0, y0, x1, y1, fill=color,outline="white", width=2, tag=tag_kind) 56 | elif tag_kind == "row": 57 | canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2, tag="row-%s" % r) 58 | else: 59 | canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2) 60 | 61 | 62 | # 绘制面板, 只有在第一次绘制时才绘制背景色方块 63 | def draw_board(canvas, block_list, isFirst=False): 64 | # 删掉原来所有的行 65 | for ri in range(R): 66 | canvas.delete("row-%s" % ri) 67 | 68 | for ri in range(R): 69 | for ci in range(C): 70 | cell_type = block_list[ri][ci] 71 | if cell_type: 72 | draw_cell_by_cr(canvas, ci, ri, SHAPESCOLOR[cell_type], tag_kind="row") 73 | elif isFirst: 74 | draw_cell_by_cr(canvas, ci, ri) 75 | 76 | 77 | def draw_cells(canvas, c, r, cell_list, color="#CCCCCC"): 78 | """ 79 | 绘制指定形状指定颜色的俄罗斯方块 80 | :param canvas: 画板 81 | :param r: 该形状设定的原点所在的行 82 | :param c: 该形状设定的原点所在的列 83 | :param cell_list: 该形状各个方格相对自身所处位置 84 | :param color: 该形状颜色 85 | :return: 86 | """ 87 | for cell in cell_list: 88 | cell_c, cell_r = cell 89 | ci = cell_c + c 90 | ri = cell_r + r 91 | # 判断该位置方格在画板内部(画板外部的方格不再绘制) 92 | if 0 <= c < C and 0 <= r < R: 93 | draw_cell_by_cr(canvas, ci, ri, color, tag_kind="falling") 94 | 95 | 96 | win = tk.Tk() 97 | canvas = tk.Canvas(win, width=width, height=height, ) 98 | canvas.pack() 99 | 100 | block_list = [] 101 | for i in range(R): 102 | i_row = ['' for j in range(C)] 103 | block_list.append(i_row) 104 | 105 | draw_board(canvas, block_list, True) 106 | 107 | 108 | def draw_block_move(canvas, block, direction=[0, 0]): 109 | """ 110 | 绘制向指定方向移动后的俄罗斯方块 111 | :param canvas: 画板 112 | :param block: 俄罗斯方块对象 113 | :param direction: 俄罗斯方块移动方向 114 | :return: 115 | """ 116 | shape_type = block['kind'] 117 | c, r = block['cr'] 118 | cell_list = block['cell_list'] 119 | 120 | # 移动前,清除原有位置绘制的俄罗斯方块 121 | canvas.delete("falling") 122 | 123 | dc, dr = direction 124 | new_c, new_r = c+dc, r+dr 125 | block['cr'] = [new_c, new_r] 126 | # 在新位置绘制新的俄罗斯方块就好 127 | draw_cells(canvas, new_c, new_r, cell_list, SHAPESCOLOR[shape_type]) 128 | 129 | 130 | def generate_new_block(): 131 | # 随机生成新的俄罗斯方块 132 | 133 | kind = random.choice(list(SHAPES.keys())) 134 | # 对应横纵坐标,以左上角为原点,水平向右为x轴正方向, 135 | # 竖直向下为y轴正方向,x对应横坐标,y对应纵坐标 136 | cr = [C // 2, 0] 137 | new_block = { 138 | 'kind': kind, # 对应俄罗斯方块的类型 139 | 'cell_list': SHAPES[kind], 140 | 'cr': cr 141 | } 142 | 143 | return new_block 144 | 145 | 146 | def check_move(block, direction=[0, 0]): 147 | """ 148 | 判断俄罗斯方块是否可以朝制定方向移动 149 | :param block: 俄罗斯方块对象 150 | :param direction: 俄罗斯方块移动方向 151 | :return: boolean 是否可以朝制定方向移动 152 | """ 153 | cc, cr = block['cr'] 154 | cell_list = block['cell_list'] 155 | 156 | for cell in cell_list: 157 | cell_c, cell_r = cell 158 | c = cell_c + cc + direction[0] 159 | r = cell_r + cr + direction[1] 160 | # 判断该位置是否超出左右边界,以及下边界 161 | # 一般不判断上边界,因为俄罗斯方块生成的时候,可能有一部分在上边界之上还没有出来 162 | if c < 0 or c >= C or r >= R: 163 | return False 164 | 165 | # 必须要判断r不小于0才行,具体原因你可以不加这个判断,试试会出现什么效果 166 | if r >= 0 and block_list[r][c]: 167 | return False 168 | 169 | return True 170 | 171 | 172 | def check_row_complete(row): 173 | for cell in row: 174 | if cell=='': 175 | return False 176 | 177 | return True 178 | 179 | 180 | score = 0 181 | win.title("SCORES: %s" % score) # 标题中展示分数 182 | 183 | 184 | def check_and_clear(): 185 | has_complete_row = False 186 | for ri in range(len(block_list)): 187 | if check_row_complete(block_list[ri]): 188 | has_complete_row = True 189 | # 当前行可消除 190 | if ri > 0: 191 | for cur_ri in range(ri, 0, -1): 192 | block_list[cur_ri] = block_list[cur_ri-1][:] 193 | block_list[0] = ['' for j in range(C)] 194 | else: 195 | block_list[ri] = ['' for j in range(C)] 196 | global score 197 | score += 10 198 | 199 | if has_complete_row: 200 | draw_board(canvas, block_list) 201 | 202 | win.title("SCORES: %s" % score) 203 | 204 | 205 | def save_block_to_list(block): 206 | # 清除原有的打上了 falling 标签的方块 207 | canvas.delete("falling") 208 | 209 | shape_type = block['kind'] 210 | cc, cr = block['cr'] 211 | cell_list = block['cell_list'] 212 | 213 | for cell in cell_list: 214 | cell_c, cell_r = cell 215 | c = cell_c + cc 216 | r = cell_r + cr 217 | # block_list 在对应位置记下其类型 218 | block_list[r][c] = shape_type 219 | 220 | draw_cell_by_cr(canvas, c, r, SHAPESCOLOR[shape_type], tag_kind="row") 221 | 222 | 223 | def horizontal_move_block(event): 224 | """ 225 | 左右水平移动俄罗斯方块 226 | """ 227 | direction = [0, 0] 228 | if event.keysym == 'Left': 229 | direction = [-1, 0] 230 | elif event.keysym == 'Right': 231 | direction = [1, 0] 232 | else: 233 | return 234 | 235 | global current_block 236 | if current_block is not None and check_move(current_block, direction): 237 | draw_block_move(canvas, current_block, direction) 238 | 239 | 240 | def rotate_block(event): 241 | global current_block 242 | if current_block is None: 243 | return 244 | 245 | cell_list = current_block['cell_list'] 246 | rotate_list = [] 247 | for cell in cell_list: 248 | cell_c, cell_r = cell 249 | rotate_cell = [cell_r, -cell_c] 250 | rotate_list.append(rotate_cell) 251 | 252 | block_after_rotate = { 253 | 'kind': current_block['kind'], # 对应俄罗斯方块的类型 254 | 'cell_list': rotate_list, 255 | 'cr': current_block['cr'] 256 | } 257 | 258 | if check_move(block_after_rotate): 259 | cc, cr= current_block['cr'] 260 | draw_cells(canvas, cc, cr, current_block['cell_list']) 261 | draw_cells(canvas, cc, cr, rotate_list,SHAPESCOLOR[current_block['kind']]) 262 | current_block = block_after_rotate 263 | 264 | 265 | def land(event): 266 | global current_block 267 | if current_block is None: 268 | return 269 | 270 | cell_list = current_block['cell_list'] 271 | cc, cr = current_block['cr'] 272 | min_height = R 273 | for cell in cell_list: 274 | cell_c, cell_r = cell 275 | c, r = cell_c + cc, cell_r + cr 276 | if r>=0 and block_list[r][c]: 277 | return 278 | h = 0 279 | for ri in range(r+1, R): 280 | if block_list[ri][c]: 281 | break 282 | else: 283 | h += 1 284 | if h < min_height: 285 | min_height = h 286 | 287 | down = [0, min_height] 288 | if check_move(current_block, down): 289 | draw_block_move(canvas, current_block, down) 290 | 291 | 292 | def left_float(): 293 | for row in block_list: 294 | first = row.pop(0) 295 | row.append(first) 296 | 297 | 298 | def game_loop(): 299 | win.update() 300 | global current_block, to_float 301 | if to_float: 302 | left_float() 303 | draw_board(canvas, block_list) 304 | to_float = False 305 | elif current_block is None: 306 | new_block = generate_new_block() 307 | # 新生成的俄罗斯方块需要先在生成位置绘制出来 308 | draw_block_move(canvas, new_block) 309 | current_block = new_block 310 | if not check_move(current_block, [0, 0]): 311 | messagebox.showinfo("Game Over!", "Your Score is %s" % score) 312 | win.destroy() 313 | return 314 | else: 315 | if check_move(current_block, [0, 1]): 316 | draw_block_move(canvas, current_block, [0, 1]) 317 | else: 318 | # 无法移动,记入 block_list 中 319 | save_block_to_list(current_block) 320 | current_block = None 321 | check_and_clear() 322 | to_float = True 323 | 324 | win.after(FPS, game_loop) 325 | 326 | 327 | canvas.focus_set() # 聚焦到canvas画板对象上 328 | canvas.bind("", horizontal_move_block) 329 | canvas.bind("", horizontal_move_block) 330 | canvas.bind("", rotate_block) 331 | canvas.bind("", land) 332 | 333 | 334 | current_block = None 335 | to_float = False 336 | 337 | win.update() 338 | win.after(FPS, game_loop) # 在FPS 毫秒后调用 game_loop方法 339 | 340 | 341 | win.mainloop() 342 | -------------------------------------------------------------------------------- /2_MODE/03.py: -------------------------------------------------------------------------------- 1 | # usr/bin/env python 2 | # -*- coding:utf-8- -*- 3 | # Up mode (上浮模式) 4 | 5 | import tkinter as tk 6 | from tkinter import messagebox 7 | import random 8 | 9 | cell_size = 30 10 | C = 12 11 | R = 20 12 | height = R * cell_size 13 | width = C * cell_size 14 | 15 | FPS = 200 # 刷新页面的毫秒间隔 16 | UP_SPACE = 2 # 上浮时间间隔 17 | 18 | # 定义各种形状 19 | SHAPES = { 20 | "O": [(-1, -1), (0, -1), (-1, 0), (0, 0)], 21 | "S": [(-1, 0), (0, 0), (0, -1), (1, -1)], 22 | "T": [(-1, 0), (0, 0), (0, -1), (1, 0)], 23 | "I": [(0, 1), (0, 0), (0, -1), (0, -2)], 24 | "L": [(-1, 0), (0, 0), (-1, -1), (-1, -2)], 25 | "J": [(-1, 0), (0, 0), (0, -1), (0, -2)], 26 | "Z": [(-1, -1), (0, -1), (0, 0), (1, 0)], 27 | } 28 | 29 | 30 | # 定义各种形状的颜色 31 | SHAPESCOLOR = { 32 | "O": "blue", 33 | "S": "red", 34 | "T": "yellow", 35 | "I": "green", 36 | "L": "purple", 37 | "J": "orange", 38 | "Z": "Cyan", 39 | "W": "grey" 40 | } 41 | 42 | 43 | def draw_cell_by_cr(canvas, c, r, color="#CCCCCC", tag_kind=""): 44 | """ 45 | :param canvas: 画板,用于绘制一个方块的Canvas对象 46 | :param c: 方块所在列 47 | :param r: 方块所在行 48 | :param color: 方块颜色,默认为#CCCCCC,轻灰色 49 | :return: 50 | """ 51 | x0 = c * cell_size 52 | y0 = r * cell_size 53 | x1 = c * cell_size + cell_size 54 | y1 = r * cell_size + cell_size 55 | if tag_kind == "falling": 56 | canvas.create_rectangle(x0, y0, x1, y1, fill=color,outline="white", width=2, tag=tag_kind) 57 | elif tag_kind == "row": 58 | canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2, tag="row-%s" % r) 59 | else: 60 | canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2) 61 | 62 | 63 | # 绘制面板, 只有在第一次绘制时才绘制背景色方块 64 | def draw_board(canvas, block_list, isFirst=False): 65 | # 删掉原来所有的行 66 | for ri in range(R): 67 | canvas.delete("row-%s" % ri) 68 | 69 | for ri in range(R): 70 | for ci in range(C): 71 | cell_type = block_list[ri][ci] 72 | if cell_type: 73 | draw_cell_by_cr(canvas, ci, ri, SHAPESCOLOR[cell_type], tag_kind="row") 74 | elif isFirst: 75 | draw_cell_by_cr(canvas, ci, ri) 76 | 77 | 78 | def draw_cells(canvas, c, r, cell_list, color="#CCCCCC"): 79 | """ 80 | 绘制指定形状指定颜色的俄罗斯方块 81 | :param canvas: 画板 82 | :param r: 该形状设定的原点所在的行 83 | :param c: 该形状设定的原点所在的列 84 | :param cell_list: 该形状各个方格相对自身所处位置 85 | :param color: 该形状颜色 86 | :return: 87 | """ 88 | for cell in cell_list: 89 | cell_c, cell_r = cell 90 | ci = cell_c + c 91 | ri = cell_r + r 92 | # 判断该位置方格在画板内部(画板外部的方格不再绘制) 93 | if 0 <= c < C and 0 <= r < R: 94 | draw_cell_by_cr(canvas, ci, ri, color, tag_kind="falling") 95 | 96 | 97 | win = tk.Tk() 98 | canvas = tk.Canvas(win, width=width, height=height, ) 99 | canvas.pack() 100 | 101 | block_list = [] 102 | for i in range(R): 103 | i_row = ['' for j in range(C)] 104 | block_list.append(i_row) 105 | 106 | draw_board(canvas, block_list, True) 107 | 108 | 109 | def draw_block_move(canvas, block, direction=[0, 0]): 110 | """ 111 | 绘制向指定方向移动后的俄罗斯方块 112 | :param canvas: 画板 113 | :param block: 俄罗斯方块对象 114 | :param direction: 俄罗斯方块移动方向 115 | :return: 116 | """ 117 | shape_type = block['kind'] 118 | c, r = block['cr'] 119 | cell_list = block['cell_list'] 120 | 121 | # 移动前,清除原有位置绘制的俄罗斯方块 122 | canvas.delete("falling") 123 | 124 | dc, dr = direction 125 | new_c, new_r = c+dc, r+dr 126 | block['cr'] = [new_c, new_r] 127 | # 在新位置绘制新的俄罗斯方块就好 128 | draw_cells(canvas, new_c, new_r, cell_list, SHAPESCOLOR[shape_type]) 129 | 130 | 131 | def generate_new_block(): 132 | # 随机生成新的俄罗斯方块 133 | 134 | kind = random.choice(list(SHAPES.keys())) 135 | # 对应横纵坐标,以左上角为原点,水平向右为x轴正方向, 136 | # 竖直向下为y轴正方向,x对应横坐标,y对应纵坐标 137 | cr = [C // 2, 0] 138 | new_block = { 139 | 'kind': kind, # 对应俄罗斯方块的类型 140 | 'cell_list': SHAPES[kind], 141 | 'cr': cr 142 | } 143 | 144 | return new_block 145 | 146 | 147 | def check_move(block, direction=[0, 0]): 148 | """ 149 | 判断俄罗斯方块是否可以朝制定方向移动 150 | :param block: 俄罗斯方块对象 151 | :param direction: 俄罗斯方块移动方向 152 | :return: boolean 是否可以朝制定方向移动 153 | """ 154 | cc, cr = block['cr'] 155 | cell_list = block['cell_list'] 156 | 157 | for cell in cell_list: 158 | cell_c, cell_r = cell 159 | c = cell_c + cc + direction[0] 160 | r = cell_r + cr + direction[1] 161 | # 判断该位置是否超出左右边界,以及下边界 162 | # 一般不判断上边界,因为俄罗斯方块生成的时候,可能有一部分在上边界之上还没有出来 163 | if c < 0 or c >= C or r >= R: 164 | return False 165 | 166 | # 必须要判断r不小于0才行,具体原因你可以不加这个判断,试试会出现什么效果 167 | if r >= 0 and block_list[r][c]: 168 | return False 169 | 170 | return True 171 | 172 | 173 | def check_row_complete(row): 174 | for cell in row: 175 | if cell=='': 176 | return False 177 | 178 | return True 179 | 180 | 181 | score = 0 182 | win.title("SCORES: %s" % score) # 标题中展示分数 183 | 184 | 185 | def check_and_clear(): 186 | has_complete_row = False 187 | for ri in range(len(block_list)): 188 | if check_row_complete(block_list[ri]): 189 | has_complete_row = True 190 | # 当前行可消除 191 | if ri > 0: 192 | for cur_ri in range(ri, 0, -1): 193 | block_list[cur_ri] = block_list[cur_ri-1][:] 194 | block_list[0] = ['' for j in range(C)] 195 | else: 196 | block_list[ri] = ['' for j in range(C)] 197 | global score 198 | score += 10 199 | 200 | if has_complete_row: 201 | draw_board(canvas, block_list) 202 | 203 | win.title("SCORES: %s" % score) 204 | 205 | 206 | def save_block_to_list(block): 207 | # 清除原有的打上了 falling 标签的方块 208 | canvas.delete("falling") 209 | 210 | shape_type = block['kind'] 211 | cc, cr = block['cr'] 212 | cell_list = block['cell_list'] 213 | 214 | for cell in cell_list: 215 | cell_c, cell_r = cell 216 | c = cell_c + cc 217 | r = cell_r + cr 218 | # block_list 在对应位置记下其类型 219 | block_list[r][c] = shape_type 220 | 221 | draw_cell_by_cr(canvas, c, r, SHAPESCOLOR[shape_type], tag_kind="row") 222 | 223 | 224 | def horizontal_move_block(event): 225 | """ 226 | 左右水平移动俄罗斯方块 227 | """ 228 | direction = [0, 0] 229 | if event.keysym == 'Left': 230 | direction = [-1, 0] 231 | elif event.keysym == 'Right': 232 | direction = [1, 0] 233 | else: 234 | return 235 | 236 | global current_block 237 | if current_block is not None and check_move(current_block, direction): 238 | draw_block_move(canvas, current_block, direction) 239 | 240 | 241 | def rotate_block(event): 242 | global current_block 243 | if current_block is None: 244 | return 245 | 246 | cell_list = current_block['cell_list'] 247 | rotate_list = [] 248 | for cell in cell_list: 249 | cell_c, cell_r = cell 250 | rotate_cell = [cell_r, -cell_c] 251 | rotate_list.append(rotate_cell) 252 | 253 | block_after_rotate = { 254 | 'kind': current_block['kind'], # 对应俄罗斯方块的类型 255 | 'cell_list': rotate_list, 256 | 'cr': current_block['cr'] 257 | } 258 | 259 | if check_move(block_after_rotate): 260 | cc, cr= current_block['cr'] 261 | draw_cells(canvas, cc, cr, current_block['cell_list']) 262 | draw_cells(canvas, cc, cr, rotate_list,SHAPESCOLOR[current_block['kind']]) 263 | current_block = block_after_rotate 264 | 265 | 266 | def land(event): 267 | global current_block 268 | if current_block is None: 269 | return 270 | 271 | cell_list = current_block['cell_list'] 272 | cc, cr = current_block['cr'] 273 | min_height = R 274 | for cell in cell_list: 275 | cell_c, cell_r = cell 276 | c, r = cell_c + cc, cell_r + cr 277 | if r>=0 and block_list[r][c]: 278 | return 279 | h = 0 280 | for ri in range(r+1, R): 281 | if block_list[ri][c]: 282 | break 283 | else: 284 | h += 1 285 | if h < min_height: 286 | min_height = h 287 | 288 | down = [0, min_height] 289 | if check_move(current_block, down): 290 | draw_block_move(canvas, current_block, down) 291 | 292 | 293 | def up_float(): 294 | block_list.pop(0) 295 | new_row = ['W' for _ in range(C)] 296 | bi = random.randint(0, C - 1) 297 | new_row[bi] = '' 298 | block_list.append(new_row) 299 | 300 | 301 | def game_loop(): 302 | win.update() 303 | global current_block, up_count 304 | if up_count > UP_SPACE: 305 | up_float() 306 | draw_board(canvas, block_list) 307 | up_count = 0 308 | elif current_block is None: 309 | new_block = generate_new_block() 310 | # 新生成的俄罗斯方块需要先在生成位置绘制出来 311 | draw_block_move(canvas, new_block) 312 | current_block = new_block 313 | if not check_move(current_block, [0, 0]): 314 | messagebox.showinfo("Game Over!", "Your Score is %s" % score) 315 | win.destroy() 316 | return 317 | else: 318 | if check_move(current_block, [0, 1]): 319 | draw_block_move(canvas, current_block, [0, 1]) 320 | else: 321 | # 无法移动,记入 block_list 中 322 | save_block_to_list(current_block) 323 | current_block = None 324 | check_and_clear() 325 | up_count += 1 326 | 327 | win.after(FPS, game_loop) 328 | 329 | 330 | canvas.focus_set() # 聚焦到canvas画板对象上 331 | canvas.bind("", horizontal_move_block) 332 | canvas.bind("", horizontal_move_block) 333 | canvas.bind("", rotate_block) 334 | canvas.bind("", land) 335 | 336 | 337 | current_block = None 338 | up_count = 0 339 | 340 | win.update() 341 | win.after(FPS, game_loop) # 在FPS 毫秒后调用 game_loop方法 342 | 343 | 344 | win.mainloop() 345 | -------------------------------------------------------------------------------- /3_AI/multi_tetris.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import messagebox 3 | import random 4 | 5 | 6 | from util import * 7 | 8 | cell_size = 30 9 | C = 36 10 | R = 20 11 | height = R * cell_size 12 | width = C * cell_size 13 | 14 | GENSPEED = 4 15 | FPS = 20 # 刷新页面的毫秒间隔 16 | range_length = 7 17 | 18 | # 定义各种形状 19 | SHAPES = { 20 | "O": [(-1, -1), (0, -1), (-1, 0), (0, 0)], 21 | "S": [(-1, 0), (0, 0), (0, -1), (1, -1)], 22 | "T": [(-1, 0), (0, 0), (0, -1), (1, 0)], 23 | "I": [(0, 1), (0, 0), (0, -1), (0, -2)], 24 | "L": [(-1, 0), (0, 0), (-1, -1), (-1, -2)], 25 | "J": [(-1, 0), (0, 0), (0, -1), (0, -2)], 26 | "Z": [(-1, -1), (0, -1), (0, 0), (1, 0)], 27 | } 28 | 29 | # 定义各种形状的颜色 30 | SHAPESCOLOR = { 31 | "O": "#d25b6a", 32 | "S": "#d2835b", 33 | "T": "#e5e234", 34 | "I": "#83d05d", 35 | "L": "#2862d2", 36 | "J": "#35b1c0", 37 | "Z": "#5835c0" 38 | } 39 | 40 | 41 | class Drawer(tk.Canvas): 42 | def __init__(self, master, c, r, cell_size): 43 | self.c = c 44 | self.r = r 45 | self.cell_size = cell_size 46 | height = r * cell_size 47 | width = c * cell_size 48 | super().__init__(master, width=width, height=height) 49 | self.pack() 50 | 51 | def init(self): 52 | for ri in range(self.r): 53 | for ci in range(self.c): 54 | self.draw_cell_by_cr(ci, ri, '') 55 | 56 | def draw_cell_by_cr(self, c, r, color, kind=None): 57 | x0 = c * cell_size 58 | y0 = r * cell_size 59 | x1 = c * cell_size + cell_size 60 | y1 = r * cell_size + cell_size 61 | # 三种类型 62 | # 没有俄罗斯方块,None 63 | # 失效的俄罗斯方块,dead 64 | # 俄罗斯方块,kind 代表第几个 65 | if kind is None: 66 | self.create_rectangle(x0, y0, x1, y1, fill="#CCCCCC", outline="white", width=2) 67 | elif kind == "dead": 68 | _tag = 'r%s' % r 69 | self.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2, tags=_tag) 70 | else: 71 | _tag = 'b%s' % kind 72 | self.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2, tags=_tag) 73 | 74 | def draw_block(self, block, kind): 75 | c, r = block['cr'] 76 | shape_type = block['kind'] 77 | cell_list = block['cell_list'] 78 | for cell in cell_list: 79 | cell_c, cell_r = cell 80 | ci = cell_c + c 81 | ri = cell_r + r 82 | # 判断该位置方格在画板内部(画板外部的方格不再绘制) 83 | if 0 <= c < C and 0 <= r < R: 84 | self.draw_cell_by_cr(ci, ri, SHAPESCOLOR[shape_type], kind) 85 | 86 | def clean_by_block_id(self, block_id): 87 | _tag = 'b%s' % block_id 88 | self.delete(_tag) 89 | 90 | def clean_by_row(self,r): 91 | _tag = 'r%s' % r 92 | self.delete(_tag) 93 | 94 | 95 | class GameApp: 96 | def __init__(self, c, r): 97 | self.win = tk.Tk() 98 | 99 | height = r * cell_size 100 | width = c * cell_size 101 | 102 | self.win.geometry("%sx%s+%s+%s" % (width, height, 200, 200)) 103 | 104 | self.fps = 50 105 | self.future_board = [] 106 | self.board = [] 107 | self.block_list = {} 108 | self.block_id = 0 109 | self.running = True 110 | 111 | self.c = c 112 | self.r = r 113 | 114 | self.score = 0 115 | self.win.title("SCORES: %s" % self.score) 116 | self.drawer = Drawer(self.win, c, r, cell_size) 117 | self.drawer.init() 118 | self.init_board() 119 | self.count = 0 120 | 121 | def init_board(self): 122 | self.board=[ 123 | ['' for ci in range(self.c)] for ri in range(self.r) 124 | ] 125 | self.future_board=[ 126 | ['' for ci in range(self.c)] for ri in range(self.r) 127 | ] 128 | 129 | def check_and_clear(self): 130 | has_complete_row = False 131 | for ri in range(self.r): 132 | if check_row_complete(self.board[ri]): 133 | has_complete_row = True 134 | # 当前行可消除 135 | if ri > 0: 136 | for cur_ri in range(ri, 0, -1): 137 | self.board[cur_ri] = self.board[cur_ri-1][:] 138 | self.board[0] = ['' for j in range(C)] 139 | else: 140 | self.board[ri] = ['' for j in range(C)] 141 | 142 | self.score += 10 143 | 144 | if has_complete_row: 145 | for r in range(self.r): 146 | self.drawer.clean_by_row(r) 147 | for c in range(self.c): 148 | v = self.board[r][c] 149 | if v: 150 | self.drawer.draw_cell_by_cr(c, r, SHAPESCOLOR[v], 'dead') 151 | 152 | self.win.title("SCORES: %s" % self.score) 153 | 154 | def game_loop(self): 155 | if self.running: 156 | self.win.update() 157 | 158 | if self.count % GENSPEED == 0: 159 | self.generate_new_block() 160 | 161 | self.move_block_list() 162 | self.check_and_clear() 163 | 164 | self.count += 1 165 | 166 | self.win.after(self.fps, self.game_loop) 167 | 168 | def run(self): 169 | self.game_loop() 170 | self.win.mainloop() 171 | 172 | def check_move(self, cr, cell_list, direction): 173 | cc, cr = cr 174 | cell_list = cell_list 175 | 176 | for cell in cell_list: 177 | cell_c, cell_r = cell 178 | c = cell_c + cc + direction[0] 179 | r = cell_r + cr + direction[1] 180 | # 判断该位置是否超出左右边界,以及下边界 181 | # 一般不判断上边界,因为俄罗斯方块生成的时候,可能有一部分在上边界之上还没有出来 182 | if c < 0 or c >= self.c or r >= self.r: 183 | return False 184 | 185 | # 必须要判断r不小于0才行,具体原因你可以不加这个判断,试试会出现什么效果 186 | if r >= 0 and self.board[r][c]: 187 | return False 188 | 189 | return True 190 | 191 | def move_block_list(self): 192 | to_del_id = [] 193 | for block_id in self.block_list: 194 | block = self.block_list[block_id] 195 | self.drawer.clean_by_block_id(block_id) 196 | 197 | if move_block_by_step(block): 198 | self.drawer.draw_block(block, block_id) 199 | else: 200 | save_block_to_list(block, self.board) 201 | self.drawer.draw_block(block, 'dead') 202 | to_del_id.append(block_id) 203 | 204 | for _id in to_del_id: 205 | del self.block_list[_id] 206 | 207 | def generate_new_block(self): 208 | kind = random.choice(list(SHAPES.keys())) 209 | # 对应横纵坐标,以左上角为原点,水平向右为x轴正方向, 210 | # 竖直向下为y轴正方向,x对应横坐标,y对应纵坐标 211 | ci = random.randint(0, self.c - 1) 212 | cr = [self.c // 2, 0] 213 | cr = [ci, 0] 214 | 215 | new_block = { 216 | 'kind': kind, # 对应俄罗斯方块的类型 217 | 'cell_list': SHAPES[kind], 218 | 'cr': cr 219 | } 220 | 221 | if self.block_id ==0: 222 | new_block = { 223 | 'kind': 'Z', # 对应俄罗斯方块的类型 224 | 'cell_list': SHAPES['S'], 225 | 'cr': cr 226 | } 227 | elif self.block_id ==1: 228 | new_block = { 229 | 'kind': 'I', # 对应俄罗斯方块的类型 230 | 'cell_list': SHAPES['I'], 231 | 'cr': cr 232 | } 233 | 234 | # 使用未来的board 进行计算, 235 | self.calculate_best_place(new_block) 236 | # 计算完了再把最终位置存到未来的board 237 | save_block_to_list(new_block, self.future_board, True) 238 | check_and_clear(self.future_board) 239 | 240 | new_block['cur_step'] = 0 241 | self.block_list[self.block_id] = new_block 242 | self.drawer.draw_block(new_block, self.block_id) 243 | 244 | self.block_id += 1 245 | 246 | def check_above_empty(self, cell_list, ci, ri): 247 | for cell in cell_list: 248 | cc, cr = cell 249 | c, r = ci + cc, ri + cr 250 | for ir in range(r): 251 | if self.board[ir][c]: 252 | return False 253 | 254 | return True 255 | 256 | def get_bottom_r(self, cell_list, ci): 257 | for ri in range(R-1, -1, -1): 258 | if self.check_move((ci, ri), cell_list, (0, 0)): 259 | for dc in [0, 1, -1]: 260 | nci = ci + dc 261 | if self.check_move((nci, ri), cell_list, (0, 0)) and \ 262 | self.check_above_empty(cell_list, nci, ri): 263 | return ri, dc 264 | 265 | raise Exception("no space to place") 266 | 267 | def check_col_accessible(self, c, cell_list): 268 | for cell in cell_list: 269 | cell_c, cell_r = cell 270 | ci = cell_c + c 271 | if ci < 0 or ci > self.c-1: 272 | return False 273 | 274 | return True 275 | 276 | def calculate_best_place(self, block): 277 | shape_type = block['kind'] 278 | 279 | block_c,block_r = block['cr'] 280 | index_id = {} 281 | index_score = {} 282 | index = 0 283 | for angle in range(4): 284 | cell_list = get_cell_list_by_angle(SHAPES[shape_type], angle) 285 | left, right = get_range(block_c, self.c, range_length) 286 | for ci in range(self.c): 287 | if self.check_col_accessible(ci, cell_list): 288 | index += 1 289 | ri, dc = get_bottom_r(cell_list, self.future_board, ci) 290 | # ri, dc = self.get_bottom_r(cell_list, ci) 291 | 292 | cur_board = [row[:] for row in self.future_board] 293 | end_block = { 294 | 'kind': shape_type, 295 | 'cell_list': cell_list, 296 | 'cr': (ci, ri) 297 | } 298 | save_block_to_list(end_block, cur_board) 299 | index_id[index] = { 300 | 'cr': (ci, ri), 301 | 'dc': dc, 302 | 'angle': angle 303 | } 304 | index_score[index] = cal_ai_score(cur_board, self.c, self.r) 305 | 306 | key_name = max(index_score, key=index_score.get) 307 | 308 | best_c = index_id[key_name]['cr'][0] 309 | if abs(best_c-block_c)>range_length: 310 | left, right = get_range(best_c, self.c, range_length) 311 | change_c = random.randint(left, right) 312 | block['cr'] = (change_c, block_r) 313 | block['best'] = index_id[key_name] 314 | 315 | cal_move_order(block) 316 | 317 | 318 | game = GameApp(C, R) 319 | game.run() -------------------------------------------------------------------------------- /3_AI/tetris_by_class.py: -------------------------------------------------------------------------------- 1 | """ 2 | block字典结构: 3 | { 4 | "kind": kind, 5 | "cell_list": SHAPES[kind], 6 | "cr": [c,r], 7 | best: {'cr': (ci, ri),'dc': dc,'angle': angle} 8 | 'move_steps': [step0,step1,...](step0为列表对象存储字符串,如‘ADW’), Example:'move_steps': ['WWA', 'AAA', 'A', ''] 9 | 'cur_step': 0(cur_step描述move_steps偏移量,如move_steps[cur_step] == ‘WWA’) 10 | } 11 | 12 | board列表结构: 13 | board[ri][ci] = '' 14 | """ 15 | 16 | import tkinter as tk 17 | import random 18 | 19 | from util import * 20 | 21 | cell_size = 30 22 | C = 12 23 | R = 20 24 | height = R * cell_size 25 | width = C * cell_size 26 | 27 | FPS = 20 # 刷新页面的毫秒间隔 28 | 29 | # 定义各种形状 30 | SHAPES = { 31 | "O": [(-1, -1), (0, -1), (-1, 0), (0, 0)], 32 | "S": [(-1, 0), (0, 0), (0, -1), (1, -1)], 33 | "T": [(-1, 0), (0, 0), (0, -1), (1, 0)], 34 | "I": [(0, 1), (0, 0), (0, -1), (0, -2)], 35 | "L": [(-1, 0), (0, 0), (-1, -1), (-1, -2)], 36 | "J": [(-1, 0), (0, 0), (0, -1), (0, -2)], 37 | "Z": [(-1, -1), (0, -1), (0, 0), (1, 0)], 38 | } 39 | 40 | # 定义各种形状的颜色 41 | SHAPESCOLOR = { 42 | "O": "#d25b6a", 43 | "S": "#d2835b", 44 | "T": "#e5e234", 45 | "I": "#83d05d", 46 | "L": "#2862d2", 47 | "J": "#35b1c0", 48 | "Z": "#5835c0" 49 | } 50 | 51 | 52 | class Drawer(tk.Canvas): 53 | def __init__(self, master, c, r, cell_size): 54 | self.c = c 55 | self.r = r 56 | self.cell_size = cell_size 57 | height = R * cell_size 58 | width = C * cell_size 59 | super().__init__(master, width=width, height=height) 60 | self.pack() 61 | 62 | # 初始化(也可以用来重置),用默认色填充画板 63 | def init(self): 64 | for ri in range(self.r): 65 | for ci in range(self.c): 66 | self.draw_cell_by_cr(ci, ri, '') 67 | 68 | # 对指定位置的格子进行涂色(通过坐标及tag_kind,tag_kind决定tags) 69 | def draw_cell_by_cr(self, c, r, color, tag_kind=None): 70 | x0 = c * cell_size 71 | y0 = r * cell_size 72 | x1 = c * cell_size + cell_size 73 | y1 = r * cell_size + cell_size 74 | # 三种类型 75 | # 没有俄罗斯方块,None 76 | # 失效的俄罗斯方块,dead(已落地,不能移动的俄罗斯方块) 77 | # 俄罗斯方块,kind 代表第几个 78 | if tag_kind is None: 79 | self.create_rectangle(x0, y0, x1, y1, fill="#CCCCCC", outline="white", width=2) 80 | elif tag_kind == "dead": 81 | _tag = 'r%s' % r 82 | self.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2, tags=_tag) 83 | else: 84 | _tag = 'b%s' % tag_kind 85 | self.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2, tags=_tag) 86 | 87 | # 对指定俄罗斯方块进行涂色,颜色由block[kind]决定,kind由函数参数决定 88 | def draw_block(self, block, tag_kind): 89 | c, r = block['cr'] 90 | shape_type = block['kind'] 91 | cell_list = block['cell_list'] 92 | for cell in cell_list: 93 | cell_c, cell_r = cell 94 | ci = cell_c + c 95 | ri = cell_r + r 96 | # 判断该位置方格在画板内部(画板外部的方格不再绘制) 97 | if 0 <= c < C and 0 <= r < R: 98 | self.draw_cell_by_cr(ci, ri, SHAPESCOLOR[shape_type], tag_kind) 99 | 100 | # 按照block_id删除画板上对应对象 101 | def clean_by_block_id(self, block_id): 102 | _tag = 'b%s' % block_id 103 | self.delete(_tag) 104 | 105 | # 按照行坐标r删除画板上对应对象 106 | def clean_by_row(self, r): 107 | _tag = 'r%s' % r 108 | self.delete(_tag) 109 | 110 | 111 | class GameApp: 112 | def __init__(self, c, r): 113 | self.win = tk.Tk() 114 | 115 | self.fps = 50 # 屏幕刷新时间 116 | self.board = [] # 二维列表,存放面板上的俄罗斯方块类型信息 117 | self.block_list = {} # block_list存储所有俄罗斯方块对象,key是id, value是block 118 | self.block_id = 0 # 对应俄罗斯方块id 119 | self.running = True 120 | 121 | self.c = c 122 | self.r = r 123 | 124 | self.score = 0 # 总分数 125 | self.win.title("SCORES: %s" % self.score) 126 | self.drawer = Drawer(self.win, c, r, cell_size) 127 | self.drawer.init() 128 | self.init_board() 129 | 130 | # 类对象初始化时调用,对类变量board列表初始化 131 | def init_board(self): 132 | self.board = [ 133 | ['' for ci in range(self.c)] for ri in range(self.r) 134 | ] 135 | 136 | # 检查是否有可清理的行,有就清理 137 | def check_and_clear(self): 138 | has_complete_row = False 139 | for ri in range(self.r): 140 | if check_row_complete(self.board[ri]): 141 | has_complete_row = True 142 | # 当前行可消除 143 | if ri > 0: 144 | # 当前行为中间行,清理后,上面的行都下落一行 145 | for cur_ri in range(ri, 0, -1): 146 | self.board[cur_ri] = self.board[cur_ri-1][:] 147 | 148 | self.board[0] = ['' for j in range(C)] # 用新的空行填充首行 149 | else: 150 | self.board[ri] = ['' for j in range(C)] 151 | 152 | self.score += 10 # 清理一行,进行一次加分 153 | 154 | if has_complete_row: 155 | for r in range(self.r): 156 | self.drawer.clean_by_row(r) # 清除一行(遍历完成后会清理所有行 157 | for c in range(self.c): 158 | v = self.board[r][c] 159 | if v: 160 | self.drawer.draw_cell_by_cr(c, r, SHAPESCOLOR[v], 'dead') 161 | 162 | self.win.title("SCORES: %s" % self.score) # 更新标题显示的分数 163 | 164 | def game_loop(self): 165 | if self.running: 166 | self.win.update() 167 | 168 | if self.block_list: 169 | self.move_block_list() 170 | self.check_and_clear() 171 | 172 | else: 173 | self.generate_new_block() 174 | 175 | self.win.after(self.fps, self.game_loop) 176 | 177 | def run(self): 178 | self.game_loop() 179 | self.win.mainloop() 180 | 181 | # 对指定方块能否在指定方向进行移动进行判断,返回值为布尔类型 182 | def check_move(self, cr, cell_list, direction): 183 | cc, cr = cr 184 | cell_list = cell_list 185 | 186 | for cell in cell_list: 187 | cell_c, cell_r = cell 188 | c = cell_c + cc + direction[0] 189 | r = cell_r + cr + direction[1] 190 | # 判断该位置是否超出左右边界,以及下边界 191 | # 一般不判断上边界,因为俄罗斯方块生成的时候,可能有一部分在上边界之上还没有出来 192 | if c < 0 or c >= self.c or r >= self.r: 193 | return False 194 | 195 | # 必须要判断r不小于0才行,具体原因你可以不加这个判断,试试会出现什么效果 196 | if r >= 0 and self.board[r][c]: 197 | return False 198 | 199 | return True 200 | 201 | # 对当前block_list字典中的block进行单步移动 202 | def move_block_list(self): 203 | to_del_id = [] 204 | for block_id in self.block_list: 205 | block = self.block_list[block_id] 206 | self.drawer.clean_by_block_id(block_id) 207 | 208 | if move_block_by_step(block): 209 | self.drawer.draw_block(block, block_id) 210 | else: 211 | save_block_to_list(block, self.board) 212 | self.drawer.draw_block(block, 'dead') 213 | to_del_id.append(block_id) 214 | 215 | for _id in to_del_id: 216 | del self.block_list[_id] 217 | 218 | def generate_new_block(self): 219 | kind = random.choice(list(SHAPES.keys())) 220 | # 对应横纵坐标,以左上角为原点,水平向右为x轴正方向, 221 | # 竖直向下为y轴正方向,x对应横坐标,y对应纵坐标 222 | cr = [self.c // 2, 0] 223 | new_block = { 224 | 'kind': kind, # 对应俄罗斯方块的类型 225 | 'cell_list': SHAPES[kind], 226 | 'cr': cr 227 | } 228 | self.calculate_best_place(new_block) 229 | new_block['cur_step'] = 0 230 | 231 | self.block_list[self.block_id] = new_block 232 | self.drawer.draw_block(new_block, self.block_id) 233 | 234 | self.block_id += 1 235 | 236 | def check_above_empty(self, cell_list, ci, ri): 237 | for cell in cell_list: 238 | cc, cr = cell 239 | c, r = ci + cc, ri + cr 240 | for ir in range(r): 241 | if self.board[ir][c]: 242 | return False 243 | 244 | return True 245 | 246 | # 对指定方块在对应列能满足插入的最下方的行ri进行查找并返回,并返回对应的列偏移量dc 247 | # dc是为了处理特殊的情况,比如落地后需要左右移动一下的情况 248 | def get_bottom_r(self, cell_list, ci): 249 | for ri in range(R-1, -1, -1): 250 | if self.check_move((ci, ri), cell_list, (0, 0)): 251 | # 按顺序检查当前位置与右左位置,上方是否没有阻挡,没有则返回 252 | for dc in [0, 1, -1]: 253 | nci = ci + dc 254 | if self.check_move((nci, ri), cell_list, (0, 0)) and \ 255 | self.check_above_empty(cell_list, nci, ri): 256 | return ri, dc 257 | 258 | # raise Exception("no space to place") 259 | return -1, -1 260 | 261 | # 检查俄罗斯方块移动到列c后是否越界,返回值为布尔型,True代表可以移动,False表示不能 262 | def check_col_accessible(self, c, cell_list): 263 | for cell in cell_list: 264 | cell_c, cell_r = cell 265 | ci = cell_c + c 266 | if ci < 0 or ci > self.c-1: 267 | return False 268 | 269 | return True 270 | 271 | # 计算当前最佳位置,进行目标cr,angle,br存储,并调用cal_move_order函数对路径进行存储 272 | def calculate_best_place(self, block): 273 | shape_type = block['kind'] 274 | index_id = {} # 存储俄罗斯方块信息键值对 275 | index_score = {} # 存储俄罗斯方块权值键值对 276 | # 每一种情况都是一个index, 277 | # 理论上,一个俄罗斯方块落地情况,可分为四个角度,c-1个列, 即共 4(c-1)种情况。 278 | index = 0 279 | for angle in range(4): 280 | cell_list = get_cell_list_by_angle(SHAPES[shape_type], angle) 281 | 282 | for ci in range(C): 283 | if self.check_col_accessible(ci, cell_list): 284 | ri, dc = self.get_bottom_r(cell_list, ci) 285 | if ri<0: # 这一列放不了东西 286 | continue 287 | 288 | index += 1 289 | 290 | # 为每一种场景都复制一个board(深度拷贝), 291 | # 使得当前场景的计算不会影响到其他的 292 | cur_board = [row[:] for row in self.board] 293 | end_block = { 294 | 'kind': shape_type, 295 | 'cell_list': cell_list, 296 | 'cr': (ci, ri) 297 | } 298 | # 将该情景下的俄罗斯方块存储至新建的board中 299 | save_block_to_list(end_block,cur_board) 300 | index_id[index] = { 301 | 'cr': (ci, ri), 302 | 'dc': dc, 303 | 'angle': angle 304 | } 305 | # 计算出当前情况的board权值,并存储到index_score 306 | index_score[index] = cal_ai_score(cur_board, self.c, self.r) 307 | 308 | if index == 0: # 没位置可以放,游戏结束 309 | self.running = False 310 | return 311 | 312 | try: 313 | key_name = max(index_score, key=index_score.get) # 得出最高分数的列表角标 314 | 315 | block['best'] = index_id[key_name] # 对最佳位置坐标,偏移量,角度flag进行存储 316 | cal_move_order(block) # 计算到达最佳位置的路径 317 | 318 | key_name = max(index_score, key=index_score.get) 319 | 320 | block['best'] = index_id[key_name] 321 | cal_move_order(block) 322 | except Exception as e: 323 | self.running = False 324 | print("game over with e:", e) 325 | print("score:", self.score) 326 | 327 | 328 | game = GameApp(C, R) 329 | game.run() --------------------------------------------------------------------------------