MULTITHREADED FOR EPICNESSSSSS

This commit is contained in:
2025-10-17 14:29:22 +02:00
parent 0e133aff32
commit f0e9d5c751
7 changed files with 148 additions and 95 deletions

Binary file not shown.

View File

@@ -12,6 +12,10 @@
#include "rooster.h" #include "rooster.h"
int same_coordinate(coordinate a, coordinate b) {
return a.x == b.x && a.y == b.y;
}
int modulo(const int number, const int mod) { int modulo(const int number, const int mod) {
int result = number % mod; int result = number % mod;
if (result < 0) { if (result < 0) {

View File

@@ -16,6 +16,7 @@ typedef struct {
int y; int y;
} coordinate; } coordinate;
// Start at 1 since the color 0 is reserved for no_color.
typedef enum { typedef enum {
BLACK = 1, BLACK = 1,
WHITE = 2, WHITE = 2,
@@ -32,6 +33,19 @@ typedef struct {
rooster *game_map; rooster *game_map;
} game_maps; } game_maps;
/*
* Checks if 2 coordinates are the same.
*
* Input:
* a: coordinate 1
* b: coordinate 2
*
* Returns:
* If they point to the same location: 1
* Otherwise: 0
*/
int same_coordinate(coordinate a, coordinate b);
/* /*
* A proper modulo function. * A proper modulo function.
* The one provided by c: '%' doesn't work according to the mathematical definition. * The one provided by c: '%' doesn't work according to the mathematical definition.

View File

@@ -21,7 +21,7 @@ static rooster *get_maze(void) {
FILE *fh = fopen("assets/maze.txt", "r"); FILE *fh = fopen("assets/maze.txt", "r");
if (fh == NULL) { if (fh == NULL) {
perror("loading maze"); perror("loading maze");
exit(EXIT_FAILURE); return NULL;
} }
rooster *rp = grid_from_file(fh); rooster *rp = grid_from_file(fh);
fclose(fh); fclose(fh);
@@ -29,7 +29,7 @@ static rooster *get_maze(void) {
// 3. Bepaal of het lezen van het rooster is gelukt. // 3. Bepaal of het lezen van het rooster is gelukt.
if (rp == NULL) { if (rp == NULL) {
fprintf(stderr, "Kan rooster niet maken.\n"); fprintf(stderr, "Kan rooster niet maken.\n");
exit(EXIT_FAILURE); return NULL;
} }
return rp; return rp;
@@ -53,7 +53,8 @@ static void maze_runner_beweeg(rooster *rp, int dx, int dy) {
if (playerx == -1 || playery == -1) { if (playerx == -1 || playery == -1) {
printf("Player not found!"); printf("Player not found!");
exit(1); rooster_zet_toestand(rp, STATE_BEGIN);
return;
} }
if (rooster_bevat(rp, playerx + dx, playery + dy) == 1) { if (rooster_bevat(rp, playerx + dx, playery + dy) == 1) {
@@ -113,11 +114,16 @@ static void speel_maze(rooster *rp) {
void maze_runner(void) { void maze_runner(void) {
// Voorbereiding. // Voorbereiding.
rooster *rp = get_maze(); rooster *rp = get_maze();
if (rp == NULL) {
return;
}
show_grid(rp); show_grid(rp);
rooster_zet_toestand(rp, STATE_AAN_HET_SPELEN); rooster_zet_toestand(rp, STATE_AAN_HET_SPELEN);
// Game zelf. // Game zelf.
speel_maze(rp); while (rooster_vraag_toestand(rp) == STATE_AAN_HET_SPELEN) {
speel_maze(rp);
}
// Exit game. // Exit game.
game_exit_screen(rp); game_exit_screen(rp);

View File

@@ -26,7 +26,7 @@ static int OFFSET_X = 5;
* menu: A pointer to the menu grid. * menu: A pointer to the menu grid.
* game: The game you want to launch. * game: The game you want to launch.
*/ */
static void launch_game(rooster *menu, const game game) { static void launch_game(const game game) {
switch (game) { switch (game) {
case GAME_MAZE_RUNNER: case GAME_MAZE_RUNNER:
maze_runner(); maze_runner();
@@ -131,12 +131,13 @@ static int navigate_menu(rooster *menu) {
menu_move(1); menu_move(1);
break; break;
case KEY_ENTER: case KEY_ENTER:
case '\n':
case 'f': case 'f':
case 'F': case 'F':
if (SELECTED_GAME == GAME_QUIT) { if (SELECTED_GAME == GAME_QUIT) {
return 1; return 1;
} }
launch_game(menu, SELECTED_GAME); launch_game(SELECTED_GAME);
break; break;
case KEY_BACKSPACE: case KEY_BACKSPACE:
return 1; return 1;

206
snake.c
View File

@@ -6,8 +6,9 @@
#include <ncurses.h> #include <ncurses.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include <time.h> #include <time.h>
#include <pthread.h>
#include <unistd.h>
#include "grid_game_engine.h" #include "grid_game_engine.h"
@@ -38,6 +39,8 @@ static coordinate SNAKE_TAIL;
static coordinate MESSAGE_LOCATION = {0,0}; static coordinate MESSAGE_LOCATION = {0,0};
static int MAP_HEIGHT = 20; static int MAP_HEIGHT = 20;
static int MAP_WIDTH = 20; static int MAP_WIDTH = 20;
static rooster *GRID;
static pthread_mutex_t MUTEX;
/* /*
* Create a snake body part. * Create a snake body part.
@@ -75,11 +78,11 @@ static direction get_body_part_direction(const char body_part) {
* Returns: * Returns:
* A pointer to the grid. * A pointer to the grid.
*/ */
static rooster *create_grid(void) { static void create_grid(void) {
const int grid_size = (MAP_WIDTH + 1) * MAP_HEIGHT + 1; const int grid_size = (MAP_WIDTH + 1) * MAP_HEIGHT + 1;
char *map = malloc(grid_size * sizeof(char)); char *map = malloc(grid_size * sizeof(char));
if (map == NULL) { if (map == NULL) {
return NULL; return;
} }
for (int i = 1; i <= (MAP_WIDTH + 1) * MAP_HEIGHT; i++) { for (int i = 1; i <= (MAP_WIDTH + 1) * MAP_HEIGHT; i++) {
@@ -105,17 +108,16 @@ static rooster *create_grid(void) {
map[grid_size - 1] = '\0'; map[grid_size - 1] = '\0';
rooster *grid = grid_from_string(map); GRID = grid_from_string(map);
free(map); free(map);
rooster_plaats(grid, rooster_breedte(grid) / 2, rooster_hoogte(grid) / 2, get_body_part(CURRENT_DIRECTION)); rooster_plaats(GRID, rooster_breedte(GRID) / 2, rooster_hoogte(GRID) / 2, get_body_part(CURRENT_DIRECTION));
return grid;
} }
/* /*
* *
*/ */
static void generate_food(rooster *gp) { static void generate_food() {
coordinate empty_spots[MAP_HEIGHT * MAP_WIDTH]; coordinate empty_spots[MAP_HEIGHT * MAP_WIDTH];
// Usable as index when initialized like this. // Usable as index when initialized like this.
@@ -123,7 +125,7 @@ static void generate_food(rooster *gp) {
for (int x = 0; x < MAP_WIDTH; x++) { for (int x = 0; x < MAP_WIDTH; x++) {
for (int y = 0; y < MAP_HEIGHT; y++) { for (int y = 0; y < MAP_HEIGHT; y++) {
if (rooster_kijk(gp, x, y) == CELL_EMPTY) { if (rooster_kijk(GRID, x, y) == CELL_EMPTY) {
coordinate new_spot = {x, y}; coordinate new_spot = {x, y};
empty_spots[available_spots] = new_spot; empty_spots[available_spots] = new_spot;
available_spots++; available_spots++;
@@ -135,7 +137,7 @@ static void generate_food(rooster *gp) {
const coordinate food_location = empty_spots[modulo(rand(), available_spots)]; const coordinate food_location = empty_spots[modulo(rand(), available_spots)];
rooster_plaats(gp, food_location.x, food_location.y, CELL_FOOD); rooster_plaats(GRID, food_location.x, food_location.y, CELL_FOOD);
} }
/* /*
@@ -147,30 +149,33 @@ static void generate_food(rooster *gp) {
* Side Effects: * Side Effects:
* Seed random with the current time. * Seed random with the current time.
*/ */
static rooster *initialize(void) { static void initialize(void) {
srand(time(NULL)); srand(time(NULL));
rooster *grid = create_grid(); create_grid();
if (grid == NULL) { if (GRID == NULL) {
return NULL; return;
} }
CURRENT_DIRECTION = DIRECTION_DOWN;
PREVIOUS_DIRECTION = DIRECTION_DOWN;
// Set snake head and snake tail. // Set snake head and snake tail.
rooster_zoek(grid, get_body_part(CURRENT_DIRECTION), &SNAKE_HEAD.x, &SNAKE_HEAD.y); rooster_zoek(GRID, get_body_part(CURRENT_DIRECTION), &SNAKE_HEAD.x, &SNAKE_HEAD.y);
SNAKE_TAIL.x = SNAKE_HEAD.x; SNAKE_TAIL.x = SNAKE_HEAD.x;
SNAKE_TAIL.y = SNAKE_HEAD.y; SNAKE_TAIL.y = SNAKE_HEAD.y;
generate_food(grid); generate_food();
if (SNAKE_HEAD.x == -1) { if (SNAKE_HEAD.x == -1) {
return NULL; free(GRID);
GRID = NULL;
return;
} }
MESSAGE_LOCATION.y = rooster_hoogte(grid) + OFFSET_Y + 5; MESSAGE_LOCATION.y = rooster_hoogte(GRID) + OFFSET_Y + 5;
MESSAGE_LOCATION.x = OFFSET_X - 5 >= 0 ? OFFSET_X - 5 : 0; MESSAGE_LOCATION.x = OFFSET_X - 5 >= 0 ? OFFSET_X - 5 : 0;
return grid;
} }
/* /*
@@ -196,16 +201,16 @@ static snake_action collision_check(char c) {
return SNAKE_DIE; return SNAKE_DIE;
} }
static void update_snake(rooster *gp, coordinate new_head, snake_action action) { static void update_snake(coordinate new_head, snake_action action) {
if (action == SNAKE_DIE) { if (action == SNAKE_DIE) {
rooster_zet_toestand(gp, STATE_VERLOREN); rooster_zet_toestand(GRID, STATE_VERLOREN);
return; return;
} }
if (action == SNAKE_MOVE) { if (action == SNAKE_MOVE) {
coordinate new_tail = SNAKE_TAIL; coordinate new_tail = SNAKE_TAIL;
switch (get_body_part_direction(rooster_kijk(gp, SNAKE_TAIL.x, SNAKE_TAIL.y))) { switch (get_body_part_direction(rooster_kijk(GRID, SNAKE_TAIL.x, SNAKE_TAIL.y))) {
case DIRECTION_UP: case DIRECTION_UP:
new_tail.y--; new_tail.y--;
break; break;
@@ -220,105 +225,128 @@ static void update_snake(rooster *gp, coordinate new_head, snake_action action)
break; break;
} }
rooster_plaats(gp, SNAKE_TAIL.x, SNAKE_TAIL.y, CELL_EMPTY); rooster_plaats(GRID, SNAKE_TAIL.x, SNAKE_TAIL.y, CELL_EMPTY);
SNAKE_TAIL = new_tail; SNAKE_TAIL = new_tail;
} }
// New head placed after tail moves. It can occupy the empty space created by the tail moving. // 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)); rooster_plaats(GRID, new_head.x, new_head.y, get_body_part(CURRENT_DIRECTION));
SNAKE_HEAD = new_head; SNAKE_HEAD = new_head;
PREVIOUS_DIRECTION = CURRENT_DIRECTION; PREVIOUS_DIRECTION = CURRENT_DIRECTION;
if (action == SNAKE_EAT) { if (action == SNAKE_EAT) {
generate_food(gp); generate_food();
} }
} }
static void snake_move(rooster *gp) { static void *snake_move(void *arg) {
coordinate new_head = SNAKE_HEAD; while (rooster_vraag_toestand(GRID) == STATE_AAN_HET_SPELEN) {
switch (CURRENT_DIRECTION) { usleep(250000);
case DIRECTION_UP: pthread_mutex_lock(&MUTEX);
new_head.y--; coordinate new_head = SNAKE_HEAD;
break; switch (CURRENT_DIRECTION) {
case DIRECTION_RIGHT: case DIRECTION_UP:
new_head.x++; new_head.y--;
break; break;
case DIRECTION_DOWN: case DIRECTION_RIGHT:
new_head.y++; new_head.x++;
break; break;
case DIRECTION_LEFT: case DIRECTION_DOWN:
new_head.x--; new_head.y++;
break; 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;
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) { static void turn_snake(direction dir) {
if ((direction)modulo(dir + 2, DIRECTION_COUNT) == PREVIOUS_DIRECTION) { if ((direction)modulo(dir + 2, DIRECTION_COUNT) == PREVIOUS_DIRECTION
&& !same_coordinate(SNAKE_HEAD, SNAKE_TAIL)) {
return; return;
} }
rooster_plaats(gp, SNAKE_HEAD.x, SNAKE_HEAD.y, get_body_part(dir)); rooster_plaats(GRID, SNAKE_HEAD.x, SNAKE_HEAD.y, get_body_part(dir));
CURRENT_DIRECTION = dir; CURRENT_DIRECTION = dir;
show_grid_on_offset(gp, OFFSET_X, OFFSET_Y); show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y);
} }
static void play_snake(rooster *gp) { static void *play_snake(void *arg) {
timeout(500); while (rooster_vraag_toestand(GRID) == STATE_AAN_HET_SPELEN) {
timeout(1);
switch (getch()) { int c = getch();
case KEY_UP: // fallthrough pthread_mutex_lock(&MUTEX);
case 'w': switch (c) {
case 'W': case KEY_UP: // fallthrough
turn_snake(gp, DIRECTION_UP); case 'w':
break; case 'W':
case KEY_DOWN: // fallthrough turn_snake(DIRECTION_UP);
case 's': break;
case 'S': case KEY_DOWN: // fallthrough
turn_snake(gp, DIRECTION_DOWN); case 's':
break; case 'S':
case KEY_LEFT: // fallthrough turn_snake(DIRECTION_DOWN);
case 'a': break;
case 'A': case KEY_LEFT: // fallthrough
turn_snake(gp, DIRECTION_LEFT); case 'a':
break; case 'A':
case KEY_RIGHT: // fallthrough turn_snake(DIRECTION_LEFT);
case 'd': break;
case 'D': case KEY_RIGHT: // fallthrough
turn_snake(gp, DIRECTION_RIGHT); case 'd':
break; case 'D':
case KEY_BACKSPACE: turn_snake(DIRECTION_RIGHT);
rooster_zet_toestand(gp, STATE_QUIT); break;
break; case KEY_BACKSPACE:
case ERR: rooster_zet_toestand(GRID, STATE_QUIT);
snake_move(gp); break;
}
pthread_mutex_unlock(&MUTEX);
} }
return NULL;
} }
//todo: ?? Win condition?
void snake(void) { void snake(void) {
rooster* const gp = initialize(); initialize();
if (gp == NULL) { if (GRID == NULL) {
return; return;
} }
// Created necessary threads.
pthread_mutex_init(&MUTEX, NULL);
pthread_t user_input;
pthread_t game_tick;
// Show game.
erase(); erase();
show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y);
mvaddstr(MESSAGE_LOCATION.y, MESSAGE_LOCATION.x, "Press SPACEBAR to start playing.");
//todo: ?? Win condition? rooster_zet_toestand(GRID, STATE_AAN_HET_SPELEN);
show_grid_on_offset(gp, OFFSET_X, OFFSET_Y); // Allow turning before you let the snake move.
pthread_create(&user_input, NULL, play_snake, NULL);
mvprintw(MESSAGE_LOCATION.y, MESSAGE_LOCATION.x, "Press SPACEBAR to start playing.");
while (getch() != ' ') {} while (getch() != ' ') {}
rooster_zet_toestand(gp, STATE_AAN_HET_SPELEN); // Cleanup the start playing message.
while (rooster_vraag_toestand(gp) == STATE_AAN_HET_SPELEN) { mvaddstr(MESSAGE_LOCATION.y, MESSAGE_LOCATION.x, " ");
play_snake(gp);
}
// game_exit_screen(gp); pthread_create(&game_tick, NULL, snake_move, NULL);
rooster_klaar(gp); // Cleanup game thread logic.
pthread_join(game_tick, NULL);
pthread_join(user_input, NULL);
pthread_mutex_destroy(&MUTEX);
rooster_klaar(GRID);
graceful_exit(); graceful_exit();
} }

BIN
spel

Binary file not shown.