news 2026/8/11 10:25:32

C语言小项目练习----2048小游戏 Day13

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言小项目练习----2048小游戏 Day13

游戏说明:

  1. 棋盘:游戏在一个4×4的网格中进行,共 16 个格子(对应你代码中的#define SIZE 4int a[4][4])。

  2. 初始状态:游戏开始时,系统会随机在网格中生成 2 个数字方块,数值通常是24(对应你代码中的randnumber函数)。

  3. 目标:通过移动方块,让格子里的数字通过合并,最终拼凑出一个2048的方块即为获胜(对应你代码里check_game_status中判断a[i][j] == 2048的逻辑)。

  4. 失败条件:当 16 个格子全部填满,且相邻的格子之间没有任何相同数字可以合并时,游戏结束(对应你代码check_game_status最后的gameover = 1)。

通过键盘输入W/A/S/D(上下左右)控制所有方块同时向那个方向滑动。每次滑动遵循以下 3 个执行步骤

  1. 压缩(靠拢):所有方块向指定方向移动,中间的空白格被填满(代码中的compress_left/up/right/down)。

  2. 合并(相加):如果压缩后,同一行/列上出现两个相邻且数值相同的方块,它们就会合并成一个,数值相加(例如2+2=44+4=8)。每次滑动,同一个方块只能参与一次合并(代码中的Merge_left/up/right/down)。

  3. 补充新方块:每次有效移动(棋盘发生过变化)之后,系统会在空白格子中随机生成一个新方块(代码中的if(moved) { randnumber(a); ... },正常概率是 90% 生成 2,10% 生成 4)。

一 准备工作

1 定义数组

2 打印网格数组(游戏界面)

//打印网格和数组 void printGame(int a[SIZE][SIZE]) { int i = 0; int j = 0; printf("---------------------\n"); for(i=0;i<SIZE;i++) { for(j=0;j<SIZE;j++) { printf("|"); if(a[i][j] ==0) { printf(" "); }else { printf("%4d",a[i][j]); } } printf("|\n"); printf("---------------------\n"); } }

3 生成随机数(游戏开局先生成两个随机数)

//生成随机值 void randnumber(int a[SIZE][SIZE]) { int i = 0; int j = 0; int cnt = 0; for(i=0;i<SIZE;i++) { for(j=0;j<SIZE;j++) { if(a[i][j]==0) cnt++; } } if(cnt==0) { return; } int val = (rand()%10<7)? 2:4; int row; int col; do { row = rand()%4; col = rand()%4; }while(a[row][col]!=0); a[row][col] = val; }

二 核心算法

步骤1:压缩操作

把一行里的格子全部推挤到最左边,不留空隙。这是 2048 移动的第一步。

逻辑:利用“双指针(读写指针)”思想。

定义pos = 0(写入位置)。

循环遍历原行row的每个元素(i从 0 到 3)。

只要遇到一个非 0 的数字,就把它“写”到row[pos]的位置,然后把pos加 1。

循环结束后,从pos直到数组末尾,全部填充为0

效果[2, 0, 0, 2]经过压缩会变成[2, 2, 0, 0],数字紧紧贴到左边。

//实现压缩操作 void compress_left(int row[SIZE]) { int i = 0; int pos = 0; while(i<SIZE) { if(row[i]!=0) { row[pos] =row[i]; pos++; } i++; } for(int j=pos;j<SIZE;j++) { row[j] = 0; } }

步骤2:合并操作

挤压靠拢之后,需要判断有没有相邻且相同的数字,把它们加在一起。

  • 逻辑

    1. 先调用compress_left把数字挤到一侧。

    2. 使用for(i=0; i<SIZE-1; i++)遍历(特别注意:只遍历到SIZE-1防止越界)。

    3. 判断:如果row[i] == row[i+1]且都不为 0,则:

      • row[i] = 2 * row[i](合并翻倍)。

      • score += row[i](分数累加)。

      • 关键row[i+1] = 0(把后面的那个数字抹掉,标记为已经被合并)。

      • 最后,再次调用compress_left。因为刚才抹掉了一个数字,中间又产生了空隙,需要再挤压一次。

      • 效果:经过完整的Merge_left后,[2, 2, 2, 0]-> (压缩) ->[2, 2, 2, 0]-> (合并) ->[4, 0, 2, 0]-> (再次压缩) ->最终[4, 2, 0, 0]

//往左边移动时相同时xiangjia void Merge_left(int row[SIZE]) { compress_left(row); int i; for(i=0;i<SIZE-1;i++) { if(row[i]==row[i+1] && row[i] !=0) { row[i] =2*row[i]; score+=row[i]; row[i+1] = 0; }else{ continue; } } compress_left(row); }

步骤3:左移操作

把二维数组的每一行提取到一维数组row,并备份到old_row。对row执行压缩和合并(Merge_left)。比较rowold_row。若有差异,将row写回原二维数组,并标记moved=1。表示发生改变。四行处理完后,若moved==1,调用randnumber生成新方块。用check_game_status判定胜负。check_game_status这个函数在后面会进行介绍

void left_move(int a[SIZE][SIZE]) { int moved = 0; int i = 0; int j=0; int row[SIZE]; int old_row[SIZE]; for(i=0;i<SIZE;i++) { for(j=0;j<SIZE;j++) { row[j] = a[i][j]; old_row[j] = a[i][j]; } Merge_left(row); int change = 0; for(j=0;j<SIZE;j++) { if(row[j]!=old_row[j]) { change = 1; break; } } if(change) { for(j=0;j<SIZE;j++) { a[i][j] = row[j]; } moved=1; } } if(moved) { randnumber(a); check_game_status(a); } }

右移与左移算法类似,就是换了个方向,不做赘述

步骤4:右移的压缩

//压缩 void compress_right(int row[SIZE]) { int i = SIZE-1; int pos = SIZE-1; while(i>=0) { if(row[i]!=0) { row[pos] =row[i]; pos--; } i--; } for(int j=0;j<=pos;j++) { row[j] = 0; } }

步骤5:

int Merge_right(int row[SIZE]) { compress_right(row); int i; for(i=SIZE-1;i>0;i--) { if(row[i]==row[i-1] && row[i] !=0) { row[i] =2*row[i]; score+=row[i]; row[i-1] = 0; }else{ continue; } } compress_right(row); }

步骤7:

void right_move(int a[SIZE][SIZE]) { int moved = 0; int i = 0; int j=0; int row[SIZE]; int old_row[SIZE]; for(i=0;i<SIZE;i++) { for(j=0;j<SIZE;j++) { row[j] = a[i][j]; old_row[j] = a[i][j]; } Merge_right(row); int change = 0; for(j=0;j<SIZE;j++) { if(row[j]!=old_row[j]) { change = 1; break; } } if(change) { for(j=0;j<SIZE;j++) { a[i][j] = row[j]; } moved=1; } } if(moved) { randnumber(a); check_game_status(a); } }

上移操作会可以将列提取出来,当作行来看,此时的上移就相当于左移,所以可以直接套用左移的压缩 合并的算法,完成压缩合并之后将行又转化为列,就可以完成上移

步骤8:上移压缩

//上移压缩 void compress_up(int col[SIZE]) { int j = 0; int pos = 0; while(j<SIZE) { if(col[j]!=0) { col[pos] =col[j]; pos++; } j++; } for(int i=pos;i<SIZE;i++) { col[i] = 0; } }

步骤9:上移合并

//上移合并 void Merge_up(int col[SIZE]) { compress_up(col); int j; for(j=0;j<SIZE-1;j++) { if(col[j]==col[j+1] && col[j] !=0) { col[j] =2*col[j]; score+=col[j]; col[j+1] = 0; }else{ continue; } } compress_up(col); }

步骤10:上移

//上移 void up_move(int a[SIZE][SIZE]) { int moved = 0; int i = 0; int j=0; int col[SIZE]; int old_col[SIZE]; for(j=0;j<SIZE;j++) { for(i=0;i<SIZE;i++) { col[i] = a[i][j]; //将要处理的数组中的列提取出来 old_col[i] = a[i][j]; } Merge_up(col); int change = 0; for(i=0;i<SIZE;i++) { if(col[i]!=old_col[i]) { change = 1; break; } } if(change) { for(i=0;i<SIZE;i++) { a[i][j] = col[i]; } moved=1; } } if(moved) { randnumber(a); check_game_status(a); } }

步骤11:下移的合并(同上,上移用左移的压缩合并算法,下移就可以用有移的压缩合并算法)

//下移压缩 void compress_down(int col[SIZE]) { int i = SIZE-1; int pos = SIZE-1; while(i>=0) { if(col[i]!=0) { col[pos] =col[i]; pos--; } i--; } for(int j=0;j<=pos;j++) { col[j] = 0; } } //右移动 int Merge_down(int col[SIZE]) { compress_down(col); int i; for(i=SIZE-1;i>0;i--) { if(col[i]==col[i-1] && col[i] !=0) { col[i] =2*col[i]; score+=col[i]; col[i-1] = 0; }else{ continue; } } compress_down(col); } //向下移动 void down_move(int a[SIZE][SIZE]) { int moved = 0; int i = 0; int j=0; int col[SIZE]; int old_col[SIZE]; for(j=0;j<SIZE;j++) { for(i=0;i<SIZE;i++) { col[i] = a[i][j]; old_col[i] = a[i][j]; } Merge_down(col); int change = 0; for(i=0;i<SIZE;i++) { if(col[i]!=old_col[i]) { change = 1; break; } } if(change) { for(i=0;i<SIZE;i++) { a[i][j] = col[i]; } moved=1; } } if(moved) { randnumber(a); check_game_status(a); } }

步骤12:检查游戏状态

第一环节:检查是否胜利(达到 2048)

遍历整个二维数组。如果发现有任何一个格子的值等于2048,就将全局胜利标志win设为1。这里使用了break;仅跳出内层j的循环,外层i仍会继续走完。但因为win已经为 1,后续的检查环节对于最终结果已经没有影响了(主循环会因!win为假而结束)。

2. 第二环节:检查是否还有“空位”

再次遍历整个数组。如果发现有a[i][j] == 0(即空白格子),直接return结束这个函数return意味着“当前还没输,还有回旋余地”,直接告诉主程序继续游戏。

3. 第三环节:检查“横向”相邻数字是否相同

遍历数组,检查同一行中,相邻左右两个格子a[i][j]a[i][j+1]的值是否相同。如果发现相同的,说明还可以进行横向合并,还有活路,直接return结束函数。游戏继续。

4. 第四环节:检查“纵向”相邻数字是否相同

遍历数组,检查同一列中,相邻上下两个格子a[j][i]a[j+1][i]的值是否相同。如果发现相同的,说明还可以进行纵向合并,有路可走,直接return结束函数

5. 最终判定:触发游戏结束

如果函数执行到了这里,意味着上面 4 个判断全部都没有触发return。满盘(没有 0)、没有横向相同、也没有纵向相同。这是一个死局。于是设置gameover = 1,主循环检测到!gameover为假,游戏宣告失败并退出。

void check_game_status(int a[SIZE][SIZE]) { int i=0; int j=0; for(i=0;i<SIZE;i++) { for(j=0;j<SIZE;j++) { if(a[i][j]==2048) { win = 1; break; } } } for(i=0;i<SIZE;i++) { for(j=0;j<SIZE;j++) { if(a[i][j]==0) { return; } } } for(i=0;i<SIZE;i++) { for(j=0;j<SIZE;j++) { if(a[i][j]==a[i][j+1]) { return; } } } for(i=0;i<SIZE;i++) { for(j=0;j<SIZE;j++) { if(a[j][i]==a[j+1][i]) { return; } } } gameover = 1; }

步骤13:主函数

1 .初始化设置

(1)int a[4][4]={0};:定义一个 4x4 的二维数组作为棋盘,并将所有元素初始化为0(表示空格)。

(2)srand((unsigned int)time(NULL));:使用当前时间作为随机数种子。这是为了确保每次运行程序时,系统生成的方块位置都是随机不重复的。

2.开局布阵

(1)randnumber(a);(执行第一次):在空棋盘上,随机生成第一个数字方块(2 或 4)。

(2)randnumber(a);(执行第二次):在空棋盘上,再随机生成第二个数字方块。(标准 2048 规则:开局一定有两个初始数字)。

3.主游戏循环

  1. 进入while(!win && !gameover)循环。只要没有赢没有输,游戏就会一直卡在这个死循环里:

    (1)printGame(a);:先把当前的棋盘状况渲染打印到屏幕上。(2)char ch = getch();挂起等待,直到玩家按下键盘上的某个键。(3)switch(ch):根据按下的键执行不同的分支。(4)(注意:在移动函数内部,会触发随机生成新方块randnumber,以及检查游戏状态check_game_status更改wingameover的值)。(5)按s/S-> 调用down_move(a)执行下移计算。(6)按w/W-> 调用up_move(a)执行上移计算。(7)按d/D-> 调用right_move(a)执行右移计算。(8)按a/A-> 调用left_move(a)执行左移计算。

4.结束退出

(1)一旦在上述某个移动操作中,check_game_status发现了a[i][j] == 2048并把win改成1或者发现了死局并把gameover改成1while循环的条件就不满足了,循环立刻中断。

(2)return 0;:程序正常结束退出。

int main(int argc,const char *argv[]) { int a[4][4]={0}; srand((unsigned int)time(NULL)); randnumber(a); randnumber(a); while(!win && !gameover) { printGame(a); char ch = getch(); switch(ch) { case 'a': case 'A': left_move(a); break; case 'D': case 'd': right_move(a); break; case 'w': case 'W': up_move(a); break; case 's': case 'S': down_move(a); break; } } return 0; }

写到这里,游戏功能就完成的差不多了。测试之后发现次打印网格时,屏幕会往下滚动,而不是在原位置覆盖,影响游戏体验,需要将光标移回屏幕左上角。

如何实现这个操作?

------------先添加 printf("\033[2J");

------------添加 printf("\033[H");

运行结果:

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/11 10:25:19

57 个大模型怎么选?2026 年中 AI 全景图

2026 年上半年&#xff0c;AI 模型市场经历了一场静默地震。开源与闭源的差距缩小到几乎没有&#xff0c;中国开源模型在中端赛道表现格外抢眼。但更关键的变化在于选择逻辑本身&#xff1a;不再是比谁的排行榜分数高&#xff0c;而是匹配场景、价格和部署位置。别跟风买贵的&a…

作者头像 李华
网站建设 2026/8/11 10:23:41

《高速公路全覆盖光伏+动态无线充电项目可行性分析》

高速公路全覆盖光伏动态无线充电项目可行性分析 该项目的核心构想是将高速公路改造为“发电补能”一体化基础设施&#xff1a;通过路面或上空的光伏板发电&#xff0c;结合路面预埋的动态无线充电线圈&#xff0c;让电动汽车在行驶中实时补能&#xff0c;理论上实现“无限续航”…

作者头像 李华
网站建设 2026/8/11 10:23:22

Spring Boot架构设计:从分层到领域驱动的可扩展实践

在技术领域&#xff0c;我们常常会探讨一些思想、理念或架构模式的前瞻性。今天&#xff0c;我们不谈具体的历史人物&#xff0c;而是聚焦于一种在软件开发中至关重要的思维方式—— 架构与设计的超前性 。这种思维方式&#xff0c;往往能帮助我们在项目初期就规避掉未来可能…

作者头像 李华
网站建设 2026/8/11 10:22:58

2025终极网盘直链下载神器:一键获取九大网盘真实下载链接

2025终极网盘直链下载神器&#xff1a;一键获取九大网盘真实下载链接 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘 / 天…

作者头像 李华
网站建设 2026/8/11 10:21:59

大语言模型跨领域链式推理性能骤降:从83%到43%的挑战与工程应对

在探索大语言模型&#xff08;LLM&#xff09;能力的边界时&#xff0c;我们常常惊叹于其在单一领域内展现出的强大推理能力。然而&#xff0c;当问题变得复杂&#xff0c;需要模型将知识串联、跨越不同领域进行链式推理时&#xff0c;即使是前沿的顶级模型&#xff0c;其表现也…

作者头像 李华
网站建设 2026/8/11 10:21:35

Adobe-GenP 3.0破解工具终极指南:快速永久激活Adobe全家桶

Adobe-GenP 3.0破解工具终极指南&#xff1a;快速永久激活Adobe全家桶 【免费下载链接】Adobe-GenP Adobe CC 2019/2020/2021/2022/2023 GenP Universal Patch 3.0 项目地址: https://gitcode.com/gh_mirrors/ad/Adobe-GenP 还在为Adobe Creative Cloud的高昂订阅费用而…

作者头像 李华