General refactor and minor improvements
This commit is contained in:
		
							
								
								
									
										262
									
								
								snake.c
									
									
									
									
									
								
							
							
						
						
									
										262
									
								
								snake.c
									
									
									
									
									
								
							| @@ -34,15 +34,19 @@ typedef enum { | ||||
|     DIRECTION_LEFT = 3 | ||||
| } direction; | ||||
|  | ||||
| static direction PREVIOUS_DIRECTION = DIRECTION_DOWN; | ||||
| static direction CURRENT_DIRECTION = DIRECTION_DOWN; | ||||
| // Snake globals | ||||
| static direction PREVIOUS_DIRECTION; | ||||
| static direction CURRENT_DIRECTION; | ||||
| static coordinate SNAKE_HEAD; | ||||
| static coordinate SNAKE_TAIL; | ||||
|  | ||||
| // Map globals | ||||
| static coordinate RENDER_AT; | ||||
| static coordinate MESSAGE_LOCATION = {0,0}; | ||||
| static int MAP_HEIGHT = 20; | ||||
| static int MAP_WIDTH = 20; | ||||
| static rooster *GRID; | ||||
| static pthread_mutex_t MUTEX; | ||||
| static grid *GRID; | ||||
| static pthread_mutex_t SNAKE_MUTEX; | ||||
|  | ||||
| /* | ||||
|  * Create a snake body part. | ||||
| @@ -51,10 +55,10 @@ static pthread_mutex_t MUTEX; | ||||
|  * dir: the direction the body part should point to. | ||||
|  * | ||||
|  * Output: | ||||
|  * a character representing that bodypart. | ||||
|  * a character representing that body part. | ||||
|  */ | ||||
| static char get_body_part(const direction dir) { | ||||
|     return (char)dir + '0'; | ||||
|     return (char)(dir + '0'); | ||||
| } | ||||
|  | ||||
| /* | ||||
| @@ -89,15 +93,15 @@ static void create_grid(void) { | ||||
|  | ||||
|     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; | ||||
|         const int bottom_line = i > grid_size - (MAP_WIDTH + 2); | ||||
|         const int top_line = i < MAP_WIDTH + 1; | ||||
|  | ||||
|         int line_position = modulo(i, MAP_WIDTH + 1); | ||||
|         const int line_position = modulo(i, MAP_WIDTH + 1); | ||||
|  | ||||
|         int line_start = line_position == 1; | ||||
|         int line_end = line_position == MAP_WIDTH; | ||||
|         const int line_start = line_position == 1; | ||||
|         const int line_end = line_position == MAP_WIDTH; | ||||
|  | ||||
|         int newline = line_position == 0; | ||||
|         const int newline = line_position == 0; | ||||
|  | ||||
|         if (newline) { | ||||
|             map[i - 1] = '\n'; | ||||
| @@ -110,36 +114,35 @@ static void create_grid(void) { | ||||
|  | ||||
|     map[grid_size - 1] = '\0'; | ||||
|  | ||||
|     GRID = grid_from_string(map); | ||||
|     free(map); | ||||
|     GRID = grid_create_from_string(map); | ||||
|  | ||||
|     rooster_plaats(GRID, rooster_breedte(GRID) / 2, rooster_hoogte(GRID) / 2, get_body_part(CURRENT_DIRECTION)); | ||||
|     free(map); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Spawn a piece of food at an empty grid location. | ||||
|  * | ||||
|  * Side Effect: | ||||
|  * One of the empty grid spaces gets replaced with a piece of food. | ||||
|  */ | ||||
| static void generate_food() { | ||||
| static void generate_food(void) { | ||||
|     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}; | ||||
|             if (grid_fetch(GRID, x, y) == CELL_EMPTY) { | ||||
|                 const 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); | ||||
|     grid_put(GRID, food_location.x, food_location.y, CELL_FOOD); | ||||
| } | ||||
|  | ||||
| /* | ||||
| @@ -150,34 +153,33 @@ static void generate_food() { | ||||
|  * | ||||
|  * Side Effects: | ||||
|  * Seed random with the current time. | ||||
|  * (Re)set the global variables. | ||||
|  * initialize the mutex | ||||
|  * | ||||
|  */ | ||||
| static void initialize(void) { | ||||
|     // Seed random. | ||||
|     srand(time(NULL)); | ||||
|  | ||||
|     // Create the grid. | ||||
|     create_grid(); | ||||
|  | ||||
|     if (GRID == NULL) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Set globals. | ||||
|     CURRENT_DIRECTION = DIRECTION_DOWN; | ||||
|     PREVIOUS_DIRECTION = DIRECTION_DOWN; | ||||
|     SNAKE_HEAD = (coordinate){.x = grid_width(GRID) / 2, .y = grid_height(GRID) / 2}; | ||||
|     SNAKE_TAIL = SNAKE_HEAD; | ||||
|     RENDER_AT = (coordinate){.x = OFFSET_X, .y = OFFSET_Y}; | ||||
|     MESSAGE_LOCATION = (coordinate){.x = RENDER_AT.x, .y = RENDER_AT.y + grid_height(GRID) + 2}; | ||||
|  | ||||
|     // 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; | ||||
|  | ||||
|     // Create the first body part and spawn the first piece of food. | ||||
|     grid_put(GRID, SNAKE_HEAD.x, SNAKE_HEAD.y, get_body_part(CURRENT_DIRECTION)); | ||||
|     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; | ||||
|     pthread_mutex_init(&SNAKE_MUTEX, NULL); | ||||
| } | ||||
|  | ||||
| /* | ||||
| @@ -187,11 +189,11 @@ static void initialize(void) { | ||||
|  * c: The char to check. | ||||
|  * | ||||
|  * Returns: | ||||
|  * The snake will move forward: 0 | ||||
|  * The snake will eat an apple: 1 | ||||
|  * The snake will die:          2 | ||||
|  * The snake will move forward: SNAKE_MOVE | ||||
|  * The snake will eat an apple: SNAKE_EAT | ||||
|  * The snake will die:          SNAKE_DIE | ||||
|  */ | ||||
| static snake_action collision_check(char c) { | ||||
| static snake_action collision_check(const char c) { | ||||
|     if (c == CELL_EMPTY) { | ||||
|         return SNAKE_MOVE; | ||||
|     } | ||||
| @@ -203,16 +205,32 @@ static snake_action collision_check(char c) { | ||||
|     return SNAKE_DIE; | ||||
| } | ||||
|  | ||||
| static void update_snake(coordinate new_head, snake_action action) { | ||||
| /* | ||||
|  * Move the snake to a given location | ||||
|  * | ||||
|  * Input: | ||||
|  * new_location: The location the snake should move to. | ||||
|  * | ||||
|  * Side effects: | ||||
|  * In case nothing is encountered: The snake moves to the new location. | ||||
|  * In case an obstacle is encountered (wall or part of the snake): The snake dies. | ||||
|  * In case a piece of food is encountered: The snake eats the food and grows by 1. | ||||
|  * | ||||
|  * Note: | ||||
|  * Moving to a location the snake can't reach is undefined behaviour. | ||||
|  */ | ||||
| static void update_snake(const coordinate new_location) { | ||||
|     const snake_action action = collision_check(grid_fetch(GRID, new_location.x, new_location.y)); | ||||
|  | ||||
|     if (action == SNAKE_DIE) { | ||||
|         rooster_zet_toestand(GRID, STATE_VERLOREN); | ||||
|         grid_put_state(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))) { | ||||
|         switch (get_body_part_direction(grid_fetch(GRID, SNAKE_TAIL.x, SNAKE_TAIL.y))) { | ||||
|             case DIRECTION_UP: | ||||
|                 new_tail.y--; | ||||
|                 break; | ||||
| @@ -227,13 +245,13 @@ static void update_snake(coordinate new_head, snake_action action) { | ||||
|                 break; | ||||
|         } | ||||
|  | ||||
|         rooster_plaats(GRID, SNAKE_TAIL.x, SNAKE_TAIL.y, CELL_EMPTY); | ||||
|         grid_put(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; | ||||
|     grid_put(GRID, new_location.x, new_location.y, get_body_part(CURRENT_DIRECTION)); | ||||
|     SNAKE_HEAD = new_location; | ||||
|     PREVIOUS_DIRECTION = CURRENT_DIRECTION; | ||||
|  | ||||
|     if (action == SNAKE_EAT) { | ||||
| @@ -241,54 +259,96 @@ static void update_snake(coordinate new_head, snake_action action) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Handle snake movement. | ||||
|  * | ||||
|  * Input: | ||||
|  * NULL, this signature is so it can be run as a thread. | ||||
|  * | ||||
|  * Returns: | ||||
|  * NULL when finished | ||||
|  * | ||||
|  * Side Effects: | ||||
|  * While the game is running, it moves the snake forward every 0.25 seconds and updates the game | ||||
|  * state accordingly. | ||||
|  * | ||||
|  * In case an obstacle is encountered (wall or part of the snake): It dies. | ||||
|  * In case a piece of food is encountered: It eats the apple and grows by 1. | ||||
|  */ | ||||
| static void *snake_move(void *arg) { | ||||
|     struct timespec timer; | ||||
|     timer.tv_sec = 0; | ||||
|     timer.tv_nsec = 250000000L; // Snake moves every 0.25 seconds. | ||||
|  | ||||
|     while (rooster_vraag_toestand(GRID) == STATE_AAN_HET_SPELEN) { | ||||
|     while (grid_fetch_state(GRID) == STATE_AAN_HET_SPELEN) { | ||||
|         nanosleep(&timer, NULL); | ||||
|         pthread_mutex_lock(&MUTEX); | ||||
|         coordinate new_head = SNAKE_HEAD; | ||||
|         pthread_mutex_lock(&SNAKE_MUTEX); | ||||
|         coordinate new_location = SNAKE_HEAD; | ||||
|         switch (CURRENT_DIRECTION) { | ||||
|             case DIRECTION_UP: | ||||
|                 new_head.y--; | ||||
|                 new_location.y--; | ||||
|                 break; | ||||
|             case DIRECTION_RIGHT: | ||||
|                 new_head.x++; | ||||
|                 new_location.x++; | ||||
|                 break; | ||||
|             case DIRECTION_DOWN: | ||||
|                 new_head.y++; | ||||
|                 new_location.y++; | ||||
|                 break; | ||||
|             case DIRECTION_LEFT: | ||||
|                 new_head.x--; | ||||
|                 new_location.x--; | ||||
|                 break; | ||||
|         } | ||||
|  | ||||
|         snake_action action = collision_check(rooster_kijk(GRID, new_head.x, new_head.y)); | ||||
|  | ||||
|         update_snake(new_head, action); | ||||
|         update_snake(new_location); | ||||
|         show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y); | ||||
|         pthread_mutex_unlock(&MUTEX); | ||||
|         pthread_mutex_unlock(&SNAKE_MUTEX); | ||||
|     } | ||||
|     return NULL; | ||||
| } | ||||
|  | ||||
| static void turn_snake(direction dir) { | ||||
|     if ((direction)modulo(dir + 2, DIRECTION_COUNT) == PREVIOUS_DIRECTION | ||||
| /* | ||||
|  * Turn the snake in the given direction. | ||||
|  * | ||||
|  * Input: | ||||
|  * direction: The direction the snake should turn to. | ||||
|  * | ||||
|  * Side effects: | ||||
|  * If the snake is able to turn in the given direction: | ||||
|  *          The snake's direction gets updated with the new value. | ||||
|  * If the snake is unable to turn in the given direction: | ||||
|  *          The snake's direction remains unchanged. | ||||
|  */ | ||||
| static void turn_snake(const direction dir) { | ||||
|     // If the snake has a length of 1, it is able to turn around on the spot. | ||||
|     if ((direction)modulo((int)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)); | ||||
|     grid_put(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) { | ||||
| /* | ||||
|  * Handle user input. | ||||
|  * | ||||
|  * Input: | ||||
|  * NULL, this signature is so it can be run as a thread. | ||||
|  * | ||||
|  * Returns: | ||||
|  * NULL when finished | ||||
|  * | ||||
|  * Side Effects: | ||||
|  * While the game is running, it constantly updates the direction the snake is pointing to | ||||
|  * based on user input. | ||||
|  * | ||||
|  * In case ESCAPE or BACKSPACE is pressed it quits the game. | ||||
|  */ | ||||
| static void *user_input(void *arg) { | ||||
|     while (grid_fetch_state(GRID) == STATE_AAN_HET_SPELEN) { | ||||
|         timeout(1); | ||||
|         int c = getch(); | ||||
|         pthread_mutex_lock(&MUTEX); | ||||
|         const int c = getch(); | ||||
|         pthread_mutex_lock(&SNAKE_MUTEX); | ||||
|         switch (c) { | ||||
|             case KEY_UP: // fallthrough | ||||
|             case 'w': | ||||
| @@ -312,46 +372,78 @@ static void *play_snake(void *arg) { | ||||
|                 break; | ||||
|             case KEY_BACKSPACE: | ||||
|             case KEY_ESCAPE: | ||||
|                 rooster_zet_toestand(GRID, STATE_QUIT); | ||||
|                 grid_put_state(GRID, STATE_QUIT); | ||||
|                 break; | ||||
|         } | ||||
|         pthread_mutex_unlock(&MUTEX); | ||||
|         pthread_mutex_unlock(&SNAKE_MUTEX); | ||||
|     } | ||||
|     return NULL; | ||||
| } | ||||
|  | ||||
| //todo: ?? Win condition? | ||||
| /* | ||||
|  * Waits for the user to press their SPACEBAR before starting the game. | ||||
|  * | ||||
|  * Side Effects: | ||||
|  * If the user presses backspace or escape, the whole game quits. | ||||
|  */ | ||||
| static void wait_for_start(void) { | ||||
|     const char start_message[] = "Press SPACEBAR to start playing."; | ||||
|     const char empty_message[] = "                                "; | ||||
|     mvaddstr(MESSAGE_LOCATION.y, MESSAGE_LOCATION.x, start_message); | ||||
|  | ||||
|     grid_put_state(GRID, STATE_AAN_HET_SPELEN); | ||||
|  | ||||
|     for (int c = getch(); c != ' '; c = getch()) { | ||||
|         switch (c) { | ||||
|             case KEY_BACKSPACE: | ||||
|             case KEY_ESCAPE: | ||||
|                 grid_put_state(GRID, STATE_QUIT); | ||||
|                 mvaddstr(MESSAGE_LOCATION.y, MESSAGE_LOCATION.x, empty_message); | ||||
|                 return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Cleanup the start playing message. | ||||
|     mvaddstr(MESSAGE_LOCATION.y, MESSAGE_LOCATION.x, empty_message); | ||||
| } | ||||
|  | ||||
| /* | ||||
|  * Cleanup the snake memory | ||||
|  * | ||||
|  * Side effects: | ||||
|  * Frees all the memory used by the snake | ||||
|  */ | ||||
| static void snake_cleanup(void) { | ||||
|     pthread_mutex_destroy(&SNAKE_MUTEX); | ||||
|     grid_cleanup(GRID); | ||||
| } | ||||
|  | ||||
| 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); | ||||
|     wait_for_start(); | ||||
|  | ||||
|     while (getch() != ' ') {} | ||||
|     if (grid_fetch_state(GRID) == STATE_AAN_HET_SPELEN) { | ||||
|         // Create and start necessary threads. | ||||
|         pthread_t input_thread; | ||||
|         pthread_t game_tick_thread; | ||||
|  | ||||
|     // Cleanup the start playing message. | ||||
|     mvaddstr(MESSAGE_LOCATION.y, MESSAGE_LOCATION.x, "                                "); | ||||
|         pthread_create(&input_thread, NULL, user_input, NULL); | ||||
|         pthread_create(&game_tick_thread, NULL, snake_move, NULL); | ||||
|  | ||||
|     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_thread, NULL); | ||||
|         pthread_join(input_thread, 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); | ||||
|     game_exit_message(GRID, MESSAGE_LOCATION); | ||||
|  | ||||
|     rooster_klaar(GRID); | ||||
|     graceful_exit(MESSAGE_LOCATION); | ||||
|     snake_cleanup(); | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user