tanks

Blow up enemy tanks using code
git clone https://git.woozle.org/neale/tanks.git

Neale Pickett  ·  2024-12-05

forftanks.c

  1#include "ctanks.h"
  2#include "dump.h"
  3#include "forf.h"
  4#include <math.h>
  5#include <stdio.h>
  6#include <stdlib.h>
  7#include <string.h>
  8#include <sys/stat.h>
  9#include <sys/types.h>
 10#include <unistd.h>
 11
 12#define MAX_TANKS 50
 13#define ROUNDS 500
 14#define SPACING 150
 15
 16#define LENV_SIZE 100
 17
 18#define DSTACK_SIZE 200
 19#define CSTACK_SIZE 500
 20#define MEMORY_SIZE 10
 21
 22struct forftank {
 23  struct forf_env env;
 24  int error_pos;
 25  char color[8]; /* "#ff0088" */
 26  char name[50];
 27  char *path;
 28  unsigned int uid;
 29
 30  struct forf_stack _prog;
 31  struct forf_value _progvals[CSTACK_SIZE];
 32  struct forf_stack _cmd;
 33  struct forf_value _cmdvals[CSTACK_SIZE];
 34  struct forf_stack _data;
 35  struct forf_value _datavals[DSTACK_SIZE];
 36  struct forf_memory _mem;
 37  long _memvals[MEMORY_SIZE];
 38};
 39
 40#ifndef NODEBUG
 41void forf_print_val(struct forf_value *val) {
 42  switch (val->type) {
 43  case forf_type_number:
 44    printf("%ld", val->v.i);
 45    break;
 46  case forf_type_proc:
 47    printf("[proc %p]", val->v.p);
 48    break;
 49  case forf_type_stack_begin:
 50    printf("{");
 51    break;
 52  case forf_type_stack_end:
 53    printf("}");
 54    break;
 55  }
 56}
 57
 58void forf_print_stack(struct forf_stack *s) {
 59  size_t pos;
 60
 61  for (pos = 0; pos < s->top; pos += 1) {
 62    forf_print_val(&(s->stack[pos]));
 63    printf(" ");
 64  }
 65}
 66
 67void forf_dump_stack(struct forf_stack *s) {
 68  printf("Stack at %p: ", s);
 69  forf_print_stack(s);
 70  printf("\n");
 71}
 72#endif
 73
 74/*
 75 *
 76 * Forf API
 77 *
 78 */
 79
 80/** Has the turret recharged? */
 81void forf_tank_fire_ready(struct forf_env *env) {
 82  struct tank *tank = (struct tank *)env->udata;
 83
 84  forf_push_num(env, tank_fire_ready(tank));
 85}
 86
 87/** Fire! */
 88void forf_tank_fire(struct forf_env *env) {
 89  struct tank *tank = (struct tank *)env->udata;
 90
 91  tank_fire(tank);
 92}
 93
 94/** Set desired speed */
 95void forf_tank_set_speed(struct forf_env *env) {
 96  struct tank *tank = (struct tank *)env->udata;
 97  long right = forf_pop_num(env);
 98  long left = forf_pop_num(env);
 99
100  tank_set_speed(tank, left, right);
101}
102
103/** Get the current turret angle */
104void forf_tank_get_turret(struct forf_env *env) {
105  struct tank *tank = (struct tank *)env->udata;
106  float angle = tank_get_turret(tank);
107
108  forf_push_num(env, rad2deg(angle));
109}
110
111/** Set the desired turret angle */
112void forf_tank_set_turret(struct forf_env *env) {
113  struct tank *tank = (struct tank *)env->udata;
114  long angle = forf_pop_num(env);
115
116  tank_set_turret(tank, deg2rad(angle));
117}
118
119/** Is a sensor active? */
120void forf_tank_get_sensor(struct forf_env *env) {
121  struct tank *tank = (struct tank *)env->udata;
122  long sensor_num = forf_pop_num(env);
123
124  forf_push_num(env, tank_get_sensor(tank, sensor_num));
125}
126
127/** Set the LED state */
128void forf_tank_set_led(struct forf_env *env) {
129  struct tank *tank = (struct tank *)env->udata;
130  long active = forf_pop_num(env);
131
132  tank_set_led(tank, active);
133}
134
135/** Pick a random number */
136void forf_proc_random(struct forf_env *env) {
137  long max = forf_pop_num(env);
138
139  if (max < 1) {
140    forf_push_num(env, 0);
141    return;
142  }
143
144  forf_push_num(env, rand() % max);
145}
146
147/* Tanks lexical environment */
148struct forf_lexical_env tanks_lenv_addons[] = {
149    {"fire-ready?", forf_tank_fire_ready},
150    {"fire!", forf_tank_fire},
151    {"set-speed!", forf_tank_set_speed},
152    {"get-turret", forf_tank_get_turret},
153    {"set-turret!", forf_tank_set_turret},
154    {"sensor?", forf_tank_get_sensor},
155    {"set-led!", forf_tank_set_led},
156    {"random", forf_proc_random},
157    {NULL, NULL}};
158
159/*
160 *
161 * Filesystem stuff
162 *
163 */
164
165int ft_read_file(char *ptr, size_t size, char *dir, char *fn) {
166  char path[256];
167  FILE *f = NULL;
168  int ret;
169  int success = 0;
170
171  do {
172    snprintf(path, sizeof(path), "%s/%s", dir, fn);
173    f = fopen(path, "r");
174    if (!f)
175      break;
176
177    ret = fread(ptr, 1, size - 1, f);
178    ptr[ret] = '\0';
179    if (!ret)
180      break;
181
182    success = 1;
183  } while (0);
184
185  if (f)
186    fclose(f);
187  if (!success) {
188    return 0;
189  }
190  return 1;
191}
192
193void ft_bricked_tank(struct tank *tank, void *ignored) {
194  /* Do nothing, the tank is comatose */
195}
196
197void ft_run_tank(struct tank *tank, struct forftank *ftank) {
198  int ret;
199
200  /* Copy program into command stack */
201  forf_stack_copy(&ftank->_cmd, &ftank->_prog);
202  forf_stack_reset(&ftank->_data);
203  ret = forf_eval(&ftank->env);
204  if (!ret) {
205    fprintf(stderr, "Error in %s: %s\n", ftank->path,
206            forf_error_str[ftank->env.error]);
207  }
208}
209
210int ft_read_program(struct forftank *ftank, struct tank *tank,
211                    struct forf_lexical_env *lenv, char *path) {
212  char progpath[256];
213  FILE *f;
214
215  /* Open program */
216  snprintf(progpath, sizeof(progpath), "%s/program", path);
217  f = fopen(progpath, "r");
218  if (!f)
219    return 0;
220
221  /* Parse program */
222  ftank->error_pos = forf_parse_file(&ftank->env, f);
223  fclose(f);
224  if (ftank->error_pos) {
225    return 0;
226  }
227
228  /* Back up the program so we can run it over and over without
229     needing to re-parse */
230  forf_stack_copy(&ftank->_prog, &ftank->_cmd);
231
232  tank_init(tank, (tank_run_func *)ft_run_tank, ftank);
233
234  return 1;
235}
236
237void ft_tank_init(struct forftank *ftank, struct tank *tank,
238                  struct forf_lexical_env *lenv) {
239  /* Set up forf environment */
240  forf_stack_init(&ftank->_prog, ftank->_progvals, CSTACK_SIZE);
241  forf_stack_init(&ftank->_cmd, ftank->_cmdvals, CSTACK_SIZE);
242  forf_stack_init(&ftank->_data, ftank->_datavals, DSTACK_SIZE);
243  forf_memory_init(&ftank->_mem, ftank->_memvals, MEMORY_SIZE);
244  forf_env_init(&ftank->env, lenv, &ftank->_data, &ftank->_cmd, &ftank->_mem,
245                tank);
246}
247
248void ft_read_sensors(struct tank *tank, char *path) {
249  int i;
250
251  for (i = 0; i < TANK_MAX_SENSORS; i += 1) {
252    int ret;
253    char fn[10];
254    char s[20];
255    char *p = s;
256    long range;
257    long angle;
258    long width;
259    long turret;
260
261    snprintf(fn, sizeof(fn), "sensor%d", i);
262    ret = ft_read_file(s, sizeof(s), path, fn);
263    if (!ret) {
264      s[0] = 0;
265    }
266    range = strtol(p, &p, 0);
267    angle = strtol(p, &p, 0);
268    width = strtol(p, &p, 0);
269    turret = strtol(p, &p, 0);
270
271    tank->sensors[i].range = min(range, TANK_SENSOR_RANGE);
272    tank->sensors[i].angle = deg2rad(angle % 360);
273    tank->sensors[i].width = deg2rad(width % 360);
274    tank->sensors[i].turret = (0 != turret);
275  }
276}
277
278// Crudely clean a string for json output
279void clean(char *s) {
280  for (; *s; s++) {
281    switch (*s) {
282    case '\0' ... '\037':
283    case '"':
284    case '\\':
285      *s = '\0';
286      return;
287    }
288  }
289}
290
291int ft_read_tank(struct forftank *ftank, struct tank *tank,
292                 struct forf_lexical_env *lenv, char *path) {
293  int ret;
294
295  ftank->path = path;
296
297  /* Store uid */
298  {
299    struct stat s;
300    if (-1 == stat(path, &s)) {
301      ftank->uid = 0;
302    } else {
303      ftank->uid = (unsigned int)s.st_ino;
304    }
305  }
306
307  /* What is your name? */
308  ret = ft_read_file(ftank->name, sizeof(ftank->name), path, "name");
309  if (!ret) {
310    snprintf(ftank->name, sizeof(ftank->name), "i:%x", ftank->uid);
311  }
312  clean(ftank->name);
313
314  /* What is your quest? */
315  ft_tank_init(ftank, tank, lenv);
316  ret = ft_read_program(ftank, tank, lenv, path);
317  if (ret) {
318    ft_read_sensors(tank, path);
319  } else {
320    /* Brick the tank */
321    tank_init(tank, ft_bricked_tank, NULL);
322  }
323
324  /* What is your favorite color? */
325  ret = ft_read_file(ftank->color, sizeof(ftank->color), path, "color");
326  if (!ret) {
327    strncpy(ftank->color, "#808080", sizeof(ftank->color));
328  }
329  clean(ftank->color);
330
331  return 1;
332}
333
334void print_header(FILE *f, struct tanks_game *game, int seed) {
335  fprintf(f, "{\n");
336  fprintf(f, "  \"seed\": %d,\n", seed);
337  fprintf(f, "  \"field\": [%d,%d],\n", (int)game->size[0], (int)game->size[1]);
338}
339
340void print_rounds(FILE *f, struct tanks_game *game, struct tank *tanks,
341                  int ntanks) {
342  int alive;
343
344  fprintf(f, "  \"rounds\": [\n");
345
346  /* Run rounds */
347  alive = ntanks;
348  for (int i = 0; (alive > 1) && (i < ROUNDS); i += 1) {
349    if (i > 0) {
350      fprintf(f, ",\n");
351    }
352
353    tanks_run_turn(game, tanks, ntanks);
354    fprintf(f, "    [");
355    alive = ntanks;
356    for (int j = 0; j < ntanks; j += 1) {
357      struct tank *t = &(tanks[j]);
358
359      int k;
360      int flags = 0;
361      int sensors = 0;
362
363      if (j > 0) {
364        fprintf(f, ",");
365      }
366
367      for (k = 0; k < TANK_MAX_SENSORS; k += 1) {
368        if (t->sensors[k].triggered) {
369          sensors |= (1 << k);
370        }
371      }
372      if (t->turret.firing) {
373        flags |= 1;
374      }
375      if (t->led) {
376        flags |= 2;
377      }
378      if (t->killer) {
379        alive -= 1;
380        flags |= 4;
381      }
382      fprintf(f, "[%d,%d,%.2f,%.2f,%d,%d]", (int)t->position[0],
383              (int)(t->position[1]), t->angle, t->turret.current, flags,
384              sensors);
385    }
386    fprintf(f, "]");
387  }
388
389  fprintf(f, "\n  ],\n");
390}
391
392void print_standings(FILE *f, struct forftank *ftanks, struct tank *tanks,
393                     int ntanks) {
394
395  fprintf(f, "  \"tanks\": [\n");
396  for (int i = 0; i < ntanks; i += 1) {
397    int killer = -1;
398    int kills = 0;
399    for (int j = 0; j < ntanks; j += 1) {
400      if (tanks[i].killer == &(tanks[j])) {
401        killer = j;
402      }
403      if (tanks[j].killer == &(tanks[i])) {
404        kills += 1;
405      }
406    }
407
408    if (i > 0) {
409      fprintf(f, ",\n");
410    }
411    fprintf(f, "    {\n");
412    fprintf(f, "      \"uid\": %d,\n", ftanks[i].uid);
413    fprintf(f, "      \"color\": \"%s\",\n", ftanks[i].color);
414    fprintf(f, "      \"name\": \"%s\",\n", ftanks[i].name);
415    fprintf(f, "      \"death\": \"%s\",\n", tanks[i].cause_death);
416    fprintf(f, "      \"killer\": %d,\n", killer);
417    fprintf(f, "      \"kills\": %d,\n", kills);
418    fprintf(f, "      \"errorPos\": %d,\n", ftanks[i].error_pos);
419    fprintf(f, "      \"error\": \"%s\",\n",
420            forf_error_str[ftanks[i].env.error]);
421    fprintf(f, "      \"sensors\": [\n");
422    for (int j = 0; j < TANK_MAX_SENSORS; j += 1) {
423      struct sensor *s = &(tanks[i].sensors[j]);
424
425      if (j > 0) {
426        fprintf(f, ",\n");
427      }
428
429      if (!s->range) {
430        fprintf(f, "        null");
431      } else {
432        fprintf(f,
433                "        "
434                "{\"range\":%d,\"angle\":%.2f,\"width\":%.2f,\"turret\":%s}",
435                (int)(s->range), s->angle, s->width,
436                s->turret ? "true" : "false");
437      }
438    }
439    fprintf(f, "\n      ]");
440    fprintf(f, "\n    }");
441  }
442
443  fprintf(f, "\n  ],\n");
444}
445
446void print_footer(FILE *f) {
447  fprintf(f,
448          "  \"\": null\n"); // sentry, so everything prior can end with a comma
449  fprintf(f, "}\n");
450}
451
452int main(int argc, char *argv[]) {
453  struct tanks_game game;
454  struct forftank myftanks[MAX_TANKS];
455  struct tank mytanks[MAX_TANKS];
456  struct forf_lexical_env lenv[LENV_SIZE];
457  int order[MAX_TANKS];
458  int ntanks = 0;
459  int i;
460  int seed;
461
462  lenv[0].name = NULL;
463  lenv[0].proc = NULL;
464  if ((!forf_extend_lexical_env(lenv, forf_base_lexical_env, LENV_SIZE)) ||
465      (!forf_extend_lexical_env(lenv, tanks_lenv_addons, LENV_SIZE))) {
466    fprintf(stderr, "Unable to initialize lexical environment.\n");
467    return 1;
468  }
469
470  /* We only need slightly random numbers */
471  {
472    char *s = getenv("SEED");
473    seed = atoi(s ? s : "");
474
475    if (!seed) {
476      seed = getpid();
477    }
478
479    srand(seed);
480  }
481
482  if ((argc < 2) || (argv[1][0] == '-')) {
483    fprintf(stderr, "usage: %s TANKDIR [TANKDIR...]\n", argv[0]);
484    return 1;
485  }
486
487  /* Every argument is a tank directory */
488  for (i = 1; ntanks < MAX_TANKS && i < argc; i += 1) {
489    if (ft_read_tank(&myftanks[ntanks], &mytanks[ntanks], lenv, argv[i])) {
490      ntanks += 1;
491    }
492  }
493
494  /* Calculate the size of the game board */
495  {
496    int x, y;
497
498    for (x = 1; x * x < ntanks; x += 1)
499      ;
500    y = ntanks / x;
501    if (ntanks % x) {
502      y += 1;
503    }
504
505    game.size[0] = x * SPACING;
506    game.size[1] = y * SPACING;
507  }
508
509  /* Shuffle the order we place things on the game board */
510  for (i = 0; i < ntanks; i += 1) {
511    order[i] = i;
512  }
513  for (i = 0; i < ntanks; i += 1) {
514    int j = rand() % ntanks;
515    int n = order[j];
516
517    order[j] = order[i];
518    order[i] = n;
519  }
520
521  /* Position tanks */
522  {
523    int x = SPACING / 2;
524    int y = SPACING / 2;
525
526    for (i = 0; i < ntanks; i += 1) {
527      mytanks[order[i]].position[0] = (float)x;
528      mytanks[order[i]].position[1] = (float)y;
529      mytanks[order[i]].angle = deg2rad(rand() % 360);
530      mytanks[order[i]].turret.current = deg2rad(rand() % 360);
531      mytanks[order[i]].turret.desired = mytanks[order[i]].turret.current;
532
533      x += SPACING;
534      if (x > game.size[0]) {
535        x %= (int)(game.size[0]);
536        y += SPACING;
537      }
538    }
539  }
540
541  print_header(stdout, &game, seed);
542  print_rounds(stdout, &game, mytanks, ntanks);
543  print_standings(stdout, myftanks, mytanks, ntanks);
544  print_footer(stdout);
545
546  return 0;
547}