diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e21b91c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required() + +# ------------------------------------------------------------------ +# Define a custom target to run the default 'make' command +# ------------------------------------------------------------------ +add_custom_target( + Week6 + COMMAND ${MAKE_EXECUTABLE} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Running make in the project directory..." + VERBATIM +) + +# ------------------------------------------------------------------ +# Define a custom target to run 'make clean' +# ------------------------------------------------------------------ +add_custom_target( + Week6Clean + COMMAND ${MAKE_EXECUTABLE} clean + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Running make clean in the project directory..." + VERBATIM +) + +# ------------------------------------------------------------------ +# Define a custom target to run 'make clean' +# ------------------------------------------------------------------ +add_custom_target( + Week6Tarball + COMMAND ${MAKE_EXECUTABLE} tarball1 + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Running make tarball in the project directory..." + VERBATIM +) \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5618a70 --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +CC = gcc +CFLAGS = -std=c11 -Wextra -Wpedantic -g3 -fsanitize=address +LDFLAGS = -lncursesw -fsanitize=address +SRC = $(filter-out voorbeeld.c,$(wildcard *.c)) +HEADERS = $(wildcard *.h) + +.PHONY: tarball1 tarball2 clean + +all: spel + +spel: $(SRC) + $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) + +voorbeeld: + $(CC) -o voorbeeld voorbeeld.c $(CFLAGS) $(LDFLAGS) + +tarball1: deel1.tar.gz +tarball2: deel2.tar.gz + +deel1.tar.gz: spel.c rooster.h rooster.c Makefile + tar czf $@ $^ + +deel2.tar.gz: $(SRC) $(HEADERS) Makefile + tar czf $@ $^ assets + +clean: + rm -f *~ *.o voorbeeld spel deel?.tar.gz diff --git a/rooster.c b/rooster.c new file mode 100644 index 0000000..d0c1b84 --- /dev/null +++ b/rooster.c @@ -0,0 +1,224 @@ +#include "rooster.h" + +#include +#include +#include + + +/* + * The rooster type this program is build around. + */ +typedef struct rooster_data { + char *rost; + int height; + int width; + toestand state; +} rooster; + +/* + * Sets a grids width and height + * + * Input: + * fh: the stream to read the grid from. + * rost: a rooster file to store the width and height in. + * + * Side effects: + * the rooster 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) { + if (fh == NULL) { + return 0; + } + + while (getc(fh) != '\n') { + if (feof(fh)) { + return 0; + } + rost->width++; + } + + if (rost->width == 0) { + return 0; + } + + fseek(fh, 0, SEEK_END); + + // Get file size (- 1 for the blank newline and EOF at the end) + if (ftell(fh) % (rost->width + 1) != 0) { + // Not all lines are the same width + return 0; + } + + rost->height = (int)ftell(fh) / (int)sizeof(char) / rost->width; + fseek(fh, 0, SEEK_SET); + + return 1; +} + +rooster *rooster_lees(FILE *fh) { + if (fh == NULL) { + return NULL; + } + + rooster rost = { + NULL, + 0, + 0, + BEGIN + }; + + // Sets the width and height of the rooster. + if (get_grid_sizes(fh, &rost) != 1) { + // Unlogical file structure. + return NULL; + } + + const int grid_size = (rost.width + 1) * rost.height + 1; + + rost.rost = malloc(grid_size * sizeof(char)); + if (rost.rost == NULL) { + return NULL; + } + + // This makes the strncat() work. + rost.rost[0] = '\0'; + + char *line = malloc((rost.width + 2) * sizeof(char)); + if (line == NULL) { + return NULL; + } + + for (int i = 0; i < rost.height; i++) { + if (fgets(line, rost.width + 2, fh) == NULL) { + free(rost.rost); + free(line); + return NULL; + } + + // Validate that the line length is correct + if ((int)strcspn(line, "\n") != rost.width) { + free(rost.rost); + free(line); + return NULL; + } + + // Width is without the newline at the end. + strncat(rost.rost, line, rost.width + 1); + } + + free(line); + + rooster *return_rooster = malloc(sizeof(rost)); + if (return_rooster == NULL) { + return NULL; + } + + memcpy(return_rooster, &rost, sizeof(rost)); + + return return_rooster; +} + +toestand rooster_vraag_toestand(const rooster *rp) { + if (rp != NULL) { + return rp->state; + } + return VERLOREN; +} + +void rooster_zet_toestand(rooster *rp, toestand t) { + if (rp != NULL) { + switch (t) { + case BEGIN: + rp->state = BEGIN; + break; + case AAN_HET_SPELEN: + rp->state = AAN_HET_SPELEN; + break; + case GEWONNEN: + rp->state = GEWONNEN; + break; + case VERLOREN: + rp->state = VERLOREN; + break; + } + } +} + +void rooster_klaar(rooster *rp) { + if (rp != NULL) { + if (rp->rost != NULL) + { + free(rp->rost); + } + free(rp); + } +} + +int rooster_breedte(const rooster *rp) { + if (rp == NULL) { + return 0; + } + return rp->width; +} + +int rooster_hoogte(const rooster *rp) { + if (rp == NULL) { + return 0; + } + return rp->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) + { + return 1; + } + } + 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)]; + } + 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; + return 1; + } + return 0; +} + +void rooster_zoek(const rooster *rp, char c, int *x, int *y) { + if (rp == NULL || rp->rost == NULL) { + *x = -1; + *y = -1; + return; + } + + const char search[2] = {c}; + + int strpos = strcspn(rp->rost, search); + + if (rp->rost[strpos] == '\0') + { + *x = -1; + *y = -1; + return; + } + + *x = strpos % (rp->width + 1); + *y = strpos / (rp->width + 1); +} diff --git a/rooster.h b/rooster.h new file mode 100644 index 0000000..5ce54c9 --- /dev/null +++ b/rooster.h @@ -0,0 +1,152 @@ +/* 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 { + BEGIN, + AAN_HET_SPELEN, + GEWONNEN, + VERLOREN +} 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 *rooster_lees(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 *rooster_maak(char* template); + +/* Vraag de huidige toestand van het spel op. + + 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/spel.c b/spel.c new file mode 100644 index 0000000..24914d2 --- /dev/null +++ b/spel.c @@ -0,0 +1,265 @@ +/* + * Naam: Jeroen Boxhoorn + * UvAnetID: 16333969 + * Studie: BSc Informatica + * + * This program loads in a maze from a text file and then lets you play that maze. + * + * Params when running the program: + * - The path to the text file containing the maze. + * + * How to play the game: + * - You are the '*' character. + * - Use either WSAD or the arrow keys to navigate through the maze. + * - The exit of the maze is marked with a '$'. + * - Walls are '#'. + * - Traps are 'X'. These kill you. + * + * You can quit the program at any time by pressing CTRL + C. + * + * Have fun playing! + */ + +#include +#include +#include + +#include +#include +#include + +#include "rooster.h" + +/* Toont het gegeven rooster met ncurses. + * + * Input: + * rp: een pointer naar het rooster. + * + * Side effect: + * De console wordt geleegd en het rooster wordt erin gezet. + */ +void toon_rooster(rooster *rp) { + clear(); + + int height = rooster_hoogte(rp); + int width = rooster_breedte(rp); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (rooster_bevat(rp, x, y) == 1) + { + mvaddch(y, x, rooster_kijk(rp, x, y)); + } + } + } + refresh(); +} + +void update_grid(rooster *rp, char c, int x, int y) { + if (rooster_plaats(rp, x, y, c) == 1) { + mvaddch(y, x, c); + } +} + +/* 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 + * 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. + * + * Side effect: het rooster wordt aangepast op basis van de handeling van + * de speler. + */ +void beweeg(rooster *rp, int dx, int dy) { + int playerx; + int playery; + rooster_zoek(rp, '*', &playerx, &playery); + + if (playerx == -1 || playery == -1) { + printf("Player not found!"); + exit(1); + } + + if (rooster_bevat(rp, playerx + dx, playery + dy) == 1) { + char new_location = rooster_kijk(rp, playerx + dx, playery + dy); + switch (new_location) { + case '#': + break; + case 'X': + update_grid(rp, ' ', playerx, playery); + rooster_zet_toestand(rp, VERLOREN); + break; + case ' ': + update_grid(rp, ' ', playerx, playery); + update_grid(rp, '*', playerx + dx, playery + dy); + break; + case '$': + update_grid(rp, ' ', playerx, playery); + rooster_zet_toestand(rp, GEWONNEN); + break; + } + refresh(); + } +} + +/* + * Speel een dolhof spel. + * + * Input: + * rp: Een pointer naar een rooster met een valide doolhof erin. + * + * Side Effects: + * Met WSAD en arrow keys kun je door het doolhof heen bewegen. + */ +void run_maze(rooster *rp) { + while (rooster_vraag_toestand(rp) == AAN_HET_SPELEN) + { + switch (getch()) { + case KEY_UP: // fallthrough + case 'w': + beweeg(rp, 0, -1); + break; + case KEY_DOWN: // fallthrough + case 's': + beweeg(rp, 0, 1); + break; + case KEY_LEFT: // fallthrough + case 'a': + beweeg(rp, -1, 0); + break; + case KEY_RIGHT: // fallthrough + case 'd': + beweeg(rp, 1, 0); + break; + case KEY_EXIT: + rooster_zet_toestand(rp, VERLOREN); + break; + } + } +} + +/* + * Toont het maze victory screen. + * + * Side Effect: + * De victory message wordt op een schone console geprint. + */ +void display_maze_victory() { + clear(); + mvprintw(2,5, "YOU WON!!!!!"); + refresh(); +} + +/* + * Toont het maze loss screen. + * + * Side Effect: + * De loss message wordt op een schone console geprint. + */ +void display_maze_loss() { + clear(); + mvprintw(2,5, "RIP, YOU DIED..."); + refresh(); +} + +/* + * Toont het maze broken screen. + * + * Side Effect: + * De hackerman message wordt op een schone console geprint. + */ +void display_hackerman() { + clear(); + mvprintw(2,5, "The hacker man strikes again..."); + refresh(); +} + +/* + * Bepaalt afhankelijk van de eindtoestand van het rooster + * welk afsluitscherm er getoond moet worden en toont dan dat rooster. + * + * Input: Het rooster om de toestand uit af te lezen. + * + * Side Effects: + * Het end-of-game scherm wordt op een blanke console geprint. + */ +void maze_exit_screen(rooster *rp) { + toestand current_state = rooster_vraag_toestand(rp); + switch (current_state) { + case GEWONNEN: + display_maze_victory(); + return; + case VERLOREN: + display_maze_loss(); + return; + } + display_hackerman(); +} + +/* + * Waits for the user to press an exit key 'q' before continuing. + */ +void graceful_exit() { + mvprintw(5, 0, "Press 'q' to exit the game."); + while (1) { + switch (getch()) { + case 'q': + return; + } + } +} + + +/* + * Speelt het spel met een gegeven rooster tot de toestand niet langer + * AAN_HET_SPELEN is. + */ +void speel(rooster *rp) { + toon_rooster(rp); + rooster_zet_toestand(rp, AAN_HET_SPELEN); + run_maze(rp); + maze_exit_screen(rp); + graceful_exit(); +} + +int main(int argc, char *argv[]) { + // 1. Controleer dat er een doolhofbestand is opgegeven op de command line. + if (argc != 2) { + fprintf(stderr, "gebruik: ./spel assets/voorbeeld_doolhof.txt\n"); + return 1; + } + + // 2. Open het doolhofbestand en lees het rooster. + FILE *fh = fopen(argv[1], "r"); + if (fh == NULL) { + perror("main"); + return 1; + } + rooster *rp = rooster_lees(fh); + fclose(fh); + + // 3. Bepaal of het lezen van het rooster is gelukt. + if (rp == NULL) { + fprintf(stderr, "Kan rooster niet maken.\n"); + return 1; + } + + // 4. Initialiseer ncurses + setlocale(LC_ALL, ""); + initscr(); + cbreak(); // zodat je kunt onderbreken met Ctrl+C + keypad(stdscr, TRUE); // luister ook naar extra toetsen zoals pijltjes + noecho(); // druk niet de letters af die je intypt + curs_set(0); // hides the cursor + + + // 5. Speel het spel. + speel(rp); + + // 6. Sluit af. + rooster_klaar(rp); + endwin(); + return 0; +} diff --git a/voorbeeld.c b/voorbeeld.c new file mode 100644 index 0000000..a1862dd --- /dev/null +++ b/voorbeeld.c @@ -0,0 +1,44 @@ +/* Voorbeeldje van het gebruik van ncurses. + In dit voorbeeld kun je alleen met een + naar links en naar rechts wandelen + over een lijn van -. +*/ + +#include + +int RANGE = 20; + +/* Toont de situatie met ncurses. + + pos: de positie van de +. +*/ +void laat_zien(int pos) { + clear(); // begin met een nieuw ncurses scherm + for (int i = 0; i < RANGE; i++) { + addch(i == pos ? '+' : '-'); + } + refresh(); // zorg dat het scherm ook echt getoond wordt +} + +int main(void) { + // Initialiseer ncurses + initscr(); + cbreak(); // zodat je kunt onderbreken met Ctrl+C + keypad(stdscr, TRUE); // luister ook naar extra toetsen zoals pijltjes + noecho(); // druk niet de letters af die je intypt + + int pos = RANGE / 2; // begin in het midden van de lijn. + while (1) { + laat_zien(pos); + int toets = getch(); + switch (toets) { + case KEY_LEFT: + pos--; + break; + case KEY_RIGHT: + pos++; + break; + } + } + + return 0; +} diff --git a/voorbeeld_doolhof.txt b/voorbeeld_doolhof.txt new file mode 100644 index 0000000..3729ef1 --- /dev/null +++ b/voorbeeld_doolhof.txt @@ -0,0 +1,13 @@ +####################### +#*# # +# # $ ########### # +# ######## # +# # ############ +# ###### # # +# # # ############ # +# #### # # # +# # ##### # XXXXXX# +# XXX# # # +# #########XXXXXXX # +# X # +#######################