jag

snake_c

public

Small snake game in C

Star0
Watch0
d5b77365a624812c3d287f7ad88f15ba543df086
2
ssh://git@git.jaglekon.de:30/jag/snake_c.git
snake.cC1102 lines26.2 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 MIN_SIZE 10
27
#define MAX_SIZE 100
28
29
#define STRING_LEN 20
30
31
#define SPEED 50 /* in ms */
32
33
#define MAX_LENGTH 1000
34
#define STARTING_LENGTH 2
35
36
typedef enum { NEW_GAME, SETTINGS, EXIT, MAIN_MENU } main_menu;
37
38
typedef enum {HEIGHT_S, WIDTH_S, SPEED_S, BACK_S, SETTINGS_MENU} settings;
39
40
typedef enum {V_FAST, FAST, AVERAGE, SLOW, V_SLOW, SPEED_L} speed;
41
42
typedef enum {
43
  NO_DIRECTION = -1,
44
  UP = 0,
45
  LEFT = 1,
46
  DOWN = 2,
47
  RIGHT = 3,
48
  DIRECTIONS = 4
49
} direction;
50
51
typedef enum {
52
  AIR, FRUIT, HEAD_1, HEAD_2, HORIZONTAL, VERTICAL, 
53
  TOP_RIGHT, TOP_LEFT, BOTTOM_RIGHT, BOTTOM_LEFT
54
} board_state;
55
56
typedef struct selected {
57
  int8_t x;
58
  int8_t y;
59
} selected;
60
61
typedef struct snake_part {
62
  selected coords;
63
  direction directions[2];
64
} snake_part;
65
66
typedef struct snake {
67
  snake_part * position;
68
  uint16_t length;
69
} snake;
70
71
72
uint8_t update_snake (selected game_size, snake *my_snake, direction shift);
73
void update_field(snake my_snake, selected game_size, 
74
  board_state **board, bool init);
75
selected new_fruit(selected game_size, board_state **board);
76
77
bool start_animation();
78
bool draw_menu(main_menu menu, struct winsize window_size);
79
bool draw_board(selected game_size, board_state **board,
80
  struct winsize window_size, bool redraw, bool paused);
81
void draw_pause(struct winsize window_size, 
82
  uint8_t field_width,uint8_t field_height);
83
84
char *give_state(board_state square);
85
86
direction read_direction(char *string, bool wasd_enabled);
87
88
int main(int argc, char *args[]) {
89
90
  /* command argument handling */
91
92
  if (argc > 1) {
93
    if (argc > 2) {
94
      printf("Too many parameters\n");
95
      goto help_info;
96
    }
97
98
    if (!strcmp(args[1], "-h") || !strcmp(args[1], "--help")) {
99
100
      printf("This is a small TUI snake game.\n\n");
101
      printf("Controls: Arrow keys/WASD to control the snake.\n");
102
      printf("          P to pause.\n");
103
      printf("          Q to quit.\n");
104
105
      return 0;
106
    }
107
108
    printf("Argument \"%s\" not recognized\n", args[1]);
109
110
  help_info:
111
    printf("Try \"-h\" or \"--help\" for more information\n");
112
    return 1;
113
  }
114
115
  /* Setting up terminal with termios */
116
117
  struct termios old, new;
118
119
  tcgetattr(STDIN_FILENO, &old);
120
121
  new = old;
122
123
  new.c_lflag &= ~(ECHO | ICANON);
124
  new.c_cc[VMIN] = 0;
125
  new.c_cc[VTIME] = 0;
126
127
  tcsetattr(STDIN_FILENO, TCSANOW, &new);
128
129
  srand(time(NULL));
130
131
  printf("\033[?25l");
132
133
  struct winsize init_size, current_size;
134
  ioctl(STDOUT_FILENO, TIOCGWINSZ, &init_size);
135
  current_size = init_size;
136
  bool too_small = false;
137
138
139
  struct timespec mili100 = {0, 1000000 * 100};
140
141
142
  /* Animation at the start */
143
  if (!start_animation())
144
    goto end_of_program;
145
146
  /* Menu */
147
148
  bool menu = true;
149
  bool setting = false;
150
  bool change_menu = true;
151
152
  main_menu selected_menu = NEW_GAME;
153
  settings selected_setting = HEIGHT_S;
154
155
  speed game_speed = AVERAGE;
156
157
  selected game_size = {WIDTH, HEIGHT};
158
159
  while (menu) {
160
161
    bool pressed_enter = false;
162
    bool changed_selected = false;
163
164
    direction arrow = NO_DIRECTION;
165
166
    char string[STRING_LEN] = {};
167
    ssize_t length_string = read(STDIN_FILENO, string, sizeof(string) - 1);
168
169
170
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &current_size);
171
172
    if (current_size.ws_col != init_size.ws_col ||
173
        current_size.ws_row != init_size.ws_row) {
174
      init_size = current_size;
175
      changed_selected = true;
176
    }
177
178
179
    if (length_string > 0) {
180
      string[length_string] = '\0';
181
182
      arrow = read_direction(string, false);
183
184
      if (string[0] == '\n')
185
        pressed_enter = true;
186
      
187
      if (toupper(string[0]) == 'Q')
188
        goto end_of_program;
189
    }
190
191
    if (!setting) {
192
      
193
      if (!too_small) {
194
        switch (arrow) {
195
          case UP:
196
            selected_menu = (selected_menu + (MAIN_MENU - 1)) % MAIN_MENU;
197
            changed_selected = true;
198
            break;
199
          case DOWN:
200
            selected_menu = (selected_menu + 1) % MAIN_MENU;
201
            changed_selected = true;
202
            break;
203
          default:
204
            break;
205
        }
206
      }
207
208
      if (changed_selected || change_menu) {
209
        too_small = draw_menu(selected_menu, init_size);
210
        change_menu = false;
211
      }
212
213
      if (pressed_enter && !too_small) {
214
        switch (selected_menu) {
215
        case NEW_GAME:
216
          menu = false;
217
          break;
218
        case SETTINGS:
219
          setting = true;
220
          change_menu = true;
221
          break;
222
        default:
223
          tcsetattr(STDIN_FILENO, TCSANOW, &old);
224
          printf("\033[?25h\n\n");
225
226
227
          printf("\033[32m");
228
          return 0;
229
          break;
230
        }
231
      }
232
    } else {
233
234
      char *setting_string[] = {
235
        "  HEIGHT  ",
236
        "  WIDTH   ",
237
        "  SPEED   ",
238
        "   BACK   "
239
      };
240
241
      char *speed_char[] = {
242
        "  VERY FAST  ",
243
        "    FAST     ",
244
        "   AVERAGE   ",
245
        "    SLOW     ",
246
        "  VERY SLOW  "
247
      };
248
249
      uint8_t width = (uint8_t) strlen(setting_string[0]);
250
      uint8_t width_setting = (uint8_t) strlen(speed_char[0]);
251
252
253
      if (changed_selected || change_menu) {
254
              
255
        uint8_t i, j;
256
257
        printf("\033[2J\033[H");
258
      
259
         
260
        for (i = 0; i < SETTINGS_MENU; i++) {
261
      
262
          printf("+");
263
          for (j = 0; j < width; j++)
264
            printf("-");
265
          printf("+");
266
267
          if (i != BACK_S) {
268
            printf ("  +");
269
270
            for (j = 0; j < width_setting; j++)
271
              printf("-");
272
            printf("+");
273
          }
274
275
          printf("\n|");
276
277
          if (i == selected_setting)
278
            printf("\033[32m");
279
280
          printf("%s\033[0m", setting_string[i]);
281
282
          printf("|");
283
284
          if (i != BACK_S) {
285
            printf("  |");
286
            if (selected_setting == i)
287
              printf("\033[32m");
288
289
            switch (i) {
290
              case SPEED_S:
291
                printf("%s", speed_char[game_speed]);
292
                break;
293
294
              case WIDTH_S:
295
              case HEIGHT_S:
296
              for (j = 0; j < (width_setting - 3) >> 1; j++)
297
                printf(" ");
298
              printf("%3d", (i == WIDTH_S) ? game_size.x : game_size.y);
299
              for (j = 0; j < width_setting - ((width_setting - 3) >> 1) - 3; j++)
300
                printf(" ");
301
302
            }
303
            printf("\033[0m");
304
            printf("|");
305
          }
306
307
          printf("\n");
308
309
          if (i != HEIGHT_S) {
310
          
311
            printf("+");
312
            for (j = 0; j < width; j++)
313
              printf("-");
314
            printf("+");
315
316
            if (i != BACK_S) {
317
              printf ("  +");
318
319
              for (j = 0; j < width_setting; j++)
320
                printf("-");
321
              printf("+");
322
            }
323
324
            if (i < SETTINGS_MENU - 1)
325
              printf("\n");
326
          }
327
        }
328
        fflush(stdout);
329
330
        change_menu = false;
331
      }
332
333
334
      if (arrow != NO_DIRECTION) {
335
336
        switch (arrow) {
337
          case UP:
338
            selected_setting = (selected_setting + (SETTINGS_MENU - 1)) % SETTINGS_MENU;
339
            break;
340
          case DOWN:
341
            selected_setting = (selected_setting + 1) % SETTINGS_MENU;
342
            break;
343
          case LEFT:
344
            switch (selected_setting) {
345
              case SPEED_S:
346
                if (game_speed < SPEED_L - 1)
347
                  game_speed++;
348
                break;
349
              case WIDTH_S:
350
                if (game_size.x > MIN_SIZE)
351
                  game_size.x--;
352
                break;
353
              case HEIGHT_S:
354
                if (game_size.y > MIN_SIZE)
355
                  game_size.y--;
356
                break;
357
              default:
358
                break;
359
            }
360
            break;
361
          case RIGHT:
362
            switch (selected_setting) {
363
              case SPEED_S:
364
                if (game_speed)
365
                  game_speed--;
366
                break;
367
              case WIDTH_S:
368
                if (game_size.x < MAX_SIZE)
369
                  game_size.x++;
370
                break;
371
              case HEIGHT_S:
372
                if (game_size.y < MAX_SIZE)
373
                  game_size.y++;
374
                break;
375
              default:
376
                break;
377
            }
378
            break;
379
          default:
380
            break;
381
        }
382
        change_menu = true;
383
384
      }
385
386
      if (pressed_enter) {
387
        switch (selected_setting) {
388
          case BACK_S:
389
            setting = false;
390
            break;
391
          default:
392
            break;
393
        }
394
        change_menu = true;
395
      }
396
      pressed_enter = false;
397
    }
398
399
  nanosleep(&mili100, NULL);
400
  }
401
402
  /* initialisation of the game */
403
404
  snake my_snake;
405
  my_snake.length = STARTING_LENGTH;
406
407
  my_snake.position = malloc((my_snake.length + 1) * sizeof(snake_part));
408
409
  direction shift = RIGHT;
410
  direction tmp_direction = shift;
411
412
  {
413
    uint16_t i;
414
415
    my_snake.position[0].coords.y = game_size.y >> 1;
416
    my_snake.position[0].coords.x = game_size.x >> 1;
417
    my_snake.position[0].directions[0] = shift;
418
    my_snake.position[0].directions[1] = shift;
419
    for (i = 1; i <= my_snake.length; i++) {
420
      my_snake.position[i].coords.y = -1;
421
      my_snake.position[i].coords.x = -1;
422
    }
423
  }
424
425
  board_state **board = malloc(game_size.y * sizeof(board_state*));
426
  board_state *values = calloc(game_size.y * game_size.x, sizeof(board_state));
427
428
  if(board == NULL || values == NULL)
429
    goto end_of_program;
430
431
  {
432
    uint8_t i;
433
434
    for (i = 0; i < game_size.y; i++)
435
      board[i] = values + i * game_size.x;
436
  }
437
438
  update_field(my_snake, game_size, board, true);
439
440
  selected fruit = new_fruit(game_size, board);
441
442
  bool paused = false;
443
444
  if ((too_small = draw_board(game_size, board, current_size, true, false)))
445
    paused = true;
446
447
448
  bool run = true;
449
450
  struct timespec milispeed = {0, 1000000 * (SPEED + game_speed * 25)};
451
452
  /* start of the actual game loop */
453
454
  while (run) {
455
456
    /* input handling  */
457
458
    
459
      char string[STRING_LEN] = {};
460
      ssize_t length_string = read(STDIN_FILENO, string, sizeof(string) - 1);
461
462
      if (length_string > 0) {
463
        string[length_string] = '\0';
464
465
        if (!too_small) {
466
          if (toupper(string[0]) == 'P' || string[0] == '\n') {
467
            paused = !paused;
468
            if (paused)
469
              draw_board(game_size, board, current_size, true, paused);
470
          }
471
472
          if (!paused)
473
            tmp_direction = read_direction(string, true);
474
        }
475
476
        if (toupper(string[0]) == 'Q')
477
          run = false;
478
      }
479
    
480
481
    /* game logic */
482
483
    /*
484
    struct timeval current;
485
    gettimeofday(&current, NULL);*/
486
487
    if (/*time_diff >= SPEED * 1000*/ true) {
488
489
      ioctl(STDOUT_FILENO, TIOCGWINSZ, &current_size);
490
491
      if (current_size.ws_col != init_size.ws_col ||
492
          current_size.ws_row != init_size.ws_row) {
493
        init_size = current_size;
494
495
        paused = true;
496
        too_small = draw_board(game_size, board, current_size, true, paused);
497
      }
498
499
      if (!paused) {
500
501
        if (tmp_direction != NO_DIRECTION &&
502
            ((shift + (DIRECTIONS >> 1)) % DIRECTIONS) != tmp_direction) {
503
          shift = tmp_direction;
504
        }
505
506
        uint8_t update = update_snake(game_size ,&my_snake, shift);
507
508
        uint8_t head_y = my_snake.position[0].coords.y;
509
        uint8_t head_x = my_snake.position[0].coords.x;
510
511
        
512
        update_field(my_snake, game_size, board, false);
513
514
        if (head_y == fruit.y && head_x == fruit.x) {
515
          my_snake.length++;
516
          snake_part *tmp = realloc(my_snake.position, (my_snake.length + 1) * sizeof(snake_part));
517
          if (tmp == NULL)
518
            goto end_free;
519
          my_snake.position = tmp;
520
          fruit = new_fruit(game_size, board);
521
        }
522
523
        draw_board(game_size, board, current_size, false, false);
524
525
        switch (update) {
526
          case 0: 
527
            printf("\nERROR\n");
528
            goto end_free;
529
            break;
530
          case 2:
531
            printf("\nHAHA LOST, LOSER");
532
            goto end_free;
533
            break;
534
          default:
535
            break;
536
        }
537
538
      }
539
    }
540
    nanosleep(&milispeed, NULL);
541
  }
542
543
end_free:
544
545
    free(my_snake.position);
546
    free(board);
547
    free(values);
548
549
end_of_program:
550
551
  tcsetattr(STDIN_FILENO, TCSANOW, &old);
552
  printf("\033[?25h\n\n");
553
554
555
  printf("\033[32m");
556
  return 0;
557
}
558
559
560
uint8_t update_snake (selected game_size, snake *my_snake, direction shift) {
561
  
562
  my_snake->position[0].directions[1] = my_snake->position[0].directions[0];
563
  my_snake->position[0].directions[0] = shift;
564
565
  uint16_t i;
566
  for (i = my_snake->length; i > 0; i--)
567
    my_snake->position[i] = my_snake->position[i - 1];
568
569
  switch (shift) {
570
  case UP:
571
    my_snake->position[0].coords.y =
572
        ((my_snake->position[0].coords.y + game_size.y - 1) % game_size.y);
573
    break;
574
  case DOWN:
575
    my_snake->position[0].coords.y = ((my_snake->position[0].coords.y + 1) % game_size.y);
576
    break;
577
  case LEFT:
578
    my_snake->position[0].coords.x =((my_snake->position[0].coords.x + game_size.x - 1) % game_size.x);
579
    break;
580
  case RIGHT:
581
    my_snake->position[0].coords.x = ((my_snake->position[0].coords.x + 1) % game_size.x);
582
    break;
583
  default:
584
    return 0;
585
    break;
586
  }
587
588
  uint8_t head_y = my_snake->position[0].coords.y;
589
  uint8_t head_x = my_snake->position[0].coords.x;
590
591
  for (i = 1; i < my_snake->length; i++) {
592
    int16_t x = my_snake->position[i].coords.x;
593
    int16_t y = my_snake->position[i].coords.y;
594
595
    if (x == head_x && y == head_y)
596
      return 2;
597
  }
598
599
600
  return 1;
601
}
602
603
void update_field(snake my_snake, selected game_size, board_state **board, bool init) {
604
605
  uint8_t i, j;
606
607
  board_state snake_bit[DIRECTIONS][DIRECTIONS] = {
608
    [UP][UP] = VERTICAL,
609
    [DOWN][DOWN] = VERTICAL,
610
    
611
    [RIGHT][RIGHT] = HORIZONTAL,
612
    [LEFT][LEFT] = HORIZONTAL,
613
    
614
    [DOWN][RIGHT] = BOTTOM_LEFT,
615
    [LEFT][UP] = BOTTOM_LEFT,
616
    
617
    [UP][RIGHT] = TOP_LEFT,
618
    [LEFT][DOWN] = TOP_LEFT,
619
    
620
    [DOWN][LEFT] = BOTTOM_RIGHT,
621
    [RIGHT][UP] = BOTTOM_RIGHT,
622
          
623
    [UP][LEFT] = TOP_RIGHT,
624
    [RIGHT][DOWN] = TOP_RIGHT
625
  };
626
627
628
  if (init) {
629
    for (i = 0; i < game_size.y; i++) {
630
      for (j = 0; j < game_size.x; j++) {
631
        board[i][j] = AIR;
632
      }
633
    }
634
635
    for (i = 1; i < my_snake.length; i++) {
636
      int16_t x = my_snake.position[i].coords.x;
637
      int16_t y = my_snake.position[i].coords.y;
638
      
639
      if (x >= 0 && y >= 0) {
640
641
        direction first = my_snake.position[i].directions[0];
642
        direction second = my_snake.position[i].directions[1];
643
644
        board[y][x] = snake_bit[first][second];
645
      }
646
    }
647
  } else {
648
    
649
    int16_t x = my_snake.position[my_snake.length].coords.x;
650
    int16_t y = my_snake.position[my_snake.length].coords.y;
651
    
652
    if (x != -1 && y != -1)
653
      board[y][x] = AIR;
654
655
    
656
    x = my_snake.position[1].coords.x;
657
    y = my_snake.position[1].coords.y;
658
  
659
    direction first = my_snake.position[1].directions[0];
660
    direction second = my_snake.position[1].directions[1];
661
    
662
    if (x != -1 && y != -1)
663
      board[y][x] = snake_bit[first][second];
664
  }
665
666
667
  int16_t x = my_snake.position[0].coords.x;
668
  int16_t y = my_snake.position[0].coords.y;
669
670
  if (my_snake.position[0].directions[0] == RIGHT) {
671
    board[y][x] = HEAD_1;
672
  } else {
673
    board[y][x] = HEAD_2;
674
  }
675
}
676
677
selected new_fruit(selected game_size, board_state **board) {
678
  bool found_space = false;
679
680
  uint8_t x;
681
  uint8_t y;
682
683
  while (!found_space) {
684
    x = rand() % game_size.x;
685
    y = rand() % game_size.y;
686
687
    if (board[y][x] == AIR)
688
      found_space = true;
689
  }
690
691
  selected fruit = {x, y};
692
693
  board[fruit.y][fruit.x] = FRUIT;
694
695
  return fruit;
696
}
697
698
bool start_animation() {  
699
700
  struct winsize current_size;
701
  ioctl(STDOUT_FILENO, TIOCGWINSZ, &current_size);
702
703
  struct timespec mili10 = {0, 1000000 * 10};
704
705
  char *snake[3][7] = {
706
    {
707
      "           ________  ",
708
      "         _/ _      \\_",
709
      "________/  (_)       ",
710
      "\\       \\            ",
711
      "/_______/   _        ",
712
      "        \\_ (_)      _",
713
      "          \\________/ "
714
    },
715
    {
716
      "        ",
717
      "________",
718
      "\\      /",
719
      " \\    / ",
720
      "  \\  /  ",
721
      "___\\/___",
722
      "        ",
723
    },
724
    {
725
      "         ",
726
      "__        ",
727
      "  \\_____ ",
728
      "        \\",
729
      "   _____/",
730
      "__/      ",
731
      "         "
732
    }
733
  };
734
735
  uint8_t width_snake[3] = {};
736
737
  {
738
    uint8_t i;
739
740
    for (i = 0; i < 3; i++)
741
      width_snake[i] = (uint8_t) strlen(snake[i][0]);
742
  }
743
744
  uint8_t middle;
745
746
  uint8_t height = 7;
747
  uint8_t width = width_snake[0] + width_snake[1] + width_snake[2];
748
749
  for (middle = 1; width < current_size.ws_col; middle++)
750
    width += width_snake[1];
751
752
  int32_t end = current_size.ws_col + width;
753
754
755
756
  while (end >= 0) {
757
758
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &current_size);
759
760
    int32_t start = end - width; 
761
762
    int32_t i, j;
763
 
764
    char string[1000] = {};
765
    ssize_t length_string = read(STDIN_FILENO, string, sizeof(string) - 1);
766
767
     if (length_string > 0) {
768
    
769
      string[length_string] = '\0';
770
771
772
    if (toupper(string[0]) == 'Q')
773
      return false;
774
775
    if (string[0] == '\n')
776
      break;
777
    }
778
779
    printf("\033[2J\033[H");
780
781
    for (j = 0; j < ((height > current_size.ws_row) ? current_size.ws_row : height) ; j++) {
782
783
    for (i = 0; i < current_size.ws_col; i++) {
784
      int32_t index = i - start;
785
786
      if (index >= 0 && index < width) {
787
        if (index < width_snake[0]) {
788
          printf("%c", snake[0][j][index]);
789
        } else if (index < width_snake[0] + width_snake[1] * middle) {
790
          printf("%c", snake[1][j][(index - width_snake[0]) % width_snake[1]]);
791
        } else {
792
          printf("%c", snake[2][j][index - (width_snake[0] + width_snake[1] * middle)]);
793
        }
794
      } else {
795
        printf(" ");
796
      }
797
    }      
798
    
799
      printf("\n");
800
801
    }
802
    fflush(stdout);
803
    
804
    end--;
805
806
    nanosleep(&mili10, NULL);
807
  }
808
  return true;
809
}
810
811
bool draw_menu(main_menu menu, struct winsize window_size) {
812
813
  char *menu_string[] = {"NEW GAME", "SETTINGS", "EXIT"};
814
815
  const uint8_t width_small = 8;
816
  const uint8_t height_small = 5;
817
818
  char *menu_string_big[][6] = {
819
      {"███╗   ██╗███████╗██╗    ██╗     ██████╗  █████╗ ███╗   ███╗███████╗",
820
       "████╗  ██║██╔════╝██║    ██║    ██╔════╝ ██╔══██╗████╗ ████║██╔════╝",
821
       "██╔██╗ ██║█████╗  ██║ █╗ ██║    ██║  ███╗███████║██╔████╔██║█████╗  ",
822
       "██║╚██╗██║██╔══╝  ██║███╗██║    ██║   ██║██╔══██║██║╚██╔╝██║██╔══╝  ",
823
       "██║ ╚████║███████╗╚███╔███╔╝    ╚██████╔╝██║  ██║██║ ╚═╝ ██║███████╗",
824
       "╚═╝  ╚═══╝╚══════╝ ╚══╝╚══╝      ╚═════╝ ╚═╝  ╚═╝╚═╝     ╚═╝╚══════╝"},
825
      {"███████╗███████╗████████╗████████╗██╗███╗   ██╗ ██████╗ ███████╗",
826
       "██╔════╝██╔════╝╚══██╔══╝╚══██╔══╝██║████╗  ██║██╔════╝ ██╔════╝",
827
       "███████╗█████╗     ██║      ██║   ██║██╔██╗ ██║██║  ███╗███████╗",
828
       "╚════██║██╔══╝     ██║      ██║   ██║██║╚██╗██║██║   ██║╚════██║",
829
       "███████║███████╗   ██║      ██║   ██║██║ ╚████║╚██████╔╝███████║",
830
       "╚══════╝╚══════╝   ╚═╝      ╚═╝   ╚═╝╚═╝  ╚═══╝ ╚═════╝ ╚══════╝"},
831
      {"███████╗██╗  ██╗██╗████████╗", "██╔════╝╚██╗██╔╝██║╚══██╔══╝",
832
       "█████╗   ╚███╔╝ ██║   ██║   ", "██╔══╝   ██╔██╗ ██║   ██║   ",
833
       "███████╗██╔╝ ██╗██║   ██║   ", "╚══════╝╚═╝  ╚═╝╚═╝   ╚═╝   "}};
834
835
  const uint8_t height_line = 6;
836
  const uint8_t height_space = 2;
837
838
  const uint8_t width_text[] = {68, 64, 28};
839
840
  const uint8_t width_big = width_text[0] /*strlen(menu_string_big[0][0])*/;
841
  const uint8_t height_big =
842
      height_line * MAIN_MENU + height_space * (MAIN_MENU - 1);
843
844
  printf("\033[2J\033[H");
845
846
  if (window_size.ws_row < height_small || window_size.ws_col < width_small) {
847
    printf("Too small\n");
848
    return true;
849
  }
850
851
  uint8_t i, j, k;
852
853
  if (window_size.ws_row >= height_big && window_size.ws_col >= width_big) {
854
855
    uint8_t vertical_buffer = (window_size.ws_row - height_big) >> 1;
856
857
    for (i = 0; i < vertical_buffer; i++)
858
      printf("\n");
859
860
    for (i = 0; i < MAIN_MENU; i++) {
861
862
      if (i == menu)
863
        printf("\033[32m");
864
865
      uint8_t horizontal_buffer = (window_size.ws_col - width_text[i]) >> 1;
866
867
      for (j = 0; j < height_line; j++) {
868
869
        for (k = 0; k < horizontal_buffer; k++)
870
          printf(" ");
871
872
        printf("%s", menu_string_big[i][j]);
873
874
        if (j != height_line - 1)
875
          printf("\n");
876
      }
877
878
      printf("\033[0m");
879
880
      if (i != MAIN_MENU - 1)
881
        printf("\n\n");
882
    }
883
884
  } else {
885
886
    uint8_t vertical_buffer = (window_size.ws_row - height_small) >> 1;
887
888
    for (i = 0; i < vertical_buffer; i++)
889
      printf("\n");
890
891
    for (i = 0; i < MAIN_MENU; i++) {
892
      if (i == menu)
893
        printf("\033[32m");
894
895
      uint8_t horizontal_buffer =
896
          (window_size.ws_col - strlen(menu_string[i])) >> 1;
897
898
      for (j = 0; j < horizontal_buffer; j++)
899
        printf(" ");
900
901
      printf("%s\033[0m", menu_string[i]);
902
903
      if (i != MAIN_MENU - 1)
904
        printf("\n\n");
905
    }
906
  }
907
908
  fflush(stdout);
909
  return false;
910
}
911
912
bool draw_board(
913
  selected game_size,
914
  board_state **board,
915
  struct winsize window_size, 
916
  bool redraw, bool paused
917
) {
918
919
  uint8_t i, j;
920
921
  uint8_t actual_height = game_size.y + 2;
922
  uint8_t actual_width = game_size.x * 2 + 1 + 2;
923
924
  if (actual_width > window_size.ws_col || actual_height > window_size.ws_row) {
925
926
    printf("\033[2J\033[HToo small\n");
927
    printf("Min width: %i & Min height: %i\n", actual_height, actual_width);
928
929
    return true;
930
  }
931
932
  uint8_t vertical_buffer = (window_size.ws_row - actual_height) >> 1;
933
  uint8_t horizontal_buffer = (window_size.ws_col - actual_width) >> 1;
934
935
  if (redraw) {
936
    printf("\033[2J\033[H");
937
    for (i = 0; i < vertical_buffer; i++)
938
      printf("\n");
939
  } else {
940
    for (i = 0; i < actual_height - 1; i++)
941
      printf("\033[%c", UP_CODE);
942
    printf("\r");
943
  }
944
945
  for (i = 0; i < horizontal_buffer; i++)
946
    printf(" ");
947
  printf("╭");
948
  for (i = 0; i < game_size.x; i++)
949
    printf("──");
950
  printf("─╮\n");
951
952
  for (i = 0; i < game_size.y; i++) {
953
954
    for (j = 0; j < horizontal_buffer; j++)
955
      printf(" ");
956
957
    printf("│");
958
959
    for (j = 0; j < game_size.x; j++)
960
      printf("%s", give_state(board[i][j]));
961
962
    printf("%s", give_state(board[i][0]));
963
964
    printf("\033[%c│\n", LEFT_CODE);
965
  }
966
967
  for (i = 0; i < horizontal_buffer; i++)
968
    printf(" ");
969
  printf("╰");
970
  for (i = 0; i < game_size.x; i++)
971
    printf("──");
972
  printf("─╯");
973
974
  if (paused)
975
    draw_pause(window_size, actual_width, actual_height);
976
977
  fflush(stdout);
978
979
  return false;
980
}
981
982
void draw_pause(struct winsize window_size, uint8_t field_width,
983
                uint8_t field_height) {
984
985
  const uint8_t height = 5;
986
  const uint8_t width = 14;
987
988
  const char *pause_menu[] = {
989
    "+------------+", 
990
    "|            |",
991
    "|   PAUSED   |",
992
    "|            |",
993
    "+------------+"
994
  };
995
996
  const uint8_t going_up = (field_height + height) >> 1;
997
  const uint8_t going_right = (field_width - width) >> 1;
998
999
  const uint8_t horizontal_buffer = (window_size.ws_col - field_width) >> 1;
1000
1001
  uint8_t i, j;
1002
1003
  for (i = 0; i < going_up; i++)
1004
    printf("\033[%c", UP_CODE);
1005
  printf("\r");
1006
1007
  for (i = 0; i < height; i++) {
1008
    for (j = 0; j < horizontal_buffer; j++)
1009
      printf("\033[%c", RIGHT_CODE);
1010
    for (j = 0; j < going_right; j++)
1011
      printf("\033[%c", RIGHT_CODE);
1012
1013
    printf("\033[37m%s\033[0m\033[%c\r", pause_menu[i], DOWN_CODE);
1014
  }
1015
1016
  for (i = 0; i < going_up - height; i++)
1017
    printf("\033[%c", DOWN_CODE);
1018
  printf("\r");
1019
1020
  fflush(stdout);
1021
}
1022
1023
char *give_state(board_state square) {
1024
1025
  switch (square) {
1026
    case FRUIT:
1027
      return "\033[32m O\033[0m";
1028
      break;
1029
    case HEAD_1:
1030
      return "\033[33m═\033[31mX\033[0m";
1031
      break;
1032
    case HEAD_2:
1033
      return "\033[31m\33[CX\033[0m";
1034
      break;    
1035
    case VERTICAL:
1036
      return "\033[33m ║\033[0m";
1037
      break;
1038
    case HORIZONTAL:
1039
      return "\033[33m══\033[0m";
1040
      break;
1041
    case TOP_RIGHT:
1042
      return "\033[33m ╚\033[0m";
1043
      break;
1044
    case TOP_LEFT:
1045
      return "\033[33m═╝\033[0m";
1046
      break;
1047
    case BOTTOM_RIGHT:
1048
      return "\033[33m ╔\033[0m";
1049
      break;
1050
    case BOTTOM_LEFT:
1051
      return "\033[33m═╗\033[0m";
1052
      break;
1053
    default:
1054
      return " ·";
1055
      break;
1056
  }
1057
}
1058
1059
1060
direction read_direction(char *string, bool wasd_enabled) {
1061
1062
  if (strlen(string) >= 3) {
1063
    if (string[0] == '\033') {
1064
      if (string[1] == '[') {
1065
1066
        switch (string[2]) {
1067
        case RIGHT_CODE:
1068
          return RIGHT;
1069
          break;
1070
        case LEFT_CODE:
1071
          return LEFT;
1072
          break;
1073
        case UP_CODE:
1074
          return UP;
1075
          break;
1076
        case DOWN_CODE:
1077
          return DOWN;
1078
          break;
1079
        }
1080
      }
1081
    }
1082
  }
1083
1084
  if (wasd_enabled) {
1085
    switch (toupper(string[0])) {
1086
    case 'A':
1087
      return LEFT;
1088
      break;
1089
    case 'D':
1090
      return RIGHT;
1091
      break;
1092
    case 'W':
1093
      return UP;
1094
      break;
1095
    case 'S':
1096
      return DOWN;
1097
      break;
1098
    }
1099
  }
1100
1101
  return NO_DIRECTION;
1102
}