jag

snake_c

public

Small snake game in C

Star0
Watch0
7b30379da950871f180973643ee057ef5657af40
2
ssh://git@git.jaglekon.de:30/jag/snake_c.git
snake.cC472 lines9.6 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 ACTUAL_WIDTH ((WIDTH * 2) + 2)
27
#define ACTUAL_HEIGHT (HEIGHT + 2)
28
29
30
#define SPEED 100 /* in ms */
31
32
#define MAX_LENGTH 1000
33
#define STARTING_LENGTH 2
34
35
typedef enum {
36
  UP, DOWN, LEFT, RIGHT
37
} direction;
38
39
typedef enum {
40
  EMPTY, SNAKE, HEAD, FRUIT
41
} field_content;
42
43
typedef struct selected {
44
  int16_t x;
45
  int16_t y;
46
} selected;
47
48
typedef struct snake {
49
  selected position[MAX_LENGTH];
50
  uint16_t length;
51
} snake;
52
53
54
55
56
void update_field(snake my_snake, field_content board[HEIGHT][WIDTH], selected fruit);
57
selected new_fruit(field_content board[HEIGHT][WIDTH]);
58
59
void draw_board(field_content board[HEIGHT][WIDTH], struct winsize window_size);
60
void draw_pause(struct winsize window_size);
61
62
direction read_direction (char* string, direction shift);
63
64
65
66
int main(int argc, char *args[]) {
67
68
  if (argc > 1) {
69
    if (argc > 2) {
70
      printf("Too many parameters\n");
71
      goto help_info;
72
    }
73
74
    if (!strcmp(args[1], "-h") || !strcmp(args[1], "--help")) {
75
76
      printf("This is a small TUI snake game.\n\n");
77
      printf("Controls: Arrow keys/WASD to control the snake.\n");
78
      printf("          P to pause.\n");
79
      printf("          Q to quit.\n");
80
81
      return 0;
82
    }
83
84
    printf("Argument \"%s\" not recognized\n", args[1]);
85
86
  help_info:
87
    printf("Try \"-h\" or \"--help\" for more information\n");
88
    return 1;
89
  }
90
91
92
  struct termios old, new;
93
94
  tcgetattr(STDIN_FILENO, &old);
95
96
  new = old;
97
98
  new.c_lflag &= ~(ECHO | ICANON);
99
  new.c_cc[VMIN] = 0;
100
  new.c_cc[VTIME] = 1;
101
102
  tcsetattr(STDIN_FILENO, TCSANOW, &new);
103
104
  srand(time(NULL));
105
106
  printf("\033[?25l");
107
108
109
  snake my_snake = {};  
110
  my_snake.length = STARTING_LENGTH;
111
112
  {
113
    uint16_t i;
114
    
115
    my_snake.position[0].x = HEIGHT >> 1;
116
    my_snake.position[0].y = WIDTH >> 1;
117
    for (i = 1; i < my_snake.length; i++) {
118
      my_snake.position[i].y = -1;
119
      my_snake.position[i].x = -1;
120
    }
121
  }
122
123
  selected fruit = {-1, -1};
124
  field_content board[HEIGHT][WIDTH] = {0};
125
  
126
  update_field(my_snake, board, fruit);
127
  
128
  fruit = new_fruit(board);
129
  update_field(my_snake, board, fruit);
130
131
  bool paused = false;
132
133
134
  struct winsize init_size, current_size;
135
  ioctl(STDOUT_FILENO, TIOCGWINSZ, &init_size);
136
  current_size = init_size;
137
138
  bool too_small = false;
139
  
140
141
  if (current_size.ws_row < ACTUAL_HEIGHT || current_size.ws_col < ACTUAL_WIDTH) {
142
    too_small = true;
143
    paused = true;
144
145
    printf("\033[2J\033[HToo small\n");
146
    printf("Min width: %i & Min height: %i\n", ACTUAL_WIDTH, ACTUAL_WIDTH);
147
  } else {
148
    draw_board(board, current_size);
149
  }
150
151
  direction shift = RIGHT;
152
  direction tmp_direction = shift;
153
154
155
  struct timeval start, current;
156
  gettimeofday(&start, NULL);
157
158
159
  bool run = true;
160
161
  while (run) {
162
163
164
    if (!too_small) {
165
      char string[1000] = {};
166
      ssize_t length_string = read(STDIN_FILENO, string, sizeof(string) - 1);
167
      
168
      gettimeofday(&current, NULL);
169
170
      if (length_string > 0) {
171
        string[length_string] = '\0';
172
    
173
        if (!paused)
174
          tmp_direction = read_direction(string, shift);
175
176
        if (toupper(string[0]) == 'Q')
177
          run = false;
178
179
         if (toupper(string[0]) == 'P') {
180
            paused = !paused;
181
        }
182
      }
183
184
      if (paused)
185
        draw_pause(current_size);
186
    }
187
188
189
    uint64_t time_diff = (current.tv_sec - start.tv_sec) * 1000000 + (current.tv_usec - start.tv_usec);
190
191
    if (time_diff >= SPEED * 1000) {
192
     
193
      ioctl(STDOUT_FILENO, TIOCGWINSZ, &current_size);
194
      
195
      if (
196
        current_size.ws_col != init_size.ws_col || 
197
        current_size.ws_row != init_size.ws_row
198
      ) {
199
        init_size = current_size;
200
        
201
        paused = true;
202
        
203
        if (current_size.ws_row < ACTUAL_HEIGHT || current_size.ws_col < ACTUAL_WIDTH) {
204
          too_small = true;
205
          
206
          printf("\033[2J\033[HToo small\n");
207
          printf("Min width: %i & Min height: %i\n", ACTUAL_WIDTH, ACTUAL_WIDTH);
208
        } else {
209
          too_small = false;
210
          
211
          draw_board(board, current_size);
212
        }
213
      }
214
215
      if (!paused) {
216
     
217
        start = current;
218
        shift = tmp_direction;
219
220
        uint16_t i;
221
        for (i = my_snake.length; i > 0; i--) {
222
          my_snake.position[i].y = my_snake.position[i - 1].y;
223
          my_snake.position[i].x = my_snake.position[i - 1].x;
224
        }
225
226
        switch (shift) {
227
          case UP:
228
            my_snake.position[0].y = ((my_snake.position[0].y + HEIGHT - 1) % HEIGHT);
229
            break;
230
          case DOWN:
231
            my_snake.position[0].y = ((my_snake.position[0].y + 1) % HEIGHT);
232
            break;
233
          case LEFT:
234
            my_snake.position[0].x = ((my_snake.position[0].x + WIDTH - 1) % WIDTH);
235
            break;
236
          case RIGHT:
237
            my_snake.position[0].x = ((my_snake.position[0].x + 1) % WIDTH);
238
            break;
239
        }
240
      
241
        uint8_t head_y = my_snake.position[0].y;
242
        uint8_t head_x = my_snake.position[0].x;
243
244
        for (i = 1; i < my_snake.length; i++) {
245
          int16_t x = my_snake.position[i].x;
246
          int16_t y = my_snake.position[i].y;
247
248
          if (
249
            x == head_x && 
250
            y == head_y
251
          ) {
252
            printf("\n\nHAHA LOST!!!\n\n");
253
            goto end_of_program;
254
          }
255
        }
256
257
        update_field(my_snake, board, fruit);
258
259
        if (head_y == fruit.y && head_x == fruit.x) {
260
          my_snake.length++;
261
          fruit = new_fruit(board);
262
        }
263
264
265
        update_field(my_snake, board, fruit);
266
        draw_board(board, current_size);
267
      }
268
    }
269
  }
270
271
272
end_of_program:
273
274
  tcsetattr(STDIN_FILENO, TCSANOW, &old);
275
  printf("\033[?25h\n\n");
276
277
  return 0;
278
}
279
280
281
282
283
284
void update_field(snake my_snake, field_content board[HEIGHT][WIDTH], selected fruit) {
285
  
286
  uint8_t i, j;
287
288
  for (i = 0; i < HEIGHT; i++) {
289
    for (j = 0; j < WIDTH; j++) {   
290
      board[i][j] = EMPTY;
291
    }
292
  }
293
294
  for (i = 0; i < my_snake.length; i++) {
295
    int16_t x = my_snake.position[i].x;
296
    int16_t y = my_snake.position[i].y;
297
    if (x >= 0 && y >= 0)
298
      board[y][x] = (i == 0) ? HEAD : SNAKE;
299
  }
300
  
301
  if (fruit.x >= 0 && fruit.y >= 0)
302
    board[fruit.y][fruit.x] = FRUIT;
303
}
304
305
306
selected new_fruit(field_content board[HEIGHT][WIDTH]) {
307
  bool found_space = false;
308
309
  uint8_t x;
310
  uint8_t y;
311
312
  while (!found_space) {
313
    x = rand() % WIDTH;
314
    y = rand() % HEIGHT;
315
316
    if (board[y][x] == EMPTY)
317
      found_space = true;
318
  }
319
320
  selected fruit = {x, y};
321
322
  return fruit;
323
}
324
325
326
void draw_board (field_content board[HEIGHT][WIDTH], struct winsize window_size) {
327
  
328
  uint8_t i, j; 
329
330
  uint8_t vertical_buffer = (window_size.ws_row - ACTUAL_HEIGHT) >> 1;
331
  uint8_t horizontal_buffer = (window_size.ws_col - ACTUAL_WIDTH) >> 1;
332
333
  printf("\033[2J\033[H");
334
335
  for (i = 0; i < vertical_buffer; i++)
336
    printf("\n");
337
338
  for (i = 0; i < horizontal_buffer; i++)
339
      printf(" ");
340
  printf("+");
341
  for (i = 0; i < WIDTH; i++)
342
    printf("--");
343
  printf("+\n");
344
345
  for ( i = 0; i < HEIGHT; i++) {
346
347
    for (j = 0; j < horizontal_buffer; j++)
348
      printf(" ");
349
350
    printf("|");
351
352
    for (j = 0; j < WIDTH; j++) {
353
354
      switch (board[i][j]) {
355
        case EMPTY:
356
          printf("  ");
357
          break;
358
        case SNAKE:
359
          printf("\033[31m x\033[0m");
360
          break;
361
        case HEAD:
362
          printf("\033[32m X\033[0m");
363
          break;
364
        case FRUIT:
365
          printf("\033[34m O\033[0m");
366
          break;
367
      }
368
    }
369
    printf("|\n");
370
  }
371
372
  for (i = 0; i < horizontal_buffer; i++)
373
    printf(" ");
374
  printf("+");
375
  for (i = 0; i < WIDTH; i++)
376
    printf("--");
377
  printf("+");
378
  fflush(stdout);
379
}
380
381
382
void draw_pause(struct winsize window_size) {
383
384
  const uint8_t height = 5;
385
  const uint8_t width = 14;
386
387
  const char *pause_menu [] = {
388
    "+------------+",
389
    "|            |",
390
    "|   PAUSED   |",
391
    "|            |",
392
    "+------------+"
393
  };
394
395
396
  const uint8_t going_up = (ACTUAL_HEIGHT + height) >> 1;
397
  const uint8_t going_right = (ACTUAL_WIDTH - width) >> 1;
398
399
  const uint8_t horizontal_buffer = (window_size.ws_col - ACTUAL_WIDTH) >> 1;
400
401
  uint8_t i, j;
402
403
  for (i = 0; i < going_up; i++)
404
    printf("\033[%c", UP_CODE);
405
  printf("\r");
406
407
  for (i = 0; i < height; i++) {
408
    for (j = 0; j < horizontal_buffer; j++)
409
      printf("\033[%c", RIGHT_CODE);
410
    for (j = 0; j < going_right; j++)
411
      printf("\033[%c", RIGHT_CODE);
412
413
    printf("\033[37m%s\033[0m\033[%c\r", pause_menu[i], DOWN_CODE);
414
  }
415
416
  for (i = 0; i < going_up - height; i++)
417
    printf("\033[%c", DOWN_CODE);
418
  printf("\r");
419
420
  fflush(stdout);
421
}
422
423
direction read_direction (char* string, direction shift) {
424
425
  if (strlen(string) >= 3) {
426
    if (string[0] == '\033') {
427
      if (string[1] == '[') {
428
       
429
        switch (string[2]) {
430
          case RIGHT_CODE:
431
            if (shift != LEFT)
432
              return RIGHT;
433
            break;
434
          case LEFT_CODE:
435
            if (shift != RIGHT)
436
              return LEFT;
437
            break;
438
          case UP_CODE:
439
            if (shift != DOWN)
440
              return UP;
441
            break;
442
          case DOWN_CODE:
443
            if (shift != UP)
444
              return DOWN;
445
            break;
446
        }          
447
      }
448
    }
449
  }
450
451
452
  switch (toupper(string[0])) {
453
    case 'A':
454
      if (shift != RIGHT)
455
        return LEFT;
456
      break;
457
    case 'D':
458
      if (shift != LEFT)
459
        return RIGHT;
460
      break;
461
    case 'W':
462
      if (shift != DOWN)
463
        return UP;
464
      break;
465
    case 'S':
466
      if (shift != UP)
467
        return DOWN;
468
      break;
469
  }
470
471
  return shift;
472
}