From 3cae9d7442a67a174d06b7eef77d478db0620bd0 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 3 Mar 2010 11:28:51 -0600 Subject: [PATCH] Fix 2 tanks bugs --- build/lib/ctf/__init__.py | 0 build/lib/ctf/flagger.py | 33 --- build/lib/ctf/html.py | 35 --- build/lib/ctf/paths.py | 6 - build/lib/ctf/pointscli.py | 40 --- build/lib/ctf/teams.py | 72 ----- build/lib/tanks/Function.py | 46 ---- build/lib/tanks/GameMath.py | 206 --------------- build/lib/tanks/Pflanzarr.py | 399 ---------------------------- build/lib/tanks/Program.py | 234 ----------------- build/lib/tanks/Tank.py | 479 ---------------------------------- build/lib/tanks/__init__.py | 0 build/lib/tanks/actions.py | 126 --------- build/lib/tanks/conditions.py | 126 --------- build/lib/tanks/docs.py | 26 -- build/lib/tanks/setup.py | 72 ----- tanks/GameMath.py | 23 +- 17 files changed, 5 insertions(+), 1918 deletions(-) delete mode 100644 build/lib/ctf/__init__.py delete mode 100644 build/lib/ctf/flagger.py delete mode 100644 build/lib/ctf/html.py delete mode 100644 build/lib/ctf/paths.py delete mode 100644 build/lib/ctf/pointscli.py delete mode 100644 build/lib/ctf/teams.py delete mode 100644 build/lib/tanks/Function.py delete mode 100644 build/lib/tanks/GameMath.py delete mode 100644 build/lib/tanks/Pflanzarr.py delete mode 100644 build/lib/tanks/Program.py delete mode 100644 build/lib/tanks/Tank.py delete mode 100644 build/lib/tanks/__init__.py delete mode 100644 build/lib/tanks/actions.py delete mode 100644 build/lib/tanks/conditions.py delete mode 100644 build/lib/tanks/docs.py delete mode 100644 build/lib/tanks/setup.py diff --git a/build/lib/ctf/__init__.py b/build/lib/ctf/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/build/lib/ctf/flagger.py b/build/lib/ctf/flagger.py deleted file mode 100644 index 04a9fa8..0000000 --- a/build/lib/ctf/flagger.py +++ /dev/null @@ -1,33 +0,0 @@ -#! /usr/bin/python - -import asynchat -import asyncore -import socket - -class Flagger(asynchat.async_chat): - """Use to connect to flagd and submit the current flag holder.""" - - def __init__(self, addr, auth): - asynchat.async_chat.__init__(self) - self.create_socket(socket.AF_INET, socket.SOCK_STREAM) - self.connect((addr, 1)) - self.push(auth + '\n') - self.flag = None - - def handle_read(self): - # We don't care. - msg = self.recv(4096) - - def handle_error(self): - # If we lose the connection to flagd, nobody can score any - # points. Terminate everything. - asyncore.close_all() - asynchat.async_chat.handle_error(self) - - def set_flag(self, team): - if team: - eteam = team.encode('utf-8') - else: - eteam = '' - self.push(eteam + '\n') - self.flag = team diff --git a/build/lib/ctf/html.py b/build/lib/ctf/html.py deleted file mode 100644 index 101c4d7..0000000 --- a/build/lib/ctf/html.py +++ /dev/null @@ -1,35 +0,0 @@ -#! /usr/bin/python - -import os -import string -import sys -from codecs import open - -from paths import * - -template_fn = os.path.join(LIB, 'template.html') -template = string.Template(open(template_fn, encoding='utf-8').read()) - -base = BASE_URL -css = base + 'ctf.css' - -def substitute(title, body, base=base, hdr='', body_class='', onload='', links=''): - return template.substitute(title=title, - hdr=hdr, - body_class=body_class, - base=base, - links=links, - onload=onload, - body=body) - -def serve(title, body, **kwargs): - out = substitute(title, body, **kwargs) - print 'Content-type: text/html' - print 'Content-length: %d' % len(out) - print - sys.stdout.write(out) - sys.stdout.flush() - -def write(filename, title, body, **kwargs): - f = open(filename, 'w', encoding='utf-8') - f.write(substitute(title, body, **kwargs)) diff --git a/build/lib/ctf/paths.py b/build/lib/ctf/paths.py deleted file mode 100644 index 54aa86f..0000000 --- a/build/lib/ctf/paths.py +++ /dev/null @@ -1,6 +0,0 @@ -VAR = "/opt/ctf/var" -WWW = "/opt/ctf/www" -LIB = "/opt/ctf/lib" -BIN = "/opt/ctf/bin" -SBIN = "/opt/ctf/sbin" -BASE_URL = "/" diff --git a/build/lib/ctf/pointscli.py b/build/lib/ctf/pointscli.py deleted file mode 100644 index 5671ebc..0000000 --- a/build/lib/ctf/pointscli.py +++ /dev/null @@ -1,40 +0,0 @@ -#! /usr/bin/python - -from urllib import quote -import teams -import time -import os -import paths - -pointsdir = os.path.join(paths.VAR, 'points') - -def award(cat, team, points): - if not team: - team = teams.house - now = time.strftime('%Y-%m-%dT%H:%M:%S') - pid = os.getpid() - qcat = quote(cat, '') - qteam = quote(team, '') - basename = '%s.%d.%s.%s' % (now, pid, qcat, qteam) - # FAT can't handle : - basename = basename.replace(':', '.') - tmpfn = os.path.join(pointsdir, 'tmp', basename) - curfn = os.path.join(pointsdir, 'cur', basename) - f = open(tmpfn, 'w') - f.write('%s\t%s\t%s\t%d\n' % (now, cat, team, points)) - f.close() - os.rename(tmpfn, curfn) - -def main(): - import optparse - - p = optparse.OptionParser('%prog CATEGORY TEAM POINTS') - opts, args = p.parse_args() - if len(args) != 3: - p.error('Wrong number of arguments') - cat, team, points = args - points = int(points) - award(cat, team, points) - -if __name__ == '__main__': - main() diff --git a/build/lib/ctf/teams.py b/build/lib/ctf/teams.py deleted file mode 100644 index 3e9407c..0000000 --- a/build/lib/ctf/teams.py +++ /dev/null @@ -1,72 +0,0 @@ -#! /usr/bin/python - -import fcntl -import time -import os -from urllib import quote, unquote -import paths - -house = 'dirtbags' -passwdfn = os.path.join(paths.VAR, 'passwd') -team_colors = ['F0888A', '88BDF0', '00782B', '999900', 'EF9C00', - 'F4B5B7', 'E2EFFB', '89CA9D', 'FAF519', 'FFE7BB', - 'BA88F0', '8DCFF4', 'BEDFC4', 'FFFAB2', 'D7D7D7', - 'C5B9D7', '006189', '8DCB41', 'FFCC00', '898989'] - -teams = {} -built = 0 -def build_teams(): - global teams, built - if not os.path.exists(passwdfn): - return - if os.path.getmtime(passwdfn) <= built: - return - - teams = {} - try: - f = open(passwdfn) - for line in f: - line = line.strip() - if not line: - continue - team, passwd, color = map(unquote, line.strip().split('\t')) - teams[team] = (passwd, color) - except IOError: - pass - built = time.time() - -def validate(team): - build_teams() - -def chkpasswd(team, passwd): - validate(team) - if teams.get(team, [None, None])[0] == passwd: - return True - else: - return False - -def exists(team): - validate(team) - if team == house: - return True - return team in teams - -def add(team, passwd): - build_teams() - color = team_colors[len(teams)%len(team_colors)] - - assert team not in teams, "Team already exists." - - f = open(passwdfn, 'a') - fcntl.lockf(f, fcntl.LOCK_EX) - f.seek(0, 2) - f.write('%s\t%s\t%s\n' % (quote(team, ''), - quote(passwd, ''), - quote(color, ''))) - -def color(team): - validate(team) - t = teams.get(team) - if not t: - return '888888' - return t[1] diff --git a/build/lib/tanks/Function.py b/build/lib/tanks/Function.py deleted file mode 100644 index 406e564..0000000 --- a/build/lib/tanks/Function.py +++ /dev/null @@ -1,46 +0,0 @@ -import math - -class Function(object): - """Represents a single condition or action. This doc string is printed - as user documentation. You should override it to say something useful.""" - - def __call__(self, tank): - """The __call__ method should be of this basic form. Actions - should return None, conditions should return True or False. Actions - should utilize the set* methods of tanks. Conditions can utilize the - tanks get* methods.""" - pass - - def _limitArgs(self, args, max): - """Raises a ValueError if there are more than max args.""" - if len(args) > max: - raise ValueError("Too many arguments: %s" % ','.join(args)) - - def _checkRange(self, value, name, min=0, max=100): - """Check that the value is in the given range. - Raises an exception with useful info for invalid values. Name is used to - let the user know which value is wrong.""" - try: - value = int(value) - except: - raise ValueError("Invalid %s value: %s" % (name, value)) - assert value >= min and value <= max, "Invalid %s. %ss must be in"\ - " the %s %d-%d" % \ - (name, name.capitalize(), value, min, max) - - return value - - def _convertAngle(self, value, name): - """Parse the given value as an angle in degrees, and return its value - in radians. Raise useful errors. - Name is used in the errors to describe the field.""" - try: - angle = math.radians(value) - except: - raise ValueError("Invalid %s value: %s" % (name, value)) - - assert angle >= 0 and angle < 2*math.pi, "Invalid %s; "\ - "It be in the range 0 and 359." % name - - return angle - diff --git a/build/lib/tanks/GameMath.py b/build/lib/tanks/GameMath.py deleted file mode 100644 index 481bf81..0000000 --- a/build/lib/tanks/GameMath.py +++ /dev/null @@ -1,206 +0,0 @@ -import math - -def rotatePoint(point, angle): - """Assuming 0,0 is the center, rotate the given point around it.""" - - x,y = point - r = math.sqrt(x**2 + y**2) - if r == 0: - return 0, 0 - - theta = math.acos(x/r) - if y < 0: - theta = -theta - theta = theta + angle - return int(round(r*math.cos(theta))), int(round(r*math.sin(theta))) - -def rotatePoly(points, angle): - """Rotate the given list of points around 0,0 by angle.""" - return [ rotatePoint(point, angle) for point in points ] - -def displace(point, disp, limits): - """Displace point by disp, wrapping around limits.""" - x = (point[0] + disp[0]) - while x >= limits[0]: - x = x - limits[0] - while x < 0: - x = x + limits[0] - - y = (point[1] + disp[1]) - while y >= limits[1]: - y = y - limits[1] - while y < 0: - y = y + limits[1] - - return x,y - -def displacePoly(points, disp, limits, coordSequence=False): - """Displace each point (x,y) in 'points' by 'disp' (x,y). The limits of - the drawing space are assumed to be at x=0, y=0 and x=limits[0], - y=limits[1]. If the poly overlaps the edge of the drawing space, the - poly is duplicated on each side. -@param coordSequence: If true, the coordinates are returned as a sequence - - x1, y1, x2, y2, ... This is need by some PIL drawing - commands. -@returns: A list of polys, displaced by disp - """ - xDup = 0; yDup = 0 - maxX, maxY = limits - basePoints = [] - for point in points: - x,y = int(point[0] + disp[0]), int(point[1] + disp[1]) - - # Check if duplication is needed on each axis - if x > maxX: - # If this is negative, then we need to duplicate in the negative - # direction. - xDup = -1 - elif x < 0: - xDup = 1 - - if y > maxY: - yDup = -1 - elif y < 0: - yDup = 1 - - basePoints.append( (x,y) ) - - polys = [basePoints] - if xDup: - polys.append([(x + maxX*xDup, y) for x,y in basePoints] ) - if yDup: - polys.append([(x, maxY*yDup + y) for x,y in basePoints] ) - if xDup and yDup: - polys.append([(x+maxX*xDup, maxY*yDup+y) for x,y in basePoints]) - - # Switch coordinates to sequence mode. - # (x1, y1, x2, y2) instead of ((x1, y1), (x2, y2)) - if coordSequence: - seqPolys = [] - for poly in polys: - points = [] - for point in poly: - points.extend(point) - seqPolys.append(points) - polys = seqPolys - - return polys - -def polar2cart(r, theta): - """Return the cartesian coordinates for r, theta.""" - x = r*math.cos(theta) - y = r*math.sin(theta) - return x,y - -def minShift(center, point, limits): - """Get the minimum distances between the two points, given that the board - wraps at the givin limits.""" - dx = point[0] - center[0] - if dx < -limits[0]/2.0: - dx = point[0] + limits[0] - center[0] - elif dx > limits[0]/2.0: - dx = point[0] - (center[0] + limits[0]) - - dy = point[1] - center[1] - if dy < - limits[1]/2.0: - dy = point[1] + limits[1] - center[1] - elif dy > limits[1]/2.0: - dy = point[1] - (limits[1] + center[1]) - - return dx, dy - -def relativePolar(center, point, limits): - """Returns the angle, from zero, to the given point assuming this -center is the origin. Take into account wrapping round the limits of the board. -@returns: r, theta - """ - - dx, dy = minShift(center, point, limits) - - r = math.sqrt(dx**2 + dy**2) - theta = math.acos(dx/r) - if dy < 0: - theta = 2*math.pi - theta - - return r, theta - -def reduceAngle(angle): - """Reduce the angle such that it is in 0 <= angle < 2pi""" - - while angle >= math.pi*2: - angle = angle - math.pi*2 - while angle < 0: - angle = angle + math.pi*2 - - return angle - -def angleDiff(angle1, angle2): - """Returns the difference between the two angles. They are assumed -to be in radians, and must be in the range 0 <= angle < 2*pi. -@raises AssertionError: The angles given must be in the range 0 <= angle < 2pi -@returns: The minimum distance between the two angles; The distance - is negative if angle2 leads angle1 (clockwise).. - """ - - for angle in angle1, angle2: - assert angle < 2*math.pi and angle >= 0, \ - 'angleDiff: bad angle %s' % angle - - diff = angle2 - angle1 - if diff > math.pi: - diff = diff - 2*math.pi - elif diff < -math.pi: - diff = diff + 2*math.pi - - return diff - -def getDist(point1, point2): - """Returns the distance between point1 and point2.""" - dx = point2[0] - point1[0] - dy = point2[1] - point1[1] - - return math.sqrt(dx**2 + dy**2) - -def segmentCircleCollision(segment, center, radius): - """Return True if the given circle touches the given line segment. -@param segment: A list of two points [(x1,y1), (x2, y2)] that define - the line segment. -@param center: The center point of the circle. -@param radius: The radius of the circle. -@returns: True if the the circle touches the line segment, False otherwise. - """ - - a = getDist(segment[0], center) - c = getDist(segment[1], center) - base = getDist(segment[0], segment[1]) - - # If we're close enough to the end points, then we're close - # enough to the segment. - if a < radius or c < radius: - return True - - # First we find the are of the triangle formed by the line segment - # and point. I use Heron's formula for the area. Using this, we'll - # find the distance d from the point to the line. We'll later make - # sure that the collision is with the line segment, and not just the - # line. - s = (a + c + base)/2 - A = math.sqrt(s*(s - a)*(s - c)*(s - base)) - d = 2*A/base - -# print s, a, c, A, d, radius - - # If the distance from the point to the line is more than the - # target radius, this isn't a hit. - if d > radius: - return False - - # If the distance from an endpoint to the intersection between - # our line segment and the line perpendicular to it that passes through - # the point is longer than the line segment, then this isn't a hit. - elif math.sqrt(a**2 - d**2) > base or \ - math.sqrt(c**2 - d**2) > base: - return False - else: - # The triangle is acute, that means we're close enough. - return True diff --git a/build/lib/tanks/Pflanzarr.py b/build/lib/tanks/Pflanzarr.py deleted file mode 100644 index 18a54fe..0000000 --- a/build/lib/tanks/Pflanzarr.py +++ /dev/null @@ -1,399 +0,0 @@ -import fcntl -import math -import os -import random -import cgi -from sets import Set as set -from ctf import teams, html, paths -from cStringIO import StringIO - -from urllib import unquote, quote - -import Tank - -class NotEnoughPlayers(Exception): - pass - -class Pflanzarr: - SPACING = 150 - - def __init__(self, dir): - """Initialize a new game of Pflanzarr. -@param dir: The data directory.""" - - # Setup the game environment - self._setupDirectories(dir) - - # Figure out what game number this is. - self.gameNum = self._getGameNum() - self.gameFilename = os.path.join(self._resultsDir, '%04d.html' % self.gameNum) - - tmpPlayers = os.listdir(self._playerDir) - players = [] - for p in tmpPlayers: - p = unquote(p) - if (not (p.startswith('.') - or p.endswith('#') - or p.endswith('~')) - and teams.exists(p)): - players.append(p) - - AIs = {} - for player in players: - AIs[player] = open(os.path.join(self._playerDir, player)).read() - defaultAIs = self._getDefaultAIs(dir) - - if len(players) < 1: - raise NotEnoughPlayers() - - # The one is added to ensure that there is at least one house - # bot. - cols = math.sqrt(len(players) + 1) - if int(cols) != cols: - cols = cols + 1 - - cols = int(cols) - cols = max(cols, 2) - - rows = len(players)/cols - if len(players) % cols != 0: - rows = rows + 1 - rows = max(rows, 2) - - self._board = (cols*self.SPACING, rows*self.SPACING) - - while len(players) < cols*rows: - players.append(None) - - self._tanks = [] - for i in range(cols): - for j in range(rows): - startX = i*self.SPACING + self.SPACING/2 - startY = j*self.SPACING + self.SPACING/2 - player = random.choice(players) - players.remove(player) - color = '#' + teams.color(player) - tank = Tank.Tank( player, (startX, startY), color, - self._board, testMode=True) - if player == None: - tank.program(random.choice(defaultAIs)) - else: - tank.program(AIs[player]) - self._tanks.append(tank) - - # We only want to make these once, so we do it here. - self._tanksByX = list(self._tanks) - self._tanksByY = list(self._tanks) - - self._deadTanks = set() - - def run(self, maxTurns=None): - kills = {} - for tank in self._tanks: - kills[tank] = set() - - # Open HTML output - hdr = StringIO() - hdr.write('\n' - '\n') - - # Decide on the winner - winner = self._chooseWinner(kills) - self.winner = winner.name - - # Now generate HTML body - body = StringIO() - body.write(' \n' % self._board) - body.write(' Sorry, you need an HTML5-capable browser to see this.\n' - ' \n' - '

\n') - if self.gameNum > 0: - body.write(' ← Prev |' % - (self.gameNum - 1)) - body.write(' Next → |' % - (self.gameNum + 1)) - body.write(' 0 fps\n' - '

\n' - ' \n' - ' \n' - ' \n' - ' \n' - ' \n' - ' \n') - - tanks = self._tanks[:] - tanks.remove(winner) - tanks[0:0] = [winner] - for tank in tanks: - if tank is winner: - rowStyle = ('style="font-weight: bold; ' - 'color: #000; ' - 'background-color: %s;"' % tank.color) - else: - rowStyle = 'style="background-color:%s; color: #000;"' % tank.color - if tank.name: - name = cgi.escape(tank.name) - else: - name = teams.house - body.write('' % - (rowStyle, - name, - len(kills[tank]), - cgi.escape(tank.deathReason))) - body.write('
TeamKillsCause of Death
%s%d%s
\n') - - # Write everything out - html.write(self.gameFilename, - 'Tanks round %d' % self.gameNum, - body.getvalue(), - hdr=hdr.getvalue(), - onload='start(turns);') - - - - def _killTanks(self, tanks, reason): - for tank in tanks: - if tank in self._tanksByX: - self._tanksByX.remove(tank) - if tank in self._tanksByY: - self._tanksByY.remove(tank) - - tank.die(reason) - - self._deadTanks = self._deadTanks.union(tanks) - - def _chooseWinner(self, kills): - """Choose a winner. In case of a tie, live tanks prevail, in case - of further ties, a winner is chosen at random. This outputs the winner - to the winners file and outputs a results table html file.""" - tanks = list(self._tanks) - def winSort(t1, t2): - """Sort by # of kill first, then by life status.""" - result = cmp(len(kills[t1]), len(kills[t2])) - if result != 0: - return result - - if t1.isDead and not t2.isDead: - return -1 - elif not t1.isDead and t2.isDead: - return 1 - else: - return 0 - tanks.sort(winSort) - tanks.reverse() - - # Get the list of potential winners - winners = [] - for i in range(len(tanks)): - if len( kills[tanks[0]] ) == len( kills[tanks[i]] ) and \ - tanks[0].isDead == tanks[i].isDead: - winners.append(tanks[i]) - else: - break - winner = random.choice(winners) - return winner - - - def _outputErrors(self, tank): - """Output errors for each team.""" - if tank.name == None: - return - - if tank._program.errors: - print tank.name, 'has errors' - - - fileName = os.path.join(self._errorDir, quote(tank.name, '')) - file = open(fileName, 'w') - for error in tank._program.errors: - file.write(error) - file.write('\n') - file.close() - - def _getNear(self): - """A dictionary of the set of tanks nearby each tank. Nearby is - defined as within the square centered the tank with side length equal - twice the sensor range. Only a few tanks within the set (those in the - corners of the square) should be outside the sensor range.""" - - self._tanksByX.sort(lambda t1, t2: cmp(t1.pos[0], t2.pos[0])) - self._tanksByY.sort(lambda t1, t2: cmp(t1.pos[1], t2.pos[1])) - - nearX = {} - nearY = {} - for tank in self._tanksByX: - nearX[tank] = set() - nearY[tank] = set() - - numTanks = len(self._tanksByX) - offset = 1 - for index in range(numTanks): - cTank = self._tanksByX[index] - maxRange = cTank.SENSOR_RANGE + cTank.RADIUS + 1 - near = set([cTank]) - for i in [(j + index) % numTanks for j in range(1, offset)]: - near.add(self._tanksByX[i]) - while offset < numTanks: - nTank = self._tanksByX[(index + offset) % numTanks] - if (index + offset >= numTanks and - self._board[0] + nTank.pos[0] - cTank.pos[0] < maxRange): - near.add(nTank) - offset = offset + 1 - elif (index + offset < numTanks and - nTank.pos[0] - cTank.pos[0] < maxRange ): - near.add(nTank) - offset = offset + 1 - else: - break - - if offset > 1: - offset = offset - 1 - - for tank in near: - nearX[tank] = nearX[tank].union(near) - - offset = 1 - for index in range(numTanks): - cTank = self._tanksByY[index] - maxRange = cTank.SENSOR_RANGE + cTank.RADIUS + 1 - near = set([cTank]) - for i in [(j + index) % numTanks for j in range(1, offset)]: - near.add(self._tanksByY[i]) - while offset < numTanks: - nTank = self._tanksByY[(index + offset) % numTanks] - if (index + offset < numTanks and - nTank.pos[1] - cTank.pos[1] < maxRange): - near.add(nTank) - offset = offset + 1 - elif (index + offset >= numTanks and - self._board[1] + nTank.pos[1] - cTank.pos[1] < maxRange): - near.add(nTank) - offset = offset + 1 - else: - break - - if offset > 1: - offset = offset - 1 - - for tank in near: - nearY[tank] = nearY[tank].union(near) - - near = {} - for tank in self._tanksByX: - near[tank] = nearX[tank].intersection(nearY[tank]) - near[tank].remove(tank) - - return near - - def _setupDirectories(self, dir): - """Setup all the directories needed by the game.""" - - if not os.path.exists(dir): - os.mkdir(dir) - - self._dir = dir - - # Don't run more than one game at the same time. - self._lockFile = open(os.path.join(dir, '.lock'), 'a') - try: - fcntl.flock(self._lockFile, fcntl.LOCK_EX|fcntl.LOCK_NB) - except: - sys.exit(1) - - # Setup all the directories we'll need. - self._resultsDir = os.path.join(dir, 'results') - self._errorDir = os.path.join(dir, 'errors') - self._playerDir = os.path.join(dir, 'ai', 'players') - - def _getDefaultAIs(self, basedir): - """Load all the house bot AIs.""" - defaultAIs = [] - - path = os.path.join(basedir, 'ai', 'house') - files = os.listdir(path) - for fn in files: - if fn.startswith('.'): - continue - - fn = os.path.join(path, fn) - file = open(fn) - defaultAIs.append(file.read()) - - return defaultAIs - - def _getGameNum(self): - """Figure out what game number this is from the past games played.""" - - games = os.listdir(self._resultsDir) - games.sort() - if games: - fn = games[-1] - s, _ = os.path.splitext(fn) - return int(s) + 1 - else: - return 0 - -if __name__ == '__main__': - import sys, traceback - try: - p = Pflanzarr(sys.argv[1]) - p.run(int(sys.argv[3])) - except: - traceback.print_exc() - print "Usage: Pflanzarr.py dataDirectory #turns" - - diff --git a/build/lib/tanks/Program.py b/build/lib/tanks/Program.py deleted file mode 100644 index cab13ab..0000000 --- a/build/lib/tanks/Program.py +++ /dev/null @@ -1,234 +0,0 @@ -"""

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 features such -as comments (Started by a #, ended at EOL), logic, versatility, and -semi-colons (all lines must end in one). As with all new military systems -it utilizes only integers; we must never rest in our -diligence against the communist floating point conspiracy. Whitespace is -provided by trusted contractors, and should never interfere with operations. -

-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 traceback -import conditions -import actions -import setup - -class Statement(object): - """Represents a single program statement. If all the condition Functions - evaluate to True, the actions are all executed in order.""" - - def __init__(self, lineNum, line, conditions, actions): - self.lineNum = lineNum - self.line = line - self._conditions = conditions - self._actions = actions - - def __call__(self, tank): - success = True - for condition in self._conditions: - if not condition(tank): - success = False - break - - if success: - for action in self._actions: - action(tank) - -class Program(object): - """This parses and represents a Tank program.""" - CONDITION_SEP = '&' - ACTION_SEP = '.' - - def __init__(self, text): - """Initialize this program, parsing the given text.""" - self.errors = [] - - self._program, self._setup = self._parse(text) - - def setup(self, tank): - """Execute all the setup actions.""" - for action in self._setup: - try: - action(tank) - except Exception, msg: - self.errors.append("Bad setup action, line %d, msg: %s" % \ - (action.lineNum, msg)) - - def __call__(self, tank): - """Execute this program on the given tank.""" - for statement in self._program: - try: - statement(tank) - except Exception, msg: - traceback.print_exc() - self.errors.append('Error executing program. \n' - '(%d) - %s\n' - 'msg: %s\n' % - (statement.lineNum, statement.line, msg) ) - - def _parse(self, text): - """Parse the text of the given program.""" - program = [] - setup = [] - inSetup = True - lines = text.split(';') - lineNum = 0 - for line in lines: - lineNum = lineNum + 1 - - originalLine = line - - # Remove Comments - parts = line.split('\n') - for i in range(len(parts)): - comment = parts[i].find('#') - if comment != -1: - parts[i] = parts[i][:comment] - # Remove all whitespace - line = ''.join(parts) - line = line.replace('\r', '') - line = line.replace('\t', '') - line = line.replace(' ', '') - - if line == '': - continue - - if line.startswith('>'): - if inSetup: - if '>' in line[1:] or ':' in line: - self.errors.append('(%d) Missing semicolon: %s' % - (lineNum, line)) - continue - - try: - setupAction = self._parseSection(line[1:], 'setup')[0] - setupAction.lineNum = lineNum - setup.append(setupAction) - except Exception, msg: - self.errors.append('(%d) Error parsing setup line: %s' - '\nThe error was: %s' % - (lineNum, originalLine, msg)) - - continue - else: - self.errors.append('(%d) Setup lines aren\'t allowed ' - 'after the first command: %s' % - (lineNum, originalLine)) - else: - # We've hit the first non-blank, non-comment, non-setup - # line - inSetup = False - - semicolons = line.count(':') - if semicolons > 1: - self.errors.append('(%d) Missing semicolon: %s' % - (lineNum, line)) - continue - elif semicolons == 1: - conditions, actions = line.split(':') - else: - self.errors.append('(%d) Invalid Line, no ":" seperator: %s'% - (lineNum, line) ) - - try: - conditions = self._parseSection(conditions, 'condition') - except Exception, msg: - self.errors.append('(%d) %s - "%s"' % - (lineNum, msg, line) ) - continue - - try: - actions = self._parseSection(actions, 'action') - except Exception, msg: - self.errors.append('(%d) %s - "%s"' % - (lineNum, msg, originalLine) ) - continue - program.append(Statement(lineNum, line, conditions, actions)) - - return program, setup - - def _parseSection(self, section, sectionType): - """Parses either the action or condition section of each command. -@param section: The text of the section of the command to be parsed. -@param sectionType: The type of section to be parsed. Should be: - 'condition', 'action', or 'setup'. -@raises ValueError: Raises ValueErrors when parsing errors occur. -@returns: Returns a list of parsed section components (Function objects). - """ - - if sectionType == 'condition': - parts = section.split(self.CONDITION_SEP) - functions = conditions.conditions - if section == '': - return [] - elif sectionType == 'action': - parts = section.split(self.ACTION_SEP) - functions = actions.actions - if section == '': - raise ValueError("The action section cannot be empty.") - elif sectionType == 'setup': - parts = [section] - functions = setup.setup - else: - raise ValueError('Invalid section Type - Contact Contest Admin') - - parsed = [] - for part in parts: - - pos = part.find('(') - if pos == -1: - raise ValueError("Missing open paren in %s: %s" % - (sectionType, part) ) - funcName = part[:pos] - - if funcName not in functions: - raise ValueError("%s function %s is not accepted." % - (sectionType.capitalize(), funcName) ) - - if part[-1] != ')': - raise ValueError("Missing closing paren in %s: %s" % - (condition, sectionType) ) - - args = part[pos+1:-1] - if args != '': - args = args.split(',') - for i in range(len(args)): - args[i] = int(args[i]) - else: - args = [] - - parsed.append(functions[funcName](*args)) - - return parsed diff --git a/build/lib/tanks/Tank.py b/build/lib/tanks/Tank.py deleted file mode 100644 index 1c9b32e..0000000 --- a/build/lib/tanks/Tank.py +++ /dev/null @@ -1,479 +0,0 @@ -import math -import random -from sets import Set as set - -import GameMath as gm -import Program - -class Tank(object): - - # How often, in turns, that we can fire. - FIRE_RATE = 20 - # How far the laser shoots from the center of the tank - FIRE_RANGE = 45.0 - # The radius of the tank, from the center of the turret. - # This is what is used for collision and hit detection. - RADIUS = 7.5 - # Max speed, in pixels - SPEED = 7.0 - # Max acceleration, as a fraction of speed. - ACCEL = 35 - # Sensor range, in pixels - SENSOR_RANGE = 90.0 - # Max turret turn rate, in radians - TURRET_TURN_RATE = math.pi/10 - - # The max number of sensors/timers/toggles - SENSOR_LIMIT = 10 - - def __init__(self, name, pos, color, boardSize, angle=None, tAngle=None, - testMode=True): - """Create a new tank. -@param name: The name name of the tank. Stored in self.name. -@param pos: The starting position of the tank (x,y) -@param color: The color of the tank. -@param boardSize: The size of the board. (maxX, maxY) -@param angle: The starting angle of the tank, defaults to random. -@param tAngle: The starting turretAngle of the tank, defaults to random. -@param testMode: When True, extra debugging information is displayed. Namely, - arcs for each sensor are drawn, which turn white when - activated. - """ - - # Keep track of what turn number it is for this tank. - self._turn = 0 - - self.name = name - self._testMode = testMode - - assert len(pos) == 2 and pos[0] > 0 and pos[1] > 0, \ - 'Bad starting position: %s' % str(pos) - self.pos = pos - - # The last speed of each tread (left, right) - self._lastSpeed = 0.0, 0.0 - # The next speed that the tank should try to attain. - self._nextMove = 0,0 - - # When set, the led is drawn on the tank. - self.led = False - - assert len(boardSize) == 2 and boardSize[0] > 0 and boardSize[1] > 0 - # The limits of the playfield (maxX, maxY) - self._limits = boardSize - - # The current angle of the tank. - if angle is None: - self._angle = random.random()*2*math.pi - else: - self._angle = angle - - # The current angle of the turret - if tAngle is None: - self._tAngle = random.random()*2*math.pi - else: - self._tAngle = tAngle - - self.color = color - - # You can't fire until fireReady is 0. - self._fireReady = self.FIRE_RATE - # Means the tank will fire at it's next opportunity. - self._fireNow = False - # True when the tank has fired this turn (for drawing purposes) - self._fired = False - - # What the desired turret angle should be (from the front of the tank). - # None means the turret should stay stationary. - self._tGoal = None - - # Holds the properties of each sensor - self._sensors = [] - # Holds the state of each sensor - self._sensorState = [] - - # The tanks toggle memory - self.toggles = [] - - # The tanks timers - self._timers = [] - - # Is this tank dead? - self.isDead = False - # The frame of the death animation. - self._deadFrame = 10 - # Death reason - self.deathReason = 'survived' - - def __repr__(self): - return '' % (self.name, self.pos[0], self.pos[1]) - - def get_turn(self): - return self._turn - turn = property(get_turn) - - def fire(self, near): - """Shoots, if ordered to and able. Returns the set of tanks - destroyed.""" - - killed = set() - if self._fireReady > 0: - # Ignore the shoot order - self._fireNow = False - - if self._fireNow: - self._fireNow = False - self._fireReady = self.FIRE_RATE - self._fired = True - - - firePoint = gm.polar2cart(self.FIRE_RANGE, - self._angle + self._tAngle) - for tank in near: - enemyPos = gm.minShift(self.pos, tank.pos, self._limits) - if gm.segmentCircleCollision(((0,0), firePoint), enemyPos, - self.RADIUS): - killed.add(tank) - else: - self._fired = False - - return killed - - def addSensor(self, range, angle, width, attachedTurret=False): - """Add a sensor to this tank. -@param angle: The angle, from the tanks front and going clockwise, - of the center of the sensor. (radians) -@param width: The width of the sensor (percent). -@param range: The range of the sensor (percent) -@param attachedTurret: If set, the sensor moves with the turret. - """ - assert range >=0 and range <= 1, "Invalid range value." - - if len(self._sensors) >= self.SENSOR_LIMIT: - raise ValueError("You can only have 10 sensors.") - - range = range * self.SENSOR_RANGE - - if attachedTurret: - attachedTurret = True - else: - attachedTurret = False - - self._sensors.append((range, angle, width, attachedTurret)) - self._sensorState.append(False) - - def getSensorState(self, key): - return self._sensorState[key] - - def setMove(self, left, right): - """Parse the speed values given, and set them for the next move.""" - - self._nextMove = left, right - - def setTurretAngle(self, angle=None): - """Set the desired angle of the turret. No angle means the turret - should remain stationary.""" - - if angle is None: - self._tGoal = None - else: - self._tGoal = gm.reduceAngle(angle) - - def setFire(self): - """Set the tank to fire, next turn.""" - self._fireNow = True - - def fireReady(self): - """Returns True if the tank can fire now.""" - return self._fireReady == 0 - - def addTimer(self, period): - """Add a timer with timeout period 'period'.""" - - if len(self._timers) >= self.SENSOR_LIMIT: - raise ValueError('You can only have 10 timers') - - self._timers.append(None) - self._timerPeriods.append(period) - - def resetTimer(self, key): - """Reset, and start the given timer, but only if it is inactive. - If it is active, raise a ValueError.""" - if self._timer[key] is None: - self._timer[key] = self._timerPeriods[key] - else: - raise ValueError("You can't reset an active timer (#%d)" % key) - - def clearTimer(self, key): - """Clear the timer.""" - self._timer[key] = None - - def checkTimer(self, key): - """Returns True if the timer has expired.""" - return self._timer[key] == 0 - - def _manageTimers(self): - """Decrement each active timer.""" - for i in range(len(self._timers)): - if self._timers[i] is not None and \ - self._timers[i] > 0: - self._timers[i] = self._timers[i] - 1 - - def program(self, text): - """Set the program for this tank.""" - - self._program = Program.Program(text) - self._program.setup(self) - - def execute(self): - """Execute this tanks program.""" - - # Decrement the active timers - self._manageTimers() - self.led = False - - self._program(self) - - self._move(self._nextMove[0], self._nextMove[1]) - self._moveTurret() - if self._fireReady > 0: - self._fireReady = self._fireReady - 1 - - self._turn = self._turn + 1 - - def sense(self, near): - """Detect collisions and trigger sensors. Returns the set of - tanks collided with, plus this one. We do both these steps at once - mainly because all the data is available.""" - - near = list(near) - polar = [] - for tank in near: - polar.append(gm.relativePolar(self.pos, tank.pos, self._limits)) - - for sensorId in range(len(self._sensors)): - # Reset the sensor - self._sensorState[sensorId] = False - - dist, sensorAngle, width, tSens = self._sensors[sensorId] - - # Adjust the sensor angles according to the tanks angles. - sensorAngle = sensorAngle + self._angle - # If the angle is tied to the turret, add that too. - if tSens: - sensorAngle = sensorAngle + self._tAngle - - while sensorAngle >= 2*math.pi: - sensorAngle = sensorAngle - 2*math.pi - - for i in range(len(near)): - r, theta = polar[i] - # Find the difference between the object angle and the sensor. - # The max this can be is pi, so adjust for that. - dAngle = gm.angleDiff(theta, sensorAngle) - - rCoord = gm.polar2cart(dist, sensorAngle - width/2) - lCoord = gm.polar2cart(dist, sensorAngle + width/2) - rightLine = ((0,0), rCoord) - leftLine = ((0,0), lCoord) - tankRelPos = gm.minShift(self.pos, near[i].pos, self._limits) - if r < (dist + self.RADIUS): - if abs(dAngle) <= (width/2) or \ - gm.segmentCircleCollision(rightLine, tankRelPos, - self.RADIUS) or \ - gm.segmentCircleCollision(leftLine, tankRelPos, - self.RADIUS): - - self._sensorState[sensorId] = True - break - - # Check for collisions here, since we already have all the data. - collided = set() - for i in range(len(near)): - r, theta = polar[i] - if r < (self.RADIUS + near[i].RADIUS): - collided.add(near[i]) - - # Add this tank (a collision kills both, after all - if collided: - collided.add(self) - - return collided - - def die(self, reason): - self.isDead = True - self.deathReason = reason - - def _moveTurret(self): - if self._tGoal is None or self._tAngle == self._tGoal: - return - - diff = gm.angleDiff(self._tGoal, self._tAngle) - - if abs(diff) < self.TURRET_TURN_RATE: - self._tAngle = self._tGoal - elif diff > 0: - self._tAngle = gm.reduceAngle(self._tAngle - self.TURRET_TURN_RATE) - else: - self._tAngle = gm.reduceAngle(self._tAngle + self.TURRET_TURN_RATE) - - def _move(self, lSpeed, rSpeed): - - assert abs(lSpeed) <= 100, "Bad speed value: %s" % lSpeed - assert abs(rSpeed) <= 100, "Bad speed value: %s" % rSpeed - - # Handle acceleration - if self._lastSpeed[0] < lSpeed and \ - self._lastSpeed[0] + self.ACCEL < lSpeed: - lSpeed = self._lastSpeed[0] + self.ACCEL - elif self._lastSpeed[0] > lSpeed and \ - self._lastSpeed[0] - self.ACCEL > lSpeed: - lSpeed = self._lastSpeed[0] - self.ACCEL - - if self._lastSpeed[1] < rSpeed and \ - self._lastSpeed[1] + self.ACCEL < rSpeed: - rSpeed = self._lastSpeed[1] + self.ACCEL - elif self._lastSpeed[1] > rSpeed and \ - self._lastSpeed[1] - self.ACCEL > rSpeed: - rSpeed = self._lastSpeed[1] - self.ACCEL - - self._lastSpeed = lSpeed, rSpeed - - # The simple case - if lSpeed == rSpeed: - fSpeed = self.SPEED*lSpeed/100 - x = fSpeed*math.cos(self._angle) - y = fSpeed*math.sin(self._angle) - # Adjust our position - self._reposition((x,y), 0) - return - - # The works as follows: - # The tank drives around in a circle of radius r, which is some - # offset on a line perpendicular to the tank. The distance it travels - # around the circle varies with the speed of each tread, and is - # such that each side of the tank moves an equal angle around the - # circle. - L = self.SPEED * lSpeed/100.0 - R = self.SPEED * rSpeed/100.0 - friction = .75 * abs(L-R)/(2.0*self.SPEED) - L = L * (1 - friction) - R = R * (1 - friction) - - # Si is the speed of the tread on the inside of the turn, - # So is the speed on the outside of the turn. - # dir is to note the direction of rotation. - if abs(L) > abs(R): - Si = R; So = L - dir = 1 - else: - Si = L; So = R - dir = -1 - - # The width of the tank... - w = self.RADIUS * 2 - - # This is the angle that will determine the circle the tank travels - # around. -# theta = math.atan((So - Sl)/w) - # This is the distance from the outer tread to the center of the - # circle formed by it's movement. - r = w*So/(So - Si) - - # The fraction of the circle traveled is equal to the speed of - # the outer tread over the circumference of the circle. - # Ft = So/(2*pi*r) - # The angle traveled is equal to the Fraction traveled * 2 * pi - # This reduces to a simple: So/r - # We multiply it by dir to adjust for the direction of rotation - theta = So/r * dir - - # These are the offsets from the center of the circle, given that - # the tank is turned in some direction. The tank is facing - # perpendicular to the circle - # So far everything has been relative to the outer tread. At this - # point, however, we need to move relative to the center of the - # tank. Hence the adjustment in r. - x = -math.cos( self._angle + math.pi/2*dir ) * (r - w/2.0) - y = -math.sin( self._angle + math.pi/2*dir ) * (r - w/2.0) - - # Now we just rotate the tank's position around the center of the - # circle to get the change in coordinates. - mx, my = gm.rotatePoint((x,y), theta) - mx = mx - x - my = my - y - - # Finally, we shift the tank relative to the playing field, and - # rotate it by theta. - self._reposition((mx, my), theta) - - def _reposition(self, move, angleChange): - """Move the tank by x,y = move, and change it's angle by angle. - I assume the tanks move slower than the boardSize.""" - - x = self.pos[0] + move[0] - y = self.pos[1] + move[1] - self._angle = self._angle + angleChange - - if x < 0: - x = self._limits[0] + x - elif x > self._limits[0]: - x = x - self._limits[0] - - if y < 0: - y = self._limits[1] + y - elif y > self._limits[1]: - y = y - self._limits[1] - - self.pos = round(x), round(y) - - while self._angle < 0: - self._angle = self._angle + math.pi * 2 - - while self._angle > math.pi * 2: - self._angle = self._angle - math.pi * 2 - - def draw(self, f): - """Output this tank's state as JSON. - - [color, x, y, angle, turret_angle, led, fired] - - """ - - f.write(' [') - f.write(str(int(self.isDead))); - f.write(',') - f.write(repr(self.color)) - f.write(',') - f.write('%d' % self.pos[0]) - f.write(',') - f.write('%d' % self.pos[1]) - f.write(',') - f.write('%.2f' % self._angle) - f.write(',') - f.write('%.2f' % self._tAngle) - f.write(',') - f.write(str(int(self.led))) - f.write(',') - f.write('%d' % (self._fired and self.FIRE_RANGE) or 0) - if not self.isDead: - f.write(',[') - for i in range(len(self._sensors)): - dist, sensorAngle, width, tSens = self._sensors[i] - - # If the angle is tied to the turret, add that. - if tSens: - sensorAngle = sensorAngle + self._tAngle - - f.write('[') - f.write(str(int(dist))) - f.write(',') - f.write('%.2f' % (sensorAngle - width/2)); - f.write(',') - f.write('%.2f' % (sensorAngle + width/2)); - f.write(',') - f.write(str(int(self._sensorState[i]))) - f.write('],') - f.write(']') - - f.write('],\n') diff --git a/build/lib/tanks/__init__.py b/build/lib/tanks/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/build/lib/tanks/actions.py b/build/lib/tanks/actions.py deleted file mode 100644 index a03e7af..0000000 --- a/build/lib/tanks/actions.py +++ /dev/null @@ -1,126 +0,0 @@ -"""Define new action Functions here. They should inherit from the -Function.Function class. To make an action usable, add it to the -actions dictionary at the end of this file.""" - -import Function - -class Move(Function.Function): - """move(left tread speed, right tread speed) - Set the speeds for the tanks right and left treads. The speeds should - be a number (percent power) between -100 and 100.""" - - def __init__(self, left, right): - self._checkRange(left, 'left tread speed', min=-100) - self._checkRange(right, 'right tread speed', min=-100) - - self._left = left - self._right = right - - def __call__(self, tank): - tank.setMove(self._left, self._right) - -class TurretCounterClockwise(Function.Function): - """turretccw([percent speed]) - Rotate the turret counter clockwise as a percentage of the max speed.""" - def __init__(self, speed=100): - self._checkRange(speed, 'turret percent speed') - self._speed = speed/100.0 - def __call__(self, tank): - tank.setTurretAngle(tank._tAngle - tank.TURRET_TURN_RATE*self._speed) - -class TurretClockwise(Function.Function): - """turretcw([percent speed]) - Rotate the turret clockwise at a rate preportional to speed.""" - - def __init__(self, speed=100): - self._checkRange(speed, 'turret percent speed') - self._speed = speed/100.0 - def __call__(self, tank): - tank.setTurretAngle(tank._tAngle + tank.TURRET_TURN_RATE*self._speed) - -class TurretSet(Function.Function): - """turretset([angle]) - Set the turret to the given angle, in degrees, relative to the front of - the tank. Angles increase counterclockwise. - The angle can be left out; in that case, this locks the turret - to it's current position.""" - - def __init__(self, angle=None): - # Convert the angle to radians - if angle is not None: - angle = self._convertAngle(angle, 'turret angle') - - self._angle = angle - - def __call__(self, tank): - tank.setTurretAngle(self._angle) - -class Fire(Function.Function): - """fire() - Attempt to fire the tanks laser cannon.""" - - def __call__(self, tank): - tank.setFire() - -class SetToggle(Function.Function): - """settoggle(key, state) -Set toggle 'key' to 'state'. -""" - def __init__(self, key, state): - self._key = key - self._state = state - def __call__(self, tank): - tank.toggles[self._key] = self._state - -class Toggle(Function.Function): - """toggle(key) -Toggle the value of toggle 'key'. -""" - def __init__(self, key): - self._key = key - def __call__(self, tank): - try: - tank.toggles[self._key] = not tank.toggles[self._key] - except IndexError: - raise IndexError('Invalid toggle: %d' % self._key) - -class LED(Function.Function): - """led(state) -Set the tanks LED to state (true is on, false is off). -The led is a light that appears behind the tanks turret. -It remains on for a single turn.""" - def __init__(self, state=1): - self._state = state - def __call__(self, tank): - tank.led = self._state - -class StartTimer(Function.Function): - """starttimer(#) -Start (and reset) the given timer, but only if it is inactive. -""" - def __init__(self, key): - self._key = key - def __call__(self, tank): - tank.resetTimer(key) - -class ClearTimer(Function.Function): - """cleartimer(#) -Clear the given timer such that it is no longer active (inactive timers -are always False).""" - def __init__(self, key): - self._key = key - def __call__(self, tank): - tank.clearTimer(self._key) - -### When adding names to this dict, make sure they are lower case and alpha -### numeric. -actions = {'move': Move, - 'turretccw': TurretCounterClockwise, - 'turretcw': TurretClockwise, - 'turretset': TurretSet, - 'fire': Fire, - 'settoggle': SetToggle, - 'toggle': Toggle, - 'led': LED, - 'starttimer': StartTimer, - 'cleartimer': ClearTimer} diff --git a/build/lib/tanks/conditions.py b/build/lib/tanks/conditions.py deleted file mode 100644 index 1401d54..0000000 --- a/build/lib/tanks/conditions.py +++ /dev/null @@ -1,126 +0,0 @@ -"""Define new condition functions here. Add it to the conditions dictionary -at the end to make it usable by Program.Program. These should inherit from -Function.Function.""" - -import Function -import math -import random - -class Sense(Function.Function): - """sense(#, [invert]) - Takes a Sensor number as an argument. - Returns True if the given sensor is currently activated, False otherwise. - If the option argument invert is set to true then logic is inverted, - and then sensor returns True when it is NOT activated, and False when - it is. Invert is false by default. - """ - - def __init__(self, sensor, invert=0): - self._sensor = sensor - self._invert = invert - - def __call__(self, tank): - state = tank.getSensorState(self._sensor) - if self._invert: - return not state - else: - return state - -class Toggle(Function.Function): - """toggle(#) -Returns True if the given toggle is set, False otherwise. """ - def __init__(self, toggle): - self._toggle = toggle - def __call__(self, tank): - return tank.toggles[toggle] - -class TimerCheck(Function.Function): - """timer(#, [invert]) -Checks the state of timer # 'key'. Returns True if time has run out. -If invert is given (and true), then True is returned if the timer has -yet to expire. -""" - def __init__(self, key, invert=0): - self._key = key - self._invert = invert - def __call__(self, tank): - state = tank.checkTimer(self._key) - if invert: - return not state - else: - return state - -class Random(Function.Function): - """random(n,m) - Takes two arguments, n and m. Generates a random number between 1 - and m (inclusive) each time it's checked. If the random number is less - than or equal - to n, then the condition returns True. Returns False otherwise.""" - - def __init__(self, n, m): - self._n = n - self._m = m - - def __call__(self, tank): - if random.randint(1,self._m) <= self._n: - return True - else: - return False - -class Sin(Function.Function): - """sin(T) - A sin wave of period T (in turns). Returns True when the wave is positive. - A wave with period 1 or 2 is always False (it's 0 each turn), only - at periods of 3 or more does this become useful.""" - - def __init__(self, T): - self._T = T - - def __call__(self, tank): - turn = tank.turn - factor = math.pi/self._T - if math.sin(turn * factor) > 0: - return True - else: - return False - -class Cos(Function.Function): - """cos(T) - A cos wave with period T (in turns). Returns True when the wave is - positive. A wave of period 1 is always True. Period 2 is True every - other turn, etc.""" - - def __init__(self, T): - self._T = T - - def __call__(self, tank): - - turn = tank.turn - factor = math.pi/self._T - if math.cos(turn * factor) > 0: - return True - else: - return False - -class FireReady(Function.Function): - """fireready() - True when the tank can fire.""" - def __call__(self, tank): - return tank.fireReady() - -class FireNotReady(Function.Function): - """firenotready() - True when the tank can not fire.""" - def __call__(self, tank): - return not tank.fireReady() - -### When adding names to this dict, make sure they are lower case and alpha -### numeric. -conditions = {'sense': Sense, - 'random': Random, - 'toggle': Toggle, - 'sin': Sin, - 'cos': Cos, - 'fireready': FireReady, - 'firenotready': FireNotReady, - 'timer': TimerCheck } diff --git a/build/lib/tanks/docs.py b/build/lib/tanks/docs.py deleted file mode 100644 index 0318250..0000000 --- a/build/lib/tanks/docs.py +++ /dev/null @@ -1,26 +0,0 @@ -import xml.sax.saxutils - -def mkDocTable(objects): - objects.sort(lambda o1, o2: cmp(o1.__doc__, o2.__doc__)) - - for object in objects: - if object.__doc__ is None: - print '
%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) - - diff --git a/build/lib/tanks/setup.py b/build/lib/tanks/setup.py deleted file mode 100644 index 81a402c..0000000 --- a/build/lib/tanks/setup.py +++ /dev/null @@ -1,72 +0,0 @@ -"""Each of these classes provides a function for configuring a tank. -They should inherit from Function.Function. -To make one available to the tank programmer, add it to the dictionary at -the end of this file.""" - -import Function - -class AddSensor(Function.Function): - """addsensor(range, angle, width, [turretAttached]) -Add a new sensor to the tank. Sensors are an arc (pie slice) centered on -the tank that detect other tanks within their sweep. -A sensor is 'on' if another tank is within this arc. -Sensors are numbered, starting at 0, in the order they are added. -

-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/GameMath.py b/tanks/GameMath.py index 481bf81..b842b80 100644 --- a/tanks/GameMath.py +++ b/tanks/GameMath.py @@ -1,5 +1,7 @@ import math +pi2 = math.pi * 2 + def rotatePoint(point, angle): """Assuming 0,0 is the center, rotate the given point around it.""" @@ -120,19 +122,14 @@ center is the origin. Take into account wrapping round the limits of the board. r = math.sqrt(dx**2 + dy**2) theta = math.acos(dx/r) if dy < 0: - theta = 2*math.pi - theta + theta = pi2 - 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 + return angle % pi2 def angleDiff(angle1, angle2): """Returns the difference between the two angles. They are assumed @@ -142,17 +139,7 @@ to be in radians, and must be in the range 0 <= angle < 2*pi. is negative if angle2 leads angle1 (clockwise).. """ - 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 + return (angle2 - angle1) % pi2 def getDist(point1, point2): """Returns the distance between point1 and point2."""