大一上C语言小作业——2048小游戏

大一上C语言小作业——2048小游戏

项目要求:使用C语言编写2048小游戏

界面设置部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void HideCursor() {            //隐藏光标
CONSOLE_CURSOR_INFO curInfo;
curInfo.dwSize = 1;
curInfo.bVisible = FALSE;
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorInfo(handle, &curInfo);
}

void Goto(int x, int y) { //光标移动到(x,y)位置
COORD pos;
pos.X = x;
pos.Y = y;
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(handle, pos);
}

void PreWork() {
system("title 2048"); //窗口标题为2048
system("mode con cols=40 lines=21"); //窗口大小
system("chcp 65001 > nul"); //中文支持
HideCursor(); //隐藏光标
CONSOLE_FONT_INFOEX cfi;
cfi.cbSize = sizeof cfi;
cfi.nFont = 0;
cfi.dwFontSize.X = 15;
cfi.dwFontSize.Y = 30;
cfi.FontFamily = FF_DONTCARE;
cfi.FontWeight = FW_NORMAL;
SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi);
srand((unsigned) time(NULL)); //随机函数种子
}

游戏初始化

游戏”棋盘“初始化要做的事:

  • 清空“棋盘”
  • 生成两个数(2或4),其中生成2的概率较大
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void InitGameMap() {
score = 0;
memset(gameMap, -1, sizeof(gameMap));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
gameMap[i][j] = 0;
}
}
int total = 0;
while (total < 2) {
int x = rand() % 4 + 1;
int y = rand() % 4 + 1;
int temp = rand() % 6 + 1; //temp 的范围是[1, 6]
int num = 2;
if (temp == 1) num = 4; //设置1/6的概率生成数字4
if (!gameMap[x][y]) {
gameMap[x][y] = num;
total++;
}
}
}

移动

2048游戏中最关键的就是处理按下方向键后,棋盘中数字的移动。以向左移动为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void MoveLeft() {
for (int i = 1; i <= n; i++) { //处理第i行
int k = 1;
for (int j = 2; j <= n; j++) {
if (gameMap[i][j]) {
if (!gameMap[i][k]) { // gameMap[i,k] is empty
gameMap[i][k] = gameMap[i][j];
gameMap[i][j] = 0;
} else if (gameMap[i][k] != gameMap[i][j]) {
if (k + 1 != j) {
gameMap[i][k + 1] = gameMap[i][j];
gameMap[i][j] = 0;
}
k++;
} else { //merge:gameMap[i][k] ==gameMap[i][j]
gameMap[i][k] <<= 1;
gameMap[i][j] = 0;
score += gameMap[i][k];
k++; // the number can't be merged again
}
}
}
}
}

其余移动原理相同,不再赘述。

判断游戏输赢

CheckGame()函数用来判断游戏输赢:

  • 若棋盘上有数字达到2048则胜利
  • 若棋盘已满且无法移动(任何两个相邻的数都不相同)则失败
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int CheckGame() {
int count_of_zero = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
if (!gameMap[i][j]) count_of_zero++;
if (gameMap[i][j] == 2048)
return 1; // win
}
if (count_of_zero)
return 0; // continue
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (gameMap[i][j] == gameMap[i + 1][j]
|| gameMap[i][j] == gameMap[i - 1][j]
|| gameMap[i][j] == gameMap[i][j + 1]
|| gameMap[i][j] == gameMap[i][j - 1])
return 0; // continue
return -1; // lose
}

打印棋盘&更新棋盘

  • 尽量减少system("pause")语句的运用,能减少屏幕闪烁的情况。
  • PrintNumber函数用来格式化地打印格子中的数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void PrintNumber(int x) {
if (x < 10)
printf(" %d │", x);
else if (x < 100)
printf(" %d │", x);
else printf("%4d│", x);
}

void ShowMap() {
system("cls");
Goto(5, 0);
if (score > best) best = score;
printf("Score: %6d Best: %6d\n", score, best);
printf(" _______________________ \n\n");
printf(" ┌────┬────┬────┬────┐\n");
for (int i = 1; i <= n; i++) {
printf(" │");
for (int j = 1; j <= n; j++) {
if (gameMap[i][j])
PrintNumber(gameMap[i][j]);
else printf(" │");
}
printf("\n");
if (i <= 3)
printf(" ├────┼────┼────┼────┤\n");
else
printf(" └────┴────┴────┴────┘\n");
}
printf(" _______________________ \n");
printf("[Q] Back to menu.");
}

void UpdateMap() { //棋盘不用重新打印
Goto(5, 0);
if (score > best) best = score;
printf("Score: %6d Best: %6d\n", score, best);
Goto(0, 3);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
Goto(3 + 5 * j, i * 2 + 2);
if (gameMap[i][j])
PrintNumber(gameMap[i][j]);
else printf(" ");//4:
}
printf("\n");
}
}

游戏进行

  • 判断是新游戏模式还是旧游戏模式,如果是新游戏,则要初始化棋盘
  • 处理键盘输入,进行数字的移动并生成新的数字
  • 每次判断游戏是否结束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
void Game(int game_type) {
if (game_type == 1) {
InitGameMap(); //new game
have_old_game = 1;
}
int haveMap = 0;
while (1) {
if (!haveMap) {
ShowMap();
haveMap = 1;
} else {
UpdateMap();
}
int state = CheckGame();
if (CheckGame() == 1) {
printf("\n You Win!\n");
system("pause");
have_old_game = 0;
break;
} else if (CheckGame() == -1) {
printf("\n You Lose!\n");
have_old_game = 0;
system("pause");
break;
}
char type;
int flag = 0;
type = getch();
switch (type) {
case UP:
case 'w':
MoveUp();
flag = 1;
break;
case DOWN:
case 's':
MoveDown();
flag = 1;
break;
case LEFT:
case 'a':
MoveLeft();
flag = 1;
break;
case RIGHT:
case 'd':
MoveRight();
flag = 1;
break;
case 'q':
case 'Q':
haveMap = 0;
return;
}
if (flag) {
int count_of_zero = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (!gameMap[i][j]) count_of_zero++;
if (count_of_zero > 0) {
int total = 0;
while (total < 1) {
int x = rand() % 4 + 1;
int y = rand() % 4 + 1;
int temp = rand() % 6 + 1;
int num = 2;
if (temp == 1) num = 4;
if (!gameMap[x][y]) {
gameMap[x][y] = num;
total++;
}
}
}
}
}
}

菜单界面

菜单界面Menu()要处理通过上下键或[W][S]键选择模式。ShowMenu(x)函数用来打印当前选择了号模式时的菜单界面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
void ShowMenu(int x) {
system("cls");
int print_X = 14;
Goto(0, 0);
printf(" ____ ___ _ _ ___ \n");
printf(" |___ \\ / _ \\| || | ( _ ) \n");
printf(" __) | | | | || |_ / _ \\ \n");
printf(" / __/| |_| |__ _| (_) |\n");
printf(" |_____|\\___/ |_| \\___/ \n");
Goto(print_X, 7);
printf("1.New Game\n");
Goto(print_X, 8);
printf("2.Old Game\n");
Goto(print_X, 9);
printf("3.Exit\n");
Goto(print_X - 1, 6 + x);
printf(">");
Goto(3, 11);
printf("Please type [UP][DOWN] or [w][s] \nto choose your game.\n Type [ENTER] to enter the game.");
}

void Menu() {
int now_point = 1;
int last_point = 0;
int haveMenu = 0;
while (1) {
char type;
int flag = 0;
if (!haveMenu) {
ShowMenu(now_point);
haveMenu = 1;
} else {
Goto(14 - 1, 6 + now_point);
printf(">");
Goto(14 - 1, 6 + last_point);
printf(" ");
}
type = getch();
switch (type) {
case UP:
case 'w':
flag = 1;
last_point = now_point;
now_point = (now_point + 1) % 3 + 1;
break;
case DOWN:
case 's':
flag = 1;
last_point = now_point;
now_point = now_point % 3 + 1;
break;
case ENTER:
if (now_point <= 2) {
haveMenu = 0;
Game(now_point);
} else {
return;
}
break;
}
}
}

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <time.h>
#include <string.h>
#include <conio.h>

#define ROW 6
#define COL 6
#define MAP_ROW 40
#define MAP_COL 30

#define UP 72
#define DOWN 80
#define LEFT 75
#define RIGHT 77
#define ENTER 13

const int n = 4;
int score = 0;
int best = 0;
int gameMap[ROW][COL];
int have_old_game = 0;

void HideCursor();

void Goto(int x, int y);

void InitGameMap();

void PreWork();

void ShowMap();

void UpdateMap();

void Game(int game_type); // begin the game

void MoveLeft();

void MoveRight();

void MoveUp();

void MoveDown();

void ShowMenu(int x);

void Menu();

void PrintNumber(int x);

int CheckGame();

int main() {
PreWork();
Menu();
return 0;
}

void HideCursor() {
CONSOLE_CURSOR_INFO curInfo;
curInfo.dwSize = 1;
curInfo.bVisible = FALSE;
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorInfo(handle, &curInfo);
}

void Goto(int x, int y) {
COORD pos;
pos.X = x;
pos.Y = y;
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(handle, pos);
}

void PreWork() {
system("title 2048");
system("mode con cols=40 lines=21");
system("chcp 65001 > nul"); //Chinese
HideCursor();
CONSOLE_FONT_INFOEX cfi;
cfi.cbSize = sizeof cfi;
cfi.nFont = 0;
cfi.dwFontSize.X = 15;
cfi.dwFontSize.Y = 30;
cfi.FontFamily = FF_DONTCARE;
cfi.FontWeight = FW_NORMAL;
SetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi);
srand((unsigned) time(NULL));
}

void InitGameMap() {
score = 0;
memset(gameMap, -1, sizeof(gameMap));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
gameMap[i][j] = 0;
}
}
int total = 0;
while (total < 2) {
int x = rand() % 4 + 1;
int y = rand() % 4 + 1;
int temp = rand() % 6 + 1;
int num = 2;
if (temp == 1) num = 4;
if (!gameMap[x][y]) {
gameMap[x][y] = num;
total++;
}
}
}

void PrintNumber(int x) {
if (x < 10)
printf(" %d │", x);
else if (x < 100)
printf(" %d │", x);
else printf("%4d│", x);
}

void ShowMap() {
system("cls");
Goto(5, 0);
if (score > best) best = score;
printf("Score: %6d Best: %6d\n", score, best);
printf(" _______________________ \n\n");
printf(" ┌────┬────┬────┬────┐\n");
for (int i = 1; i <= n; i++) {
printf(" │");
for (int j = 1; j <= n; j++) {
if (gameMap[i][j])
PrintNumber(gameMap[i][j]);
else printf(" │");
}
printf("\n");
if (i <= 3)
printf(" ├────┼────┼────┼────┤\n");
else
printf(" └────┴────┴────┴────┘\n");
}
printf(" _______________________ \n");
printf("[Q] Back to menu.");
}

void UpdateMap() {
Goto(5, 0);
if (score > best) best = score;
printf("Score: %6d Best: %6d\n", score, best);
Goto(0, 3);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
Goto(3 + 5 * j, i * 2 + 2);
if (gameMap[i][j])
PrintNumber(gameMap[i][j]);
else printf(" ");//4:
}
printf("\n");
}
}

void MoveLeft() {
for (int i = 1; i <= n; i++) {
int k = 1;
for (int j = 2; j <= n; j++) {
if (gameMap[i][j]) {
if (!gameMap[i][k]) { // gameMap[i,k] is empty
gameMap[i][k] = gameMap[i][j];
gameMap[i][j] = 0;
} else if (gameMap[i][k] != gameMap[i][j]) {
if (k + 1 != j) {
gameMap[i][k + 1] = gameMap[i][j];
gameMap[i][j] = 0;
}
k++;
} else { //merge:gameMap[i][k] ==gameMap[i][j]
gameMap[i][k] <<= 1;
gameMap[i][j] = 0;
score += gameMap[i][k];
k++; // the number can't be merged again
}
}
}
}
}

void MoveRight() {
for (int i = 1; i <= n; i++) {
int k = n;
for (int j = n - 1; j >= 1; j--) {
if (gameMap[i][j]) {
if (!gameMap[i][k]) { // gameMap[i,k] is empty
gameMap[i][k] = gameMap[i][j];
gameMap[i][j] = 0;
} else if (gameMap[i][k] != gameMap[i][j]) {
if (k - 1 != j) {
gameMap[i][k - 1] = gameMap[i][j];
gameMap[i][j] = 0;
}
k--;
} else { //merge:gameMap[i][k] ==gameMap[i][j]
gameMap[i][k] <<= 1;
gameMap[i][j] = 0;
score += gameMap[i][k];
k--; // the number can't be merged again
}
}
}
}
}

void MoveUp() {
for (int i = 1; i <= n; i++) {
int k = 1;
for (int j = 2; j <= n; j++) {
if (gameMap[j][i]) {
if (!gameMap[k][i]) { // gameMap[i,k] is empty
gameMap[k][i] = gameMap[j][i];
gameMap[j][i] = 0;
} else if (gameMap[k][i] != gameMap[j][i]) {
if (k + 1 != j) {
gameMap[k + 1][i] = gameMap[j][i];
gameMap[j][i] = 0;
}
k++;
} else { //merge:gameMap[i][k] ==gameMap[i][j]
gameMap[k][i] <<= 1;
gameMap[j][i] = 0;
score += gameMap[k][i];
k++; // the number can't be merged again
}
}
}
}
}

void MoveDown() {
for (int i = 1; i <= n; i++) {
int k = n;
for (int j = n - 1; j >= 1; j--) {
if (gameMap[j][i]) {
if (!gameMap[k][i]) { // gameMap[i,k] is empty
gameMap[k][i] = gameMap[j][i];
gameMap[j][i] = 0;
} else if (gameMap[k][i] != gameMap[j][i]) {
if (k - 1 != j) {
gameMap[k - 1][i] = gameMap[j][i];
gameMap[j][i] = 0;
}
k--;
} else { // merge:gameMap[i][k] ==gameMap[i][j]
gameMap[k][i] <<= 1;
gameMap[j][i] = 0;
score += gameMap[k][i];
k--; // the number can't be merged again
}
}
}
}
}

/**
* check if the player wins or loses the game.
* @return 1 if win,-1 if lose, 0 if continue
*/
int CheckGame() {
int count_of_zero = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
if (!gameMap[i][j]) count_of_zero++;
if (gameMap[i][j] == 2048)
return 1; // win
}
if (count_of_zero)
return 0; // continue
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (gameMap[i][j] == gameMap[i + 1][j]
|| gameMap[i][j] == gameMap[i - 1][j]
|| gameMap[i][j] == gameMap[i][j + 1]
|| gameMap[i][j] == gameMap[i][j - 1])
return 0; // continue
return -1; // lose
}

void Game(int game_type) {
if (game_type == 1) {
InitGameMap(); //new game
have_old_game = 1;
}
int haveMap = 0;
while (1) {
if (!haveMap) {
ShowMap();
haveMap = 1;
} else {
UpdateMap();
}
int state = CheckGame();
if (CheckGame() == 1) {
printf("\n You Win!\n");
system("pause");
have_old_game = 0;
break;
} else if (CheckGame() == -1) {
printf("\n You Lose!\n");
have_old_game = 0;
system("pause");
break;
}
char type;
int flag = 0;
type = getch();
switch (type) {
case UP:
case 'w':
MoveUp();
flag = 1;
break;
case DOWN:
case 's':
MoveDown();
flag = 1;
break;
case LEFT:
case 'a':
MoveLeft();
flag = 1;
break;
case RIGHT:
case 'd':
MoveRight();
flag = 1;
break;
case 'q':
case 'Q':
haveMap = 0;
return;
}
if (flag) {
int count_of_zero = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (!gameMap[i][j]) count_of_zero++;
if (count_of_zero > 0) {
int total = 0;
while (total < 1) {
int x = rand() % 4 + 1;
int y = rand() % 4 + 1;
int temp = rand() % 6 + 1;
int num = 2;
if (temp == 1) num = 4;
if (!gameMap[x][y]) {
gameMap[x][y] = num;
total++;
}
}
}
}
}
}

void ShowMenu(int x) {
system("cls");
int print_X = 14;
/*Goto(16, 3);
printf("2 0 4 8\n");*/
Goto(0, 0);
printf(" ____ ___ _ _ ___ \n");
printf(" |___ \\ / _ \\| || | ( _ ) \n");
printf(" __) | | | | || |_ / _ \\ \n");
printf(" / __/| |_| |__ _| (_) |\n");
printf(" |_____|\\___/ |_| \\___/ \n");
Goto(print_X, 7);
printf("1.New Game\n");
Goto(print_X, 8);
printf("2.Old Game\n");
Goto(print_X, 9);
printf("3.Exit\n");
Goto(print_X - 1, 6 + x);
printf(">");
Goto(3, 11);
printf("Please type [UP][DOWN] or [w][s] \nto choose your game.\n Type [ENTER] to enter the game.");
}

void Menu() {
int now_point = 1;
int last_point = 0;
int haveMenu = 0;
while (1) {
char type;
int flag = 0;
if (!haveMenu) {
ShowMenu(now_point);
haveMenu = 1;
} else {
Goto(14 - 1, 6 + now_point);
printf(">");
Goto(14 - 1, 6 + last_point);
printf(" ");
}
type = getch();
switch (type) {
case UP:
case 'w':
flag = 1;
last_point = now_point;
now_point = (now_point + 1) % 3 + 1;
break;
case DOWN:
case 's':
flag = 1;
last_point = now_point;
now_point = now_point % 3 + 1;
break;
case ENTER:
if (now_point <= 2) {
haveMenu = 0;
Game(now_point);
} else {
return;
}
break;
}
}
}