jag

snake_c

public

Small snake game in C

Star0
Watch0

Commit d82b79536846

tarball

Jaglekon <58274093+Jaglekon@users.noreply.github.com>2026-03-22 18:53 UTC

2 files changed+344-80
new snake
Modified file: art.txt+5 additions−5 deletions
@@ -1,9 +1,9 @@
11
22
33 ________
4- _/ _ \_________
5- ________/ (_) \ /
6- \ \ \ /
7- /_______/ _ \ /
8- \_ (_) ____\/___
4+ _/ _ \_ ________ _________ _
5+ ________/ (_) \ / \ / \_____
6+ \ \ \ / \ / \
7+ /_______/ _ \ / \ / _____/
8+ \_ (_) _ ___\/___ ___\/____ _/
99 \________/
Modified file: snake.c+339 additions−75 deletions
@@ -23,6 +23,8 @@
2323 #define WIDTH 20
2424 #define HEIGHT 20
2525
26+#define STRING_LEN 20
27+
2628 #define ACTUAL_WIDTH ((WIDTH * 2) + 2)
2729 #define ACTUAL_HEIGHT (HEIGHT + 2)
2830
@@ -33,12 +35,12 @@
3335 #define STARTING_LENGTH 2
3436
3537 typedef enum {
36- UP, DOWN, LEFT, RIGHT
37-} direction;
38+ NEW_GAME, SETTINGS, EXIT, MAIN_MENU
39+} main_menu;
3840
3941 typedef enum {
40- EMPTY, SNAKE, HEAD, FRUIT
41-} field_content;
42+ NO_DIRECTION = -1, UP = 0, LEFT = 1, DOWN = 2, RIGHT = 3, DIRECTIONS = 4
43+} direction;
4244
4345 typedef struct selected {
4446 int16_t x;
@@ -52,19 +54,22 @@ typedef struct snake {
5254
5355
5456
57+void update_field(snake my_snake, char board[HEIGHT][WIDTH][STRING_LEN], selected fruit);
58+selected new_fruit(char board[HEIGHT][WIDTH][STRING_LEN]);
5559
56-void update_field(snake my_snake, field_content board[HEIGHT][WIDTH], selected fruit);
57-selected new_fruit(field_content board[HEIGHT][WIDTH]);
60+bool draw_menu(main_menu menu, struct winsize window_size);
5861
59-void draw_board(field_content board[HEIGHT][WIDTH], struct winsize window_size);
62+void draw_board(char board[HEIGHT][WIDTH][STRING_LEN], struct winsize window_size);
6063 void draw_pause(struct winsize window_size);
6164
62-direction read_direction (char* string, direction shift);
65+direction read_direction (char* string, bool wasd_enabled);
6366
6467
6568
6669 int main(int argc, char *args[]) {
6770
71+ /* command argument handling */
72+
6873 if (argc > 1) {
6974 if (argc > 2) {
7075 printf("Too many parameters\n");
@@ -88,6 +93,8 @@ int main(int argc, char *args[]) {
8893 return 1;
8994 }
9095
96+
97+ /* Setting up terminal with termios */
9198
9299 struct termios old, new;
93100
@@ -105,6 +112,103 @@ int main(int argc, char *args[]) {
105112
106113 printf("\033[?25l");
107114
115+
116+ struct winsize init_size, current_size;
117+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &init_size);
118+ current_size = init_size;
119+ bool too_small = false;
120+
121+
122+
123+ struct timeval start;
124+ gettimeofday(&start, NULL);
125+
126+
127+ /* Menu */
128+
129+ bool menu = true;
130+
131+ main_menu selected_menu = NEW_GAME;
132+
133+ too_small =draw_menu(selected_menu, init_size);
134+
135+ while (menu) {
136+
137+ bool pressed_enter = false;
138+ bool changed_selected = false;
139+
140+ char string[1000] = {};
141+ ssize_t length_string = read(STDIN_FILENO, string, sizeof(string) - 1);
142+
143+ direction arrow = NO_DIRECTION;
144+
145+
146+ /* Check for window size */
147+
148+ struct timeval current;
149+ gettimeofday(&current, NULL);
150+
151+ uint64_t time_diff = (current.tv_sec - start.tv_sec) * 1000000 + (current.tv_usec - start.tv_usec);
152+
153+ if (time_diff >= SPEED * 1000) {
154+
155+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &current_size);
156+
157+ if (
158+ current_size.ws_col != init_size.ws_col ||
159+ current_size.ws_row != init_size.ws_row
160+ ) {
161+ init_size = current_size;
162+ changed_selected = true;
163+ }
164+ }
165+
166+
167+ if (length_string > 0 && !too_small) {
168+ string[length_string] = '\0';
169+
170+ arrow = read_direction(string, false);
171+
172+
173+ switch (arrow) {
174+ case UP:
175+ selected_menu = (selected_menu + (MAIN_MENU - 1)) % MAIN_MENU;
176+ changed_selected = true;
177+ break;
178+ case DOWN:
179+ selected_menu = (selected_menu + 1) % MAIN_MENU;
180+ changed_selected = true;
181+ break;
182+ default:
183+ break;
184+ }
185+
186+ if (toupper(string[0]) == 'Q')
187+ goto end_of_program;
188+
189+ if (string[0] == '\n')
190+ pressed_enter = true;
191+ }
192+
193+
194+ if (changed_selected)
195+ too_small = draw_menu(selected_menu, init_size);
196+
197+ if (pressed_enter) {
198+ switch (selected_menu) {
199+ case NEW_GAME:
200+ menu = false;
201+ break;
202+ default:
203+ goto end_of_program;
204+ break;
205+ }
206+ }
207+ }
208+
209+
210+
211+ /* initialisation of the game */
108212
109213 snake my_snake = {};
110214 my_snake.length = STARTING_LENGTH;
@@ -112,8 +216,8 @@ int main(int argc, char *args[]) {
112216 {
113217 uint16_t i;
114218
115- my_snake.position[0].x = HEIGHT >> 1;
116- my_snake.position[0].y = WIDTH >> 1;
219+ my_snake.position[0].y = HEIGHT >> 1;
220+ my_snake.position[0].x = WIDTH >> 1;
117221 for (i = 1; i < my_snake.length; i++) {
118222 my_snake.position[i].y = -1;
119223 my_snake.position[i].x = -1;
@@ -121,22 +225,15 @@ int main(int argc, char *args[]) {
121225 }
122226
123227 selected fruit = {-1, -1};
124- field_content board[HEIGHT][WIDTH] = {0};
228+ char board[HEIGHT][WIDTH][STRING_LEN] = {};
125229
126230 update_field(my_snake, board, fruit);
127231
128232 fruit = new_fruit(board);
129233 update_field(my_snake, board, fruit);
130234
131- bool paused = false;
132-
133-
134- struct winsize init_size, current_size;
135- ioctl(STDOUT_FILENO, TIOCGWINSZ, &init_size);
136- current_size = init_size;
137235
138- bool too_small = false;
139-
236+ bool paused = false;
140237
141238 if (current_size.ws_row < ACTUAL_HEIGHT || current_size.ws_col < ACTUAL_WIDTH) {
142239 too_small = true;
@@ -152,26 +249,24 @@ int main(int argc, char *args[]) {
152249 direction tmp_direction = shift;
153250
154251
155- struct timeval start, current;
156- gettimeofday(&start, NULL);
157-
158-
159252 bool run = true;
160253
254+ /* start of the actual game loop */
255+
161256 while (run) {
162257
163258
259+ /* input handling */
260+
164261 if (!too_small) {
165262 char string[1000] = {};
166263 ssize_t length_string = read(STDIN_FILENO, string, sizeof(string) - 1);
167-
168- gettimeofday(&current, NULL);
169-
264+
170265 if (length_string > 0) {
171266 string[length_string] = '\0';
172267
173268 if (!paused)
174- tmp_direction = read_direction(string, shift);
269+ tmp_direction = read_direction(string, true);
175270
176271 if (toupper(string[0]) == 'Q')
177272 run = false;
@@ -186,6 +281,11 @@ int main(int argc, char *args[]) {
186281 }
187282
188283
284+ /* game logic */
285+
286+ struct timeval current;
287+ gettimeofday(&current, NULL);
288+
189289 uint64_t time_diff = (current.tv_sec - start.tv_sec) * 1000000 + (current.tv_usec - start.tv_usec);
190290
191291 if (time_diff >= SPEED * 1000) {
@@ -215,7 +315,12 @@ int main(int argc, char *args[]) {
215315 if (!paused) {
216316
217317 start = current;
218- shift = tmp_direction;
318+ if (
319+ tmp_direction != NO_DIRECTION &&
320+ ((shift + (DIRECTIONS >> 1)) % DIRECTIONS) != tmp_direction
321+ ) {
322+ shift = tmp_direction;
323+ }
219324
220325 uint16_t i;
221326 for (i = my_snake.length; i > 0; i--) {
@@ -236,6 +341,9 @@ int main(int argc, char *args[]) {
236341 case RIGHT:
237342 my_snake.position[0].x = ((my_snake.position[0].x + 1) % WIDTH);
238343 break;
344+ default:
345+ goto end_of_program;
346+ break;
239347 }
240348
241349 uint8_t head_y = my_snake.position[0].y;
@@ -281,29 +389,84 @@ end_of_program:
281389
282390
283391
284-void update_field(snake my_snake, field_content board[HEIGHT][WIDTH], selected fruit) {
392+void update_field(snake my_snake, char board[HEIGHT][WIDTH][STRING_LEN], selected fruit) {
285393
286394 uint8_t i, j;
287395
288396 for (i = 0; i < HEIGHT; i++) {
289397 for (j = 0; j < WIDTH; j++) {
290- board[i][j] = EMPTY;
398+ strcpy(board[i][j], " ");
291399 }
292400 }
293401
402+
403+
294404 for (i = 0; i < my_snake.length; i++) {
295405 int16_t x = my_snake.position[i].x;
296406 int16_t y = my_snake.position[i].y;
297- if (x >= 0 && y >= 0)
298- board[y][x] = (i == 0) ? HEAD : SNAKE;
407+ if (x >= 0 && y >= 0) {
408+ if (!i) {
409+ if (x == (my_snake.position[i + 1].x + 1) % WIDTH) {
410+ strcpy(board[y][x], "\033[33m═\033[31mX\033[0m");
411+ } else {
412+ strcpy(board[y][x], "\033[31m X\033[0m");
413+ }
414+ } else if (i == my_snake.length - 1) {
415+ if (x != my_snake.position[i - 1].x) {
416+ strcpy(board[y][x], "\033[33m══\033[0m");
417+ } else {
418+ strcpy(board[y][x], "\033[33m ║\033[0m");
419+ }
420+ } else {
421+ if (
422+ x != my_snake.position[i - 1].x &&
423+ x != my_snake.position[i + 1].x
424+ ) {
425+ strcpy(board[y][x], "\033[33m══\033[0m");
426+ } else if (
427+ y != my_snake.position[i - 1].y &&
428+ y != my_snake.position[i + 1].y
429+ ) {
430+ strcpy(board[y][x], "\033[33m ║\033[0m");
431+ } else if (
432+ (y == (my_snake.position[i - 1].y + 1) % HEIGHT &&
433+ x == (my_snake.position[i + 1].x + 1) % WIDTH) ||
434+ (y == (my_snake.position[i + 1].y + 1) % HEIGHT &&
435+ x == (my_snake.position[i - 1].x + 1) % WIDTH)
436+ ) {
437+ strcpy(board[y][x], "\033[33m═╝\033[0m");
438+ } else if (
439+ (y == (my_snake.position[i - 1].y + (HEIGHT - 1)) % HEIGHT &&
440+ x == (my_snake.position[i + 1].x + 1) % WIDTH) ||
441+ (y == (my_snake.position[i + 1].y + HEIGHT - 1) % HEIGHT &&
442+ x == (my_snake.position[i - 1].x + 1) % WIDTH)
443+ ) {
444+ strcpy(board[y][x], "\033[33m═╗\033[0m");
445+ } else if (
446+ (y == (my_snake.position[i - 1].y + 1) % HEIGHT &&
447+ x == (my_snake.position[i + 1].x + (WIDTH - 1)) % WIDTH) ||
448+ (y == (my_snake.position[i + 1].y + 1) % HEIGHT &&
449+ x == (my_snake.position[i - 1].x + (WIDTH - 1)) % WIDTH)
450+ ) {
451+ strcpy(board[y][x], "\033[33m ╚\033[0m");
452+ } else if (
453+ (y == (my_snake.position[i - 1].y + (HEIGHT - 1)) % HEIGHT &&
454+ x == (my_snake.position[i + 1].x + (WIDTH - 1)) % WIDTH) ||
455+ (y == (my_snake.position[i + 1].y + (HEIGHT - 1)) % HEIGHT &&
456+ x == (my_snake.position[i - 1].x + (WIDTH - 1)) % WIDTH)
457+ ) {
458+ strcpy(board[y][x], "\033[33m ╔\033[0m");
459+ }
460+ }
461+ }
299462 }
300463
301464 if (fruit.x >= 0 && fruit.y >= 0)
302- board[fruit.y][fruit.x] = FRUIT;
465+ strcpy(board[fruit.y][fruit.x], "\033[32m O\033[0m");
303466 }
304467
305468
306-selected new_fruit(field_content board[HEIGHT][WIDTH]) {
469+selected new_fruit(char board[HEIGHT][WIDTH][STRING_LEN]) {
307470 bool found_space = false;
308471
309472 uint8_t x;
@@ -313,7 +476,7 @@ selected new_fruit(field_content board[HEIGHT][WIDTH]) {
313476 x = rand() % WIDTH;
314477 y = rand() % HEIGHT;
315478
316- if (board[y][x] == EMPTY)
479+ if (!strcmp(board[y][x], " "))
317480 found_space = true;
318481 }
319482
@@ -323,7 +486,128 @@ selected new_fruit(field_content board[HEIGHT][WIDTH]) {
323486 }
324487
325488
326-void draw_board (field_content board[HEIGHT][WIDTH], struct winsize window_size) {
489+bool draw_menu(main_menu menu, struct winsize window_size) {
490+
491+ char *menu_string[] = {
492+ "NEW GAME",
493+ "SETTINGS",
494+ "EXIT"
495+ };
496+
497+ const uint8_t width_small = 8;
498+ const uint8_t height_small = 5;
499+
500+
501+
502+ char *menu_string_big[][6] = {
503+ {
504+ "███╗ ██╗███████╗██╗ ██╗ ██████╗ █████╗ ███╗ ███╗███████╗",
505+ "████╗ ██║██╔════╝██║ ██║ ██╔════╝ ██╔══██╗████╗ ████║██╔════╝",
506+ "██╔██╗ ██║█████╗ ██║ █╗ ██║ ██║ ███╗███████║██╔████╔██║█████╗ ",
507+ "██║╚██╗██║██╔══╝ ██║███╗██║ ██║ ██║██╔══██║██║╚██╔╝██║██╔══╝ ",
508+ "██║ ╚████║███████╗╚███╔███╔╝ ╚██████╔╝██║ ██║██║ ╚═╝ ██║███████╗",
509+ "╚═╝ ╚═══╝╚══════╝ ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝"
510+ },
511+ {
512+ "███████╗███████╗████████╗████████╗██╗███╗ ██╗ ██████╗ ███████╗",
513+ "██╔════╝██╔════╝╚══██╔══╝╚══██╔══╝██║████╗ ██║██╔════╝ ██╔════╝",
514+ "███████╗█████╗ ██║ ██║ ██║██╔██╗ ██║██║ ███╗███████╗",
515+ "╚════██║██╔══╝ ██║ ██║ ██║██║╚██╗██║██║ ██║╚════██║",
516+ "███████║███████╗ ██║ ██║ ██║██║ ╚████║╚██████╔╝███████║",
517+ "╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══════╝"
518+ },
519+ {
520+ "███████╗██╗ ██╗██╗████████╗",
521+ "██╔════╝╚██╗██╔╝██║╚══██╔══╝",
522+ "█████╗ ╚███╔╝ ██║ ██║ ",
523+ "██╔══╝ ██╔██╗ ██║ ██║ ",
524+ "███████╗██╔╝ ██╗██║ ██║ ",
525+ "╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ "
526+ }
527+ };
528+
529+ const uint8_t height_line = 6;
530+ const uint8_t height_space = 2;
531+
532+ const uint8_t width_text[] = {68, 64, 28};
533+
534+ const uint8_t width_big = width_text[0] /*strlen(menu_string_big[0][0])*/;
535+ const uint8_t height_big =
536+ height_line * MAIN_MENU + height_space * (MAIN_MENU - 1);
537+
538+
539+ printf("\033[2J\033[H");
540+
541+ if (window_size.ws_row < height_small || window_size.ws_col < width_small) {
542+ printf("Too small\n");
543+ return true;
544+ }
545+
546+
547+ uint8_t i, j, k;
548+
549+ if (window_size.ws_row >= height_big && window_size.ws_col >= width_big) {
550+
551+ uint8_t vertical_buffer = (window_size.ws_row - height_big) >> 1;
552+
553+ for (i = 0; i < vertical_buffer; i++)
554+ printf("\n");
555+
556+ for (i = 0; i < MAIN_MENU; i++) {
557+
558+ if (i == menu)
559+ printf("\033[32m");
560+
561+ uint8_t horizontal_buffer =
562+ (window_size.ws_col - width_text[i]) >> 1;
563+
564+ for (j = 0; j < height_line; j++) {
565+
566+ for (k = 0; k < horizontal_buffer; k++)
567+ printf(" ");
568+
569+ printf("%s", menu_string_big[i][j]);
570+
571+ if (j != height_line - 1)
572+ printf("\n");
573+ }
574+
575+ printf("\033[0m");
576+
577+ if (i != MAIN_MENU - 1)
578+ printf("\n\n");
579+ }
580+
581+ } else {
582+
583+ uint8_t vertical_buffer = (window_size.ws_row - height_small) >> 1;
584+
585+ for (i = 0; i < vertical_buffer; i++)
586+ printf("\n");
587+
588+ for (i = 0; i < MAIN_MENU; i++) {
589+ if (i == menu)
590+ printf("\033[32m");
591+
592+ uint8_t horizontal_buffer =
593+ (window_size.ws_col - strlen(menu_string[i])) >> 1;
594+
595+ for (j = 0; j < horizontal_buffer; j++)
596+ printf(" ");
597+
598+ printf("%s\033[0m", menu_string[i]);
599+
600+ if (i != MAIN_MENU - 1)
601+ printf("\n\n");
602+ }
603+ }
604+
605+ fflush(stdout);
606+ return false;
607+}
608+
609+
610+void draw_board (char board[HEIGHT][WIDTH][STRING_LEN], struct winsize window_size) {
327611
328612 uint8_t i, j;
329613
@@ -349,23 +633,9 @@ void draw_board (field_content board[HEIGHT][WIDTH], struct winsize window_size)
349633
350634 printf("|");
351635
352- for (j = 0; j < WIDTH; j++) {
636+ for (j = 0; j < WIDTH; j++)
637+ printf("%s", board[i][j]);
353638
354- switch (board[i][j]) {
355- case EMPTY:
356- printf(" ");
357- break;
358- case SNAKE:
359- printf("\033[31m x\033[0m");
360- break;
361- case HEAD:
362- printf("\033[32m X\033[0m");
363- break;
364- case FRUIT:
365- printf("\033[34m O\033[0m");
366- break;
367- }
368- }
369639 printf("|\n");
370640 }
371641
@@ -420,7 +690,7 @@ void draw_pause(struct winsize window_size) {
420690 fflush(stdout);
421691 }
422692
423-direction read_direction (char* string, direction shift) {
693+direction read_direction (char* string, bool wasd_enabled) {
424694
425695 if (strlen(string) >= 3) {
426696 if (string[0] == '\033') {
@@ -428,20 +698,16 @@ direction read_direction (char* string, direction shift) {
428698
429699 switch (string[2]) {
430700 case RIGHT_CODE:
431- if (shift != LEFT)
432- return RIGHT;
701+ return RIGHT;
433702 break;
434703 case LEFT_CODE:
435- if (shift != RIGHT)
436- return LEFT;
704+ return LEFT;
437705 break;
438706 case UP_CODE:
439- if (shift != DOWN)
440- return UP;
707+ return UP;
441708 break;
442709 case DOWN_CODE:
443- if (shift != UP)
444- return DOWN;
710+ return DOWN;
445711 break;
446712 }
447713 }
@@ -449,24 +715,22 @@ direction read_direction (char* string, direction shift) {
449715 }
450716
451717
452- switch (toupper(string[0])) {
453- case 'A':
454- if (shift != RIGHT)
718+ if (wasd_enabled) {
719+ switch (toupper(string[0])) {
720+ case 'A':
455721 return LEFT;
456- break;
457- case 'D':
458- if (shift != LEFT)
722+ break;
723+ case 'D':
459724 return RIGHT;
460- break;
461- case 'W':
462- if (shift != DOWN)
725+ break;
726+ case 'W':
463727 return UP;
464- break;
465- case 'S':
466- if (shift != UP)
728+ break;
729+ case 'S':
467730 return DOWN;
468- break;
731+ break;
732+ }
469733 }
470734
471- return shift;
735+ return NO_DIRECTION;
472736 }