diff --git a/Makefile b/Makefile index 3ec11c4..878f8f9 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CC = gcc -CFLAGS = -std=c11 -Wextra -Wpedantic -g3 -fsanitize=address +CFLAGS = -std=c11 -Wextra -Wpedantic -pthread -g3 -fsanitize=address LDFLAGS = -lncurses -fsanitize=address SRC = $(filter-out voorbeeld.c,$(wildcard *.c)) HEADERS = $(wildcard *.h) @@ -17,7 +17,7 @@ voorbeeld: tarball1: deel1.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 $@ $^ deel2.tar.gz: $(SRC) $(HEADERS) Makefile diff --git a/assets/handleiding.txt b/assets/handleiding.txt index 8e81adc..55b9df2 100644 --- a/assets/handleiding.txt +++ b/assets/handleiding.txt @@ -2,17 +2,40 @@ | | | !!! 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 | | | | | | +| | 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: | | @@ -24,13 +47,10 @@ | | Select: | | | | 'f', enter, space bar | | | | | | -| | Exit: | | -| | Escape, backspace | | -| | | | | *------------------------------------* | | | | *-----------------* | -| |- MAZE RUNNER -| | +| | MAZE RUNNER | | | *------------------------------------* | | | | | | | Move up: | | @@ -45,13 +65,10 @@ | | Move left: | | | | 'a', arrow_left | | | | | | -| | Exit: | | -| | Escape, backspace | | -| | | | | *------------------------------------* | | | | *-----------------* | -| |---- SNAKE ----| | +| | SNAKE | | | *------------------------------------* | | | | | | | Turn up: | | @@ -66,10 +83,5 @@ | | Turn left: | | | | 'a', arrow_left | | | | | | -| | Exit: | | -| | Escape, backspace | | -| | | | | *------------------------------------* | *--------------------------------------------* -1234567890123456789012345678901234567890123456 - 10 20 30 40 | diff --git a/rooster.c b/grid.c similarity index 52% rename from rooster.c rename to grid.c index 5b59deb..2224b50 100644 --- a/rooster.c +++ b/grid.c @@ -1,20 +1,24 @@ -#include "rooster.h" +#include "grid.h" #include #include #include /* - * 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; int height; int width; - toestand state; -} rooster; + state state; +} 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 height = 0; 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; - rooster *rp = malloc(sizeof(rooster)); + grid *gp = malloc(sizeof(grid)); - if (rp == NULL) { + if (gp == NULL) { return NULL; } - rp->rost = malloc(grid_size * sizeof(char)); - rp->height = height; - rp->width = width; - rp->state = STATE_BEGIN; + gp->rost = malloc(grid_size * sizeof(char)); + gp->height = height; + gp->width = width; + 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: * 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: - * the rooster gets its width and height set + * the grid gets its width and height set * * Output: * 1 if the file width and height seem to match with its size * 0 otherwise */ -static int get_grid_sizes(FILE *fh, rooster *rost) { +static int get_grid_sizes(FILE *fh, grid *rost) { while (getc(fh) != '\n') { if (feof(fh)) { return 0; @@ -94,19 +98,19 @@ static int get_grid_sizes(FILE *fh, rooster *rost) { return 1; } -rooster *grid_from_file(FILE *fh) { +grid *grid_create_from_file(FILE *fh) { if (fh == NULL) { return NULL; } - rooster rost = { + grid rost = { NULL, 0, 0, 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) { // Unlogical file structure. return NULL; @@ -147,76 +151,72 @@ rooster *grid_from_file(FILE *fh) { free(line); - rooster *return_rooster = malloc(sizeof(rost)); - if (return_rooster == NULL) { + grid *return_grid = malloc(sizeof(rost)); + if (return_grid == 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) { - if (rp != NULL) { - return rp->state; +state grid_fetch_state(const grid *gp) { + if (gp != NULL) { + return gp->state; } return STATE_VERLOREN; } -void rooster_zet_toestand(rooster *rp, toestand t) { - if (rp != NULL) { +void grid_put_state(grid *gp, const state t) { + if (gp != NULL) { switch (t) { case STATE_BEGIN: - rp->state = STATE_BEGIN; + gp->state = STATE_BEGIN; break; case STATE_AAN_HET_SPELEN: - rp->state = STATE_AAN_HET_SPELEN; + gp->state = STATE_AAN_HET_SPELEN; break; case STATE_GEWONNEN: - rp->state = STATE_GEWONNEN; + gp->state = STATE_GEWONNEN; break; case STATE_VERLOREN: - rp->state = STATE_VERLOREN; + gp->state = STATE_VERLOREN; break; case STATE_QUIT: - rp->state = STATE_QUIT; + gp->state = STATE_QUIT; break; } } } -void rooster_klaar(rooster *rp) { - if (rp != NULL) { - if (rp->rost != NULL) +void grid_cleanup(grid *gp) { + if (gp != NULL) { + if (gp->rost != NULL) { - free(rp->rost); + free(gp->rost); } - free(rp); + free(gp); } } -int rooster_breedte(const rooster *rp) { - if (rp == NULL) { +int grid_width(const grid *gp) { + if (gp == NULL) { return 0; } - return rp->width; + return gp->width; } -int rooster_hoogte(const rooster *rp) { - if (rp == NULL) { +int grid_height(const grid *gp) { + if (gp == NULL) { return 0; } - return rp->height; + return gp->height; } -static int internal_location(const rooster *rp, const int x, const int y) { - return y * (rp->width + 1) + x; -} - -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) +int grid_contains(const grid *gp, const int x, const int y) { + if (gp != NULL && gp->rost != NULL) { + if (x >= 0 && y >= 0 && x < gp->width && y < gp->height) { return 1; } @@ -224,58 +224,58 @@ int rooster_bevat(const rooster *rp, int x, int y) { return 0; } -char rooster_kijk(const rooster *rp, int x, int y) { - if (rp != NULL && rp->rost != NULL && rooster_bevat(rp, x, y) == 1) { - return rp->rost[internal_location(rp, x, y)]; +char grid_fetch(const grid *gp, const int x, const int y) { + if (gp != NULL && gp->rost != NULL && grid_contains(gp, x, y) == 1) { + return gp->rost[internal_location(gp, x, y)]; } return '\0'; } -int rooster_plaats(rooster *rp, int x, int y, char c) { - if (rp != NULL && rp->rost != NULL && rooster_bevat(rp, x, y) == 1) { - rp->rost[internal_location(rp, x, y)] = c; +int grid_put(grid *gp, const int x, const int y, const char c) { + if (gp != NULL && gp->rost != NULL && grid_contains(gp, x, y) == 1) { + gp->rost[internal_location(gp, x, y)] = c; return 1; } return 0; } -char *rooster_vraag_rij(const rooster *rp, int y) { - if (rp != NULL && rp->rost != NULL && rooster_bevat(rp, 0, y) == 1) { +char *grid_fetch_row(const grid *gp, const int y) { + if (gp != NULL && gp->rost != NULL && grid_contains(gp, 0, y) == 1) { // we're going to remove the newline so this is long enough - char *row = malloc((rp->width + 1) * sizeof(char)); - memcpy(row, &rp->rost[internal_location(rp, 0, y)], rp->width * sizeof(char)); - row[rp->width] = '\0'; + char *row = malloc((gp->width + 1) * sizeof(char)); + memcpy(row, &gp->rost[internal_location(gp, 0, y)], gp->width * sizeof(char)); + row[gp->width] = '\0'; return row; } return NULL; } -rooster *rooster_kopieer(const rooster *rp) { - if (rp != NULL && rp->rost != NULL) { - const size_t grid_memory = ((rp->width + 1) * rp->height + 1) * sizeof(char); +grid *grid_copy(const grid *gp) { + if (gp != NULL && gp->rost != NULL) { + const size_t grid_memory = ((gp->width + 1) * gp->height + 1) * sizeof(char); - char *grid = malloc(grid_memory); - if (grid == NULL) { + char *rost = malloc(grid_memory); + if (rost == NULL) { return NULL; } - rooster *new_rooster = malloc(sizeof(*rp)); - if (new_rooster == NULL) { - return NULL; - } + grid *new_grid = malloc(sizeof(*gp)); + if (new_grid == 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; - return new_rooster; + new_grid->rost = rost; + return new_grid; } return NULL; } -void rooster_zoek(const rooster *rp, char c, int *x, int *y) { - if (rp == NULL || rp->rost == NULL) { +void grid_find(const grid *gp, const char c, int *x, int *y) { + if (gp == NULL || gp->rost == NULL) { *x = -1; *y = -1; return; @@ -283,15 +283,15 @@ void rooster_zoek(const rooster *rp, char c, int *x, int *y) { 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; *y = -1; return; } - *x = strpos % (rp->width + 1); - *y = strpos / (rp->width + 1); + *x = char_index % (gp->width + 1); + *y = char_index / (gp->width + 1); } diff --git a/grid.h b/grid.h new file mode 100644 index 0000000..5dd800a --- /dev/null +++ b/grid.h @@ -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 + + +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 diff --git a/grid_game_engine.c b/grid_game_engine.c index 9f1401b..82958d0 100644 --- a/grid_game_engine.c +++ b/grid_game_engine.c @@ -10,8 +10,6 @@ #include #include -#include "rooster.h" - int same_coordinate(const coordinate a, const coordinate b) { return a.x == b.x && a.y == b.y; } @@ -24,23 +22,23 @@ int modulo(const int number, const int mod) { return result; } -void show_grid_on_offset(const rooster *gp, const int starting_x, const int starting_y) { - const int height = rooster_hoogte(gp); +void show_grid_on_offset(const grid *gp, const int starting_x, const int starting_y) { + const int height = grid_height(gp); 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); free(rij); } refresh(); } -void show_grid(const rooster *gp) { +void show_grid(const grid *gp) { show_grid_on_offset(gp, 0, 0); } -void update_grid(rooster *gp, const char c, const int x, const int y) { - if (rooster_plaats(gp, x, y, c) == 1) { +void update_grid(grid *gp, const char c, const int x, const int y) { + if (grid_put(gp, x, y, c) == 1) { 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. */ static void display_victory(const coordinate location) { - erase(); - mvprintw(location.y, location.x, "YOU WON!!!!!"); - refresh(); + mvaddstr(location.y, location.x, "YOU WON!!!!!"); } /* @@ -64,9 +60,7 @@ static void display_victory(const coordinate location) { * Clears the console and prints the GAME OVER message. */ static void display_loss(const coordinate location) { - erase(); - mvprintw(location.y, location.x, "GAME OVER"); - refresh(); + mvaddstr(location.y, location.x, "GAME OVER"); } /* @@ -76,9 +70,7 @@ static void display_loss(const coordinate location) { * Clears the console and prints the quit message. */ static void display_quit(const coordinate location) { - erase(); - mvprintw(location.y, location.x, "You quit the game"); - refresh(); + mvaddstr(location.y, location.x, "You quit the game"); } /* @@ -88,15 +80,15 @@ static void display_quit(const coordinate location) { * Clears the console and prints the hacker man message. */ static void display_hackerman(const coordinate location) { - erase(); - mvprintw(location.y, location.x, "The hacker man strikes again..."); - refresh(); + mvaddstr(location.y, location.x, "The hacker man strikes again..."); } 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) { switch (getch()) { + case KEY_BACKSPACE: + case KEY_ESCAPE: case KEY_ENTER: case '\n': case ' ': @@ -105,8 +97,8 @@ void graceful_exit(const coordinate message_location) { } } -void game_exit_screen(const rooster *gp, coordinate location) { - const toestand current_state = rooster_vraag_toestand(gp); +void game_exit_message(const grid *gp, coordinate location) { + const state current_state = grid_fetch_state(gp); switch (current_state) { case STATE_GEWONNEN: display_victory(location); @@ -123,9 +115,24 @@ void game_exit_screen(const rooster *gp, coordinate location) { location.y += 2; 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; setlocale(LC_ALL, ""); initscr(); @@ -134,9 +141,6 @@ static void init_ncurses() { noecho(); // Don't write the keyboard input to the console. 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(); init_pair(BLACK, COLOR_BLACK, COLOR_BLACK); init_pair(WHITE, COLOR_BLACK, COLOR_WHITE); @@ -150,14 +154,10 @@ static void init_ncurses() { clear(); } -static void cleanup_ncurses() { - endwin(); -} - void init_engine(void) { init_ncurses(); } void cleanup_engine(void) { - cleanup_ncurses(); + endwin(); } diff --git a/grid_game_engine.h b/grid_game_engine.h index 988c574..713e90f 100644 --- a/grid_game_engine.h +++ b/grid_game_engine.h @@ -9,7 +9,7 @@ #ifndef MINIGAME_MENU_GRID_GAME_ENGINE_H #define MINIGAME_MENU_GRID_GAME_ENGINE_H -#include "rooster.h" +#include "grid.h" #define KEY_ESCAPE 27 @@ -32,7 +32,7 @@ typedef enum { typedef struct { char name[100]; - rooster *game_map; + grid *game_map; } game_maps; /* @@ -63,7 +63,7 @@ int modulo(int number, int mod); * Side effect: * 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. @@ -74,7 +74,37 @@ void show_grid_on_offset(const rooster *gp, int starting_x, int starting_y); * Side effect: * 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. @@ -88,7 +118,7 @@ void show_grid(const rooster *gp); * Side effect: * 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. @@ -100,7 +130,7 @@ void update_grid(rooster *gp, char c, int x, int y); * Side Effects: * 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. diff --git a/manual.c b/manual.c index 0655140..63cab11 100644 --- a/manual.c +++ b/manual.c @@ -5,8 +5,7 @@ #include "manual.h" #include - -#include "rooster.h" +#include "grid_game_engine.h" void manual(const coordinate display_location) { erase(); @@ -15,19 +14,22 @@ void manual(const coordinate display_location) { return; } - rooster *grid = grid_from_file(fp); + grid *grid = grid_create_from_file(fp); if (grid == NULL) { mvaddstr(display_location.y, display_location.x, "Error loading grid"); return; } fclose(fp); + show_grid_on_offset(grid, display_location.x, display_location.y); - rooster_klaar(grid); - - int ch = getch(); - while (ch != KEY_ESCAPE && ch != KEY_BACKSPACE && ch != ' ') { - ch = getch(); + // Wait until ESCAPE or BACKSPACE is pressed. + timeout(200); + for (int ch = getch(); ch != KEY_ESCAPE && ch != KEY_BACKSPACE && ch != ' '; ch = getch()) { + // Update the screen in the meantime to accommodate windows resizes. + show_grid_on_offset(grid, display_location.x, display_location.y); } + + grid_cleanup(grid); } diff --git a/maze_runner.c b/maze_runner.c index 332a84d..ca56a20 100644 --- a/maze_runner.c +++ b/maze_runner.c @@ -8,37 +8,43 @@ #include #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. + * + * Side Effects: + * Memory is allocated to store the grid in. */ -static rooster *get_maze(void) { - // TODO: echt opties aanbieden in plaats van hardcoded 1 maze. - // Alternatief is om random een maze te genereren. dit is miss beter. - - // 2. Open het doolhof bestand en lees het rooster. +static grid *get_maze(void) { + // Open het doolhof bestand en lees het rooster. FILE *fh = fopen("assets/maze.txt", "r"); if (fh == NULL) { perror("loading maze"); return NULL; } - rooster *rp = grid_from_file(fh); + grid *gp = grid_create_from_file(fh); fclose(fh); - // 3. Bepaal of het lezen van het rooster is gelukt. - if (rp == NULL) { + // Bepaal of het lezen van het rooster is gelukt. + if (gp == NULL) { fprintf(stderr, "Kan rooster niet maken.\n"); return NULL; } - return rp; + return gp; } /* Voert de benodigde veranderingen in het rooster door als de speler in een * bepaalde richting probeert te bewegen. * 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- * heden voor (dx,dy) zijn (-1,0), (1,0), (0,-1), (0,1) voor resp. * 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 * de speler. */ -static void maze_runner_beweeg(rooster *rp, int dx, int dy) { - int playerx; - int playery; - rooster_zoek(rp, '*', &playerx, &playery); +static void maze_runner_move(grid *gp, const int dx, const int dy) { + coordinate player_position = {0, 0}; + grid_find(gp, LIVING_PLAYER, &player_position.x, &player_position.y); - if (playerx == -1 || playery == -1) { + if (player_position.y == -1) { printf("Player not found!"); - rooster_zet_toestand(rp, STATE_BEGIN); + grid_put_state(gp, STATE_BEGIN); return; } - if (rooster_bevat(rp, playerx + dx, playery + dy) == 1) { - char new_location = rooster_kijk(rp, playerx + dx, playery + dy); + if (grid_contains(gp, player_position.x + dx, player_position.y + dy) == 1) { + char new_location = grid_fetch(gp, player_position.x + dx, player_position.y + dy); switch (new_location) { - case '#': + case WALL: break; - case 'X': - update_grid(rp, ' ', playerx, playery); - rooster_zet_toestand(rp, STATE_VERLOREN); + case TRAP: + grid_put_state(gp, STATE_VERLOREN); + + enable_highlight(RED); + update_grid(gp, DEAD_PLAYER, player_position.x, player_position.y); + disable_highlight(RED); break; - case ' ': - update_grid(rp, ' ', playerx, playery); - update_grid(rp, '*', playerx + dx, playery + dy); + case EMPTY: + update_grid(gp, EMPTY, player_position.x, player_position.y); + update_grid(gp, LIVING_PLAYER, player_position.x + dx, player_position.y + dy); break; - case '$': - update_grid(rp, ' ', playerx, playery); - rooster_zet_toestand(rp, STATE_GEWONNEN); + case MAZE_EXIT: + update_grid(gp, EMPTY, player_position.x, player_position.y); + grid_put_state(gp, STATE_GEWONNEN); break; } 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 * 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()) { case KEY_UP: // fallthrough case 'w': case 'W': - maze_runner_beweeg(rp, 0, -1); + maze_runner_move(gp, 0, -1); break; case KEY_DOWN: // fallthrough case 's': case 'S': - maze_runner_beweeg(rp, 0, 1); + maze_runner_move(gp, 0, 1); break; case KEY_LEFT: // fallthrough case 'a': case 'A': - maze_runner_beweeg(rp, -1, 0); + maze_runner_move(gp, -1, 0); break; case KEY_RIGHT: // fallthrough case 'd': case 'D': - maze_runner_beweeg(rp, 1, 0); + maze_runner_move(gp, 1, 0); break; case 'p': case KEY_BACKSPACE: case KEY_ESCAPE: - rooster_zet_toestand(rp, STATE_QUIT); + grid_put_state(gp, STATE_QUIT); break; } } void maze_runner(void) { // Voorbereiding. - rooster *rp = get_maze(); - if (rp == NULL) { + grid *gp = get_maze(); + if (gp == NULL) { return; } - show_grid(rp); - rooster_zet_toestand(rp, STATE_AAN_HET_SPELEN); + show_grid(gp); + grid_put_state(gp, STATE_AAN_HET_SPELEN); // Game zelf. - while (rooster_vraag_toestand(rp) == STATE_AAN_HET_SPELEN) { - speel_maze(rp); + while (grid_fetch_state(gp) == STATE_AAN_HET_SPELEN) { + speel_maze(gp); } // Exit game. - game_exit_screen(rp, (coordinate){0, 0}); - rooster_klaar(rp); + game_exit_message(gp, (coordinate){0, grid_height(gp) + 2}); + grid_cleanup(gp); } diff --git a/minigame_menu.c b/minigame_menu.c index 4bb6c67..635796a 100644 --- a/minigame_menu.c +++ b/minigame_menu.c @@ -11,20 +11,19 @@ #include "manual.h" #include "maze_runner.h" #include "minesweeper.h" -#include "rooster.h" #include "snake.h" #define AMOUNT_OF_MENU_OPTIONS 5 typedef enum { - GAME_HELP = 0, + GAME_MANUAL = 0, GAME_MAZE_RUNNER = 1, GAME_SNAKE = 2, GAME_MINESWEEPER = 3, GAME_QUIT = 4, } game; -static game SELECTED_GAME = GAME_HELP; +static game SELECTED_GAME = GAME_MANUAL; static int OFFSET_Y = 5; static int OFFSET_X = 5; @@ -37,7 +36,7 @@ static int OFFSET_X = 5; */ static void launch_game(const game game) { switch (game) { - case GAME_HELP: + case GAME_MANUAL: manual((coordinate){0,0}); break; 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 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) { - case GAME_HELP: + case GAME_MANUAL: case GAME_MAZE_RUNNER: case GAME_SNAKE: case GAME_MINESWEEPER: case GAME_QUIT: 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); free(row); @@ -94,7 +93,7 @@ static void menu_highlight(const rooster *menu) { * Side Effects: * Displays the menu */ -static void show_menu(const rooster *menu) { +static void show_menu(const grid *menu) { erase(); show_grid_on_offset(menu, OFFSET_X, OFFSET_Y); menu_highlight(menu); @@ -167,19 +166,19 @@ static int navigate_menu(void) { * Output: * A pointer to the menu grid. */ -static rooster *initialize_menu(void) { +static grid *initialize_menu(void) { char menu[] = "How to play\n" "Maze Runner\n" " Snake \n" "Minesweeper\n" " Leave \n"; - rooster *rp = grid_from_string(menu); + grid *rp = grid_create_from_string(menu); return rp; } void minigame_menu(void) { - rooster *menu = initialize_menu(); - + grid *menu = initialize_menu(); + launch_game(GAME_MANUAL); while (true) { show_menu(menu); if (navigate_menu() == 1) { @@ -187,5 +186,5 @@ void minigame_menu(void) { } } - rooster_klaar(menu); + grid_cleanup(menu); } diff --git a/rooster.h b/rooster.h deleted file mode 100644 index 565c641..0000000 --- a/rooster.h +++ /dev/null @@ -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 - - // 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 diff --git a/snake.c b/snake.c index b298c98..7d7a389 100644 --- a/snake.c +++ b/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(); } diff --git a/spel b/spel index 9feb65c..df382ac 100755 Binary files a/spel and b/spel differ diff --git a/spel.c b/spel.c index 0ffc4bb..c7123a3 100644 --- a/spel.c +++ b/spel.c @@ -12,14 +12,18 @@ */ #include "grid_game_engine.h" -#include "manual.h" #include "minigame_menu.h" +/* + * Play minigame menu. + * + * Side effect: + * Minigame menu starts. + */ int main(void) { init_engine(); // Speel het spel. - // manual((coordinate){0,3}); minigame_menu(); cleanup_engine();