jag

snake_c

public

Small snake game in C

Star0
Watch0
cd184c5729423a24393b6df760bbfa314e95cb7e
2
ssh://git@git.jaglekon.de:30/jag/snake_c.git
snake.cC670 lines18.0 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 timespec mili100 = {0, 1000000 * SPEED};
122
123
  /* Menu */
124
125
  bool menu = true;
126
127
  main_menu selected_menu = NEW_GAME;
128
129
  too_small = draw_menu(selected_menu, init_size);
130
131
  while (menu) {
132
133
    bool pressed_enter = false;
134
    bool changed_selected = false;
135
136
    char string[1000] = {};
137
    ssize_t length_string = read(STDIN_FILENO, string, sizeof(string) - 1);
138
139
    direction arrow = NO_DIRECTION;
140
141
    /* Check for window size */
142
143
    struct timeval current;
144
    gettimeofday(&current, NULL);
145
146
    if (true) {
147
148
      ioctl(STDOUT_FILENO, TIOCGWINSZ, &current_size);
149
150
      if (current_size.ws_col != init_size.ws_col ||
151
          current_size.ws_row != init_size.ws_row) {
152
        init_size = current_size;
153
        changed_selected = true;
154
      }
155
    }
156
157
    if (length_string > 0 && !too_small) {
158
      string[length_string] = '\0';
159
160
      arrow = read_direction(string, false);
161
162
      switch (arrow) {
163
      case UP:
164
        selected_menu = (selected_menu + (MAIN_MENU - 1)) % MAIN_MENU;
165
        changed_selected = true;
166
        break;
167
      case DOWN:
168
        selected_menu = (selected_menu + 1) % MAIN_MENU;
169
        changed_selected = true;
170
        break;
171
      default:
172
        break;
173
      }
174
175
      if (toupper(string[0]) == 'Q')
176
        goto end_of_program;
177
178
      if (string[0] == '\n')
179
        pressed_enter = true;
180
    }
181
182
    if (changed_selected)
183
      too_small = draw_menu(selected_menu, init_size);
184
185
    if (pressed_enter) {
186
      switch (selected_menu) {
187
      case NEW_GAME:
188
        menu = false;
189
        break;
190
      default:
191
        goto end_of_program;
192
        break;
193
      }
194
    }
195
    nanosleep(&mili100, NULL);
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 ((too_small = draw_board(board, current_size, true, false)))
225
    paused = true;
226
227
  direction shift = RIGHT;
228
  direction tmp_direction = shift;
229
230
  bool run = true;
231
232
  /* start of the actual game loop */
233
234
  while (run) {
235
236
    /* input handling  */
237
238
    if (!too_small) {
239
      char string[1000] = {};
240
      ssize_t length_string = read(STDIN_FILENO, string, sizeof(string) - 1);
241
242
      if (length_string > 0) {
243
        string[length_string] = '\0';
244
245
        if (!paused)
246
          tmp_direction = read_direction(string, true);
247
248
        if (toupper(string[0]) == 'Q')
249
          run = false;
250
251
        if (toupper(string[0]) == 'P') {
252
          paused = !paused;
253
          if (paused)
254
            draw_board(board, current_size, true, paused);
255
        }
256
      }
257
    }
258
259
    /* game logic */
260
261
    struct timeval current;
262
    gettimeofday(&current, NULL);
263
264
    if (/*time_diff >= SPEED * 1000*/ true) {
265
266
      ioctl(STDOUT_FILENO, TIOCGWINSZ, &current_size);
267
268
      if (current_size.ws_col != init_size.ws_col ||
269
          current_size.ws_row != init_size.ws_row) {
270
        init_size = current_size;
271
272
        paused = true;
273
        too_small = draw_board(board, current_size, true, paused);
274
      }
275
276
      if (!paused) {
277
278
        if (tmp_direction != NO_DIRECTION &&
279
            ((shift + (DIRECTIONS >> 1)) % DIRECTIONS) != tmp_direction) {
280
          shift = tmp_direction;
281
        }
282
283
        uint16_t i;
284
        for (i = my_snake.length; i > 0; i--) {
285
          my_snake.position[i].y = my_snake.position[i - 1].y;
286
          my_snake.position[i].x = my_snake.position[i - 1].x;
287
        }
288
289
        switch (shift) {
290
        case UP:
291
          my_snake.position[0].y =
292
              ((my_snake.position[0].y + HEIGHT - 1) % HEIGHT);
293
          break;
294
        case DOWN:
295
          my_snake.position[0].y = ((my_snake.position[0].y + 1) % HEIGHT);
296
          break;
297
        case LEFT:
298
          my_snake.position[0].x =
299
              ((my_snake.position[0].x + WIDTH - 1) % WIDTH);
300
          break;
301
        case RIGHT:
302
          my_snake.position[0].x = ((my_snake.position[0].x + 1) % WIDTH);
303
          break;
304
        default:
305
          goto end_of_program;
306
          break;
307
        }
308
309
        uint8_t head_y = my_snake.position[0].y;
310
        uint8_t head_x = my_snake.position[0].x;
311
312
        for (i = 1; i < my_snake.length; i++) {
313
          int16_t x = my_snake.position[i].x;
314
          int16_t y = my_snake.position[i].y;
315
316
          if (x == head_x && y == head_y) {
317
            printf("\n\nHAHA LOST!!!\n\n");
318
            goto end_of_program;
319
          }
320
        }
321
322
        update_field(my_snake, board, fruit);
323
324
        if (head_y == fruit.y && head_x == fruit.x) {
325
          my_snake.length++;
326
          fruit = new_fruit(board);
327
        }
328
329
        update_field(my_snake, board, fruit);
330
        draw_board(board, current_size, false, false);
331
      }
332
    }
333
    nanosleep(&mili100, NULL);
334
  }
335
336
end_of_program:
337
338
  tcsetattr(STDIN_FILENO, TCSANOW, &old);
339
  printf("\033[?25h\n\n");
340
341
  return 0;
342
}
343
344
void update_field(snake my_snake, char board[HEIGHT][WIDTH][STRING_LEN],
345
                  selected fruit) {
346
347
  uint8_t i, j;
348
349
  for (i = 0; i < HEIGHT; i++) {
350
    for (j = 0; j < WIDTH; j++) {
351
      strcpy(board[i][j], "  ");
352
    }
353
  }
354
355
  for (i = 0; i < my_snake.length; i++) {
356
    int16_t x = my_snake.position[i].x;
357
    int16_t y = my_snake.position[i].y;
358
    if (x >= 0 && y >= 0) {
359
      if (!i) {
360
        if (x == (my_snake.position[i + 1].x + 1) % WIDTH) {
361
          strcpy(board[y][x], "\033[33m═\033[31mX\033[0m");
362
        } else {
363
          strcpy(board[y][x], "\033[31m X\033[0m");
364
        }
365
      } else if (i == my_snake.length - 1) {
366
        if (x != my_snake.position[i - 1].x) {
367
          strcpy(board[y][x], "\033[33m══\033[0m");
368
        } else {
369
          strcpy(board[y][x], "\033[33m ║\033[0m");
370
        }
371
      } else {
372
        if (x != my_snake.position[i - 1].x &&
373
            x != my_snake.position[i + 1].x) {
374
          strcpy(board[y][x], "\033[33m══\033[0m");
375
        } else if (y != my_snake.position[i - 1].y &&
376
                   y != my_snake.position[i + 1].y) {
377
          strcpy(board[y][x], "\033[33m ║\033[0m");
378
        } else if ((y == (my_snake.position[i - 1].y + 1) % HEIGHT &&
379
                    x == (my_snake.position[i + 1].x + 1) % WIDTH) ||
380
                   (y == (my_snake.position[i + 1].y + 1) % HEIGHT &&
381
                    x == (my_snake.position[i - 1].x + 1) % WIDTH)) {
382
          strcpy(board[y][x], "\033[33m═╝\033[0m");
383
        } else if ((y == (my_snake.position[i - 1].y + (HEIGHT - 1)) % HEIGHT &&
384
                    x == (my_snake.position[i + 1].x + 1) % WIDTH) ||
385
                   (y == (my_snake.position[i + 1].y + HEIGHT - 1) % HEIGHT &&
386
                    x == (my_snake.position[i - 1].x + 1) % WIDTH)) {
387
          strcpy(board[y][x], "\033[33m═╗\033[0m");
388
        } else if ((y == (my_snake.position[i - 1].y + 1) % HEIGHT &&
389
                    x == (my_snake.position[i + 1].x + (WIDTH - 1)) % WIDTH) ||
390
                   (y == (my_snake.position[i + 1].y + 1) % HEIGHT &&
391
                    x == (my_snake.position[i - 1].x + (WIDTH - 1)) % WIDTH)) {
392
          strcpy(board[y][x], "\033[33m ╚\033[0m");
393
        } else if ((y == (my_snake.position[i - 1].y + (HEIGHT - 1)) % HEIGHT &&
394
                    x == (my_snake.position[i + 1].x + (WIDTH - 1)) % WIDTH) ||
395
                   (y == (my_snake.position[i + 1].y + (HEIGHT - 1)) % HEIGHT &&
396
                    x == (my_snake.position[i - 1].x + (WIDTH - 1)) % WIDTH)) {
397
          strcpy(board[y][x], "\033[33m ╔\033[0m");
398
        }
399
      }
400
    }
401
  }
402
403
  if (fruit.x >= 0 && fruit.y >= 0)
404
    strcpy(board[fruit.y][fruit.x], "\033[32m O\033[0m");
405
}
406
407
selected new_fruit(char board[HEIGHT][WIDTH][STRING_LEN]) {
408
  bool found_space = false;
409
410
  uint8_t x;
411
  uint8_t y;
412
413
  while (!found_space) {
414
    x = rand() % WIDTH;
415
    y = rand() % HEIGHT;
416
417
    if (!strcmp(board[y][x], "  "))
418
      found_space = true;
419
  }
420
421
  selected fruit = {x, y};
422
423
  return fruit;
424
}
425
426
bool draw_menu(main_menu menu, struct winsize window_size) {
427
428
  char *menu_string[] = {"NEW GAME", "SETTINGS", "EXIT"};
429
430
  const uint8_t width_small = 8;
431
  const uint8_t height_small = 5;
432
433
  char *menu_string_big[][6] = {
434
      {"███╗   ██╗███████╗██╗    ██╗     ██████╗  █████╗ ███╗   ███╗███████╗",
435
       "████╗  ██║██╔════╝██║    ██║    ██╔════╝ ██╔══██╗████╗ ████║██╔════╝",
436
       "██╔██╗ ██║█████╗  ██║ █╗ ██║    ██║  ███╗███████║██╔████╔██║█████╗  ",
437
       "██║╚██╗██║██╔══╝  ██║███╗██║    ██║   ██║██╔══██║██║╚██╔╝██║██╔══╝  ",
438
       "██║ ╚████║███████╗╚███╔███╔╝    ╚██████╔╝██║  ██║██║ ╚═╝ ██║███████╗",
439
       "╚═╝  ╚═══╝╚══════╝ ╚══╝╚══╝      ╚═════╝ ╚═╝  ╚═╝╚═╝     ╚═╝╚══════╝"},
440
      {"███████╗███████╗████████╗████████╗██╗███╗   ██╗ ██████╗ ███████╗",
441
       "██╔════╝██╔════╝╚══██╔══╝╚══██╔══╝██║████╗  ██║██╔════╝ ██╔════╝",
442
       "███████╗█████╗     ██║      ██║   ██║██╔██╗ ██║██║  ███╗███████╗",
443
       "╚════██║██╔══╝     ██║      ██║   ██║██║╚██╗██║██║   ██║╚════██║",
444
       "███████║███████╗   ██║      ██║   ██║██║ ╚████║╚██████╔╝███████║",
445
       "╚══════╝╚══════╝   ╚═╝      ╚═╝   ╚═╝╚═╝  ╚═══╝ ╚═════╝ ╚══════╝"},
446
      {"███████╗██╗  ██╗██╗████████╗", "██╔════╝╚██╗██╔╝██║╚══██╔══╝",
447
       "█████╗   ╚███╔╝ ██║   ██║   ", "██╔══╝   ██╔██╗ ██║   ██║   ",
448
       "███████╗██╔╝ ██╗██║   ██║   ", "╚══════╝╚═╝  ╚═╝╚═╝   ╚═╝   "}};
449
450
  const uint8_t height_line = 6;
451
  const uint8_t height_space = 2;
452
453
  const uint8_t width_text[] = {68, 64, 28};
454
455
  const uint8_t width_big = width_text[0] /*strlen(menu_string_big[0][0])*/;
456
  const uint8_t height_big =
457
      height_line * MAIN_MENU + height_space * (MAIN_MENU - 1);
458
459
  printf("\033[2J\033[H");
460
461
  if (window_size.ws_row < height_small || window_size.ws_col < width_small) {
462
    printf("Too small\n");
463
    return true;
464
  }
465
466
  uint8_t i, j, k;
467
468
  if (window_size.ws_row >= height_big && window_size.ws_col >= width_big) {
469
470
    uint8_t vertical_buffer = (window_size.ws_row - height_big) >> 1;
471
472
    for (i = 0; i < vertical_buffer; i++)
473
      printf("\n");
474
475
    for (i = 0; i < MAIN_MENU; i++) {
476
477
      if (i == menu)
478
        printf("\033[32m");
479
480
      uint8_t horizontal_buffer = (window_size.ws_col - width_text[i]) >> 1;
481
482
      for (j = 0; j < height_line; j++) {
483
484
        for (k = 0; k < horizontal_buffer; k++)
485
          printf(" ");
486
487
        printf("%s", menu_string_big[i][j]);
488
489
        if (j != height_line - 1)
490
          printf("\n");
491
      }
492
493
      printf("\033[0m");
494
495
      if (i != MAIN_MENU - 1)
496
        printf("\n\n");
497
    }
498
499
  } else {
500
501
    uint8_t vertical_buffer = (window_size.ws_row - height_small) >> 1;
502
503
    for (i = 0; i < vertical_buffer; i++)
504
      printf("\n");
505
506
    for (i = 0; i < MAIN_MENU; i++) {
507
      if (i == menu)
508
        printf("\033[32m");
509
510
      uint8_t horizontal_buffer =
511
          (window_size.ws_col - strlen(menu_string[i])) >> 1;
512
513
      for (j = 0; j < horizontal_buffer; j++)
514
        printf(" ");
515
516
      printf("%s\033[0m", menu_string[i]);
517
518
      if (i != MAIN_MENU - 1)
519
        printf("\n\n");
520
    }
521
  }
522
523
  fflush(stdout);
524
  return false;
525
}
526
527
bool draw_board(char board[HEIGHT][WIDTH][STRING_LEN],
528
                struct winsize window_size, bool redraw, bool paused) {
529
530
  uint8_t i, j;
531
532
  uint8_t actual_height = HEIGHT + 2;
533
  uint8_t actual_width = WIDTH * 2 + 2;
534
535
  if (actual_width > window_size.ws_col || actual_height > window_size.ws_row) {
536
537
    printf("\033[2J\033[HToo small\n");
538
    printf("Min width: %i & Min height: %i\n", actual_height, actual_width);
539
540
    return true;
541
  }
542
543
  uint8_t vertical_buffer = (window_size.ws_row - actual_height) >> 1;
544
  uint8_t horizontal_buffer = (window_size.ws_col - actual_width) >> 1;
545
546
  if (redraw) {
547
    printf("\033[2J\033[H");
548
    for (i = 0; i < vertical_buffer; i++)
549
      printf("\n");
550
  } else {
551
    for (i = 0; i < actual_height - 1; i++)
552
      printf("\033[%c", UP_CODE);
553
    printf("\r");
554
  }
555
556
  for (i = 0; i < horizontal_buffer; i++)
557
    printf(" ");
558
  printf("+");
559
  for (i = 0; i < WIDTH; i++)
560
    printf("--");
561
  printf("+\n");
562
563
  for (i = 0; i < HEIGHT; i++) {
564
565
    for (j = 0; j < horizontal_buffer; j++)
566
      printf(" ");
567
568
    printf("|");
569
570
    for (j = 0; j < WIDTH; j++)
571
      printf("%s", board[i][j]);
572
573
    printf("|\n");
574
  }
575
576
  for (i = 0; i < horizontal_buffer; i++)
577
    printf(" ");
578
  printf("+");
579
  for (i = 0; i < WIDTH; i++)
580
    printf("--");
581
  printf("+");
582
583
  if (paused)
584
    draw_pause(window_size, actual_width, actual_height);
585
586
  fflush(stdout);
587
588
  return false;
589
}
590
591
void draw_pause(struct winsize window_size, uint8_t field_width,
592
                uint8_t field_height) {
593
594
  const uint8_t height = 5;
595
  const uint8_t width = 14;
596
597
  const char *pause_menu[] = {"+------------+", "|            |",
598
                              "|   PAUSED   |", "|            |",
599
                              "+------------+"};
600
601
  const uint8_t going_up = (field_height + height) >> 1;
602
  const uint8_t going_right = (field_width - width) >> 1;
603
604
  const uint8_t horizontal_buffer = (window_size.ws_col - field_width) >> 1;
605
606
  uint8_t i, j;
607
608
  for (i = 0; i < going_up; i++)
609
    printf("\033[%c", UP_CODE);
610
  printf("\r");
611
612
  for (i = 0; i < height; i++) {
613
    for (j = 0; j < horizontal_buffer; j++)
614
      printf("\033[%c", RIGHT_CODE);
615
    for (j = 0; j < going_right; j++)
616
      printf("\033[%c", RIGHT_CODE);
617
618
    printf("\033[37m%s\033[0m\033[%c\r", pause_menu[i], DOWN_CODE);
619
  }
620
621
  for (i = 0; i < going_up - height; i++)
622
    printf("\033[%c", DOWN_CODE);
623
  printf("\r");
624
625
  fflush(stdout);
626
}
627
628
direction read_direction(char *string, bool wasd_enabled) {
629
630
  if (strlen(string) >= 3) {
631
    if (string[0] == '\033') {
632
      if (string[1] == '[') {
633
634
        switch (string[2]) {
635
        case RIGHT_CODE:
636
          return RIGHT;
637
          break;
638
        case LEFT_CODE:
639
          return LEFT;
640
          break;
641
        case UP_CODE:
642
          return UP;
643
          break;
644
        case DOWN_CODE:
645
          return DOWN;
646
          break;
647
        }
648
      }
649
    }
650
  }
651
652
  if (wasd_enabled) {
653
    switch (toupper(string[0])) {
654
    case 'A':
655
      return LEFT;
656
      break;
657
    case 'D':
658
      return RIGHT;
659
      break;
660
    case 'W':
661
      return UP;
662
      break;
663
    case 'S':
664
      return DOWN;
665
      break;
666
    }
667
  }
668
669
  return NO_DIRECTION;
670
}