From ad36ee99a00e7ad07f360d47a04cb6cbf3adeb54 Mon Sep 17 00:00:00 2001 From: Niki Date: Sat, 19 Apr 2025 00:31:36 +0200 Subject: [PATCH] many fixes and additions --- setup.debug | 66 ++++++++++++++------------- src/tdef/command.c | 81 +++++++++++++++------------------ src/tdef/command.h | 1 - src/tdef/enemy.c | 3 +- src/tdef/engine.c | 55 ++++++++++++++++++----- src/tdef/engine.h | 8 +++- src/tdef/event.c | 25 ++++++----- src/tdef/event.h | 5 +++ src/tdef/map.c | 53 +++++++++++++++------- src/tdef/map.h | 2 +- src/tdef/reader.c | 109 ++++++++------------------------------------- src/tdef/reader.h | 77 ++++++++++++++++++++++++++++++-- src/tdef/tdef.c | 30 ++++++++----- src/tdef/tower.c | 88 +++++++++++++++++++++++------------- src/tdef/tower.h | 16 +++++-- src/tdef/wave.c | 5 ++- src/tdef/wave.h | 2 +- 17 files changed, 365 insertions(+), 261 deletions(-) diff --git a/setup.debug b/setup.debug index 98af49c..2be55e5 100644 --- a/setup.debug +++ b/setup.debug @@ -16,71 +16,73 @@ # Note that white space around the separators (':' or ',') are allowed. # # Engine commands: -# * . : will process a tick in the game engine -# * readfile : will read commands from the given file(s) -- one minimum +# > . : will process a tick in the game engine +# > readfile : will read commands from the given file(s) -- one minimum # > (n) "file1..." # # Setup commands: -# * map : will setup the map (and reset the path) +# > map : will setup the map (and reset the path) # > (0) width # > (1) height -# * life : will set the number of life points +# > life : will set the number of life points # > (0) lives -# * bits : will set the current bits (money) balance +# > bits : will set the current bits (money) balance # > (0) bits_amount -# * path : will setup the enemies path (cumulative and ordered) +# > path : will setup the enemies path (cumulative and ordered) # > (n) x1,y1, x2,y2... -# * tower : will setup a new tower type +# > tower : will setup a new tower type # > (0) type : the unique identifier for this tower type -# > (1) base_cost -# * tower_lvl: will configure a tower upgrade level +# > tower_lvl: will configure a tower upgrade level # > (0) type : the unique identifier for this tower type # > (1) attack : how many points dealt to enemies # > (2) speed : the time needed to rearm after having fired # > (3) range : the range in locations ("path") # > (4) cost : upgrade cost for this level (for each of atk, spd, rng) -# * tower_sp : set the Super Power of a tower +# > tower_sp : set the Super Power of a tower # > (0) type : the unique identifier for this tower type # > (1) super_cost # > (2) super_power -# * enemy : will setup a new enemy type +# > enemy : will setup a new enemy type # > (0) type : the unique identifier for this enemy type # > (1) hp : life/hit points the enemy can take # > (2) speed : speed in paths per tick # > (3) bits : how much bits received on enemy death -# * wave : configure a new wave TODO +# > wave : configure a new wave TODO # # Add commands: -# * add_tower: add a new tower on the map (will cost bits) +# > add_tower: add a new tower on the map (will cost bits) # > (0) type : the type of tower to add -# > (1) X position: the X coordinate -# > (2) Y position: the Y coordinate -# * add_enemy : add a new enemy in the current wave (can create the first one) +# > (1) id : the *unique* id of the tower +# > (2) X position: the X coordinate +# > (3) Y position: the Y coordinate +# > add_enemy : add a new enemy in the current wave (can create the first one) # > type : the enemy type -# > id : the *unique* id of this enemy +# > id : the >unique> id of this enemy # > start_tick : the tick at which it will appear (wave-relative) # # Other commands: -# * display : will toggle display (debug) mode -# * quit : will immediately exit the game +# > display : will toggle display (debug) mode +# > quit : will immediately exit the game -map: 6, 4 +map: 72, 10 +bits: 100 -path: 0, 0 -path: 1, 0 -path: 2, 0 -path: 3, 0 -path: 3, 1 -path: 3, 2 -path: 4, 2 -path: 5, 2 +path: 0,0, 1,0, 2,0, 3,0 +path: 3,1 +path: 3,2, 4,2, 5,2, 6,2, 7,2, 8,2, 9,2, 10,2, 11,2, 12,2, 13,2, 14,2, 15,2, 16,2 -bits: 100 +# tower_lvl: type, atk, spd, rng, cost tower: 0, 10 -enemy: 0, 3, 2, 5 +tower_lvl: 0, 5, 1, 3, 10 + +# enemy: type, hp, speed, bits +enemy : 0, 7, 1, 5 +enemy : 1, 17, 2, 15 -add_tower: 0, 2, 1 -add_enemy: 0, 0, 0 +# add_enemy: type, uid, start_tick +add_enemy : 0, 22, 0 +add_enemy : 1, 88, 5 +add_tower: 0, 55, 2, 1 display diff --git a/src/tdef/command.c b/src/tdef/command.c index 6280cf8..26f73fc 100644 --- a/src/tdef/command.c +++ b/src/tdef/command.c @@ -66,36 +66,6 @@ void uninit_command(command_t *self) { } } -int command_read_count(command_t *self, const char line[]) { - int howmany_read = 0; - int in_params = 0; - int escaped = 0; - for (size_t i = 0 ; line[i] ; i++) { - if (!in_params) { - if (line[i] == ':') - in_params = 1; - - continue; - } - - if (line[i] == '"') - escaped = !escaped; - if (escaped) - continue; - - if (line[i] == ',') { - // parameter found - howmany_read++; - } - } - - // Final parameter - if (in_params) - howmany_read++; - - return howmany_read; -} - int command_read_str(command_t *self,const char line[],int pstart,int pstop) { if (!self->data) self->data = new_array(sizeof(char *), 1); @@ -111,16 +81,23 @@ int command_read_str(command_t *self,const char line[],int pstart,int pstop) { size_t sz = (!str) ? 0 : strlen(str); if (!str || str[0] != '"' || str[sz-1] != '"') { ok = 0; +#ifdef DEBUG +fprintf(stderr, "Invalid string: <%s>\n", str); +#endif +#ifndef DEBUG break; +#endif } - cstring_t *noquotes = new_cstring(); - cstring_addfn(noquotes, str, 1, sz-2); - - char **newptr = array_new(self->data); - *newptr = cstring_convert(noquotes); - free(*ptr); - *ptr = NULL; + if (ok) { + cstring_t *noquotes = new_cstring(); + cstring_addfn(noquotes, str, 1, sz-2); + + char **newptr = array_new(self->data); + *newptr = cstring_convert(noquotes); + free(*ptr); + *ptr = NULL; + } } free_array(tmp); @@ -148,13 +125,20 @@ int command_read_int(command_t *self,const char line[],int pstart,int pstop) { for (size_t i = 0 ; str[i] ; i++) { if (str[i] < '0' || str[i] > '9') { ok = 0; +#ifdef DEBUG +fprintf(stderr, "Invalid int: <%s>\n", str); +#endif +#ifndef DEBUG break; +#endif } } - char **newptr = array_new(self->data); - *newptr = *ptr; - *ptr = NULL; + if (ok) { + char **newptr = array_new(self->data); + *newptr = *ptr; + *ptr = NULL; + } } free_array(tmp); @@ -189,13 +173,20 @@ int command_read_file(command_t *self,const char line[],int pstart,int pstop) { FILE *testopen = fopen(filename, "r"); if (!testopen) { ok = 0; +#ifdef DEBUG +fprintf(stderr, "Invalid string: <%s>\n", filename); +#endif +#ifndef DEBUG break; +#endif } - fclose(testopen); - char **newptr = array_new(self->data); - *newptr = *ptr; - *ptr = NULL; + if (ok) { + fclose(testopen); + char **newptr = array_new(self->data); + *newptr = *ptr; + *ptr = NULL; + } } free_array(tmp); @@ -261,7 +252,7 @@ array_t *read_word(command_t *self,const char line[],int pstart,int pstop) { } // Final parameter - if (start < stop) { + if (start <= stop) { if ((howmany_read >= pstart) && ((howmany_read <= pstop) || (pstop < 0))) { char **ptr = array_new(tmp); diff --git a/src/tdef/command.h b/src/tdef/command.h index a18caaa..f236a55 100644 --- a/src/tdef/command.h +++ b/src/tdef/command.h @@ -104,7 +104,6 @@ void uninit_command(command_t *self); // COMMAND:param1,param2,... // pstop 0 -> continue until no more param // white space ignored (trimmed) -int command_read_count(command_t *self, const char line[]); int command_read_str(command_t *self,const char line[],int pstart,int pstop); diff --git a/src/tdef/enemy.c b/src/tdef/enemy.c index 9fdd2af..2375b91 100644 --- a/src/tdef/enemy.c +++ b/src/tdef/enemy.c @@ -41,7 +41,7 @@ int init_enemy(enemy_t *self, enemy_base_t *base, int id, size_t start_tick) { self->start_tick = start_tick; self->base = base; self->id = id; - self->hp = 1; + self->hp = base->max_hp; self->index = -1; self->alive = 0; @@ -67,3 +67,4 @@ int init_enemy_base(enemy_base_t *self, int type, int hp, int speed, int bits){ return 1; } + diff --git a/src/tdef/engine.c b/src/tdef/engine.c index 447d63d..8fcae41 100644 --- a/src/tdef/engine.c +++ b/src/tdef/engine.c @@ -115,7 +115,7 @@ void engine_tick(engine_t *self) { self->current_wave = i; event_t event; init_event(&event, EVENT_WAVE); - event.x = i, + event.id = i, array_push(self->events, &event); } @@ -135,15 +135,15 @@ void engine_tick(engine_t *self) { event_t event; init_event(&event, EVENT_BITS); - event.x = bits; - event.move_to_x = self->bits; + event.cost = bits; + event.bits = self->bits; array_push(self->events, &event); } self->current_tick++; } -int engine_add_tower(engine_t *self, int type, int x, int y) { +int engine_add_tower(engine_t *self, int type, int id, int x, int y) { if (x < 0 || y < 0) return 0; if (x >= self->map->width || y >= self->map->height) @@ -161,7 +161,8 @@ int engine_add_tower(engine_t *self, int type, int x, int y) { return 0; } - if (self->bits < base->cost) { + int cost = base->stats[0].cost; + if (self->bits < cost) { event_t event; init_event(&event, EVENT_ERROR); event.error = EEVENT_NOT_ENOUGH_BITS; @@ -171,14 +172,14 @@ int engine_add_tower(engine_t *self, int type, int x, int y) { } tower_t **ptr = array_new(self->map->towers); - *ptr = new_tower(base, x, y); + *ptr = new_tower(base, id, x, y); - self->bits -= base->cost; + self->bits -= cost; event_t event; init_event(&event, EVENT_BITS); - event.x = -base->cost; - event.move_to_x = self->bits; + event.cost = -cost; + event.bits = self->bits; array_push(self->events, &event); self->map->data[y * self->map->width + x] = tower2any(*ptr); @@ -203,7 +204,7 @@ enemy_base_t *engine_get_enemy_base(engine_t *self, int type) { return NULL; } -tower_base_t *setup_tower_base(engine_t *self, int type, int cost) { +tower_base_t *setup_tower_base(engine_t *self, int type) { tower_base_t *base = engine_get_tower_base(self, type); if (base) { // already configured event_t event; @@ -216,11 +217,43 @@ tower_base_t *setup_tower_base(engine_t *self, int type, int cost) { base = array_new(self->tbases); init_tower_base(base, type); - base->cost = cost; return base; } +int setup_tower_lvl( + engine_t *self, int type, int attack, int speed, int range, int cost +) { + tower_base_t *base = engine_get_tower_base(self, type); + if (!base) { // unknown base + event_t event; + init_event(&event, EVENT_ERROR); + event.error = EEVENT_UNKNOWN_TYPE; + array_push(self->events, &event); + + return 0; + } + + if ((base->stats_sz * sizeof(tower_lvl_t)) >= sizeof(base->stats)) { + event_t event; + init_event(&event, EVENT_ERROR); + event.error = EEVENT_ALREADY_CONFIGURED; + array_push(self->events, &event); + + return 0; + } + + tower_lvl_t *lvl = &(base->stats[base->stats_sz]); + lvl->speed = speed; + lvl->attack = attack; + lvl->range = range; + lvl->cost = cost; + + base->stats_sz ++; + + return 1; +} + int setup_wave(engine_t *self, int start_tick, int bits) { wave_t *wave = array_new(self->waves); if (!wave) diff --git a/src/tdef/engine.h b/src/tdef/engine.h index 4281489..838633b 100644 --- a/src/tdef/engine.h +++ b/src/tdef/engine.h @@ -82,7 +82,7 @@ int engine_reset_map(engine_t *self, int width, int height); // will clear events on start! void engine_tick(engine_t *self); -int engine_add_tower(engine_t *self, int type, int x, int y); +int engine_add_tower(engine_t *self, int type, int id, int x, int y); tower_base_t *engine_get_tower_base(engine_t *self, int type); @@ -90,7 +90,11 @@ enemy_base_t *engine_get_enemy_base(engine_t *self, int type); // Setup: -tower_base_t *setup_tower_base(engine_t *self, int type, int cost); +tower_base_t *setup_tower_base(engine_t *self, int type); + +int setup_tower_lvl( + engine_t *self, int type, int attack, int speed, int range, int cost +); int setup_wave(engine_t *self, int start_tick, int bits); diff --git a/src/tdef/event.c b/src/tdef/event.c index a3a08a0..f18e095 100644 --- a/src/tdef/event.c +++ b/src/tdef/event.c @@ -40,6 +40,7 @@ int init_event(event_t *self, event_type type) { self->type = type; self->error = EEVENT_SUCCESS; + self->id = 0; self->x = -1; self->y = -1; self->move_to_x = -1; @@ -93,23 +94,27 @@ void event_output(event_t *self, cstring_t *out) { const char *name = event_name(self); switch (self->type) { - case EVENT_FIRE: case EVENT_HIT: case EVENT_MOVE: - cstring_addp(out, "EVENT:%s,%d,%d,%d,%d\n", name, - self->x, self->y, self->move_to_x, self->move_to_y); + case EVENT_FIRE: case EVENT_MOVE: + cstring_addp(out, "EVENT:%s,%d,%d,%d,%d,%d\n", name, + self->id, self->x, self->y, + self->move_to_x, self->move_to_y + ); break; - case EVENT_BACKFILL: case EVENT_BREACH: - cstring_addp(out, "EVENT:%s,%d,%d\n", name, self->x, self->y); + case EVENT_HIT: + cstring_addp(out, "EVENT:%s,%d,%d,%d\n", name, + self->id, self->x, self->y, self->damage); break; case EVENT_DIE: - cstring_addp(out, "EVENT:%s,%d,%d,%d\n", name, - self->x, self->y, self->move_to_x); + cstring_addp(out, "EVENT:%s,%d,%d,%d,%d\n", name, + self->id, self->x, self->y, self->bits); break; case EVENT_BITS: cstring_addp(out, "EVENT:%s,%d,%d\n", name, - self->x, self->move_to_x); + self->cost, self->bits); break; - case EVENT_ENTER: case EVENT_WAVE: - cstring_addp(out, "EVENT:%s,%d\n", name, self->x); + case EVENT_ENTER: case EVENT_WAVE: case EVENT_BACKFILL: + case EVENT_BREACH: + cstring_addp(out, "EVENT:%s,%d\n", name, self->id); break; case EVENT_WIN: case EVENT_LOOSE: cstring_addp(out, "EVENT:%s\n", name); diff --git a/src/tdef/event.h b/src/tdef/event.h index 76a189f..6395498 100644 --- a/src/tdef/event.h +++ b/src/tdef/event.h @@ -52,10 +52,15 @@ typedef struct { char CNAME[10]; event_type type; eevent_type error; + int id; // of the target int x; int y; int move_to_x; int move_to_y; + int cost; + int bits; // remaining + int damage; + int hp; // remaining } event_t; /** diff --git a/src/tdef/map.c b/src/tdef/map.c index 2b8a460..93de0d8 100644 --- a/src/tdef/map.c +++ b/src/tdef/map.c @@ -120,20 +120,18 @@ void map_enemy_enters(map_t *self, enemy_t *enemy, array_t *events) { enemy->alive = 1; array_push(self->alive, &enemy); - event_t event; - init_event(&event, EVENT_ENTER); - event.x = enemy->id; - array_push(events, &event); + // Note: the actual event will be fired on the first MOVE + // (so not to generate a ENTER after backfill still in (-1,-1) return; } void map_move_1(map_t *self, array_t *events) { int dead = 0; - array_deloop(self->alive, ptr, enemy_t*) { - enemy_t *alive = *ptr; - if (alive->alive) - move(self, alive, events, 0); + array_deloop (self->alive, ptr, enemy_t*) { + enemy_t *e =*ptr; + if (e && e->alive) + move(self, e, events, 0); else dead++; } @@ -141,14 +139,17 @@ void map_move_1(map_t *self, array_t *events) { if (dead > (self->alive->count / 2)) { array_clear(self->tmp_e); array_loop (self->alive, ptr, enemy_t*) { - if ((*ptr)->alive) + enemy_t *e = *ptr; + if (e && e->alive) array_push(self->tmp_e, ptr); } array_steal_data(self->alive, self->tmp_e); } - if (self->backfill->count) { - enemy_t **ptr = array_first(self->backfill); + // only take one maximum per tick (else, backfill galore...) + path_t *entrance = array_first(self->paths); + if (!entrance->enemy && self->backfill->count) { + enemy_t **ptr = array_pop(self->backfill); enemy_t *enemy = *ptr; map_enemy_enters(self, enemy, events); } @@ -170,8 +171,9 @@ void map_fire_1(map_t *self, int current_tick, array_t *events) { target->next = self->projs->next; self->projs->next = target; - event.x = tower->x; - event.y = tower->y; + event.id = tower->id; + event.x = tower->x; + event.y = tower->y; event.move_to_x = target->x; event.move_to_y = target->y; array_push(events, &event); @@ -202,6 +204,7 @@ void move(map_t *self, enemy_t *enemy, array_t *events, int reverse) { int target = source >= 0 ? (source + enemy->base->speed) : 0; event_t event; init_event(&event, EVENT_MOVE); + event.id = enemy->id; path_t *path_from = NULL; if (enemy->index >= 0) { @@ -214,7 +217,18 @@ void move(map_t *self, enemy_t *enemy, array_t *events, int reverse) { if (reverse) { target = source-1; if (target == -1) { // backfill - enemy->alive = 0; + enemy->index = -1; + if (path_from) + path_from->enemy = NULL; + + array_loop (self->alive, ptr, enemy_t*) { + enemy_t *e = *ptr; + if (e && e->id == enemy->id) { + *ptr = NULL; + break; + } + } + array_push(self->backfill, &enemy); event.type = EVENT_BACKFILL; array_push(events, &event); @@ -223,7 +237,7 @@ void move(map_t *self, enemy_t *enemy, array_t *events, int reverse) { } path_t *path_to = array_get(self->paths, target); - if (path_to && path_to->enemy) // replace + if (path_to && path_to->enemy) // already one, so force it back move(self, path_to->enemy, events, 1); if (path_to) { // move @@ -237,6 +251,8 @@ void move(map_t *self, enemy_t *enemy, array_t *events, int reverse) { event.type = EVENT_BREACH; } + if (event.x < 0 || event.y < 0) + event.type = EVENT_ENTER; array_push(events, &event); return; @@ -249,7 +265,7 @@ int hit(map_t *self, proj_t *proj, array_t *events) { enemy_t *e = (path) ? path->enemy : NULL; int bits = 0; - if (e && e->hp > 0) { + if (e && e->alive) { e->hp -= proj->desc.damage; event_t event; @@ -259,11 +275,14 @@ int hit(map_t *self, proj_t *proj, array_t *events) { path->enemy = NULL; init_event(&event, EVENT_DIE); bits = e->base->bits; - event.move_to_x = bits; + event.bits = bits; } else { init_event(&event, EVENT_HIT); + event.damage = proj->desc.damage; + event.hp = e->hp; } + event.id = e->id; event.x = path->x; event.y = path->y; array_push(events, &event); diff --git a/src/tdef/map.h b/src/tdef/map.h index b8e83c2..b1facda 100644 --- a/src/tdef/map.h +++ b/src/tdef/map.h @@ -28,7 +28,7 @@ typedef struct { any_t **data; array_t *paths; // path array_t *towers; // tower* - array_t *alive; // enemy* + array_t *alive; // enemy*, a ptr can be NULL array_t *breached; // enemy* array_t *backfill; // enemy* proj_t *projs; // first one is fake (list head) diff --git a/src/tdef/reader.c b/src/tdef/reader.c index 0bc324b..c28d303 100644 --- a/src/tdef/reader.c +++ b/src/tdef/reader.c @@ -1,5 +1,5 @@ /* - * TDef: small reader defense game + * TDef: small tower defense game * * Copyright (C) 2025 Niki Roo * @@ -23,74 +23,6 @@ #include "cutils/cstring.h" #include "reader.h" -// Commands are given in the form of: -// KEYWORD -// or: -// KEYWORD: [parameters] -// or: -// # comment... (mostly used for data files) -// -// Parameters are given in the form of: -// int_parameter1, int_parameter2... -// or: -// "string parameter", "file parameter" -// -// Note that white space around the separators (':' or ',') are allowed. -// -// Engine commands: -// * . : will process a tick in the game engine -// * readfile : will read commands from the given file(s) -- one minimum -// > (n) "file1..." -// -// Setup commands: -// * map : will setup the map (and reset the path) -// > (0) width -// > (1) height -// * life : will set the number of life points -// > (0) lives -// * bits : will set the current bits (money) balance -// > (0) bits_amount -// * path : will setup the enemies path (cumulative and ordered) -// > (n) x1,y1, x2,y2... -// * tower : will setup a new tower type -// > (0) type : the unique identifier for this tower type -// > (1) base_cost -// * tower_lvl: will configure a tower upgrade level -// > (0) type : the unique identifier for this tower type -// > (1) attack : how many points dealt to enemies -// > (2) speed : the time needed to rearm after having fired -// > (3) range : the range in locations ("path") -// > (4) cost : upgrade cost for this level (for each of atk, spd, rng) -// * tower_sp : set the Super Power of a tower -// > (0) type : the unique identifier for this tower type -// > (1) super_cost -// > (2) super_power -// * enemy : will setup a new enemy type -// > (0) type : the unique identifier for this enemy type -// > (1) hp : life/hit points the enemy can take -// > (2) speed : speed in paths per tick -// > (3) bits : how much bits received on enemy death -// * wave : configure a new wave TODO -// -// Add commands: -// * add_tower: add a new tower on the map (will cost bits) -// > (0) type : the type of tower to add -// > (1) X position: the X coordinate -// > (2) Y position: the Y coordinate -// * add_enemy : add a new enemy in the current wave (can create the first one) -// > type : the enemy type -// > id : the *unique* id of this enemy -// > start_tick : the tick at which it will appear (wave-relative) -// -// Other commands: -// * display : will toggle display (debug) mode -// * quit : will immediately exit the game -// - -// TODO: configuration: -// tower: configTower('1', ...) -// enemy: configEnemy('a', ...) - int is_cmd(cstring_t *line, const char cmd[]) { if (cstring_starts_with(line->string, cmd, 0)) { int first_non_space = strlen(cmd); @@ -108,7 +40,7 @@ int is_cmd(cstring_t *line, const char cmd[]) { return 0; } -command_t *reader_readnext(FILE *file) { +command_t *reader_readnext(FILE *file, int displayMode) { static cstring_t *line = NULL; if (!line) line = new_cstring(); @@ -116,17 +48,20 @@ command_t *reader_readnext(FILE *file) { command_t *cmd = NULL; if (cstring_readline(line, file)) { + if (displayMode) + fprintf(stderr, "Reading line: <%s>\n", line->string); + cmd = new_command(COMMAND_UNKNOWN); if (is_cmd(line, "")) { // also checks for "#" cmd->type = COMMAND_COMMENT; } else if (is_cmd(line, ".")) { cmd->type = COMMAND_TICK; - if (command_read_count(cmd, line->string)) + if (cmd->data && cmd->data->count) cmd->type = COMMAND_INVALID; } else if (is_cmd(line, "display")) { cmd->type = COMMAND_DISPLAY; - if (command_read_count(cmd, line->string)) + if (cmd->data && cmd->data->count) cmd->type = COMMAND_INVALID; } else if (is_cmd(line, "readfile")) { cmd->type = COMMAND_READFILE; @@ -139,41 +74,37 @@ command_t *reader_readnext(FILE *file) { cmd->type = COMMAND_MAP; if (!command_read_int(cmd, line->string, 0, 1)) cmd->type = COMMAND_INVALID; - if (command_read_count(cmd, line->string) != 2) + if (!cmd->data || cmd->data->count != 2) cmd->type = COMMAND_INVALID; } else if (is_cmd(line, "life")) { cmd->type = COMMAND_LIFE; if (!command_read_int(cmd, line->string, 0, 0)) cmd->type = COMMAND_INVALID; - if (command_read_count(cmd, line->string) > 2) - cmd->type = COMMAND_INVALID; - if (command_read_count(cmd, line->string) != 1) + if (!cmd->data || cmd->data->count != 1) cmd->type = COMMAND_INVALID; } else if (is_cmd(line, "bits")) { cmd->type = COMMAND_BITS; if (!command_read_int(cmd, line->string, 0, 0)) cmd->type = COMMAND_INVALID; - if (command_read_count(cmd, line->string) > 2) - cmd->type = COMMAND_INVALID; - if (command_read_count(cmd, line->string) != 1) + if (!cmd->data || cmd->data->count != 1) cmd->type = COMMAND_INVALID; } else if (is_cmd(line, "path")) { cmd->type = COMMAND_PATH; if (!command_read_int(cmd, line->string, 0, -1)) cmd->type = COMMAND_INVALID; - if ((command_read_count(cmd, line->string) % 2) == 1) + if (!cmd->data || (cmd->data->count % 2) == 1) cmd->type = COMMAND_INVALID; } else if (is_cmd(line, "tower")) { cmd->type = COMMAND_TOWER; - if (!command_read_int(cmd, line->string, 0, 1)) + if (!command_read_int(cmd, line->string, 0, 0)) cmd->type = COMMAND_INVALID; - if (command_read_count(cmd, line->string) != 2) + if (!cmd->data || cmd->data->count != 1) cmd->type = COMMAND_INVALID; } else if (is_cmd(line, "tower_lvl")) { cmd->type = COMMAND_TOWER_LVL; if (!command_read_int(cmd, line->string, 0, 4)) cmd->type = COMMAND_INVALID; - if (command_read_count(cmd, line->string) != 5) + if (!cmd->data || cmd->data->count != 5) cmd->type = COMMAND_INVALID; } else if (is_cmd(line, "tower_sp")) { cmd->type = COMMAND_TOWER_SP; @@ -182,28 +113,26 @@ command_t *reader_readnext(FILE *file) { cmd->type = COMMAND_ENEMY; if (!command_read_int(cmd, line->string, 0, 3)) cmd->type = COMMAND_INVALID; - if (command_read_count(cmd, line->string) != 4) + if (!cmd->data || cmd->data->count != 4) cmd->type = COMMAND_INVALID; } else if (is_cmd(line, "wave")) { cmd->type = COMMAND_WAVE; // TODO } else if (is_cmd(line, "add_tower")) { cmd->type = COMMAND_ADD_TOWER; - if (!command_read_int(cmd, line->string, 0, 2)) + if (!command_read_int(cmd, line->string, 0, 3)) cmd->type = COMMAND_INVALID; - if (command_read_count(cmd, line->string) != 3) + if (!cmd->data || cmd->data->count != 4) cmd->type = COMMAND_INVALID; } else if (is_cmd(line, "add_enemy")) { cmd->type = COMMAND_ADD_ENEMY; if (!command_read_int(cmd, line->string, 0, 2)) cmd->type = COMMAND_INVALID; - if (command_read_count(cmd, line->string) != 3) + if (!cmd->data || cmd->data->count != 3) cmd->type = COMMAND_INVALID; } else if (is_cmd(line, "quit")) { cmd->type = COMMAND_QUIT; - if (command_read_count(cmd, line->string)) - cmd->type = COMMAND_INVALID; - if (command_read_count(cmd, line->string)) + if (cmd->data && cmd->data->count) cmd->type = COMMAND_INVALID; } } diff --git a/src/tdef/reader.h b/src/tdef/reader.h index c2a7a48..25b5e35 100644 --- a/src/tdef/reader.h +++ b/src/tdef/reader.h @@ -3,9 +3,11 @@ * @author Niki * @date 2025 * - * @brief bla + * @brief read commands from a file * - * blablabla + * This reader will accept all external commands and process them so they + * are ready to be used by the engine. + * It can display the commands as they are read if the display_mode is set. * */ @@ -15,8 +17,75 @@ #include #include "command.h" -// NULL = eof -command_t *reader_readnext(FILE *file); +/** + * Read the next command (next line) from a file. + * + * Commands are given in the form of: + * KEYWORD + * or: + * KEYWORD: [parameters] + * or: + * # comment... (mostly used for data files) + * + * Parameters are given in the form of: + * int_parameter1, int_parameter2... + * or: + * "string parameter", "file parameter" + * + * Note that white space around the separators (':' or ',') are allowed. + * + * Engine commands: + * > . : will process a tick in the game engine + * > readfile : will read commands from the given file(s) -- one minimum + * > (n) "file1..." + * + * Setup commands: + * > map: will setup the map (and reset the path) + * > (0) width + * > (1) height + * > life: will set the number of life points + * > (0) lives + * > bits: will set the current bits (money) balance + * > (0) bits : the new total available bits amount + * > path: will setup the enemies path (cumulative and ordered) + * > (n) x1,y1, x2,y2... + * > tower: will setup a new tower type + * > (0) type : the unique identifier for this tower type + * > tower_lvl: will configure a tower upgrade level + * > (0) type : the unique identifier for this tower type + * > (1) attack : how many points dealt to enemies + * > (2) speed : the time needed to rearm after having fired + * > (3) range : the range in locations ("path") + * > (4) cost : upgrade cost for this level (for each of atk, spd, rng) + * > tower_sp: set the super power of a tower + * > (0) type : the unique identifier for this tower type + * > (1) sp_cost : the cost to enable the super power + * > (2) sp_power : the super power type TODO + * > enemy: will setup a new enemy type + * > (0) type : the unique identifier for this enemy type + * > (1) hp : life/hit points the enemy can take + * > (2) speed : speed in paths per tick + * > (3) bits : how much bits received on enemy death + * > wave : configure a new wave TODO + * + * Add commands: + * > add_tower: add a new tower on the map (will cost bits) + * > (0) type : the type of tower to add + * > (1) id : the *unique* id of the tower + * > (2) X position: the X coordinate + * > (3) Y position: the Y coordinate + * > add_enemy: add a new enemy in the current wave (can create the first one) + * > (0) type : the enemy type + * > (1) id : the *unique* id of this enemy + * > (2) start_tick: the tick at which it will appear (wave-relative) + * + * Other commands: + * > display: will toggle display (debug) mode + * > quit: will immediately exit the game + * + * @return the command or NULL at enf of file + */ +command_t *reader_readnext(FILE *file, int display_mode); #endif /* READER_H */ diff --git a/src/tdef/tdef.c b/src/tdef/tdef.c index 13227e7..e1536be 100644 --- a/src/tdef/tdef.c +++ b/src/tdef/tdef.c @@ -117,20 +117,29 @@ int process_cmd(command_t *cmd, engine_t *engine) { engine->bits = command_get_int(cmd, 0); break; case COMMAND_PATH: - setup_path( - engine, - command_get_int(cmd, 0), - command_get_int(cmd, 1) - ); + for (int i = 0 ; i < cmd->data->count ; i+=2) { + setup_path( + engine, + command_get_int(cmd, i+0), + command_get_int(cmd, i+1) + ); + } break; case COMMAND_TOWER: setup_tower_base( engine, - command_get_int(cmd, 0), - command_get_int(cmd, 1) + command_get_int(cmd, 0) ); break; case COMMAND_TOWER_LVL: + setup_tower_lvl( + engine, + command_get_int(cmd, 0), + command_get_int(cmd, 1), + command_get_int(cmd, 2), + command_get_int(cmd, 3), + command_get_int(cmd, 4) + ); break; case COMMAND_TOWER_SP: // TODO @@ -154,7 +163,8 @@ int process_cmd(command_t *cmd, engine_t *engine) { engine, command_get_int(cmd, 0), command_get_int(cmd, 1), - command_get_int(cmd, 2) + command_get_int(cmd, 2), + command_get_int(cmd, 3) ); break; case COMMAND_ADD_ENEMY: @@ -203,11 +213,11 @@ int process_cmd(command_t *cmd, engine_t *engine) { } void read_commands(engine_t *engine, FILE *file) { - command_t *cmd = reader_readnext(file); + command_t *cmd = reader_readnext(file, displayMode); while (cmd) { if (process_cmd(cmd, engine)) { free_command(cmd); - cmd = reader_readnext(file); + cmd = reader_readnext(file, displayMode); } else { free_command(cmd); cmd = NULL; diff --git a/src/tdef/tower.c b/src/tdef/tower.c index 68ffd57..4249314 100644 --- a/src/tdef/tower.c +++ b/src/tdef/tower.c @@ -19,18 +19,20 @@ #include #include +#include #include "tower.h" #include "path.h" -array_t *enemies_in_range(tower_t *self, array_t *enemies); +array_t *enemies_in_range(tower_t *self, array_t *paths, array_t *enemies); array_t *paths_in_range(tower_t *self, array_t *paths); -int at_p_in(int x, int y, path_t *path, int speed); +int dist(path_t *path, int x, int y); // in loc distance +int when(path_t *path, int x, int y, int speed); // in ticks path_t *get_target(tower_t *self, array_t *enemies, array_t *paths); -tower_t *new_tower(tower_base_t *base, int x, int y) { +tower_t *new_tower(tower_base_t *base, int id, int x, int y) { tower_t *self = malloc(sizeof(tower_t)); - if (!init_tower(self, base, x, y)) { + if (!init_tower(self, base, id, x, y)) { free(self); self = NULL; } @@ -38,19 +40,21 @@ tower_t *new_tower(tower_base_t *base, int x, int y) { return self; } -int init_tower(tower_t *self, tower_base_t *base, int x, int y) { +int init_tower(tower_t *self, tower_base_t *base, int id, int x, int y) { size_t sz = sizeof(self->CNAME); strncpy(self->CNAME, "[tower ", sz); self->CNAME[sz - 2] = ']'; self->CNAME[sz - 1] = '\0'; self->base = base; + self->id = id; self->x = x; self->y = y; self->lvl_speed = 0; self->lvl_range = 0; self->lvl_range = 0; self->proj_speed = 1; + self->target = TARGET_FIRST; self->super = 0; self->fire_delayed = 0; @@ -73,7 +77,6 @@ void uninit_tower(tower_t *self) { int init_tower_base(tower_base_t *self, int type) { self->type = type; self->stats_sz = 0; - self->cost = 0; self->super_cost = 0; self->super_power = 0; memset(self->stats, '\0', sizeof(self->stats)); @@ -97,70 +100,93 @@ proj_t *tower_fire(tower_t *self, int current_tick, array_t *enemies, int attack = self->base->stats[self->lvl_attack].attack; int speed = self->base->stats[self->lvl_speed ].speed; - int range = self->base->stats[self->lvl_range ].range; proj_t *proj = new_proj(target->x, target->y); proj->desc = self->projectile; proj->desc.damage = attack; - proj->boom_tick = current_tick + 3; // TODO: compute from speed + proj->boom_tick = current_tick + when(target, self->x, self->y, speed); self->fire_delayed = speed; return proj; } -array_t *enemies_in_range(tower_t *self, array_t *enemies) { +array_t *enemies_in_range(tower_t *self, array_t *paths, array_t *enemies) { + int range = self->base->stats[self->lvl_range ].range; array_t *in_range = new_array(sizeof(enemy_t*), enemies->count/2+1); + path_t *p; + array_loop (enemies, ptr, enemy_t *) { + enemy_t *e = *ptr; + if (!e || !e->alive || e->index < 0) + continue; + + p = (path_t *)array_get(paths, e->index); + if (dist(p, self->x, self->y) <= range) { + enemy_t **ee = array_new(in_range); + *ee = e; + } + } + return in_range; } array_t *paths_in_range(tower_t *self, array_t *paths) { + int range = self->base->stats[self->lvl_range ].range; array_t *in_range = new_array(sizeof(path_t *), paths->count/2+1); + array_loop (paths, p, path_t) { + if (dist(p, self->x, self->y) <= range) { + path_t **pp = array_new(in_range); + *pp = p; + } + } + return in_range; } -int at_p_in(int x, int y, path_t *path, int speed) { - // TODO - return 1; +int dist(path_t *path, int x, int y) { + int dx = abs(path->x - x); + int dy = abs(path->y - y); + return ceil(sqrt(dx*dx + dy*dy)); +} + +int when(path_t *path, int x, int y, int speed) { + double distance = dist(path, x, y); + return ceil(distance / speed); } path_t *get_target(tower_t *self, array_t *enemies, array_t *paths) { enemy_t *e_target = NULL; path_t *p_target = NULL; - // TODO: move to configurable - #define TARGET_FIRST 0 - #define TARGET_LAST 1 - #define TARGET_WEAKER 2 - #define TARGET_STRONGER 3 - int mode = TARGET_FIRST; - - // TODO: move paths_in_range as a member of tower_t, cache it - array_t *e_in_range = enemies_in_range(self, enemies); + array_t *e_in_range = enemies_in_range(self, paths, enemies); array_t *p_in_range = (e_in_range->count) ? paths_in_range(self, paths) - : new_array(sizeof(enemy_t*), 0); - + : new_array(sizeof(enemy_t*), 1); + if (e_in_range->count && p_in_range->count) array_loop (p_in_range, pptr, path_t*) { path_t *p = *pptr; - int proj_at_p_in = at_p_in( - self->x, self->y, p, self->proj_speed - ); + int proj_at_p_in = when(p, self->x, self->y, self->proj_speed); array_loop (e_in_range, eptr, enemy_t*) { enemy_t *e = *eptr; - path_t *e_p = (path_t*)array_get(paths, e->index); - int enemy_at_p_in = at_p_in( - e_p->x, e_p->y, p, e->base->speed - ); + if (!e->alive) + continue; + + double distance = p->index - e->index; + if (distance < 0) + continue; + + int enemy_at_p_in = distance / e->base->speed; + if (enemy_at_p_in * e->base->speed != distance) + continue; // path will be skipped if (proj_at_p_in == enemy_at_p_in) { int take_it = 0; - switch(mode) { + switch(self->target) { case TARGET_FIRST: if (!e_target) take_it = 1; diff --git a/src/tdef/tower.h b/src/tdef/tower.h index 2ad6fa4..b901bad 100644 --- a/src/tdef/tower.h +++ b/src/tdef/tower.h @@ -15,6 +15,13 @@ #include "cutils/array.h" #include "proj.h" +typedef enum { + TARGET_FIRST, + TARGET_LAST, + TARGET_WEAKER, + TARGET_STRONGER, +} target_t; + typedef struct { int attack; int speed; // fire_delay in ticks @@ -26,7 +33,6 @@ typedef struct { int type; // identify the unique tower type tower_lvl_t stats[6]; int stats_sz; // how many are used - int cost; // base cost int super_cost; int super_power; } tower_base_t; @@ -39,6 +45,7 @@ typedef struct { typedef struct { char CNAME[10]; tower_base_t *base; + int id; int x; int y; // levels are 0-based @@ -46,6 +53,7 @@ typedef struct { int lvl_speed; // speed = fire_delay in ticks int lvl_range; int proj_speed; // speed of the proj in locs per tick + target_t target; int super; int fire_delayed; // in ticks still to wait int cost; // total *resell* cost in bits (all upgrades included) @@ -56,6 +64,7 @@ typedef struct { * Create a new tower. * * @param base the type of tower (will NOT be free'd by this object) + * @param id the unique id of the tower * @param x the X coordinate * @param y the Y coordinate * @@ -66,16 +75,17 @@ typedef struct { * * @return a new tower (you must later call `free_tower()`) */ -tower_t *new_tower(tower_base_t *base, int x, int y); +tower_t *new_tower(tower_base_t *base, int id, int x, int y); /** * Initialise a new tower. * * @param base the type of tower (will NOT be free'd by this object) + * @param id the unique id of the tower * @param x the X coordinate * @param y the Y coordinate */ -int init_tower(tower_t *self, tower_base_t *base, int x, int y); +int init_tower(tower_t *self, tower_base_t *base, int id, int x, int y); /** * Free the resources held for the given tower: you must not use it any more. diff --git a/src/tdef/wave.c b/src/tdef/wave.c index 0272dff..82b7568 100644 --- a/src/tdef/wave.c +++ b/src/tdef/wave.c @@ -79,7 +79,7 @@ enemy_t *wave_add_enemy( } enemy_t *wave_next_enemy(wave_t *self, int tick) { - if (self->done) + if (self->done || tick < self->start_tick) return NULL; enemy_t *enemy; @@ -88,7 +88,8 @@ enemy_t *wave_next_enemy(wave_t *self, int tick) { else enemy = array_next(self->enemies, self->last_enemy); - if (enemy && (self->start_tick + enemy->start_tick) >= tick) { + if (enemy && !enemy->alive // enemy must not *yet* be alive + && (self->start_tick + enemy->start_tick) >= tick) { self->last_enemy = enemy; return enemy; } diff --git a/src/tdef/wave.h b/src/tdef/wave.h index 81ead92..20d4579 100644 --- a/src/tdef/wave.h +++ b/src/tdef/wave.h @@ -23,7 +23,7 @@ */ typedef struct { char CNAME[10]; - size_t start_tick; + size_t start_tick; // could be started early, value must change array_t *enemies; // enemy_t array_t *tmp_e; // enemy_t enemy_t *last_enemy; -- 2.27.0