// // Created by snapshot112 on 10/15/25. // #include "snake.h" #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; /* * 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 rooster *create_grid(void) { const int grid_size = (MAP_WIDTH + 1) * MAP_HEIGHT + 1; char *map = malloc(grid_size * sizeof(char)); if (map == NULL) { return NULL; } 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'; rooster *grid = grid_from_string(map); free(map); rooster_plaats(grid, rooster_breedte(grid) / 2, rooster_hoogte(grid) / 2, get_body_part(CURRENT_DIRECTION)); return grid; } /* * */ static void generate_food(rooster *gp) { 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(gp, 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(gp, 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 rooster *initialize(void) { srand(time(NULL)); rooster *grid = create_grid(); if (grid == NULL) { return NULL; } // 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(grid); if (SNAKE_HEAD.x == -1) { return NULL; } MESSAGE_LOCATION.y = rooster_hoogte(grid) + OFFSET_Y + 5; MESSAGE_LOCATION.x = OFFSET_X - 5 >= 0 ? OFFSET_X - 5 : 0; return grid; } /* * 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(rooster *gp, coordinate new_head, snake_action action) { if (action == SNAKE_DIE) { rooster_zet_toestand(gp, STATE_VERLOREN); return; } if (action == SNAKE_MOVE) { coordinate new_tail = SNAKE_TAIL; switch (get_body_part_direction(rooster_kijk(gp, 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(gp, 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(gp, 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(gp); } } static void snake_move(rooster *gp) { 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(gp, new_head.x, new_head.y)); update_snake(gp, new_head, action); show_grid_on_offset(gp, OFFSET_X, OFFSET_Y); } static void turn_snake(rooster *gp, direction dir) { if ((direction)modulo(dir + 2, DIRECTION_COUNT) == PREVIOUS_DIRECTION) { return; } rooster_plaats(gp, SNAKE_HEAD.x, SNAKE_HEAD.y, get_body_part(dir)); CURRENT_DIRECTION = dir; show_grid_on_offset(gp, OFFSET_X, OFFSET_Y); } static void play_snake(rooster *gp) { timeout(500); switch (getch()) { case KEY_UP: // fallthrough case 'w': case 'W': turn_snake(gp, DIRECTION_UP); break; case KEY_DOWN: // fallthrough case 's': case 'S': turn_snake(gp, DIRECTION_DOWN); break; case KEY_LEFT: // fallthrough case 'a': case 'A': turn_snake(gp, DIRECTION_LEFT); break; case KEY_RIGHT: // fallthrough case 'd': case 'D': turn_snake(gp, DIRECTION_RIGHT); break; case KEY_BACKSPACE: rooster_zet_toestand(gp, STATE_QUIT); break; case ERR: snake_move(gp); } } void snake(void) { rooster* const gp = initialize(); if (gp == NULL) { return; } erase(); //todo: ?? Win condition? show_grid_on_offset(gp, OFFSET_X, OFFSET_Y); mvprintw(MESSAGE_LOCATION.y, MESSAGE_LOCATION.x, "Press SPACEBAR to start playing."); while (getch() != ' ') {} rooster_zet_toestand(gp, STATE_AAN_HET_SPELEN); while (rooster_vraag_toestand(gp) == STATE_AAN_HET_SPELEN) { play_snake(gp); } // game_exit_screen(gp); rooster_klaar(gp); graceful_exit(); }