forked from snapshot112/minigame-menu
wip refactoring file structure
This commit is contained in:
87
games/manual/assets/manual.txt
Normal file
87
games/manual/assets/manual.txt
Normal 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
games/manual/manual.c
Normal file
36
games/manual/manual.c
Normal 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
games/manual/manual.h
Normal file
27
games/manual/manual.h
Normal 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
|
||||
13
games/maze-runner/assets/maze.txt
Normal file
13
games/maze-runner/assets/maze.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
#######################
|
||||
#*# #
|
||||
# # $ ########### #
|
||||
# ######## #
|
||||
# # ############
|
||||
# ###### # #
|
||||
# # # ############ #
|
||||
# #### # # #
|
||||
# # ##### # XXXXXX#
|
||||
# XXX# # #
|
||||
# #########XXXXXXX #
|
||||
# X #
|
||||
#######################
|
||||
147
games/maze-runner/maze_runner.c
Normal file
147
games/maze-runner/maze_runner.c
Normal 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);
|
||||
}
|
||||
41
games/maze-runner/maze_runner.h
Normal file
41
games/maze-runner/maze_runner.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Created by snapshot112 on 6/10/2025
|
||||
*
|
||||
* Naam: Jeroen Boxhoorn
|
||||
* UvAnetID: 16333969
|
||||
* Studie: BSC Informatica
|
||||
*
|
||||
* 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();
|
||||
*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
#ifndef MINIGAME_MENU_MAZE_RUNNER_H
|
||||
#define MINIGAME_MENU_MAZE_RUNNER_H
|
||||
|
||||
/*
|
||||
* 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();
|
||||
*
|
||||
* Side Effects:
|
||||
* Clears the console and uses it to play a game of maze runner.
|
||||
*
|
||||
* Controls:
|
||||
* use WSAD or arrow keys to move through the maze.
|
||||
* Use BACKSPACE or ESCAPE to exit the game early.
|
||||
*/
|
||||
void maze_runner(void);
|
||||
|
||||
#endif //MINIGAME_MENU_MAZE_RUNNER_H
|
||||
16
games/minesweeper/minesweeper.c
Normal file
16
games/minesweeper/minesweeper.c
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Created by snapshot112 on 15/10/2025
|
||||
*/
|
||||
|
||||
#include "minesweeper.h"
|
||||
|
||||
#include <ncurses.h>
|
||||
|
||||
#include "../../engine/grid_game_engine.h"
|
||||
|
||||
void minesweeper() {
|
||||
clear();
|
||||
mvprintw(0,0, "Minesweeper has not yet been created");
|
||||
graceful_exit((coordinate){0, 3});
|
||||
refresh();
|
||||
}
|
||||
30
games/minesweeper/minesweeper.h
Normal file
30
games/minesweeper/minesweeper.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Created by snapshot112 on 15/10/2025
|
||||
*
|
||||
* A game of minesweeper build on the grid game engine.
|
||||
*
|
||||
* 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 build on the grid game engine.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Instructions:
|
||||
* use WSAD or arrow keys to select a grid square.
|
||||
* use SPACEBAR to open the current square.
|
||||
* use 'f' to mark/unmark a square.
|
||||
* use BACKSPACE to exit the game early.
|
||||
*
|
||||
* marked squares can't be opened.
|
||||
*/
|
||||
void minesweeper(void);
|
||||
|
||||
#endif //MINIGAME_MENU_MINESWEEPER_H
|
||||
187
games/minigame-menu/minigame_menu.c
Normal file
187
games/minigame-menu/minigame_menu.c
Normal file
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Created by snapshot112 on 15/10/2025
|
||||
*/
|
||||
|
||||
#include "minigame_menu.h"
|
||||
|
||||
#include <ncurses.h>
|
||||
#include <stdlib.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 5
|
||||
|
||||
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;
|
||||
|
||||
/*
|
||||
* Launch a game from the menu.
|
||||
*
|
||||
* Input:
|
||||
* menu: A pointer to the menu grid.
|
||||
* game: The game you want to launch.
|
||||
*/
|
||||
static void launch_game(const game game) {
|
||||
switch (game) {
|
||||
case GAME_MANUAL:
|
||||
manual((coordinate){0,0});
|
||||
break;
|
||||
case GAME_MAZE_RUNNER:
|
||||
maze_runner();
|
||||
break;
|
||||
case GAME_SNAKE:
|
||||
snake();
|
||||
break;
|
||||
case GAME_MINESWEEPER:
|
||||
minesweeper();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Highlight a valid menu option.
|
||||
*
|
||||
* Input:
|
||||
* menu: A pointer to the menu grid.
|
||||
* target: The menu option to highlight.
|
||||
* offset_x: The x offset of the menu.
|
||||
* offset_y: The y offset of the menu.
|
||||
*
|
||||
* Side effects:
|
||||
* 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 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 = grid_fetch_row(menu, SELECTED_GAME);
|
||||
mvprintw(OFFSET_Y + (int)SELECTED_GAME, OFFSET_X, "%s", row);
|
||||
free(row);
|
||||
|
||||
attroff(COLOR_PAIR(GREEN));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Show the menu screen.
|
||||
*
|
||||
* Input:
|
||||
* menu: A pointer to the menu grid.
|
||||
* default_selection: The starting selection in the menu.
|
||||
* offset_x: The x offset of the menu.
|
||||
* offset_y: The y offset of the menu.
|
||||
*
|
||||
* Side Effects:
|
||||
* Displays the menu
|
||||
*/
|
||||
static void show_menu(const grid *menu) {
|
||||
erase();
|
||||
show_grid_on_offset(menu, OFFSET_X, OFFSET_Y);
|
||||
menu_highlight(menu);
|
||||
refresh();
|
||||
}
|
||||
|
||||
/*
|
||||
* Select the game on a location determined by a given offset.
|
||||
* Negative values go up and positive values go down.
|
||||
* Out of bounds selections loop around.
|
||||
*
|
||||
* Input:
|
||||
* selected_game: The currently selected game.
|
||||
* offset: The amount offset the current selection by.
|
||||
*
|
||||
* Side effect:
|
||||
* The game on the location of the given offset will be selected.
|
||||
*/
|
||||
static void menu_move(const int offset) {
|
||||
SELECTED_GAME = modulo(SELECTED_GAME + offset, AMOUNT_OF_MENU_OPTIONS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Navigate through the menu.
|
||||
*
|
||||
* Input:
|
||||
* menu: A pointer to the menu grid.
|
||||
*
|
||||
* Output: A code that reflects the current menu state.
|
||||
* 0: Continue running.
|
||||
* 1: Exit the menu.
|
||||
*
|
||||
*
|
||||
* Side Effect:
|
||||
* Changes the SELECTED_GAME as needed and launches selected games on select.
|
||||
*/
|
||||
static int navigate_menu(void) {
|
||||
switch (getch()) {
|
||||
case KEY_UP:
|
||||
case 'w':
|
||||
case 'W':
|
||||
menu_move(-1);
|
||||
break;
|
||||
case KEY_DOWN:
|
||||
case 's':
|
||||
case 'S':
|
||||
menu_move(1);
|
||||
break;
|
||||
case KEY_ENTER:
|
||||
case '\n':
|
||||
case 'f':
|
||||
case 'F':
|
||||
case ' ':
|
||||
if (SELECTED_GAME == GAME_QUIT) {
|
||||
return 1;
|
||||
}
|
||||
launch_game(SELECTED_GAME);
|
||||
break;
|
||||
case KEY_BACKSPACE:
|
||||
case KEY_ESCAPE:
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create the menu grid.
|
||||
*
|
||||
* Output:
|
||||
* A pointer to the menu grid.
|
||||
*/
|
||||
static grid *initialize_menu(void) {
|
||||
char menu[] = "How to play\n"
|
||||
"Maze Runner\n"
|
||||
" Snake \n"
|
||||
"Minesweeper\n"
|
||||
" Leave \n";
|
||||
grid *rp = grid_create_from_string(menu);
|
||||
return rp;
|
||||
}
|
||||
|
||||
void minigame_menu(void) {
|
||||
grid *menu = initialize_menu();
|
||||
while (true) {
|
||||
if (navigate_menu() == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
grid_cleanup(menu);
|
||||
}
|
||||
28
games/minigame-menu/minigame_menu.h
Normal file
28
games/minigame-menu/minigame_menu.h
Normal 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
games/snake/snake.c
Normal file
449
games/snake/snake.c
Normal 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/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();
|
||||
}
|
||||
26
games/snake/snake.h
Normal file
26
games/snake/snake.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Created by snapshot112 on 15/10/2025
|
||||
*
|
||||
* A game of maze runner build on the grid game engine.
|
||||
*
|
||||
* Please make sure to include and initialize the game engine before calling snake();
|
||||
*/
|
||||
|
||||
#ifndef MINIGAME_MENU_SNAKE_H
|
||||
#define MINIGAME_MENU_SNAKE_H
|
||||
|
||||
/*
|
||||
* A game of snake build on the grid game engine.
|
||||
*
|
||||
* Please make sure to include and initialize the game engine before calling snake();
|
||||
*
|
||||
* Side Effects:
|
||||
* Clears the console and uses it to play a game of snake.
|
||||
*
|
||||
* Controls:
|
||||
* Use WSAD or arrow keys to redirect the snake.
|
||||
* Use BACKSPACE or ESCAPE to exit the game early.
|
||||
*/
|
||||
void snake(void);
|
||||
|
||||
#endif //MINIGAME_MENU_SNAKE_H
|
||||
Reference in New Issue
Block a user