Simplify setup

This commit is contained in:
Neale Pickett 2024-12-05 15:36:41 -07:00
parent 4fd9aa541b
commit 144d988e5c
43 changed files with 88 additions and 62 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
contrib
docs
rounds
tanks

7
Dockerfile Normal file
View File

@ -0,0 +1,7 @@
FROM golang:1.22
WORKDIR /app
COPY *.go *.c *.h Makefile www /app
RUN make
RUN mkdir rounds
COPY examples /app/tanks
ENTRYPOINT [ "./tanksd" ]

108
README.md
View File

@ -4,69 +4,63 @@ Tanks
Dirtbags Tanks is a game in which you pit your coding abilities Dirtbags Tanks is a game in which you pit your coding abilities
against other hackers. You write a program for your tank, set it out against other hackers. You write a program for your tank, set it out
on the battlefield, and watch how your program fares against tanks on the battlefield, and watch how your program fares against tanks
written by other players. Dirtbags Tanks is frequently a component of written by other players.
[Dirtbags Capture The Flag](https://dirtbags.github.io/contest/).
Running it Running it
========
$ make # build source code
$ mkdir rounds # set up storage
$ cp -r examples tanks # install some tanks
$ mkdir tanks/mytank # provision your own tank
$ ./tanksd
At this point you can connect to http://localhost:8080/ and watch all the
built-in tanks destroy your motionless tank.
You can work on your tank with the token `mytank`.
Administering a server
==================
The `tanks` directory
----------------
The `tanks` directory has tank definitions.
Each subdirectory is a token that can be used to upload a tank.
Empty subdirectories are okay.
If you were running a 9-person event,
and had a Bash shell,
you could run something like this to set up `tanks` subdirectories:
$ cd tanks
$ for i in $(seq 9); do mkdir $(printf "%04x$04x" $RANDOM $RANDOM); done
`forftanks` uses the inode of each tank subdirectory
to uniquely identify the tank.
So if you want to change somebody's token,
you should `mv` the subdirectory.
If you `cp` it, or remove it and create a new one,
the scoreboard won't know it's the same tank.
The `rounds` directory
-----------------
The `rounds` directory has internal state,
containing an `index.json` file,
and a bunch of game files.
You can delete individual games if you need to, for some reason;
tanksd will notice your change when it runs the next game.
More Documentation
============ ============
forftanks TANKDIR [TANKDIR...] * [Homepage](https://dirtbags.net/tanks/)
`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
============
* [Homepage](https://dirtbags.github.io/tanks/)
* [History](docs/history.md) * [History](docs/history.md)
* [Running](docs/running.md) * [Thanks](docs/thanks.md)
Current Maintainer Current Maintainer

View File

@ -0,0 +1 @@
#ccffff

View File

@ -0,0 +1 @@
( This tank just sits around! )

View File

@ -0,0 +1 @@
#ffccff

1
examples/easy-twit/color Normal file
View File

@ -0,0 +1 @@
#ffffcc

1
examples/easy-twit/name Normal file
View File

@ -0,0 +1 @@
Twit

View File

@ -1 +0,0 @@
berzerker

View File

@ -1 +0,0 @@
( This program shouldn't even parse

View File

@ -0,0 +1 @@
#ccccff

View File

@ -0,0 +1 @@
#ffcccc

View File

@ -0,0 +1 @@
#ccffcc

View File

@ -275,6 +275,19 @@ void ft_read_sensors(struct tank *tank, char *path) {
} }
} }
// Crudely clean a string for json output
void clean(char *s) {
for (; *s; s++) {
switch (*s) {
case '\0' ... '\037':
case '"':
case '\\':
*s = '\0';
return;
}
}
}
int ft_read_tank(struct forftank *ftank, struct tank *tank, int ft_read_tank(struct forftank *ftank, struct tank *tank,
struct forf_lexical_env *lenv, char *path) { struct forf_lexical_env *lenv, char *path) {
int ret; int ret;
@ -296,6 +309,7 @@ int ft_read_tank(struct forftank *ftank, struct tank *tank,
if (!ret) { if (!ret) {
snprintf(ftank->name, sizeof(ftank->name), "i:%x", ftank->uid); snprintf(ftank->name, sizeof(ftank->name), "i:%x", ftank->uid);
} }
clean(ftank->name);
/* What is your quest? */ /* What is your quest? */
ft_tank_init(ftank, tank, lenv); ft_tank_init(ftank, tank, lenv);
@ -312,6 +326,7 @@ int ft_read_tank(struct forftank *ftank, struct tank *tank,
if (!ret) { if (!ret) {
strncpy(ftank->color, "#808080", sizeof(ftank->color)); strncpy(ftank->color, "#808080", sizeof(ftank->color));
} }
clean(ftank->color);
return 1; return 1;
} }

View File

@ -122,7 +122,6 @@ class Replay {
} }
let tbody = this.stats.querySelector("tbody") let tbody = this.stats.querySelector("tbody")
console.log(this.stats, tbody)
tbody.replaceChildren() tbody.replaceChildren()
let byKills = Object.keys(TankNames) let byKills = Object.keys(TankNames)
@ -177,7 +176,6 @@ class Replay {
let game = this.games[fn] let game = this.games[fn]
this.player.load(game) this.player.load(game)
console.log(fn)
this.dateOutput.value = dateOfFn(fn) this.dateOutput.value = dateOfFn(fn)

View File

@ -55,6 +55,8 @@ export class Tank {
this.ctx.translate(this.x, this.y) this.ctx.translate(this.x, this.y)
this.ctx.rotate(this.rotation) this.ctx.rotate(this.rotation)
// If the cannon was fired in the same turn we were shot,
// render the laser for one frame.
if (this.fire == 5) { if (this.fire == 5) {
this.ctx.save() this.ctx.save()
this.ctx.rotate(this.turret) this.ctx.rotate(this.turret)
@ -145,7 +147,7 @@ export class Tank {
} }
draw_cannon() { draw_cannon() {
this.ctx.fillStyle = ("hsl(0, 100%, 100%, " + this.fire/5 + ")") this.ctx.fillStyle = ("hsl(0, 100%, 50%, " + this.fire/5 + ")")
this.ctx.fillRect(0, -1, 45, 2) this.ctx.fillRect(0, -1, 45, 2)
} }