用 Python 实现经典的 2048 游戏:一步步带你打造属于你的小游戏!
用 Python 实现经典的 2048 游戏:一步步带你打造属于你的小游戏!(结尾附完整代码)
简介
2048 是一个简单而又令人上瘾的数字拼图游戏。玩家通过滑动方块使相同数字的方块合并,目标是创造出数字 2048!在这篇博客中,我们将用 Python 的 Tkinter 库 从零开始实现这款游戏,涵盖从界面设计到逻辑实现的每一个细节,帮助你全面了解背后的开发思路。
游戏特点
- 经典玩法:滑动合并相同数字,尽可能达到 2048。
- 随机生成新数字:每次滑动后随机生成 2 或 4。
- 直观的图形界面:使用 Tkinter 绘制游戏网格与方块,带来清晰的视觉效果。
开发环境准备
在开始之前,确保你的环境已安装 Python,并包含以下必要库:
pip install tk
Tkinter 是 Python 内置的 GUI 工具包,因此大多数环境无需额外安装。
项目实现步骤
我们将按照以下步骤构建游戏:
- 设计网格界面
- 实现核心逻辑
- 绑定键盘事件
- 添加游戏结束检测
- 优化用户体验
1. 设计网格界面
首先,我们定义游戏的基本参数,包括网格大小、方块的尺寸、间距和颜色等。随后,通过 Tkinter 创建一个网格界面,每个格子将用矩形和文本元素表示。
import tkinter as tk
from tkinter import messagebox
import random# 常量定义
GRID_SIZE = 4 # 网格为 4x4
TILE_SIZE = 100 # 每个方块的像素大小
PADDING = 10 # 方块之间的间距
BACKGROUND_COLOR = "#92877d"
TILE_COLORS = {0: "#9e948a", 2: "#eee4da", 4: "#ede0c8",8: "#f2b179", 16: "#f59563", 32: "#f67c5f",64: "#f65e3b", 128: "#edcf72", 256: "#edcc61",512: "#edc850", 1024: "#edc53f", 2048: "#edc22e",
}# 初始化游戏界面
class Game2048:def __init__(self, master):self.master = masterself.master.title("2048")self.master.geometry(f"{GRID_SIZE * TILE_SIZE + PADDING * (GRID_SIZE + 1)}x{GRID_SIZE * TILE_SIZE + PADDING * (GRID_SIZE + 1)}")self.master.resizable(False, False)self.init_grid()self.reset_game()def init_grid(self):"""创建 4x4 网格"""self.canvas = tk.Canvas(self.master, bg=BACKGROUND_COLOR, bd=0, highlightthickness=0)self.canvas.pack(fill=tk.BOTH, expand=True)self.tiles = []for i in range(GRID_SIZE):row = []for j in range(GRID_SIZE):x1 = PADDING + j * (TILE_SIZE + PADDING)y1 = PADDING + i * (TILE_SIZE + PADDING)x2 = x1 + TILE_SIZEy2 = y1 + TILE_SIZErect = self.canvas.create_rectangle(x1, y1, x2, y2, fill=TILE_COLORS[0], outline="")text = self.canvas.create_text(x1 + TILE_SIZE // 2, y1 + TILE_SIZE // 2, text="", font=("Helvetica", 30), fill="#776e65")row.append((rect, text))self.tiles.append(row)
运行效果:运行后你会看到一个 4x4 的灰色网格,每个格子代表一个方块。
2. 实现核心逻辑
游戏的逻辑主要包括:
- 压缩和合并数字:将相邻的数字合并。
- 滑动方向操作:实现上下左右滑动功能。
- 随机生成新数字:在空格中随机生成 2 或 4。
压缩与合并
每次滑动操作可以拆分为两部分:压缩(去掉空格) 和 合并(合并相邻的相同数字)。
def compress(row):"""将行向左压缩,去掉 0"""new_row = [num for num in row if num != 0]new_row += [0] * (GRID_SIZE - len(new_row))return new_rowdef merge(row):"""合并相邻相同的数字"""for i in range(len(row) - 1):if row[i] == row[i + 1] and row[i] != 0:row[i] *= 2row[i + 1] = 0return row
滑动操作
根据方向执行不同操作(上下左右)。
def move_left(grid):"""左移操作"""new_grid = []for row in grid:compressed_row = compress(row)merged_row = merge(compressed_row)new_grid.append(compress(merged_row))return new_grid
随机生成数字
每次滑动后随机选择一个空格,填入 2 或 4。
def spawn_new_tile(grid):"""随机生成一个新的数字方块(2 或 4)"""empty_cells = [(i, j) for i in range(GRID_SIZE) for j in range(GRID_SIZE) if grid[i][j] == 0]if empty_cells:i, j = random.choice(empty_cells)grid[i][j] = 2 if random.random() < 0.9 else 4
3. 绑定键盘事件
为了让游戏动起来,我们需要捕获用户的按键(上下左右方向键),并根据按键更新网格状态。
def key_press(event):"""处理键盘输入"""key = event.keysymif key in ["Up", "Down", "Left", "Right"]:if key == "Up":game.grid = move_left(rotate_left(game.grid))game.grid = rotate_right(game.grid)elif key == "Down":game.grid = move_left(rotate_right(game.grid))game.grid = rotate_left(game.grid)elif key == "Left":game.grid = move_left(game.grid)elif key == "Right":game.grid = move_left(reverse_rows(game.grid))game.grid = reverse_rows(game.grid)spawn_new_tile(game.grid)game.update_ui()if is_game_over(game.grid):messagebox.showinfo("Game Over", "No more moves left!")
4. 添加游戏结束检测
当所有格子都被填满且没有可合并的数字时,游戏结束。
def is_game_over(grid):"""判断是否还有有效操作"""for i in range(GRID_SIZE):for j in range(GRID_SIZE):if grid[i][j] == 0: # 空格存在return Falseif j + 1 < GRID_SIZE and grid[i][j] == grid[i][j + 1]: # 横向可合并return Falseif i + 1 < GRID_SIZE and grid[i][j] == grid[i + 1][j]: # 纵向可合并return Falsereturn True
5. 完整代码
将上述代码整合的完整代码如下:运行后即可得到完整的 2048 游戏!
import tkinter as tk
from tkinter import messagebox
import random# 常量定义
GRID_SIZE = 4
TILE_SIZE = 100
PADDING = 10
BACKGROUND_COLOR = "#92877d"
TILE_COLORS = {0: "#9e948a", 2: "#eee4da", 4: "#ede0c8",8: "#f2b179", 16: "#f59563", 32: "#f67c5f",64: "#f65e3b", 128: "#edcf72", 256: "#edcc61",512: "#edc850", 1024: "#edc53f", 2048: "#edc22e",
}class Game2048:def __init__(self, master):self.master = masterself.master.title("2048")self.master.geometry(f"{GRID_SIZE * TILE_SIZE + PADDING * (GRID_SIZE + 1)}x{GRID_SIZE * TILE_SIZE + PADDING * (GRID_SIZE + 1)}")self.master.resizable(False, False)self.init_grid()self.reset_game()def init_grid(self):"""初始化网格界面"""self.canvas = tk.Canvas(self.master, bg=BACKGROUND_COLOR, bd=0, highlightthickness=0)self.canvas.pack(fill=tk.BOTH, expand=True)self.tiles = []for i in range(GRID_SIZE):row = []for j in range(GRID_SIZE):x1 = PADDING + j * (TILE_SIZE + PADDING)y1 = PADDING + i * (TILE_SIZE + PADDING)x2 = x1 + TILE_SIZEy2 = y1 + TILE_SIZErect = self.canvas.create_rectangle(x1, y1, x2, y2, fill=TILE_COLORS[0], outline="")text = self.canvas.create_text(x1 + TILE_SIZE // 2, y1 + TILE_SIZE // 2, text="", font=("Helvetica", 30), fill="#776e65")row.append((rect, text))self.tiles.append(row)def reset_game(self):"""重置游戏状态"""self.grid = [[0] * GRID_SIZE for _ in range(GRID_SIZE)]self.spawn_new_tile()self.spawn_new_tile()self.update_ui()def spawn_new_tile(self):"""随机生成一个新的数字方块(2 或 4)"""empty_cells = [(i, j) for i in range(GRID_SIZE) for j in range(GRID_SIZE) if self.grid[i][j] == 0]if empty_cells:i, j = random.choice(empty_cells)self.grid[i][j] = 2 if random.random() < 0.9 else 4def update_ui(self):"""更新界面显示"""for i in range(GRID_SIZE):for j in range(GRID_SIZE):value = self.grid[i][j]rect, text = self.tiles[i][j]self.canvas.itemconfig(rect, fill=TILE_COLORS.get(value, TILE_COLORS[2048]))self.canvas.itemconfig(text, text=str(value) if value != 0 else "")def compress(row):"""将行向左压缩,去掉 0"""new_row = [num for num in row if num != 0]new_row += [0] * (GRID_SIZE - len(new_row))return new_rowdef merge(row):"""合并相邻相同的数字"""for i in range(len(row) - 1):if row[i] == row[i + 1] and row[i] != 0:row[i] *= 2row[i + 1] = 0return rowdef move_left(grid):"""左移操作"""new_grid = []for row in grid:compressed_row = compress(row)merged_row = merge(compressed_row)new_grid.append(compress(merged_row))return new_griddef rotate_right(grid):"""矩阵顺时针旋转 90°"""return [list(row) for row in zip(*grid[::-1])]def rotate_left(grid):"""矩阵逆时针旋转 90°"""return [list(row) for row in zip(*grid)][::-1]def reverse_rows(grid):"""矩阵水平翻转"""return [row[::-1] for row in grid]def is_game_over(grid):"""判断是否还有有效操作"""for i in range(GRID_SIZE):for j in range(GRID_SIZE):if grid[i][j] == 0: # 空格存在return Falseif j + 1 < GRID_SIZE and grid[i][j] == grid[i][j + 1]: # 横向可合并return Falseif i + 1 < GRID_SIZE and grid[i][j] == grid[i + 1][j]: # 纵向可合并return Falsereturn Truedef key_press(event):"""处理键盘输入"""key = event.keysymif key in ["Up", "Down", "Left", "Right"]:if key == "Up":game.grid = move_left(rotate_left(game.grid))game.grid = rotate_right(game.grid)elif key == "Down":game.grid = move_left(rotate_right(game.grid))game.grid = rotate_left(game.grid)elif key == "Left":game.grid = move_left(game.grid)elif key == "Right":game.grid = move_left(reverse_rows(game.grid))game.grid = reverse_rows(game.grid)game.spawn_new_tile()game.update_ui()if is_game_over(game.grid):messagebox.showinfo("Game Over", "No more moves left!")root = tk.Tk()
game = Game2048(root)
root.bind("<Key>", key_press)
root.mainloop()
通过本篇博客,我们从头实现了经典的 2048 游戏。不仅让你掌握了 Tkinter 界面开发,还深入理解了游戏逻辑的实现过程。如果你喜欢这篇博客,请分享给更多的 Python 爱好者吧!


相关文章:
用 Python 实现经典的 2048 游戏:一步步带你打造属于你的小游戏!
用 Python 实现经典的 2048 游戏:一步步带你打造属于你的小游戏!(结尾附完整代码) 简介 2048 是一个简单而又令人上瘾的数字拼图游戏。玩家通过滑动方块使相同数字的方块合并,目标是创造出数字 2048!在这篇…...
Vue vs. React:两大前端框架的深度对比与分析(一)
前言 在当今快速发展的前端领域中,Vue和React作为两个备受瞩目的前端框架,已经成为许多开发者的首选。这两个框架凭借其出色的设计和强大的功能,在构建现代化、高效性能的Web应用方面扮演着重要角色。 Vue和React都以其独特的特点吸引了众多开…...
React 进阶深入理解核心概念与高阶实践
在上一节中,我们学习了 React 的基础知识,包括组件、状态管理和基本操作。接下来,我们将进一步探索 React 的高级功能和实战技巧,例如 组件间通信、高阶组件、Context API、React Router 等。这些内容将帮助你构建更复杂、功能更丰…...
Linux shell的七大功能 ---自动补齐、管道机制、别名
1、自动补齐---TAB 输入命令的前几个字符,按下tab键,会自动补齐完整的字符,若有多个命令、文件或目录的前几个字符相同,按下tab将会全部列举出来 2、管道机制---| 例如:ls -- help |more 将有关ls的帮助内容传递给“|…...
XML 在线格式化 - 加菲工具
XML 在线格式化 打开网站 加菲工具 选择“XML 在线格式化” 输入XML,点击左上角的“格式化”按钮 得到格式化后的结果...
java_多态的应用
多态数组 应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象、2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象 代码 Person类 package com.hspedu.poly_.polyarr_;import javax.swing.*;/*** author:寰愬悏瓒…...
Python+OpenCV系列:模版匹配
文章目录 1. 模板匹配基本原理2. cv2.matchTemplate() 函数函数原型: 3. 模板匹配步骤4. 单目标模板匹配示例5. 多目标模板匹配多目标模板匹配示例代码解析: 6. 多模板匹配多模板匹配示例代码解析 7. 总结 模板匹配是一种在图像中寻找模板的位置的方法。…...
【从零开始入门unity游戏开发之——C#篇10】循环结构——while、do-while、for、foreach的使用
文章目录 一、while 循环1、语法:2、示例: 二、 do-while 循环1、语法:2、示例: 三、for 循环1、语法:2、示例: 四、foreach 循环1、语法:2、示例: 五、总结对比六、注意事项七、使用…...
Spring Boot项目使用虚拟线程
Spring Boot项目启用虚拟线程 开始基本使用先写一个测试方法通过springboot配置项开启虚拟线程 目前存在的问题 开始 虚拟线程正式发布是在JDK21,对于Spring Boot版本选择3以上。 基本使用 关于虚拟线程本身的使用,之前已经介绍过。这里要说的是直接将…...
实现SpringBoot项目嵌入其他项目
很多时候我们需要在项目里面嵌入其他项目或者被其他项目嵌入,如我们开发一个开源项目b,用户需要在自己的项目a嵌入b项目,使用b项目的功能,而且要实现a项目工作最小化,最好实现引入即用。 1.定义b项目的自定义配置 …...
朗致面试---IOS/安卓/Java/架构师
朗致面试---IOS/安卓/Java/架构师 一、面试概况二、总结三、算法题目参考答案 一、面试概况 一共三轮面试: 第一轮是逻辑行测,25道题目,类似于公务员考试题目,要求90分钟内完成。第二轮是技术面试,主要是做一些数据结…...
数字信号处理:FIR滤波器
FIR(Finite Impulse Response,有限脉冲响应)滤波器是一种数字滤波器,其输出信号是输入信号的加权线性组合。FIR滤波器以其线性相位特性和易于设计的优势,广泛应用于信号处理、通信、音频处理等领域。 FIR滤波器的特点…...
鲲鹏麒麟安装Kafka-v1.1.1
因项目需要在鲲鹏麒麟服务器上安装Kafka v1.1.1,因此这里将安装配置过程记录下来。 环境说明 # 查看系统相关详细信息 [roottest kafka_2.12-1.1.1]# uname -a Linux test.novalocal 4.19.148 #1 SMP Mon Oct 5 22:04:46 EDT 2020 aarch64 aarch64 aarch64 GNU/Li…...
群控系统服务端开发模式-应用开发-操作记录功能开发
一、开放路由 在根目录下route文件夹下修改app.php文件,代码如下: // 操作日志Route::get(token/get_list,permission.Token/getList);// 获取操作日志列表Route::post(token/get_all,permission.Token/getAll);// 获取操作日志所有数据Route::post(toke…...
昇思25天学习打卡营第33天|共赴算力时代
文章目录 一、平台简介二、深度学习模型2.1 处理数据集2.2 模型训练2.3 加载模型 三、共赴算力时代 一、平台简介 昇思大模型平台,就像是AI学习者和开发者的超级基地,这里不仅提供丰富的项目、模型和大模型体验,还有一大堆经典数据集任你挑。…...
Vue 让视图区域滑到指定位置、回到顶部
滑倒指定位置:获取指定的dom,然后用scrollIntoView使dom出现在视图区域 回到顶部:操作父级dom的scrollTop 0,让该父级下的列表回到顶部 代码如下 <template><div class"testDemo"><div><el-bu…...
EasyGBS点对点穿透P2P远程访问技术在安防视频监控中的应用
随着信息技术的快速发展,安防视频监控系统在公共安全领域的应用变得越来越广泛。传统的视频监控系统多依赖于中心服务器进行视频流的集中处理和分发,这不仅增加了网络带宽的负担,还可能成为系统性能瓶颈。为了解决这些问题,P2P&am…...
Android 使用 Gson + OkHttp 实现 API 的常规使用(个人心得)
学习笔记 一、依赖和权限的添加 网络权限: 在 Android 中进行网络请求时,必须声明权限,确保应用具有访问互联网的能力。 <uses-permission android:name="android.permission.INTERNET"/> 依赖项: 确保在 build.gradle 中添加以下依赖: dependencies …...
WPF+MVVM案例实战与特效(三十九)- 深度剖析一个弧形进度条的实现
文章目录 1、使用 Path 结合 ArcSegment 绘制圆弧1、属性解读2、静态圆弧3、动态圆弧4、运行效果5、圆弧两端点的形状2、总结1、使用 Path 结合 ArcSegment 绘制圆弧 1、属性解读 Path 是 WPF 中的一个标记元素,用于绘制复杂的几何路径形状,而 ArcSegment 用于描述 Path 中…...
opencv——图片矫正
图像矫正 图像矫正的原理是透视变换,下面来介绍一下透视变换的概念。 听名字有点熟,我们在图像旋转里接触过仿射变换,知道仿射变换是把一个二维坐标系转换到另一个二维坐标系的过程,转换过程坐标点的相对位置和属性不发生变换&a…...
龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)
升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点,但无自动故障转移能力,Master宕机后需人工切换,期间消息可能无法读取。Slave仅存储数据,无法主动升级为Master响应请求ÿ…...
Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...

