当前位置: 首页 > news >正文

用 Python 实现经典的 2048 游戏:一步步带你打造属于你的小游戏!

用 Python 实现经典的 2048 游戏:一步步带你打造属于你的小游戏!(结尾附完整代码)

简介

2048 是一个简单而又令人上瘾的数字拼图游戏。玩家通过滑动方块使相同数字的方块合并,目标是创造出数字 2048!在这篇博客中,我们将用 Python 的 Tkinter 库 从零开始实现这款游戏,涵盖从界面设计到逻辑实现的每一个细节,帮助你全面了解背后的开发思路。

游戏特点

  • 经典玩法:滑动合并相同数字,尽可能达到 2048。
  • 随机生成新数字:每次滑动后随机生成 2 或 4。
  • 直观的图形界面:使用 Tkinter 绘制游戏网格与方块,带来清晰的视觉效果。

开发环境准备

在开始之前,确保你的环境已安装 Python,并包含以下必要库:

pip install tk

Tkinter 是 Python 内置的 GUI 工具包,因此大多数环境无需额外安装。 

项目实现步骤

我们将按照以下步骤构建游戏:

  1. 设计网格界面
  2. 实现核心逻辑
  3. 绑定键盘事件
  4. 添加游戏结束检测
  5. 优化用户体验

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 游戏&#xff1a;一步步带你打造属于你的小游戏&#xff01;&#xff08;结尾附完整代码&#xff09; 简介 2048 是一个简单而又令人上瘾的数字拼图游戏。玩家通过滑动方块使相同数字的方块合并&#xff0c;目标是创造出数字 2048&#xff01;在这篇…...

Vue vs. React:两大前端框架的深度对比与分析(一)

前言 在当今快速发展的前端领域中&#xff0c;Vue和React作为两个备受瞩目的前端框架&#xff0c;已经成为许多开发者的首选。这两个框架凭借其出色的设计和强大的功能&#xff0c;在构建现代化、高效性能的Web应用方面扮演着重要角色。 Vue和React都以其独特的特点吸引了众多开…...

React 进阶深入理解核心概念与高阶实践

在上一节中&#xff0c;我们学习了 React 的基础知识&#xff0c;包括组件、状态管理和基本操作。接下来&#xff0c;我们将进一步探索 React 的高级功能和实战技巧&#xff0c;例如 组件间通信、高阶组件、Context API、React Router 等。这些内容将帮助你构建更复杂、功能更丰…...

Linux shell的七大功能 ---自动补齐、管道机制、别名

1、自动补齐---TAB 输入命令的前几个字符&#xff0c;按下tab键&#xff0c;会自动补齐完整的字符&#xff0c;若有多个命令、文件或目录的前几个字符相同&#xff0c;按下tab将会全部列举出来 2、管道机制---| 例如&#xff1a;ls -- help |more 将有关ls的帮助内容传递给“|…...

XML 在线格式化 - 加菲工具

XML 在线格式化 打开网站 加菲工具 选择“XML 在线格式化” 输入XML&#xff0c;点击左上角的“格式化”按钮 得到格式化后的结果...

java_多态的应用

多态数组 应用实例:现有一个继承结构如下&#xff1a;要求创建 1 个 Person 对象、2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中&#xff0c;并调用每个对象 代码 Person类 package com.hspedu.poly_.polyarr_;import javax.swing.*;/*** author:寰愬悏瓒&#xf…...

Python+OpenCV系列:模版匹配

文章目录 1. 模板匹配基本原理2. cv2.matchTemplate() 函数函数原型&#xff1a; 3. 模板匹配步骤4. 单目标模板匹配示例5. 多目标模板匹配多目标模板匹配示例代码解析&#xff1a; 6. 多模板匹配多模板匹配示例代码解析 7. 总结 模板匹配是一种在图像中寻找模板的位置的方法。…...

【从零开始入门unity游戏开发之——C#篇10】循环结构——while、do-while、for、foreach的使用

文章目录 一、while 循环1、语法&#xff1a;2、示例&#xff1a; 二、 do-while 循环1、语法&#xff1a;2、示例&#xff1a; 三、for 循环1、语法&#xff1a;2、示例&#xff1a; 四、foreach 循环1、语法&#xff1a;2、示例&#xff1a; 五、总结对比六、注意事项七、使用…...

Spring Boot项目使用虚拟线程

Spring Boot项目启用虚拟线程 开始基本使用先写一个测试方法通过springboot配置项开启虚拟线程 目前存在的问题 开始 虚拟线程正式发布是在JDK21&#xff0c;对于Spring Boot版本选择3以上。 基本使用 关于虚拟线程本身的使用&#xff0c;之前已经介绍过。这里要说的是直接将…...

实现SpringBoot项目嵌入其他项目

很多时候我们需要在项目里面嵌入其他项目或者被其他项目嵌入&#xff0c;如我们开发一个开源项目b&#xff0c;用户需要在自己的项目a嵌入b项目&#xff0c;使用b项目的功能&#xff0c;而且要实现a项目工作最小化&#xff0c;最好实现引入即用。 1.定义b项目的自定义配置 …...

朗致面试---IOS/安卓/Java/架构师

朗致面试---IOS/安卓/Java/架构师 一、面试概况二、总结三、算法题目参考答案 一、面试概况 一共三轮面试&#xff1a; 第一轮是逻辑行测&#xff0c;25道题目&#xff0c;类似于公务员考试题目&#xff0c;要求90分钟内完成。第二轮是技术面试&#xff0c;主要是做一些数据结…...

数字信号处理:FIR滤波器

FIR&#xff08;Finite Impulse Response&#xff0c;有限脉冲响应&#xff09;滤波器是一种数字滤波器&#xff0c;其输出信号是输入信号的加权线性组合。FIR滤波器以其线性相位特性和易于设计的优势&#xff0c;广泛应用于信号处理、通信、音频处理等领域。 FIR滤波器的特点…...

鲲鹏麒麟安装Kafka-v1.1.1

因项目需要在鲲鹏麒麟服务器上安装Kafka v1.1.1&#xff0c;因此这里将安装配置过程记录下来。 环境说明 # 查看系统相关详细信息 [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文件&#xff0c;代码如下&#xff1a; // 操作日志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 加载模型 三、共赴算力时代 一、平台简介 昇思大模型平台&#xff0c;就像是AI学习者和开发者的超级基地&#xff0c;这里不仅提供丰富的项目、模型和大模型体验&#xff0c;还有一大堆经典数据集任你挑。…...

Vue 让视图区域滑到指定位置、回到顶部

滑倒指定位置&#xff1a;获取指定的dom&#xff0c;然后用scrollIntoView使dom出现在视图区域 回到顶部&#xff1a;操作父级dom的scrollTop 0&#xff0c;让该父级下的列表回到顶部 代码如下 <template><div class"testDemo"><div><el-bu…...

EasyGBS点对点穿透P2P远程访问技术在安防视频监控中的应用

随着信息技术的快速发展&#xff0c;安防视频监控系统在公共安全领域的应用变得越来越广泛。传统的视频监控系统多依赖于中心服务器进行视频流的集中处理和分发&#xff0c;这不仅增加了网络带宽的负担&#xff0c;还可能成为系统性能瓶颈。为了解决这些问题&#xff0c;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——图片矫正

图像矫正 图像矫正的原理是透视变换&#xff0c;下面来介绍一下透视变换的概念。 听名字有点熟&#xff0c;我们在图像旋转里接触过仿射变换&#xff0c;知道仿射变换是把一个二维坐标系转换到另一个二维坐标系的过程&#xff0c;转换过程坐标点的相对位置和属性不发生变换&a…...

浅谈 React Hooks

React Hooks 是 React 16.8 引入的一组 API&#xff0c;用于在函数组件中使用 state 和其他 React 特性&#xff08;例如生命周期方法、context 等&#xff09;。Hooks 通过简洁的函数接口&#xff0c;解决了状态与 UI 的高度解耦&#xff0c;通过函数式编程范式实现更灵活 Rea…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩

目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

python/java环境配置

环境变量放一起 python&#xff1a; 1.首先下载Python Python下载地址&#xff1a;Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个&#xff0c;然后自定义&#xff0c;全选 可以把前4个选上 3.环境配置 1&#xff09;搜高级系统设置 2…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

Caliper 负载(Workload)详细解析

Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...

LangFlow技术架构分析

&#x1f527; LangFlow 的可视化技术栈 前端节点编辑器 底层框架&#xff1a;基于 &#xff08;一个现代化的 React 节点绘图库&#xff09; 功能&#xff1a; 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...