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}