//$(which make) "${0%.c}" && ./"${0%.c}" && exit #include #include #include #include #include #include #include #include #include #define SPEED_INIT_MS 250 #define SPEED_DECREASE_WITH_SCORE_MS 2 #define SPEED_MIN_MS 50 #define BOARD_WIDTH 30 #define BOARD_HEIGHT 10 #define DISPLAY_PADDING_X 1 #define DISPLAY_PADDING_Y 2 #define DISPLAY_WIDTH (BOARD_WIDTH + DISPLAY_PADDING_X * 2) #define DISPLAY_HEIGHT (BOARD_HEIGHT + DISPLAY_PADDING_Y * 2) #define SCORE_MAX (BOARD_WIDTH * BOARD_HEIGHT) #define TAIL_SIZE_INIT 2 #define TAIL_SIZE_MAX (SCORE_MAX + TAIL_SIZE_INIT) #define FRUIT_COUNT 2 #define RANDOM_POS_TRIES_MAX 1000 int32_t max(int32_t a, int32_t b) { if (a > b) return a; return b; } int32_t random_in_range(int32_t from, int32_t to) { return (rand() % (to - from)) + from; } void configure_terminal(bool snake_mode) { static struct termios old_terminal_state; struct termios terminal_state; static int32_t old_stdin_fd_state; int32_t stdin_fd_state; if (snake_mode) { tcgetattr(STDIN_FILENO, &old_terminal_state); terminal_state = old_terminal_state; terminal_state.c_lflag &= ~(ICANON | ECHO); old_stdin_fd_state = fcntl(STDIN_FILENO, F_GETFL); stdin_fd_state = old_stdin_fd_state | O_NONBLOCK; } else { terminal_state = old_terminal_state; stdin_fd_state = old_stdin_fd_state; } tcsetattr(STDIN_FILENO, TCSANOW, &terminal_state); fcntl(STDIN_FILENO, F_SETFL, stdin_fd_state); } void clear_terminal(void) { printf("\033[2J\033[H"); } struct Position { int32_t x, y; }; struct Position position_new(int32_t x, int32_t y) { return (struct Position){ x, y }; } #define POS(x, y) position_new(x, y) struct Position position_random(void) { return POS( random_in_range(0, BOARD_WIDTH), random_in_range(0, BOARD_HEIGHT) ); } bool position_equals(struct Position a, struct Position b) { return a.x == b.x && a.y == b.y; } bool position_in_bounds(struct Position pos) { return pos.x >= 0 && pos.y >= 0 && pos.x < BOARD_WIDTH && pos.y < BOARD_HEIGHT; } struct Display_Position { uint32_t x, y; }; #define D_POS(x, y) ((struct Display_Position){ x, y }) #define D_POS_ZERO ((struct Display_Position){0}) struct Display_Position game_position_to_display_position(struct Position pos) { return D_POS(pos.x + DISPLAY_PADDING_X, pos.y + DISPLAY_PADDING_Y); } struct Display { uint8_t buffer[DISPLAY_HEIGHT][DISPLAY_WIDTH]; }; struct Display display_new(void) { struct Display display = {0}; memset(&display.buffer, ' ', sizeof(display.buffer)); return display; } uint8_t *display_at(struct Display* display, struct Display_Position pos) { assert(pos.x < DISPLAY_WIDTH && pos.y < DISPLAY_HEIGHT); return &display->buffer[pos.y][pos.x]; } void display_fill( struct Display* display, struct Display_Position from, struct Display_Position to, uint8_t with ) { for (uint32_t x = from.x; x < to.x; x++) for (uint32_t y = from.y; y < to.y; y++) *display_at(display, D_POS(x, y)) = with; } void display_frame( struct Display* display, struct Display_Position from, struct Display_Position to ) { for (uint32_t i = 0; i < 2; i++) for (uint32_t x = from.x + 1; x < to.x - 1; x++) *display_at(display, D_POS(x, from.y + i * (to.y - 1))) = '-'; for (uint32_t i = 0; i < 2; i++) for (uint32_t y = from.y + 1; y < to.y; y++) *display_at(display, D_POS(from.x + i * (to.x - 1), y)) = '|'; for (uint32_t x = 0; x < 2; x++) for (uint32_t y = 0; y < 2; y++) *display_at(display, D_POS(from.x + x * (to.x - 1), from.y + y * (to.y - 1))) = '+'; } void display_write( struct Display *display, struct Display_Position at, char *text ) { for (uint32_t i = 0; text[i]; i++) *display_at(display, D_POS(at.x + i, at.y)) = text[i]; } void display_display(struct Display* display) { clear_terminal(); for (uint32_t y = 0; y < BOARD_HEIGHT + DISPLAY_PADDING_Y * 2; y++) printf("%.*s\n", DISPLAY_WIDTH, display->buffer[y]); } enum Direction { DIRECTION_NONE, DIRECTION_RIGHT, DIRECTION_DOWN, DIRECTION_LEFT, DIRECTION_UP, }; enum Direction direction_opposite(enum Direction direction) { switch (direction) { case DIRECTION_RIGHT: return DIRECTION_LEFT; case DIRECTION_DOWN: return DIRECTION_UP; case DIRECTION_LEFT: return DIRECTION_RIGHT; case DIRECTION_UP: return DIRECTION_DOWN; default: return DIRECTION_NONE; } } struct Input { enum Direction direction; bool quit; }; struct Input input(void) { uint8_t c = 0; scanf("%c", &c); struct Input i = {0}; switch (c) { case 'w': i.direction = DIRECTION_UP; break; case 'a': i.direction = DIRECTION_LEFT; break; case 's': i.direction = DIRECTION_DOWN; break; case 'd': i.direction = DIRECTION_RIGHT; break; case 'q': i.quit = true; break; } return i; } struct Snake { struct Position pos; struct Position tail[TAIL_SIZE_MAX]; uint32_t tail_size; }; struct Game { uint32_t score; struct Snake snake; enum Direction last_direction; struct Position fruits[FRUIT_COUNT]; }; struct Game game_new(void) { struct Position snake_pos = POS(BOARD_WIDTH / 2, BOARD_HEIGHT / 2); struct Game game = { .score = 0, .snake = { .pos = snake_pos, .tail_size = TAIL_SIZE_INIT }, .last_direction = DIRECTION_RIGHT }; for (uint8_t t = 0; t < TAIL_SIZE_INIT; t++) game.snake.tail[t] = POS(snake_pos.x - t, snake_pos.y); for (uint8_t i = 0; i < FRUIT_COUNT; i++) game.fruits[i] = position_random(); return game; } struct Position game_random_free_position(struct Game *game) { struct Position pos; uint32_t try = 0; retry_random: if (try++ > RANDOM_POS_TRIES_MAX) return POS(-1, -1); pos = position_random(); if (position_equals(game->snake.pos, pos)) goto retry_random; for (uint32_t t = 0; t < game->snake.tail_size; t++) if (position_equals(game->snake.tail[t], pos)) goto retry_random; for (uint32_t f = 0; f < FRUIT_COUNT; f++) if (position_equals(game->fruits[f], pos)) goto retry_random; return pos; } bool game_step(struct Game *game, enum Direction direction) { if (game->score == SCORE_MAX) return false; struct Snake *snake = &game->snake; enum Direction move_direction = game->last_direction; if (direction != DIRECTION_NONE && direction != direction_opposite(game->last_direction)) { move_direction = direction; game->last_direction = direction; } int8_t move_x = 0, move_y = 0; switch (move_direction) { case DIRECTION_RIGHT: move_x = 1; break; case DIRECTION_DOWN: move_y = 1; break; case DIRECTION_LEFT: move_x = -1; break; case DIRECTION_UP: move_y = -1; break; default: break; } struct Position new_position = POS( snake->pos.x + move_x, snake->pos.y + move_y ); if (!position_in_bounds(new_position)) return false; struct Position previous_position = snake->pos; snake->pos = new_position; int32_t eaten_fruit = -1; for (uint32_t f = 0; f < FRUIT_COUNT; f++) { if (position_equals(snake->pos, game->fruits[f])) { eaten_fruit = f; game->score++; break; } } for (uint32_t t = snake->tail_size; t > 0; t--) snake->tail[t] = snake->tail[t - 1]; snake->tail[0] = previous_position; if (eaten_fruit >= 0) { snake->tail_size++; game->fruits[eaten_fruit] = game_random_free_position(game); } else { snake->tail[snake->tail_size] = (struct Position){0}; } for (uint32_t t = 0; t < snake->tail_size; t++) if (position_equals(snake->pos, snake->tail[t])) return false; return true; } void game_draw(struct Game *game, struct Display* display) { display_fill( display, D_POS(DISPLAY_PADDING_X, DISPLAY_PADDING_Y), D_POS(DISPLAY_WIDTH - DISPLAY_PADDING_X, DISPLAY_HEIGHT - DISPLAY_PADDING_Y), '.' ); display_frame( display, D_POS(0, 1), D_POS(DISPLAY_WIDTH, DISPLAY_HEIGHT - 2) ); for (uint32_t f = 0; f < FRUIT_COUNT; f++) { struct Display_Position fruit_pos = game_position_to_display_position(game->fruits[f]); *display_at(display, fruit_pos) = 'F'; } for (uint32_t t = 0; t < game->snake.tail_size; t++) { struct Display_Position tail_pos = game_position_to_display_position(game->snake.tail[t]); *display_at(display, tail_pos) = 'T'; } struct Display_Position snake_pos = game_position_to_display_position(game->snake.pos); *display_at(display, snake_pos) = 'S'; display_write(display, D_POS_ZERO, "snake!"); char score_message[] = "score: 000"; uint32_t score_message_size = sizeof(score_message) / sizeof(char) - 1; score_message[score_message_size - 1] = (game->score % 10) + '0'; score_message[score_message_size - 2] = (game->score / 10) + '0'; score_message[score_message_size - 3] = (game->score / 100) + '0'; display_write(display, D_POS(DISPLAY_WIDTH - score_message_size, 0), score_message); display_write(display, D_POS(0, DISPLAY_HEIGHT - 1), "controls: (w a s d) q"); } uint32_t game_current_sleep_duration_ms(struct Game *game) { return max(SPEED_INIT_MS - (SPEED_DECREASE_WITH_SCORE_MS * (int64_t)game->score), SPEED_MIN_MS); } int main() { srand(333); configure_terminal(true); struct Game game = game_new(); struct Display display = display_new(); while (true) { struct Input player_input = input(); if (player_input.quit) break; if(!game_step(&game, player_input.direction)) { printf("game over! score: %d\n", game.score); break; } game_draw(&game, &display); display_display(&display); usleep(game_current_sleep_duration_ms(&game) * 1000); } configure_terminal(false); return 0; }