1// snake_game.c
2// ์์ฑ๋ ๋ฑ ๊ฒ์ ๊ตฌํ
3// ANSI escape codes๋ฅผ ์ฌ์ฉํ ํฐ๋ฏธ๋ ๊ธฐ๋ฐ Snake ๊ฒ์์
๋๋ค.
4//
5// ์ปดํ์ผ: gcc -o snake_game snake_game.c
6// ์คํ: ./snake_game
7
8#include <stdio.h>
9#include <stdlib.h>
10#include <stdbool.h>
11#include <time.h>
12#include <unistd.h>
13#include <termios.h>
14#include <string.h>
15
16// ============ ๊ฒ์ ์ค์ ============
17#define WIDTH 40
18#define HEIGHT 20
19#define INITIAL_SPEED 150000 // ๋ง์ดํฌ๋ก์ด (150ms)
20#define MIN_SPEED 50000 // ์ต์ 50ms
21
22// ============ ANSI ์ ์ด ์ฝ๋ ============
23#define CLEAR "\033[2J"
24#define HOME "\033[H"
25#define HIDE_CURSOR "\033[?25l"
26#define SHOW_CURSOR "\033[?25h"
27#define MOVE(r,c) printf("\033[%d;%dH", r, c)
28
29// ============ ANSI ์์ ์ฝ๋ ============
30#define RESET "\033[0m"
31#define GREEN "\033[32m"
32#define YELLOW "\033[33m"
33#define RED "\033[31m"
34#define CYAN "\033[36m"
35#define MAGENTA "\033[35m"
36#define BOLD "\033[1m"
37
38// ============ ๋ฐฉํฅ ์ด๊ฑฐํ ============
39typedef enum { UP, DOWN, LEFT, RIGHT } Direction;
40
41// ============ ์ขํ ๊ตฌ์กฐ์ฒด ============
42typedef struct {
43 int x, y;
44} Point;
45
46// ============ ๋ฑ ๋
ธ๋ (์ฐ๊ฒฐ ๋ฆฌ์คํธ) ============
47typedef struct Node {
48 Point pos;
49 struct Node* next;
50} Node;
51
52// ============ ๊ฒ์ ์ํ ๊ตฌ์กฐ์ฒด ============
53typedef struct {
54 Node* head; // ๋ฑ ๋จธ๋ฆฌ
55 Node* tail; // ๋ฑ ๊ผฌ๋ฆฌ
56 Direction dir; // ํ์ฌ ๋ฐฉํฅ
57 Point food; // ์์ ์์น
58 int score; // ์ ์
59 int length; // ๋ฑ ๊ธธ์ด
60 bool game_over; // ๊ฒ์ ์ค๋ฒ ํ๋๊ทธ
61 bool paused; // ์ผ์์ ์ง ํ๋๊ทธ
62 int speed; // ๊ฒ์ ์๋
63 int high_score; // ์ต๊ณ ์ ์
64} Game;
65
66// ============ ํฐ๋ฏธ๋ ์ค์ ============
67static struct termios orig_termios;
68
69// ํฐ๋ฏธ๋ ์ค์ ๋ณต์
70void disable_raw_mode(void) {
71 tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
72 printf(SHOW_CURSOR);
73}
74
75// Raw ๋ชจ๋ ํ์ฑํ (non-blocking ์
๋ ฅ)
76void enable_raw_mode(void) {
77 tcgetattr(STDIN_FILENO, &orig_termios);
78 atexit(disable_raw_mode);
79
80 struct termios raw = orig_termios;
81 raw.c_lflag &= ~(ECHO | ICANON); // ์์ฝ ๋๊ธฐ, ๋ผ์ธ ๋ฒํผ๋ง ๋๊ธฐ
82 raw.c_cc[VMIN] = 0; // ์ต์ ์
๋ ฅ ๋ฌธ์ ์ 0
83 raw.c_cc[VTIME] = 0; // ํ์์์ 0 (์ฆ์ ๋ฐํ)
84
85 tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
86 printf(HIDE_CURSOR);
87}
88
89// ============ ์
๋ ฅ ์ฒ๋ฆฌ ============
90
91/**
92 * ํค๋ณด๋ ์
๋ ฅ ์ฝ๊ธฐ ๋ฐ ๋ฐฉํฅ ๋ฐํ
93 * ESC sequence ์ฒ๋ฆฌ (๋ฐฉํฅํค)
94 */
95Direction read_direction(Direction current) {
96 int ch = getchar();
97 if (ch == EOF) return current;
98
99 // ESC sequence (๋ฐฉํฅํค)
100 if (ch == '\033') {
101 if (getchar() == '[') {
102 switch (getchar()) {
103 case 'A': return (current != DOWN) ? UP : current;
104 case 'B': return (current != UP) ? DOWN : current;
105 case 'C': return (current != LEFT) ? RIGHT : current;
106 case 'D': return (current != RIGHT) ? LEFT : current;
107 }
108 }
109 }
110
111 // WASD ํค
112 switch (ch) {
113 case 'w': case 'W': return (current != DOWN) ? UP : current;
114 case 's': case 'S': return (current != UP) ? DOWN : current;
115 case 'a': case 'A': return (current != RIGHT) ? LEFT : current;
116 case 'd': case 'D': return (current != LEFT) ? RIGHT : current;
117 case 'q': case 'Q': return -1; // ์ข
๋ฃ ์ ํธ
118 }
119
120 return current;
121}
122
123// ์ผ์์ ์ง ํค ํ์ธ
124int check_pause_key(void) {
125 int ch = getchar();
126 if (ch == 'p' || ch == 'P') return 1;
127 if (ch == 'q' || ch == 'Q') return -1;
128 return 0;
129}
130
131// ============ ๋ฑ ๊ด๋ จ ํจ์ ============
132
133/**
134 * ํน์ ์์น์ ๋ฑ์ด ์๋์ง ํ์ธ
135 */
136bool snake_at(Node* head, int x, int y) {
137 for (Node* n = head; n; n = n->next) {
138 if (n->pos.x == x && n->pos.y == y) return true;
139 }
140 return false;
141}
142
143/**
144 * ์์ ์์ฑ (๋ฑ๊ณผ ๊ฒน์น์ง ์๋ ์์น)
145 */
146void spawn_food(Game* g) {
147 do {
148 g->food.x = 1 + rand() % (WIDTH - 2);
149 g->food.y = 1 + rand() % (HEIGHT - 2);
150 } while (snake_at(g->head, g->food.x, g->food.y));
151}
152
153// ============ ๊ฒ์ ์ด๊ธฐํ ============
154
155/**
156 * ๊ฒ์ ์ํ ์ด๊ธฐํ
157 */
158Game* game_init(int high_score) {
159 Game* g = malloc(sizeof(Game));
160 if (!g) return NULL;
161
162 // ๋ฑ ์ด๊ธฐํ (๊ธธ์ด 3)
163 g->head = NULL;
164 g->tail = NULL;
165 g->length = 0;
166
167 for (int i = 0; i < 3; i++) {
168 Node* n = malloc(sizeof(Node));
169 if (!n) {
170 // ๋ฉ๋ชจ๋ฆฌ ํ ๋น ์คํจ ์ ์ ๋ฆฌ
171 while (g->head) {
172 Node* temp = g->head;
173 g->head = g->head->next;
174 free(temp);
175 }
176 free(g);
177 return NULL;
178 }
179
180 n->pos.x = WIDTH / 2 - i;
181 n->pos.y = HEIGHT / 2;
182 n->next = g->head;
183 g->head = n;
184 g->length++;
185 }
186
187 // ๊ผฌ๋ฆฌ ์ฐพ๊ธฐ
188 Node* curr = g->head;
189 while (curr->next) curr = curr->next;
190 g->tail = curr;
191
192 // ๊ฒ์ ์ํ ์ด๊ธฐํ
193 g->dir = RIGHT;
194 g->score = 0;
195 g->game_over = false;
196 g->paused = false;
197 g->speed = INITIAL_SPEED;
198 g->high_score = high_score;
199
200 spawn_food(g);
201 return g;
202}
203
204/**
205 * ๊ฒ์ ๋ฉ๋ชจ๋ฆฌ ํด์
206 */
207void game_free(Game* g) {
208 if (!g) return;
209
210 Node* n = g->head;
211 while (n) {
212 Node* next = n->next;
213 free(n);
214 n = next;
215 }
216 free(g);
217}
218
219// ============ ๊ฒ์ ์
๋ฐ์ดํธ ============
220
221/**
222 * ๊ฒ์ ์ํ ์
๋ฐ์ดํธ
223 * ๋ฐํ: true = ์์์ ๋จน์, false = ๋จน์ง ์์
224 */
225bool game_update(Game* g) {
226 if (g->paused || g->game_over) return false;
227
228 // ๋ค์ ๋จธ๋ฆฌ ์์น ๊ณ์ฐ
229 Point next = g->head->pos;
230 switch (g->dir) {
231 case UP: next.y--; break;
232 case DOWN: next.y++; break;
233 case LEFT: next.x--; break;
234 case RIGHT: next.x++; break;
235 }
236
237 // ๋ฒฝ ์ถฉ๋ ๊ฒ์ฌ
238 if (next.x <= 0 || next.x >= WIDTH - 1 ||
239 next.y <= 0 || next.y >= HEIGHT - 1) {
240 g->game_over = true;
241 return false;
242 }
243
244 // ์๊ธฐ ๋ชธ ์ถฉ๋ ๊ฒ์ฌ
245 if (snake_at(g->head, next.x, next.y)) {
246 g->game_over = true;
247 return false;
248 }
249
250 // ์ ๋จธ๋ฆฌ ์ถ๊ฐ
251 Node* new_head = malloc(sizeof(Node));
252 if (!new_head) {
253 g->game_over = true;
254 return false;
255 }
256
257 new_head->pos = next;
258 new_head->next = g->head;
259 g->head = new_head;
260 g->length++;
261
262 // ์์ ํ์ธ
263 if (next.x == g->food.x && next.y == g->food.y) {
264 g->score += 10;
265 spawn_food(g);
266
267 // ์๋ ์ฆ๊ฐ (์ ์ ๋นจ๋ผ์ง)
268 if (g->speed > MIN_SPEED) {
269 g->speed -= 5000;
270 if (g->speed < MIN_SPEED) g->speed = MIN_SPEED;
271 }
272
273 return true;
274 }
275
276 // ์์์ ๋จน์ง ์์์ผ๋ฉด ๊ผฌ๋ฆฌ ์ ๊ฑฐ
277 Node* curr = g->head;
278 while (curr->next && curr->next->next) {
279 curr = curr->next;
280 }
281 if (curr->next) {
282 free(curr->next);
283 curr->next = NULL;
284 g->tail = curr;
285 g->length--;
286 }
287
288 return false;
289}
290
291// ============ ํ๋ฉด ๊ทธ๋ฆฌ๊ธฐ ============
292
293/**
294 * ๊ฒ์ ํ
๋๋ฆฌ ๊ทธ๋ฆฌ๊ธฐ
295 */
296void draw_border(void) {
297 // ์๋จ
298 MOVE(1, 1);
299 printf(CYAN "โ");
300 for (int i = 1; i < WIDTH - 1; i++) printf("โ");
301 printf("โ" RESET);
302
303 // ์ข์ฐ ์ธก๋ฉด
304 for (int i = 2; i < HEIGHT; i++) {
305 MOVE(i, 1);
306 printf(CYAN "โ" RESET);
307 MOVE(i, WIDTH);
308 printf(CYAN "โ" RESET);
309 }
310
311 // ํ๋จ
312 MOVE(HEIGHT, 1);
313 printf(CYAN "โ");
314 for (int i = 1; i < WIDTH - 1; i++) printf("โ");
315 printf("โ" RESET);
316}
317
318/**
319 * ๊ฒ์ ํ๋ฉด ๊ทธ๋ฆฌ๊ธฐ
320 */
321void draw_game(Game* g) {
322 printf(CLEAR HOME);
323
324 draw_border();
325
326 // ์์ ๊ทธ๋ฆฌ๊ธฐ
327 MOVE(g->food.y + 1, g->food.x + 1);
328 printf(RED "โ" RESET);
329
330 // ๋ฑ ๊ทธ๋ฆฌ๊ธฐ
331 bool is_head = true;
332 for (Node* n = g->head; n; n = n->next) {
333 MOVE(n->pos.y + 1, n->pos.x + 1);
334 if (is_head) {
335 printf(BOLD GREEN "โ" RESET); // ๋จธ๋ฆฌ
336 is_head = false;
337 } else {
338 printf(GREEN "โ " RESET); // ๋ชธํต
339 }
340 }
341
342 // ์ ์ ๋ฐ ์ ๋ณด ํ์
343 MOVE(HEIGHT + 1, 1);
344 printf(YELLOW "์ ์: %d | ๊ธธ์ด: %d | ์ต๊ณ : %d" RESET,
345 g->score, g->length, g->high_score);
346
347 MOVE(HEIGHT + 2, 1);
348 printf("์กฐ์: โโโโ ๋๋ WASD | P: ์ผ์์ ์ง | Q: ์ข
๋ฃ");
349
350 if (g->paused) {
351 MOVE(HEIGHT / 2, WIDTH / 2 - 5);
352 printf(BOLD YELLOW "์ผ์์ ์ง" RESET);
353 }
354
355 fflush(stdout);
356}
357
358/**
359 * ๊ฒ์ ์ค๋ฒ ํ๋ฉด
360 */
361void draw_game_over(Game* g) {
362 MOVE(HEIGHT / 2 - 1, WIDTH / 2 - 5);
363 printf(BOLD RED "GAME OVER!" RESET);
364
365 MOVE(HEIGHT / 2, WIDTH / 2 - 7);
366 printf("์ต์ข
์ ์: " YELLOW "%d" RESET, g->score);
367
368 if (g->score > g->high_score) {
369 MOVE(HEIGHT / 2 + 1, WIDTH / 2 - 6);
370 printf(BOLD MAGENTA "โ
์ ๊ธฐ๋ก! โ
" RESET);
371 }
372
373 MOVE(HEIGHT / 2 + 3, WIDTH / 2 - 8);
374 printf("R: ์ฌ์์ | Q: ์ข
๋ฃ");
375
376 fflush(stdout);
377}
378
379// ============ ์ต๊ณ ์ ์ ๊ด๋ฆฌ ============
380
381#define SCORE_FILE ".snake_highscore"
382
383int load_high_score(void) {
384 FILE* f = fopen(SCORE_FILE, "r");
385 if (!f) return 0;
386
387 int score = 0;
388 fscanf(f, "%d", &score);
389 fclose(f);
390 return score;
391}
392
393void save_high_score(int score) {
394 FILE* f = fopen(SCORE_FILE, "w");
395 if (f) {
396 fprintf(f, "%d", score);
397 fclose(f);
398 }
399}
400
401// ============ ๋ฉ์ธ ํจ์ ============
402
403int main(void) {
404 srand(time(NULL));
405 enable_raw_mode();
406
407 int high_score = load_high_score();
408 Game* game = game_init(high_score);
409
410 if (!game) {
411 fprintf(stderr, "๊ฒ์ ์ด๊ธฐํ ์คํจ\n");
412 return 1;
413 }
414
415 draw_game(game);
416
417 // ๋ฉ์ธ ๊ฒ์ ๋ฃจํ
418 while (1) {
419 if (!game->game_over) {
420 // ์
๋ ฅ ์ฒ๋ฆฌ
421 Direction new_dir = read_direction(game->dir);
422
423 if (new_dir == (Direction)-1) {
424 // Q ํค๋ก ์ข
๋ฃ
425 break;
426 }
427
428 game->dir = new_dir;
429
430 // ์ผ์์ ์ง ์ฒ๋ฆฌ
431 int pause_key = check_pause_key();
432 if (pause_key == 1) {
433 game->paused = !game->paused;
434 } else if (pause_key == -1) {
435 break;
436 }
437
438 // ๊ฒ์ ์
๋ฐ์ดํธ ๋ฐ ๊ทธ๋ฆฌ๊ธฐ
439 game_update(game);
440 draw_game(game);
441
442 if (game->game_over) {
443 // ์ต๊ณ ์ ์ ์ ์ฅ
444 if (game->score > game->high_score) {
445 save_high_score(game->score);
446 }
447 draw_game_over(game);
448 }
449 } else {
450 // ๊ฒ์ ์ค๋ฒ ์ํ์์ ํค ์
๋ ฅ ์ฒ๋ฆฌ
451 int ch = getchar();
452 if (ch == 'r' || ch == 'R') {
453 // ์ฌ์์
454 int final_high = (game->score > game->high_score) ?
455 game->score : game->high_score;
456 game_free(game);
457 game = game_init(final_high);
458 if (!game) break;
459 draw_game(game);
460 } else if (ch == 'q' || ch == 'Q') {
461 // ์ข
๋ฃ
462 break;
463 }
464 }
465
466 usleep(game->speed);
467 }
468
469 game_free(game);
470
471 // ํ๋ฉด ์ ๋ฆฌ
472 printf(CLEAR HOME SHOW_CURSOR);
473 MOVE(1, 1);
474 printf("๊ฒ์์ ์ข
๋ฃํฉ๋๋ค. ํ๋ ์ดํด์ฃผ์
์ ๊ฐ์ฌํฉ๋๋ค!\n");
475
476 return 0;
477}