jag

snake_c

public

Small snake game in C

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