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}