From ab32d51871248e0fbf6758a425eda2256712d519 Mon Sep 17 00:00:00 2001 From: citrons Date: Wed, 11 Oct 2023 23:58:12 -0500 Subject: initial commit --- Makefile | 19 ++++ die.h | 13 +++ generator.c | 68 ++++++++++++++ generator.h | 10 ++ main.c | 92 ++++++++++++++++++ random.c | 26 ++++++ random.h | 9 ++ save.c | 130 ++++++++++++++++++++++++++ save.h | 14 +++ world.c | 306 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ world.h | 94 +++++++++++++++++++ 11 files changed, 781 insertions(+) create mode 100644 Makefile create mode 100644 die.h create mode 100644 generator.c create mode 100644 generator.h create mode 100644 main.c create mode 100644 random.c create mode 100644 random.h create mode 100644 save.c create mode 100644 save.h create mode 100644 world.c create mode 100644 world.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b0f651d --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +SDL_HEADERS=/usr/include/SDL2 + +CFLAGS=-g -Wall -pedantic -std=c99 $$(sdl2-config --cflags) +LFLAGS=$$(sdl2-config --libs) + +findgame: main.o world.o random.o generator.o save.o + $(CC) -o $@ $^ $(LFLAGS) + +main.o: world.h random.h +world.o: world.h random.h generator.h save.h +generator.o: world.h random.h +save.o: world.h die.h + +.SUFFIXES: .c .o +.c.o: + $(CC) $(CFLAGS) -c -o $@ $< + +clean: + rm -f *.o findgame diff --git a/die.h b/die.h new file mode 100644 index 0000000..5048ced --- /dev/null +++ b/die.h @@ -0,0 +1,13 @@ +#include + +#include + +#define die(...) (SDL_Log(__VA_ARGS__), abort()) +static inline void sdl_error_assert(SDL_bool condition) { + if (!condition) { + SDL_Log("%s", SDL_GetError()); + // banish to hell forever + abort(); + } +} +#define sdl_error_assert(c) (sdl_error_assert(c && SDL_TRUE)) diff --git a/generator.c b/generator.c new file mode 100644 index 0000000..6cf3ad4 --- /dev/null +++ b/generator.c @@ -0,0 +1,68 @@ +#include + +#include "world.h" +#include "random.h" + +#define MAX_INFLUENCE 10 +#define SPARSITY 40.0 +#define CAVERNITY 3 + +#define SURFACE_COLLECTIBLE_RARITY 70 + +void generate_chunk(world *w, chunk *c) { + SDL_memset(c->tiles, TILE_EMPTY, sizeof(c->tiles)); + if (c->pos.y < 0) return; + + static float noise_params[CHUNK_DIM * CHUNK_DIM]; + SDL_memset(noise_params, 0, sizeof(noise_params)); + + for (int i = 0; i < CHUNK_DIM * CHUNK_DIM; i++) { + if (rand_int() % CAVERNITY == 0) { + noise_params[i] = + SDL_pow(rand_float(), SPARSITY) * MAX_INFLUENCE; + } + } + + for (int y = 0; y < CHUNK_DIM; y++) { + for (int x = 0; x < CHUNK_DIM; x++) { + float influence = noise_params[tile_index((SDL_Point) {x, y})]; + if (c->pos.y == 0) { + if (y < 96) influence = 0; + else influence *= 1.8; + } + + int boundary = influence + 0.5; + int min_x = SDL_max(x - boundary, 0); + int max_x = SDL_min(x + boundary, CHUNK_DIM - 1); + int min_y = SDL_max(y - boundary, 0); + int max_y = SDL_min(y + boundary, CHUNK_DIM - 1); + + for (int iy = min_y; iy <= max_y; iy++) { + for (int ix = min_x; ix <= max_x; ix++) { + int dx = ix - x; + int dy = iy - y; + if (dx * dx + dy * dy <= influence * influence) { + if (dx != 0 || dy != 0 || influence > 0.5) { + c->tiles[ + tile_index((SDL_Point) {ix, iy})] = TILE_WALL; + } + } + } + } + } + } + + int rarity = c->pos.x != 0 && c->pos.x != -1 ? + SURFACE_COLLECTIBLE_RARITY : 8; + if (c->pos.y == 0) { + for (int x = 0; x < CHUNK_DIM; x++) { + if (rand_int() % rarity == 0) { + int y = 0; + + while (!is_solid(c->tiles[tile_index((SDL_Point) {x, y + 1})])) + y++; + c->tiles[tile_index((SDL_Point) {x, y})] = rand_int() % TILE_LIGHT; + } + } + } +} diff --git a/generator.h b/generator.h new file mode 100644 index 0000000..557b0cb --- /dev/null +++ b/generator.h @@ -0,0 +1,10 @@ +#ifndef GENERATOR_H +#define GENERATOR_H + +#include + +#include "world.h" + +void generate_chunk(world *w, chunk *c); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..766292c --- /dev/null +++ b/main.c @@ -0,0 +1,92 @@ +#include + +#include "world.h" +#include "random.h" + +static SDL_Window *win; +static SDL_Renderer *rend; + +static world game = {0}; + +static int run() { + SDL_Event e; + while (SDL_PollEvent(&e)) { + seed_rand(e.common.timestamp); + + switch (e.type) { + case SDL_QUIT: + save_world(&game); + return 0; + case SDL_KEYDOWN: + break; + case SDL_KEYUP: + break; + default: + break; + } + } + + int nkeys; + const Uint8 *k = SDL_GetKeyboardState(&nkeys); + + if (k[SDL_SCANCODE_W]) + player_walk(&game, 0, -1); + if (k[SDL_SCANCODE_S]) + player_walk(&game, 0, 1); + if (k[SDL_SCANCODE_A]) + player_walk(&game, -1, 0); + if (k[SDL_SCANCODE_D]) + player_walk(&game, 1, 0); + + if (!k[SDL_SCANCODE_LSHIFT]) { + if (k[SDL_SCANCODE_UP]) + player_place(&game, 0, -1); + if (k[SDL_SCANCODE_DOWN]) + player_place(&game, 0, 1); + if (k[SDL_SCANCODE_LEFT]) + player_place(&game, -1, 0); + if (k[SDL_SCANCODE_RIGHT]) + player_place(&game, 1, 0); + } else { + if (k[SDL_SCANCODE_UP]) + player_destroy(&game, 0, -1); + if (k[SDL_SCANCODE_DOWN]) + player_destroy(&game, 0, 1); + if (k[SDL_SCANCODE_LEFT]) + player_destroy(&game, -1, 0); + if (k[SDL_SCANCODE_RIGHT]) + player_destroy(&game, 1, 0); + } + + tick_world(&game); + draw_world(&game, win, rend); + SDL_RenderPresent(rend); + SDL_Delay(1); + return 1; +} + +int main(int argc, char *argv[]) { + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + SDL_Log("error initializing SDL: %s", SDL_GetError()); + return 1; + } + win = SDL_CreateWindow("find", + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 960, 720, + SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); + if (!win) { + SDL_Log("error creating window: %s", SDL_GetError()); + return 1; + } + rend = SDL_CreateRenderer(win, -1, SDL_RENDERER_PRESENTVSYNC); + if (!rend) { + SDL_Log("error creating renderer: %s", SDL_GetError()); + return 1; + } + + init_world(&game); + while (run()); + + SDL_DestroyRenderer(rend); + SDL_DestroyWindow(win); + return 0; +} diff --git a/random.c b/random.c new file mode 100644 index 0000000..c53d378 --- /dev/null +++ b/random.c @@ -0,0 +1,26 @@ +#include + +static Uint64 random = 0xbee; + +void seed_rand(Uint64 seed) { + random ^= seed; +} + +Uint64 get_rand() { + random ^= random >> 7; + random ^= random << 9; + random ^= random >> 13; + return random; +} + +int rand_int() { + Uint64 rand = get_rand(); + int result; + memcpy(&result, &rand, sizeof(int)); + result = SDL_abs(result); + return result; +} + +double rand_float() { + return (get_rand() >> 11) * 0x1.0p-53; +} diff --git a/random.h b/random.h new file mode 100644 index 0000000..18a032b --- /dev/null +++ b/random.h @@ -0,0 +1,9 @@ +#ifndef RANDOM_H +#define RANDOM_H + +void seed_rand(Uint64 seed); +Uint64 get_rand(void); +int rand_int(void); +double rand_float(void); + +#endif diff --git a/save.c b/save.c new file mode 100644 index 0000000..13c2368 --- /dev/null +++ b/save.c @@ -0,0 +1,130 @@ +#include + +#include "world.h" +#include "die.h" + +typedef char filename[4096]; + +#define MAGIC 0xBEE10D5 +#define SCHEMA 0 + +static const char *pref_path() { + static char *path = NULL; + if (!path) path = SDL_GetPrefPath("citrons", "findgame"); + sdl_error_assert(path); + return path; +} + +static void get_chunk_filename(SDL_Point chunk_pos, filename name) { + int written = SDL_snprintf(name, sizeof(filename), "%schunk.%d.%d.dat", + pref_path(), chunk_pos.x, chunk_pos.y); + if (written >= sizeof(filename)) die("filename too large\n"); +} + +static void get_world_filename(filename name) { + int written = SDL_snprintf(name, sizeof(filename), "%sworld.dat", pref_path()); + if (written >= sizeof(filename)) die("filename too large\n"); +} + +static void write_header(SDL_RWops *file) { + Uint32 magic = MAGIC; + Uint32 schema = 0; + sdl_error_assert(SDL_RWwrite(file, &magic, sizeof(magic), 1)); + sdl_error_assert(SDL_RWwrite(file, &schema, sizeof(schema), 1)); +} + +static void read_header(SDL_RWops *file) { + Uint32 magic; + Uint32 schema; + sdl_error_assert(SDL_RWread(file, &magic, sizeof(magic), 1)); + if (magic != MAGIC) die("invalid save file"); + sdl_error_assert(SDL_RWread(file, &schema, sizeof(schema), 1)); + if (schema != SCHEMA) die("invalid save file"); +} + +void save_chunk(world *w, chunk *c) { + SDL_assert(c->loaded); + + filename name; + get_chunk_filename(c->pos, name); + + SDL_RWops *file = SDL_RWFromFile(name, "wb"); + sdl_error_assert(file); + write_header(file); + + sdl_error_assert( + SDL_RWwrite(file, c->tiles, sizeof(c->tiles), 1)); + + sdl_error_assert(SDL_RWclose(file) >= 0); +} + +void save_world(world *w) { + filename name; + get_world_filename(name); + + SDL_RWops *file = SDL_RWFromFile(name, "wb"); + sdl_error_assert(file); + write_header(file); + + sdl_error_assert( + SDL_RWwrite(file, &w->player.pos, sizeof(w->player.pos), 1)); + sdl_error_assert( + SDL_RWwrite(file, &w->player.scores, sizeof(w->player.scores), 1)); + + sdl_error_assert(SDL_RWclose(file) >= 0); + + for (int i = 0; i < MAX_CHUNKS; i++) { + if (w->chunks[i].loaded) + save_chunk(w, &w->chunks[i]); + } +} + +SDL_bool is_chunk_saved(world *w, SDL_Point chunk_pos) { + filename name; + get_chunk_filename(chunk_pos, name); + + SDL_RWops *file = SDL_RWFromFile(name, "a+b"); + sdl_error_assert(file); + Sint64 len = SDL_RWseek(file, 0, RW_SEEK_END); + SDL_RWclose(file); + + return len != 0; +} + +SDL_bool load_saved_chunk(world *w, chunk *c) { + if (!is_chunk_saved(w, c->pos)) return SDL_FALSE; + + filename name; + get_chunk_filename(c->pos, name); + + SDL_RWops *file = SDL_RWFromFile(name, "rb"); + read_header(file); + + sdl_error_assert( + SDL_RWread(file, c->tiles, sizeof(c->tiles), 1)); + + SDL_RWclose(file); + return SDL_TRUE; +} + +SDL_bool load_world(world *w) { + filename name; + get_world_filename(name); + + SDL_RWops *file = SDL_RWFromFile(name, "a+b"); + sdl_error_assert(file); + if (SDL_RWseek(file, 0, RW_SEEK_END) == 0) { + SDL_RWclose(file); + return SDL_FALSE; + } + SDL_RWseek(file, 0, RW_SEEK_SET); + read_header(file); + + sdl_error_assert( + SDL_RWread(file, &w->player.pos, sizeof(w->player.pos), 1)); + sdl_error_assert( + SDL_RWread(file, &w->player.scores, sizeof(w->player.scores), 1)); + + SDL_RWclose(file); + return SDL_TRUE; +} diff --git a/save.h b/save.h new file mode 100644 index 0000000..3d67b01 --- /dev/null +++ b/save.h @@ -0,0 +1,14 @@ +#ifndef SAVE_H +#define SAVE_H + +#include + +#include "world.h" + +void save_chunk(world *w, chunk *c); +SDL_bool is_chunk_saved(world *w, SDL_Point chunk_pos); +SDL_bool load_saved_chunk(world *w, chunk *c); +SDL_bool load_world(world *w); +void save_world(world *w); + +#endif diff --git a/world.c b/world.c new file mode 100644 index 0000000..8e22470 --- /dev/null +++ b/world.c @@ -0,0 +1,306 @@ +#include + +#include "world.h" +#include "random.h" +#include "generator.h" +#include "save.h" + +SDL_Color tile_colors[] = { + [TILE_RED] = {0xFF, 0x00, 0x00, 0xFF}, + [TILE_GREEN] = {0x00, 0xFF, 0x00, 0xFF}, + [TILE_BLUE] = {0x00, 0x00, 0xFF, 0xFF}, + [TILE_LIGHT] = {0xFF, 0xFF, 0xFF, 0xFF}, + [TILE_EMPTY] = {0x00, 0x00, 0x00, 0xFF}, + [TILE_WALL] = {0x50, 0x50, 0x50, 0xFF}, +}; + +chunk *get_chunk(world *w, SDL_Point chunk_pos) { + for (int i = 0; i < MAX_CHUNKS; i++) { + chunk *c = &w->chunks[i]; + if (!c->loaded) continue; + if (c->pos.x == chunk_pos.x && c->pos.y == chunk_pos.y) { + c->active = SDL_TRUE; + return c; + } + } + return NULL; +} + +SDL_bool chunk_exists(world *w, SDL_Point chunk_pos) { + if (get_chunk(w, chunk_pos)) return SDL_TRUE; + if (is_chunk_saved(w, chunk_pos)) return SDL_TRUE; + return SDL_FALSE; +} + +static void unload_chunk(world *w, chunk *c) { + save_chunk(w, c); + c->active = SDL_FALSE; + c->loaded = SDL_FALSE; +} + +static void deactivate_chunk(chunk *c) { + c->active = SDL_FALSE; +} + +chunk *load_chunk(world *w, SDL_Point chunk_pos) { + chunk *c = get_chunk(w, chunk_pos); + if (c) return c; + + static int last_loaded = 0; + for (int i = 0; i < MAX_CHUNKS; i++) { + chunk *wc = &w->chunks[(last_loaded + i) % MAX_CHUNKS]; + if (!wc->active) { + last_loaded = i; + c = wc; + break; + } + } + if (!c) return NULL; + + if (c->loaded) unload_chunk(w, c); + + c->pos = chunk_pos; + c->active = SDL_TRUE; + c->dirty = SDL_TRUE; + c->loaded = SDL_TRUE; + if (!load_saved_chunk(w, c)) + generate_chunk(w, c); + + return c; +} + +SDL_Texture *render_chunk(SDL_Renderer *rend, chunk *c) { + if (!c->tex) { + c->tex = SDL_CreateTexture(rend, + SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, + CHUNK_DIM, CHUNK_DIM); + SDL_assert(c->tex); + c->dirty = SDL_TRUE; + } + if (c->dirty) { + int pitch; + void *out; + SDL_assert(SDL_LockTexture(c->tex, NULL, &out, &pitch) >= 0); + SDL_Color *pixels = out; + + for (int i = 0; i < CHUNK_DIM * CHUNK_DIM; i++) + pixels[i] = tile_colors[c->tiles[i]]; + + SDL_UnlockTexture(c->tex); + c->dirty = SDL_FALSE; + } + return c->tex; +} + +tile get_tile(world *w, SDL_Point world_pos) { + chunk *c = load_chunk(w, chunk_pos_at(world_pos)); + SDL_assert(c); + return c->tiles[tile_index(pos_in_chunk(world_pos))]; +} + +void set_tile(world *w, SDL_Point world_pos, tile t) { + chunk *c = load_chunk(w, chunk_pos_at(world_pos)); + SDL_assert(c); + c->tiles[tile_index(pos_in_chunk(world_pos))] = t; + c->dirty = SDL_TRUE; +} + +SDL_bool is_solid(tile t) { + return t == TILE_WALL; +} + +SDL_bool player_grounded(world *w) { + if (SDL_TICKS_PASSED(SDL_GetTicks(), w->player.stamina_time)) + return SDL_FALSE; + SDL_bool grounded = SDL_FALSE; + for (int y = w->player.pos.y; y < w->player.pos.y + 2; y++) { + for (int x = w->player.pos.x - 1; x < w->player.pos.x + 2; x++) + grounded = grounded || is_solid(get_tile(w, (SDL_Point) {x, y})); + } + return grounded; +} + +void player_walk(world *w, int x, int y) { + if (!SDL_TICKS_PASSED(SDL_GetTicks(), w->player.walk_wait)) + return; + + if (x != 0 || y != 0) { + SDL_Point new_pos = w->player.pos; + new_pos.x += x; + if (player_grounded(w)) + new_pos.y += y; + if (is_solid(get_tile(w, new_pos))) { + if (y == 0 && player_grounded(w)) { + new_pos = w->player.pos; + new_pos.y -= 1; + if (!is_solid(get_tile(w, new_pos))) + w->player.pos = new_pos; + } + } else w->player.pos = new_pos; + w->player.walk_wait = SDL_GetTicks() + PLAYER_WALK_DELAY; + } +} + +void player_collect(world *w, SDL_Point pos) { + tile t = get_tile(w, pos); + if (t < MAX_COLLECTIBLE) { + w->player.scores[t] += 1; + set_tile(w, pos, TILE_EMPTY); + } +} + +void player_place(world *w, int x, int y) { + if (!player_grounded(w)) return; + if (!SDL_TICKS_PASSED(SDL_GetTicks(), w->player.place_wait)) return; + if (w->player.scores[TILE_LIGHT] < 1) return; + + w->player.scores[TILE_LIGHT]--; + w->player.place_wait = SDL_GetTicks() + PLAYER_PLACE_DELAY; + + SDL_Point place = {w->player.pos.x + x, w->player.pos.y + y}; + if (!is_solid(get_tile(w, place))) { + player_collect(w, place); + set_tile(w, place, TILE_WALL); + } else { + SDL_Point push = {w->player.pos.x - x, w->player.pos.y - y}; + if (!is_solid(get_tile(w, push))) { + set_tile(w, w->player.pos, TILE_WALL); + w->player.pos = push; + } + } +} + + +void player_destroy(world *w, int x, int y) { + if (!SDL_TICKS_PASSED(SDL_GetTicks(), w->player.place_wait)) return; + + SDL_Point destroy = {w->player.pos.x + x, w->player.pos.y + y}; + tile t = get_tile(w, destroy); + + if (t != TILE_EMPTY) { + if (t < MAX_COLLECTIBLE) { + player_collect(w, destroy); + } else { + if (w->player.scores[TILE_LIGHT] < 1) return; + w->player.scores[TILE_LIGHT]--; + + set_tile(w, destroy, TILE_EMPTY); + w->player.place_wait = SDL_GetTicks() + PLAYER_PLACE_DELAY; + } + } +} + +void draw_world(world *w, SDL_Window *win, SDL_Renderer *rend) { + int sw, sh; + SDL_GetWindowSize(win, &sw, &sh); + + double view_scale; + if (sh < sw) + view_scale = (double) sh / (double) VIEW_DIM; + else + view_scale = (double) sw / (double) VIEW_DIM; + + double view_width = (double) sw / view_scale; + double view_height = (double) sh / view_scale; + double view_x = w->view_pos.x - view_width / 2; + double view_y = w->view_pos.y - view_height / 2; + + SDL_Rect view_rect = {view_x, view_y, view_width + 1, view_height + 1}; + for (int i = 0; i < MAX_CHUNKS; i++) { + chunk *c = &w->chunks[i]; + if (c->active) { + SDL_Rect chunk_rect = { + c->pos.x * CHUNK_DIM, c->pos.y * CHUNK_DIM, + CHUNK_DIM, CHUNK_DIM, + }; + if (!SDL_HasIntersection(&view_rect, &chunk_rect)) + deactivate_chunk(c); + } + } + + SDL_Point min = chunk_pos_at((SDL_Point) {view_x, view_y}); + SDL_Point max = chunk_pos_at( + (SDL_Point) {view_x + view_width, view_y + view_height}); + for (int y = min.y; y <= max.y; y++) { + for (int x = min.x; x <= max.x; x++) { + chunk *c = load_chunk(w, (SDL_Point) {x, y}); + if (!c) continue; + SDL_Texture *tex = render_chunk(rend, c); + + int world_x = x * CHUNK_DIM; + int world_y = y * CHUNK_DIM; + int draw_x = ((double) world_x - view_x) * view_scale; + int draw_y = ((double) world_y - view_y) * view_scale; + SDL_Rect draw_rect = { + draw_x, draw_y, + (double) CHUNK_DIM * view_scale + 0.5, + (double) CHUNK_DIM * view_scale + 0.5, + }; + SDL_RenderCopy(rend, tex, NULL, &draw_rect); + } + } + + int player_x = (w->player.pos.x - view_x) * view_scale; + int player_y = (w->player.pos.y - view_y) * view_scale; + SDL_Rect player_rect = { + player_x, player_y, view_scale + 0.5, view_scale + 0.5, + }; + SDL_SetRenderDrawColor(rend, 0xFF, 0x00, 0xFF, 0xFF); + SDL_RenderFillRect(rend, &player_rect); +} + +void tick_world(world *w) { + get_rand(); // shuffle RNG + + player_collect(w, w->player.pos); + int to_combine = SDL_min(w->player.scores[TILE_RED], + SDL_min(w->player.scores[TILE_GREEN], w->player.scores[TILE_BLUE])); + w->player.scores[TILE_LIGHT] += to_combine; + w->player.scores[TILE_RED] -= to_combine; + w->player.scores[TILE_GREEN] -= to_combine; + w->player.scores[TILE_BLUE] -= to_combine; + + tile below = get_tile(w, + (SDL_Point) {w->player.pos.x, w->player.pos.y + 1}); + if (is_solid(below)) + w->player.stamina_time = SDL_GetTicks() + PLAYER_STAMINA; + + if (!player_grounded(w)) { + if (SDL_TICKS_PASSED(SDL_GetTicks(), w->player.gravity_time)) { + SDL_Point new_pos = {w->player.pos.x, w->player.pos.y + 1}; + if (!is_solid(get_tile(w, new_pos))) + w->player.pos = new_pos; + w->player.gravity_time = SDL_GetTicks() + PLAYER_GRAVITY; + } + } + + if (player_grounded(w)) { + if (w->view_pos.x < w->player.pos.x) + w->view_pos.x += 1; + else if (w->view_pos.x > w->player.pos.x) + w->view_pos.x -= 1; + if (w->view_pos.y < w->player.pos.y) + w->view_pos.y += 1; + else if (w->view_pos.y > w->player.pos.y) + w->view_pos.y -= 1; + } else { + if (w->view_pos.x < w->player.pos.x - VIEW_DIM / 3) + w->view_pos.x += 1; + else if (w->view_pos.x > w->player.pos.x + VIEW_DIM / 3) + w->view_pos.x -= 1; + if (w->view_pos.y < w->player.pos.y - VIEW_DIM / 3) + w->view_pos.y += 1; + else if (w->view_pos.y > w->player.pos.y + VIEW_DIM / 3) + w->view_pos.y -= 1; + } + + if (SDL_TICKS_PASSED(SDL_GetTicks(), w->save_time)) { + save_world(w); + w->save_time = SDL_GetTicks() + SAVE_INTERVAL; + } +} + +void init_world(world *w) { + load_world(w); + w->view_pos = w->player.pos; +} diff --git a/world.h b/world.h new file mode 100644 index 0000000..ad7acd2 --- /dev/null +++ b/world.h @@ -0,0 +1,94 @@ +#ifndef WORLD_H +#define WORLD_H + +#include + +#define CHUNK_DIM 128 + +typedef enum tile { + TILE_RED, + TILE_GREEN, + TILE_BLUE, + TILE_LIGHT, + MAX_COLLECTIBLE, + + TILE_EMPTY, + TILE_WALL, +} tile; + +extern SDL_Color tile_colors[]; + +#define PLAYER_STAMINA 1250 +#define PLAYER_WALK_DELAY 100 +#define PLAYER_PLACE_DELAY 175 +#define PLAYER_GRAVITY 30 +typedef struct player { + SDL_Point pos; + int scores[MAX_COLLECTIBLE]; + int stamina_time; + int walk_wait; + int place_wait; + int gravity_time; +} player; + +typedef struct chunk { + SDL_Point pos; + Uint8 tiles[CHUNK_DIM * CHUNK_DIM]; + SDL_Texture *tex; + SDL_bool dirty; + SDL_bool loaded; + SDL_bool active; +} chunk; + +#define MAX_CHUNKS 16 +#define SAVE_INTERVAL 120000 +typedef struct world { + player player; + SDL_Point view_pos; + chunk chunks[MAX_CHUNKS]; + int save_time; +} world; + +static inline SDL_Point chunk_pos_at(SDL_Point world_pos) { + return (SDL_Point) { + SDL_floor((double) world_pos.x / CHUNK_DIM), + SDL_floor((double) world_pos.y / CHUNK_DIM), + }; +} + +static inline SDL_Point pos_in_chunk(SDL_Point world_pos) { + SDL_Point cpos = chunk_pos_at(world_pos); + return (SDL_Point) { + world_pos.x - cpos.x * CHUNK_DIM, + world_pos.y - cpos.y * CHUNK_DIM, + }; +} + +static inline int tile_index(SDL_Point pos) { + SDL_assert(pos.x < CHUNK_DIM && pos.y < CHUNK_DIM); + return pos.y * CHUNK_DIM + pos.x; +} + +chunk *load_chunk(world *w, SDL_Point chunk_pos); +chunk *get_chunk(world *w, SDL_Point chunk_pos); +SDL_bool chunk_exists(world *w, SDL_Point chunk_pos); + +tile get_tile(world *w, SDL_Point world_pos); +void set_tile(world *w, SDL_Point world_pos, tile t); + +SDL_bool is_solid(tile t); + +SDL_bool player_grounded(world *w); +void player_walk(world *w, int x, int y); +void player_place(world *w, int x, int y); +void player_destroy(world *w, int x, int y); +void player_collect(world *w, SDL_Point pos); + +void tick_world(world *w); + +#define VIEW_DIM 40 +void draw_world(world *w, SDL_Window *win, SDL_Renderer *rend); + +void init_world(world *w); + +#endif -- cgit v1.2.3