jag

snake_c

public

Small snake game in C

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