jag

snake_c

public

Small snake game in C

Star0
Watch0
985e3c37c168f54b934d4d3cfc81e3839048c615
2
ssh://git@git.jaglekon.de:30/jag/snake_c.git
snake.cC679 lines18.3 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
void 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
        if (current_size.ws_row < ACTUAL_HEIGHT ||
286
            current_size.ws_col < ACTUAL_WIDTH) {
287
          too_small = true;
288
289
          printf("\033[2J\033[HToo small\n");
290
          printf("Min width: %i & Min height: %i\n", ACTUAL_WIDTH,
291
                 ACTUAL_WIDTH);
292
        } else {
293
          too_small = false;
294
295
          draw_board(board, current_size, true);
296
          draw_pause(current_size);
297
        }
298
      }
299
300
      if (!paused) {
301
302
        start = current;
303
        if (tmp_direction != NO_DIRECTION &&
304
            ((shift + (DIRECTIONS >> 1)) % DIRECTIONS) != tmp_direction) {
305
          shift = tmp_direction;
306
        }
307
308
        uint16_t i;
309
        for (i = my_snake.length; i > 0; i--) {
310
          my_snake.position[i].y = my_snake.position[i - 1].y;
311
          my_snake.position[i].x = my_snake.position[i - 1].x;
312
        }
313
314
        switch (shift) {
315
        case UP:
316
          my_snake.position[0].y =
317
              ((my_snake.position[0].y + HEIGHT - 1) % HEIGHT);
318
          break;
319
        case DOWN:
320
          my_snake.position[0].y = ((my_snake.position[0].y + 1) % HEIGHT);
321
          break;
322
        case LEFT:
323
          my_snake.position[0].x =
324
              ((my_snake.position[0].x + WIDTH - 1) % WIDTH);
325
          break;
326
        case RIGHT:
327
          my_snake.position[0].x = ((my_snake.position[0].x + 1) % WIDTH);
328
          break;
329
        default:
330
          goto end_of_program;
331
          break;
332
        }
333
334
        uint8_t head_y = my_snake.position[0].y;
335
        uint8_t head_x = my_snake.position[0].x;
336
337
        for (i = 1; i < my_snake.length; i++) {
338
          int16_t x = my_snake.position[i].x;
339
          int16_t y = my_snake.position[i].y;
340
341
          if (x == head_x && y == head_y) {
342
            printf("\n\nHAHA LOST!!!\n\n");
343
            goto end_of_program;
344
          }
345
        }
346
347
        update_field(my_snake, board, fruit);
348
349
        if (head_y == fruit.y && head_x == fruit.x) {
350
          my_snake.length++;
351
          fruit = new_fruit(board);
352
        }
353
354
        update_field(my_snake, board, fruit);
355
        draw_board(board, current_size, false);
356
      }
357
    }
358
  }
359
360
end_of_program:
361
362
  tcsetattr(STDIN_FILENO, TCSANOW, &old);
363
  printf("\033[?25h\n\n");
364
365
  return 0;
366
}
367
368
void update_field(snake my_snake, char board[HEIGHT][WIDTH][STRING_LEN],
369
                  selected fruit) {
370
371
  uint8_t i, j;
372
373
  for (i = 0; i < HEIGHT; i++) {
374
    for (j = 0; j < WIDTH; j++) {
375
      strcpy(board[i][j], "  ");
376
    }
377
  }
378
379
  for (i = 0; i < my_snake.length; i++) {
380
    int16_t x = my_snake.position[i].x;
381
    int16_t y = my_snake.position[i].y;
382
    if (x >= 0 && y >= 0) {
383
      if (!i) {
384
        if (x == (my_snake.position[i + 1].x + 1) % WIDTH) {
385
          strcpy(board[y][x], "\033[33m═\033[31mX\033[0m");
386
        } else {
387
          strcpy(board[y][x], "\033[31m X\033[0m");
388
        }
389
      } else if (i == my_snake.length - 1) {
390
        if (x != my_snake.position[i - 1].x) {
391
          strcpy(board[y][x], "\033[33m══\033[0m");
392
        } else {
393
          strcpy(board[y][x], "\033[33m ║\033[0m");
394
        }
395
      } else {
396
        if (x != my_snake.position[i - 1].x &&
397
            x != my_snake.position[i + 1].x) {
398
          strcpy(board[y][x], "\033[33m══\033[0m");
399
        } else if (y != my_snake.position[i - 1].y &&
400
                   y != my_snake.position[i + 1].y) {
401
          strcpy(board[y][x], "\033[33m ║\033[0m");
402
        } else if ((y == (my_snake.position[i - 1].y + 1) % HEIGHT &&
403
                    x == (my_snake.position[i + 1].x + 1) % WIDTH) ||
404
                   (y == (my_snake.position[i + 1].y + 1) % HEIGHT &&
405
                    x == (my_snake.position[i - 1].x + 1) % WIDTH)) {
406
          strcpy(board[y][x], "\033[33m═╝\033[0m");
407
        } else if ((y == (my_snake.position[i - 1].y + (HEIGHT - 1)) % HEIGHT &&
408
                    x == (my_snake.position[i + 1].x + 1) % WIDTH) ||
409
                   (y == (my_snake.position[i + 1].y + HEIGHT - 1) % HEIGHT &&
410
                    x == (my_snake.position[i - 1].x + 1) % WIDTH)) {
411
          strcpy(board[y][x], "\033[33m═╗\033[0m");
412
        } else if ((y == (my_snake.position[i - 1].y + 1) % HEIGHT &&
413
                    x == (my_snake.position[i + 1].x + (WIDTH - 1)) % WIDTH) ||
414
                   (y == (my_snake.position[i + 1].y + 1) % HEIGHT &&
415
                    x == (my_snake.position[i - 1].x + (WIDTH - 1)) % WIDTH)) {
416
          strcpy(board[y][x], "\033[33m ╚\033[0m");
417
        } else if ((y == (my_snake.position[i - 1].y + (HEIGHT - 1)) % HEIGHT &&
418
                    x == (my_snake.position[i + 1].x + (WIDTH - 1)) % WIDTH) ||
419
                   (y == (my_snake.position[i + 1].y + (HEIGHT - 1)) % HEIGHT &&
420
                    x == (my_snake.position[i - 1].x + (WIDTH - 1)) % WIDTH)) {
421
          strcpy(board[y][x], "\033[33m ╔\033[0m");
422
        }
423
      }
424
    }
425
  }
426
427
  if (fruit.x >= 0 && fruit.y >= 0)
428
    strcpy(board[fruit.y][fruit.x], "\033[32m O\033[0m");
429
}
430
431
selected new_fruit(char board[HEIGHT][WIDTH][STRING_LEN]) {
432
  bool found_space = false;
433
434
  uint8_t x;
435
  uint8_t y;
436
437
  while (!found_space) {
438
    x = rand() % WIDTH;
439
    y = rand() % HEIGHT;
440
441
    if (!strcmp(board[y][x], "  "))
442
      found_space = true;
443
  }
444
445
  selected fruit = {x, y};
446
447
  return fruit;
448
}
449
450
bool draw_menu(main_menu menu, struct winsize window_size) {
451
452
  char *menu_string[] = {"NEW GAME", "SETTINGS", "EXIT"};
453
454
  const uint8_t width_small = 8;
455
  const uint8_t height_small = 5;
456
457
  char *menu_string_big[][6] = {
458
      {"███╗   ██╗███████╗██╗    ██╗     ██████╗  █████╗ ███╗   ███╗███████╗",
459
       "████╗  ██║██╔════╝██║    ██║    ██╔════╝ ██╔══██╗████╗ ████║██╔════╝",
460
       "██╔██╗ ██║█████╗  ██║ █╗ ██║    ██║  ███╗███████║██╔████╔██║█████╗  ",
461
       "██║╚██╗██║██╔══╝  ██║███╗██║    ██║   ██║██╔══██║██║╚██╔╝██║██╔══╝  ",
462
       "██║ ╚████║███████╗╚███╔███╔╝    ╚██████╔╝██║  ██║██║ ╚═╝ ██║███████╗",
463
       "╚═╝  ╚═══╝╚══════╝ ╚══╝╚══╝      ╚═════╝ ╚═╝  ╚═╝╚═╝     ╚═╝╚══════╝"},
464
      {"███████╗███████╗████████╗████████╗██╗███╗   ██╗ ██████╗ ███████╗",
465
       "██╔════╝██╔════╝╚══██╔══╝╚══██╔══╝██║████╗  ██║██╔════╝ ██╔════╝",
466
       "███████╗█████╗     ██║      ██║   ██║██╔██╗ ██║██║  ███╗███████╗",
467
       "╚════██║██╔══╝     ██║      ██║   ██║██║╚██╗██║██║   ██║╚════██║",
468
       "███████║███████╗   ██║      ██║   ██║██║ ╚████║╚██████╔╝███████║",
469
       "╚══════╝╚══════╝   ╚═╝      ╚═╝   ╚═╝╚═╝  ╚═══╝ ╚═════╝ ╚══════╝"},
470
      {"███████╗██╗  ██╗██╗████████╗", "██╔════╝╚██╗██╔╝██║╚══██╔══╝",
471
       "█████╗   ╚███╔╝ ██║   ██║   ", "██╔══╝   ██╔██╗ ██║   ██║   ",
472
       "███████╗██╔╝ ██╗██║   ██║   ", "╚══════╝╚═╝  ╚═╝╚═╝   ╚═╝   "}};
473
474
  const uint8_t height_line = 6;
475
  const uint8_t height_space = 2;
476
477
  const uint8_t width_text[] = {68, 64, 28};
478
479
  const uint8_t width_big = width_text[0] /*strlen(menu_string_big[0][0])*/;
480
  const uint8_t height_big =
481
      height_line * MAIN_MENU + height_space * (MAIN_MENU - 1);
482
483
  printf("\033[2J\033[H");
484
485
  if (window_size.ws_row < height_small || window_size.ws_col < width_small) {
486
    printf("Too small\n");
487
    return true;
488
  }
489
490
  uint8_t i, j, k;
491
492
  if (window_size.ws_row >= height_big && window_size.ws_col >= width_big) {
493
494
    uint8_t vertical_buffer = (window_size.ws_row - height_big) >> 1;
495
496
    for (i = 0; i < vertical_buffer; i++)
497
      printf("\n");
498
499
    for (i = 0; i < MAIN_MENU; i++) {
500
501
      if (i == menu)
502
        printf("\033[32m");
503
504
      uint8_t horizontal_buffer = (window_size.ws_col - width_text[i]) >> 1;
505
506
      for (j = 0; j < height_line; j++) {
507
508
        for (k = 0; k < horizontal_buffer; k++)
509
          printf(" ");
510
511
        printf("%s", menu_string_big[i][j]);
512
513
        if (j != height_line - 1)
514
          printf("\n");
515
      }
516
517
      printf("\033[0m");
518
519
      if (i != MAIN_MENU - 1)
520
        printf("\n\n");
521
    }
522
523
  } else {
524
525
    uint8_t vertical_buffer = (window_size.ws_row - height_small) >> 1;
526
527
    for (i = 0; i < vertical_buffer; i++)
528
      printf("\n");
529
530
    for (i = 0; i < MAIN_MENU; i++) {
531
      if (i == menu)
532
        printf("\033[32m");
533
534
      uint8_t horizontal_buffer =
535
          (window_size.ws_col - strlen(menu_string[i])) >> 1;
536
537
      for (j = 0; j < horizontal_buffer; j++)
538
        printf(" ");
539
540
      printf("%s\033[0m", menu_string[i]);
541
542
      if (i != MAIN_MENU - 1)
543
        printf("\n\n");
544
    }
545
  }
546
547
  fflush(stdout);
548
  return false;
549
}
550
551
void draw_board(char board[HEIGHT][WIDTH][STRING_LEN],
552
                struct winsize window_size, bool redraw) {
553
554
  uint8_t i, j;
555
556
  uint8_t actual_height = HEIGHT + 2;
557
  uint8_t actual_width = WIDTH * 2 + 2;
558
559
  uint8_t vertical_buffer = (window_size.ws_row - actual_height) >> 1;
560
  uint8_t horizontal_buffer = (window_size.ws_col - actual_width) >> 1;
561
562
  if (redraw) {
563
    printf("\033[2J\033[H");
564
    for (i = 0; i < vertical_buffer; i++)
565
      printf("\n");
566
  } else {
567
    for (i = 0; i < actual_height - 1; i++)
568
      printf("\033[%c", UP_CODE);
569
    printf("\r");
570
  }
571
572
  for (i = 0; i < horizontal_buffer; i++)
573
    printf(" ");
574
  printf("+");
575
  for (i = 0; i < WIDTH; i++)
576
    printf("--");
577
  printf("+\n");
578
579
  for (i = 0; i < HEIGHT; i++) {
580
581
    for (j = 0; j < horizontal_buffer; j++)
582
      printf(" ");
583
584
    printf("|");
585
586
    for (j = 0; j < WIDTH; j++)
587
      printf("%s", board[i][j]);
588
589
    printf("|\n");
590
  }
591
592
  for (i = 0; i < horizontal_buffer; i++)
593
    printf(" ");
594
  printf("+");
595
  for (i = 0; i < WIDTH; i++)
596
    printf("--");
597
  printf("+");
598
  fflush(stdout);
599
}
600
601
void draw_pause(struct winsize window_size) {
602
603
  const uint8_t height = 5;
604
  const uint8_t width = 14;
605
606
  const char *pause_menu[] = {"+------------+", "|            |",
607
                              "|   PAUSED   |", "|            |",
608
                              "+------------+"};
609
610
  const uint8_t going_up = (ACTUAL_HEIGHT + height) >> 1;
611
  const uint8_t going_right = (ACTUAL_WIDTH - width) >> 1;
612
613
  const uint8_t horizontal_buffer = (window_size.ws_col - ACTUAL_WIDTH) >> 1;
614
615
  uint8_t i, j;
616
617
  for (i = 0; i < going_up; i++)
618
    printf("\033[%c", UP_CODE);
619
  printf("\r");
620
621
  for (i = 0; i < height; i++) {
622
    for (j = 0; j < horizontal_buffer; j++)
623
      printf("\033[%c", RIGHT_CODE);
624
    for (j = 0; j < going_right; j++)
625
      printf("\033[%c", RIGHT_CODE);
626
627
    printf("\033[37m%s\033[0m\033[%c\r", pause_menu[i], DOWN_CODE);
628
  }
629
630
  for (i = 0; i < going_up - height; i++)
631
    printf("\033[%c", DOWN_CODE);
632
  printf("\r");
633
634
  fflush(stdout);
635
}
636
637
direction read_direction(char *string, bool wasd_enabled) {
638
639
  if (strlen(string) >= 3) {
640
    if (string[0] == '\033') {
641
      if (string[1] == '[') {
642
643
        switch (string[2]) {
644
        case RIGHT_CODE:
645
          return RIGHT;
646
          break;
647
        case LEFT_CODE:
648
          return LEFT;
649
          break;
650
        case UP_CODE:
651
          return UP;
652
          break;
653
        case DOWN_CODE:
654
          return DOWN;
655
          break;
656
        }
657
      }
658
    }
659
  }
660
661
  if (wasd_enabled) {
662
    switch (toupper(string[0])) {
663
    case 'A':
664
      return LEFT;
665
      break;
666
    case 'D':
667
      return RIGHT;
668
      break;
669
    case 'W':
670
      return UP;
671
      break;
672
    case 'S':
673
      return DOWN;
674
      break;
675
    }
676
  }
677
678
  return NO_DIRECTION;
679
}