jag

snake_c

public

Small snake game in C

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