Compare commits

...

14 Commits

31 changed files with 1708 additions and 928 deletions

3
.gitignore vendored
View File

@@ -1,6 +1,9 @@
# Clion files
.idea
# Cmake files
cmake-build-debug
# Prerequisites
*.d

View File

@@ -1,34 +0,0 @@
cmake_minimum_required()
# ------------------------------------------------------------------
# Define a custom target to run the default 'make' command
# ------------------------------------------------------------------
add_custom_target(
MinigameMenu
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(
Clean
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(
Tarball
COMMAND ${MAKE_EXECUTABLE} tarball2
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Running make tarball in the project directory..."
VERBATIM
)

View File

@@ -1,27 +0,0 @@
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

17
Makefile.trial Normal file
View File

@@ -0,0 +1,17 @@
CC = gcc
CFLAGS = -std=gnu11 -Wextra -pedantic -Wall -O0 -g3 -fsanitize=address
LDFLAGS = -lncursesw -fsanitize=address
OBJ_DIR = ./build
OBJECTS = $(wildcard $(OBJ_DIR)/*.o)
BUILD_COMMAND = $(CC) -c $^ -o $(OBJ_DIR)/$@ $(CFLAGS) $(LDFLAGS)
main: build
build: main.o
$(BUILD_COMMAND)
clean:
rm -f *~ $(OBJECTS) main
grid: engine/grid/grid.c
$(CC) -c $^ -o $(OBJ_DIR)/$@.o $(CFLAGS) $(LDFLAGS)

45
Makefile.yeet Normal file
View File

@@ -0,0 +1,45 @@
CC = gcc
CFLAGS = -std=gnu11 -Wextra -pedantic -Wall -O0 -g3 -fsanitize=address
LDFLAGS = -lncurses -fsanitize=address
OBJ_DIR = build/
OBJECTS = $(wildcard $(OBJ_DIR)/*.o)
BUILD_COMMAND = $(CC) -c $^ -o $(OBJ_DIR)/$@ $(CFLAGS) $(LDFLAGS)
SRC = src/
GAMES_DIR = games/
ENGINE_DIR = engine/
.PHONY: clean
build: $(OBJECTS)
$(CC) -o minigame-menu $(CFLAGS) $(LDFLAGS)
clean:
rm -f *~ $(OBJECTS) main
main.o: src/main.c
$(BUILD_COMMAND)
minigame-menu.o: src/games
$(BUILD_COMMAND)
manual.o: src/games
$(BUILD_COMMAND)
maze-runner.o: src/games
$(BUILD_COMMAND)
snake.o: src/games
$(BUILD_COMMAND)
minesweeper.o: src/games
$(BUILD_COMMAND)
engine.o: src/engine
$(BUILD_COMMAND)
grid.o: src/engine
$(BUILD_COMMAND)

87
assets/manual.txt Normal file
View File

@@ -0,0 +1,87 @@
*--------------------------------------------*
| |
| !!! 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. |
| |
| *________________* |
| | GENERAL | |
| *------------------------------------* |
| | | |
| | 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 | |
| *------------------------------------* |
| | | |
| | Move down: | |
| | 's', arrow_down | |
| | | |
| | Move up: | |
| | 'w', arrow_up | |
| | | |
| | Select: | |
| | 'f', enter, space bar | |
| | | |
| *------------------------------------* |
| |
| *-----------------* |
| | MAZE RUNNER | |
| *------------------------------------* |
| | | |
| | Move up: | |
| | 'w', arrow_up | |
| | | |
| | Move down: | |
| | 's', arrow_down | |
| | | |
| | Move right: | |
| | 'd', arrow_right | |
| | | |
| | Move left: | |
| | 'a', arrow_left | |
| | | |
| *------------------------------------* |
| |
| *-----------------* |
| | SNAKE | |
| *------------------------------------* |
| | | |
| | Turn up: | |
| | 'w', arrow_up | |
| | | |
| | Turn down: | |
| | 's', arrow_down | |
| | | |
| | Turn right: | |
| | 'd', arrow_right | |
| | | |
| | Turn left: | |
| | 'a', arrow_left | |
| | | |
| *------------------------------------* |
*--------------------------------------------*

View File

@@ -1,103 +0,0 @@
/*
* Created by snapshot112 on 10/15/25.
*
* A game engine that uses the can run and display games in rectangular grids.
* The graphics are made using ncursesw.
*
* Please make sure to initialize the game engine before running any games.
*/
#ifndef MINIGAME_MENU_GRID_GAME_ENGINE_H
#define MINIGAME_MENU_GRID_GAME_ENGINE_H
#include "rooster.h"
typedef enum {
BLACK = 1,
WHITE = 2,
BLUE = 3,
GREEN = 4,
CYAN = 5,
MAGENTA = 6,
YELLOW = 7,
RED = 8
} game_colors;
typedef struct {
char name[100];
rooster *game_map;
} game_maps;
/*
* A proper modulo function.
* The one provided by c: '%' doesn't work according to the mathematical definition.
*/
int modulo(int number, int mod);
/*
* Displays the given grid with the given offset using ncurses.
*
* Input:
* gp: A pointer to the grid.
*
* 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);
/*
* Displays the given grid with ncurses.
*
* Input:
* gp: A pointer to the grid.
*
* Side effect:
* The console is cleared and the grid is printed.
*/
void show_grid(const rooster *gp);
/*
* Updates a single location in the grid.
*
* Input:
* gp: A pointer to the grid.
* c: The character to update the location with.
* x: The x-coordinate of the spot you want to update.
* y: The y-coordinate of the spot you want to update.
*
* 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);
/*
* Display the ending screen that matches the end state of the grid.
*
* Input:
* gp: A pointer to the grid.
*
* Side Effects:
* The end of game screen gets displayed with a graceful exit.
*/
void game_exit_screen(rooster *gp);
/*
* Waits for you to press 'q' before exiting the game.
*
* Side effect: Prints "Press 'q' to exit." game to the console.
*/
void graceful_exit(void);
/*
* Initialize ncurses.
*
* This enables cbreak, noecho, hides the cursor and enables the extra keys.
* This also creates the color pairs needed for game_colors,
*/
void init_engine(void);
/*
* Cleanup ncurses.
*/
void cleanup_engine(void);
#endif //MINIGAME_MENU_GRID_GAME_ENGINE_H

View File

@@ -1,128 +0,0 @@
//
// Created by snapshot112 on 10/15/25.
//
#include "maze_runner.h"
#include <ncurses.h>
#include <stdlib.h>
#include "grid_game_engine.h"
#include "rooster.h"
/*
* Reads in the maze from the assets file.
*/
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.
FILE *fh = fopen("assets/maze.txt", "r");
if (fh == NULL) {
perror("loading maze");
exit(EXIT_FAILURE);
}
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");
exit(EXIT_FAILURE);
}
return rp;
}
/* 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.
*/
static void maze_runner_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, STATE_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, STATE_GEWONNEN);
break;
}
refresh();
}
}
/*
* Speelt het spel met een gegeven rooster tot de toestand niet langer
* AAN_HET_SPELEN is.
*/
static void speel_maze(rooster *rp) {
while (rooster_vraag_toestand(rp) == STATE_AAN_HET_SPELEN)
{
switch (getch()) {
case KEY_UP: // fallthrough
case 'w':
case 'W':
maze_runner_beweeg(rp, 0, -1);
break;
case KEY_DOWN: // fallthrough
case 's':
case 'S':
maze_runner_beweeg(rp, 0, 1);
break;
case KEY_LEFT: // fallthrough
case 'a':
case 'A':
maze_runner_beweeg(rp, -1, 0);
break;
case KEY_RIGHT: // fallthrough
case 'd':
case 'D':
maze_runner_beweeg(rp, 1, 0);
break;
case KEY_BACKSPACE:
rooster_zet_toestand(rp, STATE_QUIT);
break;
}
}
}
void maze_runner(void) {
// Voorbereiding.
rooster *rp = get_maze();
show_grid(rp);
rooster_zet_toestand(rp, STATE_AAN_HET_SPELEN);
// Game zelf.
speel_maze(rp);
// Exit game.
game_exit_screen(rp);
rooster_klaar(rp);
}

View File

@@ -1,35 +0,0 @@
/*
* Created by snapshot112 on 10/15/25.
*
* A minigame menu for games configured to run on the grid game engine.
*
* Please make sure to include and initialize the game engine before calling menu();
*/
#ifndef MINIGAME_MENU_MINIGAME_MENU_H
#define MINIGAME_MENU_MINIGAME_MENU_H
typedef enum {
GAME_MAZE_RUNNER = 0,
GAME_SNAKE = 1,
GAME_MINESWEEPER = 2,
GAME_QUIT = 3,
} game;
/*
* A minigame menu for games configured to run on the grid game engine.
*
* Please make sure to include and initialize the game engine before calling menu();
*
* Side Effects:
* Clears the console and uses it to display a minigame menu.
*
* Controls:
* 'w'/'arr_up: Next menu item.
* 's'/'arr_down': Previous menu item.
* 'f': Select current menu item.
* 'BACKSPACE': Exit the menu.
*/
void minigame_menu(void);
#endif //MINIGAME_MENU_MINIGAME_MENU_H

295
rooster.c
View File

@@ -1,295 +0,0 @@
#include "rooster.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
/*
* The rooster type this program is build around.
*/
typedef struct rooster_data {
char *rost;
int height;
int width;
toestand state;
} rooster;
rooster *rooster_maak(const char* input) {
int width = 0;
int height = 0;
const size_t len = strlen(input) / sizeof(char);
if (input == NULL || len == 0) {
return NULL;
}
for (int i = 0; input[i] != '\n'; i++) {
width++;
}
height = (int)len / (width + 1);
for (int i = 0; i < height; i = i + width + 1) {
if ((int)strcspn(&input[i], "\n") != width) {
return NULL;
}
}
const int grid_size = (width + 1) * height + 1;
rooster *rp = malloc(sizeof(rooster));
if (rp == NULL) {
return NULL;
}
rp->rost = malloc(grid_size * sizeof(char));
rp->height = height;
rp->width = width;
rp->state = STATE_BEGIN;
strcpy(rp->rost, input);
return rp;
}
/*
* 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) {
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,
STATE_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 STATE_VERLOREN;
}
void rooster_zet_toestand(rooster *rp, toestand t) {
if (rp != NULL) {
switch (t) {
case STATE_BEGIN:
rp->state = STATE_BEGIN;
break;
case STATE_AAN_HET_SPELEN:
rp->state = STATE_AAN_HET_SPELEN;
break;
case STATE_GEWONNEN:
rp->state = STATE_GEWONNEN;
break;
case STATE_VERLOREN:
rp->state = STATE_VERLOREN;
break;
case STATE_QUIT:
rp->state = STATE_QUIT;
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;
}
char *rooster_vraag_rij(const rooster *rp, int y) {
if (rp != NULL && rp->rost != NULL && rooster_bevat(rp, 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';
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);
char *grid = malloc(grid_memory);
if (grid == NULL) {
return NULL;
}
rooster *new_rooster = malloc(sizeof(*rp));
if (new_rooster == NULL) {
return NULL;
}
memcpy(grid, rp->rost, grid_memory);
memcpy(new_rooster, rp, sizeof(*rp));
new_rooster->rost = grid;
return new_rooster;
}
return NULL;
}
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);
}

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 *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(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

16
snake.c
View File

@@ -1,16 +0,0 @@
//
// Created by snapshot112 on 10/15/25.
//
#include "snake.h"
#include <ncurses.h>
#include "grid_game_engine.h"
void snake(void) {
clear();
mvprintw(0,0, "Snake has not yet been created");
graceful_exit();
refresh();
}

BIN
spel

Binary file not shown.

24
spel.c
View File

@@ -1,24 +0,0 @@
/*
* Naam: Jeroen Boxhoorn
* UvAnetID: 16333969
* Studie: BSc Informatica
*
* A minigame menu that lets you play games on the grid game engine.
*
* Currently the following games are included:
* - maze runner
* - snake
* - minesweeper
*/
#include "grid_game_engine.h"
#include "minigame_menu.h"
int main(void) {
init_engine();
// 5. Speel het spel.
minigame_menu();
cleanup_engine();
}

View File

@@ -1,7 +1,5 @@
/*
* Created by snapshot112 on 10/15/25.
*
* A game engine that can run and display games in square grids.
* Created by snapshot112 on 8/10/2025
*/
#include "grid_game_engine.h"
@@ -10,7 +8,9 @@
#include <ncurses.h>
#include <stdlib.h>
#include "rooster.h"
int same_coordinate(const coordinate a, const coordinate b) {
return a.x == b.x && a.y == b.y;
}
int modulo(const int number, const int mod) {
int result = number % mod;
@@ -20,23 +20,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 *rp) {
show_grid_on_offset(rp, 0, 0);
void show_grid(const grid *gp) {
show_grid_on_offset(gp, 0, 0);
}
void update_grid(rooster *rp, char c, int x, int y) {
if (rooster_plaats(rp, 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);
}
}
@@ -47,10 +47,8 @@ void update_grid(rooster *rp, char c, int x, int y) {
* Side Effect:
* Clears the console and prints the victory message.
*/
static void display_victory(void) {
clear();
mvprintw(2,5, "YOU WON!!!!!");
refresh();
static void display_victory(const coordinate location) {
mvaddstr(location.y, location.x, "YOU WON!!!!!");
}
/*
@@ -59,10 +57,8 @@ static void display_victory(void) {
* Side Effect:
* Clears the console and prints the GAME OVER message.
*/
static void display_loss(void) {
clear();
mvprintw(2,5, "GAME OVER");
refresh();
static void display_loss(const coordinate location) {
mvaddstr(location.y, location.x, "GAME OVER");
}
/*
@@ -71,10 +67,8 @@ static void display_loss(void) {
* Side Effect:
* Clears the console and prints the quit message.
*/
static void display_quit(void) {
clear();
mvprintw(2,5, "You quit the game");
refresh();
static void display_quit(const coordinate location) {
mvaddstr(location.y, location.x, "You quit the game");
}
/*
@@ -83,42 +77,60 @@ static void display_quit(void) {
* Side Effect:
* Clears the console and prints the hacker man message.
*/
static void display_hackerman(void) {
clear();
mvprintw(2,5, "The hacker man strikes again...");
refresh();
static void display_hackerman(const coordinate location) {
mvaddstr(location.y, location.x, "The hacker man strikes again...");
}
void graceful_exit(void) {
mvprintw(5, 5, "Press 'q' to exit.");
void graceful_exit(const coordinate message_location) {
mvaddstr(message_location.y, message_location.x, "Press ENTER to exit.");
while (1) {
switch (getch()) {
case 'q':
case 'Q':
case KEY_BACKSPACE:
case KEY_ESCAPE:
case KEY_ENTER:
case '\n':
return;
}
}
}
void game_exit_screen(rooster *gp) {
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();
display_victory(location);
break;
case STATE_VERLOREN:
display_loss();
display_loss(location);
break;
case STATE_QUIT:
display_quit();
display_quit(location);
break;
default:
display_hackerman();
display_hackerman(location);
}
graceful_exit();
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();
cbreak(); // So you can cancel the game with ctrl + c.
@@ -126,9 +138,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);
@@ -138,10 +147,8 @@ static void init_ncurses() {
init_pair(MAGENTA, COLOR_BLACK, COLOR_MAGENTA);
init_pair(YELLOW, COLOR_BLACK, COLOR_YELLOW);
init_pair(RED, COLOR_BLACK, COLOR_RED);
}
static void cleanup_ncurses() {
endwin();
clear();
}
void init_engine(void) {
@@ -149,5 +156,5 @@ void init_engine(void) {
}
void cleanup_engine(void) {
cleanup_ncurses();
endwin();
}

View File

@@ -0,0 +1,157 @@
/*
* Created by snapshot112 on 15/10/2025.
*
* A game engine build on top of a grid api to run and display games using ncurses.
*
* Please make sure to initialize the game engine before running any games.
*/
#ifndef MINIGAME_MENU_GRID_GAME_ENGINE_H
#define MINIGAME_MENU_GRID_GAME_ENGINE_H
#include "../grid/grid.h"
#define KEY_ESCAPE 27
typedef struct {
int x;
int y;
} coordinate;
// Start at 1 since the color 0 is reserved for no_color.
typedef enum {
BLACK = 1,
WHITE = 2,
BLUE = 3,
GREEN = 4,
CYAN = 5,
MAGENTA = 6,
YELLOW = 7,
RED = 8
} game_colors;
typedef struct {
char name[100];
grid *game_map;
} game_maps;
/*
* Checks if 2 coordinates are the same.
*
* Input:
* a: coordinate 1
* b: coordinate 2
*
* Returns:
* If they point to the same location: 1
* Otherwise: 0
*/
int same_coordinate(coordinate a, coordinate b);
/*
* A proper modulo function.
* The one provided by c: '%' doesn't work according to the mathematical definition.
*/
int modulo(int number, int mod);
/*
* Displays the given grid with the given offset using ncurses.
*
* Input:
* gp: A pointer to the grid.
*
* Side effect:
* The console is cleared and the grid is printed.
*/
void show_grid_on_offset(const grid *gp, int starting_x, int starting_y);
/*
* Displays the given grid with ncurses.
*
* Input:
* gp: A pointer to the grid.
*
* Side effect:
* The console is cleared and the grid is printed.
*/
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.
*
* Input:
* gp: A pointer to the grid.
* c: The character to update the location with.
* x: The x-coordinate of the spot you want to update.
* y: The y-coordinate of the spot you want to update.
*
* Side effect:
* The update gets applied both on the grid and on the screen.
*/
void update_grid(grid *gp, char c, int x, int y);
/*
* Display the ending screen that matches the end state of the grid.
*
* Input:
* gp: A pointer to the grid.
* coordinate: The location to show the ending screen.
*
* Side Effects:
* The end of game screen gets displayed with a graceful exit.
*/
void game_exit_message(const grid *gp, coordinate location);
/*
* Waits for you to press ENTER before exiting the game.
*
* Input:
* coordinate: The location to show the message.
*
* Side effect: Prints "Press ENTER to exit." game to the console.
*/
void graceful_exit(coordinate message_location);
/*
* 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.
*/
void init_engine(void);
/*
* Cleanup ncurses.
*/
void cleanup_engine(void);
#endif //MINIGAME_MENU_GRID_GAME_ENGINE_H

305
src/engine/grid/grid.c Normal file
View File

@@ -0,0 +1,305 @@
/*
* Created by snapshot112 on 2/10/2025
*/
#include "grid.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
/*
* The grid type this program is build around.
*/
typedef struct grid_data {
char *locations;
int height;
int width;
state state;
} grid;
/*
* Translate x and y coordinates to a location index on the grid.
*/
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);
if (input == NULL || len == 0) {
printf("invalid input\n");
return NULL;
}
for (int i = 0; input[i] != '\n'; i++) {
width++;
}
height = (int)len / (width + 1);
for (int i = 0; i < height; i = i + width + 1) {
if ((int)strcspn(&input[i], "\n") != width) {
printf("line %d was not %d wide\n", i, width);
return NULL;
}
}
const int grid_size = (width + 1) * height + 1;
grid *gp = malloc(sizeof(grid));
if (gp == NULL) {
return NULL;
}
gp->locations = malloc(grid_size * sizeof(char));
gp->height = height;
gp->width = width;
gp->state = STATE_BEGIN;
strcpy(gp->locations, input);
return gp;
}
/*
* Sets a grids width and height
*
* Input:
* fh: The stream to read the grid from.
* gp: A pointer to the grid whose width and height you want to set.
*
* Side effects:
* 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, grid *gp) {
while (getc(fh) != '\n') {
if (feof(fh)) {
return 0;
}
gp->width++;
}
if (gp->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) % (gp->width + 1) != 0) {
// Not all lines are the same width
return 0;
}
gp->height = (int)ftell(fh) / (int)sizeof(char) / (gp->width + 1);
fseek(fh, 0, SEEK_SET);
return 1;
}
grid *grid_create_from_file(FILE *fh) {
if (fh == NULL) {
return NULL;
}
grid temp_grid = {
NULL,
0,
0,
STATE_BEGIN
};
// Sets the width and height of the grid.
if (get_grid_sizes(fh, &temp_grid) != 1) {
// Unlogical file structure.
return NULL;
}
const int grid_size = (temp_grid.width + 1) * temp_grid.height + 1;
temp_grid.locations = malloc(grid_size * sizeof(char));
if (temp_grid.locations == NULL) {
return NULL;
}
// This makes the strncat() work.
temp_grid.locations[0] = '\0';
char *line = malloc((temp_grid.width + 2) * sizeof(char));
if (line == NULL) {
return NULL;
}
for (int i = 0; i < temp_grid.height; i++) {
if (fgets(line, temp_grid.width + 2, fh) == NULL) {
free(temp_grid.locations);
free(line);
return NULL;
}
// Validate that the line length is correct
if ((int)strcspn(line, "\n") != temp_grid.width) {
free(temp_grid.locations);
free(line);
return NULL;
}
// Width is without the newline at the end.
strncat(temp_grid.locations, line, temp_grid.width + 1);
}
free(line);
grid *return_grid = malloc(sizeof(temp_grid));
if (return_grid == NULL) {
return NULL;
}
memcpy(return_grid, &temp_grid, sizeof(temp_grid));
return return_grid;
}
state grid_fetch_state(const grid *gp) {
if (gp != NULL) {
return gp->state;
}
return STATE_VERLOREN;
}
void grid_put_state(grid *gp, const state t) {
if (gp != NULL) {
switch (t) {
case STATE_BEGIN:
gp->state = STATE_BEGIN;
break;
case STATE_AAN_HET_SPELEN:
gp->state = STATE_AAN_HET_SPELEN;
break;
case STATE_GEWONNEN:
gp->state = STATE_GEWONNEN;
break;
case STATE_VERLOREN:
gp->state = STATE_VERLOREN;
break;
case STATE_QUIT:
gp->state = STATE_QUIT;
break;
}
}
}
void grid_cleanup(grid *gp) {
if (gp != NULL) {
if (gp->locations != NULL)
{
free(gp->locations);
}
free(gp);
}
}
int grid_width(const grid *gp) {
if (gp == NULL) {
return 0;
}
return gp->width;
}
int grid_height(const grid *gp) {
if (gp == NULL) {
return 0;
}
return gp->height;
}
int grid_contains(const grid *gp, const int x, const int y) {
if (gp != NULL && gp->locations != NULL) {
if (x >= 0 && y >= 0 && x < gp->width && y < gp->height)
{
return 1;
}
}
return 0;
}
char grid_fetch(const grid *gp, const int x, const int y) {
if (gp != NULL && gp->locations != NULL && grid_contains(gp, x, y) == 1) {
return gp->locations[internal_location(gp, x, y)];
}
return '\0';
}
int grid_put(grid *gp, const int x, const int y, const char c) {
if (gp != NULL && gp->locations != NULL && grid_contains(gp, x, y) == 1) {
gp->locations[internal_location(gp, x, y)] = c;
return 1;
}
return 0;
}
char *grid_fetch_row(const grid *gp, const int y) {
if (gp != NULL && gp->locations != NULL && grid_contains(gp, 0, y) == 1) {
// we're going to remove the newline so this is long enough
char *row = malloc((gp->width + 1) * sizeof(char));
memcpy(row, &gp->locations[internal_location(gp, 0, y)], gp->width * sizeof(char));
row[gp->width] = '\0';
return row;
}
return NULL;
}
grid *grid_copy(const grid *gp) {
if (gp != NULL && gp->locations != NULL) {
const size_t grid_memory = ((gp->width + 1) * gp->height + 1) * sizeof(char);
char *locations = malloc(grid_memory);
if (locations == NULL) {
return NULL;
}
grid *new_grid = malloc(sizeof(*gp));
if (new_grid == NULL) {
return NULL;
}
memcpy(locations, gp->locations, grid_memory);
memcpy(new_grid, gp, sizeof(*gp));
new_grid->locations = locations;
return new_grid;
}
return NULL;
}
void grid_find(const grid *gp, const char c, int *x, int *y) {
if (gp == NULL || gp->locations == NULL) {
*x = -1;
*y = -1;
return;
}
const char search[2] = {c};
const int char_index = (int)strcspn(gp->locations, search);
if (gp->locations[char_index] == '\0')
{
*x = -1;
*y = -1;
return;
}
*x = char_index % (gp->width + 1);
*y = char_index / (gp->width + 1);
}

174
src/engine/grid/grid.h Normal file
View File

@@ -0,0 +1,174 @@
/*
* Created by snapshot112 on 2/10/2025
*
* 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

@@ -0,0 +1,87 @@
*--------------------------------------------*
| |
| !!! 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. |
| |
| *________________* |
| | GENERAL | |
| *------------------------------------* |
| | | |
| | 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 | |
| *------------------------------------* |
| | | |
| | Move down: | |
| | 's', arrow_down | |
| | | |
| | Move up: | |
| | 'w', arrow_up | |
| | | |
| | Select: | |
| | 'f', enter, space bar | |
| | | |
| *------------------------------------* |
| |
| *-----------------* |
| | MAZE RUNNER | |
| *------------------------------------* |
| | | |
| | Move up: | |
| | 'w', arrow_up | |
| | | |
| | Move down: | |
| | 's', arrow_down | |
| | | |
| | Move right: | |
| | 'd', arrow_right | |
| | | |
| | Move left: | |
| | 'a', arrow_left | |
| | | |
| *------------------------------------* |
| |
| *-----------------* |
| | SNAKE | |
| *------------------------------------* |
| | | |
| | Turn up: | |
| | 'w', arrow_up | |
| | | |
| | Turn down: | |
| | 's', arrow_down | |
| | | |
| | Turn right: | |
| | 'd', arrow_right | |
| | | |
| | Turn left: | |
| | 'a', arrow_left | |
| | | |
| *------------------------------------* |
*--------------------------------------------*

36
src/games/manual/manual.c Normal file
View File

@@ -0,0 +1,36 @@
/*
* Created by snapshot112 on 10/17/2025
*/
#include "manual.h"
#include <ncurses.h>
#include "../../engine/grid_game_engine.h"
void manual(const coordinate display_location) {
erase();
FILE *fp = fopen("assets/manual.txt", "r");
if (fp == NULL) {
return;
}
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);
// Wait until ESCAPE or BACKSPACE is pressed.
timeout(200);
for (int ch = getch(); ch != KEY_ESCAPE && ch != KEY_BACKSPACE; 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);
}

27
src/games/manual/manual.h Normal file
View File

@@ -0,0 +1,27 @@
/*
* Created by snapshot112 on 10/17/2025
*
* Provides a way to display the minigame manual in the assets in game.
*/
#ifndef MINIGAME_MENU_MANUAL_H
#define MINIGAME_MENU_MANUAL_H
#include "../../engine/grid_game_engine.h"
/*
* An in game manual for the minigames menu.
*
* Please make sure to include and initialize the game engine before opening the manual
*
* Input:
* display_location: The location to display the manual.
*
* Side Effects:
* Clears the console and uses it to display the manual.
*
* Controls:
* Press ESCAPE or BACKSPACE to exit the manual.
*/
void manual(coordinate display_location);
#endif //MINIGAME_MENU_MANUAL_H

View File

@@ -0,0 +1,147 @@
/*
* Created by snapshot112 on 6/10/2025
*/
#include "maze_runner.h"
#include <ncurses.h>
#include <stdlib.h>
#include "../../engine/grid_game_engine.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 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;
}
grid *gp = grid_create_from_file(fh);
fclose(fh);
// Bepaal of het lezen van het rooster is gelukt.
if (gp == NULL) {
fprintf(stderr, "Kan rooster niet maken.\n");
return NULL;
}
return gp;
}
/* Voert de benodigde veranderingen in het rooster door als de speler in een
* bepaalde richting probeert te bewegen.
* Input:
* 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.
*
* Side effect: het rooster wordt aangepast op basis van de handeling van
* de speler.
*/
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 (player_position.y == -1) {
printf("Player not found!");
grid_put_state(gp, STATE_BEGIN);
return;
}
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 WALL:
break;
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 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 MAZE_EXIT:
update_grid(gp, EMPTY, player_position.x, player_position.y);
grid_put_state(gp, STATE_GEWONNEN);
break;
}
refresh();
}
}
/*
* 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(grid *gp) {
switch (getch()) {
case KEY_UP: // fallthrough
case 'w':
case 'W':
maze_runner_move(gp, 0, -1);
break;
case KEY_DOWN: // fallthrough
case 's':
case 'S':
maze_runner_move(gp, 0, 1);
break;
case KEY_LEFT: // fallthrough
case 'a':
case 'A':
maze_runner_move(gp, -1, 0);
break;
case KEY_RIGHT: // fallthrough
case 'd':
case 'D':
maze_runner_move(gp, 1, 0);
break;
case 'p':
case KEY_BACKSPACE:
case KEY_ESCAPE:
grid_put_state(gp, STATE_QUIT);
break;
}
}
void maze_runner(void) {
// Voorbereiding.
grid *gp = get_maze();
if (gp == NULL) {
return;
}
show_grid(gp);
grid_put_state(gp, STATE_AAN_HET_SPELEN);
// Game zelf.
while (grid_fetch_state(gp) == STATE_AAN_HET_SPELEN) {
speel_maze(gp);
}
// Exit game.
game_exit_message(gp, (coordinate){0, grid_height(gp) + 2});
grid_cleanup(gp);
}

View File

@@ -1,9 +1,11 @@
/*
* Created by snapshot112 on 6/10/2025
*
* Naam: Jeroen Boxhoorn
* UvAnetID: 16333969
* Studie: BSc Informatica
* Studie: BSC Informatica
*
* A game of maze runner configured to run on the grid game engine.
* A game of maze runner build on the grid game engine.
*
* Please make sure to include and initialize the game engine before calling maze_runner();
*
@@ -23,7 +25,7 @@
#define MINIGAME_MENU_MAZE_RUNNER_H
/*
* A game of maze runner configured to run on the grid game engine.
* A game of maze runner build on the grid game engine.
*
* Please make sure to include and initialize the game engine before calling maze_runner();
*
@@ -32,7 +34,7 @@
*
* Controls:
* use WSAD or arrow keys to move through the maze.
* Use BACKSPACE to exit the game early.
* Use BACKSPACE or ESCAPE to exit the game early.
*/
void maze_runner(void);

View File

@@ -1,16 +1,16 @@
//
// Created by snapshot112 on 10/15/25.
//
/*
* Created by snapshot112 on 15/10/2025
*/
#include "minesweeper.h"
#include <ncurses.h>
#include "grid_game_engine.h"
#include "../../engine/grid_game_engine.h"
void minesweeper() {
clear();
mvprintw(0,0, "Minesweeper has not yet been created");
graceful_exit();
graceful_exit((coordinate){0, 3});
refresh();
}

View File

@@ -1,18 +1,18 @@
/*
* Created by snapshot112 on 10/15/25.
* Created by snapshot112 on 15/10/2025
*
* A game of minesweeper runner configured to run on the grid game engine.
* A game of minesweeper build on the grid game engine.
*
* Please make sure to include and initialize the game engine before calling snake();
* Please make sure to include and initialize the game engine before calling minesweeper();
*/
#ifndef MINIGAME_MENU_MINESWEEPER_H
#define MINIGAME_MENU_MINESWEEPER_H
/*
* A game of minesweeper configured to run on the grid game engine.
* A game of minesweeper build on the grid game engine.
*
* Please make sure to include and initialize the game engine before calling snake();
* Please make sure to include and initialize the game engine before calling minesweeper();
*
* Side Effects:
* Clears the console and uses it to play a game of minesweeper.

View File

@@ -1,21 +1,29 @@
//
// Created by snapshot112 on 10/15/25.
//
/*
* Created by snapshot112 on 15/10/2025
*/
#include "minigame_menu.h"
#include <ncurses.h>
#include <stdlib.h>
#include "grid_game_engine.h"
#include "maze_runner.h"
#include "minesweeper.h"
#include "rooster.h"
#include "snake.h"
#include "../../engine/grid_game_engine.h"
#include "../manual/manual.h"
#include "../maze-runner/maze_runner.h"
#include "../minesweeper/minesweeper.h"
#include "../snake/snake.h"
#define AMOUNT_OF_MENU_OPTIONS 4
#define AMOUNT_OF_MENU_OPTIONS 5
static game SELECTED_GAME = GAME_MAZE_RUNNER;
typedef enum {
GAME_MANUAL = 0,
GAME_MAZE_RUNNER = 1,
GAME_SNAKE = 2,
GAME_MINESWEEPER = 3,
GAME_QUIT = 4,
} game;
static game SELECTED_GAME = GAME_MANUAL;
static int OFFSET_Y = 5;
static int OFFSET_X = 5;
@@ -26,8 +34,11 @@ static int OFFSET_X = 5;
* menu: A pointer to the menu grid.
* game: The game you want to launch.
*/
static void launch_game(rooster *menu, const game game) {
static void launch_game(const game game) {
switch (game) {
case GAME_MANUAL:
manual((coordinate){0,0});
break;
case GAME_MAZE_RUNNER:
maze_runner();
break;
@@ -53,15 +64,16 @@ static void launch_game(rooster *menu, 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_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);
@@ -81,8 +93,8 @@ static void menu_highlight(const rooster *menu) {
* Side Effects:
* Displays the menu
*/
static void show_menu(const rooster *menu) {
clear();
static void show_menu(const grid *menu) {
erase();
show_grid_on_offset(menu, OFFSET_X, OFFSET_Y);
menu_highlight(menu);
refresh();
@@ -118,7 +130,7 @@ static void menu_move(const int offset) {
* Side Effect:
* Changes the SELECTED_GAME as needed and launches selected games on select.
*/
static int navigate_menu(rooster *menu) {
static int navigate_menu(void) {
switch (getch()) {
case KEY_UP:
case 'w':
@@ -131,14 +143,17 @@ static int navigate_menu(rooster *menu) {
menu_move(1);
break;
case KEY_ENTER:
case '\n':
case 'f':
case 'F':
case ' ':
if (SELECTED_GAME == GAME_QUIT) {
return 1;
}
launch_game(menu, SELECTED_GAME);
launch_game(SELECTED_GAME);
break;
case KEY_BACKSPACE:
case KEY_ESCAPE:
return 1;
}
return 0;
@@ -150,24 +165,23 @@ static int navigate_menu(rooster *menu) {
* Output:
* A pointer to the menu grid.
*/
static rooster *initialize_menu(void) {
char menu[] = "Maze Runner\n"
static grid *initialize_menu(void) {
char menu[] = "How to play\n"
"Maze Runner\n"
" Snake \n"
"Minesweeper\n"
" Leave \n";
rooster *rp = rooster_maak(menu);
grid *rp = grid_create_from_string(menu);
return rp;
}
void minigame_menu(void) {
rooster *menu = initialize_menu();
grid *menu = initialize_menu();
while (true) {
show_menu(menu);
if (navigate_menu(menu) == 1) {
if (navigate_menu() == 1) {
break;
}
}
rooster_klaar(menu);
grid_cleanup(menu);
}

View File

@@ -0,0 +1,28 @@
/*
* Created by snapshot112 on 15/10/2025
*
* A minigame menu for games build on the grid game engine.
*
* Please make sure to include and initialize the game engine before calling menu();
*/
#ifndef MINIGAME_MENU_MINIGAME_MENU_H
#define MINIGAME_MENU_MINIGAME_MENU_H
/*
* A minigame menu for games build on the grid game engine.
*
* Please make sure to include and initialize the game engine before calling menu();
*
* Side Effects:
* Clears the console and uses it to display a minigame menu.
*
* Controls:
* 'w'/'arr_up': Next menu item.
* 's'/'arr_down': Previous menu item.
* 'f'/'ENTER': Select current menu item.
* 'BACKSPACE'/'ESC': Exit the menu.
*/
void minigame_menu(void);
#endif //MINIGAME_MENU_MINIGAME_MENU_H

449
src/games/snake/snake.c Normal file
View File

@@ -0,0 +1,449 @@
/*
* Created by snapshot112 on 15/10/2025
*/
#define _POSIX_C_SOURCE 199309
#include "snake.h"
#include <ncurses.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>
#include "../../engine/engine/grid_game_engine.h"
#define CELL_EMPTY ' '
#define CELL_FOOD '$'
#define CELL_WALL '#'
#define DIRECTION_COUNT 4
#define OFFSET_X 6
#define OFFSET_Y 3
typedef enum {
SNAKE_MOVE = 0,
SNAKE_EAT = 1,
SNAKE_DIE = 2
} snake_action;
typedef enum {
DIRECTION_UP = 0,
DIRECTION_RIGHT = 1,
DIRECTION_DOWN = 2,
DIRECTION_LEFT = 3
} direction;
// 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 grid *GRID;
static pthread_mutex_t SNAKE_MUTEX;
/*
* Create a snake body part.
*
* Input:
* dir: the direction the body part should point to.
*
* Output:
* a character representing that body part.
*/
static char get_body_part(const direction dir) {
return (char)(dir + '0');
}
/*
* Gets the direction of a body part.
*
* Input:
* body_part: A part of the snake's body.
*
* Output:
* The direction the next body part is pointing to.
*/
static direction get_body_part_direction(const char body_part) {
return (direction)(body_part - '0');
}
/*
* Create a grid for snake with a given height and width.
*
* Input:
* height: The height of the map.
* width: The width of the map.
*
* Returns:
* A pointer to the grid.
*/
static void create_grid(void) {
const int grid_size = (MAP_WIDTH + 1) * MAP_HEIGHT + 1;
char *map = malloc(grid_size * sizeof(char));
if (map == NULL) {
return;
}
for (int i = 1; i <= (MAP_WIDTH + 1) * MAP_HEIGHT; i++) {
// Also subtract the null terminator
const int bottom_line = i > grid_size - (MAP_WIDTH + 2);
const int top_line = i < MAP_WIDTH + 1;
const int line_position = modulo(i, MAP_WIDTH + 1);
const int line_start = line_position == 1;
const int line_end = line_position == MAP_WIDTH;
const int newline = line_position == 0;
if (newline) {
map[i - 1] = '\n';
} else if (top_line || bottom_line || line_start || line_end) {
map[i - 1] = CELL_WALL;
} else {
map[i - 1] = CELL_EMPTY;
}
}
map[grid_size - 1] = '\0';
GRID = grid_create_from_string(map);
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(void) {
coordinate empty_spots[MAP_HEIGHT * MAP_WIDTH];
int available_spots = 0;
for (int x = 0; x < MAP_WIDTH; x++) {
for (int y = 0; y < MAP_HEIGHT; y++) {
if (grid_fetch(GRID, x, y) == CELL_EMPTY) {
const coordinate new_spot = {x, y};
empty_spots[available_spots] = new_spot;
available_spots++;
}
}
}
const coordinate food_location = empty_spots[modulo(rand(), available_spots)];
grid_put(GRID, food_location.x, food_location.y, CELL_FOOD);
}
/*
* Setup the game(map)
*
* Output:
* A pointer to the game grid.
*
* 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};
// 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();
pthread_mutex_init(&SNAKE_MUTEX, NULL);
}
/*
* Checks what happens when the snake moves over a given char.
*
* Input:
* c: The char to check.
*
* Returns:
* 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(const char c) {
if (c == CELL_EMPTY) {
return SNAKE_MOVE;
}
if (c == CELL_FOOD) {
return SNAKE_EAT;
}
return SNAKE_DIE;
}
/*
* 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) {
grid_put_state(GRID, STATE_VERLOREN);
return;
}
if (action == SNAKE_MOVE) {
coordinate new_tail = SNAKE_TAIL;
switch (get_body_part_direction(grid_fetch(GRID, SNAKE_TAIL.x, SNAKE_TAIL.y))) {
case DIRECTION_UP:
new_tail.y--;
break;
case DIRECTION_RIGHT:
new_tail.x++;
break;
case DIRECTION_DOWN:
new_tail.y++;
break;
case DIRECTION_LEFT:
new_tail.x--;
break;
}
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.
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) {
generate_food();
}
}
/*
* 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 (grid_fetch_state(GRID) == STATE_AAN_HET_SPELEN) {
nanosleep(&timer, NULL);
pthread_mutex_lock(&SNAKE_MUTEX);
coordinate new_location = SNAKE_HEAD;
switch (CURRENT_DIRECTION) {
case DIRECTION_UP:
new_location.y--;
break;
case DIRECTION_RIGHT:
new_location.x++;
break;
case DIRECTION_DOWN:
new_location.y++;
break;
case DIRECTION_LEFT:
new_location.x--;
break;
}
update_snake(new_location);
show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y);
pthread_mutex_unlock(&SNAKE_MUTEX);
}
return NULL;
}
/*
* 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;
}
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);
}
/*
* 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);
const int c = getch();
pthread_mutex_lock(&SNAKE_MUTEX);
switch (c) {
case KEY_UP: // fallthrough
case 'w':
case 'W':
turn_snake(DIRECTION_UP);
break;
case KEY_DOWN: // fallthrough
case 's':
case 'S':
turn_snake(DIRECTION_DOWN);
break;
case KEY_LEFT: // fallthrough
case 'a':
case 'A':
turn_snake(DIRECTION_LEFT);
break;
case KEY_RIGHT: // fallthrough
case 'd':
case 'D':
turn_snake(DIRECTION_RIGHT);
break;
case KEY_BACKSPACE:
case KEY_ESCAPE:
grid_put_state(GRID, STATE_QUIT);
break;
}
pthread_mutex_unlock(&SNAKE_MUTEX);
}
return NULL;
}
/*
* 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;
}
// Show game.
erase();
show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y);
wait_for_start();
if (grid_fetch_state(GRID) == STATE_AAN_HET_SPELEN) {
// Create and start necessary threads.
pthread_t input_thread;
pthread_t game_tick_thread;
pthread_create(&input_thread, NULL, user_input, NULL);
pthread_create(&game_tick_thread, 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);
}
game_exit_message(GRID, MESSAGE_LOCATION);
snake_cleanup();
}

View File

@@ -1,7 +1,7 @@
/*
* Created by snapshot112 on 10/15/25.
* Created by snapshot112 on 15/10/2025
*
* A game of maze runner configured to run on the grid game engine.
* A game of maze runner build on the grid game engine.
*
* Please make sure to include and initialize the game engine before calling snake();
*/
@@ -10,7 +10,7 @@
#define MINIGAME_MENU_SNAKE_H
/*
* A game of snake configured to run on the grid game engine.
* A game of snake build on the grid game engine.
*
* Please make sure to include and initialize the game engine before calling snake();
*
@@ -19,7 +19,7 @@
*
* Controls:
* Use WSAD or arrow keys to redirect the snake.
* Use BACKSPACE to exit the game early.
* Use BACKSPACE or ESCAPE to exit the game early.
*/
void snake(void);

34
src/main.c Normal file
View File

@@ -0,0 +1,34 @@
/*
* Created by snapshot112 on 15/10/2025
*
* Naam: Jeroen Boxhoorn
* UvAnetID: 16333969
* Studie: BSC Informatica
*
* A minigame menu that lets you play games on the grid game engine.
*
* Currently the following games are included:
* - maze runner
* - snake
* - minesweeper
*
* A user manual can be found in the assets or by selected it in the menu using ENTER or 'f'.
*/
#include "engine/engine/grid_game_engine.h"
#include "games/minigame-menu/minigame_menu.h"
/*
* Play minigame menu.
*
* Side effect:
* Minigame menu starts.
*/
int main(void) {
init_engine();
// Speel het spel.
minigame_menu();
cleanup_engine();
}