mirror of https://github.com/dirtbags/moth.git
Remove tanks, we want that to be its own project
This commit is contained in:
parent
1adc53d511
commit
c99198605f
18
Makefile
18
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 \
|
||||
|
|
2
setup.py
2
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'])
|
||||
|
|
|
@ -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
|
|
@ -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">← Prev</a> |' %
|
||||
(self.gameNum - 1))
|
||||
body.write(' <a href="%04d.html">Next →</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"
|
||||
|
||||
|
100
tanks/Program.py
100
tanks/Program.py
|
@ -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()
|
||||
|
426
tanks/Tank.py
426
tanks/Tank.py
|
@ -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))
|
|
@ -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)
|
||||
|
||||
|
290
tanks/forf.py
290
tanks/forf.py
|
@ -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()
|
Loading…
Reference in New Issue