Output actual JSON, and modernize it a little

This commit is contained in:
Neale Pickett 2024-11-05 16:34:07 -07:00
parent 49152d5128
commit a3cb9f8fe1
24 changed files with 146 additions and 298 deletions

1
.gitignore vendored
View File

@ -8,6 +8,7 @@ points
forftanks forftanks
designer.cgi designer.cgi
upload.cgi
forf.html forf.html
summary.html summary.html

View File

@ -1,21 +1,13 @@
BINARIES = forftanks upload.cgi DESTDIR ?= $(HOME)
HTML = forf.html procs.html intro.html designer.html debugger.html
WWW = style.css grunge.png designer.js figures.js tanks.js nav.html.inc jstanks.js
CFLAGS = -Wall CFLAGS = -Wall
all: $(BINARIES) $(HTML) BINARIES = forftanks upload.cgi
install: all: $(BINARIES)
install -d $(DESTDIR)/usr/bin
install run-tanks $(DESTDIR)/usr/bin
install forftanks $(DESTDIR)/usr/bin
install -d $(DESTDIR)/usr/lib/tanks install: $(BINARIES)
install designer.cgi $(DESTDIR)/usr/lib/tanks install -d $(DESTDIR)$(PREFIX)/bin
install $(HTML) $(DESTDIR)/usr/lib/tanks install $(BINARIES) $(DESTDIR)$(PREFIX)/bin
install $(WWW) $(DESTDIR)/usr/lib/tanks
cp -r examples $(DESTDIR)/usr/lib/tanks/examples
forftanks: forftanks.o ctanks.o forf.o forftanks: forftanks.o ctanks.o forf.o
forftanks: LDLIBS = -lm forftanks: LDLIBS = -lm
@ -24,9 +16,6 @@ forftanks.o: forf.h ctanks.h
forf.o: forf.c forf.h forf.o: forf.c forf.h
ctanks.o: ctanks.h ctanks.o: ctanks.h
%.html: %.html.m4 nav.html.inc
m4 $< > $@
clean: clean:
rm -f *.o next-round round-*.html rm -f *.o next-round round-*.html
rm -f $(BINARIES) $(HTML) rm -f $(BINARIES)

View File

@ -8,15 +8,68 @@ written by other players. Dirtbags Tanks is frequently a component of
[Dirtbags Capture The Flag](https://dirtbags.github.io/contest/). [Dirtbags Capture The Flag](https://dirtbags.github.io/contest/).
Running it
============
forftanks TANKDIR [TANKDIR...]
`forftanks` will run a round with every tank provided as an argument.
It outputs a JSON object describing the round.
Output fields
------
`seed`
: Seed used by the random number generator.
You can specify your own seed with the environment variable `SEED.
If the same seed is used with the same tanks,
you will get the same output.
`field`
: Dimensions of the play field.
`tanks`
: Description of each tank.
`rounds`
: List of frames for each round.
Each frame is a list of tank state for each tank.
Tank state is described below.
Tank state
--------
Tank state is packed more tightly than most modern JSON APIs.
Tank state is a tuple of (x position, y position, orientation angle, turret angle, flags, sensor bits).
x position, y position
: Tank's position on the play field.
orientation angle
: Tank's orientation on the play field, in radians.
turret angle
: Turret angle, relative to the tank, in radians.
flags
: Logical or of 1 (firing), 2 (LED on), and 4 (dead)
sensor bits
: Bit field of sensor state (1 = triggered)
Documentation Documentation
------------- ============
* [Homepage](https://dirtbags.github.io/tanks/) * [Homepage](https://dirtbags.github.io/tanks/)
* [History](docs/history.md) * [History](docs/history.md)
* [Running](docs/running.md) * [Running](docs/running.md)
Author Current Maintainer
------ =====
Neale Pickett <neale@woozle.org> Neale Pickett <neale@woozle.org>

View File

@ -1,5 +1,4 @@
#ifndef __CTANKS_H__ #pragma once
#define __CTANKS_H__
/* τ = 2π */ /* τ = 2π */
#define TAU 6.28318530717958647692 #define TAU 6.28318530717958647692
@ -107,6 +106,3 @@ int tank_get_sensor(struct tank *tank, int sensor_num);
/** Set the LED state */ /** Set the LED state */
void tank_set_led(struct tank *tank, int active); void tank_set_led(struct tank *tank, int active);
#endif /* __CTANKS_H__ */

View File

@ -52,7 +52,7 @@ to accept a scaled-down version of PostScript. The IRC channel we
frequent collectively agreed to give this new language the derisive name frequent collectively agreed to give this new language the derisive name
"Forf", which should ideally be followed by punching someone after it is "Forf", which should ideally be followed by punching someone after it is
spoken aloud. I wrote a Python implementation of Forf, which was slow, spoken aloud. I wrote a Python implementation of Forf, which was slow,
and then Adam Glasgall wrote a C implementation, which was quick. and then Anna Glasgall wrote a C implementation, which was quick.
I decided to take Tanks to Def Con in July 2010, and just for bragging I decided to take Tanks to Def Con in July 2010, and just for bragging
rights, to have it run on an Asus WL-500gU. This is a $50 device with a rights, to have it run on an Asus WL-500gU. This is a $50 device with a

View File

@ -6,7 +6,7 @@ title: thanks
* Paul Ferrell: Initial implementation * Paul Ferrell: Initial implementation
* Aaron McPhall: Math optimizations * Aaron McPhall: Math optimizations
* Nick Moffitt: Suggestion to use FORTH-like language * Nick Moffitt: Suggestion to use FORTH-like language
* Adam Glasgall: C Forf prototype * Anna Glasgall: C Forf prototype
* Adam Thomas: Various patches * Adam Thomas: Various patches
* Various Australians: Creation of some mind-blowing tanks * Various Australians: Creation of some mind-blowing tanks
* Alyssa Milburn: JS Tanks, Tank Debugger, many more other helpful contributions * Alyssa Milburn: JS Tanks, Tank Debugger, many more other helpful contributions

4
dump.h
View File

@ -1,4 +1,4 @@
#ifndef __DUMP_H__ #pragma once
#include <stdio.h> #include <stdio.h>
@ -28,5 +28,3 @@
#define TEK_line(x1, y1, x2, y2) TEK("\035%c%c%c%c%c%c%c%c", TEK_coord(x1, y1), TEK_coord(x2, y2)) #define TEK_line(x1, y1, x2, y2) TEK("\035%c%c%c%c%c%c%c%c", TEK_coord(x1, y1), TEK_coord(x2, y2))
#define TEK_point(x, y) TEK("\034%c%c%c%c", TEK_coord(x, y)) #define TEK_point(x, y) TEK("\034%c%c%c%c", TEK_coord(x, y))
#define TEK_text(x, y, s) TEK("\035%c%c%c%c\037%s", TEK_coord(x, y), s) #define TEK_text(x, y, s) TEK("\035%c%c%c%c\037%s", TEK_coord(x, y), s)
#endif

5
forf.h
View File

@ -1,5 +1,4 @@
#ifndef __FORF_H__ #pragma once
#define __FORF_H__
#include <stdio.h> #include <stdio.h>
#include <inttypes.h> #include <inttypes.h>
@ -146,5 +145,3 @@ int forf_eval_once(struct forf_env *env);
/** Evaluate the entire command stack */ /** Evaluate the entire command stack */
int forf_eval(struct forf_env *env); int forf_eval(struct forf_env *env);
#endif

View File

@ -368,38 +368,11 @@ ft_read_tank(struct forftank *ftank,
void void
print_header(FILE *f, print_header(FILE *f,
struct tanks_game *game, struct tanks_game *game,
struct forftank *forftanks, int seed)
struct tank *tanks,
int ntanks)
{ {
int i, j; fprintf(f, "{\n");
fprintf(f, " \"seed\": %d,\n", seed);
fprintf(f, "[[%d,%d],[\n", fprintf(f, " \"field\": [%d,%d],\n", (int)game->size[0], (int)game->size[1]);
(int)game->size[0], (int)game->size[1]);
for (i = 0; i < ntanks; i += 1) {
fprintf(f, " [\"%s\",[", forftanks[i].color);
for (j = 0; j < TANK_MAX_SENSORS; j += 1) {
struct sensor *s = &(tanks[i].sensors[j]);
if (! s->range) {
fprintf(f, "0,");
} else {
fprintf(f, "[%d,%.2f,%.2f,%d],",
(int)(s->range),
s->angle,
s->width,
s->turret);
}
}
fprintf(f, "]],\n");
}
fprintf(f, "],[\n");
}
void
print_footer(FILE *f)
{
fprintf(f, "]]\n");
} }
void void
@ -408,24 +381,31 @@ print_rounds(FILE *f,
struct tank *tanks, struct tank *tanks,
int ntanks) int ntanks)
{ {
int i;
int alive; int alive;
fprintf(f, " \"rounds\": [\n");
/* Run rounds */ /* Run rounds */
alive = ntanks; alive = ntanks;
for (i = 0; (alive > 1) && (i < ROUNDS); i += 1) { for (int i = 0; (alive > 1) && (i < ROUNDS); i += 1) {
int j; if (i > 0) {
fprintf(f, ",\n");
}
tanks_run_turn(game, tanks, ntanks); tanks_run_turn(game, tanks, ntanks);
fprintf(f, "[\n"); fprintf(f, " [");
alive = ntanks; alive = ntanks;
for (j = 0; j < ntanks; j += 1) { for (int j = 0; j < ntanks; j += 1) {
struct tank *t = &(tanks[j]); struct tank *t = &(tanks[j]);
int k; int k;
int flags = 0; int flags = 0;
int sensors = 0; int sensors = 0;
if (j > 0) {
fprintf(f, ",");
}
for (k = 0; k < TANK_MAX_SENSORS; k += 1) { for (k = 0; k < TANK_MAX_SENSORS; k += 1) {
if (t->sensors[k].triggered) { if (t->sensors[k].triggered) {
sensors |= (1 << k); sensors |= (1 << k);
@ -441,7 +421,7 @@ print_rounds(FILE *f,
alive -= 1; alive -= 1;
flags |= 4; flags |= 4;
} }
fprintf(f, " [%d,%d,%.2f,%.2f,%d,%d],\n", fprintf(f, "[%d,%d,%.2f,%.2f,%d,%d]",
(int)t->position[0], (int)t->position[0],
(int)(t->position[1]), (int)(t->position[1]),
t->angle, t->angle,
@ -449,8 +429,10 @@ print_rounds(FILE *f,
flags, flags,
sensors); sensors);
} }
fprintf(f, "],\n"); fprintf(f, "]");
} }
fprintf(f, "\n ],\n");
} }
void void
@ -459,18 +441,56 @@ print_standings(FILE *f,
struct tank *tanks, struct tank *tanks,
int ntanks) int ntanks)
{ {
int i;
for (i = 0; i < ntanks; i += 1) { fprintf(f, " \"tanks\": [\n");
/* &tank path cause &killer parse_error_pos lasterr */ for (int i = 0; i < ntanks; i += 1) {
fprintf(f, "%p\t%s\t%s\t%p\t%d\t%s\n", int killer = -1;
&(tanks[i]), for (int j = 0; j < ntanks; j += 1) {
ftanks[i].path, if (tanks[i].killer == &(tanks[j])) {
tanks[i].cause_death, killer = j;
tanks[i].killer, }
ftanks[i].error_pos, }
forf_error_str[ftanks[i].env.error]);
if (i > 0) {
fprintf(f, ",\n");
}
fprintf(f, " {\n");
fprintf(f, " \"color\": \"%s\",\n", ftanks[i].color);
fprintf(f, " \"path\": \"%s\",\n", ftanks[i].path);
fprintf(f, " \"death\": \"%s\",\n", tanks[i].cause_death);
fprintf(f, " \"killer\": %d,\n", killer);
fprintf(f, " \"errorPos\": %d,\n", ftanks[i].error_pos);
fprintf(f, " \"error\": \"%s\",\n", forf_error_str[ftanks[i].env.error]);
fprintf(f, " \"sensors\": [\n");
for (int j = 0; j < TANK_MAX_SENSORS; j += 1) {
struct sensor *s = &(tanks[i].sensors[j]);
if (j > 0) {
fprintf(f, ",\n");
}
if (! s->range) {
fprintf(f, " null");
} else {
fprintf(f, " {\"range\":%d,\"angle\":%.2f,\"width\":%.2f,\"turret\":%s}",
(int)(s->range),
s->angle,
s->width,
s->turret?"true":"false");
}
}
fprintf(f, "\n ]");
fprintf(f, "\n }");
} }
fprintf(f, "\n ],\n");
}
void
print_footer(FILE *f)
{
fprintf(f, " \"\": null\n"); // sentry, so everything prior can end with a comma
fprintf(f, "}\n");
} }
int int
@ -483,6 +503,7 @@ main(int argc, char *argv[])
int order[MAX_TANKS]; int order[MAX_TANKS];
int ntanks = 0; int ntanks = 0;
int i; int i;
int seed;
lenv[0].name = NULL; lenv[0].name = NULL;
lenv[0].proc = NULL; lenv[0].proc = NULL;
@ -495,14 +516,18 @@ main(int argc, char *argv[])
/* We only need slightly random numbers */ /* We only need slightly random numbers */
{ {
char *s = getenv("SEED"); char *s = getenv("SEED");
int seed = atoi(s?s:""); seed = atoi(s?s:"");
if (! seed) { if (! seed) {
seed = getpid(); seed = getpid();
} }
srand(seed); srand(seed);
fprintf(stdout, "// SEED=%d\n", seed); }
if ((argc < 2) || (argv[1][0] == '-')) {
fprintf(stderr, "usage: %s TANKDIR [TANKDIR...]\n", argv[0]);
return 1;
} }
/* Every argument is a tank directory */ /* Every argument is a tank directory */
@ -515,11 +540,6 @@ main(int argc, char *argv[])
} }
} }
if (0 == ntanks) {
fprintf(stderr, "No usable tanks!\n");
return 1;
}
/* Calculate the size of the game board */ /* Calculate the size of the game board */
{ {
int x, y; int x, y;
@ -566,24 +586,10 @@ main(int argc, char *argv[])
} }
} }
print_header(stdout, &game, myftanks, mytanks, ntanks); print_header(stdout, &game, seed);
print_rounds(stdout, &game, mytanks, ntanks); print_rounds(stdout, &game, mytanks, ntanks);
print_standings(stdout, myftanks, mytanks, ntanks);
print_footer(stdout); print_footer(stdout);
/* Output standings to fd3.
*
* fd 3 is normally closed, so this won't normally do anything.
* To output to fd3 from the shell, you'll need to do something like this:
*
* ./run-tanks 3>standing
**/
{
FILE *standings = fdopen(3, "w");
if (standings) {
print_standings(standings, myftanks, mytanks, ntanks);
}
}
return 0; return 0;
} }

View File

@ -1,83 +0,0 @@
#! /usr/bin/awk -f
BEGIN {
FS = "\t";
}
function esc(s) {
gsub(/&/, "&amp;", s);
gsub(/</, "&lt;", s);
gsub(/>/, "&gt;", s);
return s;
}
{
id = $1;
ntanks += 1;
tanks[id] = id;
if ($4 == "(nil)") {
score[id] += 1;
} else {
reason[id] = $3;
killer[id] = $4;
kills[$4] += 1;
score[$4] += 1;
}
path[id] = $2;
if ($5) {
lasterr[id] = $6 " around char " $5;
} else {
lasterr[id] = $6;
}
if (1 == getline < (path[id] "/name")) {
name[id] = esc($0);
} else {
name[id] = "<i>Unnamed</i>";
}
getline < (path[id] "/color");
if (/^#[0-9A-Fa-f]+$/) {
color[id] = $0;
} else {
color[id] = "#c0c0c0";
}
}
END {
# Fill in who killed whom
for (id in tanks) {
if (score[id] > topscore) {
winner = id;
topscore = score[id];
}
if (killer[id]) {
reason[id] = reason[id] " (<span style='color:" color[killer[id]] ";'>" name[killer[id]] "</span>)";
}
# XXX: track points a different way
# print score[id] >> (path[id] "/points");
}
# Output the table
print "<table id=\"results\">";
print "<tr><th>Name</th><th>Score</th><th>Cause of Death</th><th>Last Error</th></tr>";
for (i = ntanks; i >= 0; i -= 1) {
for (me in tanks) {
if (score[me] == i) {
if (me == winner) {
style = "style=\"font-weight: bold; background-color: #666666\"";
} else {
style = "";
}
printf("<tr " style ">");
printf("<td><span class=\"swatch\" style=\"background-color: " color[me] "\">#</span> " name[me] "</td>");
printf("<td>" score[me] "</td>");
printf("<td>" reason[me] "</td>");
printf("<td>" lasterr[me] "</td>");
printf("</tr>\n");
printf("<!-- score " path[me] " " i " -->\n");
}
}
}
print "</table>";
}

View File

@ -1,77 +0,0 @@
#! /usr/bin/awk -f
function esc(s) {
gsub(/&/, "&amp;", s);
gsub(/</, "&lt;", s);
gsub(/>/, "&gt;", s);
return s;
}
BEGIN {
ngames = 20;
getline rounds < "next-round";
print "<!DOCTYPE html>";
print "<html>";
print " <head>";
print " <title>Dirtbags Tanks</title>";
print " <link rel=\"stylesheet\" href=\"style.css\" type=\"text/css\">";
print " </head>";
print " <body>";
print " <h1>Dirtbags Tanks</h1>";
print " <p>New here? Read the <a href=\"intro.html\">introduction</a>.</p>";
print " <p>New round every minute.</p>";
print " <h2>Rankings</h2>";
print " <p>Over the last " ngames" games only.</p>";
print " <ol>";
for (i = rounds - ngames - 1; i > 0 && i < rounds; i += 1) {
fn = sprintf("round-%04d.html", i)
while (getline < fn) {
if ($2 == "score") {
scores[$3] += $4
if (scores[$3] > topscore) {
topscore = scores[$3]
}
}
}
}
for (id in scores) {
if (1 == getline < (id "/name")) {
names[id] = esc($0)
} else {
names[id] = "<i>Unnamed</i>"
}
getline < (id "/color")
if (/^#[0-9A-Fa-f]+$/) {
color[id] = $0
} else {
color[id] = "#c0c0c0"
}
}
for (s = topscore; s >= 0; s -= 1) {
for (id in scores) {
if (scores[id] == s) {
printf("<li><span class=\"swatch\" style=\"background-color: %s;\">#</span> %s (%d points)</li>\n", color[id], names[id], scores[id]);
}
}
}
print " </ol>";
print " <h2>Rounds</h2>";
print " <ul>";
for (i = rounds - 1; (i >= rounds - 721) && (i > 0); i -= 1) {
printf("<li><a href=\"round-%04d.html\">%04d</a></li>\n", i, i);
}
print " </ul>";
while (getline < ENVIRON["NAV_HTML_INC"]) {
print;
}
print " </body>";
print "</html>";
}

View File

@ -1,32 +0,0 @@
#! /usr/bin/awk -f
BEGIN {
FS = "\t";
}
{
tanks[$1] = $2;
if ($4 == "(nil)") {
p = $1;
} else {
p = $4;
}
score[p] += 1;
if (score[p] == topscore) {
winners += 1;
} else if (score[p] > topscore) {
winners = 1;
topscore = score[p];
}
}
END {
if (winners > 1) {
exit;
}
for (id in tanks) {
if (score[id] == topscore) {
print tanks[id];
}
}
}