| 
									
										
										
										
											2025-10-18 21:35:01 +02:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Created by snapshot112 on 15/10/2025 | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-10-16 01:10:09 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-17 15:27:18 +02:00
										 |  |  | #define _POSIX_C_SOURCE 199309
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-16 01:10:09 +02:00
										 |  |  | #include "snake.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <ncurses.h>
 | 
					
						
							| 
									
										
										
										
											2025-10-16 12:05:47 +02:00
										 |  |  | #include <stdlib.h>
 | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | #include <time.h>
 | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  | #include <pthread.h>
 | 
					
						
							|  |  |  | #include <unistd.h>
 | 
					
						
							| 
									
										
										
										
											2025-10-16 01:10:09 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-22 15:07:52 +02:00
										 |  |  | #include "../../engine/grid_game_engine.h"
 | 
					
						
							| 
									
										
										
										
											2025-10-16 01:10:09 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | #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; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  | // Snake globals
 | 
					
						
							|  |  |  | static direction PREVIOUS_DIRECTION; | 
					
						
							|  |  |  | static direction CURRENT_DIRECTION; | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | static coordinate SNAKE_HEAD; | 
					
						
							|  |  |  | static coordinate SNAKE_TAIL; | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | // Map globals
 | 
					
						
							|  |  |  | static coordinate RENDER_AT; | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | static coordinate MESSAGE_LOCATION = {0,0}; | 
					
						
							|  |  |  | static int MAP_HEIGHT = 20; | 
					
						
							|  |  |  | static int MAP_WIDTH = 20; | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  | static grid *GRID; | 
					
						
							|  |  |  | static pthread_mutex_t SNAKE_MUTEX; | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Create a snake body part. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Input: | 
					
						
							|  |  |  |  * dir: the direction the body part should point to. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Output: | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |  * a character representing that body part. | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |  */ | 
					
						
							|  |  |  | static char get_body_part(const direction dir) { | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |     return (char)(dir + '0'); | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * 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'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-16 12:05:47 +02:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * 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. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  | static void create_grid(void) { | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |     const int grid_size = (MAP_WIDTH + 1) * MAP_HEIGHT + 1; | 
					
						
							| 
									
										
										
										
											2025-10-16 12:05:47 +02:00
										 |  |  |     char *map = malloc(grid_size * sizeof(char)); | 
					
						
							|  |  |  |     if (map == NULL) { | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |         return; | 
					
						
							| 
									
										
										
										
											2025-10-16 12:05:47 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |     for (int i = 1; i <= (MAP_WIDTH + 1) * MAP_HEIGHT; i++) { | 
					
						
							|  |  |  |         // Also subtract the null terminator
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |         const int bottom_line = i > grid_size - (MAP_WIDTH + 2); | 
					
						
							|  |  |  |         const int top_line = i < MAP_WIDTH + 1; | 
					
						
							| 
									
										
										
										
											2025-10-16 12:05:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |         const int line_position = modulo(i, MAP_WIDTH + 1); | 
					
						
							| 
									
										
										
										
											2025-10-16 12:05:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |         const int line_start = line_position == 1; | 
					
						
							|  |  |  |         const int line_end = line_position == MAP_WIDTH; | 
					
						
							| 
									
										
										
										
											2025-10-16 12:05:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |         const int newline = line_position == 0; | 
					
						
							| 
									
										
										
										
											2025-10-16 12:05:47 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (newline) { | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |             map[i - 1] = '\n'; | 
					
						
							| 
									
										
										
										
											2025-10-16 12:05:47 +02:00
										 |  |  |         } else if (top_line || bottom_line || line_start || line_end) { | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |             map[i - 1] = CELL_WALL; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             map[i - 1] = CELL_EMPTY; | 
					
						
							| 
									
										
										
										
											2025-10-16 12:05:47 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     map[grid_size - 1] = '\0'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |     GRID = grid_create_from_string(map); | 
					
						
							| 
									
										
										
										
											2025-10-16 12:05:47 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |     free(map); | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |  * Spawn a piece of food at an empty grid location. | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |  * Side Effect: | 
					
						
							|  |  |  |  * One of the empty grid spaces gets replaced with a piece of food. | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  | static void generate_food(void) { | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |     coordinate empty_spots[MAP_HEIGHT * MAP_WIDTH]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     int available_spots = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (int x = 0; x < MAP_WIDTH; x++) { | 
					
						
							|  |  |  |         for (int y = 0; y < MAP_HEIGHT; y++) { | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |             if (grid_fetch(GRID, x, y) == CELL_EMPTY) { | 
					
						
							|  |  |  |                 const coordinate new_spot = {x, y}; | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |                 empty_spots[available_spots] = new_spot; | 
					
						
							|  |  |  |                 available_spots++; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const coordinate food_location = empty_spots[modulo(rand(), available_spots)]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |     grid_put(GRID, food_location.x, food_location.y, CELL_FOOD); | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Setup the game(map) | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Output: | 
					
						
							|  |  |  |  * A pointer to the game grid. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Side Effects: | 
					
						
							|  |  |  |  * Seed random with the current time. | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |  * (Re)set the global variables. | 
					
						
							|  |  |  |  * initialize the mutex | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  | static void initialize(void) { | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |     // Seed random.
 | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |     srand(time(NULL)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |     // Create the grid.
 | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |     create_grid(); | 
					
						
							|  |  |  |     if (GRID == NULL) { | 
					
						
							|  |  |  |         return; | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |     // Set globals.
 | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |     CURRENT_DIRECTION = DIRECTION_DOWN; | 
					
						
							|  |  |  |     PREVIOUS_DIRECTION = DIRECTION_DOWN; | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |     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}; | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |     // 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)); | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |     generate_food(); | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |     pthread_mutex_init(&SNAKE_MUTEX, NULL); | 
					
						
							| 
									
										
										
										
											2025-10-16 12:05:47 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Checks what happens when the snake moves over a given char. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Input: | 
					
						
							|  |  |  |  * c: The char to check. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Returns: | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |  * The snake will move forward: SNAKE_MOVE | 
					
						
							|  |  |  |  * The snake will eat an apple: SNAKE_EAT | 
					
						
							|  |  |  |  * The snake will die:          SNAKE_DIE | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  | static snake_action collision_check(const char c) { | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |     if (c == CELL_EMPTY) { | 
					
						
							|  |  |  |         return SNAKE_MOVE; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (c == CELL_FOOD) { | 
					
						
							|  |  |  |         return SNAKE_EAT; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return SNAKE_DIE; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * 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)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |     if (action == SNAKE_DIE) { | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |         grid_put_state(GRID, STATE_VERLOREN); | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (action == SNAKE_MOVE) { | 
					
						
							|  |  |  |         coordinate new_tail = SNAKE_TAIL; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |         switch (get_body_part_direction(grid_fetch(GRID, SNAKE_TAIL.x, SNAKE_TAIL.y))) { | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |             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; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |         grid_put(GRID, SNAKE_TAIL.x, SNAKE_TAIL.y, CELL_EMPTY); | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |         SNAKE_TAIL = new_tail; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // New head placed after tail moves. It can occupy the empty space created by the tail moving.
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |     grid_put(GRID, new_location.x, new_location.y, get_body_part(CURRENT_DIRECTION)); | 
					
						
							|  |  |  |     SNAKE_HEAD = new_location; | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |     PREVIOUS_DIRECTION = CURRENT_DIRECTION; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (action == SNAKE_EAT) { | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |         generate_food(); | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * 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. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  | static void *snake_move(void *arg) { | 
					
						
							| 
									
										
										
										
											2025-10-17 15:27:18 +02:00
										 |  |  |     struct timespec timer; | 
					
						
							|  |  |  |     timer.tv_sec = 0; | 
					
						
							| 
									
										
										
										
											2025-10-17 21:08:03 +02:00
										 |  |  |     timer.tv_nsec = 250000000L; // Snake moves every 0.25 seconds.
 | 
					
						
							| 
									
										
										
										
											2025-10-17 15:27:18 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |     while (grid_fetch_state(GRID) == STATE_AAN_HET_SPELEN) { | 
					
						
							| 
									
										
										
										
											2025-10-17 15:27:18 +02:00
										 |  |  |         nanosleep(&timer, NULL); | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |         pthread_mutex_lock(&SNAKE_MUTEX); | 
					
						
							|  |  |  |         coordinate new_location = SNAKE_HEAD; | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |         switch (CURRENT_DIRECTION) { | 
					
						
							|  |  |  |             case DIRECTION_UP: | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |                 new_location.y--; | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |                 break; | 
					
						
							|  |  |  |             case DIRECTION_RIGHT: | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |                 new_location.x++; | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |                 break; | 
					
						
							|  |  |  |             case DIRECTION_DOWN: | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |                 new_location.y++; | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |                 break; | 
					
						
							|  |  |  |             case DIRECTION_LEFT: | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |                 new_location.x--; | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |                 break; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |         update_snake(new_location); | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |         show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y); | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |         pthread_mutex_unlock(&SNAKE_MUTEX); | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |     } | 
					
						
							|  |  |  |     return NULL; | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * 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 | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |         && !same_coordinate(SNAKE_HEAD, SNAKE_TAIL)) { | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |         return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |     grid_put(GRID, SNAKE_HEAD.x, SNAKE_HEAD.y, get_body_part(dir)); | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |     CURRENT_DIRECTION = dir; | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |     show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y); | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * 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) { | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |         timeout(1); | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |         const int c = getch(); | 
					
						
							|  |  |  |         pthread_mutex_lock(&SNAKE_MUTEX); | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |         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: | 
					
						
							| 
									
										
										
										
											2025-10-17 21:08:03 +02:00
										 |  |  |             case KEY_ESCAPE: | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |                 grid_put_state(GRID, STATE_QUIT); | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |                 break; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |         pthread_mutex_unlock(&SNAKE_MUTEX); | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |     return NULL; | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * 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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-16 01:10:09 +02:00
										 |  |  | void snake(void) { | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |     initialize(); | 
					
						
							|  |  |  |     if (GRID == NULL) { | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |         return; | 
					
						
							| 
									
										
										
										
											2025-10-16 12:05:47 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Show game.
 | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  |     erase(); | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  |     show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y); | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |     wait_for_start(); | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |     if (grid_fetch_state(GRID) == STATE_AAN_HET_SPELEN) { | 
					
						
							|  |  |  |         // Create and start necessary threads.
 | 
					
						
							|  |  |  |         pthread_t input_thread; | 
					
						
							|  |  |  |         pthread_t game_tick_thread; | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |         pthread_create(&input_thread, NULL, user_input, NULL); | 
					
						
							|  |  |  |         pthread_create(&game_tick_thread, NULL, snake_move, NULL); | 
					
						
							| 
									
										
										
										
											2025-10-17 14:29:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |         // Wait until the gamestate is no longer STATE_AAN_HET_SPELEN
 | 
					
						
							|  |  |  |         pthread_join(game_tick_thread, NULL); | 
					
						
							|  |  |  |         pthread_join(input_thread, NULL); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |     game_exit_message(GRID, MESSAGE_LOCATION); | 
					
						
							| 
									
										
										
										
											2025-10-17 11:15:53 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-18 18:56:28 +02:00
										 |  |  |     snake_cleanup(); | 
					
						
							| 
									
										
										
										
											2025-10-16 01:10:09 +02:00
										 |  |  | } |