jag

snake_c

public

Small snake game in C

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