first basic build, pathfinding, maze generation, enemy+loot spawning, no memleaks, no major bugs

AuthorKonata <konata@posteo.jp>
Date
Commit5aaf1fdf10e092087ede7d34c19a12153239c485
Parent5dd6f78
15 files changed, 1651 insertions(+), 9 deletions(-)
▾A.gitignore
@@ -0,0 +1,3 @@
.kdev4
heaptrack*
build/
▾MCMakeLists.txt
@@ -1,5 +1,21 @@
project(orcsmasher)
cmake_minimum_required(VERSION 3.0)
add_executable(orcsmasher main.c)
project(orcsmasher LANGUAGES C)
install(TARGETS orcsmasher RUNTIME DESTINATION bin)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wpedantic -Wno-sign-compare")
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Og -g -fsanitize=address")
endif()
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
include_directories(include)
file(GLOB SOURCES "src/*.c")
set(CURSES_NEED_NCURSES TRUE)
set(CURSES_NEED_WIDE TRUE)
find_package(Curses REQUIRED)
include_directories(${CURSES_INCLUDE_DIR})
add_executable(orcsmasher ${SOURCES})
target_link_libraries(orcsmasher ${CURSES_LIBRARIES} panelw)
▾Ainclude/algorithm.h
@@ -0,0 +1,15 @@
#ifndef ALGORITHM_H
#define ALGORITHM_H
#include <stddef.h>
#include <game.h>
struct game make_maze(size_t y, size_t x);
typedef darray(struct point) pointarr;
//algorithm terminates with no path if target is not found within maxdist
//result contains target as well as the source point
//so even if next to the target, the result arr is at least 2 items big
pointarr astar(struct game *g, struct point start, struct point target, size_t maxdist);
#endif //ALGORITHM_H
▾Ainclude/config.h
@@ -0,0 +1,17 @@
#ifndef CONFIG_H
#define CONFIG_H
#define AGGRO_RANGE 4
#define GAME_MIN_Y 10
#define GAME_MIN_X 10
#define GAME_MAX_Y 30
#define GAME_MAX_X 30
#define MIN_ENEMIES 5
#define MAX_ENEMIES 15
#define MIN_LOOT 5
#define MAX_LOOT 15
#endif //COFNIG_H
▾Ainclude/darray.h
@@ -0,0 +1,362 @@
/*
* Copyright (C) 2011 Joseph Adams <joeyadams3.14159@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef CCAN_DARRAY_H
#define CCAN_DARRAY_H
#include <stdlib.h>
#include <string.h>
/*
* SYNOPSIS
*
* Life cycle of a darray (dynamically-allocated array):
*
* darray(int) a = darray_new();
* darray_free(a);
*
* struct {darray(int) a;} foo;
* darray_init(foo.a);
* darray_free(foo.a);
*
* Typedefs for darrays of common types:
*
* darray_char, darray_schar, darray_uchar
* darray_short, darray_int, darray_long
* darray_ushort, darray_uint, darray_ulong
*
* Access:
*
* T darray_item(darray(T) arr, size_t index);
* size_t darray_size(darray(T) arr);
* size_t darray_alloc(darray(T) arr);
* bool darray_empty(darray(T) arr);
*
* Insertion (single item):
*
* void darray_append(darray(T) arr, T item);
* void darray_prepend(darray(T) arr, T item);
* void darray_insert(darray(T) arr, size_t index, T item);
* void darray_push(darray(T) arr, T item); // same as darray_append
*
* Insertion (multiple items):
*
* void darray_append_items(darray(T) arr, T *items, size_t count);
* void darray_prepend_items(darray(T) arr, T *items, size_t count);
*
* void darray_appends(darray(T) arr, [T item, [...]]);
* void darray_prepends(darray(T) arr, [T item, [...]]);
*
* // Same functionality as above, but does not require typeof.
* void darray_appends_t(darray(T) arr, #T, [T item, [...]]);
* void darray_prepends_t(darray(T) arr, #T, [T item, [...]]);
*
* Removal:
*
* T darray_pop(darray(T) arr | darray_size(arr) != 0);
* T* darray_pop_check(darray(T*) arr);
* void darray_remove(darray(T) arr, size_t index);
*
* Replacement:
*
* void darray_from_items(darray(T) arr, T *items, size_t count);
* void darray_from_c(darray(T) arr, T c_array[N]);
*
* String buffer:
*
* void darray_append_string(darray(char) arr, const char *str);
* void darray_append_lit(darray(char) arr, char stringLiteral[N+1]);
*
* void darray_prepend_string(darray(char) arr, const char *str);
* void darray_prepend_lit(darray(char) arr, char stringLiteral[N+1]);
*
* void darray_from_string(darray(T) arr, const char *str);
* void darray_from_lit(darray(char) arr, char stringLiteral[N+1]);
*
* Size management:
*
* void darray_resize(darray(T) arr, size_t newSize);
* void darray_resize0(darray(T) arr, size_t newSize);
*
* void darray_realloc(darray(T) arr, size_t newAlloc);
* void darray_growalloc(darray(T) arr, size_t newAlloc);
*
* void darray_make_room(darray(T) arr, size_t room);
*
* Traversal:
*
* darray_foreach(T *&i, darray(T) arr) {...}
* darray_foreach_reverse(T *&i, darray(T) arr) {...}
*
* Except for darray_foreach, darray_foreach_reverse, and darray_remove,
* all macros evaluate their non-darray arguments only once.
*/
/*** Life cycle ***/
#define darray(type) struct {type *item; size_t size; size_t alloc;}
#define darray_new() {0,0,0}
#define darray_init(arr) do {(arr).item=0; (arr).size=0; (arr).alloc=0;} while(0)
#define darray_free(arr) do {free((arr).item);} while(0)
/*
* Typedefs for darrays of common types. These are useful
* when you want to pass a pointer to an darray(T) around.
*
* The following will produce an incompatible pointer warning:
*
* void foo(darray(int) *arr);
* darray(int) arr = darray_new();
* foo(&arr);
*
* The workaround:
*
* void foo(darray_int *arr);
* darray_int arr = darray_new();
* foo(&arr);
*/
typedef darray(char) darray_char;
typedef darray(signed char) darray_schar;
typedef darray(unsigned char) darray_uchar;
typedef darray(short) darray_short;
typedef darray(int) darray_int;
typedef darray(long) darray_long;
typedef darray(unsigned short) darray_ushort;
typedef darray(unsigned int) darray_uint;
typedef darray(unsigned long) darray_ulong;
/*** Access ***/
#define darray_item(arr, i) ((arr).item[i])
#define darray_size(arr) ((arr).size)
#define darray_alloc(arr) ((arr).alloc)
#define darray_empty(arr) ((arr).size == 0)
/*** Insertion (single item) ***/
#define darray_append(arr, ...) do { \
darray_resize(arr, (arr).size+1); \
(arr).item[(arr).size-1] = (__VA_ARGS__); \
} while(0)
#define darray_prepend(arr, ...) do { \
darray_resize(arr, (arr).size+1); \
memmove((arr).item+1, (arr).item, ((arr).size-1)*sizeof(*(arr).item)); \
(arr).item[0] = (__VA_ARGS__); \
} while(0)
#define darray_insert(arr, i, ...) do { \
size_t index_ = (i); \
darray_resize(arr, (arr).size+1); \
memmove((arr).item+index_+1, (arr).item+index_, ((arr).size-index_-1)*sizeof(*(arr).item)); \
(arr).item[index_] = (__VA_ARGS__); \
} while(0)
#define darray_push(arr, ...) darray_append(arr, __VA_ARGS__)
/*** Insertion (multiple items) ***/
#define darray_append_items(arr, items, count) do { \
size_t count_ = (count), oldSize_ = (arr).size; \
darray_resize(arr, oldSize_ + count_); \
memcpy((arr).item + oldSize_, items, count_ * sizeof(*(arr).item)); \
} while(0)
#define darray_prepend_items(arr, items, count) do { \
size_t count_ = (count), oldSize_ = (arr).size; \
darray_resize(arr, count_ + oldSize_); \
memmove((arr).item + count_, (arr).item, oldSize_ * sizeof(*(arr).item)); \
memcpy((arr).item, items, count_ * sizeof(*(arr).item)); \
} while(0)
#define darray_append_items_nullterminate(arr, items, count) do { \
size_t count_ = (count), oldSize_ = (arr).size; \
darray_resize(arr, oldSize_ + count_ + 1); \
memcpy((arr).item + oldSize_, items, count_ * sizeof(*(arr).item)); \
(arr).item[--(arr).size] = 0; \
} while(0)
#define darray_prepend_items_nullterminate(arr, items, count) do { \
size_t count_ = (count), oldSize_ = (arr).size; \
darray_resize(arr, count_ + oldSize_ + 1); \
memmove((arr).item + count_, (arr).item, oldSize_ * sizeof(*(arr).item)); \
memcpy((arr).item, items, count_ * sizeof(*(arr).item)); \
(arr).item[--(arr).size] = 0; \
} while(0)
#if HAVE_TYPEOF
#define darray_appends(arr, ...) darray_appends_t(arr, typeof((*(arr).item)), __VA_ARGS__)
#define darray_prepends(arr, ...) darray_prepends_t(arr, typeof((*(arr).item)), __VA_ARGS__)
#endif
#define darray_appends_t(arr, type, ...) do { \
type src_[] = {__VA_ARGS__}; \
darray_append_items(arr, src_, sizeof(src_)/sizeof(*src_)); \
} while(0)
#define darray_prepends_t(arr, type, ...) do { \
type src_[] = {__VA_ARGS__}; \
darray_prepend_items(arr, src_, sizeof(src_)/sizeof(*src_)); \
} while(0)
/*** Removal ***/
/* Warning: Do not call darray_pop on an empty darray. */
#define darray_pop(arr) ((arr).item[--(arr).size])
#define darray_pop_check(arr) ((arr).size ? darray_pop(arr) : NULL)
/* Warning, slow: Requires copying all elements after removed item. */
#define darray_remove(arr, i) do { \
size_t index_ = (i); \
if (index_ < arr.size-1) \
memmove(&(arr).item[index_], &(arr).item[index_+1], ((arr).size-1-index_)*sizeof(*(arr).item)); \
(arr).size--; \
} while(0)
/*** Replacement ***/
#define darray_from_items(arr, items, count) do {size_t count_ = (count); darray_resize(arr, count_); memcpy((arr).item, items, count_*sizeof(*(arr).item));} while(0)
#define darray_from_c(arr, c_array) darray_from_items(arr, c_array, sizeof(c_array)/sizeof(*(c_array)))
/*** String buffer ***/
#define darray_append_string(arr, str) do {const char *str_ = (str); darray_append_items(arr, str_, strlen(str_)+1); (arr).size--;} while(0)
#define darray_append_lit(arr, stringLiteral) do {darray_append_items(arr, stringLiteral, sizeof(stringLiteral)); (arr).size--;} while(0)
#define darray_prepend_string(arr, str) do { \
const char *str_ = (str); \
darray_prepend_items_nullterminate(arr, str_, strlen(str_)); \
} while(0)
#define darray_prepend_lit(arr, stringLiteral) \
darray_prepend_items_nullterminate(arr, stringLiteral, sizeof(stringLiteral) - 1)
#define darray_from_string(arr, str) do {const char *str_ = (str); darray_from_items(arr, str_, strlen(str_)+1); (arr).size--;} while(0)
#define darray_from_lit(arr, stringLiteral) do {darray_from_items(arr, stringLiteral, sizeof(stringLiteral)); (arr).size--;} while(0)
/*** Size management ***/
#define darray_resize(arr, newSize) darray_growalloc(arr, (arr).size = (newSize))
#define darray_resize0(arr, newSize) do { \
size_t oldSize_ = (arr).size, newSize_ = (newSize); \
(arr).size = newSize_; \
if (newSize_ > oldSize_) { \
darray_growalloc(arr, newSize_); \
memset(&(arr).item[oldSize_], 0, (newSize_ - oldSize_) * sizeof(*(arr).item)); \
} \
} while(0)
#define darray_realloc(arr, newAlloc) do { \
(arr).item = xrealloc((arr).item, ((arr).alloc = (newAlloc)) * sizeof(*(arr).item)); \
} while(0)
#define darray_growalloc(arr, need) do { \
size_t need_ = (need); \
if (need_ > (arr).alloc) \
darray_realloc(arr, darray_next_alloc((arr).alloc, need_)); \
} while(0)
#if HAVE_STATEMENT_EXPR==1
#define darray_make_room(arr, room) ({size_t newAlloc = (arr).size+(room); if ((arr).alloc<newAlloc) darray_realloc(arr, newAlloc); (arr).item+(arr).size; })
#endif
static inline size_t darray_next_alloc(size_t alloc, size_t need)
{
if (alloc == 0)
alloc = 1;
while (alloc < need)
alloc *= 2;
return alloc;
}
/*** Traversal ***/
/*
* darray_foreach(T *&i, darray(T) arr) {...}
*
* Traverse a darray. `i` must be declared in advance as a pointer to an item.
*/
#define darray_foreach(i, arr) \
for ((i) = &(arr).item[0]; (i) < &(arr).item[(arr).size]; (i)++)
/*
* darray_foreach_reverse(T *&i, darray(T) arr) {...}
*
* Like darray_foreach, but traverse in reverse order.
*/
#define darray_foreach_reverse(i, arr) \
for ((i) = &(arr).item[(arr).size]; (i)-- > &(arr).item[0]; )
#endif /* CCAN_DARRAY_H */
/*
darray_growalloc(arr, newAlloc) sees if the darray can currently hold newAlloc items;
if not, it increases the alloc to satisfy this requirement, allocating slack
space to avoid having to reallocate for every size increment.
darray_from_string(arr, str) copies a string to an darray_char.
darray_push(arr, item) pushes an item to the end of the darray.
darray_pop(arr) pops it back out. Be sure there is at least one item in the darray before calling.
darray_pop_check(arr) does the same as darray_pop, but returns NULL if there are no more items left in the darray.
darray_make_room(arr, room) ensures there's 'room' elements of space after the end of the darray, and it returns a pointer to this space.
Currently requires HAVE_STATEMENT_EXPR, but I plan to remove this dependency by creating an inline function.
The following require HAVE_TYPEOF==1 :
darray_appends(arr, item0, item1...) appends a collection of comma-delimited items to the darray.
darray_prepends(arr, item0, item1...) prepends a collection of comma-delimited items to the darray.\
Examples:
darray(int) arr;
int *i;
darray_appends(arr, 0,1,2,3,4);
darray_appends(arr, -5,-4,-3,-2,-1);
darray_foreach(i, arr)
printf("%d ", *i);
printf("\n");
darray_free(arr);
typedef struct {int n,d;} Fraction;
darray(Fraction) fractions;
Fraction *i;
darray_appends(fractions, {3,4}, {3,5}, {2,1});
darray_foreach(i, fractions)
printf("%d/%d\n", i->n, i->d);
darray_free(fractions);
*/
▾Ainclude/game.h
@@ -0,0 +1,180 @@
#ifndef GAME_H
#define GAME_H
#include <stddef.h>
#include <stdbool.h>
#include <assert.h>
#include <helper.h>
#include <darray.h>
#define MAXITEMS 10
extern unsigned m_id;
enum objtype {NOOBJ = 0, MONSTER, ITEM, WALL, TARGET};
enum itemtype {NOITEM = 0, EQUIP, USE};
struct equip
{
unsigned hp;
unsigned dmg;
bool isequipped;
};
struct use
{
unsigned restorehp;
};
struct item
{
const char *name;
enum itemtype type;
union
{
struct equip e;
struct use u;
}u;
};
struct monster
{
const char *name;
unsigned id;
unsigned current_hp;
unsigned max_hp;
unsigned dmg;
unsigned level;
unsigned xp;
darray(struct item) items;
bool isplayer;
};
static inline size_t item_len(const struct monster *m)
{
for(size_t i = 0; i < MAXITEMS; i++)
{
if(darray_item(m->items, i).type == NOITEM)
return i;
}
return MAXITEMS;
}
struct object
{
enum objtype type;
char symbol;
union
{
struct monster m;
struct item i;
} u;
};
struct game
{
struct object *map;
size_t xs, ys;
};
static inline struct object *game_at(const struct game *g, struct point p)
{
return &g->map[p.y * g->xs + p.x];
}
static inline struct game create_game(size_t y, size_t x)
{
struct game ret;
ret.map = xmalloc(x * y * sizeof * ret.map);
ret.xs = x;
ret.ys = y;
return ret;
}
static inline void delete_game(struct game *g)
{
for(size_t y = 0; y < g->ys; y++)
{
for(size_t x = 0; x < g->xs; x++)
{
if(game_at(g, (struct point){y,x})->type == MONSTER)
darray_free(game_at(g, (struct point){y,x})->u.m.items);
}
}
free(g->map);
}
enum action {MOVE, SPAWN, QUIT, INVENTORY, REDRAW, INVALID};
enum direction {UP,DOWN,LEFT,RIGHT};
struct choice
{
enum action a;
union
{
enum direction d;
}u;
};
#define MAX(x,y) (x >= y ? x : y)
#define MIN(x,y) (x <= y ? x : y)
#define ORC (struct object)\
{\
.type = MONSTER,\
.symbol = 'O',\
.u.m = {"Zog Zog", m_id++, 10, 10, 1, 5 , 0, darray_new(), false}\
}
#define EMPTYFIELD (struct object)\
{\
.type = NOOBJ,\
.symbol = ' ',\
}
#define WALLFIELD (struct object)\
{\
.type = WALL,\
.symbol = '#',\
}
static inline struct point relative(struct point p, enum direction d)
{
switch(d)
{
case UP:
return (struct point){p.y - 1, p.x};
case DOWN:
return (struct point){p.y + 1, p.x};
case LEFT:
return (struct point){p.y, p.x - 1};
case RIGHT:
return (struct point){p.y, p.x + 1};
default:
assert(false);
}
}
static inline bool in_bounds(struct game *g, struct point p)
{
if(p.y >= 0 && p.y < g->ys && p.x >= 0 && p.x < g->xs)
return true;
else
return false;
}
static inline void spawn_enemy(struct game *g)
{
while(true)
{
struct point p = {randrange(0, g->ys), randrange(0, g->xs)};
if(in_bounds(g, p) && game_at(g, p)->type == NOOBJ) {
*game_at(g, p) = ORC;
break;
}
}
}
#endif
▾Ainclude/helper.h
@@ -0,0 +1,70 @@
#ifndef HELPER_H
#define HELPER_H
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
struct point
{
int y;
int x;
};
static inline bool pointeq(struct point p1, struct point p2)
{
return p1.y == p2.y && p1.x == p2.x;
}
static inline unsigned int iabs(int i)
{
return i < 0 ? -i : i;
}
static inline int ipow(int base, int exp)
{
int result = 1;
for (;;)
{
if (exp & 1)
result *= base;
exp >>= 1;
if (!exp)
break;
base *= base;
}
return result;
}
static inline int randrange(int min, int max)
{
return rand() % (max + 1 - min) + min;
}
static inline void *xmalloc(size_t n)
{
void *ret = malloc(n);
if(!ret)
{
fprintf(stderr, "malloc failed\n");
abort();
}
return ret;
}
static inline void *xrealloc(void *ptr, size_t n)
{
void *ret = realloc(ptr, n);
if(!ret)
{
fprintf(stderr, "realloc failed\n");
abort();
}
return ret;
}
#endif //HELPER_H
▾Ainclude/items.h
@@ -0,0 +1,32 @@
#ifndef ITEMS_H
#define ITEMS_H
#include <game.h>
#define DAGGER_OF_DARKNESS *game_at(&g, (struct point){8,3}) = (struct object)\
{\
.type = ITEM,\
.symbol = 'T',\
.u.i = {"Dagger of Darkness", EQUIP, {{0, 2, 0}}}\
};
#define HP_POTION (struct object)\
{\
.type = ITEM,\
.symbol = 'p',\
.u.i = {"HP Potion", USE, {{5, 0, 0}}}\
};
static inline void spawn_item(struct game *g)
{
while(true)
{
struct point p = {randrange(0, g->ys), randrange(0, g->xs)};
if(in_bounds(g, p) && game_at(g, p)->type == NOOBJ) {
*game_at(g, p) = HP_POTION;
break;
}
}
}
#endif //ITEMS_H
▾Ainclude/loop.h
@@ -0,0 +1,7 @@
#ifndef LOOP_H
#define LOOP_H
void loop(struct game *g);
#endif //LOOP_H
▾Ainclude/tui.h
@@ -0,0 +1,25 @@
#ifndef TUI_H
#define TUI_H
#include <game.h>
#include <stdbool.h>
#include <stddef.h>
bool setup(); //setups screens, doesn't redaw contents
void printlog();
void logstr(const char *format, ...); //echoes printf string to the logwindow TODO: fix multiline log
void charclear();
void charprint(const char *format, ...); //echoes printf string to the charwindow
void log_scroll(enum direction d); //scrolls the log up or down
void draw_map(const struct game *g, struct point p); //draws the map around the player
bool messagebox(const char *str, int y, int x); //shows a centered messagebox of at least the given size
typedef bool(itemselectfn)(struct monster *m, size_t i); //returns true if cursor should be reset
void itemselect(struct monster *m, itemselectfn f); //provides an inventory screen, calls f when the space is pressed on an item
//TODO: create general select screen
extern size_t logindex;
extern size_t loglength;
extern char **logs;
#endif //TUI_H
▾Dmain.c
-6
@@ -1,6 +0,0 @@
#include <stdio.h>
int main(int argc, char **argv) {
printf("Hello World");
return 0;
}
▾Asrc/algorithm.c
@@ -0,0 +1,212 @@
#include <helper.h>
#include <stdbool.h>
#include <string.h>
#include <stddef.h>
#include <darray.h>
#include <algorithm.h>
#define index(x) ((x)*2+1)
#define indexpoint(p) ((struct point){index(p.y), index(p.x)})
#define unindex(x) (((x)-1)/2)
#define unindexpoint(p) ((struct point){unindex(p.y), unindex(p.x)})
#define UNVISITED '#'
#define VISITED ' '
#define WALL '#'
#define NOWALL ' '
typedef unsigned long size_t;
static char *maze_at(char *m, size_t xs, struct point p)
{
return &m[p.y*xs+p.x];
}
static bool maze_in_bounds(size_t ys, size_t xs, struct point p)
{
return p.y > 0 && p.y < ys-1 &&
p.x > 0 && p.x < xs-1;
}
struct game make_maze(size_t y, size_t x)
{
bool yeven = (y%2 == 0);
bool xeven = (x%2 == 0);
y += yeven; //it must be an uneven number to look fine
x += xeven;
struct game g = create_game(y, x);
char *maze = xmalloc(y * x + 1);
memset(maze, WALL, y * x);
maze[y * x] = '\0';
darray(struct point) stack = darray_new();
struct point current = {0, 0};
*maze_at(maze, x, indexpoint(current)) = VISITED;
while(true)
{
struct point nb[4] =
{
{index(current.y), index(current.x-1)},
{index(current.y), index(current.x+1)},
{index(current.y-1), index(current.x)},
{index(current.y+1), index(current.x)},
};
if((maze_in_bounds(y,x,nb[0]) && (*maze_at(maze,x,nb[0]) == UNVISITED)) ||
(maze_in_bounds(y,x,nb[1]) && (*maze_at(maze,x,nb[1]) == UNVISITED)) ||
(maze_in_bounds(y,x,nb[2]) && (*maze_at(maze,x,nb[2]) == UNVISITED)) ||
(maze_in_bounds(y,x,nb[3]) && (*maze_at(maze,x,nb[3]) == UNVISITED)))
{
int c;
darray_append(stack, current);
while(!maze_in_bounds(y,x,nb[c=randrange(0,3)]) || *maze_at(maze, x, nb[c]) != UNVISITED);
*maze_at(maze, x, nb[c]) = VISITED;
struct point choice = unindexpoint(nb[c]);
struct point diff = {choice.y-current.y, choice.x-current.x};
*maze_at(maze, x, (struct point){index(current.y)+diff.y, index(current.x)+diff.x}) = NOWALL;
current = choice;
} else if(!darray_empty(stack)) {
current = darray_pop(stack);
} else {
break;
}
}
for(size_t i = 0; i < y; i++)
{
for(size_t j = 0; j < x; j++)
{
if(*maze_at(maze, x, (struct point){i,j}) == NOWALL)
*game_at(&g, (struct point){i,j}) = EMPTYFIELD;
else
*game_at(&g, (struct point){i,j}) = WALLFIELD;
}
}
game_at(&g, (struct point){y-2,x-2})->symbol = '*';
game_at(&g, (struct point){y-2,x-2})->type = TARGET;
darray_free(stack);
free(maze);
return g;
}
struct node
{
struct node *parent;
struct point p;
size_t g;
size_t h;
size_t f;
};
pointarr astar(struct game *g, struct point start, struct point target, size_t maxdist)
{
darray(struct node*) open = darray_new();
darray(struct node*) closed = darray_new();
pointarr result = darray_new();
struct node *startnode = xmalloc(sizeof(*startnode));
startnode->p = start;
startnode->parent = NULL;
startnode->f = 0;
startnode->g = 0;
startnode->h = 0;
darray_append(open, startnode);
while(!darray_empty(open))
{
size_t currenti = 0;
struct node *currentnode = darray_item(open, currenti);
for(size_t i = 0; i < open.size; i++)
{
if(darray_item(open, i)->f < currentnode->f) {
currenti = i;
currentnode = darray_item(open, i);
}
}
darray_append(closed, darray_item(open, currenti));
darray_remove(open, currenti);
if(pointeq(currentnode->p, target))
{
struct node *this = currentnode;
darray_append(result, this->p);
while((this = this->parent) != NULL)
{
darray_append(result, this->p);
}
}
darray(struct point) children = darray_new();
struct point up = (struct point){currentnode->p.y-1, currentnode->p.x};
struct point down = (struct point){currentnode->p.y+1, currentnode->p.x};
struct point left = (struct point){currentnode->p.y, currentnode->p.x-1};
struct point right = (struct point){currentnode->p.y, currentnode->p.x+1};
if(game_at(g,left)->type == NOOBJ || game_at(g,left)->type == MONSTER)
darray_append(children, left);
if(game_at(g,right)->type == NOOBJ || game_at(g,right)->type == MONSTER)
darray_append(children, right);
if(game_at(g,up)->type == NOOBJ || game_at(g,up)->type == MONSTER)
darray_append(children, up);
if(game_at(g,down)->type == NOOBJ || game_at(g,down)->type == MONSTER)
darray_append(children, down);
for(size_t i = 0; i < children.size; i++)
{
struct point child = darray_item(children, i);
struct node **n;
darray_foreach(n, closed)
{
if(pointeq((*n)->p, child))
goto next;
}
if(maxdist != 0 && currentnode->g+1 > maxdist)
goto next;
darray_foreach(n, open)
{
if(pointeq((*n)->p, child) && currentnode->g+1 > (*n)->g)
goto next;
}
darray_foreach(n, closed)
{
if(pointeq((*n)->p, child) &&currentnode->g+1 > (*n)->g)
goto next;
}
struct node *childnode = xmalloc(sizeof(*childnode));
childnode->p = child;
childnode->parent = currentnode;
childnode->g = currentnode->g + 1;
childnode->h = ipow(childnode->p.y - target.y, 2) + ipow(childnode->p.x - target.x, 2);
childnode->f = childnode->g + childnode->h;
darray_append(open, childnode);
next:;
}
darray_free(children);
}
struct node **n;
darray_foreach(n, closed)
{
free(*n);
}
darray_free(open);
darray_free(closed);
return result;
}
▾Asrc/loop.c
@@ -0,0 +1,298 @@
#define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE_EXTENDED
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>
#include <unistd.h>
#include <ncurses.h>
#include <game.h>
#include <helper.h>
#include <tui.h>
#include <algorithm.h>
#include <config.h>
#define NOOBJ_OBJECT (struct object){.type = NOOBJ, .symbol = ' '}
unsigned m_id = 0;
struct choice get_input(void)
{
struct choice c;
start:;
int res = getch();
assert(res != ERR);
switch(res)
{
case 'w':
c.a = MOVE;
c.u.d = UP;
break;
case 'a':
c.a = MOVE;
c.u.d = LEFT;
break;
case 's':
c.a = MOVE;
c.u.d = DOWN;
break;
case 'd':
c.a = MOVE;
c.u.d = RIGHT;
break;
case 'f':
c.a = SPAWN;
break;
case 'q':
c.a = QUIT;
break;
case 'i':
c.a = INVENTORY;
break;
case KEY_RESIZE:
c.a = REDRAW;
break;
case KEY_UP:
log_scroll(UP);
goto start;
case KEY_DOWN:
log_scroll(DOWN);
goto start;
default:
c.a = INVALID;
}
return c;
}
static void addxp(struct monster *m, unsigned xp)
{
m->xp += xp;
if(m->xp >= m->level *100) {
m->xp -= m->level*100;
m->level++;
m->dmg++;
m->max_hp += 10;
m->current_hp += 10;
const char *str = "!You leveled up!";
messagebox(str, 1, strlen(str));
}
}
static void attack(struct object *src, struct object *target)
{
assert(src->type == MONSTER);
if(target->type == MONSTER) {
if(src->u.m.isplayer)
logstr("%s hit %s for %d damage", src->u.m.name, target->u.m.name, src->u.m.dmg);
if(src->u.m.dmg >= target->u.m.current_hp) {
logstr("%s killed %s", src->u.m.name ,target->u.m.name);
if(src->u.m.isplayer) {
addxp(&src->u.m, target->u.m.level*10);
} else {
messagebox("U done did dedden", 1, 0);
exit(0);
}
darray_free(target->u.m.items);
*target = NOOBJ_OBJECT;
} else {
target->u.m.current_hp -= src->u.m.dmg;
}
} else {
logstr("Target is not a monster or player");
}
}
static bool additem(struct monster *m, struct item *it)
{
darray_append(m->items, *it);
return true;
}
static void rmvitem(struct monster *m, size_t i)
{
darray_remove(m->items, i);
}
bool inventorycallback(struct monster *m, size_t i)
{
struct item *it = &darray_item(m->items,i);
if(it->type == USE) {
m->current_hp = MIN(m->max_hp, m->current_hp+it->u.u.restorehp);
rmvitem(m, i);
return true;
} else if(it->type == EQUIP) {
if(it->u.e.isequipped) {
m->max_hp -= it->u.e.hp;
m->current_hp = MAX(1, m->current_hp-it->u.e.hp);
m->dmg -= it->u.e.dmg;
} else {
m->max_hp += it->u.e.hp;
m->current_hp += it->u.e.hp;
m->dmg += it->u.e.dmg;
}
it->u.e.isequipped = !it->u.e.isequipped;
}
return false;
}
void inventory(struct monster *m)
{
itemselect(m, inventorycallback);
}
//TODO: player moves on target, create new map
struct point move_object(struct game *g, struct point src, struct point target)
{
if(game_at(g, target)->type == NOOBJ) {
*game_at(g, target) = *game_at(g, src);
*game_at(g, src) = NOOBJ_OBJECT;
return target;
} else if(game_at(g, target)->type == ITEM && game_at(g, src)->type == MONSTER) {
if(!additem(&game_at(g, src)->u.m, &game_at(g, target)->u.i)) {
logstr("Inventory is full");
return src;
}
*game_at(g, target) = *game_at(g, src);
*game_at(g, src) = NOOBJ_OBJECT;
return target;
} else {
return src;
}
}
static inline struct point attack_move(struct game *g, struct point src, struct point target)
{
struct object *t = game_at(g, target);
if(t->type == MONSTER) {
attack(game_at(g, src), t);
} else {
return move_object(g, src, target);
}
return src;
}
void run_ai(struct game *g, struct point player)
{
darray(unsigned) checked = darray_new();
for(size_t y = 0; y < g->ys; y++)
{
for(size_t x = 0; x < g->xs; x++)
{
pointarr res;
if(game_at(g, (struct point){y,x})->type == MONSTER && !game_at(g, (struct point){y,x})->u.m.isplayer) {
unsigned *id;
darray_foreach(id, checked)
{
if(*id == game_at(g, (struct point){y,x})->u.m.id)
goto next;
}
darray_append(checked, game_at(g, (struct point){y,x})->u.m.id);
res = astar(g, (struct point){y,x}, player, AGGRO_RANGE);
if(res.size >= 2) {
attack_move(g, (struct point){y,x}, darray_item(res, res.size-2));
} else {
struct point ps[4] =
{
{y, x-1},
{y, x+1},
{y-1, x},
{y+1, x}
};
move_object(g, (struct point){y,x}, ps[randrange(0,3)]);
}
darray_free(res);
}
next:;
}
}
darray_free(checked);
}
void loop(struct game *g)
{
/*
"\
███████╗██████╗ ██╗ ██╗ ███╗ ██╗███████╗ ██████╗██╗ ██╗ █████╗ ██████╗ ██████╗ ███████╗███╗ ███╗██╗ ██╗███████╗███╗ ██╗██████╗ \n\
██╔════╝██╔══██╗██║ ██║ ████╗ ██║██╔════╝██╔════╝██║ ██╔╝██╔══██╗██╔══██╗██╔════╝ ██╔════╝████╗ ████║██║ ██║██╔════╝████╗ ██║██╔══██╗\n\
███████╗██████╔╝███████║ ██╔██╗ ██║█████╗ ██║ █████╔╝ ███████║██████╔╝██║ ███╗█████╗ ██╔████╔██║██║ ██║█████╗ ██╔██╗ ██║██║ ██║\n\
╚════██║██╔══██╗██╔══██║ ██║╚██╗██║██╔══╝ ██║ ██╔═██╗ ██╔══██║██╔══██╗██║ ██║██╔══╝ ██║╚██╔╝██║██║ ██║██╔══╝ ██║╚██╗██║██║ ██║\n\
███████║██║ ██║██║ ██║ ██║ ╚████║███████╗╚██████╗██║ ██╗██║ ██║██║ ██║╚██████╔╝███████╗██║ ╚═╝ ██║╚██████╔╝███████╗██║ ╚████║██████╔╝\n\
"
*/
*game_at(g, (struct point){1,1}) = (struct object)
{
.type = MONSTER,
.symbol = 'P',
.u.m =
{
.id = -1,
.name = "Player",
.max_hp = 20,
.current_hp = 20,
.dmg = 2,
.level = 1,
.xp = 0,
.items = darray_new(),
.isplayer = true
}
};
struct point player = {1,1};
while(true)
{
charclear();
charprint("(づ。◕‿‿◕。)づ");
charprint("Name: %s", game_at(g, player)->u.m.name);
charprint("HP : %u/%u", game_at(g, player)->u.m.current_hp, game_at(g, player)->u.m.max_hp);
charprint("DMG: %u", game_at(g, player)->u.m.dmg);
charprint("LVL: %u", game_at(g, player)->u.m.level);
charprint("XP : %u", game_at(g, player)->u.m.xp);
draw_map(g, player);
struct choice c = get_input();
switch(c.a)
{
case INVALID:
logstr("Invalid input, try again");
continue;
case QUIT:
return;
case MOVE:;
struct point targetpos = relative(player, c.u.d);
if(!in_bounds(g, targetpos)) {
logstr("Target not in bounds");
continue;
}
player = attack_move(g, player, targetpos);
run_ai(g, player);
break;
case SPAWN:
spawn_enemy(g);
break;
case INVENTORY:
inventory(&game_at(g, player)->u.m);
break;
case REDRAW:
setup();
printlog();
break;
}
}
}
▾Asrc/main.c
@@ -0,0 +1,45 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>
#include <locale.h>
#include <time.h>
#include <helper.h>
#include <tui.h>
#include <loop.h>
#include <game.h>
#include <algorithm.h>
#include <config.h>
#include <items.h>
#include <unistd.h>
int main()
{
srand(time(NULL));
setup();
logstr("Game started");
struct game g = make_maze(randrange(GAME_MIN_Y, GAME_MAX_Y), randrange(GAME_MIN_X, GAME_MAX_X));
size_t num_enemies = randrange(MIN_ENEMIES, MAX_ENEMIES);
for(size_t i = 0; i < num_enemies; i++)
{
spawn_enemy(&g);
}
size_t num_items = randrange(MIN_LOOT, MAX_LOOT);
for(size_t i = 0; i < num_items; i++)
{
spawn_item(&g);
}
loop(&g);
delete_game(&g);
}
▾Asrc/tui.c
@@ -0,0 +1,366 @@
#include <tui.h>
#define NCURSES_WIDECHAR 1
#include <curses.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <time.h>
#include <locale.h>
#include <stdlib.h>
#include <stdarg.h>
#include <panel.h>
#include <string.h>
#include <helper.h>
static PANEL *stdpanel = NULL;
static WINDOW *logbox = NULL;
static WINDOW *logwin = NULL;
static WINDOW *charbox = NULL;
static WINDOW *charwin = NULL;
static WINDOW *mapbox = NULL;
static WINDOW *mapwin = NULL;
static bool initialized = false;
static bool setup_log(struct winsize w)
{
int ystart = 0;
int xstart = w.ws_col/2+1;
int ysize = w.ws_row;
int xsize = w.ws_col/2-1;
logbox = subwin(stdscr, ysize, xsize, ystart, xstart);
if(!logbox)
return false;
box(logbox,'|', '-');
wmove(logbox, 0,1);
wprintw(logbox, "Log");
logwin = subwin(logbox, ysize-2, xsize-2, ystart+1, xstart+1);
if(!logwin)
return false;
// scrollok(logwin, true);
return true;
}
static bool setup_char(struct winsize w)
{
int ystart = 0;
int xstart = 0;
int ysize = w.ws_row/3;
int xsize = w.ws_col/2;
charbox = subwin(stdscr, ysize, xsize, ystart, xstart);
if(!charbox)
return false;
box(charbox,'|', '-');
wmove(charbox, 0,1);
wprintw(charbox, "Character");
charwin = subwin(charbox, ysize-2, xsize-2, ystart+1, xstart+1);
if(!charwin)
return false;
return true;
}
static bool setup_map(struct winsize w)
{
int ystart = w.ws_row/3+1;
int xstart = 0;
int ysize = w.ws_row-w.ws_row/3-1;
int xsize = w.ws_col/2;
mapbox = subwin(stdscr, ysize, xsize, ystart, xstart);
if(!mapbox)
return false;
box(mapbox,'|', '-');
wmove(mapbox, 0,1);
wprintw(mapbox, "Map");
mapwin = subwin(mapbox, ysize-2, xsize-2, ystart+1, xstart+1);
if(!mapwin)
return false;
return true;
}
void quit(void)
{
endwin();
}
bool setup()
{
if(initialized) {
erase();
if(logwin != NULL)
delwin(logwin);
if(logbox != NULL)
delwin(logbox);
if(charwin != NULL)
delwin(charwin);
if(charbox != NULL)
delwin(charbox);
if(mapwin != NULL)
delwin(mapwin);
if(mapbox != NULL)
delwin(mapbox);
} else {
atexit(quit);
setlocale(LC_ALL,"");
stdscr = initscr();
stdpanel = new_panel(stdscr);
assert(stdpanel != NULL);
noecho();
cbreak();
curs_set(0);
keypad(stdscr, true);
initialized = true;
}
struct winsize w;
assert(ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1);
if(w.ws_col < 25 || w.ws_row < 10) {
endwin();
puts("Terminal too small");
abort();
}
if(!setup_char(w)) {
fprintf(stderr, "char\n");
return false;
}
if(!setup_log(w)) {
fprintf(stderr, "log\n");
return false;
}
if(!setup_map(w)) {
fprintf(stderr, "map\n");
return false;
}
update_panels();
doupdate();
return true;
}
size_t logindex = 0;
size_t loglength;
char **logs = NULL;
void printlog() //TODO: fix logs if they go multiline
{
werase(logwin);
wmove(logwin, 0, 0);
int ymax = getmaxy(logwin);
for(int i = 0; i < ymax && i+logindex < loglength; i++)
{
waddstr(logwin, logs[i+logindex]);
waddstr(logwin, "\n");
}
wrefresh(logwin);
}
void logstr(const char *format, ...)
{
static size_t capacity;
if(logs == NULL) {
logs = xmalloc(8*sizeof(*logs));
capacity = 8;
loglength = 0;
}
if(capacity == loglength++) {
logs = xrealloc(logs, (capacity*=2) * sizeof(*logs));
}
va_list ap;
va_start(ap, format);
va_list ap2;
va_copy(ap2, ap);
int bufsz = vsnprintf(NULL, 0, format, ap);
va_end(ap);
struct tm mytime = *localtime(&(time_t){time(NULL)});
logs[loglength-1] = xmalloc(bufsz+11+2); //11 == size needed for the time header;
strftime(logs[loglength-1], 11, "%T> ", &mytime);
vsprintf(logs[loglength-1]+10, format, ap2);
va_end(ap2);
if(loglength - logindex == getmaxy(logwin))
logindex++;
printlog();
}
void charclear()
{
werase(charwin);
wrefresh(charwin);
}
void charprint(const char* format, ...)
{
va_list ap;
va_start(ap, format);
vw_printw(charwin, format, ap);
wprintw(charwin, "\n");
wrefresh(charwin);
va_end(ap);
}
void log_scroll(enum direction d)
{
if(d == UP && logindex != 0)
logindex--;
else if(d == DOWN && logindex != loglength-1)
logindex++;
printlog();
wrefresh(logwin);
}
void draw_map(const struct game *g, struct point p)
{
int x, y;
getmaxyx(mapwin, y, x);
size_t starty = MAX(0, p.y-y/2);
size_t endy = MIN(starty + y, g->ys);
size_t startx = MAX(0, p.x-x/2);
size_t endx = MIN(startx + x, g->xs);
werase(mapwin);
int line = 0;
for(size_t i = starty; i < endy; i++)
{
wmove(mapwin, line++, 0);
for(size_t j = startx; j < endx; j++)
{
waddch(mapwin, game_at(g, (struct point){.y = i, .x = j})->symbol);
}
}
wrefresh(mapwin);
}
bool messagebox(const char *str, int y, int x)
{
int yorig = y, xorig = x;
struct winsize w;
assert(ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1);
const char *returnstr = "Press return to continue";
x = MAX(strlen(returnstr), x)+2;
y += 4;
if(y > w.ws_row || x > w.ws_col)
return false;
int ystart = w.ws_row/2-y/2;
int xstart = w.ws_col/2-x/2;
WINDOW *msgbox = newwin(y, x, ystart, xstart);
PANEL *msgboxpanel = new_panel(msgbox);
box(msgbox, '|', '-');
WINDOW *msgwin = newwin(y-2, x-2, ystart+1, xstart+1);
PANEL *msgwinpanel = new_panel(msgwin);
waddstr(msgwin, str);
waddstr(msgwin, "\n\n");
waddstr(msgwin, returnstr);
update_panels();
doupdate();
int c;
while((c = getch()) != '\n' && c != KEY_RESIZE);
del_panel(msgwinpanel);
del_panel(msgboxpanel);
delwin(msgbox);
delwin(msgwin);
update_panels();
doupdate();
if(c == KEY_RESIZE)
{
while(!setup());
return messagebox(str, yorig, xorig);
}
printlog(); //incase we resized
return true;
}
void itemselect(struct monster *m, itemselectfn f)
{
WINDOW *menu = newwin(0, 0, 0, 0);
PANEL *menupanel = new_panel(menu);
size_t i = 0; //we put the index before the label so we keep it between redraws
redraw:;
struct winsize w;
assert(ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1);
for(size_t i = 0; i < m->items.size; i++)
{
struct item *it = &darray_item(m->items, i);
if(it->type != NOITEM) {
if(it->type == EQUIP) {
mvwprintw(menu, i, 0, "%.*s (%c)", w.ws_col, it->name, it->u.e.isequipped ? '*' : ' ');
} else {
mvwprintw(menu, i, 0, "%.*s", w.ws_col, it->name);
}
} else {
mvwaddstr(menu, i, 0, "EMPTY");
}
}
mvwchgat(menu, i, 0, -1, A_REVERSE, 0, NULL);
update_panels();
doupdate();
int c;
while((c = getch()) != 'i' && c != 'q')
{
switch(c)
{
case ' ':
if(f(m, i))
i = 0;
werase(menu);
goto redraw; //items might have changed, so we redraw the list
break;
case 's':
if(i != (m->items.size == 0 ? 0 :m->items.size-1)) {
wchgat(menu , -1, A_NORMAL, 0, NULL);
mvwchgat(menu, ++i, 0, -1, A_REVERSE, 0, NULL);
}
break;
case 'w':
if(i != 0) {
wchgat(menu, -1, A_NORMAL, 0, NULL);
mvwchgat(menu, --i, 0, -1, A_REVERSE, 0, NULL);
}
break;
case KEY_RESIZE:
werase(menu);
goto redraw;
break;
}
update_panels();
doupdate();
}
del_panel(menupanel);
delwin(menu);
update_panels();
doupdate();
}