# 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
}
}
-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);
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);
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);
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);
}
// Final parameter
- if (start < stop) {
+ if (start <= stop) {
if ((howmany_read >= pstart) &&
((howmany_read <= pstop) || (pstop < 0))) {
char **ptr = array_new(tmp);
// 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);
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;
return 1;
}
+
self->current_wave = i;
event_t event;
init_event(&event, EVENT_WAVE);
- event.x = i,
+ event.id = i,
array_push(self->events, &event);
}
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)
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;
}
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);
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;
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)
// 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);
// 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);
self->type = type;
self->error = EEVENT_SUCCESS;
+ self->id = 0;
self->x = -1;
self->y = -1;
self->move_to_x = -1;
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);
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;
/**
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++;
}
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);
}
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);
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) {
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);
}
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
event.type = EVENT_BREACH;
}
+ if (event.x < 0 || event.y < 0)
+ event.type = EVENT_ENTER;
array_push(events, &event);
return;
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;
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);
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)
/*
- * 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);
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();
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;
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;
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;
}
}
* @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 */
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
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:
}
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;
#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;
}
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;
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));
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;
#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
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;
typedef struct {
char CNAME[10];
tower_base_t *base;
+ int id;
int x;
int y;
// levels are 0-based
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)
* 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
*
*
* @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.
}
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;
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;
}
*/
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;