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

C语言小结---2048游戏实现

游戏核心逻辑实现

一行数字向左合并

void merge_left(int *p) 71 { 72 int i=0; 73 int j=0; 74 for(i=0;i<4;i++) 75 { 76 if(p[i]!=0) 77 { 78 for(j=i+1;j<4;++j) 79 { 80 if(p[i]==p[j]) //匹配相同值,合并 81 { 82 p[i]*=2; 83 p[j]=0; 84 break; //合并一次退出,避免双重合并 85 }else if(p[j]!=0) //碰到非0且不相同值,说明此数不可能合并,退出循环避免跨数合并 86 { 87 break; 88 } //p【j】等于0,j++继续找 89 } 90 } 91 } 92 }

从左边p[0]开始向右寻找与p[0]相同数字,0值跳过,遇到非0且不等的数说明此数无法合并,直接退出后看p[1]依次查看下去

一行数字向左移动

94 void compress_left(int p[]) 95 { 96 int i=0; 97 int j=0; 98 for(i=0;i<4;++i) 99 { 100 if(p[i]!=0) 101 { 102 for(j=i-1;j>=0;--j) 103 { 104 if(p[j]==0) 105 { 106 p[j]=p[i]; 107 p[i]=0; 108 --i; 109 } 110 } 111 } 112 } 113 }

通过i外层循环寻找非0数,找到之后同过内层循环让非0数向左移动(非0数左边是0的情况下),通过--i让p[i]逐渐“冒泡”到左边

通过ai,获得一个更高效且符合规范的算法(上述算法在循环内改变了条件值i)

void compress_left(int p[], int n) { int write = 0; // 下一个非零元素要写入的位置 for (int read = 0; read < n; ++read) { if (p[read] != 0) { p[write++] = p[read]; } } // 剩余位置补零 while (write < n) { p[write++] = 0; } }

此算法通过双指针寻找非0值和0位,巧妙处在于即使p[0]有值,也只是p[0]赋值给p[0]。write每次写入后自动+1(先用后加)
read只遍历一遍数组,而且只赋值一次而不消除原处的数据,所以write之后的数据需要清零

数组向左移动

115 void left_move(int p[][4]) 116 { 117 int i; 118 for(i=0;i<4;++i) 119 { 120 merge_left(&p[i][0]); 121 compress_left(&p[i][0]); 122 } 123 }

通过函数复用轻松完成了整个数组的左移

数组向右移动

易得将数组左右翻转之后向左移动再翻转可以获得向右移动的效果

125 void reverse_row(int m[4]) 126 { 127 int row[4]={}; 128 int i=0; 129 for(i=0;i<4;++i) 130 { 131 row[i]=m[3-i]; 132 } 133 for(i=0;i<4;++i) 134 { 135 m[i]=row[i]; 136 } 137 }

翻转数组(一行)

void right_move(int p[][4]) 140 { 141 int i; 142 for(i=0;i<4;++i) 143 { 144 reverse_row(&p[i][0]); 145 merge_left(&p[i][0]); 146 compress_left(&p[i][0]); 147 reverse_row(&p[i][0]); 148 } 149 }

循环同样是对一行数据进行操作的,所以通过翻转实现了右移
重写向右合并与向右移动函数虽然已有前例对照,但是实现代码的效率还是不如翻转好

数组向上移动

有了以上经验,我们知道可以将数组改造成便于向左一行一行移动的结构即可

151 void up_move(int p[][4]) 152 { 153 int colmn[4]; 154 int i,j; 155 for(i=0;i<4;++i) 156 { 157 for(j=0;j<4;j++) 158 { 159 colmn[j]=p[j][i]; 160 } 161 merge_left(colmn); 162 compress_left(colmn); 163 for(j=0;j<4;++j) 164 { 165 p[j][i]=colmn[j]; 166 } 167 } 168 } 169

通过外循环(i)内部的两次循环(j),一次外循环就将一列数据取出、移动然后填回

数组向下移动

170 void down_move(int p[][4]) 171 { 172 int colmn[4]; 173 int i,j; 174 for(i=0;i<4;++i) 175 { 176 for(j=0;j<4;++j) 177 { 178 colmn[j]=p[j][i]; 179 } 180 reverse_row(colmn); 181 merge_left(colmn); 182 compress_left(colmn); 183 reverse_row(colmn); 184 for(j=0;j<4;++j) 185 { 186 p[j][i]=colmn[j]; 187 } 188 } 189 }

与向上移动类型,添加了两次翻转

判断失败或者胜利

void check_game_status(int p[][4]) 214 { 215 win=0; 216 int i,j; 217 for(i=0;i<4;++i) 218 { 219 for(j=0;j<4;++j) 220 { 221 if(p[i][j]==2048) 222 { 223 win=1; 224 return; //游戏胜利 225 } 226 } 227 } 228 for(i=0;i<4;++i) 229 { 230 for(j=0;j<4;++j) 231 { 232 if(p[i][j]==0) 233 { 234 return; //游戏继续 235 } 236 } 237 } 238 for(i=0;i<4;++i) 239 { 240 for(j=0;j<4;++j) 241 { 242 if(j==3 && i!=3) 243 { 244 if(p[i][j]==p[i+1][j]) 245 { 246 return; //游戏继续 247 } 248 }else if(i==3 && j!=3) 249 { 250 if(p[i][j]==p[i][j+1]) 251 { 252 return; //游戏继续 253 } 254 }else if(i!=3 && j!=3) 255 { 256 if(p[i][j]==p[i][j+1]) 257 { 258 return; //游戏继续 259 } 260 if(p[i][j]==p[i+1][j]) 261 { 262 return; //游戏继续 263 } 264 } 265 266 } 267 } 268 game_over=1; 269 }

优先判断是否有2048(胜利条件),防止2048出现后游戏继续
有空格直接判断游戏继续
无空格则判断是否有相同且相邻的数,有则继续,无则结束
我的判断相同相邻值算法略显复杂,插入一个更优秀的算法

for (int i = 0; i < 4; ++i) { for (int j = 0; j < 4; ++j) { // 检查右邻居(j+1 < 4) if (j + 1 < 4 && p[i][j] == p[i][j + 1]) return; // 检查下邻居(i+1 < 4) if (i + 1 < 4 && p[i][j] == p[i + 1][j]) return; } }

算法原理相同,我为了防止数组越界写的较为复杂,这是一种更简易且易理解的算法

对数组初始化

37 void init(int (*p)[4]) 38 { 39 srand(time(NULL)); 40 int init_val1,init_val2; 41 memset(p,0,sizeof(int[4][4])); 42 if(rand()%10==1) 43 { 44 init_val1=4; 45 }else 46 { 47 init_val1=2; 48 } 49 if(rand()%10==1) 50 { 51 init_val2=4; 52 }else 53 { 54 init_val2=2; 55 } 56 p[rand()%4][rand()%4]=init_val1; 57 int flage=1; 58 while(flage) 59 { 60 int n=rand()%4; 61 int m=rand()%4; 62 if(p[n][m]==0) 63 { 64 flage=0; 65 p[n][m]=init_val2; 66 } 67 } 68 }

初始化一局游戏只需要一次,所以这里顺便取出随机种子值(种子值一次运行只能取一次,否则时间戳相同,第一个随机数也是相同的)
这里通过对10求余,概率取得(0~9)十个结果,所以初始值有10%的概率为4,90%的概率为2
再通过flag检测是否两个初始值填入一个位置

填入随机值

10 void generate_random(int (*p)[4]) 11 { 12 int i,j; 13 int q[16][2]={}; 14 int t=0; 15 for(i=0;i<4;++i) 16 { 17 for(j=0;j<4;++j) 18 { 19 if(p[i][j]==0) 20 { 21 q[t][0]=i; 22 q[t][1]=j; 23 t++; 24 } 25 } 26 } 27 if(t==0) 28 return; 29 int r=rand()%t; 30 if(rand()%10>=7) 31 { 32 p[q[r][0]][q[r][1]]=4; 33 }else 34 { 35 p[q[r][0]][q[r][1]]=2; 36 } 37 }

创建一个数组存储所有空白格的行列坐标
从t个空白格随机选择一个
以 30% 的概率生成 4,70% 的概率生成 2

http://www.jsqmd.com/news/1337504/

相关文章:

  • 撤三风险从来不是一纸通知:权大师如何帮助企业建立“一标一档”证据管理机制
  • Java设计模式实战:从单例到策略,代码实现与避坑指南
  • FreeMocap开源动捕指南:用普通摄像头实现低成本3D动作捕捉
  • Unity序列化核心机制:Serializable、SerializeField与SerializeReference深度解析
  • 乌鲁木齐市达坂城区外墙漏水维修_2026新疆中部风谷地区漏水维修流程教程与价格表 - 雨婺虹修缮
  • 2026年8月广东整厂打包回收/广东厂房拆除回收厂家深度推荐_深圳市钰东再生资源回收有限公司 - 品牌宣传支持者
  • 算法面试——哈希表:两数之和、三数之和、最长连续序列
  • 采购河北外墙钢板网厂家如何把控质量与交货周期 - 品牌优推
  • MATLAB偏微分方程求解进阶:从一维非线性到二维有限元实战
  • 终极指南:如何在ESP32上打造情感丰富的AI聊天机器人表情系统
  • 鸣潮工具箱WaveTools完整指南:三步解锁120帧画质优化终极方案
  • 基于8086微处理器的电子时钟系统设计与实现
  • 2026 年新消息:克孜勒苏柯尔克孜自治州专业的tpo防水卷材p型供应厂家推荐,贴在屋顶用十年不漏水的这玩意儿,居然不是靠沥青? - 企业推荐官【认证】
  • 2026随州活动拍摄公司优选榜** | 会议拍摄 | 活动跟拍 | 视频直播 | 照片直播 | 年会拍摄服务商评测对比 - 政企影像扫地僧
  • MySQL存储引擎与高可用架构实战解析
  • 系统卡顿排查指南:从死锁到线程池问题的实战诊断
  • Windows 11透明任务栏:原理、优化与高阶玩法全解析
  • JavaSE初学(2)
  • STM32 GPIO API深度解析:从标准库函数到高效配置与避坑指南
  • 单总线CPU硬布线控制器设计:现代时序与状态机实现详解
  • 5分钟快速上手:ReportGenerator代码覆盖率报告转换完整指南
  • Digital M7478-AA 单板计算机
  • 大模型推理能力提升实战:思维链与由少至多提示技术详解
  • 戴森球计划工厂蓝图库:3000+设计方案让星际建造更简单
  • Deno运行时:安全默认设置带来出色开发体验,多方式安装轻松上手!
  • 链式前向星:从邻接矩阵到高效存图,详解原理与C++/Python实现
  • Funplay Unity MCP execute_code:AI驱动Unity开发的代码沙盒与执行引擎
  • Mysql:主键
  • 成都管道疏通与漏水检测服务怎么选?专业机构横向参考指南 - 优质品牌商家
  • 构建高效Shiro密钥字典:从原理到实战的攻防利器