jag

snake_c

public

Small snake game in C

Star0
Watch0
6d17890946020f740f5e776e041ebd6e37974a40
2
ssh://git@git.jaglekon.de:30/jag/snake_c.git
snake.cC687 lines18.4 KB
1
#include <termios.h>
2
#include <unistd.h>
3
4
#include <stdbool.h>
5
#include <stdint.h>
6
#include <stdio.h>
7
#include <stdlib.h>
8
9
#include <sys/ioctl.h>
10
11
#include <sys/time.h>
12
#include <time.h>
13
14
#include <ctype.h>
15
#include <string.h>
16
17
#define RIGHT_CODE 'C'
18
#define LEFT_CODE 'D'
19
#define UP_CODE 'A'
20
#define DOWN_CODE 'B'
21
22
#define WIDTH 20
23
#define HEIGHT 20
24
25
#define STRING_LEN 20
26
27
#define ACTUAL_WIDTH ((WIDTH * 2) + 2)
28
#define ACTUAL_HEIGHT (HEIGHT + 2)
29
30
#define SPEED 100 /* in ms */
31
32
#define MAX_LENGTH 1000
33
#define STARTING_LENGTH 2
34
35
typedef enum { NEW_GAME, SETTINGS, EXIT, MAIN_MENU } main_menu;
36
37
typedef enum {
38
  NO_DIRECTION = -1,
39
  UP = 0,
40
  LEFT = 1,
41
  DOWN = 2,
42
  RIGHT = 3,
43
  DIRECTIONS = 4
44
} direction;
45
46
typedef struct selected {
47
  int16_t x;
48
  int16_t y;
49
} selected;
50
51
typedef struct snake {
52
  selected position[MAX_LENGTH];
53
  uint16_t length;
54
} snake;
55
56
void update_field(snake my_snake, char board[HEIGHT][WIDTH][STRING_LEN],
57
                  selected fruit);
58
selected new_fruit(char board[HEIGHT][WIDTH][STRING_LEN]);
59
60
bool draw_menu(main_menu menu, struct winsize window_size);
61
62
bool draw_board(char board[HEIGHT][WIDTH][STRING_LEN],
63
                struct winsize window_size, bool redraw);
64
void draw_pause(struct winsize window_size);
65
66
direction read_direction(char *string, bool wasd_enabled);
67
68
int main(int argc, char *args[]) {
69
70
  /* command argument handling */
71
72
  if (argc > 1) {
73
    if (argc > 2) {
74
      printf("Too many parameters\n");
75
      goto help_info;
76
    }
77
78
    if (!strcmp(args[1], "-h") || !strcmp(args[1], "--help")) {
79
80
      printf("This is a small TUI snake game.\n\n");
81
      printf("Controls: Arrow keys/WASD to control the snake.\n");
82
      printf("          P to pause.\n");
83
      printf("          Q to quit.\n");
84
85
      return 0;
86
    }
87
88
    printf("Argument \"%s\" not recognized\n", args[1]);
89
90
  help_info:
91
    printf("Try \"-h\" or \"--help\" for more information\n");
92
    return 1;
93
  }
94
95
  /* Setting up terminal with termios */
96
97
  struct termios old, new;
98
99
  tcgetattr(STDIN_FILENO, &old);
100
101
  new = old;
102
103
  new.c_lflag &= ~(ECHO | ICANON);
104
  new.c_cc[VMIN] = 0;
105
  new.c_cc[VTIME] = 1;
106
107
  tcsetattr(STDIN_FILENO, TCSANOW, &new);
108
109
  srand(time(NULL));
110
111
  printf("\033[?25l");
112
113
  struct winsize init_size, current_size;
114
  ioctl(STDOUT_FILENO, TIOCGWINSZ, &init_size);
115
  current_size = init_size;
116
  bool too_small = false;
117
118
  struct timeval start;
119
  gettimeofday(&start, NULL);
120
121
  /* Menu */
122
123
  bool menu = true;
124
125
  main_menu selected_menu = NEW_GAME;
126
127
  too_small = draw_menu(selected_menu, init_size);
128
129
  while (menu) {
130
131
    bool pressed_enter = false;
132
    bool changed_selected = false;
133
134
    char string[1000] = {};
135
    ssize_t length_string = read(STDIN_FILENO, string, sizeof(string) - 1);
136
137
    direction arrow = NO_DIRECTION;
138
139
    /* Check for window size */
140
141
    struct timeval current;
142
    gettimeofday(&current, NULL);
143
144
    uint64_t time_diff = (current.tv_sec - start.tv_sec) * 1000000 +
145
                         (current.tv_usec - start.tv_usec);
146
147
    if (time_diff >= SPEED * 1000) {
148
149
      ioctl(STDOUT_FILENO, TIOCGWINSZ, &current_size);
150
151
      if (current_size.ws_col != init_size.ws_col ||
152
          current_size.ws_row != init_size.ws_row) {
153
        init_size = current_size;
154
        changed_selected = true;
155
      }
156
    }
157
158
    if (length_string > 0 && !too_small) {
159
      string[length_string] = '\0';
160
161
      arrow = read_direction(string, false);
162
163
      switch (arrow) {
164
      case UP:
165
        selected_menu = (selected_menu + (MAIN_MENU - 1)) % MAIN_MENU;
166
        changed_selected = true;
167
        break;
168
      case DOWN:
169
        selected_menu = (selected_menu + 1) % MAIN_MENU;
170
        changed_selected = true;
171
        break;
172
      default:
173
        break;
174
      }
175
176
      if (toupper(string[0]) == 'Q')
177
        goto end_of_program;
178
179
      if (string[0] == '\n')
180
        pressed_enter = true;
181
    }
182
183
    if (changed_selected)
184
      too_small = draw_menu(selected_menu, init_size);
185
186
    if (pressed_enter) {
187
      switch (selected_menu) {
188
      case NEW_GAME:
189
        menu = false;
190
        break;
191
      default:
192
        goto end_of_program;
193
        break;
194
      }
195
    }
196
  }
197
198
  /* initialisation of the game */
199
200
  snake my_snake = {};
201
  my_snake.length = STARTING_LENGTH;
202
203
  {
204
    uint16_t i;
205
206
    my_snake.position[0].y = HEIGHT >> 1;
207
    my_snake.position[0].x = WIDTH >> 1;
208
    for (i = 1; i < my_snake.length; i++) {
209
      my_snake.position[i].y = -1;
210
      my_snake.position[i].x = -1;
211
    }
212
  }
213
214
  selected fruit = {-1, -1};
215
  char board[HEIGHT][WIDTH][STRING_LEN] = {};
216
217
  update_field(my_snake, board, fruit);
218
219
  fruit = new_fruit(board);
220
  update_field(my_snake, board, fruit);
221
222
  bool paused = false;
223
224
  if (current_size.ws_row < ACTUAL_HEIGHT ||
225
      current_size.ws_col < ACTUAL_WIDTH) {
226
    too_small = true;
227
    paused = true;
228
229
    printf("\033[2J\033[HToo small\n");
230
    printf("Min width: %i & Min height: %i\n", ACTUAL_WIDTH, ACTUAL_WIDTH);
231
  } else {
232
    draw_board(board, current_size, true);
233
  }
234
235
  direction shift = RIGHT;
236
  direction tmp_direction = shift;
237
238
  bool run = true;
239
240
  /* start of the actual game loop */
241
242
  while (run) {
243
244
    /* input handling  */
245
246
    if (!too_small) {
247
      char string[1000] = {};
248
      ssize_t length_string = read(STDIN_FILENO, string, sizeof(string) - 1);
249
250
      if (length_string > 0) {
251
        string[length_string] = '\0';
252
253
        if (!paused)
254
          tmp_direction = read_direction(string, true);
255
256
        if (toupper(string[0]) == 'Q')
257
          run = false;
258
259
        if (toupper(string[0]) == 'P') {
260
          paused = !paused;
261
          if (paused)
262
            draw_pause(current_size);
263
        }
264
      }
265
    }
266
267
    /* game logic */
268
269
    struct timeval current;
270
    gettimeofday(&current, NULL);
271
272
    uint64_t time_diff = (current.tv_sec - start.tv_sec) * 1000000 +
273
                         (current.tv_usec - start.tv_usec);
274
275
    if (time_diff >= SPEED * 1000) {
276
277
      ioctl(STDOUT_FILENO, TIOCGWINSZ, &current_size);
278
279
      if (current_size.ws_col != init_size.ws_col ||
280
          current_size.ws_row != init_size.ws_row) {
281
        init_size = current_size;
282
283
        paused = true;
284
285
        draw_board(board, current_size, true);
286
287
        if (current_size.ws_row < ACTUAL_HEIGHT ||
288
            current_size.ws_col < ACTUAL_WIDTH) {
289
          too_small = true;
290
        } else {
291
          too_small = false;
292
293
          draw_pause(current_size);
294
        }
295
      }
296
297
      if (!paused) {
298
299
        start = current;
300
        if (tmp_direction != NO_DIRECTION &&
301
            ((shift + (DIRECTIONS >> 1)) % DIRECTIONS) != tmp_direction) {
302
          shift = tmp_direction;
303
        }
304
305
        uint16_t i;
306
        for (i = my_snake.length; i > 0; i--) {
307
          my_snake.position[i].y = my_snake.position[i - 1].y;
308
          my_snake.position[i].x = my_snake.position[i - 1].x;
309
        }
310
311
        switch (shift) {
312
        case UP:
313
          my_snake.position[0].y =
314
              ((my_snake.position[0].y + HEIGHT - 1) % HEIGHT);
315
          break;
316
        case DOWN:
317
          my_snake.position[0].y = ((my_snake.position[0].y + 1) % HEIGHT);
318
          break;
319
        case LEFT:
320
          my_snake.position[0].x =
321
              ((my_snake.position[0].x + WIDTH - 1) % WIDTH);
322
          break;
323
        case RIGHT:
324
          my_snake.position[0].x = ((my_snake.position[0].x + 1) % WIDTH);
325
          break;
326
        default:
327
          goto end_of_program;
328
          break;
329
        }
330
331
        uint8_t head_y = my_snake.position[0].y;
332
        uint8_t head_x = my_snake.position[0].x;
333
334
        for (i = 1; i < my_snake.length; i++) {
335
          int16_t x = my_snake.position[i].x;
336
          int16_t y = my_snake.position[i].y;
337
338
          if (x == head_x && y == head_y) {
339
            printf("\n\nHAHA LOST!!!\n\n");
340
            goto end_of_program;
341
          }
342
        }
343
344
        update_field(my_snake, board, fruit);
345
346
        if (head_y == fruit.y && head_x == fruit.x) {
347
          my_snake.length++;
348
          fruit = new_fruit(board);
349
        }
350
351
        update_field(my_snake, board, fruit);
352
        draw_board(board, current_size, false);
353
      }
354
    }
355
  }
356
357
end_of_program:
358
359
  tcsetattr(STDIN_FILENO, TCSANOW, &old);
360
  printf("\033[?25h\n\n");
361
362
  return 0;
363
}
364
365
void update_field(snake my_snake, char board[HEIGHT][WIDTH][STRING_LEN],
366
                  selected fruit) {
367
368
  uint8_t i, j;
369
370
  for (i = 0; i < HEIGHT; i++) {
371
    for (j = 0; j < WIDTH; j++) {
372
      strcpy(board[i][j], "  ");
373
    }
374
  }
375
376
  for (i = 0; i < my_snake.length; i++) {
377
    int16_t x = my_snake.position[i].x;
378
    int16_t y = my_snake.position[i].y;
379
    if (x >= 0 && y >= 0) {
380
      if (!i) {
381
        if (x == (my_snake.position[i + 1].x + 1) % WIDTH) {
382
          strcpy(board[y][x], "\033[33m═\033[31mX\033[0m");
383
        } else {
384
          strcpy(board[y][x], "\033[31m X\033[0m");
385
        }
386
      } else if (i == my_snake.length - 1) {
387
        if (x != my_snake.position[i - 1].x) {
388
          strcpy(board[y][x], "\033[33m══\033[0m");
389
        } else {
390
          strcpy(board[y][x], "\033[33m ║\033[0m");
391
        }
392
      } else {
393
        if (x != my_snake.position[i - 1].x &&
394
            x != my_snake.position[i + 1].x) {
395
          strcpy(board[y][x], "\033[33m══\033[0m");
396
        } else if (y != my_snake.position[i - 1].y &&
397
                   y != my_snake.position[i + 1].y) {
398
          strcpy(board[y][x], "\033[33m ║\033[0m");
399
        } else if ((y == (my_snake.position[i - 1].y + 1) % HEIGHT &&
400
                    x == (my_snake.position[i + 1].x + 1) % WIDTH) ||
401
                   (y == (my_snake.position[i + 1].y + 1) % HEIGHT &&
402
                    x == (my_snake.position[i - 1].x + 1) % WIDTH)) {
403
          strcpy(board[y][x], "\033[33m═╝\033[0m");
404
        } else if ((y == (my_snake.position[i - 1].y + (HEIGHT - 1)) % HEIGHT &&
405
                    x == (my_snake.position[i + 1].x + 1) % WIDTH) ||
406
                   (y == (my_snake.position[i + 1].y + HEIGHT - 1) % HEIGHT &&
407
                    x == (my_snake.position[i - 1].x + 1) % WIDTH)) {
408
          strcpy(board[y][x], "\033[33m═╗\033[0m");
409
        } else if ((y == (my_snake.position[i - 1].y + 1) % HEIGHT &&
410
                    x == (my_snake.position[i + 1].x + (WIDTH - 1)) % WIDTH) ||
411
                   (y == (my_snake.position[i + 1].y + 1) % HEIGHT &&
412
                    x == (my_snake.position[i - 1].x + (WIDTH - 1)) % WIDTH)) {
413
          strcpy(board[y][x], "\033[33m ╚\033[0m");
414
        } else if ((y == (my_snake.position[i - 1].y + (HEIGHT - 1)) % HEIGHT &&
415
                    x == (my_snake.position[i + 1].x + (WIDTH - 1)) % WIDTH) ||
416
                   (y == (my_snake.position[i + 1].y + (HEIGHT - 1)) % HEIGHT &&
417
                    x == (my_snake.position[i - 1].x + (WIDTH - 1)) % WIDTH)) {
418
          strcpy(board[y][x], "\033[33m ╔\033[0m");
419
        }
420
      }
421
    }
422
  }
423
424
  if (fruit.x >= 0 && fruit.y >= 0)
425
    strcpy(board[fruit.y][fruit.x], "\033[32m O\033[0m");
426
}
427
428
selected new_fruit(char board[HEIGHT][WIDTH][STRING_LEN]) {
429
  bool found_space = false;
430
431
  uint8_t x;
432
  uint8_t y;
433
434
  while (!found_space) {
435
    x = rand() % WIDTH;
436
    y = rand() % HEIGHT;
437
438
    if (!strcmp(board[y][x], "  "))
439
      found_space = true;
440
  }
441
442
  selected fruit = {x, y};
443
444
  return fruit;
445
}
446
447
bool draw_menu(main_menu menu, struct winsize window_size) {
448
449
  char *menu_string[] = {"NEW GAME", "SETTINGS", "EXIT"};
450
451
  const uint8_t width_small = 8;
452
  const uint8_t height_small = 5;
453
454
  char *menu_string_big[][6] = {
455
      {"███╗   ██╗███████╗██╗    ██╗     ██████╗  █████╗ ███╗   ███╗███████╗",
456
       "████╗  ██║██╔════╝██║    ██║    ██╔════╝ ██╔══██╗████╗ ████║██╔════╝",
457
       "██╔██╗ ██║█████╗  ██║ █╗ ██║    ██║  ███╗███████║██╔████╔██║█████╗  ",
458
       "██║╚██╗██║██╔══╝  ██║███╗██║    ██║   ██║██╔══██║██║╚██╔╝██║██╔══╝  ",
459
       "██║ ╚████║███████╗╚███╔███╔╝    ╚██████╔╝██║  ██║██║ ╚═╝ ██║███████╗",
460
       "╚═╝  ╚═══╝╚══════╝ ╚══╝╚══╝      ╚═════╝ ╚═╝  ╚═╝╚═╝     ╚═╝╚══════╝"},
461
      {"███████╗███████╗████████╗████████╗██╗███╗   ██╗ ██████╗ ███████╗",
462
       "██╔════╝██╔════╝╚══██╔══╝╚══██╔══╝██║████╗  ██║██╔════╝ ██╔════╝",
463
       "███████╗█████╗     ██║      ██║   ██║██╔██╗ ██║██║  ███╗███████╗",
464
       "╚════██║██╔══╝     ██║      ██║   ██║██║╚██╗██║██║   ██║╚════██║",
465
       "███████║███████╗   ██║      ██║   ██║██║ ╚████║╚██████╔╝███████║",
466
       "╚══════╝╚══════╝   ╚═╝      ╚═╝   ╚═╝╚═╝  ╚═══╝ ╚═════╝ ╚══════╝"},
467
      {"███████╗██╗  ██╗██╗████████╗", "██╔════╝╚██╗██╔╝██║╚══██╔══╝",
468
       "█████╗   ╚███╔╝ ██║   ██║   ", "██╔══╝   ██╔██╗ ██║   ██║   ",
469
       "███████╗██╔╝ ██╗██║   ██║   ", "╚══════╝╚═╝  ╚═╝╚═╝   ╚═╝   "}};
470
471
  const uint8_t height_line = 6;
472
  const uint8_t height_space = 2;
473
474
  const uint8_t width_text[] = {68, 64, 28};
475
476
  const uint8_t width_big = width_text[0] /*strlen(menu_string_big[0][0])*/;
477
  const uint8_t height_big =
478
      height_line * MAIN_MENU + height_space * (MAIN_MENU - 1);
479
480
  printf("\033[2J\033[H");
481
482
  if (window_size.ws_row < height_small || window_size.ws_col < width_small) {
483
    printf("Too small\n");
484
    return true;
485
  }
486
487
  uint8_t i, j, k;
488
489
  if (window_size.ws_row >= height_big && window_size.ws_col >= width_big) {
490
491
    uint8_t vertical_buffer = (window_size.ws_row - height_big) >> 1;
492
493
    for (i = 0; i < vertical_buffer; i++)
494
      printf("\n");
495
496
    for (i = 0; i < MAIN_MENU; i++) {
497
498
      if (i == menu)
499
        printf("\033[32m");
500
501
      uint8_t horizontal_buffer = (window_size.ws_col - width_text[i]) >> 1;
502
503
      for (j = 0; j < height_line; j++) {
504
505
        for (k = 0; k < horizontal_buffer; k++)
506
          printf(" ");
507
508
        printf("%s", menu_string_big[i][j]);
509
510
        if (j != height_line - 1)
511
          printf("\n");
512
      }
513
514
      printf("\033[0m");
515
516
      if (i != MAIN_MENU - 1)
517
        printf("\n\n");
518
    }
519
520
  } else {
521
522
    uint8_t vertical_buffer = (window_size.ws_row - height_small) >> 1;
523
524
    for (i = 0; i < vertical_buffer; i++)
525
      printf("\n");
526
527
    for (i = 0; i < MAIN_MENU; i++) {
528
      if (i == menu)
529
        printf("\033[32m");
530
531
      uint8_t horizontal_buffer =
532
          (window_size.ws_col - strlen(menu_string[i])) >> 1;
533
534
      for (j = 0; j < horizontal_buffer; j++)
535
        printf(" ");
536
537
      printf("%s\033[0m", menu_string[i]);
538
539
      if (i != MAIN_MENU - 1)
540
        printf("\n\n");
541
    }
542
  }
543
544
  fflush(stdout);
545
  return false;
546
}
547
548
bool draw_board(char board[HEIGHT][WIDTH][STRING_LEN],
549
                struct winsize window_size, bool redraw) {
550
551
  uint8_t i, j;
552
553
  uint8_t actual_height = HEIGHT + 2;
554
  uint8_t actual_width = WIDTH * 2 + 2;
555
556
  if (actual_width > window_size.ws_col || actual_height > window_size.ws_row) {
557
558
    printf("\033[2J\033[HToo small\n");
559
    printf("Min width: %i & Min height: %i\n", actual_height, actual_width);
560
561
    return true;
562
  }
563
564
  uint8_t vertical_buffer = (window_size.ws_row - actual_height) >> 1;
565
  uint8_t horizontal_buffer = (window_size.ws_col - actual_width) >> 1;
566
567
  if (redraw) {
568
    printf("\033[2J\033[H");
569
    for (i = 0; i < vertical_buffer; i++)
570
      printf("\n");
571
  } else {
572
    for (i = 0; i < actual_height - 1; i++)
573
      printf("\033[%c", UP_CODE);
574
    printf("\r");
575
  }
576
577
  for (i = 0; i < horizontal_buffer; i++)
578
    printf(" ");
579
  printf("+");
580
  for (i = 0; i < WIDTH; i++)
581
    printf("--");
582
  printf("+\n");
583
584
  for (i = 0; i < HEIGHT; i++) {
585
586
    for (j = 0; j < horizontal_buffer; j++)
587
      printf(" ");
588
589
    printf("|");
590
591
    for (j = 0; j < WIDTH; j++)
592
      printf("%s", board[i][j]);
593
594
    printf("|\n");
595
  }
596
597
  for (i = 0; i < horizontal_buffer; i++)
598
    printf(" ");
599
  printf("+");
600
  for (i = 0; i < WIDTH; i++)
601
    printf("--");
602
  printf("+");
603
604
  fflush(stdout);
605
606
  return false;
607
}
608
609
void draw_pause(struct winsize window_size) {
610
611
  const uint8_t height = 5;
612
  const uint8_t width = 14;
613
614
  const char *pause_menu[] = {"+------------+", "|            |",
615
                              "|   PAUSED   |", "|            |",
616
                              "+------------+"};
617
618
  const uint8_t going_up = (ACTUAL_HEIGHT + height) >> 1;
619
  const uint8_t going_right = (ACTUAL_WIDTH - width) >> 1;
620
621
  const uint8_t horizontal_buffer = (window_size.ws_col - ACTUAL_WIDTH) >> 1;
622
623
  uint8_t i, j;
624
625
  for (i = 0; i < going_up; i++)
626
    printf("\033[%c", UP_CODE);
627
  printf("\r");
628
629
  for (i = 0; i < height; i++) {
630
    for (j = 0; j < horizontal_buffer; j++)
631
      printf("\033[%c", RIGHT_CODE);
632
    for (j = 0; j < going_right; j++)
633
      printf("\033[%c", RIGHT_CODE);
634
635
    printf("\033[37m%s\033[0m\033[%c\r", pause_menu[i], DOWN_CODE);
636
  }
637
638
  for (i = 0; i < going_up - height; i++)
639
    printf("\033[%c", DOWN_CODE);
640
  printf("\r");
641
642
  fflush(stdout);
643
}
644
645
direction read_direction(char *string, bool wasd_enabled) {
646
647
  if (strlen(string) >= 3) {
648
    if (string[0] == '\033') {
649
      if (string[1] == '[') {
650
651
        switch (string[2]) {
652
        case RIGHT_CODE:
653
          return RIGHT;
654
          break;
655
        case LEFT_CODE:
656
          return LEFT;
657
          break;
658
        case UP_CODE:
659
          return UP;
660
          break;
661
        case DOWN_CODE:
662
          return DOWN;
663
          break;
664
        }
665
      }
666
    }
667
  }
668
669
  if (wasd_enabled) {
670
    switch (toupper(string[0])) {
671
    case 'A':
672
      return LEFT;
673
      break;
674
    case 'D':
675
      return RIGHT;
676
      break;
677
    case 'W':
678
      return UP;
679
      break;
680
    case 'S':
681
      return DOWN;
682
      break;
683
    }
684
  }
685
686
  return NO_DIRECTION;
687
}