many fixes and additions
authorNiki <niki@nikiroo.be>
Fri, 18 Apr 2025 22:31:36 +0000 (00:31 +0200)
committerNiki <niki@nikiroo.be>
Fri, 18 Apr 2025 22:31:36 +0000 (00:31 +0200)
17 files changed:
setup.debug
src/tdef/command.c
src/tdef/command.h
src/tdef/enemy.c
src/tdef/engine.c
src/tdef/engine.h
src/tdef/event.c
src/tdef/event.h
src/tdef/map.c
src/tdef/map.h
src/tdef/reader.c
src/tdef/reader.h
src/tdef/tdef.c
src/tdef/tower.c
src/tdef/tower.h
src/tdef/wave.c
src/tdef/wave.h

index 98af49c4ab5a9ce9e3c44010aa65b6a2e3ec796b..2be55e53803a6de380fa2854e13997cef69a3a54 100644 (file)
 # 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
 
index 6280cf8ef976d95d169e4f723941ddcd91ecb069..26f73fc49e638ba842e56cfc6dc4bac9d74492f1 100644 (file)
@@ -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);
index a18caaa2c90ed9f33624f3d8fd065855f0148e33..f236a55999debb277985d152b13bfc51a5addcfd 100644 (file)
@@ -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);
 
index 9fdd2af7d64afefe26afecb8326d3c3dddf71db7..2375b918a45665f6b6d0234e7d9154a94a478f22 100644 (file)
@@ -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;
 }
+
index 447d63d0ba9cffae7183b406b9c5d7d6d5426685..8fcae418ed353be4d63c511dc21720b64da5bcd7 100644 (file)
@@ -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)
index 42814893779154abb9d98ccecb524e79bb811e9b..838633b349510951204ddb89d2a50f28c288ff6b 100644 (file)
@@ -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);
 
index a3a08a01657577558e89f8a6f0c181320ae18c4c..f18e0955b3269b912625b53c286d53a794118a5f 100644 (file)
@@ -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);
index 76a189fb7fd6a4abe5fc35e13af002594d8076d2..639549885161f88350427ca7ecee2ac21b0c7937 100644 (file)
@@ -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;
 
 /**
index 2b8a46015332109bad3b8affbf6d50c2d5208038..93de0d8fc778985eb6b4342772ac701d67d00c63 100644 (file)
@@ -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);
index b8e83c2ab031c9d727765fe71b8b5ed70f8f07e7..b1facdad6b5eefe74368a00ab2f55d305b1a8763 100644 (file)
@@ -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)
index 0bc324b39a46dfcc29dfdf07892ce2f4bb133604..c28d303182472e6ad179bb173ac5a4c417d2cbef 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * TDef: small reader defense game
+ * TDef: small tower defense game
  *
  * Copyright (C) 2025 Niki Roo
  *
 #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;
                }
        }
index c2a7a4822542306703d627d7c6ca1fc4f76a21d6..25b5e35c8a1698fb58c7d6f3084865260711a713 100644 (file)
@@ -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.
  *
  */
 
 #include <stdio.h>
 #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 */
 
index 13227e760e255e3c1431410e5de9d2d81b7b5b2e..e1536be4a3069e7632cf3432bc46d108857feaa5 100644 (file)
@@ -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;
index 68ffd57bde229df6005f2c684d7fa34de2904433..4249314384d4acf5a7938af088b57560e1ee594d 100644 (file)
 
 #include <string.h>
 #include <stdlib.h>
+#include <math.h>
 
 #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;
index 2ad6fa481f485d67b55ec7d09b61a801a723f4ae..b901badf0ea0018a2d9b9c97e58759a750054d06 100644 (file)
 #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.
index 0272dff9e988733d04d58fafa80f950b743597ce..82b7568f0d8e5d24eb49b9ad61f3c69f46cd52ae 100644 (file)
@@ -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;
        }
index 81ead921e258d113640f596c82706d21bf2e16cf..20d4579565f262df6a45540c291ef8ce1f79fec1 100644 (file)
@@ -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;