snake_ncurses.c

Download
c 471 lines 10.7 KB
  1// snake_ncurses.c
  2// NCurses ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ ๋ฑ€ ๊ฒŒ์ž„
  3//
  4// *** ์ด ํŒŒ์ผ์€ ncurses ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค ***
  5//
  6// ์„ค์น˜ ๋ฐฉ๋ฒ•:
  7//   macOS:   brew install ncurses
  8//   Ubuntu:  sudo apt install libncurses5-dev
  9//   Fedora:  sudo dnf install ncurses-devel
 10//
 11// ์ปดํŒŒ์ผ:
 12//   macOS:   gcc -o snake_ncurses snake_ncurses.c -lncurses
 13//   Linux:   gcc -o snake_ncurses snake_ncurses.c -lncurses
 14//
 15// ์‹คํ–‰:
 16//   ./snake_ncurses
 17
 18#include <ncurses.h>
 19#include <stdlib.h>
 20#include <time.h>
 21#include <unistd.h>
 22#include <stdbool.h>
 23
 24// ============ ๊ฒŒ์ž„ ์„ค์ • ============
 25#define WIDTH 40
 26#define HEIGHT 20
 27#define INITIAL_SPEED 150000  // 150ms
 28#define MIN_SPEED 50000       // 50ms
 29
 30// ============ ์ƒ‰์ƒ ์ •์˜ ============
 31enum {
 32    COLOR_SNAKE = 1,
 33    COLOR_FOOD,
 34    COLOR_BORDER,
 35    COLOR_TEXT
 36};
 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// ============ ์ „์—ญ ๋ณ€์ˆ˜ ============
 67WINDOW* game_win;
 68WINDOW* info_win;
 69
 70// ============ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ ============
 71
 72/**
 73 * ํŠน์ • ์œ„์น˜์— ๋ฑ€์ด ์žˆ๋Š”์ง€ ํ™•์ธ
 74 */
 75bool snake_at(Node* head, int x, int y) {
 76    for (Node* n = head; n; n = n->next) {
 77        if (n->pos.x == x && n->pos.y == y) return true;
 78    }
 79    return false;
 80}
 81
 82/**
 83 * ์Œ์‹ ์ƒ์„ฑ
 84 */
 85void spawn_food(Game* g) {
 86    do {
 87        g->food.x = 1 + rand() % (WIDTH - 2);
 88        g->food.y = 1 + rand() % (HEIGHT - 2);
 89    } while (snake_at(g->head, g->food.x, g->food.y));
 90}
 91
 92// ============ ๊ฒŒ์ž„ ์ดˆ๊ธฐํ™” ============
 93
 94/**
 95 * NCurses ์ดˆ๊ธฐํ™”
 96 */
 97void init_ncurses(void) {
 98    initscr();              // NCurses ์‹œ์ž‘
 99    cbreak();               // ๋ผ์ธ ๋ฒ„ํผ๋ง ๋„๊ธฐ
100    noecho();               // ์ž…๋ ฅ ๋ฌธ์ž ํ‘œ์‹œ ์•ˆํ•จ
101    nodelay(stdscr, TRUE);  // Non-blocking ์ž…๋ ฅ
102    keypad(stdscr, TRUE);   // ๋ฐฉํ–ฅํ‚ค ํ™œ์„ฑํ™”
103    curs_set(0);            // ์ปค์„œ ์ˆจ๊ธฐ๊ธฐ
104
105    // ์ƒ‰์ƒ ์ดˆ๊ธฐํ™”
106    if (has_colors()) {
107        start_color();
108        init_pair(COLOR_SNAKE, COLOR_GREEN, COLOR_BLACK);
109        init_pair(COLOR_FOOD, COLOR_RED, COLOR_BLACK);
110        init_pair(COLOR_BORDER, COLOR_CYAN, COLOR_BLACK);
111        init_pair(COLOR_TEXT, COLOR_YELLOW, COLOR_BLACK);
112    }
113
114    // ๊ฒŒ์ž„ ์œˆ๋„์šฐ ์ƒ์„ฑ
115    game_win = newwin(HEIGHT, WIDTH, 1, 2);
116    info_win = newwin(3, WIDTH, HEIGHT + 2, 2);
117}
118
119/**
120 * NCurses ์ข…๋ฃŒ
121 */
122void cleanup_ncurses(void) {
123    delwin(game_win);
124    delwin(info_win);
125    endwin();
126}
127
128/**
129 * ๊ฒŒ์ž„ ์ดˆ๊ธฐํ™”
130 */
131Game* game_init(int high_score) {
132    Game* g = malloc(sizeof(Game));
133    if (!g) return NULL;
134
135    // ๋ฑ€ ์ดˆ๊ธฐํ™” (๊ธธ์ด 3)
136    g->head = NULL;
137    g->tail = NULL;
138    g->length = 0;
139
140    for (int i = 0; i < 3; i++) {
141        Node* n = malloc(sizeof(Node));
142        if (!n) {
143            while (g->head) {
144                Node* temp = g->head;
145                g->head = g->head->next;
146                free(temp);
147            }
148            free(g);
149            return NULL;
150        }
151
152        n->pos.x = WIDTH / 2 - i;
153        n->pos.y = HEIGHT / 2;
154        n->next = g->head;
155        g->head = n;
156        g->length++;
157    }
158
159    // ๊ผฌ๋ฆฌ ์ฐพ๊ธฐ
160    Node* curr = g->head;
161    while (curr->next) curr = curr->next;
162    g->tail = curr;
163
164    // ์ƒํƒœ ์ดˆ๊ธฐํ™”
165    g->dir = RIGHT;
166    g->score = 0;
167    g->game_over = false;
168    g->paused = false;
169    g->speed = INITIAL_SPEED;
170    g->high_score = high_score;
171
172    spawn_food(g);
173    return g;
174}
175
176/**
177 * ๊ฒŒ์ž„ ๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ
178 */
179void game_free(Game* g) {
180    if (!g) return;
181
182    Node* n = g->head;
183    while (n) {
184        Node* next = n->next;
185        free(n);
186        n = next;
187    }
188    free(g);
189}
190
191// ============ ์ž…๋ ฅ ์ฒ˜๋ฆฌ ============
192
193/**
194 * ํ‚ค๋ณด๋“œ ์ž…๋ ฅ ์ฒ˜๋ฆฌ
195 * ๋ฐ˜ํ™˜: 0 = ๊ณ„์†, -1 = ์ข…๋ฃŒ
196 */
197int handle_input(Game* g) {
198    int ch = getch();
199
200    if (ch == ERR) return 0;  // ์ž…๋ ฅ ์—†์Œ
201
202    if (ch == 'q' || ch == 'Q') return -1;  // ์ข…๋ฃŒ
203
204    if (ch == 'p' || ch == 'P') {
205        g->paused = !g->paused;
206        return 0;
207    }
208
209    if (g->paused || g->game_over) return 0;
210
211    // ๋ฐฉํ–ฅ ๋ณ€๊ฒฝ (๋ฐ˜๋Œ€ ๋ฐฉํ–ฅ ๋ถˆ๊ฐ€)
212    switch (ch) {
213        case KEY_UP:
214        case 'w':
215        case 'W':
216            if (g->dir != DOWN) g->dir = UP;
217            break;
218        case KEY_DOWN:
219        case 's':
220        case 'S':
221            if (g->dir != UP) g->dir = DOWN;
222            break;
223        case KEY_LEFT:
224        case 'a':
225        case 'A':
226            if (g->dir != RIGHT) g->dir = LEFT;
227            break;
228        case KEY_RIGHT:
229        case 'd':
230        case 'D':
231            if (g->dir != LEFT) g->dir = RIGHT;
232            break;
233    }
234
235    return 0;
236}
237
238// ============ ๊ฒŒ์ž„ ์—…๋ฐ์ดํŠธ ============
239
240/**
241 * ๊ฒŒ์ž„ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
242 */
243bool game_update(Game* g) {
244    if (g->paused || g->game_over) return false;
245
246    // ๋‹ค์Œ ๋จธ๋ฆฌ ์œ„์น˜
247    Point next = g->head->pos;
248    switch (g->dir) {
249        case UP:    next.y--; break;
250        case DOWN:  next.y++; break;
251        case LEFT:  next.x--; break;
252        case RIGHT: next.x++; break;
253    }
254
255    // ๋ฒฝ ์ถฉ๋Œ
256    if (next.x <= 0 || next.x >= WIDTH - 1 ||
257        next.y <= 0 || next.y >= HEIGHT - 1) {
258        g->game_over = true;
259        return false;
260    }
261
262    // ์ž๊ธฐ ๋ชธ ์ถฉ๋Œ
263    if (snake_at(g->head, next.x, next.y)) {
264        g->game_over = true;
265        return false;
266    }
267
268    // ์ƒˆ ๋จธ๋ฆฌ ์ถ”๊ฐ€
269    Node* new_head = malloc(sizeof(Node));
270    if (!new_head) {
271        g->game_over = true;
272        return false;
273    }
274
275    new_head->pos = next;
276    new_head->next = g->head;
277    g->head = new_head;
278    g->length++;
279
280    // ์Œ์‹ ํ™•์ธ
281    if (next.x == g->food.x && next.y == g->food.y) {
282        g->score += 10;
283        spawn_food(g);
284
285        // ์†๋„ ์ฆ๊ฐ€
286        if (g->speed > MIN_SPEED) {
287            g->speed -= 5000;
288            if (g->speed < MIN_SPEED) g->speed = MIN_SPEED;
289        }
290
291        return true;
292    }
293
294    // ๊ผฌ๋ฆฌ ์ œ๊ฑฐ
295    Node* curr = g->head;
296    while (curr->next && curr->next->next) {
297        curr = curr->next;
298    }
299    if (curr->next) {
300        free(curr->next);
301        curr->next = NULL;
302        g->tail = curr;
303        g->length--;
304    }
305
306    return false;
307}
308
309// ============ ํ™”๋ฉด ๊ทธ๋ฆฌ๊ธฐ ============
310
311/**
312 * ๊ฒŒ์ž„ ํ™”๋ฉด ๊ทธ๋ฆฌ๊ธฐ
313 */
314void draw_game(Game* g) {
315    // ๊ฒŒ์ž„ ์œˆ๋„์šฐ ์ง€์šฐ๊ธฐ
316    werase(game_win);
317
318    // ํ…Œ๋‘๋ฆฌ ๊ทธ๋ฆฌ๊ธฐ
319    wattron(game_win, COLOR_PAIR(COLOR_BORDER));
320    box(game_win, 0, 0);
321    wattroff(game_win, COLOR_PAIR(COLOR_BORDER));
322
323    // ์Œ์‹ ๊ทธ๋ฆฌ๊ธฐ
324    wattron(game_win, COLOR_PAIR(COLOR_FOOD) | A_BOLD);
325    mvwaddch(game_win, g->food.y, g->food.x, 'O');
326    wattroff(game_win, COLOR_PAIR(COLOR_FOOD) | A_BOLD);
327
328    // ๋ฑ€ ๊ทธ๋ฆฌ๊ธฐ
329    wattron(game_win, COLOR_PAIR(COLOR_SNAKE));
330    bool is_head = true;
331    for (Node* n = g->head; n; n = n->next) {
332        if (is_head) {
333            wattron(game_win, A_BOLD);
334            mvwaddch(game_win, n->pos.y, n->pos.x, '@');
335            wattroff(game_win, A_BOLD);
336            is_head = false;
337        } else {
338            mvwaddch(game_win, n->pos.y, n->pos.x, '#');
339        }
340    }
341    wattroff(game_win, COLOR_PAIR(COLOR_SNAKE));
342
343    // ์ผ์‹œ์ •์ง€ ๋ฉ”์‹œ์ง€
344    if (g->paused) {
345        wattron(game_win, COLOR_PAIR(COLOR_TEXT) | A_BOLD);
346        mvwprintw(game_win, HEIGHT / 2, WIDTH / 2 - 5, "์ผ์‹œ์ •์ง€");
347        wattroff(game_win, COLOR_PAIR(COLOR_TEXT) | A_BOLD);
348    }
349
350    // ๊ฒŒ์ž„ ์œˆ๋„์šฐ ๊ฐฑ์‹ 
351    wrefresh(game_win);
352
353    // ์ •๋ณด ์œˆ๋„์šฐ ๊ทธ๋ฆฌ๊ธฐ
354    werase(info_win);
355    wattron(info_win, COLOR_PAIR(COLOR_TEXT));
356    mvwprintw(info_win, 0, 1, "์ ์ˆ˜: %d  |  ๊ธธ์ด: %d  |  ์ตœ๊ณ : %d",
357              g->score, g->length, g->high_score);
358    mvwprintw(info_win, 1, 1, "์กฐ์ž‘: โ†‘โ†“โ†โ†’ / WASD  |  P: ์ผ์‹œ์ •์ง€  |  Q: ์ข…๋ฃŒ");
359    wattroff(info_win, COLOR_PAIR(COLOR_TEXT));
360    wrefresh(info_win);
361}
362
363/**
364 * ๊ฒŒ์ž„ ์˜ค๋ฒ„ ํ™”๋ฉด
365 */
366void draw_game_over(Game* g) {
367    wattron(game_win, COLOR_PAIR(COLOR_TEXT) | A_BOLD);
368
369    mvwprintw(game_win, HEIGHT / 2 - 1, WIDTH / 2 - 5, "GAME OVER!");
370    mvwprintw(game_win, HEIGHT / 2, WIDTH / 2 - 7, "์ตœ์ข… ์ ์ˆ˜: %d", g->score);
371
372    if (g->score > g->high_score) {
373        mvwprintw(game_win, HEIGHT / 2 + 1, WIDTH / 2 - 6, "โ˜… ์‹ ๊ธฐ๋ก! โ˜…");
374    }
375
376    mvwprintw(game_win, HEIGHT / 2 + 3, WIDTH / 2 - 8, "R: ์žฌ์‹œ์ž‘  |  Q: ์ข…๋ฃŒ");
377
378    wattroff(game_win, COLOR_PAIR(COLOR_TEXT) | A_BOLD);
379    wrefresh(game_win);
380}
381
382// ============ ์ตœ๊ณ  ์ ์ˆ˜ ๊ด€๋ฆฌ ============
383
384#define SCORE_FILE ".snake_ncurses_highscore"
385
386int load_high_score(void) {
387    FILE* f = fopen(SCORE_FILE, "r");
388    if (!f) return 0;
389
390    int score = 0;
391    fscanf(f, "%d", &score);
392    fclose(f);
393    return score;
394}
395
396void save_high_score(int score) {
397    FILE* f = fopen(SCORE_FILE, "w");
398    if (f) {
399        fprintf(f, "%d", score);
400        fclose(f);
401    }
402}
403
404// ============ ๋ฉ”์ธ ํ•จ์ˆ˜ ============
405
406int main(void) {
407    srand(time(NULL));
408
409    init_ncurses();
410    atexit(cleanup_ncurses);
411
412    int high_score = load_high_score();
413    Game* game = game_init(high_score);
414
415    if (!game) {
416        cleanup_ncurses();
417        fprintf(stderr, "๊ฒŒ์ž„ ์ดˆ๊ธฐํ™” ์‹คํŒจ\n");
418        return 1;
419    }
420
421    draw_game(game);
422
423    // ๋ฉ”์ธ ๊ฒŒ์ž„ ๋ฃจํ”„
424    while (1) {
425        // ์ž…๋ ฅ ์ฒ˜๋ฆฌ
426        if (handle_input(game) == -1) {
427            break;  // ์ข…๋ฃŒ
428        }
429
430        if (!game->game_over) {
431            // ๊ฒŒ์ž„ ์—…๋ฐ์ดํŠธ
432            game_update(game);
433            draw_game(game);
434
435            if (game->game_over) {
436                // ์ตœ๊ณ  ์ ์ˆ˜ ์ €์žฅ
437                if (game->score > game->high_score) {
438                    save_high_score(game->score);
439                }
440                draw_game_over(game);
441            }
442        } else {
443            // ๊ฒŒ์ž„ ์˜ค๋ฒ„ ์ƒํƒœ์—์„œ ์žฌ์‹œ์ž‘ ์ฒ˜๋ฆฌ
444            int ch = getch();
445            if (ch == 'r' || ch == 'R') {
446                int final_high = (game->score > game->high_score) ?
447                                 game->score : game->high_score;
448                game_free(game);
449                game = game_init(final_high);
450                if (!game) break;
451                draw_game(game);
452            } else if (ch == 'q' || ch == 'Q') {
453                break;
454            }
455        }
456
457        usleep(game->speed);
458    }
459
460    game_free(game);
461
462    // ์ข…๋ฃŒ ๋ฉ”์‹œ์ง€
463    clear();
464    mvprintw(0, 0, "๊ฒŒ์ž„์„ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค. ํ”Œ๋ ˆ์ดํ•ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!");
465    refresh();
466    nodelay(stdscr, FALSE);
467    getch();
468
469    return 0;
470}