mirror of https://github.com/dirtbags/moth.git
Various work on Tanks.
This commit is contained in:
parent
e95fabccb7
commit
41f077192d
|
@ -3,7 +3,11 @@
|
||||||
import fcntl
|
import fcntl
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
from urllib.parse import quote, unquote
|
# python 2 compatibility
|
||||||
|
try:
|
||||||
|
from urllib.parse import quote, unquote
|
||||||
|
except:
|
||||||
|
from urllib import quote, unquote
|
||||||
from . import config
|
from . import config
|
||||||
|
|
||||||
house = config.get('global', 'house_team')
|
house = config.get('global', 'house_team')
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
random(1,10): move(50, 100);
|
||||||
|
random(1,10): move(100, 50);
|
||||||
|
random(1,10): turretcw();
|
||||||
|
random(1,10): turretccw();
|
||||||
|
random(1,30): fire();
|
|
@ -0,0 +1,8 @@
|
||||||
|
>addsensor(50, 0, 0, 1);
|
||||||
|
>addsensor(70, 0, 50); # 1-Anti-collision sensor
|
||||||
|
|
||||||
|
: move(100, 100) . turretset(180);
|
||||||
|
random(1, 8): move(70, 100);
|
||||||
|
random(1, 8): move(100, 70);
|
||||||
|
sense(0) : fire();
|
||||||
|
sense(1) : move(-0, 100);
|
|
@ -0,0 +1,57 @@
|
||||||
|
# 3
|
||||||
|
# 000 33
|
||||||
|
# 2
|
||||||
|
# 2
|
||||||
|
# 2
|
||||||
|
# 11111 4
|
||||||
|
# 4
|
||||||
|
# 4
|
||||||
|
# @@/
|
||||||
|
# @@@
|
||||||
|
# @@@
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
>addsensor(50, 0, 05, 1); # 0 Fire Sensor
|
||||||
|
>addsensor(30, 0, 50); # 1 Anti-collision sensor
|
||||||
|
>addsensor(50, 0, 10); # 2 Anti-collision sensor
|
||||||
|
>addsensor(100, 315, 100, 1); # 3 Turret ccw
|
||||||
|
>addsensor(100, 45, 100, 1); # 4 Turret cw
|
||||||
|
>addsensor(60, 180, 180, 0); # 5 Ass
|
||||||
|
|
||||||
|
##
|
||||||
|
## Add "ears" so the tank is easy to pick out.
|
||||||
|
##
|
||||||
|
>addsensor(20, 90, 30, 0);
|
||||||
|
>addsensor(20, 270, 30, 0);
|
||||||
|
|
||||||
|
# Can't fire
|
||||||
|
: led(0) . move(80, 80) . turretset(0);
|
||||||
|
random(1, 3): led(0) . move(60, 80) . turretset(0);
|
||||||
|
random(2, 3): led(0) . move(80, 60) . turretset(0);
|
||||||
|
|
||||||
|
sense(0) : led(0) . move(10, 20) . turretset(0);
|
||||||
|
sense(1) : led(0) . move(10, 10) . turretset(0);
|
||||||
|
sense(2) : led(0) . move(10, 20) . turretset(0);
|
||||||
|
sense(3) : led(0) . move(70, 50) . turretset(0);
|
||||||
|
sense(4) : led(0) . move(50, 70) . turretset(0);
|
||||||
|
sense(3) & sense(4): led(0) . move(-100, 20) . turretset(0);
|
||||||
|
sense(5) : led(0) . move(100, 50) . turretset(0);
|
||||||
|
|
||||||
|
|
||||||
|
# Can fire
|
||||||
|
fireready() : led(1) . move(70, 70) . turretset(0);
|
||||||
|
fireready() & random(2, 40): led(1) . move(40, 70) . turretset(0);
|
||||||
|
fireready() & random(1, 40): led(1) . move(70, 40) . turretset(0);
|
||||||
|
|
||||||
|
fireready() & sense(3) : led(1) . move(0, 60) . turretccw(50);
|
||||||
|
fireready() & sense(4) : led(1) . move(60, 0) . turretcw(50);
|
||||||
|
fireready() & sense(3) & sense(4): led(1) . move(100, 100) . turretset();
|
||||||
|
fireready() & sense(1) : led(1) . turretset(0) . move(10, 10);
|
||||||
|
fireready() & sense(2) : led(1) . turretset(0) . move(10, 10);
|
||||||
|
fireready() & sense(0) : led(1) . turretset() . fire();
|
||||||
|
|
||||||
|
fireready() & sense(5) : led(1) . move(100, 40);
|
|
@ -0,0 +1,22 @@
|
||||||
|
>addsensor(55, 0, 5, 1);
|
||||||
|
>addsensor(40, 0, 30);
|
||||||
|
>addsensor(80, 30, 59, 0);
|
||||||
|
>addsensor(80, 330, 59, 0);
|
||||||
|
>addsensor(70, 180, 180);
|
||||||
|
>addsensor(80, 90, 59, 0);
|
||||||
|
>addsensor(80, 270, 59, 0);
|
||||||
|
|
||||||
|
# : move(70,80);
|
||||||
|
# random(3,6) : move(80,70);
|
||||||
|
: move(65,85);
|
||||||
|
random(2,6) : move(90,65);
|
||||||
|
sense(2) : move(80,10).turretcw(100);
|
||||||
|
sense(3) : move(10,80).turretccw(100);
|
||||||
|
sense(4) : move(90, 90);
|
||||||
|
sense(5) : move(90,10).turretcw(100);
|
||||||
|
sense(6) : move(10,90).turretccw(100);
|
||||||
|
sense(0) & fireready() : turretset().move(90,90).fire();
|
||||||
|
sense(1) : move(-100, -100);
|
||||||
|
: turretset(0);
|
||||||
|
fireready() : led();
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
>addsensor(50, 0, 45, 1); # 0-Fire Sensor
|
||||||
|
>addsensor(30, 0, 180); # 1-Anti-collision sensor
|
||||||
|
>addsensor(100, 40, 60, 1); # 2 turret clockwise
|
||||||
|
>addsensor(100, 320, 60, 1); # 3 turret ccw
|
||||||
|
>addsensor(80, 180, 160); # 4 Coward
|
||||||
|
>addsensor(100, 0, 0, 1); # 5-Fire Sensor2
|
||||||
|
>addsensor(100, 0, 0); # 6-Chase Sensor
|
||||||
|
>addsensor(75, 75, 30); # 7-quick turn right
|
||||||
|
>addsensor(75, 285, 30); # 8-quick turn left
|
||||||
|
|
||||||
|
# Commands
|
||||||
|
: move(70, 75).
|
||||||
|
turretset(0);
|
||||||
|
random(1, 10): move(75, 75).
|
||||||
|
turretset(0);
|
||||||
|
sense(2) : turretcw(50).
|
||||||
|
move(85, 70);
|
||||||
|
sense(2) & sense(0): turretcw(25).
|
||||||
|
move(85, 70);
|
||||||
|
sense(3) : turretccw(50).
|
||||||
|
move(70, 85);
|
||||||
|
sense(3) & sense(0) : turretccw(25).
|
||||||
|
move(70, 85);
|
||||||
|
sense(5) & sense(7) : move(70, 30);
|
||||||
|
sense(5) & sense(8) : move(30, 70);
|
||||||
|
#sense(5) : turretset();
|
||||||
|
sense(0) & sense(5) : fire();
|
||||||
|
sense(6) & sense(5) & fireready(): move(100,100);
|
||||||
|
sense(4) : move(100,100);
|
||||||
|
sense(1) : move(-50, 25);
|
||||||
|
fireready() : led();
|
|
@ -0,0 +1,8 @@
|
||||||
|
>addsensor(50, 0, 5, 1); # 0-Fire Sensor
|
||||||
|
>addsensor(30, 0, 50); # 1-Anti-collision sensor
|
||||||
|
|
||||||
|
# Commands
|
||||||
|
: move(90, 100).
|
||||||
|
turretset(0);
|
||||||
|
sense(0) : fire();
|
||||||
|
sense(1) : move(-100, 100)
|
|
@ -0,0 +1,9 @@
|
||||||
|
>addsensor(50, 0, 10, 1); # 0-Fire Sensor
|
||||||
|
>addsensor(100, 90, 150, 1);
|
||||||
|
>addsensor(100, 270, 150, 1);
|
||||||
|
|
||||||
|
: turretcw(75);
|
||||||
|
sense(0): fire();
|
||||||
|
sense(1): turretcw();
|
||||||
|
sense(2): turretccw();
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Just sit there and sweep the field until it finds something to shoot.
|
||||||
|
# Uses a long-range sensor on the left and right to hone in.
|
||||||
|
|
||||||
|
>addsensor(50, 0, 5, 1); # 0
|
||||||
|
>addsensor(100, 90, 150, 1); # 1
|
||||||
|
>addsensor(100, 270, 150, 1); # 2
|
||||||
|
>addsensor(100, 0, 359, 0); # 3
|
||||||
|
|
||||||
|
# Default movement if nothing is detected
|
||||||
|
: move(70, 70) . turretccw();
|
||||||
|
random(2, 3): move(40, 70) . turretccw();
|
||||||
|
random(1, 3): move(70, 40) . turretccw();
|
||||||
|
|
||||||
|
# We found something!!
|
||||||
|
sense(3): move(0, 0);
|
||||||
|
sense(1): turretcw();
|
||||||
|
sense(2): turretccw();
|
||||||
|
sense(0): fire();
|
|
@ -0,0 +1,34 @@
|
||||||
|
FAKE = fakeroot -s fake -i fake
|
||||||
|
INSTALL = $(fake) install
|
||||||
|
|
||||||
|
all: tanks.tce
|
||||||
|
|
||||||
|
tanks.tce: target
|
||||||
|
$(FAKE) sh -c 'cd target && tar -czf - .' > $@
|
||||||
|
|
||||||
|
target:
|
||||||
|
$(INSTALL) -d target/var/lib/tanks/
|
||||||
|
$(INSTALL) -d target/var/lib/tanks/results/
|
||||||
|
$(INSTALL) -d target/var/lib/tanks/ai/easy
|
||||||
|
$(INSTALL) -d target/var/lib/tanks/ai/medium
|
||||||
|
$(INSTALL) -d target/var/lib/tanks/ai/hard
|
||||||
|
$(INSTALL) -d target/var/lib/tanks/ai/players
|
||||||
|
$(INSTALL) AI/easy/* target/var/lib/tanks/ai/easy/
|
||||||
|
$(INSTALL) AI/medium/* target/var/lib/tanks/ai/medium/
|
||||||
|
$(INSTALL) AI/hard/* target/var/lib/tanks/ai/hard/
|
||||||
|
|
||||||
|
$(INSTALL) -d target/var/lib/www/tanks/
|
||||||
|
$(INSTALL) www/* target/var/lib/www/tanks/
|
||||||
|
# $(FAKE) ln -s target/var/lib/tanks/ target/var/lib/www/data
|
||||||
|
|
||||||
|
$(INSTALL) -d target/usr/lib/python2.6/site-packages/tanks/
|
||||||
|
$(INSTALL) lib/* target/usr/lib/python2.6/site-packages/tanks/
|
||||||
|
|
||||||
|
$(INSTALL) -d target/var/service/tanks
|
||||||
|
$(INSTALL) run target/var/service/tanks/run
|
||||||
|
|
||||||
|
$(INSTALL) -d target/var/service/tanks/log/
|
||||||
|
$(INSTALL) log.run target/var/service/tanks/log/run
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf target tanks.tce fake
|
Binary file not shown.
|
@ -0,0 +1,47 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _convertAngle(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
|
||||||
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
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 = point[0] + disp[0], 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
|
|
@ -0,0 +1,400 @@
|
||||||
|
import fcntl
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import subprocess
|
||||||
|
import xml.sax.saxutils
|
||||||
|
|
||||||
|
from PIL import Image, ImageColor, ImageDraw
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ctf import teams
|
||||||
|
except:
|
||||||
|
import sys
|
||||||
|
path = '/home/pflarr/repos/gctf/'
|
||||||
|
sys.path.append(path)
|
||||||
|
from ctf import teams
|
||||||
|
|
||||||
|
import Tank
|
||||||
|
|
||||||
|
class Pflanzarr:
|
||||||
|
|
||||||
|
FRAME_DELAY = 15
|
||||||
|
|
||||||
|
SPACING = 150
|
||||||
|
backgroundColor = '#ffffff'
|
||||||
|
|
||||||
|
def __init__(self, dir, difficulty='easy'):
|
||||||
|
"""Initialize a new game of Pflanzarr.
|
||||||
|
@param dir: The data directory."""
|
||||||
|
|
||||||
|
assert difficulty in ('easy', 'medium', 'hard')
|
||||||
|
|
||||||
|
# Setup the game environment
|
||||||
|
self._setupDirectories(dir)
|
||||||
|
|
||||||
|
# Figure out what game number this is.
|
||||||
|
self._gameNum = self._getGameNum()
|
||||||
|
self._gameDir = os.path.join(self._resultsDir, str(self._gameNum))
|
||||||
|
if not os.path.exists(self._gameDir):
|
||||||
|
os.mkdir(self._gameDir)
|
||||||
|
|
||||||
|
tmpPlayers = os.listdir(self._playerDir)
|
||||||
|
players = []
|
||||||
|
for p in tmpPlayers:
|
||||||
|
p = unquote(p)
|
||||||
|
if not (p.startswith('.') or p.endswith('#') or p.endswith('~'))
|
||||||
|
and p in teams.teams:
|
||||||
|
players.append(p)
|
||||||
|
|
||||||
|
AIs = {}
|
||||||
|
for player in players:
|
||||||
|
AIs[player] = open(os.path.join(self._playerDir, player)).read()
|
||||||
|
defaultAIs = self._getDefaultAIs(dir, difficulty)
|
||||||
|
|
||||||
|
assert len(players) >= 1, "There must be at least one player."
|
||||||
|
|
||||||
|
# The one is added to ensure that there is at least one defaultAI bot.
|
||||||
|
size = math.sqrt(len(players) + 1)
|
||||||
|
if int(size) != size:
|
||||||
|
size = size + 1
|
||||||
|
|
||||||
|
size = int(size)
|
||||||
|
if size < 2:
|
||||||
|
size = 2
|
||||||
|
|
||||||
|
self._board = (size*self.SPACING, size*self.SPACING)
|
||||||
|
|
||||||
|
while len(players) < size**2:
|
||||||
|
players.append('#default')
|
||||||
|
|
||||||
|
self._tanks = []
|
||||||
|
for i in range(size):
|
||||||
|
for j in range(size):
|
||||||
|
startX = i*self.SPACING + self.SPACING/2
|
||||||
|
startY = j*self.SPACING + self.SPACING/2
|
||||||
|
player = random.choice(players)
|
||||||
|
players.remove(player)
|
||||||
|
if player == '#default':
|
||||||
|
color = '#a0a0a0'
|
||||||
|
else:
|
||||||
|
color = team.teams[player][1]
|
||||||
|
tank = Tank.Tank( player, (startX, startY), color,
|
||||||
|
self._board, testMode=True)
|
||||||
|
if player == '#default':
|
||||||
|
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):
|
||||||
|
|
||||||
|
print "Starting new game with %d players." % len(self._tanks)
|
||||||
|
|
||||||
|
kills = {}
|
||||||
|
for tank in self._tanks:
|
||||||
|
kills[tank] = set()
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
image = Image.new('RGB', self._board)
|
||||||
|
draw = ImageDraw.Draw(image)
|
||||||
|
draw.rectangle(((0,0), self._board), fill=self.backgroundColor)
|
||||||
|
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' % tank.name)
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
# Draw the explosions
|
||||||
|
for tank in self._deadTanks:
|
||||||
|
tank.draw(image)
|
||||||
|
|
||||||
|
# Draw the live tanks.
|
||||||
|
for tank in self._tanksByX:
|
||||||
|
# Have the tank run its program, move, etc.
|
||||||
|
tank.draw(image)
|
||||||
|
|
||||||
|
# Have the live tanks do their turns
|
||||||
|
for tank in self._tanksByX:
|
||||||
|
tank.execute()
|
||||||
|
|
||||||
|
fileName = os.path.join(self._imageDir, '%05d.ppm' % turn)
|
||||||
|
image.save(fileName, 'PPM')
|
||||||
|
turn = turn + 1
|
||||||
|
|
||||||
|
# Removes tanks from their own kill lists.
|
||||||
|
for tank in kills:
|
||||||
|
if tank in kills[tank]:
|
||||||
|
kills[tank].remove(tank)
|
||||||
|
|
||||||
|
self._saveResults(kills)
|
||||||
|
for tank in self._tanks:
|
||||||
|
self._outputErrors(tank)
|
||||||
|
self._makeMovie()
|
||||||
|
|
||||||
|
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 _saveResults(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."""
|
||||||
|
resultsFile = open(os.path.join(self._gameDir, 'results.html'), 'w')
|
||||||
|
winnerFile = open(os.path.join(self._dir, 'winner'),'w')
|
||||||
|
|
||||||
|
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, reverse=1)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
html = ['<html><body>',
|
||||||
|
'<table><tr><th>Team<th>Kills<th>Cause of Death']
|
||||||
|
for tank in tanks:
|
||||||
|
if tank is winner:
|
||||||
|
rowStyle = 'style="color:red;"'
|
||||||
|
else:
|
||||||
|
rowStyle = ''
|
||||||
|
html.append('<tr %s><td>%s<td>%d<td>%s' %
|
||||||
|
(rowStyle,
|
||||||
|
xml.sax.saxutils.escape(tank.name),
|
||||||
|
len(kills[tank]),
|
||||||
|
xml.sax.saxutils.escape(tank.deathReason)))
|
||||||
|
|
||||||
|
html.append('</table><body></html>')
|
||||||
|
|
||||||
|
if winner.name != '#default':
|
||||||
|
winnerFile.write(tanks[0].name)
|
||||||
|
winnerFile.close()
|
||||||
|
|
||||||
|
resultsFile.write('\n'.join(html))
|
||||||
|
resultsFile.close()
|
||||||
|
|
||||||
|
def _makeMovie(self):
|
||||||
|
"""Make the game movie."""
|
||||||
|
|
||||||
|
movieCmd = ['ffmpeg',
|
||||||
|
'-r', '10', # Set the framerate to 10/second
|
||||||
|
'-b', '8k', # Set the bitrate
|
||||||
|
'-i', '%s/%%05d.ppm' % self._imageDir, # The input files.
|
||||||
|
# '-vcodec', 'msmpeg4v2',
|
||||||
|
'%s/game.avi' % self._gameDir]
|
||||||
|
|
||||||
|
# movieCmd = ['mencoder', 'mf://%s/*.png' % self._imageDir,
|
||||||
|
# '-mf', 'fps=10', '-o', '%s/game.avi' % self._gameDir,
|
||||||
|
# '-ovc', 'lavc', '-lavcopts',
|
||||||
|
# 'vcodec=msmpeg4v2:vbitrate=800']
|
||||||
|
clearFrames = ['rm', '-rf', '%s' % self._imageDir]
|
||||||
|
|
||||||
|
print 'Making Movie'
|
||||||
|
subprocess.call(movieCmd)
|
||||||
|
# subprocess.call(movieCmd, stderr=open('/dev/null', 'w'),
|
||||||
|
# stdout=open('/dev/null', 'w'))
|
||||||
|
subprocess.call(clearFrames)
|
||||||
|
|
||||||
|
def _outputErrors(self, tank):
|
||||||
|
"""Output errors for each team."""
|
||||||
|
if tank.name == '#default':
|
||||||
|
return
|
||||||
|
|
||||||
|
if tank._program.errors:
|
||||||
|
print tank.name, 'has errors'
|
||||||
|
|
||||||
|
|
||||||
|
fileName = os.path.join(self._errorDir, 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._imageDir = os.path.join(dir, 'frames')
|
||||||
|
os.mkdir( self._imageDir )
|
||||||
|
self._playerDir = os.path.join(dir, 'ai', 'players')
|
||||||
|
|
||||||
|
def _getDefaultAIs(self, dir, difficulty):
|
||||||
|
"""Load all the 'computer' controlled bot AIs for the given
|
||||||
|
difficulty."""
|
||||||
|
defaultAIs = []
|
||||||
|
|
||||||
|
path = os.path.join(dir, 'ai', difficulty)
|
||||||
|
files = os.listdir( path )
|
||||||
|
for file in files:
|
||||||
|
if file.startswith('.'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
path = os.path.join(dir, 'ai', difficulty, file)
|
||||||
|
file = open( path )
|
||||||
|
defaultAIs.append( file.read() )
|
||||||
|
|
||||||
|
return defaultAIs
|
||||||
|
|
||||||
|
def _getGameNum(self):
|
||||||
|
"""Figure out what game number this is from the past games played."""
|
||||||
|
|
||||||
|
oldGames = os.listdir(self._resultsDir)
|
||||||
|
games = []
|
||||||
|
for dir in oldGames:
|
||||||
|
try:
|
||||||
|
games.append( int(dir) )
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
games.sort(reverse=True)
|
||||||
|
if games:
|
||||||
|
return games[0] + 1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys, traceback
|
||||||
|
try:
|
||||||
|
p = Pflanzarr(sys.argv[1], sys.argv[2])
|
||||||
|
p.run( int(sys.argv[3]) )
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
print "Usage: python2.6 Pflanzarr.py dataDirectory easy|medium|hard #turns"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,232 @@
|
||||||
|
"""<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 feature 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 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:
|
||||||
|
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
|
|
@ -0,0 +1,537 @@
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
from PIL import Image
|
||||||
|
from PIL import ImageDraw
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def turn(self):
|
||||||
|
return self._turn
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# A rectangle starting 2 pixels past the tank front, and two pixels to the
|
||||||
|
# side. width = 4, height = 18.
|
||||||
|
tread1 = [(9,-9), (9, -5), (-8, -5), (-8, -9)]
|
||||||
|
# Same as tread one, except to the right.
|
||||||
|
tread2 = [(9, 9), (9,5), (-8,5), (-8,9)]
|
||||||
|
# A circle of radius 7.5 centered on center
|
||||||
|
body = [(-6,-6), (6,6)]
|
||||||
|
gun = [(0, -2), (0,2), (12,1), (12,-1)]
|
||||||
|
ledPoly = [(0, -2), (0,2), (-2,2), (-2,-2)]
|
||||||
|
hood = [(8, -6), (8, 6), (-7, 6), (-7, -6)]
|
||||||
|
laser = [(12,2), (FIRE_RANGE, 2), (FIRE_RANGE,-2), (12, -2)]
|
||||||
|
treadColor = '#777777'
|
||||||
|
def draw(self, image):
|
||||||
|
|
||||||
|
d = ImageDraw.Draw(image)
|
||||||
|
|
||||||
|
if self.isDead:
|
||||||
|
if self._deadFrame > 0:
|
||||||
|
# Draw the explosion instead of the normal art
|
||||||
|
self._drawExplosion(d)
|
||||||
|
self._drawLaser(d)
|
||||||
|
return image
|
||||||
|
|
||||||
|
if self._testMode:
|
||||||
|
self._drawSensors(d)
|
||||||
|
|
||||||
|
hood = gm.rotatePoly(self.hood, self._angle)
|
||||||
|
tread1 = gm.rotatePoly(self.tread1, self._angle)
|
||||||
|
tread2 = gm.rotatePoly(self.tread2, self._angle)
|
||||||
|
gun = gm.rotatePoly( self.gun, self._angle + self._tAngle )
|
||||||
|
led = gm.rotatePoly( self.ledPoly, self._angle + self._tAngle )
|
||||||
|
|
||||||
|
# The base body rectangle.
|
||||||
|
for poly in gm.displacePoly(hood, self.pos, self._limits):
|
||||||
|
d.polygon( poly, fill=self._color )
|
||||||
|
|
||||||
|
# The treads
|
||||||
|
for poly in gm.displacePoly(tread1, self.pos, self._limits) + \
|
||||||
|
gm.displacePoly(tread2, self.pos, self._limits):
|
||||||
|
d.polygon( poly, fill=self.treadColor )
|
||||||
|
|
||||||
|
# The turret circle
|
||||||
|
for poly in gm.displacePoly(self.body, self.pos, self._limits):
|
||||||
|
d.ellipse( poly, fill=self._color, outline='black')
|
||||||
|
|
||||||
|
self._drawLaser(d)
|
||||||
|
|
||||||
|
for poly in gm.displacePoly(gun, self.pos, self._limits):
|
||||||
|
d.polygon( poly, fill='#000000')
|
||||||
|
|
||||||
|
if self.led:
|
||||||
|
for poly in gm.displacePoly(led, self.pos, self._limits):
|
||||||
|
d.polygon( poly, fill='#ffffff')
|
||||||
|
|
||||||
|
def _drawLaser(self, drawing):
|
||||||
|
"""Draw the laser line if we shot this turn."""
|
||||||
|
if self._fired:
|
||||||
|
laser = gm.rotatePoly( self.laser, self._angle + self._tAngle )
|
||||||
|
for poly in gm.displacePoly(laser, self.pos, self._limits):
|
||||||
|
drawing.polygon(poly, fill=self._color)
|
||||||
|
|
||||||
|
self._fired = False
|
||||||
|
|
||||||
|
def _drawExplosion(self, drawing):
|
||||||
|
"""Draw the tank exploding."""
|
||||||
|
|
||||||
|
self._deadFrame = self._deadFrame - 1
|
||||||
|
innerBoom = []
|
||||||
|
outerBoom = []
|
||||||
|
points = 20
|
||||||
|
for i in range(points):
|
||||||
|
if i%2 == 1:
|
||||||
|
radius = 1.5 * self.RADIUS / 2
|
||||||
|
else:
|
||||||
|
radius = 1.5 * self.RADIUS
|
||||||
|
innerBoom.append(gm.polar2cart(radius/2, i*2*math.pi/points))
|
||||||
|
outerBoom.append(gm.polar2cart(radius, i*2*math.pi/points))
|
||||||
|
|
||||||
|
for poly in gm.displacePoly(outerBoom, self.pos, self._limits):
|
||||||
|
drawing.polygon(poly, fill='red')
|
||||||
|
for poly in gm.displacePoly(innerBoom, self.pos, self._limits):
|
||||||
|
drawing.polygon(poly, fill='orange')
|
||||||
|
|
||||||
|
def _drawSensors(self, drawing):
|
||||||
|
"""Draw sensor arcs for all of the defined sensors."""
|
||||||
|
|
||||||
|
for i in range( len( self._sensors) ):
|
||||||
|
if self._sensorState[i]:
|
||||||
|
color = '#000000'
|
||||||
|
else:
|
||||||
|
color = self._color
|
||||||
|
|
||||||
|
r, angle, width, tAttached = self._sensors[i]
|
||||||
|
r = int(r)
|
||||||
|
sensorCircle = [(-r, -r), (r, r)]
|
||||||
|
angle = angle + self._angle
|
||||||
|
if tAttached:
|
||||||
|
angle = angle + self._tAngle
|
||||||
|
min = int( math.degrees( angle - width/2 ) )
|
||||||
|
max = int( math.degrees( angle + width/2 ) )
|
||||||
|
for poly in gm.displacePoly(sensorCircle, self.pos, self._limits,
|
||||||
|
coordSequence=True):
|
||||||
|
drawing.chord(poly, min, max, outline=color)
|
|
@ -0,0 +1,123 @@
|
||||||
|
"""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):
|
||||||
|
tank.toggles[key] = not tank.toggles[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}
|
|
@ -0,0 +1,125 @@
|
||||||
|
"""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 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 }
|
|
@ -0,0 +1,27 @@
|
||||||
|
import xml.sax.saxutils
|
||||||
|
|
||||||
|
def mkDocTable(objects):
|
||||||
|
objects.sort(lambda o1, o2: cmp(o1.__doc__, o2.__doc__))
|
||||||
|
|
||||||
|
for object in objects:
|
||||||
|
print '<table class="docs">'
|
||||||
|
if object.__doc__ is None:
|
||||||
|
print '<tr><th>%s<tr><td colspan=2>Bad object' % \
|
||||||
|
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)
|
||||||
|
print '</table>'
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
"""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}
|
|
@ -0,0 +1,99 @@
|
||||||
|
/**** document ****/
|
||||||
|
|
||||||
|
html {
|
||||||
|
background: #222 url(grunge.png) repeat-x;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
color: #eee;
|
||||||
|
margin: 50px 0 0 100px;
|
||||||
|
padding: 10px;
|
||||||
|
max-width: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** heading ****/
|
||||||
|
|
||||||
|
h1:first-child {
|
||||||
|
text-transform: lowercase;
|
||||||
|
font-size: 1.6em;
|
||||||
|
/* background-color: #222; */
|
||||||
|
/* opacity: 0.9; */
|
||||||
|
padding: 3px;
|
||||||
|
color: #2a2;
|
||||||
|
margin: 0 0 1em 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1:first-child:before {
|
||||||
|
color: #fff;
|
||||||
|
letter-spacing: -0.1em;
|
||||||
|
content: "Capture The Flag: ";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** body ****/
|
||||||
|
|
||||||
|
a img {
|
||||||
|
border: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #2a2;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #fff;
|
||||||
|
background: #2a2;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
h1, h2, h3 {
|
||||||
|
color: #999;
|
||||||
|
letter-spacing: -0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
code, pre, .readme {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #555;
|
||||||
|
margin: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scoreboard td {
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 1.4em;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt div.tab {
|
||||||
|
background-color: #333;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px;
|
||||||
|
border: 3px solid green;
|
||||||
|
border-bottom: none;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
border: 3px solid green;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 5px;
|
||||||
|
background-color: #282828;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset * {
|
||||||
|
margin: 3px;
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
print """Content-Type: text/html\n\n"""
|
||||||
|
print """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">\n\n"""
|
||||||
|
import cgitb; cgitb.enable()
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
from Tanks import Program, setup, conditions, actions, docs
|
||||||
|
except:
|
||||||
|
path = os.getcwd().split('/')
|
||||||
|
path.pop()
|
||||||
|
path.append('lib')
|
||||||
|
sys.path.append(os.path.join('/', *path))
|
||||||
|
import Program, setup, conditions, actions, docs
|
||||||
|
|
||||||
|
print open('head.html').read() % "Documentation"
|
||||||
|
print '<BODY>'
|
||||||
|
print '<H1>Pflanzarr Documentation</H1>'
|
||||||
|
print '<a href="submit.html">Submit</a> | <a href="results.cgi">Results</a> | <a href="docs.cgi">Documentation</a>'
|
||||||
|
print Program.__doc__
|
||||||
|
|
||||||
|
print '<H3>Setup Actions:</H3>'
|
||||||
|
print 'These functions can be used to setup your tank. Abuse of these functions has, in the past, resulted in mine sweeping duty. With a broom.'
|
||||||
|
print "<P>"
|
||||||
|
docs.mkDocTable(setup.setup.values())
|
||||||
|
|
||||||
|
print '<H3>Conditions:</H3>'
|
||||||
|
print 'These functions are used to check the state of reality. If reality stops being real, refer to chapter 5 in your girl scout handbook.<P>'
|
||||||
|
docs.mkDocTable(conditions.conditions.values())
|
||||||
|
|
||||||
|
print '<H3>Actions:</H3>'
|
||||||
|
print 'These actions are not for cowards. Remember, if actions contradict, your tank will simply do the last thing it was told in a turn. If ordered to hop on a plane to hell it will gladly do so. If order to make tea shortly afterwards, it will serve it politely and with cookies instead.<P>'
|
||||||
|
docs.mkDocTable(actions.actions.values())
|
||||||
|
|
||||||
|
print '</body></html>'
|
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
|
@ -0,0 +1,5 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link href="ctf.css" rel="stylesheet" type="text/css">'
|
||||||
|
<title>%s</title>"
|
||||||
|
</head>
|
|
@ -0,0 +1,47 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import cgitb; cgitb.enable()
|
||||||
|
import os
|
||||||
|
|
||||||
|
print """Content-Type: text/html\n\n"""
|
||||||
|
print """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">\n\n"""
|
||||||
|
head = open('head.html').read() % "Pflanzarr Results"
|
||||||
|
print head
|
||||||
|
print "<H1>Results</H1>"
|
||||||
|
print '<a href="submit.html">Submit</a> | <a href="results.cgi">Results</a> | <a href="docs.cgi">Documentation</a>'
|
||||||
|
|
||||||
|
try:
|
||||||
|
winner = open(os.path.join('data', 'winner')).read()
|
||||||
|
except:
|
||||||
|
winner = "No winner yet."
|
||||||
|
|
||||||
|
print "<H3>Last Winner: ", winner, '<H3>'
|
||||||
|
print "<H2>Results so far:</H2>"
|
||||||
|
|
||||||
|
try:
|
||||||
|
games = os.listdir(os.path.join('data', 'results'))
|
||||||
|
except:
|
||||||
|
print '<p>The data directory does not exist.'
|
||||||
|
games = []
|
||||||
|
|
||||||
|
if not games:
|
||||||
|
print "<p>No games have occurred yet."
|
||||||
|
gameNums = []
|
||||||
|
for game in games:
|
||||||
|
try:
|
||||||
|
num = int(game)
|
||||||
|
path = os.path.join( 'data', "results", game, 'results.html')
|
||||||
|
if os.path.exists( path ):
|
||||||
|
gameNums.append( int(num) )
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
gameNums.sort(reverse=True)
|
||||||
|
|
||||||
|
for num in gameNums:
|
||||||
|
print '<p>%d - ' % num,
|
||||||
|
print '<a href="data/results/%d/game.avi">v</a>' % num,
|
||||||
|
print '<a href="data/results/%d/results.html">r</a>' % num
|
|
@ -0,0 +1,16 @@
|
||||||
|
body { background-color : #000000;
|
||||||
|
color : #E0E0E0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border : 2px solid #00EE00;
|
||||||
|
border-collapse : collapse;
|
||||||
|
margin : 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td { border : 1px solid #00BB00;
|
||||||
|
padding-left: 3px;
|
||||||
|
padding-right: 3px;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import cgi
|
||||||
|
import cgitb; cgitb.enable()
|
||||||
|
import os
|
||||||
|
|
||||||
|
try:
|
||||||
|
from urllib.parse import quote
|
||||||
|
except:
|
||||||
|
from urllib import quote
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ctf import teams
|
||||||
|
except:
|
||||||
|
import sys
|
||||||
|
path = '/home/pflarr/repos/gctf/'
|
||||||
|
sys.path.append(path)
|
||||||
|
from ctf import teams
|
||||||
|
|
||||||
|
print """Content-Type: text/html\n\n"""
|
||||||
|
print """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN">\n\n"""
|
||||||
|
head = open('head.html').read() % "Submission Results"
|
||||||
|
print head
|
||||||
|
print "<H1>Results</H1>"
|
||||||
|
print '<a href="submit.html">Submit</a> | <a href="results.cgi">Results</a> | <a href="docs.cgi">Documentation</a>'
|
||||||
|
|
||||||
|
def done():
|
||||||
|
print '</body></html>'
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
fields = cgi.FieldStorage()
|
||||||
|
team = fields.getfirst('team', '').strip()
|
||||||
|
passwd = fields.getfirst('passwd', '').strip()
|
||||||
|
code = fields.getfirst('code', '')
|
||||||
|
if not team:
|
||||||
|
print '<p>No team specified'; done()
|
||||||
|
elif not passwd:
|
||||||
|
print '<p>No password given'; done()
|
||||||
|
elif not code:
|
||||||
|
print '<p>No program given.'; done()
|
||||||
|
|
||||||
|
if team not in teams.teams:
|
||||||
|
print '<p>Team is not registered.'; done()
|
||||||
|
|
||||||
|
if passwd != teams.teams[team][0]:
|
||||||
|
print '<p>Invalid password.'; done()
|
||||||
|
|
||||||
|
path = os.path.join('data/ai/players', encode(team) )
|
||||||
|
file = open(path, 'w')
|
||||||
|
file.write(code)
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
done()
|
|
@ -0,0 +1,21 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link href="ctf.css" rel="stylesheet" type="text/css">'
|
||||||
|
<title>Program Submission</title>"
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<H1>Program Submission</H1>
|
||||||
|
<p><a href="submit.html">Submit</a> | <a href="results.cgi">Results</a> | <a href="docs.cgi">Documentation</a>
|
||||||
|
|
||||||
|
<form action="submit.cgi" method="post">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Your program:</legend>
|
||||||
|
Team: <input type="text" name="team"><BR>
|
||||||
|
Password: <input type="text" name="passwd"><BR>
|
||||||
|
<textarea cols="80" rows="30" name="code"></textarea><BR>
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue