diff --git a/Makefile b/Makefile
index 3bf5baf..8618ce3 100644
--- a/Makefile
+++ b/Makefile
@@ -14,7 +14,7 @@ MDWNTOHTML = $(CURDIR)/mdwntohtml.py --template=$(TEMPLATE) --base=$(BASE_URL)
default: install
-TARGETS = tanks puzzles
+TARGETS = puzzles
include $(wildcard */*.mk)
CLEAN_TARGETS = $(addsuffix -clean, $(TARGETS))
INSTALL_TARGETS = $(addsuffix -install, $(TARGETS))
@@ -36,22 +36,6 @@ puzzles-install: puzzles-build
puzzles-clean:
rm -rf $(BUILD_DIR)/puzzles $(DESTDIR)$(WWW)/puzzler $(DESTDIR)$(LIB)/puzzler.keys
-tanks-install:
- install --directory $(DESTDIR)$(VAR)/tanks
- install --directory $(DESTDIR)$(VAR)/tanks/results
- install --directory $(DESTDIR)$(VAR)/tanks/errors
- install --directory $(DESTDIR)$(VAR)/tanks/ai
- install --directory $(DESTDIR)$(VAR)/tanks/ai/players
- install --directory $(DESTDIR)$(VAR)/tanks/ai/house
-
- ln -sf $(VAR)/tanks/results $(DESTDIR)$(WWW)/tanks/results
-
- install bin/run-tanks $(DESTDIR)$(SBIN)
-
-tanks-clean:
- rm -rf $(DESTDIR)$(VAR)/tanks
- rm -rf $(DESTDIR)$(WWW)/tanks
-
install: $(INSTALL_TARGETS)
install bin/pointscli $(DESTDIR)$(BIN)
install bin/in.pointsd bin/in.flagd \
diff --git a/setup.py b/setup.py
index 87813d8..45ab699 100755
--- a/setup.py
+++ b/setup.py
@@ -8,4 +8,4 @@ setup(name='ctf',
author='Neale Pickett',
author_email='neale@lanl.gov',
url='http://dirtbags.net/ctf/',
- packages=['ctf', 'tanks'])
+ packages=['ctf'])
diff --git a/tanks/GameMath.py b/tanks/GameMath.py
deleted file mode 100644
index 4af140c..0000000
--- a/tanks/GameMath.py
+++ /dev/null
@@ -1,196 +0,0 @@
-import math
-
-pi2 = math.pi * 2
-
-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 = pi2 - theta
-
- return r, theta
-
-def reduceAngle(angle):
- """Reduce the angle such that it is in 0 <= angle < 2pi"""
-
- return angle % pi2
-
-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)..
- """
-
- ret = (angle2 - angle1) % pi2
- if ret > math.pi:
- ret -= pi2
- return ret
-
-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/Pflanzarr.py b/tanks/Pflanzarr.py
deleted file mode 100644
index 58fdf4b..0000000
--- a/tanks/Pflanzarr.py
+++ /dev/null
@@ -1,392 +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 = []
- AIs = {}
- for fn in tmpPlayers:
- p = unquote(fn)
- if (not (p.startswith('.')
- or p.endswith('#')
- or p.endswith('~'))
- and teams.exists(p)):
- players.append(p)
- AIs[p] = open(os.path.join(self._playerDir, fn)).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 = int(math.ceil(math.sqrt(len(players) + 1)))
- cols = max(cols, 2)
-
- rows = int(math.ceil(len(players)/float(cols)))
- 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)
- if player == None:
- tank.set_program(random.choice(defaultAIs))
- else:
- tank.set_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'
- '
\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'
- ' Team | \n'
- ' Kills | \n'
- ' Cause of Death | \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('%s | %d | %s |
' %
- (rowStyle,
- name,
- len(kills[tank]),
- cgi.escape(tank.deathReason)))
- body.write('
\n')
-
- links='''Tanks
-
-'''
-
- # Write everything out
- html.write(self.gameFilename,
- 'Tanks round %d' % self.gameNum,
- body.getvalue(),
- hdr=hdr.getvalue(),
- links=links,
- 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."""
-
- out = tank.program.get_output()
- print 'Errors %r: %r' % (tank, out)
-
- if tank.name == None:
- return
-
- fileName = os.path.join(self._errorDir, quote(tank.name, ''))
- open(fileName, 'w').write(tank.program.get_output())
-
- 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, 'players')
-
- def _getDefaultAIs(self, basedir):
- """Load all the house bot AIs."""
- defaultAIs = []
-
- path = os.path.join(basedir, 'house')
- files = os.listdir(path)
- for fn in files:
- if fn.startswith('.'):
- continue
-
- fn = os.path.join(path, fn)
- f = open(fn)
- defaultAIs.append(f.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/tanks/Program.py b/tanks/Program.py
deleted file mode 100755
index cb2a824..0000000
--- a/tanks/Program.py
+++ /dev/null
@@ -1,100 +0,0 @@
-#! /usr/bin/python
-
-import forf
-import random
-import rfc822
-from cStringIO import StringIO
-from math import pi
-
-def deg2rad(deg):
- return float(deg) * pi / 180
-
-def rad2deg(rad):
- return int(rad * 180 / pi)
-
-class Environment(forf.Environment):
- def __init__(self, tank, stdout):
- forf.Environment.__init__(self)
- self.tank = tank
- self.stdout = stdout
-
- def err(self, msg):
- self.stdout.write('Error: %s\n' % msg)
-
- def msg(self, msg):
- self.stdout.write('%s\n' % msg)
-
- def cmd_random(self, data):
- high = data.pop()
- ret = random.randrange(high)
- data.push(ret)
-
- def cmd_fireready(self, data):
- ret = self.tank.fireReady()
- data.push(ret)
-
- def cmd_sensoractive(self, data):
- sensor = data.pop()
- try:
- ret = int(self.tank.getSensorState(sensor))
- except KeyError:
- ret = 0
- data.push(ret)
-
- def cmd_getturret(self, data):
- rad = self.tank.getTurretAngle()
- deg = rad2deg(rad)
- data.push(deg)
-
- def cmd_setled(self, data):
- self.tank.setLED()
-
- def cmd_fire(self, data):
- self.tank.setFire()
-
- def cmd_move(self, data):
- right = data.pop()
- left = data.pop()
- self.tank.setMove(left, right)
-
- def cmd_setturret(self, data):
- deg = data.pop()
- rad = deg2rad(deg)
- self.tank.setTurretAngle(rad)
-
-
-class Program:
- def __init__(self, tank, source):
- self.tank = tank
- self.stdout = StringIO()
- self.env = Environment(self.tank, self.stdout)
-
- code_str = self.read_source(StringIO(source))
- self.env.parse_str(code_str)
-
- def get_output(self):
- return self.stdout.getvalue()
-
- def read_source(self, f):
- """Read in a tank program, establish sensors, and return code.
-
- Tank programs are stored as rfc822 messages. The header
- block includes fields for sensors (Sensor:)
- and other crap which may be used later.
- """
-
- message = rfc822.Message(f)
- print 'reading tank %s' % message['Name']
- sensors = message.getallmatchingheaders('Sensor')
- for s in sensors:
- k, v = s.strip().split(':')
- r, angle, width, turret = [int(p) for p in v.split()]
- r = float(r) / 100
- angle = deg2rad(angle)
- width = deg2rad(width)
- self.tank.addSensor(r, angle, width, turret)
- return message.fp.read()
-
- def run(self):
- self.env.eval()
-
diff --git a/tanks/Tank.py b/tanks/Tank.py
deleted file mode 100644
index 906f183..0000000
--- a/tanks/Tank.py
+++ /dev/null
@@ -1,426 +0,0 @@
-import math
-import random
-from sets import Set as set
-
-import GameMath as gm
-import Program
-from cStringIO import StringIO
-
-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
- MAXSPEED = 7.0
- # Max acceleration, as a fraction of speed.
- MAXACCEL = 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):
- """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.
- """
-
- self.name = name
-
- 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
-
- # Death reason
- self.deathReason = 'survived'
-
- # Something to log to
- self.stdout = StringIO()
-
-
- 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)
- 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 (radians).
-@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 getTurretAngle(self):
- return self._tAngle
-
- 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 setLED(self):
- self.led = True
-
- def set_program(self, text):
- """Set the program for this tank."""
-
- self.program = Program.Program(self, text)
-
- def execute(self):
- """Execute this tanks program."""
-
- self.led = False
-
- self.program.run()
-
- self._move(self._nextMove[0], self._nextMove[1])
- self._moveTurret()
- if self._fireReady > 0:
- self._fireReady = self._fireReady - 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.MAXACCEL < lSpeed:
- lSpeed = self._lastSpeed[0] + self.MAXACCEL
- elif self._lastSpeed[0] > lSpeed and \
- self._lastSpeed[0] - self.MAXACCEL > lSpeed:
- lSpeed = self._lastSpeed[0] - self.MAXACCEL
-
- if self._lastSpeed[1] < rSpeed and \
- self._lastSpeed[1] + self.MAXACCEL < rSpeed:
- rSpeed = self._lastSpeed[1] + self.MAXACCEL
- elif self._lastSpeed[1] > rSpeed and \
- self._lastSpeed[1] - self.MAXACCEL > rSpeed:
- rSpeed = self._lastSpeed[1] - self.MAXACCEL
-
- self._lastSpeed = lSpeed, rSpeed
-
- # The simple case
- if lSpeed == rSpeed:
- fSpeed = self.MAXSPEED*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.MAXSPEED * lSpeed/100.0
- R = self.MAXSPEED * rSpeed/100.0
- friction = .75 * abs(L-R)/(2.0*self.MAXSPEED)
- 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 describe(self, f):
- """Output a description of this tank"""
-
- f.write(' ["%s",[' % self.color)
- for i in range(len(self._sensors)):
- dist, sensorAngle, width, tSens = self._sensors[i]
-
- f.write('[%d,%.2f,%.2f,%d],' % (dist, sensorAngle, width, tSens))
- f.write(']],\n')
-
- def draw(self, f):
- """Output this tank's state as JSON.
-
- [color, x, y, angle, turret_angle, led, fired]
-
- """
-
- if self.isDead:
- f.write(' 0,\n')
- else:
- flags = (self._fired << 0) | (self.led << 1)
- sensors = 0
- for i in range(len(self._sensorState)):
- sensors |= self._sensorState[i] << i
- f.write(' [%d,%d,%.2f,%.2f,%d,%d],\n' % (self.pos[0],
- self.pos[1],
- self._angle,
- self._tAngle,
- flags,
- sensors))
diff --git a/tanks/__init__.py b/tanks/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tanks/docs.py b/tanks/docs.py
deleted file mode 100644
index 0318250..0000000
--- a/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 '' % \
- 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 '%s | Intentionally blank | |
---|
%s' % (head, body)
-
-
diff --git a/tanks/forf.py b/tanks/forf.py
deleted file mode 100755
index e2c8b29..0000000
--- a/tanks/forf.py
+++ /dev/null
@@ -1,290 +0,0 @@
-#! /usr/bin/python
-
-"""A shitty FORTH interpreter
-
-15:58 WELCOME TO FORF!
-15:58 *PUNCH*
-"""
-
-import operator
-
-class ParseError(Exception):
- pass
-
-class Overflow(Exception):
- pass
-
-class Underflow(Exception):
- pass
-
-class Stack:
- def __init__(self, init=None, size=50):
- self.size = size
- self.stack = init or []
-
- def __str__(self):
- if not self.stack:
- return '{}'
- guts = ' '.join(repr(i) for i in self.stack)
- return '{ %s }' % guts
- __repr__ = __str__
-
- def push(self, *values):
- for val in values:
- if len(self.stack) == self.size:
- raise Overflow()
- self.stack.append(val)
-
- def extend(self, other):
- self.stack.extend(other.stack)
-
- def dup(self):
- return Stack(init=self.stack[:], size=self.size)
-
- def pop(self):
- if not self.stack:
- raise Underflow()
- return self.stack.pop()
-
- def mpop(self, n):
- return [self.pop() for i in range(n)]
-
- def __nonzero__(self):
- return bool(self.stack)
-
-
-class Environment:
- def __init__(self, ticks=2000, codelen=500):
- self.ticks = ticks
- self.codelen = codelen
- self.registers = [0] * 10
- self.unfuncs = {'~' : operator.inv,
- '!' : operator.not_,
- 'abs': operator.abs,
- }
- self.binfuncs = {'+' : operator.add,
- '-' : operator.sub,
- '*' : operator.mul,
- '/' : operator.div,
- '%' : operator.mod,
- '**': operator.pow,
- '&' : operator.and_,
- '|' : operator.or_,
- '^' : operator.xor,
- '<<': operator.lshift,
- '>>': operator.rshift,
- '>' : operator.gt,
- '>=': operator.ge,
- '<' : operator.lt,
- '<=': operator.le,
- '=' : operator.eq,
- '<>': operator.ne,
- '!=': operator.ne,
- }
- self.data = Stack()
-
- def get(self, s):
- unfunc = self.unfuncs.get(s)
- if unfunc:
- return self.apply_unfunc(unfunc)
-
- binfunc = self.binfuncs.get(s)
- if binfunc:
- return self.apply_binfunc(binfunc)
-
- try:
- return getattr(self, 'cmd_' + s)
- except AttributeError:
- return None
-
- def apply_unfunc(self, func):
- """Apply a unary function"""
-
- def f(data):
- a = data.pop()
- data.push(int(func(a)))
- return f
-
- def apply_binfunc(self, func):
- """Apply a binary function"""
-
- def f(data):
- a = data.pop()
- b = data.pop()
- data.push(int(func(b, a)))
- return f
-
- def run(self, s):
- self.parse_str(s)
- self.eval()
-
- def parse_str(self, s):
- tokens = s.strip().split()
- tokens.reverse() # so .parse can tokens.pop()
- self.code = self.parse(tokens)
-
- def parse(self, tokens, token=0, depth=0):
- if depth > 4:
- raise ParseError('Maximum recursion depth exceeded at token %d' % token)
- code = []
- while tokens:
- val = tokens.pop()
- token += 1
- f = self.get(val)
- if f:
- code.append(f)
- elif val == '(':
- # Comment
- while val != ')':
- val = tokens.pop()
- token += 1
- elif val == '{}':
- # Empty block
- code.append(Stack())
- elif val == '{':
- block = self.parse(tokens, token, depth+1)
- code.append(block)
- elif val == '}':
- break
- else:
- # The only literals we support are ints
- try:
- code.append(int(val))
- except ValueError:
- raise ParseError('Invalid literal at token %d (%s)' % (token, val))
- if len(code) > self.codelen:
- raise ParseError('Code stack overflow')
- # Reverse so we can .pop()
- code.reverse()
- return Stack(code, size=self.codelen)
-
- def eval(self):
- ticks = self.ticks
- code_orig = self.code.dup()
- while self.code and ticks:
- ticks -= 1
- val = self.code.pop()
- try:
- if callable(val):
- val(self.data)
- else:
- self.data.push(val)
- except Underflow:
- self.err('Stack underflow at proc %r' % (val))
- except Overflow:
- self.err('Stack overflow at proc %r' % (val))
- if self.code:
- self.err('Ran out of ticks!')
- self.code = code_orig
-
- def err(self, msg):
- print 'Error: %s' % msg
-
- def msg(self, msg):
- print msg
-
- ##
- ## Commands
- ##
- def cmd_print(self, data):
- a = data.pop()
- self.msg(a)
-
- def cmd_dumpstack(self, data):
- a = data.pop()
- self.msg('(dumpstack %d) %r' % (a, data.stack))
-
- def cmd_dumpmem(self, data):
- a = data.pop()
- self.msg('(dumpmem %d) %r' % (a, self.registers))
-
- def cmd_exch(self, data):
- a, b = data.mpop(2)
- data.push(a, b)
-
- def cmd_dup(self, data):
- a = data.pop()
- data.push(a, a)
-
- def cmd_pop(self, data):
- data.pop()
-
- def cmd_store(self, data):
- a, b = data.mpop(2)
- self.registers[a % 10] = b
-
- def cmd_fetch(self, data):
- a = data.pop()
- data.push(self.registers[a % 10])
-
- ##
- ## Evaluation commands
- ##
- def eval_block(self, block):
- try:
- self.code.extend(block)
- except TypeError:
- # If it's not a block, just append it
- self.code.push(block)
-
- def cmd_if(self, data):
- block = data.pop()
- cond = data.pop()
- if cond:
- self.eval_block(block)
-
- def cmd_ifelse(self, data):
- elseblock = data.pop()
- ifblock = data.pop()
- cond = data.pop()
- if cond:
- self.eval_block(ifblock)
- else:
- self.eval_block(elseblock)
-
- def cmd_eval(self, data):
- # Interestingly, this is the same as "1 exch if"
- block = data.pop()
- self.eval_block(block)
-
- def cmd_call(self, data):
- # Shortcut for "fetch eval"
- self.cmd_fetch(data)
- self.cmd_eval(data)
-
-
-def repl():
- env = Environment()
- while True:
- try:
- s = raw_input('>8[= =] ')
- except (KeyboardInterrupt, EOFError):
- print
- break
- try:
- env.run(s)
- print env.data
- except ParseError, err:
- print r' \ nom nom nom, %s!' % err
- print r' \ bye bye!'
-
-if __name__ == '__main__':
- import sys
- import time
- try:
- import readline
- except ImportError:
- pass
-
- if len(sys.argv) > 1:
- s = open(sys.argv[1]).read()
- env = Environment()
- begin = time.time()
- env.run(s)
- end = time.time()
- elapsed = end - begin
- print 'Evaluated in %.2f seconds' % elapsed
- else:
- print 'WELCOME TO FORF!'
- print '*PUNCH*'
- repl()
|