diff options
| -rw-r--r-- | Makefile | 19 | ||||
| -rw-r--r-- | die.h | 13 | ||||
| -rw-r--r-- | generator.c | 68 | ||||
| -rw-r--r-- | generator.h | 10 | ||||
| -rw-r--r-- | main.c | 92 | ||||
| -rw-r--r-- | random.c | 26 | ||||
| -rw-r--r-- | random.h | 9 | ||||
| -rw-r--r-- | save.c | 130 | ||||
| -rw-r--r-- | save.h | 14 | ||||
| -rw-r--r-- | world.c | 306 | ||||
| -rw-r--r-- | world.h | 94 |
11 files changed, 781 insertions, 0 deletions
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 @@ -0,0 +1,13 @@ +#include <SDL.h> + +#include <stdlib.h> + +#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 <SDL.h> + +#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 <SDL.h> + +#include "world.h" + +void generate_chunk(world *w, chunk *c); + +#endif @@ -0,0 +1,92 @@ +#include <SDL.h> + +#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 <SDL.h> + +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 @@ -0,0 +1,130 @@ +#include <SDL.h> + +#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; +} @@ -0,0 +1,14 @@ +#ifndef SAVE_H +#define SAVE_H + +#include <SDL.h> + +#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 @@ -0,0 +1,306 @@ +#include <SDL.h> + +#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; +} @@ -0,0 +1,94 @@ +#ifndef WORLD_H +#define WORLD_H + +#include <SDL.h> + +#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 |
