// // Created by snapshot112 on 10/15/25. // #define _POSIX_C_SOURCE 199309 #include "snake.h" #include #include #include #include #include #include "grid_game_engine.h" #define CELL_EMPTY ' ' #define CELL_FOOD '$' #define CELL_WALL '#' #define DIRECTION_COUNT 4 #define OFFSET_X 6 #define OFFSET_Y 3 typedef enum { SNAKE_MOVE = 0, SNAKE_EAT = 1, SNAKE_DIE = 2 } snake_action; typedef enum { DIRECTION_UP = 0, DIRECTION_RIGHT = 1, DIRECTION_DOWN = 2, DIRECTION_LEFT = 3 } direction; static direction PREVIOUS_DIRECTION = DIRECTION_DOWN; static direction CURRENT_DIRECTION = DIRECTION_DOWN; static coordinate SNAKE_HEAD; static coordinate SNAKE_TAIL; static coordinate MESSAGE_LOCATION = {0,0}; static int MAP_HEIGHT = 20; static int MAP_WIDTH = 20; static rooster *GRID; static pthread_mutex_t MUTEX; /* * Create a snake body part. * * Input: * dir: the direction the body part should point to. * * Output: * a character representing that bodypart. */ static char get_body_part(const direction dir) { return (char)dir + '0'; } /* * Gets the direction of a body part. * * Input: * body_part: A part of the snake's body. * * Output: * The direction the next body part is pointing to. */ static direction get_body_part_direction(const char body_part) { return (direction)(body_part - '0'); } /* * Create a grid for snake with a given height and width. * * Input: * height: The height of the map. * width: The width of the map. * * Returns: * A pointer to the grid. */ static void create_grid(void) { const int grid_size = (MAP_WIDTH + 1) * MAP_HEIGHT + 1; char *map = malloc(grid_size * sizeof(char)); if (map == NULL) { return; } for (int i = 1; i <= (MAP_WIDTH + 1) * MAP_HEIGHT; i++) { // Also subtract the null terminator int bottom_line = i > grid_size - (MAP_WIDTH + 2); int top_line = i < MAP_WIDTH + 1; int line_position = modulo(i, MAP_WIDTH + 1); int line_start = line_position == 1; int line_end = line_position == MAP_WIDTH; int newline = line_position == 0; if (newline) { map[i - 1] = '\n'; } else if (top_line || bottom_line || line_start || line_end) { map[i - 1] = CELL_WALL; } else { map[i - 1] = CELL_EMPTY; } } map[grid_size - 1] = '\0'; GRID = grid_from_string(map); free(map); rooster_plaats(GRID, rooster_breedte(GRID) / 2, rooster_hoogte(GRID) / 2, get_body_part(CURRENT_DIRECTION)); } /* * */ static void generate_food() { coordinate empty_spots[MAP_HEIGHT * MAP_WIDTH]; // Usable as index when initialized like this. int available_spots = 0; for (int x = 0; x < MAP_WIDTH; x++) { for (int y = 0; y < MAP_HEIGHT; y++) { if (rooster_kijk(GRID, x, y) == CELL_EMPTY) { coordinate new_spot = {x, y}; empty_spots[available_spots] = new_spot; available_spots++; } } } // Available spots will now be a counter. const coordinate food_location = empty_spots[modulo(rand(), available_spots)]; rooster_plaats(GRID, food_location.x, food_location.y, CELL_FOOD); } /* * Setup the game(map) * * Output: * A pointer to the game grid. * * Side Effects: * Seed random with the current time. */ static void initialize(void) { srand(time(NULL)); create_grid(); if (GRID == NULL) { return; } CURRENT_DIRECTION = DIRECTION_DOWN; PREVIOUS_DIRECTION = DIRECTION_DOWN; // Set snake head and snake tail. rooster_zoek(GRID, get_body_part(CURRENT_DIRECTION), &SNAKE_HEAD.x, &SNAKE_HEAD.y); SNAKE_TAIL.x = SNAKE_HEAD.x; SNAKE_TAIL.y = SNAKE_HEAD.y; generate_food(); if (SNAKE_HEAD.x == -1) { free(GRID); GRID = NULL; return; } MESSAGE_LOCATION.y = rooster_hoogte(GRID) + OFFSET_Y + 5; MESSAGE_LOCATION.x = OFFSET_X - 5 >= 0 ? OFFSET_X - 5 : 0; } /* * Checks what happens when the snake moves over a given char. * * Input: * c: The char to check. * * Returns: * The snake will move forward: 0 * The snake will eat an apple: 1 * The snake will die: 2 */ static snake_action collision_check(char c) { if (c == CELL_EMPTY) { return SNAKE_MOVE; } if (c == CELL_FOOD) { return SNAKE_EAT; } return SNAKE_DIE; } static void update_snake(coordinate new_head, snake_action action) { if (action == SNAKE_DIE) { rooster_zet_toestand(GRID, STATE_VERLOREN); return; } if (action == SNAKE_MOVE) { coordinate new_tail = SNAKE_TAIL; switch (get_body_part_direction(rooster_kijk(GRID, SNAKE_TAIL.x, SNAKE_TAIL.y))) { case DIRECTION_UP: new_tail.y--; break; case DIRECTION_RIGHT: new_tail.x++; break; case DIRECTION_DOWN: new_tail.y++; break; case DIRECTION_LEFT: new_tail.x--; break; } rooster_plaats(GRID, SNAKE_TAIL.x, SNAKE_TAIL.y, CELL_EMPTY); SNAKE_TAIL = new_tail; } // New head placed after tail moves. It can occupy the empty space created by the tail moving. rooster_plaats(GRID, new_head.x, new_head.y, get_body_part(CURRENT_DIRECTION)); SNAKE_HEAD = new_head; PREVIOUS_DIRECTION = CURRENT_DIRECTION; if (action == SNAKE_EAT) { generate_food(); } } static void *snake_move(void *arg) { struct timespec timer; timer.tv_sec = 0; timer.tv_nsec = 250000000L; while (rooster_vraag_toestand(GRID) == STATE_AAN_HET_SPELEN) { nanosleep(&timer, NULL); pthread_mutex_lock(&MUTEX); coordinate new_head = SNAKE_HEAD; switch (CURRENT_DIRECTION) { case DIRECTION_UP: new_head.y--; break; case DIRECTION_RIGHT: new_head.x++; break; case DIRECTION_DOWN: new_head.y++; break; case DIRECTION_LEFT: new_head.x--; break; } snake_action action = collision_check(rooster_kijk(GRID, new_head.x, new_head.y)); update_snake(new_head, action); show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y); pthread_mutex_unlock(&MUTEX); } return NULL; } static void turn_snake(direction dir) { if ((direction)modulo(dir + 2, DIRECTION_COUNT) == PREVIOUS_DIRECTION && !same_coordinate(SNAKE_HEAD, SNAKE_TAIL)) { return; } rooster_plaats(GRID, SNAKE_HEAD.x, SNAKE_HEAD.y, get_body_part(dir)); CURRENT_DIRECTION = dir; show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y); } static void *play_snake(void *arg) { while (rooster_vraag_toestand(GRID) == STATE_AAN_HET_SPELEN) { timeout(1); int c = getch(); pthread_mutex_lock(&MUTEX); switch (c) { case KEY_UP: // fallthrough case 'w': case 'W': turn_snake(DIRECTION_UP); break; case KEY_DOWN: // fallthrough case 's': case 'S': turn_snake(DIRECTION_DOWN); break; case KEY_LEFT: // fallthrough case 'a': case 'A': turn_snake(DIRECTION_LEFT); break; case KEY_RIGHT: // fallthrough case 'd': case 'D': turn_snake(DIRECTION_RIGHT); break; case KEY_BACKSPACE: rooster_zet_toestand(GRID, STATE_QUIT); break; } pthread_mutex_unlock(&MUTEX); } return NULL; } //todo: ?? Win condition? void snake(void) { initialize(); if (GRID == NULL) { return; } // Created necessary threads. pthread_mutex_init(&MUTEX, NULL); pthread_t user_input; pthread_t game_tick; // Show game. erase(); show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y); mvaddstr(MESSAGE_LOCATION.y, MESSAGE_LOCATION.x, "Press SPACEBAR to start playing."); rooster_zet_toestand(GRID, STATE_AAN_HET_SPELEN); while (getch() != ' ') {} // Cleanup the start playing message. mvaddstr(MESSAGE_LOCATION.y, MESSAGE_LOCATION.x, " "); pthread_create(&user_input, NULL, play_snake, NULL); pthread_create(&game_tick, NULL, snake_move, NULL); // Wait until the gamestate is no longer STATE_AAN_HET_SPELEN pthread_join(game_tick, NULL); pthread_join(user_input, NULL); pthread_mutex_destroy(&MUTEX); rooster_klaar(GRID); graceful_exit(); }