I just ended “beta version” of snake game and want to review it. I think there are some bugs and I hope you help me find them. Also, I called this “beta” because I don’t understand how to implement something like timer for async i/o (snake have to move every n seconds). I tried to use select()
on linux and threads (via pthreads) on windows, but it didn’t work correctly (I also asked a question about this problem on StackOverflow). Hope you help me with it too.
main.c
#include <stdio.h> #include <ctype.h> #include <stdlib.h> #include <time.h> #include <stddef.h> #include <stdbool.h> #include <stdint.h> #ifdef WIN32 #include <conio.h> // getch #include <windows.h> // SetConsoleCursorPosition #endif #ifdef __linux__ #include <termio.h> // for getch imitation #define CSI "3[" static struct termios old, new; #endif /** Types **/ #define nullptr ((void *) 0) typedef enum cell_state_t { CS_EMPTY, CS_EAT, CS_SHEAD, CS_STAIL } cell_state_t; typedef enum game_state_t { GS_ACTIVE, GS_PAUSED, GS_VICTORY, GS_DEFEAT } game_state_t; typedef enum direction_t { D_N, D_S, D_W, D_E } direction_t; typedef enum key_map_t { KM_N, KM_UP = 'w', KM_DOWN = 's', KM_LEFT = 'a', KM_RIGHT = 'd', KM_PAUSE = 'p', KM_EXIT = 'z' } key_map_t; enum { F_ROWS = 10, F_COLS = 10, F_TOTAL = F_ROWS * F_COLS }; typedef struct point_t { int16_t x, y; } point_t; typedef struct Snake { point_t p; direction_t dir; size_t tail_size; point_t tail[F_TOTAL - 1]; } Snake; /** Globals **/ static cell_state_t field[F_ROWS][F_COLS]; static Snake snake; static game_state_t game_state; static uint32_t score; /** Functions **/ #ifdef WIN32 void clear_screen(void) { system("cls"); } void restore_cursor_pos(void) { SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), (COORD) {0, 0}); } #endif #ifdef __linux__ void clear_screen(void) { system("clear"); } void restore_cursor_pos(void) { printf(CSI"1;1H"); } /* these functions were taken from * https://stackoverflow.com/questions/7469139/what-is-the-equivalent-to-getch-getche-in-linux */ void initTermios(bool echo) { tcgetattr(0, &old); /* grab old terminal i/o settings */ new = old; new.c_lflag &= ~ICANON; /* disable buffered i/o */ if (echo) { new.c_lflag |= ECHO; /* set echo mode */ } else { new.c_lflag &= ~ECHO; /* set no echo mode */ } tcsetattr(0, TCSANOW, &new); /* use these new terminal i/o settings now */ } /* Restore old terminal i/o settings */ void resetTermios(void) { tcsetattr(0, TCSANOW, &old); } int32_t getch(void) { int32_t ch; initTermios(0); ch = getchar(); resetTermios(); return ch; } #endif direction_t get_opposit_dir(direction_t d) { switch (d1) { case D_N: return D_S; case D_S: return D_N; case D_W: return D_E; case D_E: return D_W; } } key_map_t get_key_from_user(void) { int32_t c; c = tolower(getch()); switch (c) { case 'w': return KM_UP; case 's': return KM_DOWN; case 'a': return KM_LEFT; case 'd': return KM_RIGHT; case 'p': return KM_PAUSE; case 'z': return KM_EXIT; default: return KM_N; } } void place_eat(void) { uint16_t x, y; do { x = rand() % F_COLS; y = rand() % F_ROWS; } while (field[y][x] != CS_EMPTY); field[y][x] = CS_EAT; } void move_snake(direction_t dir, bool ate) { point_t buf1, buf2; buf1 = snake.p; if (get_opposit_dir(snake.dir) != dir) snake.dir = dir; field[snake.p.y][snake.p.x] = CS_EMPTY; switch (snake.dir) { case D_N: snake.p.y = (snake.p.y - 1 + F_ROWS) % F_ROWS; break; case D_S: snake.p.y = (snake.p.y + 1) % F_ROWS; break; case D_W: snake.p.x = (snake.p.x - 1 + F_COLS) % F_COLS; break; case D_E: snake.p.x = (snake.p.x + 1) % F_COLS; break; } for (size_t i = 0; i < snake.tail_size; ++i) { field[snake.tail[i].y][snake.tail[i].x] = CS_EMPTY; buf2 = snake.tail[i]; snake.tail[i] = buf1; buf1 = buf2; field[snake.tail[i].y][snake.tail[i].x] = CS_STAIL; } if (ate) { snake.tail[snake.tail_size] = buf1; field[buf1.y][buf1.x] = CS_STAIL; ++snake.tail_size; } } void draw_screen(void) { restore_cursor_pos(); for (size_t i = 0; i < F_ROWS; ++i) { for (size_t j = 0; j < F_COLS; ++j) { switch (field[i][j]) { case CS_EMPTY: putchar(' '); break; case CS_SHEAD: putchar('o'); break; case CS_STAIL: putchar('*'); break; case CS_EAT: putchar('e'); break; } putchar(' '); } if (i == 2) printf("\tScore: %d", score); else if (i == 3) printf("\tTo pause press 'p', to exit press 'z'"); putchar('\n'); } } void init_game(void) { srand(time(nullptr)); clear_screen(); for (size_t i = 0; i < F_ROWS; ++i) for (size_t j = 0; j < F_COLS; ++j) field[i][j] = CS_EMPTY; snake.p = (point_t) {4, 4}; snake.dir = D_N; snake.tail_size = 0; field[snake.p.y][snake.p.x] = CS_SHEAD; place_eat(); game_state = GS_ACTIVE; score = 0; } void main_loop(void) { direction_t nd = D_N; key_map_t k = KM_N; bool ate = false; draw_screen(); while ((k = get_key_from_user()) != KM_EXIT && (game_state == GS_ACTIVE || game_state == GS_PAUSED)) { if (k == KM_PAUSE) game_state = (game_state == GS_PAUSED) ? GS_ACTIVE : GS_PAUSED; if (game_state == GS_PAUSED) continue; switch (k) { case KM_UP: nd = D_N; break; case KM_DOWN: nd = D_S; break; case KM_LEFT: nd = D_W; break; case KM_RIGHT: nd = D_E; break; default: nd = snake.dir; } move_snake(nd, ate); ate = false; if (field[snake.p.y][snake.p.x] == CS_EAT) { ate = true; score += 100; place_eat(); } if (field[snake.p.y][snake.p.x] == CS_STAIL) game_state = GS_DEFEAT; field[snake.p.y][snake.p.x] = CS_SHEAD; draw_screen(); if (snake.tail_size == F_TOTAL - 1) { game_state = GS_VICTORY; break; } } } void end_game(void) { if (game_state == GS_VICTORY) printf("\n\nVICTORY!\n"); else printf("\n\nDEFEAT!\n"); } int main() { init_game(); main_loop(); end_game(); return 0; }