General refactor and minor improvements

This commit is contained in:
2025-10-18 18:56:28 +02:00
parent 103f85d07e
commit 6b0e858064
13 changed files with 616 additions and 466 deletions

View File

@@ -1,5 +1,5 @@
CC = gcc CC = gcc
CFLAGS = -std=c11 -Wextra -Wpedantic -g3 -fsanitize=address CFLAGS = -std=c11 -Wextra -Wpedantic -pthread -g3 -fsanitize=address
LDFLAGS = -lncurses -fsanitize=address LDFLAGS = -lncurses -fsanitize=address
SRC = $(filter-out voorbeeld.c,$(wildcard *.c)) SRC = $(filter-out voorbeeld.c,$(wildcard *.c))
HEADERS = $(wildcard *.h) HEADERS = $(wildcard *.h)
@@ -17,7 +17,7 @@ voorbeeld:
tarball1: deel1.tar.gz tarball1: deel1.tar.gz
tarball2: deel2.tar.gz tarball2: deel2.tar.gz
deel1.tar.gz: spel.c rooster.h rooster.c Makefile deel1.tar.gz: spel.c grid.h grid.c Makefile
tar czf $@ $^ tar czf $@ $^
deel2.tar.gz: $(SRC) $(HEADERS) Makefile deel2.tar.gz: $(SRC) $(HEADERS) Makefile

View File

@@ -2,17 +2,40 @@
| | | |
| !!! HOWTO MINIGAME !!! | | !!! HOWTO MINIGAME !!! |
| | | |
| Make sure that your terminal |
| has enough space to display |
| all the output of the game. |
| |
| This can be achieved by |
| going fullscreen and/or |
| zooming out. |
| |
| *________________* | | *________________* |
| |--- MANUAL ---| | | | GENERAL | |
| *------------------------------------* | | *------------------------------------* |
| | | | | | | |
| | Exit: | | | | Exit current screen: | |
| | Escape, backspace | | | | Escape, backspace | |
| | | | | | | |
| | Reset zoom: | |
| | "'ctrl' + '0'" | |
| | | |
| | Zoom out: | |
| | "'ctrl' + '-'" | |
| | | |
| | Zoom in: | |
| | "'ctrl' + '+'" | |
| | "'ctrl' + 'shift' + '='" | |
| | | |
| *------------------------------------* | | *------------------------------------* |
| | | |
| "'ctrl' + '-'" |
| |
| You can reset the zoom with: |
| "'ctrl' + '0'" |
| |
| *________________* | | *________________* |
| |---- MENU ----| | | | MENU | |
| *------------------------------------* | | *------------------------------------* |
| | | | | | | |
| | Move down: | | | | Move down: | |
@@ -24,13 +47,10 @@
| | Select: | | | | Select: | |
| | 'f', enter, space bar | | | | 'f', enter, space bar | |
| | | | | | | |
| | Exit: | |
| | Escape, backspace | |
| | | |
| *------------------------------------* | | *------------------------------------* |
| | | |
| *-----------------* | | *-----------------* |
| |- MAZE RUNNER -| | | | MAZE RUNNER | |
| *------------------------------------* | | *------------------------------------* |
| | | | | | | |
| | Move up: | | | | Move up: | |
@@ -45,13 +65,10 @@
| | Move left: | | | | Move left: | |
| | 'a', arrow_left | | | | 'a', arrow_left | |
| | | | | | | |
| | Exit: | |
| | Escape, backspace | |
| | | |
| *------------------------------------* | | *------------------------------------* |
| | | |
| *-----------------* | | *-----------------* |
| |---- SNAKE ----| | | | SNAKE | |
| *------------------------------------* | | *------------------------------------* |
| | | | | | | |
| | Turn up: | | | | Turn up: | |
@@ -66,10 +83,5 @@
| | Turn left: | | | | Turn left: | |
| | 'a', arrow_left | | | | 'a', arrow_left | |
| | | | | | | |
| | Exit: | |
| | Escape, backspace | |
| | | |
| *------------------------------------* | | *------------------------------------* |
*--------------------------------------------* *--------------------------------------------*
1234567890123456789012345678901234567890123456
10 20 30 40 |

View File

@@ -1,20 +1,24 @@
#include "rooster.h" #include "grid.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
/* /*
* The rooster type this program is build around. * The grid type this program is build around.
*/ */
typedef struct rooster_data { typedef struct grid_data {
char *rost; char *rost;
int height; int height;
int width; int width;
toestand state; state state;
} rooster; } grid;
rooster *grid_from_string(const char* input) { static int internal_location(const grid *gp, const int x, const int y) {
return y * (gp->width + 1) + x;
}
grid *grid_create_from_string(const char* input) {
int width = 0; int width = 0;
int height = 0; int height = 0;
const size_t len = strlen(input) / sizeof(char); const size_t len = strlen(input) / sizeof(char);
@@ -38,20 +42,20 @@ rooster *grid_from_string(const char* input) {
const int grid_size = (width + 1) * height + 1; const int grid_size = (width + 1) * height + 1;
rooster *rp = malloc(sizeof(rooster)); grid *gp = malloc(sizeof(grid));
if (rp == NULL) { if (gp == NULL) {
return NULL; return NULL;
} }
rp->rost = malloc(grid_size * sizeof(char)); gp->rost = malloc(grid_size * sizeof(char));
rp->height = height; gp->height = height;
rp->width = width; gp->width = width;
rp->state = STATE_BEGIN; gp->state = STATE_BEGIN;
strcpy(rp->rost, input); strcpy(gp->rost, input);
return rp; return gp;
} }
/* /*
@@ -59,16 +63,16 @@ rooster *grid_from_string(const char* input) {
* *
* Input: * Input:
* fh: the stream to read the grid from. * fh: the stream to read the grid from.
* rost: a rooster file to store the width and height in. * rost: a grid file to store the width and height in.
* *
* Side effects: * Side effects:
* the rooster gets its width and height set * the grid gets its width and height set
* *
* Output: * Output:
* 1 if the file width and height seem to match with its size * 1 if the file width and height seem to match with its size
* 0 otherwise * 0 otherwise
*/ */
static int get_grid_sizes(FILE *fh, rooster *rost) { static int get_grid_sizes(FILE *fh, grid *rost) {
while (getc(fh) != '\n') { while (getc(fh) != '\n') {
if (feof(fh)) { if (feof(fh)) {
return 0; return 0;
@@ -94,19 +98,19 @@ static int get_grid_sizes(FILE *fh, rooster *rost) {
return 1; return 1;
} }
rooster *grid_from_file(FILE *fh) { grid *grid_create_from_file(FILE *fh) {
if (fh == NULL) { if (fh == NULL) {
return NULL; return NULL;
} }
rooster rost = { grid rost = {
NULL, NULL,
0, 0,
0, 0,
STATE_BEGIN STATE_BEGIN
}; };
// Sets the width and height of the rooster. // Sets the width and height of the grid.
if (get_grid_sizes(fh, &rost) != 1) { if (get_grid_sizes(fh, &rost) != 1) {
// Unlogical file structure. // Unlogical file structure.
return NULL; return NULL;
@@ -147,76 +151,72 @@ rooster *grid_from_file(FILE *fh) {
free(line); free(line);
rooster *return_rooster = malloc(sizeof(rost)); grid *return_grid = malloc(sizeof(rost));
if (return_rooster == NULL) { if (return_grid == NULL) {
return NULL; return NULL;
} }
memcpy(return_rooster, &rost, sizeof(rost)); memcpy(return_grid, &rost, sizeof(rost));
return return_rooster; return return_grid;
} }
toestand rooster_vraag_toestand(const rooster *rp) { state grid_fetch_state(const grid *gp) {
if (rp != NULL) { if (gp != NULL) {
return rp->state; return gp->state;
} }
return STATE_VERLOREN; return STATE_VERLOREN;
} }
void rooster_zet_toestand(rooster *rp, toestand t) { void grid_put_state(grid *gp, const state t) {
if (rp != NULL) { if (gp != NULL) {
switch (t) { switch (t) {
case STATE_BEGIN: case STATE_BEGIN:
rp->state = STATE_BEGIN; gp->state = STATE_BEGIN;
break; break;
case STATE_AAN_HET_SPELEN: case STATE_AAN_HET_SPELEN:
rp->state = STATE_AAN_HET_SPELEN; gp->state = STATE_AAN_HET_SPELEN;
break; break;
case STATE_GEWONNEN: case STATE_GEWONNEN:
rp->state = STATE_GEWONNEN; gp->state = STATE_GEWONNEN;
break; break;
case STATE_VERLOREN: case STATE_VERLOREN:
rp->state = STATE_VERLOREN; gp->state = STATE_VERLOREN;
break; break;
case STATE_QUIT: case STATE_QUIT:
rp->state = STATE_QUIT; gp->state = STATE_QUIT;
break; break;
} }
} }
} }
void rooster_klaar(rooster *rp) { void grid_cleanup(grid *gp) {
if (rp != NULL) { if (gp != NULL) {
if (rp->rost != NULL) if (gp->rost != NULL)
{ {
free(rp->rost); free(gp->rost);
} }
free(rp); free(gp);
} }
} }
int rooster_breedte(const rooster *rp) { int grid_width(const grid *gp) {
if (rp == NULL) { if (gp == NULL) {
return 0; return 0;
} }
return rp->width; return gp->width;
} }
int rooster_hoogte(const rooster *rp) { int grid_height(const grid *gp) {
if (rp == NULL) { if (gp == NULL) {
return 0; return 0;
} }
return rp->height; return gp->height;
} }
static int internal_location(const rooster *rp, const int x, const int y) { int grid_contains(const grid *gp, const int x, const int y) {
return y * (rp->width + 1) + x; if (gp != NULL && gp->rost != NULL) {
} if (x >= 0 && y >= 0 && x < gp->width && y < gp->height)
int rooster_bevat(const rooster *rp, int x, int y) {
if (rp != NULL && rp->rost != NULL) {
if (x >= 0 && y >= 0 && x < rp->width && y < rp->height)
{ {
return 1; return 1;
} }
@@ -224,58 +224,58 @@ int rooster_bevat(const rooster *rp, int x, int y) {
return 0; return 0;
} }
char rooster_kijk(const rooster *rp, int x, int y) { char grid_fetch(const grid *gp, const int x, const int y) {
if (rp != NULL && rp->rost != NULL && rooster_bevat(rp, x, y) == 1) { if (gp != NULL && gp->rost != NULL && grid_contains(gp, x, y) == 1) {
return rp->rost[internal_location(rp, x, y)]; return gp->rost[internal_location(gp, x, y)];
} }
return '\0'; return '\0';
} }
int rooster_plaats(rooster *rp, int x, int y, char c) { int grid_put(grid *gp, const int x, const int y, const char c) {
if (rp != NULL && rp->rost != NULL && rooster_bevat(rp, x, y) == 1) { if (gp != NULL && gp->rost != NULL && grid_contains(gp, x, y) == 1) {
rp->rost[internal_location(rp, x, y)] = c; gp->rost[internal_location(gp, x, y)] = c;
return 1; return 1;
} }
return 0; return 0;
} }
char *rooster_vraag_rij(const rooster *rp, int y) { char *grid_fetch_row(const grid *gp, const int y) {
if (rp != NULL && rp->rost != NULL && rooster_bevat(rp, 0, y) == 1) { if (gp != NULL && gp->rost != NULL && grid_contains(gp, 0, y) == 1) {
// we're going to remove the newline so this is long enough // we're going to remove the newline so this is long enough
char *row = malloc((rp->width + 1) * sizeof(char)); char *row = malloc((gp->width + 1) * sizeof(char));
memcpy(row, &rp->rost[internal_location(rp, 0, y)], rp->width * sizeof(char)); memcpy(row, &gp->rost[internal_location(gp, 0, y)], gp->width * sizeof(char));
row[rp->width] = '\0'; row[gp->width] = '\0';
return row; return row;
} }
return NULL; return NULL;
} }
rooster *rooster_kopieer(const rooster *rp) { grid *grid_copy(const grid *gp) {
if (rp != NULL && rp->rost != NULL) { if (gp != NULL && gp->rost != NULL) {
const size_t grid_memory = ((rp->width + 1) * rp->height + 1) * sizeof(char); const size_t grid_memory = ((gp->width + 1) * gp->height + 1) * sizeof(char);
char *grid = malloc(grid_memory); char *rost = malloc(grid_memory);
if (grid == NULL) { if (rost == NULL) {
return NULL; return NULL;
} }
rooster *new_rooster = malloc(sizeof(*rp)); grid *new_grid = malloc(sizeof(*gp));
if (new_rooster == NULL) { if (new_grid == NULL) {
return NULL; return NULL;
} }
memcpy(grid, rp->rost, grid_memory); memcpy(rost, gp->rost, grid_memory);
memcpy(new_rooster, rp, sizeof(*rp)); memcpy(new_grid, gp, sizeof(*gp));
new_rooster->rost = grid; new_grid->rost = rost;
return new_rooster; return new_grid;
} }
return NULL; return NULL;
} }
void rooster_zoek(const rooster *rp, char c, int *x, int *y) { void grid_find(const grid *gp, const char c, int *x, int *y) {
if (rp == NULL || rp->rost == NULL) { if (gp == NULL || gp->rost == NULL) {
*x = -1; *x = -1;
*y = -1; *y = -1;
return; return;
@@ -283,15 +283,15 @@ void rooster_zoek(const rooster *rp, char c, int *x, int *y) {
const char search[2] = {c}; const char search[2] = {c};
int strpos = strcspn(rp->rost, search); const int char_index = (int)strcspn(gp->rost, search);
if (rp->rost[strpos] == '\0') if (gp->rost[char_index] == '\0')
{ {
*x = -1; *x = -1;
*y = -1; *y = -1;
return; return;
} }
*x = strpos % (rp->width + 1); *x = char_index % (gp->width + 1);
*y = strpos / (rp->width + 1); *y = char_index / (gp->width + 1);
} }

174
grid.h Normal file
View File

@@ -0,0 +1,174 @@
/*
* grid.h
*
* This module provides a grid api.
*
* A grid is a rectangular grid of objects. Every object in this grid is a char.
*/
#ifndef _GRID_H
#define _GRID_H
#include <stdio.h>
struct grid_data;
typedef struct grid_data grid;
typedef enum {
STATE_BEGIN,
STATE_AAN_HET_SPELEN,
STATE_GEWONNEN,
STATE_VERLOREN,
STATE_QUIT
} state;
/* Maak een rooster op basis van de data in de gegeven stream.
*
* fh: de stream waaruit het doolhof gelezen moet worden.
*
* Uitvoer: als alles goed gaat, een pointer naar een rooster (die op de heap is
* gealloceerd), dat overeenkomt met de gegeven beschrijving.
* De begintoestand is BEGIN.
*
* Als de beschrijving niet consistent is (bijvoorbeeld
* niet alle rijen zijn even lang, of er klopt iets anders niet), of
* als niet voldoende geheugen kan worden gereserveerd, dan wordt
* NULL teruggegeven. (In dat geval blijft geen gereserveerd geheugen
* achter.)
*/
grid *grid_create_from_file(FILE *fh);
/*
* Maak een rooster op basis van een gegeven string.
*
* Uitvoer: als alles goed gaat, een pointer naar een rooster (die op de heap is
* gealloceerd), dat overeenkomt met de gegeven beschrijving.
* De begintoestand is BEGIN.
*
* Als de beschrijving niet consistent is (bijvoorbeeld
* niet alle rijen zijn even lang, of er klopt iets anders niet), of
* als niet voldoende geheugen kan worden gereserveerd, dan wordt
* NULL teruggegeven. (In dat geval blijft geen gereserveerd geheugen
* achter.)
*/
grid *grid_create_from_string(const char* input);
/*
* Haal een rij uit het rooster op.
*
* Input:
* gp: een pointer naar het rooster
* y: de y-coordinaat van de rij die je wilt hebben.
*
* Output:
* Een pointer naar een nieuwe string met daarin alle karakters in die rij.
*/
char *grid_fetch_row(const grid *gp, int y);
/*
* Maak een kopie van een rooster
*
* Input:
* gp: Een pointer naar het rooster.
*
* Output:
* Een pointer naar het kopie.
*/
grid *grid_copy(const grid *gp);
/* Vraag de huidige toestand van het spel op.
*
* Input:
* gp: een pointer naar het rooster.
*
* Uitvoer: de toestand.
*/
state grid_fetch_state(const grid *gp);
/* Verander de huidige toestand van het spel.
*
* gp: een pointer naar het rooster.
* t: de nieuwe toestand.
*/
void grid_put_state(grid *gp, state t);
/* Geef alle resources vrij die zijn gealloceerd voor een rooster.
* De rooster pointer is na aanroep van deze functie niet meer bruikbaar.
*
* gp: een pointer naar het rooster.
*/
void grid_cleanup(grid *gp);
/* Vraag de breedte van het rooster op, dat wil zeggen, het aantal kolommen.
*
* gp: een pointer naar het rooster.
*
* Uitvoer: de breedte.
*/
int grid_width(const grid *gp);
/* Vraag de hoogte van het rooster op, dat wil zeggen, het aantal rijen.
*
* gp: een pointer naar het rooster.
*
* Uitvoer: de hoogte.
*/
int grid_height(const grid *gp);
/* Kijk of de gegeven positie binnen het rooster valt.
*
* gp: een pointer naar het rooster.
* x,y: de positie.
*
* Uitvoer: 1 als de positie binnen het rooster valt, anders 0.
*/
int grid_contains(const grid *gp, int x, int y);
/* Kijk welk object er staat op een bepaalde positie in het rooster.
*
* gp : een pointer naar het rooster
* x,y: de betreffende positie.
*
* Uitvoer: het object op die positie, of '\0' als de positie buiten het
* rooster valt.
*/
char grid_fetch(const grid *gp, int x, int y);
/* Schrijf een bepaald object op een bepaalde positie in het rooster.
*
* gp : een pointer naar het rooster
* x,y: de betreffende positie.
* c : het object.
*
* Effect: als (x,y) binnen het rooster ligt, wordt het object op
* de opgegeven positie geplaatst, anders verandert er niets.
*
* Uitvoer: 1 als het object is geplaatst, of 0 als het buiten de grenzen lag.
*/
int grid_put(grid *gp, int x, int y, char c);
/* Zoek een bepaald object in het rooster, en geef de coordinaten van het
* object terug via de gegeven pointers. Let op: als er meerdere objecten van
* het gezochte soort in het rooster voorkomen, is niet gedefinieerd van welke
* de positie wordt gevonden.
*
* gp : een pointer naar het rooster
* c : het object dat moet worden gezocht
* x,y: pointers naar de geheugenlocaties waar de gevonden positie moet worden
* geschreven.
*
* Uitvoer: via de pointers x en y. Als het object niet wordt gevonden worden
* *x en *y op -1 gezet.
*/
void grid_find(const grid *gp, char c, int *x, int *y);
#endif //_GRID_H

View File

@@ -10,8 +10,6 @@
#include <ncurses.h> #include <ncurses.h>
#include <stdlib.h> #include <stdlib.h>
#include "rooster.h"
int same_coordinate(const coordinate a, const coordinate b) { int same_coordinate(const coordinate a, const coordinate b) {
return a.x == b.x && a.y == b.y; return a.x == b.x && a.y == b.y;
} }
@@ -24,23 +22,23 @@ int modulo(const int number, const int mod) {
return result; return result;
} }
void show_grid_on_offset(const rooster *gp, const int starting_x, const int starting_y) { void show_grid_on_offset(const grid *gp, const int starting_x, const int starting_y) {
const int height = rooster_hoogte(gp); const int height = grid_height(gp);
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
char *rij = rooster_vraag_rij(gp, y); char *rij = grid_fetch_row(gp, y);
mvprintw(starting_y + y, starting_x, "%s", rij); mvprintw(starting_y + y, starting_x, "%s", rij);
free(rij); free(rij);
} }
refresh(); refresh();
} }
void show_grid(const rooster *gp) { void show_grid(const grid *gp) {
show_grid_on_offset(gp, 0, 0); show_grid_on_offset(gp, 0, 0);
} }
void update_grid(rooster *gp, const char c, const int x, const int y) { void update_grid(grid *gp, const char c, const int x, const int y) {
if (rooster_plaats(gp, x, y, c) == 1) { if (grid_put(gp, x, y, c) == 1) {
mvaddch(y, x, c); mvaddch(y, x, c);
} }
} }
@@ -52,9 +50,7 @@ void update_grid(rooster *gp, const char c, const int x, const int y) {
* Clears the console and prints the victory message. * Clears the console and prints the victory message.
*/ */
static void display_victory(const coordinate location) { static void display_victory(const coordinate location) {
erase(); mvaddstr(location.y, location.x, "YOU WON!!!!!");
mvprintw(location.y, location.x, "YOU WON!!!!!");
refresh();
} }
/* /*
@@ -64,9 +60,7 @@ static void display_victory(const coordinate location) {
* Clears the console and prints the GAME OVER message. * Clears the console and prints the GAME OVER message.
*/ */
static void display_loss(const coordinate location) { static void display_loss(const coordinate location) {
erase(); mvaddstr(location.y, location.x, "GAME OVER");
mvprintw(location.y, location.x, "GAME OVER");
refresh();
} }
/* /*
@@ -76,9 +70,7 @@ static void display_loss(const coordinate location) {
* Clears the console and prints the quit message. * Clears the console and prints the quit message.
*/ */
static void display_quit(const coordinate location) { static void display_quit(const coordinate location) {
erase(); mvaddstr(location.y, location.x, "You quit the game");
mvprintw(location.y, location.x, "You quit the game");
refresh();
} }
/* /*
@@ -88,15 +80,15 @@ static void display_quit(const coordinate location) {
* Clears the console and prints the hacker man message. * Clears the console and prints the hacker man message.
*/ */
static void display_hackerman(const coordinate location) { static void display_hackerman(const coordinate location) {
erase(); mvaddstr(location.y, location.x, "The hacker man strikes again...");
mvprintw(location.y, location.x, "The hacker man strikes again...");
refresh();
} }
void graceful_exit(const coordinate message_location) { void graceful_exit(const coordinate message_location) {
mvprintw(message_location.y, message_location.x, "Press ENTER or SPACE to exit."); mvaddstr(message_location.y, message_location.x, "Press ENTER or SPACE to exit.");
while (1) { while (1) {
switch (getch()) { switch (getch()) {
case KEY_BACKSPACE:
case KEY_ESCAPE:
case KEY_ENTER: case KEY_ENTER:
case '\n': case '\n':
case ' ': case ' ':
@@ -105,8 +97,8 @@ void graceful_exit(const coordinate message_location) {
} }
} }
void game_exit_screen(const rooster *gp, coordinate location) { void game_exit_message(const grid *gp, coordinate location) {
const toestand current_state = rooster_vraag_toestand(gp); const state current_state = grid_fetch_state(gp);
switch (current_state) { switch (current_state) {
case STATE_GEWONNEN: case STATE_GEWONNEN:
display_victory(location); display_victory(location);
@@ -123,9 +115,24 @@ void game_exit_screen(const rooster *gp, coordinate location) {
location.y += 2; location.y += 2;
graceful_exit(location); graceful_exit(location);
refresh();
} }
static void init_ncurses() { void enable_highlight(const game_colors color) {
attron(COLOR_PAIR(color));
}
void disable_highlight(const game_colors color) {
attroff(COLOR_PAIR(color));
}
/*
* Initialize ncurses.
*
* This enables cbreak, noecho, hides the cursor and enables the extra keys.
* This also creates the color pairs needed for game_colors and sets ESCDELAY to 0.
*/
static void init_ncurses(void) {
ESCDELAY = 0; ESCDELAY = 0;
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
initscr(); initscr();
@@ -134,9 +141,6 @@ static void init_ncurses() {
noecho(); // Don't write the keyboard input to the console. noecho(); // Don't write the keyboard input to the console.
curs_set(0); // Hides the cursor. curs_set(0); // Hides the cursor.
// mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL); // Don't mask any mouse events
// printf("\033[?1003h\n"); // Makes the terminal report mouse movement events
start_color(); start_color();
init_pair(BLACK, COLOR_BLACK, COLOR_BLACK); init_pair(BLACK, COLOR_BLACK, COLOR_BLACK);
init_pair(WHITE, COLOR_BLACK, COLOR_WHITE); init_pair(WHITE, COLOR_BLACK, COLOR_WHITE);
@@ -150,14 +154,10 @@ static void init_ncurses() {
clear(); clear();
} }
static void cleanup_ncurses() {
endwin();
}
void init_engine(void) { void init_engine(void) {
init_ncurses(); init_ncurses();
} }
void cleanup_engine(void) { void cleanup_engine(void) {
cleanup_ncurses(); endwin();
} }

View File

@@ -9,7 +9,7 @@
#ifndef MINIGAME_MENU_GRID_GAME_ENGINE_H #ifndef MINIGAME_MENU_GRID_GAME_ENGINE_H
#define MINIGAME_MENU_GRID_GAME_ENGINE_H #define MINIGAME_MENU_GRID_GAME_ENGINE_H
#include "rooster.h" #include "grid.h"
#define KEY_ESCAPE 27 #define KEY_ESCAPE 27
@@ -32,7 +32,7 @@ typedef enum {
typedef struct { typedef struct {
char name[100]; char name[100];
rooster *game_map; grid *game_map;
} game_maps; } game_maps;
/* /*
@@ -63,7 +63,7 @@ int modulo(int number, int mod);
* Side effect: * Side effect:
* The console is cleared and the grid is printed. * The console is cleared and the grid is printed.
*/ */
void show_grid_on_offset(const rooster *gp, int starting_x, int starting_y); void show_grid_on_offset(const grid *gp, int starting_x, int starting_y);
/* /*
* Displays the given grid with ncurses. * Displays the given grid with ncurses.
@@ -74,7 +74,37 @@ void show_grid_on_offset(const rooster *gp, int starting_x, int starting_y);
* Side effect: * Side effect:
* The console is cleared and the grid is printed. * The console is cleared and the grid is printed.
*/ */
void show_grid(const rooster *gp); void show_grid(const grid *gp);
/*
* Turn on color highlighting for ncurses
*
* Input:
* color: The color you want the highlight to be.
*
* Side Effects:
* Enables color highlighting in ncurses.
*
* Note:
* This should always be disabled with disable_highlights before being called again.
* Not disabling the current highlight before enabling another one is undefined behaviour.
*/
void enable_highlight(game_colors color);
/*
* Turn off color highlighting for ncurses
*
* Input:
* color: The color highlighting you want to turn off.
*
* Side Effects:
* Enables color highlighting in ncurses.
*
* Note:
* This should always be enabled with enable_highlights before being called.
* Disabling the highlight before enabling it is undefined behaviour.
*/
void disable_highlight(game_colors color);
/* /*
* Updates a single location in the grid. * Updates a single location in the grid.
@@ -88,7 +118,7 @@ void show_grid(const rooster *gp);
* Side effect: * Side effect:
* The update gets applied both on the grid and on the screen. * The update gets applied both on the grid and on the screen.
*/ */
void update_grid(rooster *gp, char c, int x, int y); void update_grid(grid *gp, char c, int x, int y);
/* /*
* Display the ending screen that matches the end state of the grid. * Display the ending screen that matches the end state of the grid.
@@ -100,7 +130,7 @@ void update_grid(rooster *gp, char c, int x, int y);
* Side Effects: * Side Effects:
* The end of game screen gets displayed with a graceful exit. * The end of game screen gets displayed with a graceful exit.
*/ */
void game_exit_screen(const rooster *gp, coordinate location); void game_exit_message(const grid *gp, coordinate location);
/* /*
* Waits for you to press ENTER or SPACE before exiting the game. * Waits for you to press ENTER or SPACE before exiting the game.

View File

@@ -5,8 +5,7 @@
#include "manual.h" #include "manual.h"
#include <ncurses.h> #include <ncurses.h>
#include "grid_game_engine.h"
#include "rooster.h"
void manual(const coordinate display_location) { void manual(const coordinate display_location) {
erase(); erase();
@@ -15,19 +14,22 @@ void manual(const coordinate display_location) {
return; return;
} }
rooster *grid = grid_from_file(fp); grid *grid = grid_create_from_file(fp);
if (grid == NULL) { if (grid == NULL) {
mvaddstr(display_location.y, display_location.x, "Error loading grid"); mvaddstr(display_location.y, display_location.x, "Error loading grid");
return; return;
} }
fclose(fp); fclose(fp);
show_grid_on_offset(grid, display_location.x, display_location.y); show_grid_on_offset(grid, display_location.x, display_location.y);
rooster_klaar(grid); // Wait until ESCAPE or BACKSPACE is pressed.
timeout(200);
int ch = getch(); for (int ch = getch(); ch != KEY_ESCAPE && ch != KEY_BACKSPACE && ch != ' '; ch = getch()) {
while (ch != KEY_ESCAPE && ch != KEY_BACKSPACE && ch != ' ') { // Update the screen in the meantime to accommodate windows resizes.
ch = getch(); show_grid_on_offset(grid, display_location.x, display_location.y);
} }
grid_cleanup(grid);
} }

View File

@@ -8,37 +8,43 @@
#include <stdlib.h> #include <stdlib.h>
#include "grid_game_engine.h" #include "grid_game_engine.h"
#include "rooster.h"
#define EMPTY ' '
#define WALL '#'
#define LIVING_PLAYER '*'
#define DEAD_PLAYER '@'
#define MAZE_EXIT '$'
#define TRAP 'X'
/* /*
* Reads in the maze from the assets file. * Reads in the maze from the assets file.
*
* Side Effects:
* Memory is allocated to store the grid in.
*/ */
static rooster *get_maze(void) { static grid *get_maze(void) {
// TODO: echt opties aanbieden in plaats van hardcoded 1 maze. // Open het doolhof bestand en lees het rooster.
// Alternatief is om random een maze te genereren. dit is miss beter.
// 2. Open het doolhof bestand en lees het rooster.
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");
return NULL; return NULL;
} }
rooster *rp = grid_from_file(fh); grid *gp = grid_create_from_file(fh);
fclose(fh); fclose(fh);
// 3. Bepaal of het lezen van het rooster is gelukt. // Bepaal of het lezen van het rooster is gelukt.
if (rp == NULL) { if (gp == NULL) {
fprintf(stderr, "Kan rooster niet maken.\n"); fprintf(stderr, "Kan rooster niet maken.\n");
return NULL; return NULL;
} }
return rp; return gp;
} }
/* Voert de benodigde veranderingen in het rooster door als de speler in een /* Voert de benodigde veranderingen in het rooster door als de speler in een
* bepaalde richting probeert te bewegen. * bepaalde richting probeert te bewegen.
* Input: * Input:
* rp : een pointer naar het rooster * gp : een pointer naar het rooster
* dx,dy: de richting waarin de speler probeert te bewegen. De vier mogelijk- * dx,dy: de richting waarin de speler probeert te bewegen. De vier mogelijk-
* heden voor (dx,dy) zijn (-1,0), (1,0), (0,-1), (0,1) voor resp. * heden voor (dx,dy) zijn (-1,0), (1,0), (0,-1), (0,1) voor resp.
* links, rechts, omhoog en omlaag. * links, rechts, omhoog en omlaag.
@@ -46,33 +52,35 @@ static rooster *get_maze(void) {
* Side effect: het rooster wordt aangepast op basis van de handeling van * Side effect: het rooster wordt aangepast op basis van de handeling van
* de speler. * de speler.
*/ */
static void maze_runner_beweeg(rooster *rp, int dx, int dy) { static void maze_runner_move(grid *gp, const int dx, const int dy) {
int playerx; coordinate player_position = {0, 0};
int playery; grid_find(gp, LIVING_PLAYER, &player_position.x, &player_position.y);
rooster_zoek(rp, '*', &playerx, &playery);
if (playerx == -1 || playery == -1) { if (player_position.y == -1) {
printf("Player not found!"); printf("Player not found!");
rooster_zet_toestand(rp, STATE_BEGIN); grid_put_state(gp, STATE_BEGIN);
return; return;
} }
if (rooster_bevat(rp, playerx + dx, playery + dy) == 1) { if (grid_contains(gp, player_position.x + dx, player_position.y + dy) == 1) {
char new_location = rooster_kijk(rp, playerx + dx, playery + dy); char new_location = grid_fetch(gp, player_position.x + dx, player_position.y + dy);
switch (new_location) { switch (new_location) {
case '#': case WALL:
break; break;
case 'X': case TRAP:
update_grid(rp, ' ', playerx, playery); grid_put_state(gp, STATE_VERLOREN);
rooster_zet_toestand(rp, STATE_VERLOREN);
enable_highlight(RED);
update_grid(gp, DEAD_PLAYER, player_position.x, player_position.y);
disable_highlight(RED);
break; break;
case ' ': case EMPTY:
update_grid(rp, ' ', playerx, playery); update_grid(gp, EMPTY, player_position.x, player_position.y);
update_grid(rp, '*', playerx + dx, playery + dy); update_grid(gp, LIVING_PLAYER, player_position.x + dx, player_position.y + dy);
break; break;
case '$': case MAZE_EXIT:
update_grid(rp, ' ', playerx, playery); update_grid(gp, EMPTY, player_position.x, player_position.y);
rooster_zet_toestand(rp, STATE_GEWONNEN); grid_put_state(gp, STATE_GEWONNEN);
break; break;
} }
refresh(); refresh();
@@ -82,52 +90,58 @@ static void maze_runner_beweeg(rooster *rp, int dx, int dy) {
/* /*
* Speelt het spel met een gegeven rooster tot de toestand niet langer * Speelt het spel met een gegeven rooster tot de toestand niet langer
* AAN_HET_SPELEN is. * AAN_HET_SPELEN is.
*
* Input:
* gp: Een pointer naar het rooster.
*
* Side effects:
* Het rooster wordt ge-updated afhankelijk van de user input.
*/ */
static void speel_maze(rooster *rp) { static void speel_maze(grid *gp) {
switch (getch()) { switch (getch()) {
case KEY_UP: // fallthrough case KEY_UP: // fallthrough
case 'w': case 'w':
case 'W': case 'W':
maze_runner_beweeg(rp, 0, -1); maze_runner_move(gp, 0, -1);
break; break;
case KEY_DOWN: // fallthrough case KEY_DOWN: // fallthrough
case 's': case 's':
case 'S': case 'S':
maze_runner_beweeg(rp, 0, 1); maze_runner_move(gp, 0, 1);
break; break;
case KEY_LEFT: // fallthrough case KEY_LEFT: // fallthrough
case 'a': case 'a':
case 'A': case 'A':
maze_runner_beweeg(rp, -1, 0); maze_runner_move(gp, -1, 0);
break; break;
case KEY_RIGHT: // fallthrough case KEY_RIGHT: // fallthrough
case 'd': case 'd':
case 'D': case 'D':
maze_runner_beweeg(rp, 1, 0); maze_runner_move(gp, 1, 0);
break; break;
case 'p': case 'p':
case KEY_BACKSPACE: case KEY_BACKSPACE:
case KEY_ESCAPE: case KEY_ESCAPE:
rooster_zet_toestand(rp, STATE_QUIT); grid_put_state(gp, STATE_QUIT);
break; break;
} }
} }
void maze_runner(void) { void maze_runner(void) {
// Voorbereiding. // Voorbereiding.
rooster *rp = get_maze(); grid *gp = get_maze();
if (rp == NULL) { if (gp == NULL) {
return; return;
} }
show_grid(rp); show_grid(gp);
rooster_zet_toestand(rp, STATE_AAN_HET_SPELEN); grid_put_state(gp, STATE_AAN_HET_SPELEN);
// Game zelf. // Game zelf.
while (rooster_vraag_toestand(rp) == STATE_AAN_HET_SPELEN) { while (grid_fetch_state(gp) == STATE_AAN_HET_SPELEN) {
speel_maze(rp); speel_maze(gp);
} }
// Exit game. // Exit game.
game_exit_screen(rp, (coordinate){0, 0}); game_exit_message(gp, (coordinate){0, grid_height(gp) + 2});
rooster_klaar(rp); grid_cleanup(gp);
} }

View File

@@ -11,20 +11,19 @@
#include "manual.h" #include "manual.h"
#include "maze_runner.h" #include "maze_runner.h"
#include "minesweeper.h" #include "minesweeper.h"
#include "rooster.h"
#include "snake.h" #include "snake.h"
#define AMOUNT_OF_MENU_OPTIONS 5 #define AMOUNT_OF_MENU_OPTIONS 5
typedef enum { typedef enum {
GAME_HELP = 0, GAME_MANUAL = 0,
GAME_MAZE_RUNNER = 1, GAME_MAZE_RUNNER = 1,
GAME_SNAKE = 2, GAME_SNAKE = 2,
GAME_MINESWEEPER = 3, GAME_MINESWEEPER = 3,
GAME_QUIT = 4, GAME_QUIT = 4,
} game; } game;
static game SELECTED_GAME = GAME_HELP; static game SELECTED_GAME = GAME_MANUAL;
static int OFFSET_Y = 5; static int OFFSET_Y = 5;
static int OFFSET_X = 5; static int OFFSET_X = 5;
@@ -37,7 +36,7 @@ static int OFFSET_X = 5;
*/ */
static void launch_game(const game game) { static void launch_game(const game game) {
switch (game) { switch (game) {
case GAME_HELP: case GAME_MANUAL:
manual((coordinate){0,0}); manual((coordinate){0,0});
break; break;
case GAME_MAZE_RUNNER: case GAME_MAZE_RUNNER:
@@ -65,16 +64,16 @@ static void launch_game(const game game) {
* If a valid menu option is provided: It will be highlighted in green. * If a valid menu option is provided: It will be highlighted in green.
* If an invalid menu option is provided: Nothing happens * If an invalid menu option is provided: Nothing happens
*/ */
static void menu_highlight(const rooster *menu) { static void menu_highlight(const grid *menu) {
switch (SELECTED_GAME) { switch (SELECTED_GAME) {
case GAME_HELP: case GAME_MANUAL:
case GAME_MAZE_RUNNER: case GAME_MAZE_RUNNER:
case GAME_SNAKE: case GAME_SNAKE:
case GAME_MINESWEEPER: case GAME_MINESWEEPER:
case GAME_QUIT: case GAME_QUIT:
attron(COLOR_PAIR(GREEN)); attron(COLOR_PAIR(GREEN));
char* row = rooster_vraag_rij(menu, SELECTED_GAME); char* row = grid_fetch_row(menu, SELECTED_GAME);
mvprintw(OFFSET_Y + (int)SELECTED_GAME, OFFSET_X, "%s", row); mvprintw(OFFSET_Y + (int)SELECTED_GAME, OFFSET_X, "%s", row);
free(row); free(row);
@@ -94,7 +93,7 @@ static void menu_highlight(const rooster *menu) {
* Side Effects: * Side Effects:
* Displays the menu * Displays the menu
*/ */
static void show_menu(const rooster *menu) { static void show_menu(const grid *menu) {
erase(); erase();
show_grid_on_offset(menu, OFFSET_X, OFFSET_Y); show_grid_on_offset(menu, OFFSET_X, OFFSET_Y);
menu_highlight(menu); menu_highlight(menu);
@@ -167,19 +166,19 @@ static int navigate_menu(void) {
* Output: * Output:
* A pointer to the menu grid. * A pointer to the menu grid.
*/ */
static rooster *initialize_menu(void) { static grid *initialize_menu(void) {
char menu[] = "How to play\n" char menu[] = "How to play\n"
"Maze Runner\n" "Maze Runner\n"
" Snake \n" " Snake \n"
"Minesweeper\n" "Minesweeper\n"
" Leave \n"; " Leave \n";
rooster *rp = grid_from_string(menu); grid *rp = grid_create_from_string(menu);
return rp; return rp;
} }
void minigame_menu(void) { void minigame_menu(void) {
rooster *menu = initialize_menu(); grid *menu = initialize_menu();
launch_game(GAME_MANUAL);
while (true) { while (true) {
show_menu(menu); show_menu(menu);
if (navigate_menu() == 1) { if (navigate_menu() == 1) {
@@ -187,5 +186,5 @@ void minigame_menu(void) {
} }
} }
rooster_klaar(menu); grid_cleanup(menu);
} }

177
rooster.h
View File

@@ -1,177 +0,0 @@
/* rooster.h
Deze module verzorgt het datatype "rooster". Een rooster representeert een
rechthoekig grid van objecten. Elk object is in dit rooster een char.
Deze header file beschrijft het interface voor "rooster".
De implementatie, in "rooster.c", moet je grotendeels zelf schrijven.
*/
#ifndef _ROOSTER_H
#define _ROOSTER_H
#include <stdio.h>
// Dankzij de typedef hoef je niet telkens "struct rooster_data" te schrijven.
// Definieer struct rooster_data in rooster.c.
struct rooster_data;
typedef struct rooster_data rooster;
typedef enum {
STATE_BEGIN,
STATE_AAN_HET_SPELEN,
STATE_GEWONNEN,
STATE_VERLOREN,
STATE_QUIT
} toestand;
/* Maak een rooster op basis van de data in de gegeven stream.
fh: de stream waaruit het doolhof gelezen moet worden.
Uitvoer: als alles goed gaat, een pointer naar een rooster (die op de heap is
gealloceerd), dat overeenkomt met de gegeven beschrijving.
De begintoestand is BEGIN.
Als de beschrijving niet consistent is (bijvoorbeeld
niet alle rijen zijn even lang, of er klopt iets anders niet), of
als niet voldoende geheugen kan worden gereserveerd, dan wordt
NULL teruggegeven. (In dat geval blijft geen gereserveerd geheugen
achter.)
*/
rooster *grid_from_file(FILE *fh);
/*
* Maak een rooster op basis van een gegeven string.
*
* Uitvoer: als alles goed gaat, een pointer naar een rooster (die op de heap is
* gealloceerd), dat overeenkomt met de gegeven beschrijving.
* De begintoestand is BEGIN.
*
* Als de beschrijving niet consistent is (bijvoorbeeld
* niet alle rijen zijn even lang, of er klopt iets anders niet), of
* als niet voldoende geheugen kan worden gereserveerd, dan wordt
* NULL teruggegeven. (In dat geval blijft geen gereserveerd geheugen
* achter.)
*/
rooster *grid_from_string(const char* input);
/*
* Haal een rij uit het rooster op.
*
* Input:
* rp: een pointer naar het rooster
* y: de y-coordinaat van de rij die je wilt hebben.
*
* Output:
* Een pointer naar een nieuwe string met daarin alle karakters in die rij.
*/
char *rooster_vraag_rij(const rooster *rp, int y);
/*
* Maak een kopie van een rooster
*
* Input:
* rp: Een pointer naar het rooster.
*
* Output:
* Een pointer naar het kopie.
*/
rooster *rooster_kopieer(const rooster *rp);
/* Vraag de huidige toestand van het spel op.
Input:
rp: een pointer naar het rooster.
Uitvoer: de toestand.
*/
toestand rooster_vraag_toestand(const rooster *rp);
/* Verander de huidige toestand van het spel.
rp: een pointer naar het rooster.
t: de nieuwe toestand.
*/
void rooster_zet_toestand(rooster *rp, toestand t);
/* Geef alle resources vrij die zijn gealloceerd voor een rooster.
De rooster pointer is na aanroep van deze functie niet meer bruikbaar.
rp: een pointer naar het rooster.
*/
void rooster_klaar(rooster *rp);
/* Vraag de breedte van het rooster op, dat wil zeggen, het aantal kolommen.
rp: een pointer naar het rooster.
Uitvoer: de breedte.
*/
int rooster_breedte(const rooster *rp);
/* Vraag de hoogte van het rooster op, dat wil zeggen, het aantal rijen.
rp: een pointer naar het rooster.
Uitvoer: de hoogte.
*/
int rooster_hoogte(const rooster *rp);
/* Kijk of de gegeven positie binnen het rooster valt.
rp: een pointer naar het rooster.
x,y: de positie.
Uitvoer: 1 als de positie binnen het rooster valt, anders 0.
*/
int rooster_bevat(const rooster *rp, int x, int y);
/* Kijk welk object er staat op een bepaalde positie in het rooster.
rp : een pointer naar het rooster
x,y: de betreffende positie.
Uitvoer: het object op die positie, of '\0' als de positie buiten het
rooster valt.
*/
char rooster_kijk(const rooster *rp, int x, int y);
/* Schrijf een bepaald object op een bepaalde positie in het rooster.
rp : een pointer naar het rooster
x,y: de betreffende positie.
c : het object.
Effect: als (x,y) binnen het rooster ligt, wordt het object op
de opgegeven positie geplaatst, anders verandert er niets.
Uitvoer: 1 als het object is geplaatst, of 0 als het buiten de grenzen lag.
*/
int rooster_plaats(rooster *rp, int x, int y, char c);
/* Zoek een bepaald object in het rooster, en geef de coordinaten van het
object terug via de gegeven pointers. Let op: als er meerdere objecten van
het gezochte soort in het rooster voorkomen, is niet gedefinieerd van welke
de positie wordt gevonden.
rp : een pointer naar het rooster
c : het object dat moet worden gezocht
x,y: pointers naar de geheugenlocaties waar de gevonden positie moet worden
geschreven.
Uitvoer: via de pointers x en y. Als het object niet wordt gevonden worden
*x en *y op -1 gezet.
*/
void rooster_zoek(const rooster *rp, char c, int *x, int *y);
#endif

262
snake.c
View File

@@ -34,15 +34,19 @@ typedef enum {
DIRECTION_LEFT = 3 DIRECTION_LEFT = 3
} direction; } direction;
static direction PREVIOUS_DIRECTION = DIRECTION_DOWN; // Snake globals
static direction CURRENT_DIRECTION = DIRECTION_DOWN; static direction PREVIOUS_DIRECTION;
static direction CURRENT_DIRECTION;
static coordinate SNAKE_HEAD; static coordinate SNAKE_HEAD;
static coordinate SNAKE_TAIL; static coordinate SNAKE_TAIL;
// Map globals
static coordinate RENDER_AT;
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 grid *GRID;
static pthread_mutex_t MUTEX; static pthread_mutex_t SNAKE_MUTEX;
/* /*
* Create a snake body part. * Create a snake body part.
@@ -51,10 +55,10 @@ static pthread_mutex_t MUTEX;
* dir: the direction the body part should point to. * dir: the direction the body part should point to.
* *
* Output: * Output:
* a character representing that bodypart. * a character representing that body part.
*/ */
static char get_body_part(const direction dir) { 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++) { for (int i = 1; i <= (MAP_WIDTH + 1) * MAP_HEIGHT; i++) {
// Also subtract the null terminator // Also subtract the null terminator
int bottom_line = i > grid_size - (MAP_WIDTH + 2); const int bottom_line = i > grid_size - (MAP_WIDTH + 2);
int top_line = i < MAP_WIDTH + 1; 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; const int line_start = line_position == 1;
int line_end = line_position == MAP_WIDTH; const int line_end = line_position == MAP_WIDTH;
int newline = line_position == 0; const int newline = line_position == 0;
if (newline) { if (newline) {
map[i - 1] = '\n'; map[i - 1] = '\n';
@@ -110,36 +114,35 @@ static void create_grid(void) {
map[grid_size - 1] = '\0'; map[grid_size - 1] = '\0';
GRID = grid_from_string(map); GRID = grid_create_from_string(map);
free(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]; coordinate empty_spots[MAP_HEIGHT * MAP_WIDTH];
// Usable as index when initialized like this.
int available_spots = 0; int available_spots = 0;
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(GRID, x, y) == CELL_EMPTY) { if (grid_fetch(GRID, x, y) == CELL_EMPTY) {
coordinate new_spot = {x, y}; const coordinate new_spot = {x, y};
empty_spots[available_spots] = new_spot; empty_spots[available_spots] = new_spot;
available_spots++; available_spots++;
} }
} }
} }
// Available spots will now be a counter.
const coordinate food_location = empty_spots[modulo(rand(), available_spots)]; 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: * Side Effects:
* Seed random with the current time. * Seed random with the current time.
* (Re)set the global variables.
* initialize the mutex
*
*/ */
static void initialize(void) { static void initialize(void) {
// Seed random.
srand(time(NULL)); srand(time(NULL));
// Create the grid.
create_grid(); create_grid();
if (GRID == NULL) { if (GRID == NULL) {
return; return;
} }
// Set globals.
CURRENT_DIRECTION = DIRECTION_DOWN; CURRENT_DIRECTION = DIRECTION_DOWN;
PREVIOUS_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. // Create the first body part and spawn the first piece of food.
rooster_zoek(GRID, get_body_part(CURRENT_DIRECTION), &SNAKE_HEAD.x, &SNAKE_HEAD.y); grid_put(GRID, SNAKE_HEAD.x, SNAKE_HEAD.y, get_body_part(CURRENT_DIRECTION));
SNAKE_TAIL.x = SNAKE_HEAD.x;
SNAKE_TAIL.y = SNAKE_HEAD.y;
generate_food(); generate_food();
if (SNAKE_HEAD.x == -1) { pthread_mutex_init(&SNAKE_MUTEX, NULL);
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;
} }
/* /*
@@ -187,11 +189,11 @@ static void initialize(void) {
* c: The char to check. * c: The char to check.
* *
* Returns: * Returns:
* The snake will move forward: 0 * The snake will move forward: SNAKE_MOVE
* The snake will eat an apple: 1 * The snake will eat an apple: SNAKE_EAT
* The snake will die: 2 * 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) { if (c == CELL_EMPTY) {
return SNAKE_MOVE; return SNAKE_MOVE;
} }
@@ -203,16 +205,32 @@ static snake_action collision_check(char c) {
return SNAKE_DIE; 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) { if (action == SNAKE_DIE) {
rooster_zet_toestand(GRID, STATE_VERLOREN); grid_put_state(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(GRID, SNAKE_TAIL.x, SNAKE_TAIL.y))) { switch (get_body_part_direction(grid_fetch(GRID, SNAKE_TAIL.x, SNAKE_TAIL.y))) {
case DIRECTION_UP: case DIRECTION_UP:
new_tail.y--; new_tail.y--;
break; break;
@@ -227,13 +245,13 @@ static void update_snake(coordinate new_head, snake_action action) {
break; 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; 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(GRID, new_head.x, new_head.y, get_body_part(CURRENT_DIRECTION)); grid_put(GRID, new_location.x, new_location.y, get_body_part(CURRENT_DIRECTION));
SNAKE_HEAD = new_head; SNAKE_HEAD = new_location;
PREVIOUS_DIRECTION = CURRENT_DIRECTION; PREVIOUS_DIRECTION = CURRENT_DIRECTION;
if (action == SNAKE_EAT) { 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) { static void *snake_move(void *arg) {
struct timespec timer; struct timespec timer;
timer.tv_sec = 0; timer.tv_sec = 0;
timer.tv_nsec = 250000000L; // Snake moves every 0.25 seconds. 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); nanosleep(&timer, NULL);
pthread_mutex_lock(&MUTEX); pthread_mutex_lock(&SNAKE_MUTEX);
coordinate new_head = SNAKE_HEAD; coordinate new_location = SNAKE_HEAD;
switch (CURRENT_DIRECTION) { switch (CURRENT_DIRECTION) {
case DIRECTION_UP: case DIRECTION_UP:
new_head.y--; new_location.y--;
break; break;
case DIRECTION_RIGHT: case DIRECTION_RIGHT:
new_head.x++; new_location.x++;
break; break;
case DIRECTION_DOWN: case DIRECTION_DOWN:
new_head.y++; new_location.y++;
break; break;
case DIRECTION_LEFT: case DIRECTION_LEFT:
new_head.x--; new_location.x--;
break; break;
} }
snake_action action = collision_check(rooster_kijk(GRID, new_head.x, new_head.y)); update_snake(new_location);
update_snake(new_head, action);
show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y); show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y);
pthread_mutex_unlock(&MUTEX); pthread_mutex_unlock(&SNAKE_MUTEX);
} }
return NULL; 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)) { && !same_coordinate(SNAKE_HEAD, SNAKE_TAIL)) {
return; 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; CURRENT_DIRECTION = dir;
show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y); 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); timeout(1);
int c = getch(); const int c = getch();
pthread_mutex_lock(&MUTEX); pthread_mutex_lock(&SNAKE_MUTEX);
switch (c) { switch (c) {
case KEY_UP: // fallthrough case KEY_UP: // fallthrough
case 'w': case 'w':
@@ -312,46 +372,78 @@ static void *play_snake(void *arg) {
break; break;
case KEY_BACKSPACE: case KEY_BACKSPACE:
case KEY_ESCAPE: case KEY_ESCAPE:
rooster_zet_toestand(GRID, STATE_QUIT); grid_put_state(GRID, STATE_QUIT);
break; break;
} }
pthread_mutex_unlock(&MUTEX); pthread_mutex_unlock(&SNAKE_MUTEX);
} }
return NULL; 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) { void snake(void) {
initialize(); initialize();
if (GRID == NULL) { if (GRID == NULL) {
return; return;
} }
// Created necessary threads.
pthread_mutex_init(&MUTEX, NULL);
pthread_t user_input;
pthread_t game_tick;
// Show game. // Show game.
erase(); erase();
show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y); 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. pthread_create(&input_thread, NULL, user_input, NULL);
mvaddstr(MESSAGE_LOCATION.y, MESSAGE_LOCATION.x, " "); pthread_create(&game_tick_thread, NULL, snake_move, NULL);
pthread_create(&user_input, NULL, play_snake, NULL); // Wait until the gamestate is no longer STATE_AAN_HET_SPELEN
pthread_create(&game_tick, NULL, snake_move, NULL); pthread_join(game_tick_thread, NULL);
pthread_join(input_thread, NULL);
}
// Wait until the gamestate is no longer STATE_AAN_HET_SPELEN game_exit_message(GRID, MESSAGE_LOCATION);
pthread_join(game_tick, NULL);
pthread_join(user_input, NULL);
pthread_mutex_destroy(&MUTEX);
rooster_klaar(GRID); snake_cleanup();
graceful_exit(MESSAGE_LOCATION);
} }

BIN
spel

Binary file not shown.

8
spel.c
View File

@@ -12,14 +12,18 @@
*/ */
#include "grid_game_engine.h" #include "grid_game_engine.h"
#include "manual.h"
#include "minigame_menu.h" #include "minigame_menu.h"
/*
* Play minigame menu.
*
* Side effect:
* Minigame menu starts.
*/
int main(void) { int main(void) {
init_engine(); init_engine();
// Speel het spel. // Speel het spel.
// manual((coordinate){0,3});
minigame_menu(); minigame_menu();
cleanup_engine(); cleanup_engine();