From 30d4fc68d4ca54135503dfb5b761305452d20e52 Mon Sep 17 00:00:00 2001 From: mszczepa <marin.szczepanski@inria.fr> Date: Mon, 23 May 2022 13:26:13 +0200 Subject: [PATCH] Ajout d'un draft de documentation pour le moteur --- engines/fullgame/API.md | 221 ++++++++++++++++++++++++++++++++++++ levels/interrupteurs/main.c | 3 +- levels/interrupteurs/map.c | 2 + 3 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 engines/fullgame/API.md diff --git a/engines/fullgame/API.md b/engines/fullgame/API.md new file mode 100644 index 00000000..2af3a59b --- /dev/null +++ b/engines/fullgame/API.md @@ -0,0 +1,221 @@ +# Draft for the complete game engine API + +This is just a draft. + +## Including the library and common stuff +(Implemented) +Use `#include "agbdentures/agdbentures.h"`. + +The *bool exit_main_loop* is defined as static and global variable. +Use it in your main loop. It will be modified by some functions of the library. + +Two basic functions for success and defeat are defined: `level_success(void)` and +`void level_failure(void)`. They set *exit_main_loop* as true and display a message. +Feel free to write your own if you need something more complex. + +## Managing map and entities +(Implemented) + +You have access to the following types and definitions: +```c +typedef enum { + UP, + DOWN, + LEFT, + RIGHT, + UNKNOWN +} direction; + +typedef struct { + char * name; // shop, overworld, dungeon_Nth_level... + tile * floor; // immobile floor + entity * entities; // ennemies, NPCs, items, switches... anything used by events + int width; // map size + int height; + int player_x; // player position + int player_y; + int player_direction; +} map; + +typedef struct { + int id; // identifier to display the tile + bool obstacle; // whether the player can move on it or not +} tile; + +typedef struc { + int id; // identifier to display and use in events + int category; // used to group in events, like monster, NPC... + void * stats; // any property the entity may have (life points, state...) +} entity; +``` + +For pEdaGoGIcaL PUrpOSeS, the maps are managed using a stack. +This means it's super cool for creating a donjon, but terribly bad +if you have any cyclic path in your world. + +**`void init_map_stack(void)`** +At the beginning, you have to initialize the map stack, by calling `init_map_stack(void)` once. +Don't call other map functions before that. + +**`map * load_map(const char * name, int width, int height, int player_x, int player_y, unsigned player_direction)`** +Allocates memory for a map, which becomes the current map. +After creating the map with this function, you have to set yourself its values for *floor* +and *entities*. That's because these are too game-dependant. +But the memory for these two is already allocated. + +**`map * unload_map(void)`** +Frees the allocated memory of a map, and the previous map +in the stack becomes the current map. Returns NULL if the unloaded +map was the last in the stack. +Also frees the memory for *floor* and *entities* (and *entities->stats*). + +**`map * current_map(void)`** +Returns the current map, which is on top of the stack. +Returns NULL if there is no map in the stack. + +**`void free_map_stack(void)`** +Call `unload_map(void)` for each map. +After that the map stack is the same as after calling `ini_map_stack(void)` at the beginning. + +**`int coord_idx(int y, int x)`** +Utilitary function, returns the number *i* such that *current_map()->floor[i]* +and *current_map()->entities[i]* correspond to the coordinates (*x*, *y*). + +**`int player_idx(void)`** +Same but using *current_map()->player_x* and *current_map()->player_y*. + +## Managing inputs +(Not implemented) + +The following type is defined: +```c +typedef struct Command { + char* command_buffer; // the name of the command + unsigned n_args; // a number of optional arguments (can be 0) + char** args; // the arguments, as many as n_args (NULL if n_args is 0) +} command; +``` + +**`bool init_inputs(const char * filename)`** +If *filename* is an empty string "", the inputs will be prompted to the user *via* the standard input. +If *filename* is valid, the inputs will be read in this file (one per line, end with a blank line). +So each input in an input file must be written like `<input> [args]\n`. +Example of an input file (notice the blank line at the end): +``` +UP +RIGHT_N 5 +UP +ATTACK + +``` +It should be possible to switch mode or to change the input file during the program execution, +by calling this function again. +Returns true on success, false on failure (if you gave a non-empty *filename* but the +file could not be opened). + +**`command * get_next_input(void)`** +Allocates memory for the next user input and returns it. +Blocking until the player gives an input, +because the game engine is simplistic and turn-based. +Returns NULL if inputs are being read from a file but the end of it has been reached. + +**`void free_input(command * i)`** +Frees the allocated memory of an input. To avoid memory leaks +you should call this once for each call to `get_next_input(void)`. + +**`command * get_and_free_input(command * i)`** +For your convenience, this function does exactly the same as `get_next_input(void)` +but in addition it will free *i* if this parameter is not NULL. +Setting *i* as NULL does the same exact thing is calling `get_next_input(void)`. + +If you are reading inputs from a file, you don't have to worry about the memory allocation +for the file, you only care about the inputs, like in prompt mode. +But you have to manage the case where `get_new_input(void)` returns NULL, indicating the end of +the file has been reached. Moreover, if you stop your program before the end of the file has +been reached, some memory will be lost. You can avoid this by adding, for example, after your main loop: +```c +while (!get_new_input()) {} +``` + +## Managing events +(Implemented) + +**`void add_event(void (* event) (void))`** +Adds an event to the end of the list. *event* must be a pointer to a function `void event(void)`. +If *event* is already in the list, nothing happens. + +**`void remove_event(void (* event) (void))`** +Removes the given *event* from the list. +No effect if *event* is not in the list. + +**`void remove_all_events(void)`** +Removes all events from the list. Please call this at the end of +your program to free all the memory allocated to events. + +**`void apply_events(void)`** +Calls all *event* functions added by `add_event(void (* event) (void))`, +in a fixed order: the events added **last** are called **first**. + +Inside a event function, you can call `add_event(void (* event) (void))`, +`remove_event(void (* event) (void))` (even on itself) and `remove_all_events(void)`. +In theory you can call `apply_events(void)`, but this will create +infinite recursion if the function calling it is also in the event list. + +## Functions given for convenience +todo: monsters and NPC bases +(Implemented except monsters and NPC) + +**`void up(void)`** +**`void down(void)`** +**`void left(void)`** +**`void right(void)`** +Moves the player in the given direction and change its orientation. +Displays a message if the player can't move because of an obstacle. + +**`void up_n(unsigned n)`** +**`void down_n(unsigned n)`** +**`void left_n(unsigned n)`** +**`void right_n(unsigned n)`** +Moves *n* times in a given direction. + +**`void turn_left(void)`** +**`void turn_right(void)`** +**`void forward(void)`** +**`void forward_n(unsigned n)`** +Legacy functions to have the player direction change by a quarter of a turn, +or to have the player move forward, depending of the current direction faced. + +All these functions will modify the values *player_x*, *player_y* and *player_direction* +of the current map. +Functions `*_n(unsigned n)` will call `apply_events(void)` n - 1 times, +so the main loop stays clean (see [Main Loop](#main-loop)). + +Talking, attacking, interacting depend of your game. Code them yourself. + +## Inventory +todo + +## Main Loop + +The (Holy) Main Loop should look like this: +```c +// before: all initialization + +while (!exit_main_loop) { + command = get_next_input(); + + apply_input(command); + + if (!exit_main_loop) + apply_events(); + + free_input(command); +} + +// after: all frees +``` +You have to write `apply_input(input * command)` yourself, but it's quite easy. + +Here we have the assumption that the game is turn-based, so nothing happens as long as the player doesn't give any input. For real-time games, the call to `get_next_input(void)` will generaly be at the end, and a timer determines when the next frame occurs, whether there has been an input or not. But this game engine is simplistic and turn-based, so `get_next_input(void)` is blocking. + +Don't forget to free all the allocated memory at the end, notably by calling `remove_all_events(void)` and `free_map_stack(void)`. diff --git a/levels/interrupteurs/main.c b/levels/interrupteurs/main.c index 8451f861..a2ee2b9d 100644 --- a/levels/interrupteurs/main.c +++ b/levels/interrupteurs/main.c @@ -74,8 +74,9 @@ void apply_input(Command * c) { int main() { CommandList inputs; + char * filename = "input.txt"; - if (read_input_file("input.txt", &inputs)) { + if (read_input_file(filename, &inputs)) { printf("Error while reading the inputs!\n"); free_commands(&inputs); exit(1); diff --git a/levels/interrupteurs/map.c b/levels/interrupteurs/map.c index 2572430a..f2f0371f 100644 --- a/levels/interrupteurs/map.c +++ b/levels/interrupteurs/map.c @@ -11,6 +11,7 @@ interactable * create_switch(int x, int y, int n) { // @AGDB always return the same switch, so they all light up at once interactable * swi = malloc(sizeof(interactable)); #ifdef template + // @AGDB this creates memory leak because the malloc above is never free'd swi = &template_swi; /* copy template */ #else swi->state = DEACTIVATED; @@ -141,6 +142,7 @@ void unload_map (void) free(m->floor); free(m->name); + // @AGDB if the bug is stack_alloc, those free are invalid because m->inter[i] is on the stack for (int i = 0; i < m->width * m->height; i++) { if (m->inter[i] != NULL) free(m->inter[i]); -- GitLab