forked from snapshot112/minigame-menu
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