jag

snake_c

public

Small snake game in C

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