From e95fabccb7456b3c910f869e7d0c998a4aeb948b Mon Sep 17 00:00:00 2001 From: "Paul S. Ferrell" Date: Mon, 5 Oct 2009 16:39:43 -0600 Subject: [PATCH 1/5] Removed some stuff that shouldn't have been in the repo any longer. --- badmath/Flagger.py | 31 ------------------------------- badmath/badmath.state | Bin 274 -> 0 bytes 2 files changed, 31 deletions(-) delete mode 100644 badmath/Flagger.py delete mode 100644 badmath/badmath.state diff --git a/badmath/Flagger.py b/badmath/Flagger.py deleted file mode 100644 index 8722ea1..0000000 --- a/badmath/Flagger.py +++ /dev/null @@ -1,31 +0,0 @@ -import asynchat -import asyncore -import socket - -class Flagger(asynchat.async_chat): - """Connection to flagd""" - - def __init__(self, addr, auth): - asynchat.async_chat.__init__(self) - self.create_socket(socket.AF_INET, socket.SOCK_STREAM) - self.connect((addr, 6668)) - self.push(auth + b'\n') - self.flag = None - - def handle_read(self): - msg = self.recv(4096) - raise ValueError("Flagger died: %r" % msg) - - 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 = b'' - self.push(eteam + b'\n') - self.flag = team diff --git a/badmath/badmath.state b/badmath/badmath.state deleted file mode 100644 index 43d3747474391bae01b98bd2efd7253d20959929..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 274 zcmXAjJ5R$f6onlc(l$xUD?D~U%G9Om4)fiZyhN1?4z_BN9@{~pQY4oCZ!EKY-@WHt z-QURwR`ZN8=KHo&>aQ}vfVf6=bn?x)C From 41f077192da8103c172cc2d36065cb0f842e9130 Mon Sep 17 00:00:00 2001 From: "Paul S. Ferrell" Date: Tue, 6 Oct 2009 11:50:21 -0600 Subject: [PATCH 2/5] Various work on Tanks. --- ctf/teams.py | 6 +- tanks/AI/easy/berzerker | 5 + tanks/AI/easy/rabbitwithgun | 8 + tanks/AI/hard/crashmaster | 57 +++ tanks/AI/hard/foobar | 22 ++ tanks/AI/hard/pflarr | 31 ++ tanks/AI/medium/simpleton | 8 + tanks/AI/medium/sittingduckwithteeth | 9 + tanks/AI/medium/sweeper | 18 + tanks/Makefile | 34 ++ tanks/lib/.actions.py.swp | Bin 0 -> 20480 bytes tanks/lib/Function.py | 47 +++ tanks/lib/GameMath.py | 206 ++++++++++ tanks/lib/Pflanzarr.py | 400 ++++++++++++++++++++ tanks/lib/Program.py | 232 ++++++++++++ tanks/lib/Tank.py | 537 +++++++++++++++++++++++++++ tanks/lib/__init__.py | 0 tanks/lib/actions.py | 123 ++++++ tanks/lib/conditions.py | 125 +++++++ tanks/lib/docs.py | 27 ++ tanks/lib/setup.py | 72 ++++ tanks/log.run | 0 tanks/run | 0 tanks/www/ctf.css | 99 +++++ tanks/www/docs.cgi | 37 ++ tanks/www/grunge.png | Bin 0 -> 5893 bytes tanks/www/head.html | 5 + tanks/www/results.cgi | 47 +++ tanks/www/style.css | 16 + tanks/www/submit.cgi | 53 +++ tanks/www/submit.html | 21 ++ 31 files changed, 2244 insertions(+), 1 deletion(-) create mode 100644 tanks/AI/easy/berzerker create mode 100644 tanks/AI/easy/rabbitwithgun create mode 100644 tanks/AI/hard/crashmaster create mode 100644 tanks/AI/hard/foobar create mode 100644 tanks/AI/hard/pflarr create mode 100644 tanks/AI/medium/simpleton create mode 100644 tanks/AI/medium/sittingduckwithteeth create mode 100644 tanks/AI/medium/sweeper create mode 100644 tanks/Makefile create mode 100644 tanks/lib/.actions.py.swp create mode 100644 tanks/lib/Function.py create mode 100644 tanks/lib/GameMath.py create mode 100644 tanks/lib/Pflanzarr.py create mode 100644 tanks/lib/Program.py create mode 100644 tanks/lib/Tank.py create mode 100644 tanks/lib/__init__.py create mode 100644 tanks/lib/actions.py create mode 100644 tanks/lib/conditions.py create mode 100644 tanks/lib/docs.py create mode 100644 tanks/lib/setup.py create mode 100755 tanks/log.run create mode 100755 tanks/run create mode 100644 tanks/www/ctf.css create mode 100755 tanks/www/docs.cgi create mode 100644 tanks/www/grunge.png create mode 100644 tanks/www/head.html create mode 100755 tanks/www/results.cgi create mode 100644 tanks/www/style.css create mode 100755 tanks/www/submit.cgi create mode 100644 tanks/www/submit.html diff --git a/ctf/teams.py b/ctf/teams.py index 4f73799..41da559 100755 --- a/ctf/teams.py +++ b/ctf/teams.py @@ -3,7 +3,11 @@ import fcntl import time 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 house = config.get('global', 'house_team') diff --git a/tanks/AI/easy/berzerker b/tanks/AI/easy/berzerker new file mode 100644 index 0000000..e0351d0 --- /dev/null +++ b/tanks/AI/easy/berzerker @@ -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(); \ No newline at end of file diff --git a/tanks/AI/easy/rabbitwithgun b/tanks/AI/easy/rabbitwithgun new file mode 100644 index 0000000..b820488 --- /dev/null +++ b/tanks/AI/easy/rabbitwithgun @@ -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); \ No newline at end of file diff --git a/tanks/AI/hard/crashmaster b/tanks/AI/hard/crashmaster new file mode 100644 index 0000000..ca94b98 --- /dev/null +++ b/tanks/AI/hard/crashmaster @@ -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); \ No newline at end of file diff --git a/tanks/AI/hard/foobar b/tanks/AI/hard/foobar new file mode 100644 index 0000000..bcdf807 --- /dev/null +++ b/tanks/AI/hard/foobar @@ -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(); + diff --git a/tanks/AI/hard/pflarr b/tanks/AI/hard/pflarr new file mode 100644 index 0000000..b4df468 --- /dev/null +++ b/tanks/AI/hard/pflarr @@ -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(); diff --git a/tanks/AI/medium/simpleton b/tanks/AI/medium/simpleton new file mode 100644 index 0000000..64fc607 --- /dev/null +++ b/tanks/AI/medium/simpleton @@ -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) diff --git a/tanks/AI/medium/sittingduckwithteeth b/tanks/AI/medium/sittingduckwithteeth new file mode 100644 index 0000000..4ea551f --- /dev/null +++ b/tanks/AI/medium/sittingduckwithteeth @@ -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(); + diff --git a/tanks/AI/medium/sweeper b/tanks/AI/medium/sweeper new file mode 100644 index 0000000..e02e950 --- /dev/null +++ b/tanks/AI/medium/sweeper @@ -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(); diff --git a/tanks/Makefile b/tanks/Makefile new file mode 100644 index 0000000..23fdfc3 --- /dev/null +++ b/tanks/Makefile @@ -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 diff --git a/tanks/lib/.actions.py.swp b/tanks/lib/.actions.py.swp new file mode 100644 index 0000000000000000000000000000000000000000..420b2285120f511a0a1df93e831a93ff55b14b4d GIT binary patch literal 20480 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 YM^(f%a#^$7wn)yfy_u4HIdRv20l=Q4(EtDd literal 0 HcmV?d00001 diff --git a/tanks/lib/Function.py b/tanks/lib/Function.py new file mode 100644 index 0000000..7017f55 --- /dev/null +++ b/tanks/lib/Function.py @@ -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 + diff --git a/tanks/lib/GameMath.py b/tanks/lib/GameMath.py new file mode 100644 index 0000000..ff47880 --- /dev/null +++ b/tanks/lib/GameMath.py @@ -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 diff --git a/tanks/lib/Pflanzarr.py b/tanks/lib/Pflanzarr.py new file mode 100644 index 0000000..8f05ed3 --- /dev/null +++ b/tanks/lib/Pflanzarr.py @@ -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 = ['', + '
TeamKillsCause of Death'] + for tank in tanks: + if tank is winner: + rowStyle = 'style="color:red;"' + else: + rowStyle = '' + html.append('
%s%d%s' % + (rowStyle, + xml.sax.saxutils.escape(tank.name), + len(kills[tank]), + xml.sax.saxutils.escape(tank.deathReason))) + + html.append('
') + + 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" + + diff --git a/tanks/lib/Program.py b/tanks/lib/Program.py new file mode 100644 index 0000000..ad30b1d --- /dev/null +++ b/tanks/lib/Program.py @@ -0,0 +1,232 @@ +"""

Introduction

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

Programming Your Tank

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

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

>addsensor(80, 90, 33);
+>addsensor(50, 0, 10, 1);
+>addtimer(3);
+ +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: +
+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);
+ +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 diff --git a/tanks/lib/Tank.py b/tanks/lib/Tank.py new file mode 100644 index 0000000..dffa85e --- /dev/null +++ b/tanks/lib/Tank.py @@ -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 '' % (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) diff --git a/tanks/lib/__init__.py b/tanks/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tanks/lib/actions.py b/tanks/lib/actions.py new file mode 100644 index 0000000..790afab --- /dev/null +++ b/tanks/lib/actions.py @@ -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} diff --git a/tanks/lib/conditions.py b/tanks/lib/conditions.py new file mode 100644 index 0000000..3a6c0ac --- /dev/null +++ b/tanks/lib/conditions.py @@ -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 } diff --git a/tanks/lib/docs.py b/tanks/lib/docs.py new file mode 100644 index 0000000..1724d52 --- /dev/null +++ b/tanks/lib/docs.py @@ -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 '' + if object.__doc__ is None: + print '
%s
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('.', '.
') + body.append(line) + + body = '\n'.join(body) + print '
%s
%s
' % (head, body) + #print '
%sIntentionally blank
%s' % (head, body) + print '
' + diff --git a/tanks/lib/setup.py b/tanks/lib/setup.py new file mode 100644 index 0000000..29bc861 --- /dev/null +++ b/tanks/lib/setup.py @@ -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. +

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

+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} diff --git a/tanks/log.run b/tanks/log.run new file mode 100755 index 0000000..e69de29 diff --git a/tanks/run b/tanks/run new file mode 100755 index 0000000..e69de29 diff --git a/tanks/www/ctf.css b/tanks/www/ctf.css new file mode 100644 index 0000000..ca7c9f6 --- /dev/null +++ b/tanks/www/ctf.css @@ -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; +} diff --git a/tanks/www/docs.cgi b/tanks/www/docs.cgi new file mode 100755 index 0000000..26eb752 --- /dev/null +++ b/tanks/www/docs.cgi @@ -0,0 +1,37 @@ +#!/usr/bin/python + +print """Content-Type: text/html\n\n""" +print """\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 '' +print '

Pflanzarr Documentation

' +print 'Submit | Results | Documentation' +print Program.__doc__ + +print '

Setup Actions:

' +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 "

" +docs.mkDocTable(setup.setup.values()) + +print '

Conditions:

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

' +docs.mkDocTable(conditions.conditions.values()) + +print '

Actions:

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

' +docs.mkDocTable(actions.actions.values()) + +print '' diff --git a/tanks/www/grunge.png b/tanks/www/grunge.png new file mode 100644 index 0000000000000000000000000000000000000000..2b98730b0354a8305ea9eb33e64d65ab7de8a393 GIT binary patch literal 5893 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 literal 0 HcmV?d00001 diff --git a/tanks/www/head.html b/tanks/www/head.html new file mode 100644 index 0000000..b98713e --- /dev/null +++ b/tanks/www/head.html @@ -0,0 +1,5 @@ + + + ' + %s" + diff --git a/tanks/www/results.cgi b/tanks/www/results.cgi new file mode 100755 index 0000000..4901ac4 --- /dev/null +++ b/tanks/www/results.cgi @@ -0,0 +1,47 @@ +#!/usr/bin/python + +import cgitb; cgitb.enable() +import os + +print """Content-Type: text/html\n\n""" +print """\n\n""" +head = open('head.html').read() % "Pflanzarr Results" +print head +print "

Results

" +print 'Submit | Results | Documentation' + +try: + winner = open(os.path.join('data', 'winner')).read() +except: + winner = "No winner yet." + +print "

Last Winner: ", winner, '

' +print "

Results so far:

" + +try: + games = os.listdir(os.path.join('data', 'results')) +except: + print '

The data directory does not exist.' + games = [] + +if not games: + print "

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 '

%d - ' % num, + print 'v' % num, + print 'r' % num diff --git a/tanks/www/style.css b/tanks/www/style.css new file mode 100644 index 0000000..ac787f0 --- /dev/null +++ b/tanks/www/style.css @@ -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; + } diff --git a/tanks/www/submit.cgi b/tanks/www/submit.cgi new file mode 100755 index 0000000..01ad8c8 --- /dev/null +++ b/tanks/www/submit.cgi @@ -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 """\n\n""" +head = open('head.html').read() % "Submission Results" +print head +print "

Results

" +print 'Submit | Results | Documentation' + +def done(): + print '' + sys.exit(0) + +fields = cgi.FieldStorage() +team = fields.getfirst('team', '').strip() +passwd = fields.getfirst('passwd', '').strip() +code = fields.getfirst('code', '') +if not team: + print '

No team specified'; done() +elif not passwd: + print '

No password given'; done() +elif not code: + print '

No program given.'; done() + +if team not in teams.teams: + print '

Team is not registered.'; done() + +if passwd != teams.teams[team][0]: + print '

Invalid password.'; done() + +path = os.path.join('data/ai/players', encode(team) ) +file = open(path, 'w') +file.write(code) +file.close() + +done() diff --git a/tanks/www/submit.html b/tanks/www/submit.html new file mode 100644 index 0000000..86cc13c --- /dev/null +++ b/tanks/www/submit.html @@ -0,0 +1,21 @@ + + + ' + Program Submission" + + + +

Program Submission

+

Submit | Results | Documentation + +

+
+ Your program: + Team:
+ Password:
+
+ +
+
+ + From 2034591fb615d65b95ab0a68e7764788ce45b6a0 Mon Sep 17 00:00:00 2001 From: "Paul S. Ferrell" Date: Tue, 6 Oct 2009 12:47:36 -0600 Subject: [PATCH 3/5] Final (I hope tanks update). --- .gitignore | 1 + ctf/teams.py | 1 - tanks/lib/.actions.py.swp | Bin 20480 -> 0 bytes tanks/lib/Pflanzarr.py | 38 ++++++++++++--------- tanks/www/Config.py | 1 + tanks/www/ctf.css | 3 +- tanks/www/docs.cgi | 2 +- tanks/www/errors.cgi | 69 ++++++++++++++++++++++++++++++++++++++ tanks/www/head.html | 4 +-- tanks/www/links.html | 4 +++ tanks/www/results.cgi | 16 +++++---- tanks/www/style.css | 16 --------- tanks/www/submit.cgi | 7 ++-- tanks/www/submit.html | 7 ++-- 14 files changed, 122 insertions(+), 47 deletions(-) delete mode 100644 tanks/lib/.actions.py.swp create mode 100644 tanks/www/Config.py create mode 100755 tanks/www/errors.cgi create mode 100644 tanks/www/links.html delete mode 100644 tanks/www/style.css diff --git a/.gitignore b/.gitignore index 15953f7..dd4952d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *~ *.pyc *.dat +*.swp passwd target/ puzzler/ diff --git a/ctf/teams.py b/ctf/teams.py index 41da559..29ed25b 100755 --- a/ctf/teams.py +++ b/ctf/teams.py @@ -18,7 +18,6 @@ teams = {} built = 0 def build_teams(): global teams, built - if not os.path.exists(passwdfn): return if os.path.getmtime(passwdfn) <= built: diff --git a/tanks/lib/.actions.py.swp b/tanks/lib/.actions.py.swp deleted file mode 100644 index 420b2285120f511a0a1df93e831a93ff55b14b4d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 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 YM^(f%a#^$7wn)yfy_u4HIdRv20l=Q4(EtDd diff --git a/tanks/lib/Pflanzarr.py b/tanks/lib/Pflanzarr.py index 8f05ed3..4f31ca1 100644 --- a/tanks/lib/Pflanzarr.py +++ b/tanks/lib/Pflanzarr.py @@ -5,6 +5,8 @@ import random import subprocess import xml.sax.saxutils +from urllib import unquote, quote + from PIL import Image, ImageColor, ImageDraw try: @@ -14,6 +16,7 @@ except: path = '/home/pflarr/repos/gctf/' sys.path.append(path) from ctf import teams +teams.build_teams() import Tank @@ -43,7 +46,7 @@ class Pflanzarr: players = [] for p in tmpPlayers: p = unquote(p) - if not (p.startswith('.') or p.endswith('#') or p.endswith('~')) + if not (p.startswith('.') or p.endswith('#') or p.endswith('~'))\ and p in teams.teams: players.append(p) @@ -54,23 +57,27 @@ class Pflanzarr: 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 + # The one is added to ensure that there is at least one #default bot. + cols = math.sqrt(len(players) + 1) + if int(cols) != cols: + cols = cols + 1 - size = int(size) - if size < 2: - size = 2 + cols = int(cols) + if cols < 2: + cols = 2 - self._board = (size*self.SPACING, size*self.SPACING) + rows = len(players)/cols + if len(players) % cols != 0: + rows = rows + 1 + + self._board = (cols*self.SPACING, rows*self.SPACING) - while len(players) < size**2: + while len(players) < cols*rows: players.append('#default') self._tanks = [] - for i in range(size): - for j in range(size): + 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) @@ -78,7 +85,7 @@ class Pflanzarr: if player == '#default': color = '#a0a0a0' else: - color = team.teams[player][1] + color = '#%s' % teams.teams[player][1] tank = Tank.Tank( player, (startX, startY), color, self._board, testMode=True) if player == '#default': @@ -250,7 +257,7 @@ class Pflanzarr: print tank.name, 'has errors' - fileName = os.path.join(self._errorDir, tank.name) + fileName = os.path.join(self._errorDir, quote(tank.name)) file = open(fileName, 'w') for error in tank._program.errors: file.write(error) @@ -351,7 +358,8 @@ class Pflanzarr: 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 ) + if not os.path.isdir(self._imageDir): + os.mkdir( self._imageDir ) self._playerDir = os.path.join(dir, 'ai', 'players') def _getDefaultAIs(self, dir, difficulty): diff --git a/tanks/www/Config.py b/tanks/www/Config.py new file mode 100644 index 0000000..4821f03 --- /dev/null +++ b/tanks/www/Config.py @@ -0,0 +1 @@ +DATA_PATH = '/var/lib/tanks/' diff --git a/tanks/www/ctf.css b/tanks/www/ctf.css index ca7c9f6..f3b91d1 100644 --- a/tanks/www/ctf.css +++ b/tanks/www/ctf.css @@ -54,7 +54,7 @@ h1, h2, h3 { letter-spacing: -0.05em; } -code, pre, .readme { +code, pre, .readme, div.errors { color: #fff; background-color: #555; margin: 1em; @@ -97,3 +97,4 @@ dd { fieldset * { margin: 3px; } + diff --git a/tanks/www/docs.cgi b/tanks/www/docs.cgi index 26eb752..8ceb4d0 100755 --- a/tanks/www/docs.cgi +++ b/tanks/www/docs.cgi @@ -18,7 +18,7 @@ except: print open('head.html').read() % "Documentation" print '' print '

Pflanzarr Documentation

' -print 'Submit | Results | Documentation' +print open('links.html').read() print Program.__doc__ print '

Setup Actions:

' diff --git a/tanks/www/errors.cgi b/tanks/www/errors.cgi new file mode 100755 index 0000000..1359cc9 --- /dev/null +++ b/tanks/www/errors.cgi @@ -0,0 +1,69 @@ +#!/usr/bin/python + +print """Content-Type: text/html\n\n""" +print """\n\n""" +import cgi +import cgitb; cgitb.enable() +import os + +import Config + +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 +teams.build_teams() + +head = open('head.html').read() % "Error Report" +print head +print open('links.html').read() + +def done(): + print '' + sys.exit(0) + +fields = cgi.FieldStorage() +team = fields.getfirst('team', '').strip() +passwd = fields.getfirst('passwd', '').strip() +if team and passwd and \ + team in teams.teams and passwd == teams.teams[team][0]: + path = os.path.join(Config.DATA_PATH, 'errors', quote(team)) + if os.path.isfile(path): + errors = open(path).readlines() + print '

Your latest errors:' + print '

' + if errors: + print '
\n'.join(errors) + else: + print 'There were no errors.' + print '
' + else: + print '

No error file found.' + + done() + +if team and team not in teams.teams: + print '

Invalid team.' + +if team and team in teams.teams and passwd != teams.teams[team][0]: + print '

Invalid password.' + +print ''' +

+
+ Error report request: + Team:
+ Password:
+ +
+
''' + +done() diff --git a/tanks/www/head.html b/tanks/www/head.html index b98713e..f2b66ea 100644 --- a/tanks/www/head.html +++ b/tanks/www/head.html @@ -1,5 +1,5 @@ - ' - %s" + + %s diff --git a/tanks/www/links.html b/tanks/www/links.html new file mode 100644 index 0000000..1615bf7 --- /dev/null +++ b/tanks/www/links.html @@ -0,0 +1,4 @@ +Documentation | +Results | +Submit | +My Errors diff --git a/tanks/www/results.cgi b/tanks/www/results.cgi index 4901ac4..a17ee77 100755 --- a/tanks/www/results.cgi +++ b/tanks/www/results.cgi @@ -3,15 +3,17 @@ import cgitb; cgitb.enable() import os +import Config + print """Content-Type: text/html\n\n""" print """\n\n""" head = open('head.html').read() % "Pflanzarr Results" print head print "

Results

" -print 'Submit | Results | Documentation' +print open('links.html').read() try: - winner = open(os.path.join('data', 'winner')).read() + winner = open(os.path.join(Config.DATA_PATH, 'winner')).read() except: winner = "No winner yet." @@ -19,9 +21,9 @@ print "

Last Winner: ", winner, '

' print "

Results so far:

" try: - games = os.listdir(os.path.join('data', 'results')) + games = os.listdir(os.path.join('results')) except: - print '

The data directory does not exist.' + print '

The results directory does not exist.' games = [] if not games: @@ -30,7 +32,7 @@ gameNums = [] for game in games: try: num = int(game) - path = os.path.join( 'data', "results", game, 'results.html') + path = os.path.join( 'results', game, 'results.html') if os.path.exists( path ): gameNums.append( int(num) ) else: @@ -43,5 +45,5 @@ gameNums.sort(reverse=True) for num in gameNums: print '

%d - ' % num, - print 'v' % num, - print 'r' % num + print 'v' % num, + print 'r' % num diff --git a/tanks/www/style.css b/tanks/www/style.css deleted file mode 100644 index ac787f0..0000000 --- a/tanks/www/style.css +++ /dev/null @@ -1,16 +0,0 @@ -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; - } diff --git a/tanks/www/submit.cgi b/tanks/www/submit.cgi index 01ad8c8..9d57907 100755 --- a/tanks/www/submit.cgi +++ b/tanks/www/submit.cgi @@ -4,6 +4,8 @@ import cgi import cgitb; cgitb.enable() import os +import Config + try: from urllib.parse import quote except: @@ -16,13 +18,14 @@ except: path = '/home/pflarr/repos/gctf/' sys.path.append(path) from ctf import teams +teams.build_teams() print """Content-Type: text/html\n\n""" print """\n\n""" head = open('head.html').read() % "Submission Results" print head print "

Results

" -print 'Submit | Results | Documentation' +print open('links.html').read() def done(): print '' @@ -45,7 +48,7 @@ if team not in teams.teams: if passwd != teams.teams[team][0]: print '

Invalid password.'; done() -path = os.path.join('data/ai/players', encode(team) ) +path = os.path.join(Config.DATA_PATH, 'ai/players', encode(team) ) file = open(path, 'w') file.write(code) file.close() diff --git a/tanks/www/submit.html b/tanks/www/submit.html index 86cc13c..f538d5d 100644 --- a/tanks/www/submit.html +++ b/tanks/www/submit.html @@ -6,8 +6,11 @@

Program Submission

-

Submit | Results | Documentation - +

+ Documentation | + Results | + Submit | + My Errors

Your program: From b840cb03f684edfadd9aad305c00e89dfdb511b9 Mon Sep 17 00:00:00 2001 From: "Paul S. Ferrell" Date: Tue, 6 Oct 2009 14:14:16 -0600 Subject: [PATCH 4/5] Finish Makefiles for badmath and tanks. --- badmath/Makefile | 26 ++++++++++++++++++++++++++ badmath/badmath.tce | Bin 0 -> 4492 bytes badmath/fake | 14 ++++++++++++++ badmath/log.run | 3 +++ tanks/Makefile | 4 ++-- tanks/log.run | 3 +++ tanks/report_score.py | 16 ++++++++++++++++ tanks/run | 6 ++++++ tanks/run_tanks.py | 20 ++++++++++++++++++++ 9 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 badmath/Makefile create mode 100644 badmath/badmath.tce create mode 100644 badmath/fake create mode 100755 badmath/log.run create mode 100755 tanks/report_score.py create mode 100644 tanks/run_tanks.py diff --git a/badmath/Makefile b/badmath/Makefile new file mode 100644 index 0000000..ae0b82f --- /dev/null +++ b/badmath/Makefile @@ -0,0 +1,26 @@ +FAKE = fakeroot -s fake -i fake +INSTALL = $(FAKE) install + +all: badmath.tce + +badmath.tce: target + $(FAKE) sh -c 'cd target && tar -czf - --exclude=placeholder --exclude=*~ .' > $@ + + +target: + $(INSTALL) -d target/usr/lib/ctf/badmath + $(INSTALL) Gyopi.py badmath.py target/usr/lib/ctf/badmath + + $(INSTALL) -d target/var/lib/badmath + + $(INSTALL) -d target/var/service/badmath + $(INSTALL) run target/var/service/badmath/run + + $(INSTALL) -d target/var/service/badmath/log + $(INSTALL) log.run target/var/service/badmath/log/run + +clean: + rm -rf target badmath.tce fake + +%.pyc: %.py + python3 -c 'import $*' diff --git a/badmath/badmath.tce b/badmath/badmath.tce new file mode 100644 index 0000000000000000000000000000000000000000..ec0516ab8ef924ae1e2181836425ae9ac0c6e1e6 GIT binary patch literal 4492 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 literal 0 HcmV?d00001 diff --git a/badmath/fake b/badmath/fake new file mode 100644 index 0000000..5e3c978 --- /dev/null +++ b/badmath/fake @@ -0,0 +1,14 @@ +dev=16,ino=87557208,mode=40755,uid=0,gid=0,nlink=2,rdev=0 +dev=16,ino=87557227,mode=40755,uid=0,gid=0,nlink=2,rdev=0 +dev=16,ino=87557231,mode=40755,uid=0,gid=0,nlink=2,rdev=0 +dev=16,ino=87557232,mode=40755,uid=0,gid=0,nlink=2,rdev=0 +dev=16,ino=87557233,mode=40755,uid=0,gid=0,nlink=2,rdev=0 +dev=16,ino=87557234,mode=40755,uid=0,gid=0,nlink=2,rdev=0 +dev=16,ino=87557235,mode=40755,uid=0,gid=0,nlink=2,rdev=0 +dev=16,ino=87557237,mode=40755,uid=0,gid=0,nlink=2,rdev=0 +dev=16,ino=87557238,mode=40755,uid=0,gid=0,nlink=2,rdev=0 +dev=16,ino=87557239,mode=40755,uid=0,gid=0,nlink=2,rdev=0 +dev=16,ino=87557240,mode=40755,uid=0,gid=0,nlink=2,rdev=0 +dev=16,ino=87573208,mode=100755,uid=0,gid=0,nlink=1,rdev=0 +dev=16,ino=87573433,mode=100755,uid=0,gid=0,nlink=1,rdev=0 +dev=16,ino=87573456,mode=100755,uid=0,gid=0,nlink=1,rdev=0 diff --git a/badmath/log.run b/badmath/log.run new file mode 100755 index 0000000..c455835 --- /dev/null +++ b/badmath/log.run @@ -0,0 +1,3 @@ +#! /bin/sh + +exec logger -t badmath diff --git a/tanks/Makefile b/tanks/Makefile index 23fdfc3..cb27c38 100644 --- a/tanks/Makefile +++ b/tanks/Makefile @@ -1,5 +1,5 @@ FAKE = fakeroot -s fake -i fake -INSTALL = $(fake) install +INSTALL = $(FAKE) install -o 100 all: tanks.tce @@ -19,7 +19,7 @@ target: $(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 + $(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/ diff --git a/tanks/log.run b/tanks/log.run index e69de29..c4eff97 100755 --- a/tanks/log.run +++ b/tanks/log.run @@ -0,0 +1,3 @@ +#! /bin/sh + +exec logger -t tanks diff --git a/tanks/report_score.py b/tanks/report_score.py new file mode 100755 index 0000000..32e958e --- /dev/null +++ b/tanks/report_score.py @@ -0,0 +1,16 @@ +#!/usr/bin/python3.0 + +from ctf.flagd import Flagger + +key = 'tanks:::2bac5e912ff2e1ad559b177eb5aeecca' + +f = Flagger.Flagger('localhost', key) + +while 1: + time.sleep(1) + try: + winner = open('/var/lib/tanks/winner').read() + except: + winner = None + + f.set_flag(winner) diff --git a/tanks/run b/tanks/run index e69de29..a8157be 100755 --- a/tanks/run +++ b/tanks/run @@ -0,0 +1,6 @@ +#! /bin/sh + +[ -f /var/lib/ctf/disabled/tanks ] && exit 0 + +envuidgid ctf python2.6 run_tanks.py /var/lib/tanks/ easy 100 & +envuidgid ctf report_score.py diff --git a/tanks/run_tanks.py b/tanks/run_tanks.py new file mode 100644 index 0000000..4263e15 --- /dev/null +++ b/tanks/run_tanks.py @@ -0,0 +1,20 @@ +import time +from tanks import Pflanzarr +import sys + +T = 60*5 + +try: + while 1: + start = time.time() + p = Pflanzarr(sys.argv[1], sys.argv[2]) + p.run(int(sys.argv[3])) + + diff = time.time() - start + if diff - T > 0: + time.sleep( diff - T ) + +except: + print 'Usage: python2.6 run_tanks.py data_dir easy|medium|hard max_turns' + + From 5dc4055f3cf896671394839e4b0b2936577e7897 Mon Sep 17 00:00:00 2001 From: "Paul S. Ferrell" Date: Tue, 6 Oct 2009 14:16:47 -0600 Subject: [PATCH 5/5] chash sucks. --- puzzles/webapp/2/2.cgi | 2 +- puzzles/webapp/3/3.cgi | 2 +- puzzles/webapp/4/4.cgi | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/puzzles/webapp/2/2.cgi b/puzzles/webapp/2/2.cgi index 2f7249c..3e9b6cd 100755 --- a/puzzles/webapp/2/2.cgi +++ b/puzzles/webapp/2/2.cgi @@ -34,7 +34,7 @@ if (fields.has_key('num')): ''' % int(fields['num'].value) print ''' - + Enter an integer: diff --git a/puzzles/webapp/3/3.cgi b/puzzles/webapp/3/3.cgi index 0ac4e73..63f495b 100755 --- a/puzzles/webapp/3/3.cgi +++ b/puzzles/webapp/3/3.cgi @@ -47,7 +47,7 @@ else:

SALE: %s

Use the order form below to place an order.

-
+ How many would you like?