jag

snake_c

public

Small snake game in C

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