C++实现图形化2048小游戏

article/2025/6/7 19:32:09

目录

  • 一、游戏规则
  • 二、步骤实现
  • (一) SDL库的安装
  • (二) 初始化游戏界面
    • 1. 后台数字模型
    • 2 显示模型
      • 2.1 SDL库的使用
        • 2.1.1 窗口渲染
        • 2.1.2 矩形绘制
      • 2.2 SDL-ttf库的使用
        • 2.2.1 设置字体属性
        • 2.2.2 创建纹理图层
        • 2.2.3 绘制文字
  • (三) 随机生成2个数字(2或4)
  • (四) 捕获用户方向键操作
    • 1.合并运算函数
  • (五) 更新游戏界面
  • (六) 判断游戏结束
    • 1.判断获胜
    • 2.判断失败
  • 三、总结
  • 四、源码下载

在这里插入图片描述

一、游戏规则

2048游戏开始时,棋盘内会随机出现2到3个数字,这2到3个数字通常是2或4。玩家可以通过方向键来控制棋盘上的数字方块向该方向移动,每次相同的数字方块会进行合并,新的数值为原来2个方块数值之和。游戏的目标是得出最高数2048这个数,一旦所有格子都被填满且未达到2048这个目标,游戏就失败了。

游戏的设计思路:

  1. 初始化游戏界面
  2. 随机生成2个数字(2或4)
  3. 捕获用户方向键操作
  4. 更新游戏界面
  5. 判断游戏结束

二、步骤实现

(一) SDL库的安装

需要SDL库、SDL-ttf库,在linux系统中安装命令apt install libsdl2-devapt install libsdl2-ttf-dev即可安装。

(二) 初始化游戏界面

SDL库使用的顺序是生成窗口 → \to 生成渲染器 → \to 生成纹理 → \to 纹理拷贝至渲染器 → \to 显示。

SDL-ttf库使用的顺序是打开字库 → \to 设置字体属性 → \to 创建表面 → \to 创建纹理。

我希望游戏的格子是4X4的,金黄和橙色相间,而格子里的不同数字也使用不同的颜色渲染(初步设计成3色)。

知识点:类、类外函数定义。

class Chess2048 {private:int gameBoard[16] = {}; //4X4棋盘格SDL_Renderer * render; // 内置窗口渲染器指针TTF_Font *font; //内置字体指针TextSize textSize[11]; //2、4、8...2048的字体尺寸SDL_Texture *texture[11]; //2、4、8...2048的字体纹理指针SDL_Color bgColor[2] = {{255,255,0,255}, {255,180,0,255}}; //金黄、橙SDL_Color fgColor[3] = {{25, 160, 0, 255}, {85, 200, 200, 255}, {55, 0, 100, 255}}; //绿、天蓝、紫public:Chess2048(SDL_Renderer * , TTF_Font *); //构造~Chess2048(); //析构//棋盘移动操作bool up();bool down();bool left();bool right();//判断是否满格bool full();//判断是否胜利bool win();//显示函数void show(int);
};

1. 后台数字模型

如上段代码,其中int gameBoard[16] = {}; 就是后台的棋盘数字4X4的模型,其他的成员都跟图像显示有关。

2 显示模型

知识点:SDL库的使用、SDL-ttf库的使用。

2.1 SDL库的使用

2.1.1 窗口渲染

在main函数中进行SDL初始化操作,如下:

//初始化SDL成视频模式
SDL_Init(SDL_INIT_VIDEO);
//初始化窗口,位置(x,y)默认,尺寸800X800,窗口为显示模式
SDL_Window *window = SDL_CreateWindow("2048小游戏", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 4*DIM, 4*DIM, SDL_WINDOW_SHOWN);
if (!window) {cout << "窗口生成异常" << endl;return 1;
}
//初始化窗口渲染器,相当于画布,-1表示默认的渲染设备,使用软件渲染
SDL_Renderer *render = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE);
if (!render) {cout << "渲染器生成异常" << endl;return 1;
}

生成了窗口渲染器后传递给棋盘类的构造函数,还有1个参数ttf,在2.2部分会讲:
Chess2048 chess = Chess2048(render, ttf);
这里另外涉及1个全局变量DIM,设置成你想要的尺寸就行,我这里定义为200,代表棋盘上1个格子的边长。

2.1.2 矩形绘制

棋盘总共是4X4个格子,所以要绘制16个格子,而每个格子的颜色是相间的,所以这里用到了SDL_Color bgColor[2] = {{255,255,0,255}, {255,180,0,255}}; //金黄、橙这个变量。矩形绘制是在show成员函数中实现的:

//param:interval ms刷新频率
void Chess2048::show(int interval) {//设置背景色白色SDL_SetRenderDrawColor(render, 255, 255, 255, 255); //清空渲染器SDL_RenderClear(render);for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {// cout << this->gameBoard[i*4+j] << " ";//命令行显示//绘制格子SDL_Rect gridRect = {j*DIM, i*DIM, DIM, DIM}; //格子矩形SDL_SetRenderDrawColor(this->render, this->bgColor[(i+j)%2].r, this->bgColor[(i+j)%2].g, this->bgColor[(i+j)%2].b, this->bgColor[(i+j)%2].a);SDL_RenderFillRect(this->render, &gridRect);//绘制数字部分//...}// cout << endl; //命令行显示}SDL_RenderPresent(this->render); //显示SDL_Delay(interval); //延时
}

2.2 SDL-ttf库的使用

在main函数中进行SDL-ttf初始化操作,并传递给棋盘类的构造函数,其中相应的字体我提前拷贝到了程序文件夹,否则就会找不到字体:

//初始化字体
TTF_Init();
//打开字库
TTF_Font *ttf = TTF_OpenFont("NotoSansTamil-Regular.ttf", 50);
if (!ttf) {cout << "字体打开失败" << endl;return 1;
}
Chess2048 chess = Chess2048(render, ttf);
2.2.1 设置字体属性

在构造函数中设置字体加粗属性,当然还有其他属性比如描边等,大家可以自行探索:
TTF_SetFontStyle(font, TTF_STYLE_BOLD);

2.2.2 创建纹理图层

同样是在构造函数中完成的,因为程序一直要用到2、4、8…2048这11个数字,所以我把它设置成了成员数组保存,而我又需要3种颜色,于是随便设置了3种颜色:

private:TextSize textSize[11]; //2、4、8...2048的字体尺寸SDL_Texture *texture[11]; //2、4、8...2048的字体纹理指针SDL_Color fgColor[3] = {{25, 160, 0, 255}, {85, 200, 200, 255}, {55, 0, 100, 255}}; //绿、天蓝、紫

而字体纹理是由字体表面得来的,于是接下来就通过构建字体表面进而构建出纹理来:

Chess2048::Chess2048(SDL_Renderer * render, TTF_Font *font): render(render), font(font) {//在随机的位置随机生成2个数2或4//...//创建字体表面,保存字体纹理for (int i = 0; i < 11; i++) {string s = to_string(((int)pow(2, i+1))); //2、4、8...2048SDL_Surface * textSurface = TTF_RenderUTF8_Solid(font, s.c_str(), this->fgColor[i % 3]);this->textSize[i].w = textSurface->w; this->textSize[i].h = textSurface->h;this->texture[i] = SDL_CreateTextureFromSurface(render, textSurface);SDL_FreeSurface(textSurface); //释放字体表面}
}
2.2.3 绘制文字

同矩形绘制一样,在show成员函数中实现:

void Chess2048::show(int interval) {//设置背景色白色SDL_SetRenderDrawColor(render, 255, 255, 255, 255); //清空渲染器SDL_RenderClear(render);for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {// cout << this->gameBoard[i*4+j] << " ";//命令行显示//绘制格子部分//...//绘制不为0的数字int num = this->gameBoard[i*4+j];if (num) {int index = (int)log2(num) - 1;SDL_Rect textRect = {j*DIM+DIM/2 - this->textSize[index].w/2, i*DIM+DIM/2 - this->textSize[index].h/2, this->textSize[index].w, this->textSize[index].h}; //字 SDL_RenderCopy(this->render, this->texture[index], nullptr, &textRect); //显示}}// cout << endl; //命令行显示}SDL_RenderPresent(this->render);SDL_Delay(interval);
}

(三) 随机生成2个数字(2或4)

知识点:伪随机数生成。

  1. 主要是在构造函数是实现的,能产生2到3个随机的2或4:
Chess2048::Chess2048(SDL_Renderer * render, TTF_Font *font): render(render), font(font) {//在随机的位置随机生成2个数2或4srand(time(NULL));this->gameBoard[rand() % 16] = (rand() % 2 + 1) * 2;this->gameBoard[rand() % 16] = (rand() % 2 + 1) * 2;//随机生成第3个数if (rand() % 2) {this->gameBoard[rand() % 16] = (rand() % 2 + 1) * 2;}//构造字体部分//...
}

这里需要提醒的是srand(time(NULL));必须要,不然光靠rand()函数是不能生成伪随机数的。

  1. 其次在移动棋盘过程中也会随机出现不多于2个的数字2或4:
//如果发生了移动则在空余位置生成随机的2或4
if (moved) {int pos = rand() % 16;if (!this->gameBoard[pos]) this->gameBoard[pos] = (rand() % 2 + 1) * 2;pos = rand() % 16;if (!this->gameBoard[pos]) this->gameBoard[pos] = (rand() % 2 + 1) * 2;
}

(四) 捕获用户方向键操作

在main函数中实现:

Chess2048 chess = Chess2048(render, ttf);
while(true) {if (chess.win()) {cout << "You Win!" << endl;break;} else if (chess.full()) {cout << "You Lose!" << endl;break;}//捕获事件SDL_Event event;SDL_PollEvent(&event); //等待事件
// cout << event.type << " " << flush;if (event.type == SDL_QUIT) { // 退出break;} else if (event.type == SDL_KEYDOWN) { // 按键//防抖while(true) {SDL_PollEvent(&event); //等待事件if ((event.type == SDL_KEYUP)) break;}switch (event.key.keysym.sym) { //检测按了哪个方向键case SDLK_UP:chess.up();break;case SDLK_DOWN:chess.down();break;case SDLK_LEFT:chess.left();break;case SDLK_RIGHT:chess.right();break;}} //end of ifchess.show(20); //按频率显示
} //退出循环

这时event.type表示SDL事件类型,我捕获的是按键,但按键有按下和抬起2种事件,因此增加了防抖机制,即直到按下后抬起再开始响应。而event.key.keysym.sym可以通过与SDL内置的键码进行比较,就可判断是哪个方向键按下了。

1.合并运算函数

这一部分是重难点,以上移操作为例,使用了变量k作为指示功能:

bool Chess2048::up() {bool moved = false;//所有格子偿试上移for (int j = 0; j < 4; j++) { //列int k = -1; // 标记压缩后不为0的行标末尾if (this->gameBoard[j]) k = 0;for (int i = 1; i < 4; i++) { //行if (!this->gameBoard[i*4+j]) continue;k++;if (k < i) { //直到不为0时往前压缩this->gameBoard[k*4+j] = this->gameBoard[i*4+j];this->gameBoard[i*4+j] = 0;moved = true;}if (k > 0 && this->gameBoard[(k-1)*4+j] == this->gameBoard[k*4+j]) { //与上一行相同则合并this->gameBoard[(k-1)*4+j] *= 2;this->gameBoard[k*4+j] = 0;k--;moved = true;}}}//如果发生了移动则在空余位置生成随机的2或4//...return moved;
}

(五) 更新游戏界面

在(四) 捕获用户方向键操作部分已经提到了,即chess.show(20); //按频率显示

(六) 判断游戏结束

1.判断获胜

bool Chess2048::win() {for (int i = 0; i < 16; i++) {if (this->gameBoard[i] == 2048) return true;}return false;
}

2.判断失败

判断完获胜后,只需判断有没有没到2048就满格的,但需判断是不是假满格,所谓假满格就是相邻格子可以合并的:

//判断是否满格,不能是假满格(即相邻单元格可以合并)
bool Chess2048::full() {// 所有数字全部和左、上比较for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {if (!this->gameBoard[i*4+j]) return false; //如果有0,就不满if (i == 0 && j == 0) continue;if (j > 0 && this->gameBoard[i*4+j] == this->gameBoard[i*4+j-1]) return false; //和左边相等,假满格if (i > 0 && this->gameBoard[i*4+j] == this->gameBoard[(i-1)*4+j]) return false; //和上边相等,假满格}}return true;
}

三、总结

本文教大家使用C++语言实现一个图形化2048小游戏,希望有所收获,如有好的建议欢迎留言,谢谢大家啦!

四、源码下载

2048小游戏(C++ SDL版)


http://www.hkcw.cn/article/yMuudeavcG.shtml

相关文章

Halcon光度立体法

1、光度立体法&#xff0c;可用于将对象的三维形状与其二维纹理&#xff08;例如打印图像&#xff09;分离。需要用不同方向而且已知照明方向的多个光源&#xff0c;拍摄同一物体的至少三张图像。请注意&#xff0c;所有图像的相机视角必须相同。 物体的三维形状主要被计算为三…

北方局地40℃又来了 干热烤验来临

天气即将变热,南北方的高温特点各不相同。北方是干热型高温,南方则是闷热型高温。全国大部分地区降水稀少,仅局部有雨。从今天夜间到后两天,降水预报图上将出现大片无降水区域,雨水不再是天气舞台的主要角色。气温成为焦点,南北方30℃以上的高温将连成一片,部分地区还将…

【后端架构师的发展路线】

后端架构师的发展路线是从基础开发到技术领导的系统性进阶过程&#xff0c;需融合技术深度、架构思维和业务洞察力。以下是基于行业实践的职业发展路径和关键能力模型&#xff1a; 一、职业发展阶梯‌ 初级工程师&#xff08;1-3年&#xff09;‌ 核心能力‌&#xff1a;掌…

Python爬虫监控程序设计思路

最近因为爬虫程序太多&#xff0c;想要为Python爬虫设计一个监控程序&#xff0c;主要功能包括一下几种&#xff1a; 1、监控爬虫的运行状态&#xff08;是否在运行、运行时间等&#xff09; 2、监控爬虫的性能&#xff08;如请求频率、响应时间、错误率等&#xff09; 3、资…

[手写系列]从0到1开发并上线Edge浏览器插件

[手写系列]从0到1开发并上线Edge浏览器插件 一、实战开发 我们将从0到1创建一个实用的"页面分析助手"插件&#xff0c;它可以显示当前页面的字数统计、阅读时间和主要关键词。 官方插件文档链接&#xff1a;https://learn.microsoft.com/zh-cn/microsoft-edge/exten…

归一化还是标准化?如何为你的数据选择最佳缩放方法

为什么你的模型需要"身高均等"&#xff1f; 想象一下&#xff0c;如果你在篮球队里同时安排了姚明&#xff08;2.29米&#xff09;和"小土豆"姜山&#xff08;1.65米&#xff09;一起打球&#xff0c;结果会怎样&#xff1f;显然&#xff0c;姚明会"…

JS逆向-基础入门案例(详细步骤)

一、基础入门案例AES(详细步骤) https://36kr.com/p/952011547555464 点击搜索 输入 decrypt( 看看是否有AES.decrypt( 点进去之后&#xff0c;打断点&#xff0c;打完断点之后&#xff0c;进行刷新 复制内容&#xff0c;可以在控制台输入 可以看到能获取到明文数据 创建…

项目目标和期望未被清晰传达,如何改进?

在项目管理实践中&#xff0c;目标模糊、期望不明、沟通渠道混乱是导致项目偏离方向、资源浪费和团队士气低落的核心原因。根据PMI《项目管理知识体系指南》&#xff08;PMBOK&#xff09;&#xff0c;超过39%的项目失败源于沟通不畅。要有效解决这一问题&#xff0c;必须优化沟…

推荐一款PDF压缩的工具

今天一位小伙伴找来&#xff0c;问我有没有办法将PDF变小的办法。 详细了解了一下使用场景&#xff1a; 小伙伴要在某系统上传一个PDF文件&#xff0c;原文件是11.6MB&#xff0c;但是上传时系统做了限制&#xff0c;只能上传小于10MB的文件&#xff0c;如图&#xff1a; 我听…

以太网帧结构和封装【三】-- TCP/UDP头部信息

TCP头部用于建立可靠连接、流量控制及数据完整性校验。 Ipv4封装tcp报&#xff1a; Ipv6封装tcp报&#xff1a; UDP头部信息 UDP关键协议特性&#xff1a; 1&#xff09;无连接&#xff1a;无需握手&#xff0c;直接发送数据。 2&#xff09;不可靠性&#xff1a;不保证数据…

61、ESB详解

ESB&#xff08;Enterprise Service Bus&#xff0c;企业服务总线&#xff09;是一种用于集成企业内不同应用程序和系统的中间件架构&#xff0c;它在企业信息化建设中扮演着关键角色&#xff0c;以下从核心概念、架构组成、功能特性、应用场景、优势与挑战几个方面进行详解&am…

六步完成软件验收:从计划到终验的全面指南(二)

在软件开发项目中&#xff0c;验收环节是确保软件质量、满足客户需求并成功交付的关键步骤。本文将为您详细介绍如何通过六个步骤&#xff0c;从计划到终验&#xff0c;全面完成软件验收工作。 四、执行验收测试并记录结果 按照验收测试计划&#xff0c;执行相应的测试用例&am…

fdisk给磁盘扩容实录

fdisk给磁盘扩容实录 步骤 1:对 /dev/sdb 进行分区步骤 2:创建物理卷(PV)步骤 3:将物理卷添加到卷组(VG)步骤 4:扩展逻辑卷(LV)步骤 5:调整文件系统大小步骤 1:对 /dev/sdb 进行分区 使用 fdisk 工具对 /dev/sdb 进行分区,创建一个新分区。 fdisk /dev/sdb 在 fd…

AbMole| Dimethyl sulfoxide(DMSO, 二甲基亚砜)

Dimethyl sulfoxide&#xff08;DMSO&#xff0c;二甲基亚砜&#xff09;是一种常用的有机溶剂&#xff0c;溶解能力强&#xff0c;能溶于水、乙醇、丙醇、乙醚、苯和氯仿等大多数有机物&#xff0c;可用于化合物等产品的溶解。 一、化学性质/溶解性/储存 分子量78.13分子式C2…

多线程1(Thread)

认识线程&#xff08;Thread&#xff09; 在进程中&#xff0c;要创建一个进程和销毁一个进程所消耗的硬件和软件资源是巨大的&#xff0c;因此为了优化上述过程&#xff0c;我们引入了“线程”。 线程是系统调度的基本单位。 1&#xff09;线程和进程的关系 可以认为进程包…

如何进行页面前端监控

&#x1f9d1;‍&#x1f4bb; 写在开头 点赞 收藏 学会&#x1f923;&#x1f923;&#x1f923; 前端监控主要分三个方向 前端性能&#xff08;用户体验优化&#xff09; 异常监控 业务指标跟 下面我来分别介绍三类指标如何获取 1&#xff09;前端性能指标&#xff1a; …

【JAVA版】意象CRM客户关系管理系统+uniapp全开源

一.介绍 CRM意象客户关系管理系统&#xff0c;是一个综合性的客户管理平台&#xff0c;旨在帮助企业高效地管理客户信息、商机、合同以及员工业绩。系统通过首页、系统管理、工作流程、审批中心、线索管理、客户管理、商机管理、合同管理、CRM系统、数据统计和系统配置等模块&…

【Python连接数据库基础 04】Django ORM开发指南:模型设计与高效查询完全攻略

Django ORM开发指南&#xff1a;模型设计与高效查询完全攻略 关键词&#xff1a;Django ORM、模型设计、数据库查询优化、Model关系、QuerySet、数据库性能、Python Web开发、ORM最佳实践 摘要&#xff1a;深入解析Django ORM的核心概念和高级用法&#xff0c;从模型设计原则到…

项目计划缺乏风险评估和应对策略,如何完善

项目计划缺乏风险评估和应对策略可通过建立风险识别机制、实施风险定性与定量评估、制定具体应对措施、建立风险监控体系、加强风险意识培训来完善。 特别是实施风险定性与定量评估&#xff0c;这一环节直接决定了风险的处理优先级和资源分配。通过定性评估&#xff0c;我们能明…

java29

1.IO流续续集 序列化流&#xff1a; 上传javabean类后再修改它&#xff0c;会报错 自己定义版本号 方便生产版本号的设置&#xff1a; 版本号要在javabean写完再写 综合练习&#xff1a; 因为不知道写进去多少个对象&#xff0c;因此读的时候不确定读多少次&#xff08;读一次…