From 14d7e3509d176ba2d18f77a1551257c6ccf1ff72 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Mon, 13 Sep 2010 16:58:30 -0600 Subject: [PATCH] Add some online docs, clean up some cruft --- .gitmodules | 3 - mdwntohtml.py | 44 --------- mkpuzzles.py | 150 ----------------------------- puzzles | 1 - setup.py | 11 --- www/ctf.css | 4 +- www/index.html | 54 +++++++++++ www/puzzler.cgi | 186 ------------------------------------ www/register.cgi | 106 --------------------- www/scoring.html | 94 +++++++++++++++++++ www/tanks/errors.cgi | 56 ----------- www/tanks/results.cgi | 33 ------- www/tanks/submit.cgi | 39 -------- www/tanks/tanks.js | 213 ------------------------------------------ 14 files changed, 150 insertions(+), 844 deletions(-) delete mode 100644 .gitmodules delete mode 100755 mdwntohtml.py delete mode 100755 mkpuzzles.py delete mode 160000 puzzles delete mode 100755 setup.py create mode 100644 www/index.html delete mode 100755 www/puzzler.cgi delete mode 100755 www/register.cgi create mode 100644 www/scoring.html delete mode 100755 www/tanks/errors.cgi delete mode 100755 www/tanks/results.cgi delete mode 100755 www/tanks/submit.cgi delete mode 100644 www/tanks/tanks.js diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 703ccba..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "puzzles"] - path = puzzles - url = cfl:/var/projects/ctf-puzzles diff --git a/mdwntohtml.py b/mdwntohtml.py deleted file mode 100755 index bafe89f..0000000 --- a/mdwntohtml.py +++ /dev/null @@ -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) diff --git a/mkpuzzles.py b/mkpuzzles.py deleted file mode 100755 index c1ce50e..0000000 --- a/mkpuzzles.py +++ /dev/null @@ -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 = ''' - -''' - -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('
%s
\n' % readme) - if files: - body.append('\n') - body.append(''' -
-
- Your answer: - - - Team:
- Password:
- Key:
- -
-
-''' % {'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) - diff --git a/puzzles b/puzzles deleted file mode 160000 index 2c4f955..0000000 --- a/puzzles +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2c4f955d275b03891cd84f17a80dd397780272c6 diff --git a/setup.py b/setup.py deleted file mode 100755 index 87813d8..0000000 --- a/setup.py +++ /dev/null @@ -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']) diff --git a/www/ctf.css b/www/ctf.css index 5aea339..2e58d80 100644 --- a/www/ctf.css +++ b/www/ctf.css @@ -35,8 +35,8 @@ a img { } a { - text-decoration: none; - color: #b71; + text-decoration: underline; + color: #84b; font-weight: bold; } diff --git a/www/index.html b/www/index.html new file mode 100644 index 0000000..d6cce55 --- /dev/null +++ b/www/index.html @@ -0,0 +1,54 @@ + + + + Welcome + + + +

Welcome

+ +

Important Links

+ + +

Claim Token

+
+
+ team:
+ token: +
+
+ +

Rules

+ + + diff --git a/www/puzzler.cgi b/www/puzzler.cgi deleted file mode 100755 index b43757c..0000000 --- a/www/puzzler.cgi +++ /dev/null @@ -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('') - 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('') - else: - out.write('

None (someone is slacking)

') - 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('

%d points for %s.

' % (points, cgi.escape(team))) - out.write('

Back to %s.

' % (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: diff --git a/www/register.cgi b/www/register.cgi deleted file mode 100755 index bb0021c..0000000 --- a/www/register.cgi +++ /dev/null @@ -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, "&", "&") - str = string.gsub(str, "<", "<") - str = string.gsub(str, ">", ">") - 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("") - print("") - print(" ") - print(" ") - 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() \ No newline at end of file diff --git a/www/scoring.html b/www/scoring.html new file mode 100644 index 0000000..b78c82f --- /dev/null +++ b/www/scoring.html @@ -0,0 +1,94 @@ +<!DOCTYPE html> +<html> + <head> + <title>About scoring + + + + +

About scoring

+

+ 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. +

+ +

+ There are two main ways to make points: puzzles + and tokens. Your contest may have other ways to make + points: these will either be automatic, or explained elsewhere. +

+ + +

Puzzles

+

+ Many of the categories are in the form of + multiple puzzles: 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. +

+ + +

Tokens

+

+ Tokens are strings redeemable once for a single point each. A + token for the "example" category might look like this: +

+ +
example:xenon-codex
+ +

+ 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 welcome page, or you + can write your own script to automate token submission. +

+ +

+ 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. +

+ +

Reading the scoreboard

+

+ The scoreboard 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. +

+ +

+ 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. +

+ + +

About time

+

+ 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. +

+

+ 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. +

+ + diff --git a/www/tanks/errors.cgi b/www/tanks/errors.cgi deleted file mode 100755 index f99d874..0000000 --- a/www/tanks/errors.cgi +++ /dev/null @@ -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 = ''' -

Tanks

-
  • Docs
  • -
  • Results
  • -
  • Submit
  • -
  • My Errors
  • -''' - -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('

    Your latest errors:

    ') - errors = open(path).readlines() - if errors: - body.append('') - else: - body.append('

    There were no errors.

    ') - else: - body.append('

    No error file found.

    ') -else: - body.append('Authentication failed.') - -body.append(''' -
    -
    - Error report request: - Team:
    - Password:
    - -
    -
    ''') - -html.serve('Tanks Errors', '\n'.join(body), links=links) - diff --git a/www/tanks/results.cgi b/www/tanks/results.cgi deleted file mode 100755 index e6632f1..0000000 --- a/www/tanks/results.cgi +++ /dev/null @@ -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 = ''' -

    Tanks

    -
  • Docs
  • -
  • Results
  • -
  • Submit
  • -
  • My Errors
  • -''' - -body = [] - -body.append('

    Last Winner:

    ') -body.append('

    ') -body.append(escape(open(os.path.join(basedir, 'winner')).read())) -body.append('

    ') -body.append('

    Results so far:

    ') -body.append('') - -html.serve('Tanks Results', '\n'.join(body), links=links) diff --git a/www/tanks/submit.cgi b/www/tanks/submit.cgi deleted file mode 100755 index 8750d3b..0000000 --- a/www/tanks/submit.cgi +++ /dev/null @@ -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 = ''' -

    Tanks

    -
  • Docs
  • -
  • Results
  • -
  • Submit
  • -
  • My Errors
  • -''' - - -fields = cgi.FieldStorage() -team = fields.getfirst('team', '').strip() -passwd = fields.getfirst('passwd', '').strip() -code = fields.getfirst('code', '') -if not teams.chkpasswd(team, passwd): - body = '

    Authentication failed.

    ' -elif not code: - body = '

    No program given.

    ' -else: - path = os.path.join(basedir, 'ai/players', quote(team, safe='')) - file = open(path, 'w') - file.write(code) - file.close() - - body = ("

    Submission successful.

    ") - -html.serve('Tanks Submission', body, links=links) diff --git a/www/tanks/tanks.js b/www/tanks/tanks.js deleted file mode 100644 index f416423..0000000 --- a/www/tanks/tanks.js +++ /dev/null @@ -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); -} -