jag

snake_c

public

Small snake game in C

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