Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
zmx0142857
GitHub Repository: zmx0142857/mini-games
Path: blob/master/c/sokoban/sokoban.c
362 views
1
#include <string.h> // memset(), strcpy()
2
#include <ctype.h> // isdigit()
3
#include "game.h"
4
5
// 常量
6
#define MAP_W 17
7
#define MAP_H 13
8
9
#define SPACE ' '
10
#define WALL '#'
11
#define HERO '@'
12
#define BOX '$'
13
#define STAR '.'
14
#define BOX_ON_STAR '*'
15
#define HERO_ON_STAR '!'
16
17
// 全局变量
18
char map[MAP_H][MAP_W] = {{0}}; // 地图
19
int hero[2] = {0}; // 人的行列坐标分别是 hero[0], hero[1]
20
#define hero_y hero[0]
21
#define hero_x hero[1]
22
23
char level_name[256]; // 关卡名
24
char file_name[256]; // 关卡文件
25
int move_cnt = 0; // 移动的次数
26
int push_cnt = 0; // 推箱子的次数
27
int misplaced_box_cnt = 0; // 未归位的箱子数
28
29
// 函数
30
31
void map_print()
32
{
33
for (int i = 0; i < MAP_H; ++i, putchar('\n'))
34
for (int j = 0; j < MAP_W; ++j)
35
switch (map[i][j]) {
36
case HERO: case HERO_ON_STAR:
37
color_print(FG_CYAN, "囧"); break;
38
case WALL: color_print(FG_WHITE, SYM_BLOCK); break;
39
case BOX: color_print(FG_WHITE, "田"); break;
40
case BOX_ON_STAR: color_print(FG_YELLOW, "田"); break;
41
case STAR: color_print(FG_YELLOW, SYM_STAR); break;
42
default: printf(" ");
43
}
44
color_print(FG_WHITE,
45
"\n\n"
46
" 关卡 %s 移动%3d 推箱%3d\n\n"
47
" R - 重新来过\n"
48
" <ESC> - 回主画面\n",
49
level_name, move_cnt, push_cnt
50
);
51
cursor_goto(0, 0);
52
}
53
54
void scene_welcome()
55
{
56
screen_clear();
57
58
set_color(FG_BLUE);
59
char *buf[] = {
60
" # # # # # #####",
61
" ####### ### ### #",
62
" # # # # # # # #",
63
" ###### ### ### #######",
64
" ## # # # # # #",
65
" ## #### ### ### #",
66
" # #### # ### ##"
67
};
68
for (int i = 0; i < sizeof(buf)/sizeof(char*); ++i) {
69
for (char *p = buf[i]; *p != '\0'; ++p) {
70
if (*p == WALL)
71
printf("田");
72
else
73
putchar(*p);
74
}
75
putchar('\n');
76
}
77
color_print(FG_WHITE, "\n\n\n"
78
" 1 开始游戏\n\n"
79
" 2 选择关卡\n\n"
80
" 3 游戏帮助\n\n"
81
" 4 退出游戏\n");
82
}
83
84
void scene_help()
85
{
86
screen_clear();
87
88
printf("\n\n");
89
color_print(FG_CYAN, " 囧 ");
90
color_print(FG_WHITE, " 你\n\n");
91
92
color_print(FG_WHITE, " " SYM_BLOCK " ");
93
color_print(FG_WHITE, " 墙\n\n");
94
95
color_print(FG_WHITE, " 田 ");
96
color_print(FG_WHITE, " 箱子 你无法同时推动两个箱子!\n\n");
97
98
color_print(FG_YELLOW," " SYM_STAR " ");
99
color_print(FG_WHITE, " 目标 将所有箱子推到目标上!\n\n");
100
101
color_print(FG_YELLOW," 田 ");
102
color_print(FG_WHITE, " 就位的箱子\n\n");
103
104
printf(" " SYM_ARROWS " 移动\n\n"
105
" R 重新来过\n\n"
106
" <ESC> 回主画面\n");
107
108
cursor_goto(0, 0);
109
getch();
110
}
111
112
bool level_load()
113
{
114
FILE *fin = fopen(file_name, "r");
115
if (!fin)
116
return 0;
117
118
memset(map, SPACE, sizeof(map));
119
misplaced_box_cnt = 0;
120
move_cnt = 0;
121
push_cnt = 0;
122
123
int ch, x = 0, y = 0;
124
while ((ch = fgetc(fin)) != EOF) {
125
switch (ch) {
126
case '\n':
127
x = 0;
128
++y;
129
continue;
130
case HERO: case HERO_ON_STAR:
131
hero_y = y;
132
hero_x = x;
133
break;
134
case STAR: case WALL: case BOX_ON_STAR:
135
break;
136
case BOX:
137
++misplaced_box_cnt;
138
break;
139
default:
140
ch = SPACE;
141
}
142
map[y][x++] = ch;
143
}
144
fclose(fin);
145
146
Sleep(255);
147
screen_clear();
148
map_print();
149
return 1;
150
}
151
152
bool is_game_won()
153
{
154
return misplaced_box_cnt == 0;
155
}
156
157
// 若要回到主画面, 则返回 1
158
bool control()
159
{
160
char key = getch();
161
if (key == 'u' || key == 'U') { // U - 撤消
162
;
163
} else if (key == 'r' || key == 'R') { // R - 重新来过
164
level_load();
165
#ifdef __MINGW32__
166
} else if (key == 27) { // <ESC> - 回主画面
167
return 1;
168
} else if (key == -32) { // 方向键
169
key = getch();
170
if (key != 'H' && key != 'P' && key != 'K' && key != 'M')
171
return 0;
172
173
int dy = key == 'H' ? -1 : (key == 'P' ? 1 : 0);
174
int dx = key == 'K' ? -1 : (key == 'M' ? 1 : 0);
175
#endif
176
#ifdef __linux__
177
} else if (key == 27) { // 方向键
178
key = getch();
179
if (key != '[')
180
return 1;
181
key = getch();
182
if (key != 'A' && key != 'B' && key != 'C' && key != 'D')
183
return 0;
184
185
int dy = key == 'A' ? -1 : (key == 'B' ? 1 : 0);
186
int dx = key == 'D' ? -1 : (key == 'C' ? 1 : 0);
187
#endif
188
int next_y = hero_y + dy;
189
int next_x = hero_x + dx;
190
191
bool hero_on_star = map[hero_y][hero_x] == HERO_ON_STAR;
192
bool next_space = map[next_y][next_x] == SPACE;
193
bool next_star = map[next_y][next_x] == STAR;
194
bool push_space = map[next_y][next_x] == BOX;
195
bool push_star = map[next_y][next_x] == BOX_ON_STAR;
196
bool push_to_space = map[next_y + dy][next_x + dx] == SPACE;
197
bool push_to_star = map[next_y + dy][next_x + dx] == STAR;
198
bool update_hero = 0;
199
200
if (next_space || next_star) {
201
update_hero = 1;
202
} else if ((push_space || push_star)
203
&& (push_to_space || push_to_star)) {
204
update_hero = 1;
205
map[next_y + dy][next_x + dx] = push_to_star ? BOX_ON_STAR : BOX;
206
color_mvprint(
207
(push_to_star ? FG_YELLOW : FG_WHITE),
208
next_y + dy, 2*(next_x + dx),
209
"田"
210
);
211
212
if (push_star)
213
++misplaced_box_cnt;
214
if (push_to_star)
215
--misplaced_box_cnt;
216
color_mvprint(FG_WHITE, MAP_H + 2, 30, "%2d", ++push_cnt);
217
}
218
219
if (update_hero) {
220
map[hero_y][hero_x] = hero_on_star ? STAR : SPACE;
221
map[next_y][next_x] = next_star || push_star ? HERO_ON_STAR : HERO;
222
color_mvprint(
223
FG_YELLOW,
224
hero_y, 2*hero_x,
225
hero_on_star ? "★" : " "
226
);
227
color_mvprint(
228
FG_CYAN,
229
next_y, 2*next_x,
230
"囧"
231
);
232
hero_y += dy;
233
hero_x += dx;
234
color_mvprint(FG_WHITE, MAP_H + 2, 20, "%2d", ++move_cnt);
235
}
236
}
237
return 0;
238
}
239
240
// max level number is 999
241
void play(const char *levelname)
242
{
243
strcpy(level_name, levelname);
244
// check if levelname is number
245
bool is_number = 1;
246
for (const char *p = levelname; *p != '\0'; ++p) {
247
if (!isdigit(*p)) {
248
is_number = 0;
249
break;
250
}
251
}
252
if (is_number) {
253
int i = atoi(levelname);
254
sprintf(file_name, "level/%03d.level", i);
255
while (level_load()) {
256
do {
257
if (control())
258
return;
259
} while (!is_game_won());
260
sprintf(file_name, "level/%03d.level", ++i);
261
sprintf(level_name, "%d", i);
262
}
263
} else {
264
sprintf(file_name, "level/%s.level", levelname);
265
if (level_load()) {
266
do {
267
if (control())
268
return;
269
} while (!is_game_won());
270
}
271
}
272
}
273
274
void scene_level_select()
275
{
276
screen_clear();
277
printf("\n\n\n\n\n\n\n\n\t\t选择关卡: ");
278
char buf[256];
279
scanf("%s", buf);
280
play(buf);
281
}
282
283
int main()
284
{
285
game_init();
286
screen_size(20, 48);
287
bool is_game_over = false;
288
while (!is_game_over) {
289
scene_welcome();
290
switch (getch()) {
291
case '1': play("1"); break;
292
case '2': scene_level_select(); break;
293
case '3': scene_help(); break;
294
case '4': is_game_over = true; break;
295
default: continue;
296
}
297
}
298
game_over();
299
return 0;
300
}
301
302