Add some online docs, clean up some cruft

This commit is contained in:
Neale Pickett 2010-09-13 16:58:30 -06:00
parent 8d47593986
commit 14d7e3509d
14 changed files with 150 additions and 844 deletions

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "puzzles"]
path = puzzles
url = cfl:/var/projects/ctf-puzzles

View File

@ -1,44 +0,0 @@
#! /usr/bin/python
import os
import shutil
import optparse
import string
import markdown
from codecs import open
p = optparse.OptionParser('%prog [OPTIONS] infile outfile')
p.add_option('-t', '--template', dest='template', default='template.html',
help='Location of HTML template')
p.add_option('-b', '--base', dest='base', default='',
help='Base URL for contest')
opts, args = p.parse_args()
basedir = os.path.dirname(args[0])
links_fn = os.path.join(basedir, 'links.xml')
try:
links = open(links_fn, encoding='utf-8').read()
except IOError:
links = ''
f = open(args[0], encoding='utf-8')
title = ''
for line in f:
line = line.strip()
if not line:
break
k, v = line.split(': ')
if k.lower() == 'title':
title = v
body = markdown.markdown(f.read(99999))
template = string.Template(open(opts.template, encoding='utf-8').read())
page = template.substitute(hdr='',
title=title,
base=opts.base,
links=links,
body_class='',
onload='',
body=body)
open(args[1], 'w', encoding='utf-8').write(page)

View File

@ -1,150 +0,0 @@
#! /usr/bin/python
import os
import shutil
import optparse
import string
import markdown
import rfc822
from codecs import open
p = optparse.OptionParser()
p.add_option('-t', '--template', dest='template', default='template.html',
help='Location of HTML template')
p.add_option('-b', '--base', dest='base', default='',
help='Base URL for contest')
p.add_option('-p', '--puzzles', dest='puzzles', default='puzzles',
help='Directory containing puzzles')
p.add_option('-w', '--htmldir', dest='htmldir', default='puzzler',
help='Directory to write HTML puzzle tree')
p.add_option('-k', '--keyfile', dest='keyfile', default='puzzler.keys',
help='Where to write keys')
opts, args = p.parse_args()
keys = []
tmpl_f = open(opts.template, encoding='utf-8')
template = string.Template(tmpl_f.read())
tmpl_f.close()
js = '''
<script type="text/javascript">
function readCookie(key) {
var s = key + '=';
var toks = document.cookie.split(';');
for (var i = 0; i < toks.length; i++) {
var tok = toks[i];
while (tok.charAt(0) == ' ') {
tok = tok.substring(1, tok.length);
}
if (tok.indexOf(s) == 0) {
return tok.substring(s.length, tok.length);
}
}
return null;
}
function getTeamInfo() {
team = readCookie('team');
passwd = readCookie('passwd');
if (team != null) {
document.getElementById("form").t.value = team;
}
if (passwd != null) {
document.getElementById("form").w.value = passwd;
}
}
</script>
'''
for cat in os.listdir(opts.puzzles):
dirname = os.path.join(opts.puzzles, cat)
for points in os.listdir(dirname):
pointsdir = os.path.join(dirname, points)
if not os.path.isdir(pointsdir):
continue
outdir = os.path.join(opts.htmldir, cat, points)
try:
os.makedirs(outdir)
except OSError:
pass
readme = ''
files = []
for fn in os.listdir(pointsdir):
path = os.path.join(pointsdir, fn)
if fn == 'key':
for key in open(path, encoding='utf-8'):
key = key.rstrip()
keys.append((cat, points, key))
elif fn == 'hint':
pass
elif fn == 'index.exe':
p = os.popen(path)
m = rfc822.Message(p)
for key in m.getallmatchingheaders('Key'):
print key
keys.append((cat, points, key))
readme = m.fp.read()
if m.get('Content-Type', 'text/markdown') == 'text/markdown':
readme = markdown.markdown(readme)
elif fn == 'index.html':
readme = open(path, encoding='utf-8').read()
elif fn == 'index.mdwn':
readme = open(path, encoding='utf-8').read()
readme = markdown.markdown(readme)
elif fn.endswith('~'):
pass
else:
files.append((fn, path))
title = '%s for %s points' % (cat, points)
body = []
if readme:
body.append('<div class="readme">%s</div>\n' % readme)
if files:
body.append('<ul>\n')
for fn, path in files:
if os.path.isdir(path):
shutil.rmtree(os.path.join(outdir, fn), ignore_errors=True)
shutil.copytree(path, os.path.join(outdir, fn))
else:
shutil.copy(path, outdir)
if not fn.startswith(','):
body.append('<li><a href="%s">%s</a></li>\n' % (fn, fn))
body.append('</ul>\n')
body.append('''
<form id="form" action="%(base)spuzzler.cgi" method="post">
<fieldset>
<legend>Your answer:</legend>
<input type="hidden" name="c" value="%(cat)s" />
<input type="hidden" name="p" value="%(points)s" />
Team: <input name="t" /><br />
Password: <input type="password" name="w" /><br />
Key: <input name="k" /><br />
<input type="submit" />
</fieldset>
</form>
''' % {'base': opts.base,
'cat': cat,
'points': points})
page = template.substitute(hdr=js,
title=title,
base=opts.base,
links='',
body_class='',
onload = "getTeamInfo()",
body=''.join(body))
f = open(os.path.join(outdir, 'index.html'), 'w', encoding='utf-8')
f.write(page)
f = open(opts.keyfile, 'w', encoding='utf-8')
for key in keys:
f.write('%s\t%s\t%s\n' % key)

@ -1 +0,0 @@
Subproject commit 2c4f955d275b03891cd84f17a80dd397780272c6

View File

@ -1,11 +0,0 @@
#! /usr/bin/python
from distutils.core import setup
setup(name='ctf',
version='1.0',
description='Capture The Flag contest',
author='Neale Pickett',
author_email='neale@lanl.gov',
url='http://dirtbags.net/ctf/',
packages=['ctf', 'tanks'])

View File

@ -35,8 +35,8 @@ a img {
}
a {
text-decoration: none;
color: #b71;
text-decoration: underline;
color: #84b;
font-weight: bold;
}

54
www/index.html Normal file
View File

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html>
<head>
<title>Welcome</title>
<link rel="stylesheet" href="ctf.css" type="text/css">
</head>
<body>
<h1>Welcome</h1>
<h2>Important Links</h2>
<ul>
<li>
<a href="scoreboard.html">Scoreboard</a>
</li>
<li>
<a href="puzzles.html">Puzzles</a>
</li>
<li>
<a href="irc://10.0.0.1/ctf"
title="IRC on 10.0.0.1, channel #ctf">Contest chat</a>
</li>
</ul>
<h2>Claim Token</h2>
<form action="claim.cgi" method="post">
<fieldset>
team: <input name="t" size="8"><br>
token: <input name="k" size="20">
</fieldset>
</form>
<h2>Rules</h2>
<ul>
<li>
No ARP-level attacks: this includes IP spoofing.
</li>
<li>
No DoS attacks.
</li>
<li>
Do not attack machines outside the contest network
(10.<i>x</i>.<i>x</i>.<i>x</i>).
</li>
<li>
Consider the contest network hostile. It is up to you to
safeguard, encrypt, or delete sensitive data on your computer.
</li>
<li>
We reserve the right to kick you out of the contest for any
reason, so play nice.
</li>
</ul>
</body>
</html>

View File

@ -1,186 +0,0 @@
#! /usr/bin/python
##
## This is pretty crufty :<
##
import cgitb; cgitb.enable()
import cgi
import os
import fcntl
import re
import sys
from cgi import escape
import Cookie as cookies
from urllib import quote, unquote
from codecs import open
from sets import Set as set
from cStringIO import StringIO
from ctf import pointscli, teams, html, paths
keysfile = os.path.join(paths.LIB, 'puzzler.keys')
datafile = os.path.join(paths.VAR, 'puzzler.dat')
puzzles_dir = os.path.join(paths.WWW, 'puzzler')
##
## This allows you to edit the URL and work on puzzles that haven't been
## unlocked yet. For now I think that's an okay vulnerability. It's a
## hacking contest, after all.
##
cat_re = re.compile(r'^[a-z]+$')
points_re = re.compile(r'^[0-9]+$')
points_by_cat = {}
points_by_team = {}
try:
for line in open(datafile, encoding='utf-8'):
cat, team, pts = [unquote(v) for v in line.strip().split('\t')]
pts = int(pts)
points_by_cat[cat] = max(points_by_cat.get(cat, 0), pts)
points_by_team.setdefault((team, cat), set()).add(pts)
except IOError:
pass
c = cookies.SimpleCookie(os.environ.get('HTTP_COOKIE', ''))
try:
team = c['team'].value
passwd = c['passwd'].value
except KeyError:
team, passwd = None, None
f = cgi.FieldStorage()
cat = f.getfirst('c')
points = f.getfirst('p')
team = f.getfirst('t', team)
passwd = f.getfirst('w', passwd)
key = f.getfirst('k', '').decode('utf-8')
def serve(title, sf=None, **kwargs):
if team or passwd:
c = cookies.SimpleCookie()
if team:
c['team'] = team
if passwd:
c['passwd'] = passwd
print(c)
if not sf:
sf = StringIO()
return html.serve(title, sf.getvalue(), **kwargs)
def safe_join(*args):
safe = list(args[:1])
for a in args[1:]:
if not a:
return None
else:
a = a.replace('..', '')
a = a.replace('/', '')
safe.append(a)
ret = '/'.join(safe)
if os.path.exists(ret):
return ret
def dump_file(fn):
f = open(fn, 'rb')
while True:
d = f.read(4096)
if not d:
break
sys.stdout.buffer.write(d)
def disabled(cat):
return os.path.exists(os.path.join(paths.VAR, 'disabled', cat))
def show_cats():
out = StringIO()
out.write('<ul>')
puzzles = os.listdir(puzzles_dir)
puzzles.sort()
for p in puzzles:
if disabled(p):
continue
out.write('<li><a href="%spuzzler.cgi?c=%s">%s</a></li>' % (html.base, p, p))
out.write('</ul>')
serve('Categories', out)
def show_puzzles(cat, cat_dir):
out = StringIO()
opened = points_by_cat.get(cat, 0)
puzzles = ([int(v) for v in os.listdir(cat_dir)])
puzzles.sort()
if puzzles:
out.write('<ul>')
for p in puzzles:
cls = ''
try:
if p in points_by_team[(team, cat)]:
cls = 'solved'
except KeyError:
pass
out.write('<li><a href="%(base)spuzzler/%(cat)s/%(points)d" class="%(class)s">%(points)d</a></li>' %
{'base': html.base,
'cat': cat,
'points': p,
'class': cls})
if p > opened:
break
out.write('</ul>')
else:
out.write('<p>None (someone is slacking)</p>')
serve('Open in %s' % escape(cat), out)
def win(cat, team, points):
out = StringIO()
points = int(points)
f = open(datafile, 'a', encoding='utf-8')
pointscli.award(cat, team, points)
fcntl.lockf(f, fcntl.LOCK_EX)
f.write('%s\t%s\t%d\n' % (quote(cat), quote(team), points))
out.write('<p>%d points for %s.</p>' % (points, cgi.escape(team)))
out.write('<p>Back to <a href="%spuzzler.cgi?c=%s">%s</a>.</p>' % (html.base, cat, cat))
serve('Winner!', out)
def check_key(cat, points, candidate):
for line in open(keysfile, encoding='utf-8'):
thiscat, thispoints, key = line.split('\t', 2)
if (cat, points) == (thiscat, thispoints):
if key.rstrip() == candidate:
return True
return False
def main():
cat_dir = safe_join(puzzles_dir, cat)
points_dir = safe_join(puzzles_dir, cat, points)
if not cat_dir:
# Show categories
show_cats()
elif not points_dir:
# Show available puzzles in category
show_puzzles(cat, cat_dir)
else:
if not teams.chkpasswd(team, passwd):
serve('Wrong password')
elif not check_key(cat, points, key):
serve('Wrong key')
elif int(points) in points_by_team.get((team, cat), set()):
serve('Greedy greedy')
else:
win(cat, team, points)
if __name__ == '__main__':
import optparse
import sys, codecs
sys.stdout = codecs.getwriter('utf-8')(sys.stdout)
main()
# Local Variables:
# mode: python
# End:

View File

@ -1,106 +0,0 @@
#! /usr/bin/lua
function decode(str)
local hexdec = function(h)
return string.char(tonumber(h, 16))
end
str = string.gsub(str, "+", " ")
return string.gsub(str, "%%(%x%x)", hexdec)
end
function decode_query(query)
local ret = {}
for key, val in string.gfind(query, "([^&=]+)=([^&=]+)") do
ret[string.lower(decode(key))] = decode(val)
end
return ret
end
function escape(str)
str = string.gsub(str, "&", "&amp;")
str = string.gsub(str, "<", "&lt;")
str = string.gsub(str, ">", "&gt;")
return str
end
function djbhash(s)
local hash = 5380
for i=0,string.len(s) do
local c = string.byte(string.sub(s, i, i+1))
hash = math.mod(((hash * 32) + hash + c), 2147483647)
end
return string.format("%08x", hash)
end
function head(title)
print("Content-type: text/html")
print("")
print("<!DOCTYPE html>")
print("<html>")
print(" <head>")
print(" <title>")
print(title)
print(" </title")
print(' <link rel="stylesheet" href="ctf.css" type="text/css">')
print(" </head>")
print(" <body>")
print(" <h1>")
print(title)
print(" </h1>")
end
function foot()
print(" </body>")
print("</html>")
os.exit()
end
if (os.getenv("REQUEST_METHOD") ~= "POST") then
print("405 Method not allowed")
print("Allow: POST")
print("Content-type: text/html")
print()
print("<h1>Method not allowed</h1>")
print("<p>I only speak POST. Sorry.</p>")
end
inlen = tonumber(os.getenv("CONTENT_LENGTH"))
if (inlen > 200) then
head("Bad team name")
print("<p>That's a bit on the long side, don't you think?</p>")
foot()
end
formdata = io.read(inlen)
f = decode_query(formdata)
team = f["t"]
if (not team) or (team == "dirtbags") then
head("Bad team name")
print("<p>Go back and try again.</p>")
foot()
end
hash = djbhash(team)
if io.open(hash) then
head("Team name taken")
print("<p>Either someone's already using that team name,")
print("or you found a hash collision. Either way, you're")
print("going to have to pick something else.</p>")
foot()
end
f = io.open(hash, "w"):write(team)
head("Team registered")
print("<p>Team name: <samp>")
print(escape(team))
print("</samp></p>")
print("<p>Team token: <samp>")
print(hash)
print("</samp></p>")
print("<p><b>Save your team token somewhere</b>!")
print("You will need it to claim points.</p>")
foot()

94
www/scoring.html Normal file
View File

@ -0,0 +1,94 @@
<!DOCTYPE html>
<html>
<head>
<title>About scoring</title>
<link rel="stylesheet" href="ctf.css" type="text/css">
<meta charset="utf-8">
</head>
<body>
<h1>About scoring</h1>
<p>
The contest is made up of multiple categories. Each category is
worth one point toward the total score; the percentage of the
total points held by your team is the percentage of one point your
team has for that category. The team that has 30% of the points
in each of five categories has 1.5 points, whereas the team that
has 80% of the points in only one category has 0.8 points. It is
typically better to have a few points in many categories, than
many points in a few categories.
</p>
<p>
There are two main ways to make points: <em>puzzles</em>
and <em>tokens</em>. Your contest may have other ways to make
points: these will either be automatic, or explained elsewhere.
</p>
<h2>Puzzles</h2>
<p>
Many of the categories are in the form of
multiple <em>puzzles</em>: for each puzzle presented, a
case-sensitive answer must be found to recieve the amount of
points that puzzle is worth. Any team may answer any puzzle
question at any time. A new puzzle is revealed when a team
correctly answers the highest-valued puzzle in that category.
</p>
<h2>Tokens</h2>
<p>
Tokens are strings redeemable once for a single point each. A
token for the "example" category might look like this:
</p>
<pre>example:xenon-codex</pre>
<p>
Tokens are typically associated with "live" categories, such as a
network-based service or a treasure hunt. Tokens can be submitted
with the form on the <a href="index.html">welcome page</a>, or you
can write your own script to automate token submission.
</p>
<p>
Some tokens change periodically, typically once a minute. If you
find a token, it's worth looking in the same place again later to
see if the token changes.
</p>
<h2>Reading the scoreboard</h2>
<p>
The <a href="scoreboard.html">scoreboard</a> shows total score on
the left, and scores within each category. If you have a smaller
or more novice team, you may wish to ignore total rankings and
strive to do well within only a few categories.
</p>
<p>
If your browser supports the HTML5 canvas (Firefox, Safari,
Chrome, Opera, iPhone, Android) and JavaScript, the scoreboard
will also have a graph of scores over time. Additionally,
JavaScript-enabled browsers will highlight all point blocks
belonging to a team, and a team's line on the graph, when the
mouse cursor is over a point block.
</p>
<h2>About time</h2>
<p>
Many Capture The Flag contests attempt to reward teams who answer
quickly with more points, by adding a "quick answer" bonus or
decaying point values over time. Our contest doesn't work this
way.
</p>
<p>
We believe that given enough things to work on, quick-moving teams
will emerge with more points by answering more questions; while
providing novice teams a realistic picture of how they're doing.
In addition, when the game infrastructure goes down—which seems to
happen a lot in anybody's CTF—there's no losing points while the
organizers struggle to get things back up.
</p>
</body>
</html>

View File

@ -1,56 +0,0 @@
#!/usr/bin/python
import cgi
import cgitb; cgitb.enable()
import sys
import os
from urllib import quote
from ctf import teams, html, paths
basedir = os.path.join(paths.VAR, 'tanks')
links = '''
<h3>Tanks</h3>
<li><a href="docs.html">Docs</a></li>
<li><a href="results.cgi">Results</a></li>
<li><a href="submit.html">Submit</a></li>
<li><a href="errors.cgi">My Errors</a></li>
'''
body = []
fields = cgi.FieldStorage()
team = fields.getfirst('team', '').strip()
passwd = fields.getfirst('passwd', '').strip()
if not team:
pass
elif teams.chkpasswd(team, passwd):
path = os.path.join(basedir, 'errors', quote(team))
if os.path.isfile(path):
body.append('<p>Your latest errors:</p>')
errors = open(path).readlines()
if errors:
body.append('<ul class="errors">')
for e in errors:
body.append('<li>%s</li>' % cgi.escape(e))
body.append('</ul>')
else:
body.append('<p>There were no errors.</p>')
else:
body.append('<p>No error file found.</p>')
else:
body.append('Authentication failed.')
body.append('''
<form action="errors.cgi" method="get">
<fieldset>
<legend>Error report request:</legend>
Team: <input type="text" name="team"/><br/>
Password: <input type="password" name="passwd"/><br/>
<button type="get my errors">Submit</button>
</fieldset>
</form>''')
html.serve('Tanks Errors', '\n'.join(body), links=links)

View File

@ -1,33 +0,0 @@
#! /usr/bin/python
import os
from ctf import html, paths
from cgi import escape
basedir = os.path.join(paths.VAR, 'tanks')
links = '''
<h3>Tanks</h3>
<li><a href="docs.html">Docs</a></li>
<li><a href="results.cgi">Results</a></li>
<li><a href="submit.html">Submit</a></li>
<li><a href="errors.cgi">My Errors</a></li>
'''
body = []
body.append('<h1>Last Winner:</h1>')
body.append('<p>')
body.append(escape(open(os.path.join(basedir, 'winner')).read()))
body.append('</p>')
body.append('<h1>Results so far:</h1>')
body.append('<ul>')
results = os.listdir(os.path.join(basedir, 'results'))
results.sort()
results.reverse()
for fn in results:
num, _ = os.path.splitext(fn)
body.append('<li><a href="results/%s">%s</a></li>' % (fn, num))
body.append('</ul>')
html.serve('Tanks Results', '\n'.join(body), links=links)

View File

@ -1,39 +0,0 @@
#!/usr/bin/python
import cgi
import cgitb; cgitb.enable()
import os
import sys
from urllib import quote
from ctf import teams, html, paths
basedir = os.path.join(paths.VAR, 'tanks')
links = '''
<h3>Tanks</h3>
<li><a href="docs.html">Docs</a></li>
<li><a href="results.cgi">Results</a></li>
<li><a href="submit.html">Submit</a></li>
<li><a href="errors.cgi">My Errors</a></li>
'''
fields = cgi.FieldStorage()
team = fields.getfirst('team', '').strip()
passwd = fields.getfirst('passwd', '').strip()
code = fields.getfirst('code', '')
if not teams.chkpasswd(team, passwd):
body = '<p>Authentication failed.</p>'
elif not code:
body = '<p>No program given.</p>'
else:
path = os.path.join(basedir, 'ai/players', quote(team, safe=''))
file = open(path, 'w')
file.write(code)
file.close()
body = ("<p>Submission successful.</p>")
html.serve('Tanks Submission', body, links=links)

View File

@ -1,213 +0,0 @@
function dbg(o) {
e = document.getElementById("debug");
e.innerHTML = o;
}
function torgba(color, alpha) {
var r = parseInt(color.substring(1,3), 16);
var g = parseInt(color.substring(3,5), 16);
var b = parseInt(color.substring(5,7), 16);
return "rgba(" + r + "," + g + "," + b + "," + alpha + ")";
}
function Tank(ctx, width, height, color, sensors) {
var craterStroke = torgba(color, 0.5);
var craterFill = torgba(color, 0.2);
var sensorStroke = torgba(color, 0.4);
var maxlen = 0;
this.x = 0;
this.y = 0;
this.rotation = 0;
this.turret = 0;
// Do all the yucky math up front
this.sensors = new Array();
for (i in sensors) {
s = sensors[i];
// r, angle, width, turret
this.sensors[i] = new Array();
this.sensors[i][0] = s[0];
this.sensors[i][1] = s[1] - (s[2]/2);
this.sensors[i][2] = s[1] + (s[2]/2);
this.sensors[i][3] = s[3]?1:0;
if (s[0] > maxlen) {
maxlen = s[0];
}
}
// Set up our state, for later interleaved draw requests
this.set_state = function(x, y, rotation, turret, flags, sensor_state) {
this.x = x;
this.y = y;
this.rotation = rotation;
this.turret = turret;
this.fire = flags & 1;
this.led = flags & 2;
this.sensor_state = sensor_state;
}
this.draw_crater = function() {
var points = 7;
var angle = Math.PI / points;
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.lineWidth = 2;
ctx.strokeStyle = craterStroke;
ctx.fillStyle = craterFill;
ctx.beginPath();
ctx.moveTo(12, 0);
for (i = 0; i < points; i += 1) {
ctx.rotate(angle);
ctx.lineTo(6, 0);
ctx.rotate(angle);
ctx.lineTo(12, 0);
}
ctx.closePath()
ctx.stroke();
ctx.fill();
ctx.restore();
}
this.draw_sensors = function() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.lineWidth = 1;
for (i in this.sensors) {
var s = this.sensors[i];
var adj = this.turret * s[3];
if (this.sensor_state & (1 << i)) {
// Sensor is triggered
ctx.strokeStyle = "#000";
} else {
ctx.strokeStyle = sensorStroke;
}
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.arc(0, 0, s[0], s[1] + adj, s[2] + adj, false);
ctx.closePath();
ctx.stroke();
}
ctx.restore();
}
this.draw_tank = function() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.fillStyle = color;
ctx.fillRect(-5, -4, 10, 8);
ctx.fillStyle = "#777";
ctx.fillRect(-7, -9, 15, 5);
ctx.fillRect(-7, 4, 15, 5);
ctx.rotate(this.turret);
if (this.fire) {
ctx.fillStyle = color;
ctx.fillRect(0, -1, 45, 2);
} else {
if (this.led) {
ctx.fillStyle = "#f00";
} else {
ctx.fillStyle = "#000";
}
ctx.fillRect(0, -1, 10, 2);
}
ctx.restore();
}
this.draw_wrap_sensors = function() {
var orig_x = this.x;
var orig_y = this.y;
for (x = this.x - width; x < width + maxlen; x += width) {
for (y = this.y - height; y < height + maxlen; y += height) {
if ((-maxlen < x) && (x < width + maxlen) &&
(-maxlen < y) && (y < height + maxlen)) {
this.x = x;
this.y = y;
this.draw_sensors();
}
}
}
this.x = orig_x;
this.y = orig_y;
}
}
function start(game) {
var canvas = document.getElementById('battlefield');
var ctx = canvas.getContext('2d');
var loop_id;
canvas.width = game[0];
canvas.height = game[1];
// game[2] is tank descriptions
var turns = game[3];
// Set up tanks
var tanks = new Array();
for (i in game[2]) {
var desc = game[2][i];
tanks[i] = new Tank(ctx, game[0], game[1], desc[0], desc[1]);
}
var frame = 0;
var lastframe = 0;
var fps = document.getElementById('fps');
function update_fps() {
fps.innerHTML = (frame - lastframe);
lastframe = frame;
}
function update() {
var idx = frame % (turns.length + 20);
var turn;
frame += 1;
if (idx >= turns.length) {
return;
}
canvas.width = canvas.width;
turn = turns[idx];
// Draw craters first
for (i in turn) {
t = turn[i];
if (! t) {
tanks[i].draw_crater();
}
}
// Then sensors
for (i in turn) {
t = turn[i];
if (t) {
// Surely there's a better way to do this.
tanks[i].set_state(t[0], t[1], t[2], t[3], t[4], t[5]);
tanks[i].draw_wrap_sensors();
}
}
// Then tanks
for (i in turn) {
t = turn[i];
if (t) {
tanks[i].draw_tank()
}
}
}
loop_id = setInterval(update, 66);
setInterval(update_fps, 1000);
}