Fix 2 tanks bugs

This commit is contained in:
Neale Pickett 2010-03-03 11:28:51 -06:00
parent bbd78ab32f
commit 3cae9d7442
17 changed files with 5 additions and 1918 deletions

View File

@ -1,33 +0,0 @@
#! /usr/bin/python
import asynchat
import asyncore
import socket
class Flagger(asynchat.async_chat):
"""Use to connect to flagd and submit the current flag holder."""
def __init__(self, addr, auth):
asynchat.async_chat.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((addr, 1))
self.push(auth + '\n')
self.flag = None
def handle_read(self):
# We don't care.
msg = self.recv(4096)
def handle_error(self):
# If we lose the connection to flagd, nobody can score any
# points. Terminate everything.
asyncore.close_all()
asynchat.async_chat.handle_error(self)
def set_flag(self, team):
if team:
eteam = team.encode('utf-8')
else:
eteam = ''
self.push(eteam + '\n')
self.flag = team

View File

@ -1,35 +0,0 @@
#! /usr/bin/python
import os
import string
import sys
from codecs import open
from paths import *
template_fn = os.path.join(LIB, 'template.html')
template = string.Template(open(template_fn, encoding='utf-8').read())
base = BASE_URL
css = base + 'ctf.css'
def substitute(title, body, base=base, hdr='', body_class='', onload='', links=''):
return template.substitute(title=title,
hdr=hdr,
body_class=body_class,
base=base,
links=links,
onload=onload,
body=body)
def serve(title, body, **kwargs):
out = substitute(title, body, **kwargs)
print 'Content-type: text/html'
print 'Content-length: %d' % len(out)
print
sys.stdout.write(out)
sys.stdout.flush()
def write(filename, title, body, **kwargs):
f = open(filename, 'w', encoding='utf-8')
f.write(substitute(title, body, **kwargs))

View File

@ -1,6 +0,0 @@
VAR = "/opt/ctf/var"
WWW = "/opt/ctf/www"
LIB = "/opt/ctf/lib"
BIN = "/opt/ctf/bin"
SBIN = "/opt/ctf/sbin"
BASE_URL = "/"

View File

@ -1,40 +0,0 @@
#! /usr/bin/python
from urllib import quote
import teams
import time
import os
import paths
pointsdir = os.path.join(paths.VAR, 'points')
def award(cat, team, points):
if not team:
team = teams.house
now = time.strftime('%Y-%m-%dT%H:%M:%S')
pid = os.getpid()
qcat = quote(cat, '')
qteam = quote(team, '')
basename = '%s.%d.%s.%s' % (now, pid, qcat, qteam)
# FAT can't handle :
basename = basename.replace(':', '.')
tmpfn = os.path.join(pointsdir, 'tmp', basename)
curfn = os.path.join(pointsdir, 'cur', basename)
f = open(tmpfn, 'w')
f.write('%s\t%s\t%s\t%d\n' % (now, cat, team, points))
f.close()
os.rename(tmpfn, curfn)
def main():
import optparse
p = optparse.OptionParser('%prog CATEGORY TEAM POINTS')
opts, args = p.parse_args()
if len(args) != 3:
p.error('Wrong number of arguments')
cat, team, points = args
points = int(points)
award(cat, team, points)
if __name__ == '__main__':
main()

View File

@ -1,72 +0,0 @@
#! /usr/bin/python
import fcntl
import time
import os
from urllib import quote, unquote
import paths
house = 'dirtbags'
passwdfn = os.path.join(paths.VAR, 'passwd')
team_colors = ['F0888A', '88BDF0', '00782B', '999900', 'EF9C00',
'F4B5B7', 'E2EFFB', '89CA9D', 'FAF519', 'FFE7BB',
'BA88F0', '8DCFF4', 'BEDFC4', 'FFFAB2', 'D7D7D7',
'C5B9D7', '006189', '8DCB41', 'FFCC00', '898989']
teams = {}
built = 0
def build_teams():
global teams, built
if not os.path.exists(passwdfn):
return
if os.path.getmtime(passwdfn) <= built:
return
teams = {}
try:
f = open(passwdfn)
for line in f:
line = line.strip()
if not line:
continue
team, passwd, color = map(unquote, line.strip().split('\t'))
teams[team] = (passwd, color)
except IOError:
pass
built = time.time()
def validate(team):
build_teams()
def chkpasswd(team, passwd):
validate(team)
if teams.get(team, [None, None])[0] == passwd:
return True
else:
return False
def exists(team):
validate(team)
if team == house:
return True
return team in teams
def add(team, passwd):
build_teams()
color = team_colors[len(teams)%len(team_colors)]
assert team not in teams, "Team already exists."
f = open(passwdfn, 'a')
fcntl.lockf(f, fcntl.LOCK_EX)
f.seek(0, 2)
f.write('%s\t%s\t%s\n' % (quote(team, ''),
quote(passwd, ''),
quote(color, '')))
def color(team):
validate(team)
t = teams.get(team)
if not t:
return '888888'
return t[1]

View File

@ -1,46 +0,0 @@
import math
class Function(object):
"""Represents a single condition or action. This doc string is printed
as user documentation. You should override it to say something useful."""
def __call__(self, tank):
"""The __call__ method should be of this basic form. Actions
should return None, conditions should return True or False. Actions
should utilize the set* methods of tanks. Conditions can utilize the
tanks get* methods."""
pass
def _limitArgs(self, args, max):
"""Raises a ValueError if there are more than max args."""
if len(args) > max:
raise ValueError("Too many arguments: %s" % ','.join(args))
def _checkRange(self, value, name, min=0, max=100):
"""Check that the value is in the given range.
Raises an exception with useful info for invalid values. Name is used to
let the user know which value is wrong."""
try:
value = int(value)
except:
raise ValueError("Invalid %s value: %s" % (name, value))
assert value >= min and value <= max, "Invalid %s. %ss must be in"\
" the %s %d-%d" % \
(name, name.capitalize(), value, min, max)
return value
def _convertAngle(self, value, name):
"""Parse the given value as an angle in degrees, and return its value
in radians. Raise useful errors.
Name is used in the errors to describe the field."""
try:
angle = math.radians(value)
except:
raise ValueError("Invalid %s value: %s" % (name, value))
assert angle >= 0 and angle < 2*math.pi, "Invalid %s; "\
"It be in the range 0 and 359." % name
return angle

View File

@ -1,206 +0,0 @@
import math
def rotatePoint(point, angle):
"""Assuming 0,0 is the center, rotate the given point around it."""
x,y = point
r = math.sqrt(x**2 + y**2)
if r == 0:
return 0, 0
theta = math.acos(x/r)
if y < 0:
theta = -theta
theta = theta + angle
return int(round(r*math.cos(theta))), int(round(r*math.sin(theta)))
def rotatePoly(points, angle):
"""Rotate the given list of points around 0,0 by angle."""
return [ rotatePoint(point, angle) for point in points ]
def displace(point, disp, limits):
"""Displace point by disp, wrapping around limits."""
x = (point[0] + disp[0])
while x >= limits[0]:
x = x - limits[0]
while x < 0:
x = x + limits[0]
y = (point[1] + disp[1])
while y >= limits[1]:
y = y - limits[1]
while y < 0:
y = y + limits[1]
return x,y
def displacePoly(points, disp, limits, coordSequence=False):
"""Displace each point (x,y) in 'points' by 'disp' (x,y). The limits of
the drawing space are assumed to be at x=0, y=0 and x=limits[0],
y=limits[1]. If the poly overlaps the edge of the drawing space, the
poly is duplicated on each side.
@param coordSequence: If true, the coordinates are returned as a sequence -
x1, y1, x2, y2, ... This is need by some PIL drawing
commands.
@returns: A list of polys, displaced by disp
"""
xDup = 0; yDup = 0
maxX, maxY = limits
basePoints = []
for point in points:
x,y = int(point[0] + disp[0]), int(point[1] + disp[1])
# Check if duplication is needed on each axis
if x > maxX:
# If this is negative, then we need to duplicate in the negative
# direction.
xDup = -1
elif x < 0:
xDup = 1
if y > maxY:
yDup = -1
elif y < 0:
yDup = 1
basePoints.append( (x,y) )
polys = [basePoints]
if xDup:
polys.append([(x + maxX*xDup, y) for x,y in basePoints] )
if yDup:
polys.append([(x, maxY*yDup + y) for x,y in basePoints] )
if xDup and yDup:
polys.append([(x+maxX*xDup, maxY*yDup+y) for x,y in basePoints])
# Switch coordinates to sequence mode.
# (x1, y1, x2, y2) instead of ((x1, y1), (x2, y2))
if coordSequence:
seqPolys = []
for poly in polys:
points = []
for point in poly:
points.extend(point)
seqPolys.append(points)
polys = seqPolys
return polys
def polar2cart(r, theta):
"""Return the cartesian coordinates for r, theta."""
x = r*math.cos(theta)
y = r*math.sin(theta)
return x,y
def minShift(center, point, limits):
"""Get the minimum distances between the two points, given that the board
wraps at the givin limits."""
dx = point[0] - center[0]
if dx < -limits[0]/2.0:
dx = point[0] + limits[0] - center[0]
elif dx > limits[0]/2.0:
dx = point[0] - (center[0] + limits[0])
dy = point[1] - center[1]
if dy < - limits[1]/2.0:
dy = point[1] + limits[1] - center[1]
elif dy > limits[1]/2.0:
dy = point[1] - (limits[1] + center[1])
return dx, dy
def relativePolar(center, point, limits):
"""Returns the angle, from zero, to the given point assuming this
center is the origin. Take into account wrapping round the limits of the board.
@returns: r, theta
"""
dx, dy = minShift(center, point, limits)
r = math.sqrt(dx**2 + dy**2)
theta = math.acos(dx/r)
if dy < 0:
theta = 2*math.pi - theta
return r, theta
def reduceAngle(angle):
"""Reduce the angle such that it is in 0 <= angle < 2pi"""
while angle >= math.pi*2:
angle = angle - math.pi*2
while angle < 0:
angle = angle + math.pi*2
return angle
def angleDiff(angle1, angle2):
"""Returns the difference between the two angles. They are assumed
to be in radians, and must be in the range 0 <= angle < 2*pi.
@raises AssertionError: The angles given must be in the range 0 <= angle < 2pi
@returns: The minimum distance between the two angles; The distance
is negative if angle2 leads angle1 (clockwise)..
"""
for angle in angle1, angle2:
assert angle < 2*math.pi and angle >= 0, \
'angleDiff: bad angle %s' % angle
diff = angle2 - angle1
if diff > math.pi:
diff = diff - 2*math.pi
elif diff < -math.pi:
diff = diff + 2*math.pi
return diff
def getDist(point1, point2):
"""Returns the distance between point1 and point2."""
dx = point2[0] - point1[0]
dy = point2[1] - point1[1]
return math.sqrt(dx**2 + dy**2)
def segmentCircleCollision(segment, center, radius):
"""Return True if the given circle touches the given line segment.
@param segment: A list of two points [(x1,y1), (x2, y2)] that define
the line segment.
@param center: The center point of the circle.
@param radius: The radius of the circle.
@returns: True if the the circle touches the line segment, False otherwise.
"""
a = getDist(segment[0], center)
c = getDist(segment[1], center)
base = getDist(segment[0], segment[1])
# If we're close enough to the end points, then we're close
# enough to the segment.
if a < radius or c < radius:
return True
# First we find the are of the triangle formed by the line segment
# and point. I use Heron's formula for the area. Using this, we'll
# find the distance d from the point to the line. We'll later make
# sure that the collision is with the line segment, and not just the
# line.
s = (a + c + base)/2
A = math.sqrt(s*(s - a)*(s - c)*(s - base))
d = 2*A/base
# print s, a, c, A, d, radius
# If the distance from the point to the line is more than the
# target radius, this isn't a hit.
if d > radius:
return False
# If the distance from an endpoint to the intersection between
# our line segment and the line perpendicular to it that passes through
# the point is longer than the line segment, then this isn't a hit.
elif math.sqrt(a**2 - d**2) > base or \
math.sqrt(c**2 - d**2) > base:
return False
else:
# The triangle is acute, that means we're close enough.
return True

View File

@ -1,399 +0,0 @@
import fcntl
import math
import os
import random
import cgi
from sets import Set as set
from ctf import teams, html, paths
from cStringIO import StringIO
from urllib import unquote, quote
import Tank
class NotEnoughPlayers(Exception):
pass
class Pflanzarr:
SPACING = 150
def __init__(self, dir):
"""Initialize a new game of Pflanzarr.
@param dir: The data directory."""
# Setup the game environment
self._setupDirectories(dir)
# Figure out what game number this is.
self.gameNum = self._getGameNum()
self.gameFilename = os.path.join(self._resultsDir, '%04d.html' % self.gameNum)
tmpPlayers = os.listdir(self._playerDir)
players = []
for p in tmpPlayers:
p = unquote(p)
if (not (p.startswith('.')
or p.endswith('#')
or p.endswith('~'))
and teams.exists(p)):
players.append(p)
AIs = {}
for player in players:
AIs[player] = open(os.path.join(self._playerDir, player)).read()
defaultAIs = self._getDefaultAIs(dir)
if len(players) < 1:
raise NotEnoughPlayers()
# The one is added to ensure that there is at least one house
# bot.
cols = math.sqrt(len(players) + 1)
if int(cols) != cols:
cols = cols + 1
cols = int(cols)
cols = max(cols, 2)
rows = len(players)/cols
if len(players) % cols != 0:
rows = rows + 1
rows = max(rows, 2)
self._board = (cols*self.SPACING, rows*self.SPACING)
while len(players) < cols*rows:
players.append(None)
self._tanks = []
for i in range(cols):
for j in range(rows):
startX = i*self.SPACING + self.SPACING/2
startY = j*self.SPACING + self.SPACING/2
player = random.choice(players)
players.remove(player)
color = '#' + teams.color(player)
tank = Tank.Tank( player, (startX, startY), color,
self._board, testMode=True)
if player == None:
tank.program(random.choice(defaultAIs))
else:
tank.program(AIs[player])
self._tanks.append(tank)
# We only want to make these once, so we do it here.
self._tanksByX = list(self._tanks)
self._tanksByY = list(self._tanks)
self._deadTanks = set()
def run(self, maxTurns=None):
kills = {}
for tank in self._tanks:
kills[tank] = set()
# Open HTML output
hdr = StringIO()
hdr.write('<script type="application/javascript" src="../tanks.js"></script>\n'
'<script type="application/javascript">\n')
hdr.write('turns = [\n')
turn = 0
lastTurns = 3
while ((maxTurns is None) or turn < maxTurns) and lastTurns > 0:
if len(self._tanks) - len(self._deadTanks) < 2:
lastTurns = lastTurns - 1
near = self._getNear()
deadThisTurn = set()
liveTanks = set(self._tanks).difference(self._deadTanks)
for tank in liveTanks:
# Shoot now, if we said to shoot last turn
dead = tank.fire( near[tank] )
kills[tank] = kills[tank].union(dead)
self._killTanks(dead, 'Shot by %s' % cgi.escape(tank.name or teams.house))
for tank in liveTanks:
# We also check for collisions here, while we're at it.
dead = tank.sense( near[tank] )
kills[tank] = kills[tank].union(dead)
self._killTanks(dead, 'Collision')
hdr.write(' [\n')
# Draw the explosions
for tank in self._deadTanks:
tank.draw(hdr)
# Draw the live tanks.
for tank in self._tanksByX:
# Have the tank run its program, move, etc.
tank.draw(hdr)
hdr.write(' ],\n')
# Have the live tanks do their turns
for tank in self._tanksByX:
tank.execute()
turn = turn + 1
# Removes tanks from their own kill lists.
for tank in kills:
if tank in kills[tank]:
kills[tank].remove(tank)
for tank in self._tanks:
self._outputErrors(tank)
hdr.write('];\n')
hdr.write('</script>\n')
# Decide on the winner
winner = self._chooseWinner(kills)
self.winner = winner.name
# Now generate HTML body
body = StringIO()
body.write(' <canvas id="battlefield" width="%d" height="%d">\n' % self._board)
body.write(' Sorry, you need an HTML5-capable browser to see this.\n'
' </canvas>\n'
' <p>\n')
if self.gameNum > 0:
body.write(' <a href="%04d.html">&larr; Prev</a> |' %
(self.gameNum - 1))
body.write(' <a href="%04d.html">Next &rarr;</a> |' %
(self.gameNum + 1))
body.write(' <span id="fps">0</span> fps\n'
' </p>\n'
' <table class="results">\n'
' <tr>\n'
' <th>Team</th>\n'
' <th>Kills</th>\n'
' <th>Cause of Death</th>\n'
' </tr>\n')
tanks = self._tanks[:]
tanks.remove(winner)
tanks[0:0] = [winner]
for tank in tanks:
if tank is winner:
rowStyle = ('style="font-weight: bold; '
'color: #000; '
'background-color: %s;"' % tank.color)
else:
rowStyle = 'style="background-color:%s; color: #000;"' % tank.color
if tank.name:
name = cgi.escape(tank.name)
else:
name = teams.house
body.write('<tr %s><td>%s</td><td>%d</td><td>%s</td></tr>' %
(rowStyle,
name,
len(kills[tank]),
cgi.escape(tank.deathReason)))
body.write(' </table>\n')
# Write everything out
html.write(self.gameFilename,
'Tanks round %d' % self.gameNum,
body.getvalue(),
hdr=hdr.getvalue(),
onload='start(turns);')
def _killTanks(self, tanks, reason):
for tank in tanks:
if tank in self._tanksByX:
self._tanksByX.remove(tank)
if tank in self._tanksByY:
self._tanksByY.remove(tank)
tank.die(reason)
self._deadTanks = self._deadTanks.union(tanks)
def _chooseWinner(self, kills):
"""Choose a winner. In case of a tie, live tanks prevail, in case
of further ties, a winner is chosen at random. This outputs the winner
to the winners file and outputs a results table html file."""
tanks = list(self._tanks)
def winSort(t1, t2):
"""Sort by # of kill first, then by life status."""
result = cmp(len(kills[t1]), len(kills[t2]))
if result != 0:
return result
if t1.isDead and not t2.isDead:
return -1
elif not t1.isDead and t2.isDead:
return 1
else:
return 0
tanks.sort(winSort)
tanks.reverse()
# Get the list of potential winners
winners = []
for i in range(len(tanks)):
if len( kills[tanks[0]] ) == len( kills[tanks[i]] ) and \
tanks[0].isDead == tanks[i].isDead:
winners.append(tanks[i])
else:
break
winner = random.choice(winners)
return winner
def _outputErrors(self, tank):
"""Output errors for each team."""
if tank.name == None:
return
if tank._program.errors:
print tank.name, 'has errors'
fileName = os.path.join(self._errorDir, quote(tank.name, ''))
file = open(fileName, 'w')
for error in tank._program.errors:
file.write(error)
file.write('\n')
file.close()
def _getNear(self):
"""A dictionary of the set of tanks nearby each tank. Nearby is
defined as within the square centered the tank with side length equal
twice the sensor range. Only a few tanks within the set (those in the
corners of the square) should be outside the sensor range."""
self._tanksByX.sort(lambda t1, t2: cmp(t1.pos[0], t2.pos[0]))
self._tanksByY.sort(lambda t1, t2: cmp(t1.pos[1], t2.pos[1]))
nearX = {}
nearY = {}
for tank in self._tanksByX:
nearX[tank] = set()
nearY[tank] = set()
numTanks = len(self._tanksByX)
offset = 1
for index in range(numTanks):
cTank = self._tanksByX[index]
maxRange = cTank.SENSOR_RANGE + cTank.RADIUS + 1
near = set([cTank])
for i in [(j + index) % numTanks for j in range(1, offset)]:
near.add(self._tanksByX[i])
while offset < numTanks:
nTank = self._tanksByX[(index + offset) % numTanks]
if (index + offset >= numTanks and
self._board[0] + nTank.pos[0] - cTank.pos[0] < maxRange):
near.add(nTank)
offset = offset + 1
elif (index + offset < numTanks and
nTank.pos[0] - cTank.pos[0] < maxRange ):
near.add(nTank)
offset = offset + 1
else:
break
if offset > 1:
offset = offset - 1
for tank in near:
nearX[tank] = nearX[tank].union(near)
offset = 1
for index in range(numTanks):
cTank = self._tanksByY[index]
maxRange = cTank.SENSOR_RANGE + cTank.RADIUS + 1
near = set([cTank])
for i in [(j + index) % numTanks for j in range(1, offset)]:
near.add(self._tanksByY[i])
while offset < numTanks:
nTank = self._tanksByY[(index + offset) % numTanks]
if (index + offset < numTanks and
nTank.pos[1] - cTank.pos[1] < maxRange):
near.add(nTank)
offset = offset + 1
elif (index + offset >= numTanks and
self._board[1] + nTank.pos[1] - cTank.pos[1] < maxRange):
near.add(nTank)
offset = offset + 1
else:
break
if offset > 1:
offset = offset - 1
for tank in near:
nearY[tank] = nearY[tank].union(near)
near = {}
for tank in self._tanksByX:
near[tank] = nearX[tank].intersection(nearY[tank])
near[tank].remove(tank)
return near
def _setupDirectories(self, dir):
"""Setup all the directories needed by the game."""
if not os.path.exists(dir):
os.mkdir(dir)
self._dir = dir
# Don't run more than one game at the same time.
self._lockFile = open(os.path.join(dir, '.lock'), 'a')
try:
fcntl.flock(self._lockFile, fcntl.LOCK_EX|fcntl.LOCK_NB)
except:
sys.exit(1)
# Setup all the directories we'll need.
self._resultsDir = os.path.join(dir, 'results')
self._errorDir = os.path.join(dir, 'errors')
self._playerDir = os.path.join(dir, 'ai', 'players')
def _getDefaultAIs(self, basedir):
"""Load all the house bot AIs."""
defaultAIs = []
path = os.path.join(basedir, 'ai', 'house')
files = os.listdir(path)
for fn in files:
if fn.startswith('.'):
continue
fn = os.path.join(path, fn)
file = open(fn)
defaultAIs.append(file.read())
return defaultAIs
def _getGameNum(self):
"""Figure out what game number this is from the past games played."""
games = os.listdir(self._resultsDir)
games.sort()
if games:
fn = games[-1]
s, _ = os.path.splitext(fn)
return int(s) + 1
else:
return 0
if __name__ == '__main__':
import sys, traceback
try:
p = Pflanzarr(sys.argv[1])
p.run(int(sys.argv[3]))
except:
traceback.print_exc()
print "Usage: Pflanzarr.py dataDirectory #turns"

View File

@ -1,234 +0,0 @@
"""<H2>Introduction</H2>
You are the proud new operator of a M-375 Pflanzarr Tank. Your tank is
equipped with a powerful laser cannon, independently rotating turret
section, up to 10 enemy detection sensors, and a standard issue NATO hull.
Unfortunately, it lacks seats, and thus must rely own its own wits and your
skills at designing those wits to survive.
<H2>Programming Your Tank</H2>
Your tanks are programmed using the Super Useful Command and Kontrol language,
the very best in laser tank AI languages. It includes amazing features such
as comments (Started by a #, ended at EOL), logic, versatility, and
semi-colons (all lines must end in one). As with all new military systems
it utilizes only integers; we must never rest in our
diligence against the communist floating point conspiracy. Whitespace is
provided by trusted contractors, and should never interfere with operations.
<P>
Your program should be separated into Setup and AI commands. The definitions
section lets you designated the behaviors of its sensors and memory.
Each setup command must begin with a '>'. Placing setup commands after
the first AI command is a violation of protocol.
Here are some examples of correct setup commands:
<pre class="docs">>addsensor(80, 90, 33);
>addsensor(50, 0, 10, 1);
>addtimer(3);</pre>
The AI section will act as the brain of your tank. Each AI line is
separated into a group of conditions functions and a group of action
functions. If all the conditions are satisfactory (true), all of the actions
are given as orders. Conditions are separated by ampersands, actions separated
by periods. Here are some examples of AI commands:
<pre class="docs">
sensor(1) & sensor(2) & fireready() : fire();
sensor(0,0)&sin(5): move(40, 30) . turretcw(50);
sensor(4) & random(4,5) : led(1).settoggle(0,1);</pre>
Your tank will check its program each turn, and attempt to the best of its
abilities to carry out its orders (or die trying). Like any military mind,
your tank may receive a plethora of often conflicting orders and information.
This a SMART TANK, however. It knows that the proper thing to do with each
subsystem is to have that subsystem follow only the last order given each turn.
"""
import traceback
import conditions
import actions
import setup
class Statement(object):
"""Represents a single program statement. If all the condition Functions
evaluate to True, the actions are all executed in order."""
def __init__(self, lineNum, line, conditions, actions):
self.lineNum = lineNum
self.line = line
self._conditions = conditions
self._actions = actions
def __call__(self, tank):
success = True
for condition in self._conditions:
if not condition(tank):
success = False
break
if success:
for action in self._actions:
action(tank)
class Program(object):
"""This parses and represents a Tank program."""
CONDITION_SEP = '&'
ACTION_SEP = '.'
def __init__(self, text):
"""Initialize this program, parsing the given text."""
self.errors = []
self._program, self._setup = self._parse(text)
def setup(self, tank):
"""Execute all the setup actions."""
for action in self._setup:
try:
action(tank)
except Exception, msg:
self.errors.append("Bad setup action, line %d, msg: %s" % \
(action.lineNum, msg))
def __call__(self, tank):
"""Execute this program on the given tank."""
for statement in self._program:
try:
statement(tank)
except Exception, msg:
traceback.print_exc()
self.errors.append('Error executing program. \n'
'(%d) - %s\n'
'msg: %s\n' %
(statement.lineNum, statement.line, msg) )
def _parse(self, text):
"""Parse the text of the given program."""
program = []
setup = []
inSetup = True
lines = text.split(';')
lineNum = 0
for line in lines:
lineNum = lineNum + 1
originalLine = line
# Remove Comments
parts = line.split('\n')
for i in range(len(parts)):
comment = parts[i].find('#')
if comment != -1:
parts[i] = parts[i][:comment]
# Remove all whitespace
line = ''.join(parts)
line = line.replace('\r', '')
line = line.replace('\t', '')
line = line.replace(' ', '')
if line == '':
continue
if line.startswith('>'):
if inSetup:
if '>' in line[1:] or ':' in line:
self.errors.append('(%d) Missing semicolon: %s' %
(lineNum, line))
continue
try:
setupAction = self._parseSection(line[1:], 'setup')[0]
setupAction.lineNum = lineNum
setup.append(setupAction)
except Exception, msg:
self.errors.append('(%d) Error parsing setup line: %s'
'\nThe error was: %s' %
(lineNum, originalLine, msg))
continue
else:
self.errors.append('(%d) Setup lines aren\'t allowed '
'after the first command: %s' %
(lineNum, originalLine))
else:
# We've hit the first non-blank, non-comment, non-setup
# line
inSetup = False
semicolons = line.count(':')
if semicolons > 1:
self.errors.append('(%d) Missing semicolon: %s' %
(lineNum, line))
continue
elif semicolons == 1:
conditions, actions = line.split(':')
else:
self.errors.append('(%d) Invalid Line, no ":" seperator: %s'%
(lineNum, line) )
try:
conditions = self._parseSection(conditions, 'condition')
except Exception, msg:
self.errors.append('(%d) %s - "%s"' %
(lineNum, msg, line) )
continue
try:
actions = self._parseSection(actions, 'action')
except Exception, msg:
self.errors.append('(%d) %s - "%s"' %
(lineNum, msg, originalLine) )
continue
program.append(Statement(lineNum, line, conditions, actions))
return program, setup
def _parseSection(self, section, sectionType):
"""Parses either the action or condition section of each command.
@param section: The text of the section of the command to be parsed.
@param sectionType: The type of section to be parsed. Should be:
'condition', 'action', or 'setup'.
@raises ValueError: Raises ValueErrors when parsing errors occur.
@returns: Returns a list of parsed section components (Function objects).
"""
if sectionType == 'condition':
parts = section.split(self.CONDITION_SEP)
functions = conditions.conditions
if section == '':
return []
elif sectionType == 'action':
parts = section.split(self.ACTION_SEP)
functions = actions.actions
if section == '':
raise ValueError("The action section cannot be empty.")
elif sectionType == 'setup':
parts = [section]
functions = setup.setup
else:
raise ValueError('Invalid section Type - Contact Contest Admin')
parsed = []
for part in parts:
pos = part.find('(')
if pos == -1:
raise ValueError("Missing open paren in %s: %s" %
(sectionType, part) )
funcName = part[:pos]
if funcName not in functions:
raise ValueError("%s function %s is not accepted." %
(sectionType.capitalize(), funcName) )
if part[-1] != ')':
raise ValueError("Missing closing paren in %s: %s" %
(condition, sectionType) )
args = part[pos+1:-1]
if args != '':
args = args.split(',')
for i in range(len(args)):
args[i] = int(args[i])
else:
args = []
parsed.append(functions[funcName](*args))
return parsed

View File

@ -1,479 +0,0 @@
import math
import random
from sets import Set as set
import GameMath as gm
import Program
class Tank(object):
# How often, in turns, that we can fire.
FIRE_RATE = 20
# How far the laser shoots from the center of the tank
FIRE_RANGE = 45.0
# The radius of the tank, from the center of the turret.
# This is what is used for collision and hit detection.
RADIUS = 7.5
# Max speed, in pixels
SPEED = 7.0
# Max acceleration, as a fraction of speed.
ACCEL = 35
# Sensor range, in pixels
SENSOR_RANGE = 90.0
# Max turret turn rate, in radians
TURRET_TURN_RATE = math.pi/10
# The max number of sensors/timers/toggles
SENSOR_LIMIT = 10
def __init__(self, name, pos, color, boardSize, angle=None, tAngle=None,
testMode=True):
"""Create a new tank.
@param name: The name name of the tank. Stored in self.name.
@param pos: The starting position of the tank (x,y)
@param color: The color of the tank.
@param boardSize: The size of the board. (maxX, maxY)
@param angle: The starting angle of the tank, defaults to random.
@param tAngle: The starting turretAngle of the tank, defaults to random.
@param testMode: When True, extra debugging information is displayed. Namely,
arcs for each sensor are drawn, which turn white when
activated.
"""
# Keep track of what turn number it is for this tank.
self._turn = 0
self.name = name
self._testMode = testMode
assert len(pos) == 2 and pos[0] > 0 and pos[1] > 0, \
'Bad starting position: %s' % str(pos)
self.pos = pos
# The last speed of each tread (left, right)
self._lastSpeed = 0.0, 0.0
# The next speed that the tank should try to attain.
self._nextMove = 0,0
# When set, the led is drawn on the tank.
self.led = False
assert len(boardSize) == 2 and boardSize[0] > 0 and boardSize[1] > 0
# The limits of the playfield (maxX, maxY)
self._limits = boardSize
# The current angle of the tank.
if angle is None:
self._angle = random.random()*2*math.pi
else:
self._angle = angle
# The current angle of the turret
if tAngle is None:
self._tAngle = random.random()*2*math.pi
else:
self._tAngle = tAngle
self.color = color
# You can't fire until fireReady is 0.
self._fireReady = self.FIRE_RATE
# Means the tank will fire at it's next opportunity.
self._fireNow = False
# True when the tank has fired this turn (for drawing purposes)
self._fired = False
# What the desired turret angle should be (from the front of the tank).
# None means the turret should stay stationary.
self._tGoal = None
# Holds the properties of each sensor
self._sensors = []
# Holds the state of each sensor
self._sensorState = []
# The tanks toggle memory
self.toggles = []
# The tanks timers
self._timers = []
# Is this tank dead?
self.isDead = False
# The frame of the death animation.
self._deadFrame = 10
# Death reason
self.deathReason = 'survived'
def __repr__(self):
return '<tank: %s, (%d, %d)>' % (self.name, self.pos[0], self.pos[1])
def get_turn(self):
return self._turn
turn = property(get_turn)
def fire(self, near):
"""Shoots, if ordered to and able. Returns the set of tanks
destroyed."""
killed = set()
if self._fireReady > 0:
# Ignore the shoot order
self._fireNow = False
if self._fireNow:
self._fireNow = False
self._fireReady = self.FIRE_RATE
self._fired = True
firePoint = gm.polar2cart(self.FIRE_RANGE,
self._angle + self._tAngle)
for tank in near:
enemyPos = gm.minShift(self.pos, tank.pos, self._limits)
if gm.segmentCircleCollision(((0,0), firePoint), enemyPos,
self.RADIUS):
killed.add(tank)
else:
self._fired = False
return killed
def addSensor(self, range, angle, width, attachedTurret=False):
"""Add a sensor to this tank.
@param angle: The angle, from the tanks front and going clockwise,
of the center of the sensor. (radians)
@param width: The width of the sensor (percent).
@param range: The range of the sensor (percent)
@param attachedTurret: If set, the sensor moves with the turret.
"""
assert range >=0 and range <= 1, "Invalid range value."
if len(self._sensors) >= self.SENSOR_LIMIT:
raise ValueError("You can only have 10 sensors.")
range = range * self.SENSOR_RANGE
if attachedTurret:
attachedTurret = True
else:
attachedTurret = False
self._sensors.append((range, angle, width, attachedTurret))
self._sensorState.append(False)
def getSensorState(self, key):
return self._sensorState[key]
def setMove(self, left, right):
"""Parse the speed values given, and set them for the next move."""
self._nextMove = left, right
def setTurretAngle(self, angle=None):
"""Set the desired angle of the turret. No angle means the turret
should remain stationary."""
if angle is None:
self._tGoal = None
else:
self._tGoal = gm.reduceAngle(angle)
def setFire(self):
"""Set the tank to fire, next turn."""
self._fireNow = True
def fireReady(self):
"""Returns True if the tank can fire now."""
return self._fireReady == 0
def addTimer(self, period):
"""Add a timer with timeout period 'period'."""
if len(self._timers) >= self.SENSOR_LIMIT:
raise ValueError('You can only have 10 timers')
self._timers.append(None)
self._timerPeriods.append(period)
def resetTimer(self, key):
"""Reset, and start the given timer, but only if it is inactive.
If it is active, raise a ValueError."""
if self._timer[key] is None:
self._timer[key] = self._timerPeriods[key]
else:
raise ValueError("You can't reset an active timer (#%d)" % key)
def clearTimer(self, key):
"""Clear the timer."""
self._timer[key] = None
def checkTimer(self, key):
"""Returns True if the timer has expired."""
return self._timer[key] == 0
def _manageTimers(self):
"""Decrement each active timer."""
for i in range(len(self._timers)):
if self._timers[i] is not None and \
self._timers[i] > 0:
self._timers[i] = self._timers[i] - 1
def program(self, text):
"""Set the program for this tank."""
self._program = Program.Program(text)
self._program.setup(self)
def execute(self):
"""Execute this tanks program."""
# Decrement the active timers
self._manageTimers()
self.led = False
self._program(self)
self._move(self._nextMove[0], self._nextMove[1])
self._moveTurret()
if self._fireReady > 0:
self._fireReady = self._fireReady - 1
self._turn = self._turn + 1
def sense(self, near):
"""Detect collisions and trigger sensors. Returns the set of
tanks collided with, plus this one. We do both these steps at once
mainly because all the data is available."""
near = list(near)
polar = []
for tank in near:
polar.append(gm.relativePolar(self.pos, tank.pos, self._limits))
for sensorId in range(len(self._sensors)):
# Reset the sensor
self._sensorState[sensorId] = False
dist, sensorAngle, width, tSens = self._sensors[sensorId]
# Adjust the sensor angles according to the tanks angles.
sensorAngle = sensorAngle + self._angle
# If the angle is tied to the turret, add that too.
if tSens:
sensorAngle = sensorAngle + self._tAngle
while sensorAngle >= 2*math.pi:
sensorAngle = sensorAngle - 2*math.pi
for i in range(len(near)):
r, theta = polar[i]
# Find the difference between the object angle and the sensor.
# The max this can be is pi, so adjust for that.
dAngle = gm.angleDiff(theta, sensorAngle)
rCoord = gm.polar2cart(dist, sensorAngle - width/2)
lCoord = gm.polar2cart(dist, sensorAngle + width/2)
rightLine = ((0,0), rCoord)
leftLine = ((0,0), lCoord)
tankRelPos = gm.minShift(self.pos, near[i].pos, self._limits)
if r < (dist + self.RADIUS):
if abs(dAngle) <= (width/2) or \
gm.segmentCircleCollision(rightLine, tankRelPos,
self.RADIUS) or \
gm.segmentCircleCollision(leftLine, tankRelPos,
self.RADIUS):
self._sensorState[sensorId] = True
break
# Check for collisions here, since we already have all the data.
collided = set()
for i in range(len(near)):
r, theta = polar[i]
if r < (self.RADIUS + near[i].RADIUS):
collided.add(near[i])
# Add this tank (a collision kills both, after all
if collided:
collided.add(self)
return collided
def die(self, reason):
self.isDead = True
self.deathReason = reason
def _moveTurret(self):
if self._tGoal is None or self._tAngle == self._tGoal:
return
diff = gm.angleDiff(self._tGoal, self._tAngle)
if abs(diff) < self.TURRET_TURN_RATE:
self._tAngle = self._tGoal
elif diff > 0:
self._tAngle = gm.reduceAngle(self._tAngle - self.TURRET_TURN_RATE)
else:
self._tAngle = gm.reduceAngle(self._tAngle + self.TURRET_TURN_RATE)
def _move(self, lSpeed, rSpeed):
assert abs(lSpeed) <= 100, "Bad speed value: %s" % lSpeed
assert abs(rSpeed) <= 100, "Bad speed value: %s" % rSpeed
# Handle acceleration
if self._lastSpeed[0] < lSpeed and \
self._lastSpeed[0] + self.ACCEL < lSpeed:
lSpeed = self._lastSpeed[0] + self.ACCEL
elif self._lastSpeed[0] > lSpeed and \
self._lastSpeed[0] - self.ACCEL > lSpeed:
lSpeed = self._lastSpeed[0] - self.ACCEL
if self._lastSpeed[1] < rSpeed and \
self._lastSpeed[1] + self.ACCEL < rSpeed:
rSpeed = self._lastSpeed[1] + self.ACCEL
elif self._lastSpeed[1] > rSpeed and \
self._lastSpeed[1] - self.ACCEL > rSpeed:
rSpeed = self._lastSpeed[1] - self.ACCEL
self._lastSpeed = lSpeed, rSpeed
# The simple case
if lSpeed == rSpeed:
fSpeed = self.SPEED*lSpeed/100
x = fSpeed*math.cos(self._angle)
y = fSpeed*math.sin(self._angle)
# Adjust our position
self._reposition((x,y), 0)
return
# The works as follows:
# The tank drives around in a circle of radius r, which is some
# offset on a line perpendicular to the tank. The distance it travels
# around the circle varies with the speed of each tread, and is
# such that each side of the tank moves an equal angle around the
# circle.
L = self.SPEED * lSpeed/100.0
R = self.SPEED * rSpeed/100.0
friction = .75 * abs(L-R)/(2.0*self.SPEED)
L = L * (1 - friction)
R = R * (1 - friction)
# Si is the speed of the tread on the inside of the turn,
# So is the speed on the outside of the turn.
# dir is to note the direction of rotation.
if abs(L) > abs(R):
Si = R; So = L
dir = 1
else:
Si = L; So = R
dir = -1
# The width of the tank...
w = self.RADIUS * 2
# This is the angle that will determine the circle the tank travels
# around.
# theta = math.atan((So - Sl)/w)
# This is the distance from the outer tread to the center of the
# circle formed by it's movement.
r = w*So/(So - Si)
# The fraction of the circle traveled is equal to the speed of
# the outer tread over the circumference of the circle.
# Ft = So/(2*pi*r)
# The angle traveled is equal to the Fraction traveled * 2 * pi
# This reduces to a simple: So/r
# We multiply it by dir to adjust for the direction of rotation
theta = So/r * dir
# These are the offsets from the center of the circle, given that
# the tank is turned in some direction. The tank is facing
# perpendicular to the circle
# So far everything has been relative to the outer tread. At this
# point, however, we need to move relative to the center of the
# tank. Hence the adjustment in r.
x = -math.cos( self._angle + math.pi/2*dir ) * (r - w/2.0)
y = -math.sin( self._angle + math.pi/2*dir ) * (r - w/2.0)
# Now we just rotate the tank's position around the center of the
# circle to get the change in coordinates.
mx, my = gm.rotatePoint((x,y), theta)
mx = mx - x
my = my - y
# Finally, we shift the tank relative to the playing field, and
# rotate it by theta.
self._reposition((mx, my), theta)
def _reposition(self, move, angleChange):
"""Move the tank by x,y = move, and change it's angle by angle.
I assume the tanks move slower than the boardSize."""
x = self.pos[0] + move[0]
y = self.pos[1] + move[1]
self._angle = self._angle + angleChange
if x < 0:
x = self._limits[0] + x
elif x > self._limits[0]:
x = x - self._limits[0]
if y < 0:
y = self._limits[1] + y
elif y > self._limits[1]:
y = y - self._limits[1]
self.pos = round(x), round(y)
while self._angle < 0:
self._angle = self._angle + math.pi * 2
while self._angle > math.pi * 2:
self._angle = self._angle - math.pi * 2
def draw(self, f):
"""Output this tank's state as JSON.
[color, x, y, angle, turret_angle, led, fired]
"""
f.write(' [')
f.write(str(int(self.isDead)));
f.write(',')
f.write(repr(self.color))
f.write(',')
f.write('%d' % self.pos[0])
f.write(',')
f.write('%d' % self.pos[1])
f.write(',')
f.write('%.2f' % self._angle)
f.write(',')
f.write('%.2f' % self._tAngle)
f.write(',')
f.write(str(int(self.led)))
f.write(',')
f.write('%d' % (self._fired and self.FIRE_RANGE) or 0)
if not self.isDead:
f.write(',[')
for i in range(len(self._sensors)):
dist, sensorAngle, width, tSens = self._sensors[i]
# If the angle is tied to the turret, add that.
if tSens:
sensorAngle = sensorAngle + self._tAngle
f.write('[')
f.write(str(int(dist)))
f.write(',')
f.write('%.2f' % (sensorAngle - width/2));
f.write(',')
f.write('%.2f' % (sensorAngle + width/2));
f.write(',')
f.write(str(int(self._sensorState[i])))
f.write('],')
f.write(']')
f.write('],\n')

View File

@ -1,126 +0,0 @@
"""Define new action Functions here. They should inherit from the
Function.Function class. To make an action usable, add it to the
actions dictionary at the end of this file."""
import Function
class Move(Function.Function):
"""move(left tread speed, right tread speed)
Set the speeds for the tanks right and left treads. The speeds should
be a number (percent power) between -100 and 100."""
def __init__(self, left, right):
self._checkRange(left, 'left tread speed', min=-100)
self._checkRange(right, 'right tread speed', min=-100)
self._left = left
self._right = right
def __call__(self, tank):
tank.setMove(self._left, self._right)
class TurretCounterClockwise(Function.Function):
"""turretccw([percent speed])
Rotate the turret counter clockwise as a percentage of the max speed."""
def __init__(self, speed=100):
self._checkRange(speed, 'turret percent speed')
self._speed = speed/100.0
def __call__(self, tank):
tank.setTurretAngle(tank._tAngle - tank.TURRET_TURN_RATE*self._speed)
class TurretClockwise(Function.Function):
"""turretcw([percent speed])
Rotate the turret clockwise at a rate preportional to speed."""
def __init__(self, speed=100):
self._checkRange(speed, 'turret percent speed')
self._speed = speed/100.0
def __call__(self, tank):
tank.setTurretAngle(tank._tAngle + tank.TURRET_TURN_RATE*self._speed)
class TurretSet(Function.Function):
"""turretset([angle])
Set the turret to the given angle, in degrees, relative to the front of
the tank. Angles increase counterclockwise.
The angle can be left out; in that case, this locks the turret
to it's current position."""
def __init__(self, angle=None):
# Convert the angle to radians
if angle is not None:
angle = self._convertAngle(angle, 'turret angle')
self._angle = angle
def __call__(self, tank):
tank.setTurretAngle(self._angle)
class Fire(Function.Function):
"""fire()
Attempt to fire the tanks laser cannon."""
def __call__(self, tank):
tank.setFire()
class SetToggle(Function.Function):
"""settoggle(key, state)
Set toggle 'key' to 'state'.
"""
def __init__(self, key, state):
self._key = key
self._state = state
def __call__(self, tank):
tank.toggles[self._key] = self._state
class Toggle(Function.Function):
"""toggle(key)
Toggle the value of toggle 'key'.
"""
def __init__(self, key):
self._key = key
def __call__(self, tank):
try:
tank.toggles[self._key] = not tank.toggles[self._key]
except IndexError:
raise IndexError('Invalid toggle: %d' % self._key)
class LED(Function.Function):
"""led(state)
Set the tanks LED to state (true is on, false is off).
The led is a light that appears behind the tanks turret.
It remains on for a single turn."""
def __init__(self, state=1):
self._state = state
def __call__(self, tank):
tank.led = self._state
class StartTimer(Function.Function):
"""starttimer(#)
Start (and reset) the given timer, but only if it is inactive.
"""
def __init__(self, key):
self._key = key
def __call__(self, tank):
tank.resetTimer(key)
class ClearTimer(Function.Function):
"""cleartimer(#)
Clear the given timer such that it is no longer active (inactive timers
are always False)."""
def __init__(self, key):
self._key = key
def __call__(self, tank):
tank.clearTimer(self._key)
### When adding names to this dict, make sure they are lower case and alpha
### numeric.
actions = {'move': Move,
'turretccw': TurretCounterClockwise,
'turretcw': TurretClockwise,
'turretset': TurretSet,
'fire': Fire,
'settoggle': SetToggle,
'toggle': Toggle,
'led': LED,
'starttimer': StartTimer,
'cleartimer': ClearTimer}

View File

@ -1,126 +0,0 @@
"""Define new condition functions here. Add it to the conditions dictionary
at the end to make it usable by Program.Program. These should inherit from
Function.Function."""
import Function
import math
import random
class Sense(Function.Function):
"""sense(#, [invert])
Takes a Sensor number as an argument.
Returns True if the given sensor is currently activated, False otherwise.
If the option argument invert is set to true then logic is inverted,
and then sensor returns True when it is NOT activated, and False when
it is. Invert is false by default.
"""
def __init__(self, sensor, invert=0):
self._sensor = sensor
self._invert = invert
def __call__(self, tank):
state = tank.getSensorState(self._sensor)
if self._invert:
return not state
else:
return state
class Toggle(Function.Function):
"""toggle(#)
Returns True if the given toggle is set, False otherwise. """
def __init__(self, toggle):
self._toggle = toggle
def __call__(self, tank):
return tank.toggles[toggle]
class TimerCheck(Function.Function):
"""timer(#, [invert])
Checks the state of timer # 'key'. Returns True if time has run out.
If invert is given (and true), then True is returned if the timer has
yet to expire.
"""
def __init__(self, key, invert=0):
self._key = key
self._invert = invert
def __call__(self, tank):
state = tank.checkTimer(self._key)
if invert:
return not state
else:
return state
class Random(Function.Function):
"""random(n,m)
Takes two arguments, n and m. Generates a random number between 1
and m (inclusive) each time it's checked. If the random number is less
than or equal
to n, then the condition returns True. Returns False otherwise."""
def __init__(self, n, m):
self._n = n
self._m = m
def __call__(self, tank):
if random.randint(1,self._m) <= self._n:
return True
else:
return False
class Sin(Function.Function):
"""sin(T)
A sin wave of period T (in turns). Returns True when the wave is positive.
A wave with period 1 or 2 is always False (it's 0 each turn), only
at periods of 3 or more does this become useful."""
def __init__(self, T):
self._T = T
def __call__(self, tank):
turn = tank.turn
factor = math.pi/self._T
if math.sin(turn * factor) > 0:
return True
else:
return False
class Cos(Function.Function):
"""cos(T)
A cos wave with period T (in turns). Returns True when the wave is
positive. A wave of period 1 is always True. Period 2 is True every
other turn, etc."""
def __init__(self, T):
self._T = T
def __call__(self, tank):
turn = tank.turn
factor = math.pi/self._T
if math.cos(turn * factor) > 0:
return True
else:
return False
class FireReady(Function.Function):
"""fireready()
True when the tank can fire."""
def __call__(self, tank):
return tank.fireReady()
class FireNotReady(Function.Function):
"""firenotready()
True when the tank can not fire."""
def __call__(self, tank):
return not tank.fireReady()
### When adding names to this dict, make sure they are lower case and alpha
### numeric.
conditions = {'sense': Sense,
'random': Random,
'toggle': Toggle,
'sin': Sin,
'cos': Cos,
'fireready': FireReady,
'firenotready': FireNotReady,
'timer': TimerCheck }

View File

@ -1,26 +0,0 @@
import xml.sax.saxutils
def mkDocTable(objects):
objects.sort(lambda o1, o2: cmp(o1.__doc__, o2.__doc__))
for object in objects:
if object.__doc__ is None:
print '<table><tr><th>%s<tr><td colspan=2>Bad object</table>' % \
xml.sax.saxutils.escape(str(object))
continue
text = object.__doc__
lines = text.split('\n')
head = lines[0].strip()
head = xml.sax.saxutils.escape(head)
body = []
for line in lines[1:]:
line = line.strip() #xml.sax.saxutils.escape( line.strip() )
line = line.replace('.', '.<BR>')
body.append(line)
body = '\n'.join(body)
print '<DL><DT><DIV class="tab">%s</DIV></DT><DD>%s</DD></DL>' % (head, body)
#print '<tr><th>%s<th>Intentionally blank<th><tr><td colspan=3>%s' % (head, body)

View File

@ -1,72 +0,0 @@
"""Each of these classes provides a function for configuring a tank.
They should inherit from Function.Function.
To make one available to the tank programmer, add it to the dictionary at
the end of this file."""
import Function
class AddSensor(Function.Function):
"""addsensor(range, angle, width, [turretAttached])
Add a new sensor to the tank. Sensors are an arc (pie slice) centered on
the tank that detect other tanks within their sweep.
A sensor is 'on' if another tank is within this arc.
Sensors are numbered, starting at 0, in the order they are added.
<p>
range - The range of the sensor, as a percent of the tanks max range.
angle - The angle of the center of the sensor, in degrees.
width - The width of the sensor, in percent (100 is a full circle).
turretAttached - Normally, the angle is relative to the front of the
tank. When this is set, the angle is relative to the current turret
direction.
<p>
Sensors are drawn for each tank, but not in the way you might expect.
Instead of drawing a pie slice (the actual shap of the sensor), an arc with
the end points connected by a line is drawn. Sensors with 0 width don't show
up, but still work.
"""
def __init__(self, range, angle, width, turretAttached=False):
self._checkRange(range, 'sensor range')
self._range = range / 100.0
self._width = self._convertAngle(width, 'sensor width')
self._angle = self._convertAngle(angle, 'sensor angle')
self._turretAttached = turretAttached
def __call__(self, tank):
tank.addSensor(self._range, self._angle, self._width,
self._turretAttached)
class AddToggle(Function.Function):
"""addtoggle([state])
Add a toggle to the tank. The state of the toggle defaults to 0 (False).
These essentially act as a single bit of memory.
Use the toggle() condition to check its state and the settoggle, cleartoggle,
and toggle actions to change the state. Toggles are named numerically,
starting at 0.
"""
def __init__(self, state=0):
self._state = state
def __call__(self, tank):
if len(tank.toggles) >= tank.SENSOR_LIMIT:
raise ValueError('You can not have more than 10 toggles.')
tank.toggles.append(self._state)
class AddTimer(Function.Function):
"""addtimer(timeout)
Add a new timer (they're numbered in the order added, starting from 0),
with the given timeout. The timeout is in number of turns. The timer
is created in inactive mode. You'll need to do a starttimer() action
to reset and start the timer. When the timer expires, the timer()
condition will begin to return True."""
def __init__(self, timeout):
self._timeout = timeout
def __call__(self, tank):
tank.addTimer(timeout)
setup = {'addsensor': AddSensor,
'addtoggle': AddToggle,
'addtimer': AddTimer}

View File

@ -1,5 +1,7 @@
import math import math
pi2 = math.pi * 2
def rotatePoint(point, angle): def rotatePoint(point, angle):
"""Assuming 0,0 is the center, rotate the given point around it.""" """Assuming 0,0 is the center, rotate the given point around it."""
@ -120,19 +122,14 @@ center is the origin. Take into account wrapping round the limits of the board.
r = math.sqrt(dx**2 + dy**2) r = math.sqrt(dx**2 + dy**2)
theta = math.acos(dx/r) theta = math.acos(dx/r)
if dy < 0: if dy < 0:
theta = 2*math.pi - theta theta = pi2 - theta
return r, theta return r, theta
def reduceAngle(angle): def reduceAngle(angle):
"""Reduce the angle such that it is in 0 <= angle < 2pi""" """Reduce the angle such that it is in 0 <= angle < 2pi"""
while angle >= math.pi*2: return angle % pi2
angle = angle - math.pi*2
while angle < 0:
angle = angle + math.pi*2
return angle
def angleDiff(angle1, angle2): def angleDiff(angle1, angle2):
"""Returns the difference between the two angles. They are assumed """Returns the difference between the two angles. They are assumed
@ -142,17 +139,7 @@ to be in radians, and must be in the range 0 <= angle < 2*pi.
is negative if angle2 leads angle1 (clockwise).. is negative if angle2 leads angle1 (clockwise)..
""" """
for angle in angle1, angle2: return (angle2 - angle1) % pi2
assert angle < 2*math.pi and angle >= 0, \
'angleDiff: bad angle %s' % angle
diff = angle2 - angle1
if diff > math.pi:
diff = diff - 2*math.pi
elif diff < -math.pi:
diff = diff + 2*math.pi
return diff
def getDist(point1, point2): def getDist(point1, point2):
"""Returns the distance between point1 and point2.""" """Returns the distance between point1 and point2."""