diff --git a/src/game.c b/src/game.c new file mode 100644 index 0000000..9ec951b --- /dev/null +++ b/src/game.c @@ -0,0 +1,57 @@ +#include "ok_game.h" +#include "ok_map.h" +#include "ok_unit.h" +#include + +void ok_game_spawn(OkGame *g, OkFaction f, OkUnitKind k, int x, int y) { + if (g->unit_count >= OK_MAX_UNITS) return; + if (!ok_map_walkable(&g->map, x, y)) return; + OkUnit *u = &g->units[g->unit_count++]; + memset(u, 0, sizeof(*u)); + u->x = x; + u->y = y; + u->faction = f; + u->kind = k; + u->hp_max = (k == OK_UNIT_VEHICLE) ? 80 : 30; + u->hp = u->hp_max; + u->target = -1; + u->alive = true; +} + +void ok_game_init(OkGame *g) { + memset(g, 0, sizeof(*g)); + ok_map_generate(&g->map); + g->selected = -1; + g->running = true; + /* two camps */ + ok_game_spawn(g, OK_FACTION_SURVIVORS, OK_UNIT_INFANTRY, 6, 6); + ok_game_spawn(g, OK_FACTION_SURVIVORS, OK_UNIT_VEHICLE, 8, 6); + ok_game_spawn(g, OK_FACTION_SCAVENGERS, OK_UNIT_INFANTRY, OK_MAP_W - 8, OK_MAP_H - 8); + ok_game_spawn(g, OK_FACTION_SCAVENGERS, OK_UNIT_VEHICLE, OK_MAP_W - 10, OK_MAP_H - 8); + /* auto-aggro first pair */ + if (g->unit_count >= 4) { + g->units[0].target = 2; + g->units[1].target = 3; + g->units[2].target = 0; + g->units[3].target = 1; + } +} + +void ok_game_update(OkGame *g, float dt) { + if (!g->running) return; + g->tick++; + ok_units_update(g, dt); + int s_alive = 0, c_alive = 0; + for (int i = 0; i < g->unit_count; i++) { + if (!g->units[i].alive) continue; + if (g->units[i].faction == OK_FACTION_SURVIVORS) s_alive++; + else c_alive++; + } + if (s_alive == 0) { + g->scavengers_win = true; + g->running = false; + } else if (c_alive == 0) { + g->survivors_win = true; + g->running = false; + } +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..b57b889 --- /dev/null +++ b/src/main.c @@ -0,0 +1,44 @@ +#include "ok_game.h" +#include "ok_render.h" +#include +#include + +#ifndef OPENKKND_VERSION +#define OPENKKND_VERSION "dev" +#endif + +int main(int argc, char **argv) { + (void)argc; + (void)argv; + printf("OpenKKND %s — original genre RTS (MIT). No commercial IP assets.\n", OPENKKND_VERSION); + + const int tw = 12; + if (!ok_render_init(OK_MAP_W * tw, OK_MAP_H * tw)) { + fprintf(stderr, "render init failed\n"); + return 1; + } + + OkGame game; + ok_game_init(&game); + + Uint64 prev = SDL_GetPerformanceCounter(); + const Uint64 freq = SDL_GetPerformanceFrequency(); + + while (game.running || true) { + SDL_Event e; + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) goto done; + if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE) goto done; + if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_r) ok_game_init(&game); + } + Uint64 now = SDL_GetPerformanceCounter(); + float dt = (float)(now - prev) / (float)freq; + prev = now; + if (dt > 0.05f) dt = 0.05f; + if (game.running) ok_game_update(&game, dt); + ok_render_draw(&game); + } +done: + ok_render_shutdown(); + return 0; +} diff --git a/src/map.c b/src/map.c new file mode 100644 index 0000000..c372d83 --- /dev/null +++ b/src/map.c @@ -0,0 +1,19 @@ +#include "ok_map.h" +#include + +void ok_map_generate(OkMap *m) { + for (int y = 0; y < OK_MAP_H; y++) { + for (int x = 0; x < OK_MAP_W; x++) { + m->tile[y][x] = 0; + if (x == 0 || y == 0 || x == OK_MAP_W - 1 || y == OK_MAP_H - 1) + m->tile[y][x] = 1; + else if ((x * 17 + y * 31) % 47 == 0) + m->tile[y][x] = 2; /* scrap pile */ + } + } +} + +bool ok_map_walkable(const OkMap *m, int x, int y) { + if (x < 0 || y < 0 || x >= OK_MAP_W || y >= OK_MAP_H) return false; + return m->tile[y][x] != 1; +} diff --git a/src/render.c b/src/render.c new file mode 100644 index 0000000..f8a5bcc --- /dev/null +++ b/src/render.c @@ -0,0 +1,71 @@ +#include "ok_render.h" +#include + +static SDL_Window *win; +static SDL_Renderer *ren; +static int tile = 12; + +bool ok_render_init(int w, int h) { + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0) { + fprintf(stderr, "SDL_Init: %s\n", SDL_GetError()); + return false; + } + win = SDL_CreateWindow("OpenKKND " OPENKKND_VERSION, + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + w, h, SDL_WINDOW_SHOWN); + if (!win) return false; + ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!ren) ren = SDL_CreateRenderer(win, -1, 0); + return ren != NULL; +} + +SDL_Window *ok_render_window(void) { return win; } + +void ok_render_draw(const OkGame *g) { + SDL_SetRenderDrawColor(ren, 20, 18, 16, 255); + SDL_RenderClear(ren); + for (int y = 0; y < OK_MAP_H; y++) { + for (int x = 0; x < OK_MAP_W; x++) { + uint8_t t = g->map.tile[y][x]; + if (t == 1) SDL_SetRenderDrawColor(ren, 50, 45, 40, 255); + else if (t == 2) SDL_SetRenderDrawColor(ren, 90, 70, 30, 255); + else SDL_SetRenderDrawColor(ren, 35, 40, 32, 255); + SDL_Rect r = {x * tile, y * tile, tile - 1, tile - 1}; + SDL_RenderFillRect(ren, &r); + } + } + for (int i = 0; i < g->unit_count; i++) { + const OkUnit *u = &g->units[i]; + if (!u->alive) continue; + if (u->faction == OK_FACTION_SURVIVORS) + SDL_SetRenderDrawColor(ren, 60, 160, 220, 255); + else + SDL_SetRenderDrawColor(ren, 220, 90, 50, 255); + int s = (u->kind == OK_UNIT_VEHICLE) ? tile : tile - 3; + SDL_Rect r = {u->x * tile + 1, u->y * tile + 1, s, s}; + SDL_RenderFillRect(ren, &r); + /* hp bar */ + SDL_SetRenderDrawColor(ren, 0, 0, 0, 255); + SDL_Rect bg = {r.x, r.y - 3, s, 2}; + SDL_RenderFillRect(ren, &bg); + int hw = s * u->hp / (u->hp_max ? u->hp_max : 1); + SDL_SetRenderDrawColor(ren, 40, 200, 60, 255); + SDL_Rect hg = {r.x, r.y - 3, hw, 2}; + SDL_RenderFillRect(ren, &hg); + } + if (!g->running) { + if (g->survivors_win) SDL_SetRenderDrawColor(ren, 40, 180, 255, 180); + else SDL_SetRenderDrawColor(ren, 255, 80, 40, 180); + SDL_Rect banner = {0, (OK_MAP_H * tile) / 2 - 20, OK_MAP_W * tile, 40}; + SDL_RenderFillRect(ren, &banner); + } + SDL_RenderPresent(ren); +} + +void ok_render_shutdown(void) { + if (ren) SDL_DestroyRenderer(ren); + if (win) SDL_DestroyWindow(win); + ren = NULL; + win = NULL; + SDL_Quit(); +} diff --git a/src/unit.c b/src/unit.c new file mode 100644 index 0000000..5e536f3 --- /dev/null +++ b/src/unit.c @@ -0,0 +1,37 @@ +#include "ok_unit.h" +#include "ok_map.h" +#include + +static int dist2(int ax, int ay, int bx, int by) { + int dx = ax - bx, dy = ay - by; + return dx * dx + dy * dy; +} + +void ok_units_update(OkGame *g, float dt) { + (void)dt; + for (int i = 0; i < g->unit_count; i++) { + OkUnit *u = &g->units[i]; + if (!u->alive) continue; + if (u->target < 0 || u->target >= g->unit_count) continue; + OkUnit *t = &g->units[u->target]; + if (!t->alive || t->faction == u->faction) { + u->target = -1; + continue; + } + int range = (u->kind == OK_UNIT_VEHICLE) ? 8 : 4; + if (dist2(u->x, u->y, t->x, t->y) <= range * range) { + int dmg = (u->kind == OK_UNIT_VEHICLE) ? 4 : 2; + t->hp -= dmg; + if (t->hp <= 0) { + t->alive = false; + t->hp = 0; + } + } else { + /* crude step toward target */ + int nx = u->x + (t->x > u->x) - (t->x < u->x); + int ny = u->y + (t->y > u->y) - (t->y < u->y); + if (ok_map_walkable(&g->map, nx, u->y)) u->x = nx; + if (ok_map_walkable(&g->map, u->x, ny)) u->y = ny; + } + } +}