完整源代码如下,敬请读者批评指正:
1 /*
2 * Copyright (C) Judge Young
3 * E-mail: [email protected]
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 }
运行界面如下,仅供读者参考玩乐:
游戏2048源代码 - C语言控制台界面版,布布扣,bubuko.com
时间: 2024-12-22 18:06:48