
游戏说明棋盘游戏在一个4×4的网格中进行共 16 个格子对应你代码中的#define SIZE 4和int a[4][4]。初始状态游戏开始时系统会随机在网格中生成 2 个数字方块数值通常是2或4对应你代码中的randnumber函数。目标通过移动方块让格子里的数字通过合并最终拼凑出一个2048的方块即为获胜对应你代码里check_game_status中判断a[i][j] 2048的逻辑。失败条件当 16 个格子全部填满且相邻的格子之间没有任何相同数字可以合并时游戏结束对应你代码check_game_status最后的gameover 1。通过键盘输入W/A/S/D上下左右控制所有方块同时向那个方向滑动。每次滑动遵循以下 3 个执行步骤压缩靠拢所有方块向指定方向移动中间的空白格被填满代码中的compress_left/up/right/down。合并相加如果压缩后同一行/列上出现两个相邻且数值相同的方块它们就会合并成一个数值相加例如224448。每次滑动同一个方块只能参与一次合并代码中的Merge_left/up/right/down。补充新方块每次有效移动棋盘发生过变化之后系统会在空白格子中随机生成一个新方块代码中的if(moved) { randnumber(a); ... }正常概率是 90% 生成 210% 生成 4。一 准备工作1 定义数组2 打印网格数组游戏界面//打印网格和数组 void printGame(int a[SIZE][SIZE]) { int i 0; int j 0; printf(---------------------\n); for(i0;iSIZE;i) { for(j0;jSIZE;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(i0;iSIZE;i) { for(j0;jSIZE;j) { if(a[i][j]0) cnt; } } if(cnt0) { return; } int val (rand()%107)? 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(iSIZE) { if(row[i]!0) { row[pos] row[i]; pos; } i; } for(int jpos;jSIZE;j) { row[j] 0; } }步骤2合并操作挤压靠拢之后需要判断有没有相邻且相同的数字把它们加在一起。逻辑先调用compress_left把数字挤到一侧。使用for(i0; iSIZE-1; i)遍历特别注意只遍历到SIZE-1防止越界。判断如果row[i] row[i1]且都不为 0则row[i] 2 * row[i]合并翻倍。score row[i]分数累加。关键row[i1] 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(i0;iSIZE-1;i) { if(row[i]row[i1] row[i] !0) { row[i] 2*row[i]; scorerow[i]; row[i1] 0; }else{ continue; } } compress_left(row); }步骤3左移操作把二维数组的每一行提取到一维数组row并备份到old_row。对row执行压缩和合并Merge_left。比较row和old_row。若有差异将row写回原二维数组并标记moved1。表示发生改变。四行处理完后若moved1调用randnumber生成新方块。用check_game_status判定胜负。check_game_status这个函数在后面会进行介绍void left_move(int a[SIZE][SIZE]) { int moved 0; int i 0; int j0; int row[SIZE]; int old_row[SIZE]; for(i0;iSIZE;i) { for(j0;jSIZE;j) { row[j] a[i][j]; old_row[j] a[i][j]; } Merge_left(row); int change 0; for(j0;jSIZE;j) { if(row[j]!old_row[j]) { change 1; break; } } if(change) { for(j0;jSIZE;j) { a[i][j] row[j]; } moved1; } } if(moved) { randnumber(a); check_game_status(a); } }右移与左移算法类似就是换了个方向不做赘述步骤4右移的压缩//压缩 void compress_right(int row[SIZE]) { int i SIZE-1; int pos SIZE-1; while(i0) { if(row[i]!0) { row[pos] row[i]; pos--; } i--; } for(int j0;jpos;j) { row[j] 0; } }步骤5int Merge_right(int row[SIZE]) { compress_right(row); int i; for(iSIZE-1;i0;i--) { if(row[i]row[i-1] row[i] !0) { row[i] 2*row[i]; scorerow[i]; row[i-1] 0; }else{ continue; } } compress_right(row); }步骤7void right_move(int a[SIZE][SIZE]) { int moved 0; int i 0; int j0; int row[SIZE]; int old_row[SIZE]; for(i0;iSIZE;i) { for(j0;jSIZE;j) { row[j] a[i][j]; old_row[j] a[i][j]; } Merge_right(row); int change 0; for(j0;jSIZE;j) { if(row[j]!old_row[j]) { change 1; break; } } if(change) { for(j0;jSIZE;j) { a[i][j] row[j]; } moved1; } } if(moved) { randnumber(a); check_game_status(a); } }上移操作会可以将列提取出来当作行来看此时的上移就相当于左移所以可以直接套用左移的压缩 合并的算法完成压缩合并之后将行又转化为列就可以完成上移步骤8上移压缩//上移压缩 void compress_up(int col[SIZE]) { int j 0; int pos 0; while(jSIZE) { if(col[j]!0) { col[pos] col[j]; pos; } j; } for(int ipos;iSIZE;i) { col[i] 0; } }步骤9上移合并//上移合并 void Merge_up(int col[SIZE]) { compress_up(col); int j; for(j0;jSIZE-1;j) { if(col[j]col[j1] col[j] !0) { col[j] 2*col[j]; scorecol[j]; col[j1] 0; }else{ continue; } } compress_up(col); }步骤10上移//上移 void up_move(int a[SIZE][SIZE]) { int moved 0; int i 0; int j0; int col[SIZE]; int old_col[SIZE]; for(j0;jSIZE;j) { for(i0;iSIZE;i) { col[i] a[i][j]; //将要处理的数组中的列提取出来 old_col[i] a[i][j]; } Merge_up(col); int change 0; for(i0;iSIZE;i) { if(col[i]!old_col[i]) { change 1; break; } } if(change) { for(i0;iSIZE;i) { a[i][j] col[i]; } moved1; } } if(moved) { randnumber(a); check_game_status(a); } }步骤11下移的合并同上上移用左移的压缩合并算法下移就可以用有移的压缩合并算法//下移压缩 void compress_down(int col[SIZE]) { int i SIZE-1; int pos SIZE-1; while(i0) { if(col[i]!0) { col[pos] col[i]; pos--; } i--; } for(int j0;jpos;j) { col[j] 0; } } //右移动 int Merge_down(int col[SIZE]) { compress_down(col); int i; for(iSIZE-1;i0;i--) { if(col[i]col[i-1] col[i] !0) { col[i] 2*col[i]; scorecol[i]; col[i-1] 0; }else{ continue; } } compress_down(col); } //向下移动 void down_move(int a[SIZE][SIZE]) { int moved 0; int i 0; int j0; int col[SIZE]; int old_col[SIZE]; for(j0;jSIZE;j) { for(i0;iSIZE;i) { col[i] a[i][j]; old_col[i] a[i][j]; } Merge_down(col); int change 0; for(i0;iSIZE;i) { if(col[i]!old_col[i]) { change 1; break; } } if(change) { for(i0;iSIZE;i) { a[i][j] col[i]; } moved1; } } 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][j1]的值是否相同。如果发现相同的说明还可以进行横向合并还有活路直接return结束函数。游戏继续。4. 第四环节检查“纵向”相邻数字是否相同遍历数组检查同一列中相邻上下两个格子a[j][i]和a[j1][i]的值是否相同。如果发现相同的说明还可以进行纵向合并有路可走直接return结束函数。5. 最终判定触发游戏结束如果函数执行到了这里意味着上面 4 个判断全部都没有触发return。满盘没有 0、没有横向相同、也没有纵向相同。这是一个死局。于是设置gameover 1主循环检测到!gameover为假游戏宣告失败并退出。void check_game_status(int a[SIZE][SIZE]) { int i0; int j0; for(i0;iSIZE;i) { for(j0;jSIZE;j) { if(a[i][j]2048) { win 1; break; } } } for(i0;iSIZE;i) { for(j0;jSIZE;j) { if(a[i][j]0) { return; } } } for(i0;iSIZE;i) { for(j0;jSIZE;j) { if(a[i][j]a[i][j1]) { return; } } } for(i0;iSIZE;i) { for(j0;jSIZE;j) { if(a[j][i]a[j1][i]) { return; } } } gameover 1; }步骤13主函数1 .初始化设置1int a[4][4]{0};定义一个 4x4 的二维数组作为棋盘并将所有元素初始化为0表示空格。2srand((unsigned int)time(NULL));使用当前时间作为随机数种子。这是为了确保每次运行程序时系统生成的方块位置都是随机不重复的。2.开局布阵1randnumber(a);执行第一次在空棋盘上随机生成第一个数字方块2 或 4。2randnumber(a);执行第二次在空棋盘上再随机生成第二个数字方块。标准 2048 规则开局一定有两个初始数字。3.主游戏循环进入while(!win !gameover)循环。只要没有赢且没有输游戏就会一直卡在这个死循环里1printGame(a);先把当前的棋盘状况渲染打印到屏幕上。2char ch getch();挂起等待直到玩家按下键盘上的某个键。3switch(ch)根据按下的键执行不同的分支。4注意在移动函数内部会触发随机生成新方块randnumber以及检查游戏状态check_game_status更改win或gameover的值。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循环的条件就不满足了循环立刻中断。2return 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);运行结果