snake_game.c

Download
c 478 lines 11.3 KB
  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}