Documentation and a little cleanup

This commit is contained in:
Neale Pickett 2010-07-22 22:41:46 -06:00
parent fc98369eba
commit e932242cfa
7 changed files with 340 additions and 164 deletions

10
.gitignore vendored
View File

@ -1,11 +1,17 @@
*~
*#
*.o
round-*.html
next-round
run-tanks
forf.c
forf.h
forf.html
designer.cgi
next-round
forf.html
summary.html
designer.html
intro.html
procs.html

73
HISTORY.txt Normal file
View File

@ -0,0 +1,73 @@
History
=======
This is a port of the "Tanks" program written by Paul Ferrell
<pflarr@clanspum.net> in 2009-2010. Paul created the entire game based
off a brief description I provided him of Crobots and a vague desire to
"make something fun for high school kids to learn some programming." We
ran Paul's Tanks as part of a 100-attendee computer security contest in
February of 2010 and by all accounts it was a huge success. It even
made the nightly news.
Paul's version was written in Python and provided a custom language
called "Bullet", which looked like this:
>addsensor(50, 0, 5, 1); # 0
>addsensor(100, 90, 150, 1); # 1
>addsensor(100, 270, 150, 1); # 2
>addsensor(100, 0, 359, 0); # 3
# Default movement if nothing is detected
: move(70, 70) . turretccw();
random(2, 3): move(40, 70) . turretccw();
random(1, 3): move(70, 40) . turretccw();
# We found something!!
sense(3): move(0, 0);
sense(1): turretcw();
sense(2): turretccw();
sense(0): fire();
Nick Moffitt played with this original version and convinced me (Neale)
that something like Forth would be a better language. I added some code
to accept a scaled-down version of PostScript. The IRC channel we
frequent collectively agreed to give this new language the derisive name
"Forf", which should ideally be followed by punching someone after it is
spoken aloud.
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
240 MHz MIPS CPU, 16MB RAM, and a 4MB flash disk, along with an
802.11b/g radio, 4-port 10/100 switch, and an additional 10/100 "uplink"
port; it's sold as a home wireless router. I had originally intended to
run it off a lantern battery just for fun, but eventually thought better
of it: doing so would be wasteful for no good reason.
Aaron McPhall <amcphall@mcphall.org>, my summer intern at the time, got
OpenWRT and Python onto the box and benchmarked it at about 60 seconds
for a 16-tank game, after he had profiled the code and optimized a lot
of the math. That wasn't bad, it meant we could run a reasonably-sized
game at one turn per minute, which we knew from past experience was
about the right rate. But it required a USB thumb drive to hold Python,
and when we used the Python Forf implementation, the run-time shot up to
several minutes. I began this C port while Adam Glasgall, another fool
on the previously-mentioned IRC channel, started work on a C version of
a Forf interpreter.
This C version with Forf runs about 6 times faster than the Python
version with Bullet, on the WL-500gU. A 1GHz Intel Atom runs a 16-tank
game in about 0.2 seconds.
What's so great about Forf?
---------------------------
Nothing's great about Forf. It's a crummy language that only does
integer math. For this application it's a good choice, for the
following reasons:
* No library dependencies, not even malloc
* Runs in fixed size memory
* Not Turing-complete, I think (impossible to make endless loops)
* Lends itself to genetic algorithms

View File

@ -1,7 +1,9 @@
BINARIES = run-tanks designer.cgi
HTML = forf.html procs.html intro.html designer.html
CFLAGS = -Wall
all: html run-tanks designer.cgi
html: forf.html procs.html intro.html
all: $(BINARIES) $(HTML)
run-tanks: run-tanks.o ctanks.o forf.o
run-tanks: LDFLAGS = -lm
@ -22,5 +24,5 @@ forf/%:
git submodule update --init
clean:
rm -f run-tanks designer.cgi *.o forf.c forf.h
rm -f next-round round-*.html summary.html forf.html
rm -f *.o forf.c forf.h next-round round-*.html
rm -f $(BINARIES) $(HTML)

153
README.txt Normal file
View File

@ -0,0 +1,153 @@
CTanks
======
This is a programming game, inspired by Crobots by Tom Poindexter
(http://www.nyx.net/~tpoindex/crob.html). Players write programs for
tanks, with the intent to seek out and destroy other tanks.
Output is a JSON object, and some scripts are provided to wrap the
object up in a web page.
Included programs
-----------------
I tried to stick with the Unix philosophy of one program per task. I
also tried to avoid doing any string processing in C. The result is a
hodgepodge of C, Bourne shell, and awk, but at least each piece is
fairly simple to audit.
### round.sh tank1 tank2 ...
Runs a single round, awards points with rank.awk, and creates a new
summary.html with summary.awk. This is the main interface that you want
to run from cron or whatever.
### run-tanks tank1 tank2 ...
A program to run a round of tanks and output a JSON description of the
game. This is what tanks.js uses to render a game graphically.
The object printed contains:
[[game-width, game-height],
[[tank1-color,
[[sensor1range, sensor1angle, sensor1width, sensor1turret],
...]],
...],
[[
[tank1x, tank1y, tank1angle, tank1sensangle,
tank1flags, tank1sensors],
...],
...]]
If file descriptor 3 is open for writes, it also outputs the results of
the round to fd3.
### rank.awk
Processes the fd3 output of run-tanks to award points and output an
HTML results table.
### summary.awk tank1 tank2
Creates summary.html, linking to all rounds and showing overall
standing.
### designer.cgi
Accepts form input and writes a tank.
Building from source
--------------------
You should be able to just run "make". The C is supposedly ANSI C, and
might even compile on Windows. I've built it on Linux, with glibc and
uClibc, big- and little-endian.
History
-------
This is a port of the "Tanks" program written by Paul Ferrell
<pflarr@clanspum.net> in 2009-2010. Paul created the entire game based
off a brief description I provided him of Crobots and a vague desire to
"make something fun for high school kids to learn some programming." We
ran Paul's Tanks as part of a 100-attendee computer security contest in
February of 2010 and by all accounts it was a huge success. It even
made the nightly news.
Paul's version was written in Python and provided a custom language
called "Bullet", which looked like this:
>addsensor(50, 0, 5, 1); # 0
>addsensor(100, 90, 150, 1); # 1
>addsensor(100, 270, 150, 1); # 2
>addsensor(100, 0, 359, 0); # 3
# Default movement if nothing is detected
: move(70, 70) . turretccw();
random(2, 3): move(40, 70) . turretccw();
random(1, 3): move(70, 40) . turretccw();
# We found something!!
sense(3): move(0, 0);
sense(1): turretcw();
sense(2): turretccw();
sense(0): fire();
Nick Moffitt played with this original version and convinced me (Neale)
that something like Forth would be a better language. I added some code
to accept a scaled-down version of PostScript. The IRC channel we
frequent collectively agreed to give this new language the derisive name
"Forf", which should ideally be followed by punching someone after it is
spoken aloud.
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
240 MHz MIPS CPU, 16MB RAM, and a 4MB flash disk, along with an
802.11b/g radio, 4-port 10/100 switch, and an additional 10/100 "uplink"
port; it's sold as a home wireless router. I had originally intended to
run it off a lantern battery just for fun, but eventually thought better
of it: doing so would be wasteful for no good reason.
Aaron McPhall <amcphall@mcphall.org>, my summer intern at the time, got
OpenWRT and Python onto the box and benchmarked it at about 60 seconds
for a 16-tank game, after he had profiled the code and optimized a lot
of the math. That wasn't bad, it meant we could run a reasonably-sized
game at one turn per minute, which we knew from past experience was
about the right rate. But it required a USB thumb drive to hold Python,
and when we used the Python Forf implementation, the run-time shot up to
several minutes. I began this C port while Adam Glasgall, another fool
on the previously-mentioned IRC channel, started work on a C version of
a Forf interpreter.
This C version with Forf runs about 6 times faster than the Python
version with Bullet, on the WL-500gU. A 1GHz Intel Atom runs a 16-tank
game in about 0.2 seconds.
What's so great about Forf?
---------------------------
Nothing's great about Forf. It's a crummy language that only does
integer math. For this application it's a good choice, for the
following reasons:
* No library dependencies, not even malloc
* Runs in fixed size memory
* Not Turing-complete, I think (impossible to make endless loops)
* Lends itself to genetic algorithms
Author
------
Neale Pickett <neale@woozle.org>

View File

@ -1,156 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Tank Designer</title>
<link rel="stylesheet" href="dirtbags.css" type="text/css">
<style type="text/css">
#preview {
float: right;
}
#sensors input {
width: 5em;
}
#program textarea {
width: 100%;
min-height: 20em;
}
td {
text-align: left;
}
</style>
<script type="application/javascript" src="tanks.js"></script>
<script type="application/javascript" src="designer.js"></script>
<script type="application/javascript">
window.onload = design;
</script>
</head>
<body>
<h1>Tank Designer</h1>
<div id="preview"><canvas id="design"></canvas><p id="debug"></p></div>
<p>
Before you can get going with a tank, you need a token. If you
need a token, just ask one of the dirtbags.
</p>
<form action="designer.cgi" method="post">
<fieldset id="metadata">
<legend>Information</legend>
<table>
<tr>
<td>Token:</td>
<td><input name="token"></td>
</tr>
<tr>
<td>Tank name:</td>
<td><input name="name"></td>
</tr>
<tr>
<td>Author:</td>
<td><input name="author"> (eg. Joe Cool
&lt;joe@cool.cc&gt;)</td>
</tr>
<tr>
<td>Color:</td>
<td><input name="color" type="color" value="#c0c0c0"
onchange="update();"> (eg. #c7e148)</td>
</tr>
</table>
</fieldset>
<fieldset id="sensors">
<legend>Sensors</legend>
<table>
<thead>
<tr>
<td>#</td>
<td>Range</td>
<td>Angle</td>
<td>Width</td>
<td>Turret?</td>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td><input name="s0r" type="number" min="0" max="100" onchange="update();"></td>
<td><input name="s0a" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s0w" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s0t" type="checkbox" onchange="update();"></td>
</tr>
<tr>
<td>1</td>
<td><input name="s1r" type="number" min="0" max="100" onchange="update();"></td>
<td><input name="s1a" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s1w" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s1t" type="checkbox" onchange="update();"></td>
</tr>
<tr>
<td>2</td>
<td><input name="s2r" type="number" min="0" max="100" onchange="update();"></td>
<td><input name="s2a" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s2w" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s2t" type="checkbox" onchange="update();"></td>
</tr>
<tr>
<td>3</td>
<td><input name="s3r" type="number" min="0" max="100" onchange="update();"></td>
<td><input name="s3a" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s3w" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s3t" type="checkbox" onchange="update();"></td>
</tr>
<tr>
<td>4</td>
<td><input name="s4r" type="number" min="0" max="100" onchange="update();"></td>
<td><input name="s4a" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s4w" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s4t" type="checkbox" onchange="update();"></td>
</tr>
<tr>
<td>5</td>
<td><input name="s5r" type="number" min="0" max="100" onchange="update();"></td>
<td><input name="s5a" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s5w" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s5t" type="checkbox" onchange="update();"></td>
</tr>
<tr>
<td>6</td>
<td><input name="s6r" type="number" min="0" max="100" onchange="update();"></td>
<td><input name="s6a" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s6w" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s6t" type="checkbox" onchange="update();"></td>
</tr>
<tr>
<td>7</td>
<td><input name="s7r" type="number" min="0" max="100" onchange="update();"></td>
<td><input name="s7a" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s7w" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s7t" type="checkbox" onchange="update();"></td>
</tr>
<tr>
<td>8</td>
<td><input name="s8r" type="number" min="0" max="100" onchange="update();"></td>
<td><input name="s8a" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s8w" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s8t" type="checkbox" onchange="update();"></td>
</tr>
<tr>
<td>9</td>
<td><input name="s9r" type="number" min="0" max="100" onchange="update();"></td>
<td><input name="s9a" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s9w" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s9t" type="checkbox" onchange="update();"></td>
</tr>
</tbody>
</table>
</fieldset>
<fieldset id="program">
<legend>Program</legend>
<textarea name="program"></textarea>
</fieldset>
<input type="submit" value="Submit">
</form>
</body>
</html>

98
designer.html.m4 Normal file
View File

@ -0,0 +1,98 @@
<!DOCTYPE html>
<html>
<head>
<title>Tank Designer</title>
<link rel="stylesheet" href="dirtbags.css" type="text/css">
<style type="text/css">
#preview {
float: right;
}
#sensors input {
width: 5em;
}
#program textarea {
width: 100%;
min-height: 20em;
}
td {
text-align: left;
}
</style>
<script type="application/javascript" src="tanks.js"></script>
<script type="application/javascript" src="designer.js"></script>
<script type="application/javascript">
window.onload = design;
</script>
</head>
<body>
<h1>Tank Designer</h1>
<div id="preview"><canvas id="design"></canvas><p id="debug"></p></div>
<p>
Before you can get going with a tank, you need a password. If you
need a password, just ask one of the dirtbags.
</p>
<form action="designer.cgi" method="post">
<fieldset id="metadata">
<legend>Information</legend>
<table>
<tr>
<td>Token:</td>
<td><input name="token"></td>
</tr>
<tr>
<td>Tank name:</td>
<td><input name="name"></td>
</tr>
<tr>
<td>Author:</td>
<td><input name="author"> (eg. Joe Cool
&lt;joe@cool.cc&gt;)</td>
</tr>
<tr>
<td>Color:</td>
<td><input name="color" type="color" value="#c0c0c0"
onchange="update();"> (eg. #c7e148)</td>
</tr>
</table>
</fieldset>
<fieldset id="sensors">
<legend>Sensors</legend>
<table>
<thead>
<tr>
<td>#</td>
<td>Range</td>
<td>Angle</td>
<td>Width</td>
<td>Turret?</td>
</tr>
</thead>
<tbody>
define(sensor,
`ifelse(`$1',`10',,`
<tr>
<td>$1</td>
<td><input name="s$1r" type="number" min="0" max="100" onchange="update();"></td>
<td><input name="s$1a" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s$1w" type="number" min="0" max="360" onchange="update();"></td>
<td><input name="s$1t" type="checkbox" onchange="update();"></td>
</tr>
sensor(incr($1))')')
sensor(0)
</tbody>
</table>
</fieldset>
<fieldset id="program">
<legend>Program</legend>
<textarea name="program"></textarea>
</fieldset>
<input type="submit" value="Submit">
</form>
include(nav.html.inc)
</body>
</html>

View File

@ -353,8 +353,8 @@ print_header(FILE *f,
{
int i, j;
fprintf(f, "[[%d,%d,%d],[\n",
(int)game->size[0], (int)game->size[1], TANK_CANNON_RANGE);
fprintf(f, "[[%d,%d],[\n",
(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) {