完整源代码如下,敬请读者批评指正:
1 /* 2 * Copyright (C) Judge Young 3 * E-mail: yjjtc@126.com 4 * Version: 1.0 5 */ 6 7 #include <stdio.h> 8 #include <time.h> /* 包含设定随机数种子所需要的time()函数 */ 9 #include <conio.h> /* 包含Windows平台上完成输入字符不带回显和回车确认的getch()函数 */ 10 #include <windows.h> /* 包含Windows平台上完成设定输出光标位置达到清屏功能的函数 */ 11 12 void start_game(); /* 开始游戏 */ 13 void reset_game(); /* 重置游戏 */ 14 15 /* 往左右上下四个方向移动 */ 16 void move_left(); 17 void move_right(); 18 void move_up(); 19 void move_down(); 20 21 void refresh_show(); /* 刷新界面显示 */ 22 void add_rand_num(); /* 生成随机数,本程序中仅生成2或4,概率之比设为2:1 */ 23 void check_game_over(); /* 检测是否输掉游戏,设定游戏结束标志 */ 24 int get_null_count(); /* 获取游戏面板上空位置数量 */ 25 26 int board[4][4]; /* 游戏数字面板,抽象为二维数组 */ 27 int score; /* 游戏的分 */ 28 int best; /* 游戏最高分 */ 29 int if_need_add_num; /* 是否需要生成随机数标志,1表示需要,0表示不需要 */ 30 int if_game_over; /* 是否游戏结束标志,1表示游戏结束,0表示正常 */ 31 32 /* main函数 函数定义 */ 33 int main() 34 { 35 start_game(); 36 } 37 38 /* 开始游戏 函数定义 */ 39 void start_game() 40 { 41 reset_game(); 42 char cmd; 43 while (1) 44 { 45 cmd = getch(); /* 接收标准输入流字符命令 */ 46 47 if (if_game_over) /* 判断是否需已经输掉游戏 */ 48 { 49 if (cmd == ‘y‘ || cmd == ‘Y‘) /* 重玩游戏 */ 50 { 51 reset_game(); 52 continue; 53 } 54 else if (cmd == ‘n‘ || cmd == ‘N‘) /* 退出 */ 55 { 56 return; 57 } 58 else 59 { 60 continue; 61 } 62 } 63 64 if_need_add_num = 0; /* 先设定不默认需要生成随机数,需要时再设定为1 */ 65 66 switch (cmd) /* 命令解析,w,s,a,d字符代表上下左右命令 */ 67 { 68 case ‘a‘: 69 case ‘A‘: 70 case 75 : 71 move_left(); 72 break; 73 case ‘s‘: 74 case ‘S‘: 75 case 80 : 76 move_down(); 77 break; 78 case ‘w‘: 79 case ‘W‘: 80 case 72 : 81 move_up(); 82 break; 83 case ‘d‘: 84 case ‘D‘: 85 case 77 : 86 move_right(); 87 break; 88 } 89 90 score > best ? best = score : 1; /* 打破得分纪录 */ 91 92 if (if_need_add_num) /* 默认为需要生成随机数时也同时需要刷新显示,反之亦然 */ 93 { 94 add_rand_num(); 95 refresh_show(); 96 } 97 } 98 } 99 100 /* 重置游戏 函数定义 */ 101 void reset_game() 102 { 103 score = 0; 104 if_need_add_num = 1; 105 if_game_over = 0; 106 107 /* 了解到游戏初始化时出现的两个数一定会有个2,所以先随机生成一个2,其他均为0 */ 108 int n = rand() % 16; 109 for (int i = 0; i < 4; i++) 110 { 111 for (int j = 0; j < 4; j++) 112 { 113 board[i][j] = (n-- == 0 ? 2 : 0); 114 } 115 } 116 117 /* 前面已经生成了一个2,这里再生成一个随机的2或者4,且设定生成2的概率是4的两倍 */ 118 add_rand_num(); 119 120 /* 在这里刷新界面并显示的时候,界面上已经默认出现了两个数字,其他的都为空(值为0) */ 121 system("cls"); 122 refresh_show(); 123 } 124 125 /* 生成随机数 函数定义 */ 126 void add_rand_num() 127 { 128 srand(time(0)); 129 int n = rand() % get_null_count();/* 确定在何处空位置生成随机数 */ 130 for (int i = 0; i < 4; i++) 131 { 132 for (int j = 0; j < 4; j++) 133 { 134 if (board[i][j] == 0 && n-- == 0) /* 定位待生成的位置 */ 135 { 136 board[i][j] = (rand() % 3 ? 2 : 4);/* 确定生成何值,设定生成2的概率是4的概率的两倍 */ 137 return; 138 } 139 } 140 } 141 } 142 143 /* 获取空位置数量 函数定义 */ 144 int get_null_count() 145 { 146 int n = 0; 147 for (int i = 0; i < 4; i++) 148 { 149 for (int j = 0; j < 4; j++) 150 { 151 board[i][j] == 0 ? n++ : 1; 152 } 153 } 154 return n; 155 } 156 157 /* 检查游戏是否结束 函数定义 */ 158 void check_game_over() 159 { 160 for (int i = 0; i < 4; i++) 161 { 162 for (int j = 0; j < 3; j++) 163 { 164 /* 横向和纵向比较挨着的两个元素是否相等,若有相等则游戏不结束 */ 165 if (board[i][j] == board[i][j+1] || board[j][i] == board[j+1][i]) 166 { 167 if_game_over = 0; 168 return; 169 } 170 } 171 } 172 if_game_over = 1; 173 } 174 175 /* 176 * 如下四个函数,实现上下左右移动时数字面板的变化算法 177 * 左和右移动的本质一样,区别仅仅是列项的遍历方向相反 178 * 上和下移动的本质一样,区别仅仅是行项的遍历方向相反 179 * 左和上移动的本质也一样,区别仅仅是遍历时行和列互换 180 */ 181 182 /* 左移 函数定义 */ 183 void move_left() 184 { 185 /* 变量i用来遍历行项的下标,并且在移动时所有行相互独立,互不影响 */ 186 for (int i = 0; i < 4; i++) 187 { 188 /* 变量j为列下标,变量k为待比较(合并)项的下标,循环进入时k<j */ 189 for (int j = 1, k = 0; j < 4; j++) 190 { 191 if (board[i][j] > 0) /* 找出k后面第一个不为空的项,下标为j,之后分三种情况 */ 192 { 193 if (board[i][k] == board[i][j]) /* 情况1:k项和j项相等,此时合并方块并计分 */ 194 { 195 score += board[i][k++] <<= 1; 196 board[i][j] = 0; 197 if_need_add_num = 1; /* 需要生成随机数和刷新界面 */ 198 } 199 else if (board[i][k] == 0) /* 情况2:k项为空,则把j项赋值给k项,相当于j方块移动到k方块 */ 200 { 201 board[i][k] = board[i][j]; 202 board[i][j] = 0; 203 if_need_add_num = 1; 204 } 205 else /* 情况3:k项不为空,且和j项不相等,此时把j项赋值给k+1项,相当于移动到k+1的位置 */ 206 { 207 board[i][++k] = board[i][j]; 208 if (j != k) /* 判断j项和k项是否原先就挨在一起,若不是则把j项赋值为空(值为0) */ 209 { 210 board[i][j] = 0; 211 if_need_add_num = 1; 212 } 213 } 214 } 215 } 216 } 217 } 218 219 /* 右移 函数定义 */ 220 void move_right() 221 { 222 /* 仿照左移操作,区别仅仅是j和k都反向遍历 */ 223 for (int i = 0; i < 4; i++) 224 { 225 for (int j = 2, k = 3; j >= 0; j--) 226 { 227 if (board[i][j] > 0) 228 { 229 if (board[i][k] == board[i][j]) 230 { 231 score += board[i][k--] <<= 1; 232 board[i][j] = 0; 233 if_need_add_num = 1; 234 } 235 else if (board[i][k] == 0) 236 { 237 board[i][k] = board[i][j]; 238 board[i][j] = 0; 239 if_need_add_num = 1; 240 } 241 else 242 { 243 board[i][--k] = board[i][j]; 244 if (j != k) 245 { 246 board[i][j] = 0; 247 if_need_add_num = 1; 248 } 249 } 250 } 251 } 252 } 253 } 254 255 /* 上移 函数定义 */ 256 void move_up() 257 { 258 /* 仿照左移操作,区别仅仅是行列互换后遍历 */ 259 for (int i = 0; i < 4; i++) 260 { 261 for (int j = 1, k = 0; j < 4; j++) 262 { 263 if (board[j][i] > 0) 264 { 265 if (board[k][i] == board[j][i]) 266 { 267 score += board[k++][i] <<= 1; 268 board[j][i] = 0; 269 if_need_add_num = 1; 270 } 271 else if (board[k][i] == 0) 272 { 273 board[k][i] = board[j][i]; 274 board[j][i] = 0; 275 if_need_add_num = 1; 276 } 277 else 278 { 279 board[++k][i] = board[j][i]; 280 if (j != k) 281 { 282 board[j][i] = 0; 283 if_need_add_num = 1; 284 } 285 } 286 } 287 } 288 } 289 } 290 291 /* 下移 函数定义 */ 292 void move_down() 293 { 294 /* 仿照左移操作,区别仅仅是行列互换后遍历,且j和k都反向遍历 */ 295 for (int i = 0; i < 4; i++) 296 { 297 for (int j = 2, k = 3; j >= 0; j--) 298 { 299 if (board[j][i] > 0) 300 { 301 if (board[k][i] == board[j][i]) 302 { 303 score += board[k--][i] <<= 1; 304 board[j][i] = 0; 305 if_need_add_num = 1; 306 } 307 else if (board[k][i] == 0) 308 { 309 board[k][i] = board[j][i]; 310 board[j][i] = 0; 311 if_need_add_num = 1; 312 } 313 else 314 { 315 board[--k][i] = board[j][i]; 316 if (j != k) 317 { 318 board[j][i] = 0; 319 if_need_add_num = 1; 320 } 321 } 322 } 323 } 324 } 325 } 326 327 328 /* 刷新界面 函数定义 */ 329 void refresh_show() 330 { 331 /* 重设光标输出位置方式清屏可以减少闪烁,system("cls")为备用清屏命令,均为Windows平台相关*/ 332 COORD pos = {0, 0}; 333 SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos); 334 335 printf("\n\n\n\n"); 336 printf(" GAME: 2048 SCORE: %06d BEST: %06d\n", score, best); 337 printf(" --------------------------------------------------\n\n"); 338 339 /* 绘制表格和数字 */ 340 printf(" ┌──┬──┬──┬──┐\n"); 341 for (int i = 0; i < 4; i++) 342 { 343 printf(" │"); 344 for (int j = 0; j < 4; j++) 345 { 346 if (board[i][j] != 0) 347 { 348 if (board[i][j] < 10) 349 { 350 printf(" %d │", board[i][j]); 351 } 352 else if (board[i][j] < 100) 353 { 354 printf(" %d │", board[i][j]); 355 } 356 else if (board[i][j] < 1000) 357 { 358 printf(" %d│", board[i][j]); 359 } 360 else if (board[i][j] < 10000) 361 { 362 printf("%4d│", board[i][j]); 363 } 364 else 365 { 366 int n = board[i][j]; 367 for (int k = 1; k < 20; k++) 368 { 369 n >>= 1; 370 if (n == 1) 371 { 372 printf("2^%02d│", k); /* 超过四位的数字用2的幂形式表示,如2^13形式 */ 373 break; 374 } 375 } 376 } 377 } 378 else printf(" │"); 379 } 380 381 if (i < 3) 382 { 383 printf("\n ├──┼──┼──┼──┤\n"); 384 } 385 else 386 { 387 printf("\n └──┴──┴──┴──┘\n"); 388 } 389 } 390 391 printf("\n"); 392 printf(" --------------------------------------------------\n"); 393 printf(" W↑ A← →D ↓S"); 394 395 if (get_null_count() == 0) 396 { 397 check_game_over(); 398 if (if_game_over) /* 判断是否输掉游戏 */ 399 { 400 printf("\r GAME OVER! TRY THE GAME AGAIN? [Y/N]"); 401 } 402 } 403 }
运行界面如下,仅供读者参考玩乐: