Remove tanks, we want that to be its own project

This commit is contained in:
Neale Pickett 2010-05-15 21:22:44 -06:00
parent 1adc53d511
commit c99198605f
9 changed files with 2 additions and 1448 deletions

View File

@ -14,7 +14,7 @@ MDWNTOHTML = $(CURDIR)/mdwntohtml.py --template=$(TEMPLATE) --base=$(BASE_URL)
default: install default: install
TARGETS = tanks puzzles TARGETS = puzzles
include $(wildcard */*.mk) include $(wildcard */*.mk)
CLEAN_TARGETS = $(addsuffix -clean, $(TARGETS)) CLEAN_TARGETS = $(addsuffix -clean, $(TARGETS))
INSTALL_TARGETS = $(addsuffix -install, $(TARGETS)) INSTALL_TARGETS = $(addsuffix -install, $(TARGETS))
@ -36,22 +36,6 @@ puzzles-install: puzzles-build
puzzles-clean: puzzles-clean:
rm -rf $(BUILD_DIR)/puzzles $(DESTDIR)$(WWW)/puzzler $(DESTDIR)$(LIB)/puzzler.keys 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: $(INSTALL_TARGETS)
install bin/pointscli $(DESTDIR)$(BIN) install bin/pointscli $(DESTDIR)$(BIN)
install bin/in.pointsd bin/in.flagd \ install bin/in.pointsd bin/in.flagd \

View File

@ -8,4 +8,4 @@ setup(name='ctf',
author='Neale Pickett', author='Neale Pickett',
author_email='neale@lanl.gov', author_email='neale@lanl.gov',
url='http://dirtbags.net/ctf/', url='http://dirtbags.net/ctf/',
packages=['ctf', 'tanks']) packages=['ctf'])

View File

@ -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

View File

@ -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('<script type="application/javascript" src="../tanks.js"></script>\n'
'<script type="application/javascript">\n')
hdr.write('turns = [%d, %d,[\n' % self._board)
# Describe tanks
for tank in self._tanks:
tank.describe(hdr)
hdr.write('],\n')
hdr.write('[\n')
turn = 0
lastTurns = 3
while ((maxTurns is None) or turn < maxTurns) and lastTurns > 0:
if len(self._tanks) - len(self._deadTanks) < 2:
lastTurns = lastTurns - 1
near = self._getNear()
liveTanks = set(self._tanks).difference(self._deadTanks)
for tank in liveTanks:
# Shoot now, if we said to shoot last turn
dead = tank.fire( near[tank] )
kills[tank] = kills[tank].union(dead)
self._killTanks(dead, 'Shot by %s' % cgi.escape(tank.name or teams.house))
for tank in liveTanks:
# We also check for collisions here, while we're at it.
dead = tank.sense( near[tank] )
kills[tank] = kills[tank].union(dead)
self._killTanks(dead, 'Collision')
hdr.write(' [\n')
for tank in self._tanks:
tank.draw(hdr)
hdr.write(' ],\n')
# Have the live tanks do their turns
for tank in self._tanksByX:
tank.execute()
turn = turn + 1
# Removes tanks from their own kill lists.
for tank in kills:
if tank in kills[tank]:
kills[tank].remove(tank)
for tank in self._tanks:
self._outputErrors(tank)
hdr.write(']];\n')
hdr.write('</script>\n')
# Decide on the winner
winner = self._chooseWinner(kills)
self.winner = winner.name
# Now generate HTML body
body = StringIO()
body.write(' <canvas id="battlefield" width="%d" height="%d">\n' % self._board)
body.write(' Sorry, you need an HTML5-capable browser to see this.\n'
' </canvas>\n'
' <p>\n')
if self.gameNum > 0:
body.write(' <a href="%04d.html">&larr; Prev</a> |' %
(self.gameNum - 1))
body.write(' <a href="%04d.html">Next &rarr;</a> |' %
(self.gameNum + 1))
body.write(' <span id="fps">0</span> fps\n'
' </p>\n'
' <table class="results">\n'
' <tr>\n'
' <th>Team</th>\n'
' <th>Kills</th>\n'
' <th>Cause of Death</th>\n'
' </tr>\n')
tanks = self._tanks[:]
tanks.remove(winner)
tanks[0:0] = [winner]
for tank in tanks:
if tank is winner:
rowStyle = ('style="font-weight: bold; '
'color: #000; '
'background-color: %s;"' % tank.color)
else:
rowStyle = 'style="background-color:%s; color: #000;"' % tank.color
if tank.name:
name = cgi.escape(tank.name)
else:
name = teams.house
body.write('<tr %s><td>%s</td><td>%d</td><td>%s</td></tr>' %
(rowStyle,
name,
len(kills[tank]),
cgi.escape(tank.deathReason)))
body.write(' </table>\n')
links='''<h3>Tanks</h3>
<ul>
<li><a href="../docs.html">Docs</a></li>
<li><a href="../results.cgi">Results</a></li>
<li><a href="../submit.html">Submit</a></li>
<li><a href="../errors.cgi">My Errors</a></li>
</ul>
'''
# 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"

View File

@ -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()

View File

@ -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 '<tank: %s, (%d, %d)>' % (self.name, self.pos[0], self.pos[1])
def fire(self, near):
"""Shoots, if ordered to and able. Returns the set of tanks
destroyed."""
killed = set()
if self._fireReady > 0:
# Ignore the shoot order
self._fireNow = False
if self._fireNow:
self._fireNow = False
self._fireReady = self.FIRE_RATE
self._fired = True
firePoint = gm.polar2cart(self.FIRE_RANGE,
self._angle + self._tAngle)
for tank in near:
enemyPos = gm.minShift(self.pos, tank.pos, self._limits)
if gm.segmentCircleCollision(((0,0), firePoint), enemyPos,
self.RADIUS):
killed.add(tank)
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))

View File

View File

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

View File

@ -1,290 +0,0 @@
#! /usr/bin/python
"""A shitty FORTH interpreter
15:58 <SpaceHobo> WELCOME TO FORF!
15:58 <SpaceHobo> *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()