Change tanks language to forf

This commit is contained in:
Neale Pickett 2010-03-07 23:57:15 -07:00
parent ca6e158fc8
commit 22c6e09647
12 changed files with 504 additions and 727 deletions

View File

@ -1,5 +1,18 @@
random(1,10): move(50, 100);
random(1,10): move(100, 50);
random(1,10): turretcw();
random(1,10): turretccw();
random(1,30): fire();
Name: berzerker
Author: Neale
2 random
0 =
{ 50 100 move }
{ 100 50 move }
ifelse
4 random
0 =
{ 360 random setturret }
if
30 random
0 =
{ fire }
if

View File

@ -1,8 +1,31 @@
>addsensor(50, 0, 0, 1);
>addsensor(70, 0, 50); # 1-Anti-collision sensor
Name: Rabbit With Gun
Author: Neale
Sensor: 50 0 0 1
Sensor: 70 0 50 0
: move(100, 100) . turretset(180);
random(1, 8): move(70, 100);
random(1, 8): move(100, 70);
sense(0) : fire();
sense(1) : move(-0, 100);
100 100 move
( Always set turret to 180 degrees )
180 setturret
( Vary walk 1/8 of the time )
8 random
0 =
{
2 random
0 =
{ 70 100 move }
{ 100 70 move }
ifelse
}
if
( If you see something, shoot it )
0 sensoractive
{ fire }
if
( Turn, if trouble lies ahead )
1 sensoractive
{ 0 100 move }
if

View File

@ -1,57 +1,55 @@
# 3
# 000 33
# 2
# 2
# 2
# 11111 4
# 4
# 4
# @@/
# @@@
# @@@
#
#
#
#
Name: crashmaster
Author: Neale
Sensor: 50 0 8 1
Sensor: 30 0 50 0
Sensor: 50 0 10 0
Sensor: 100 315 100 1
Sensor: 100 45 100 1
Sensor: 60 180 180 0
( Mem 0: Turn number )
0 fetch
1 +
0 store
>addsensor(50, 0, 05, 1); # 0 Fire Sensor
>addsensor(30, 0, 50); # 1 Anti-collision sensor
>addsensor(50, 0, 10); # 2 Anti-collision sensor
>addsensor(100, 315, 100, 1); # 3 Turret ccw
>addsensor(100, 45, 100, 1); # 4 Turret cw
>addsensor(60, 180, 180, 0); # 5 Ass
( Mem 1: Move turret (procedure) )
{
getturret
-
setturret
} 1 store
##
## Add "ears" so the tank is easy to pick out.
##
>addsensor(20, 90, 30, 0);
>addsensor(20, 270, 30, 0);
0 fetch 30 % 10 / ( [0..2], changes every 10 turns )
dup 0 = { 80 80 move } if
dup 1 = { 60 80 move } if
dup 2 = { 80 60 move } if
pop
# Can't fire
: led(0) . move(80, 80) . turretset(0);
random(1, 3): led(0) . move(60, 80) . turretset(0);
random(2, 3): led(0) . move(80, 60) . turretset(0);
0 setturret
sense(0) : led(0) . move(10, 20) . turretset(0);
sense(1) : led(0) . move(10, 10) . turretset(0);
sense(2) : led(0) . move(10, 20) . turretset(0);
sense(3) : led(0) . move(70, 50) . turretset(0);
sense(4) : led(0) . move(50, 70) . turretset(0);
sense(3) & sense(4): led(0) . move(-100, 20) . turretset(0);
sense(5) : led(0) . move(100, 50) . turretset(0);
fireready
{
( Behavior for when we can shoot )
0 sensoractive { fire } if
1 sensoractive { 10 10 move 0 setturret } if
2 sensoractive { 10 10 move 0 setturret } if
3 sensoractive { 0 60 move -50 1 call } if
4 sensoractive { 60 0 move 50 1 call } if
3 sensoractive 4 sensoractive & { 100 100 move getturret setturret } if
5 sensoractive { 100 40 move } if
}
{
( Behavior for when we can't shoot )
# Can fire
fireready() : led(1) . move(70, 70) . turretset(0);
fireready() & random(2, 40): led(1) . move(40, 70) . turretset(0);
fireready() & random(1, 40): led(1) . move(70, 40) . turretset(0);
setled
fireready() & sense(3) : led(1) . move(0, 60) . turretccw(50);
fireready() & sense(4) : led(1) . move(60, 0) . turretcw(50);
fireready() & sense(3) & sense(4): led(1) . move(100, 100) . turretset();
fireready() & sense(1) : led(1) . turretset(0) . move(10, 10);
fireready() & sense(2) : led(1) . turretset(0) . move(10, 10);
fireready() & sense(0) : led(1) . turretset() . fire();
fireready() & sense(5) : led(1) . move(100, 40);
0 sensoractive { 10 20 move } if
1 sensoractive { 10 10 move } if
2 sensoractive { 10 20 move } if
3 sensoractive { 70 50 move } if
4 sensoractive { 50 70 move } if
3 sensoractive 4 sensoractive & { -100 20 move } if
5 sensoractive { 100 50 move } if
}
ifelse

View File

@ -1,46 +0,0 @@
import math
class Function(object):
"""Represents a single condition or action. This doc string is printed
as user documentation. You should override it to say something useful."""
def __call__(self, tank):
"""The __call__ method should be of this basic form. Actions
should return None, conditions should return True or False. Actions
should utilize the set* methods of tanks. Conditions can utilize the
tanks get* methods."""
pass
def _limitArgs(self, args, max):
"""Raises a ValueError if there are more than max args."""
if len(args) > max:
raise ValueError("Too many arguments: %s" % ','.join(args))
def _checkRange(self, value, name, min=0, max=100):
"""Check that the value is in the given range.
Raises an exception with useful info for invalid values. Name is used to
let the user know which value is wrong."""
try:
value = int(value)
except:
raise ValueError("Invalid %s value: %s" % (name, value))
assert value >= min and value <= max, "Invalid %s. %ss must be in"\
" the %s %d-%d" % \
(name, name.capitalize(), value, min, max)
return value
def _convertAngle(self, value, name):
"""Parse the given value as an angle in degrees, and return its value
in radians. Raise useful errors.
Name is used in the errors to describe the field."""
try:
angle = math.radians(value)
except:
raise ValueError("Invalid %s value: %s" % (name, value))
assert angle >= 0 and angle < 2*math.pi, "Invalid %s; "\
"It be in the range 0 and 359." % name
return angle

View File

@ -66,11 +66,11 @@ class Pflanzarr:
players.remove(player)
color = '#' + teams.color(player)
tank = Tank.Tank( player, (startX, startY), color,
self._board, testMode=True)
self._board)
if player == None:
tank.program(random.choice(defaultAIs))
tank.set_program(random.choice(defaultAIs))
else:
tank.program(AIs[player])
tank.set_program(AIs[player])
self._tanks.append(tank)
# We only want to make these once, so we do it here.
@ -247,19 +247,15 @@ class Pflanzarr:
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
if tank._program.errors:
print tank.name, 'has errors'
fileName = os.path.join(self._errorDir, quote(tank.name, ''))
file = open(fileName, 'w')
for error in tank._program.errors:
file.write(error)
file.write('\n')
file.close()
open(fileName, 'w').write(tank.program.get_output())
def _getNear(self):
"""A dictionary of the set of tanks nearby each tank. Nearby is
@ -354,21 +350,21 @@ class Pflanzarr:
# Setup all the directories we'll need.
self._resultsDir = os.path.join(dir, 'results')
self._errorDir = os.path.join(dir, 'errors')
self._playerDir = os.path.join(dir, 'ai', 'players')
self._playerDir = os.path.join(dir, 'players')
def _getDefaultAIs(self, basedir):
"""Load all the house bot AIs."""
defaultAIs = []
path = os.path.join(basedir, 'ai', 'house')
path = os.path.join(basedir, 'house')
files = os.listdir(path)
for fn in files:
if fn.startswith('.'):
continue
fn = os.path.join(path, fn)
file = open(fn)
defaultAIs.append(file.read())
f = open(fn)
defaultAIs.append(f.read())
return defaultAIs

286
tanks/Program.py Normal file → Executable file
View File

@ -1,234 +1,100 @@
"""<H2>Introduction</H2>
You are the proud new operator of a M-375 Pflanzarr Tank. Your tank is
equipped with a powerful laser cannon, independently rotating turret
section, up to 10 enemy detection sensors, and a standard issue NATO hull.
Unfortunately, it lacks seats, and thus must rely own its own wits and your
skills at designing those wits to survive.
#! /usr/bin/python
<H2>Programming Your Tank</H2>
Your tanks are programmed using the Super Useful Command and Kontrol language,
the very best in laser tank AI languages. It includes amazing features such
as comments (Started by a #, ended at EOL), logic, versatility, and
semi-colons (all lines must end in one). As with all new military systems
it utilizes only integers; we must never rest in our
diligence against the communist floating point conspiracy. Whitespace is
provided by trusted contractors, and should never interfere with operations.
<P>
Your program should be separated into Setup and AI commands. The definitions
section lets you designated the behaviors of its sensors and memory.
Each setup command must begin with a '>'. Placing setup commands after
the first AI command is a violation of protocol.
Here are some examples of correct setup commands:
<pre class="docs">>addsensor(80, 90, 33);
>addsensor(50, 0, 10, 1);
>addtimer(3);</pre>
import forf
import random
import rfc822
from cStringIO import StringIO
from math import pi
The AI section will act as the brain of your tank. Each AI line is
separated into a group of conditions functions and a group of action
functions. If all the conditions are satisfactory (true), all of the actions
are given as orders. Conditions are separated by ampersands, actions separated
by periods. Here are some examples of AI commands:
<pre class="docs">
sensor(1) & sensor(2) & fireready() : fire();
sensor(0,0)&sin(5): move(40, 30) . turretcw(50);
sensor(4) & random(4,5) : led(1).settoggle(0,1);</pre>
def deg2rad(deg):
return float(deg) * pi / 180
Your tank will check its program each turn, and attempt to the best of its
abilities to carry out its orders (or die trying). Like any military mind,
your tank may receive a plethora of often conflicting orders and information.
This a SMART TANK, however. It knows that the proper thing to do with each
subsystem is to have that subsystem follow only the last order given each turn.
"""
def rad2deg(rad):
return int(rad * 180 / pi)
import traceback
import conditions
import actions
import setup
class Environment(forf.Environment):
def __init__(self, tank, stdout):
forf.Environment.__init__(self)
self.tank = tank
self.stdout = stdout
class Statement(object):
"""Represents a single program statement. If all the condition Functions
evaluate to True, the actions are all executed in order."""
def err(self, msg):
self.stdout.write('Error: %s\n' % msg)
def __init__(self, lineNum, line, conditions, actions):
self.lineNum = lineNum
self.line = line
self._conditions = conditions
self._actions = actions
def msg(self, msg):
self.stdout.write('%s\n' % msg)
def __call__(self, tank):
success = True
for condition in self._conditions:
if not condition(tank):
success = False
break
def cmd_random(self, data):
high = data.pop()
ret = random.randrange(high)
data.push(ret)
if success:
for action in self._actions:
action(tank)
def cmd_fireready(self, data):
ret = self.tank.fireReady()
data.push(ret)
class Program(object):
"""This parses and represents a Tank program."""
CONDITION_SEP = '&'
ACTION_SEP = '.'
def cmd_sensoractive(self, data):
sensor = data.pop()
try:
ret = int(self.tank.getSensorState(sensor))
except KeyError:
ret = 0
data.push(ret)
def __init__(self, text):
"""Initialize this program, parsing the given text."""
self.errors = []
def cmd_getturret(self, data):
rad = self.tank.getTurretAngle()
deg = rad2deg(rad)
data.push(deg)
self._program, self._setup = self._parse(text)
def setup(self, tank):
"""Execute all the setup actions."""
for action in self._setup:
try:
action(tank)
except Exception, msg:
self.errors.append("Bad setup action, line %d, msg: %s" % \
(action.lineNum, msg))
def cmd_setled(self, data):
self.tank.setLED()
def __call__(self, tank):
"""Execute this program on the given tank."""
for statement in self._program:
try:
statement(tank)
except Exception, msg:
traceback.print_exc()
self.errors.append('Error executing program. \n'
'(%d) - %s\n'
'msg: %s\n' %
(statement.lineNum, statement.line, msg) )
def cmd_fire(self, data):
self.tank.setFire()
def _parse(self, text):
"""Parse the text of the given program."""
program = []
setup = []
inSetup = True
lines = text.split(';')
lineNum = 0
for line in lines:
lineNum = lineNum + 1
def cmd_move(self, data):
right = data.pop()
left = data.pop()
self.tank.setMove(left, right)
originalLine = line
def cmd_setturret(self, data):
deg = data.pop()
rad = deg2rad(deg)
self.tank.setTurretAngle(rad)
# Remove Comments
parts = line.split('\n')
for i in range(len(parts)):
comment = parts[i].find('#')
if comment != -1:
parts[i] = parts[i][:comment]
# Remove all whitespace
line = ''.join(parts)
line = line.replace('\r', '')
line = line.replace('\t', '')
line = line.replace(' ', '')
if line == '':
continue
if line.startswith('>'):
if inSetup:
if '>' in line[1:] or ':' in line:
self.errors.append('(%d) Missing semicolon: %s' %
(lineNum, line))
continue
class Program:
def __init__(self, tank, source):
self.tank = tank
self.stdout = StringIO()
self.env = Environment(self.tank, self.stdout)
try:
setupAction = self._parseSection(line[1:], 'setup')[0]
setupAction.lineNum = lineNum
setup.append(setupAction)
except Exception, msg:
self.errors.append('(%d) Error parsing setup line: %s'
'\nThe error was: %s' %
(lineNum, originalLine, msg))
code_str = self.read_source(StringIO(source))
self.env.parse_str(code_str)
continue
else:
self.errors.append('(%d) Setup lines aren\'t allowed '
'after the first command: %s' %
(lineNum, originalLine))
else:
# We've hit the first non-blank, non-comment, non-setup
# line
inSetup = False
def get_output(self):
return self.stdout.getvalue()
semicolons = line.count(':')
if semicolons > 1:
self.errors.append('(%d) Missing semicolon: %s' %
(lineNum, line))
continue
elif semicolons == 1:
conditions, actions = line.split(':')
else:
self.errors.append('(%d) Invalid Line, no ":" seperator: %s'%
(lineNum, line) )
def read_source(self, f):
"""Read in a tank program, establish sensors, and return code.
try:
conditions = self._parseSection(conditions, 'condition')
except Exception, msg:
self.errors.append('(%d) %s - "%s"' %
(lineNum, msg, line) )
continue
try:
actions = self._parseSection(actions, 'action')
except Exception, msg:
self.errors.append('(%d) %s - "%s"' %
(lineNum, msg, originalLine) )
continue
program.append(Statement(lineNum, line, conditions, actions))
return program, setup
def _parseSection(self, section, sectionType):
"""Parses either the action or condition section of each command.
@param section: The text of the section of the command to be parsed.
@param sectionType: The type of section to be parsed. Should be:
'condition', 'action', or 'setup'.
@raises ValueError: Raises ValueErrors when parsing errors occur.
@returns: Returns a list of parsed section components (Function objects).
Tank programs are stored as rfc822 messages. The header
block includes fields for sensors (Sensor:)
and other crap which may be used later.
"""
if sectionType == 'condition':
parts = section.split(self.CONDITION_SEP)
functions = conditions.conditions
if section == '':
return []
elif sectionType == 'action':
parts = section.split(self.ACTION_SEP)
functions = actions.actions
if section == '':
raise ValueError("The action section cannot be empty.")
elif sectionType == 'setup':
parts = [section]
functions = setup.setup
else:
raise ValueError('Invalid section Type - Contact Contest Admin')
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()
parsed = []
for part in parts:
def run(self):
self.env.eval()
pos = part.find('(')
if pos == -1:
raise ValueError("Missing open paren in %s: %s" %
(sectionType, part) )
funcName = part[:pos]
if funcName not in functions:
raise ValueError("%s function %s is not accepted." %
(sectionType.capitalize(), funcName) )
if part[-1] != ')':
raise ValueError("Missing closing paren in %s: %s" %
(condition, sectionType) )
args = part[pos+1:-1]
if args != '':
args = args.split(',')
for i in range(len(args)):
args[i] = int(args[i])
else:
args = []
parsed.append(functions[funcName](*args))
return parsed

View File

@ -4,6 +4,7 @@ from sets import Set as set
import GameMath as gm
import Program
from cStringIO import StringIO
class Tank(object):
@ -15,9 +16,9 @@ class Tank(object):
# This is what is used for collision and hit detection.
RADIUS = 7.5
# Max speed, in pixels
SPEED = 7.0
MAXSPEED = 7.0
# Max acceleration, as a fraction of speed.
ACCEL = 35
MAXACCEL = 35
# Sensor range, in pixels
SENSOR_RANGE = 90.0
# Max turret turn rate, in radians
@ -26,8 +27,7 @@ class Tank(object):
# The max number of sensors/timers/toggles
SENSOR_LIMIT = 10
def __init__(self, name, pos, color, boardSize, angle=None, tAngle=None,
testMode=True):
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)
@ -35,16 +35,9 @@ class Tank(object):
@param boardSize: The size of the board. (maxX, maxY)
@param angle: The starting angle of the tank, defaults to random.
@param tAngle: The starting turretAngle of the tank, defaults to random.
@param testMode: When True, extra debugging information is displayed. Namely,
arcs for each sensor are drawn, which turn white when
activated.
"""
# Keep track of what turn number it is for this tank.
self._turn = 0
self.name = name
self._testMode = testMode
assert len(pos) == 2 and pos[0] > 0 and pos[1] > 0, \
'Bad starting position: %s' % str(pos)
@ -100,18 +93,17 @@ class Tank(object):
# Is this tank dead?
self.isDead = False
# The frame of the death animation.
self._deadFrame = 10
# 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 get_turn(self):
return self._turn
turn = property(get_turn)
def fire(self, near):
"""Shoots, if ordered to and able. Returns the set of tanks
destroyed."""
@ -143,7 +135,7 @@ class Tank(object):
"""Add a sensor to this tank.
@param angle: The angle, from the tanks front and going clockwise,
of the center of the sensor. (radians)
@param width: The width of the sensor (percent).
@param 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.
"""
@ -170,6 +162,9 @@ class Tank(object):
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."""
@ -187,60 +182,26 @@ class Tank(object):
"""Returns True if the tank can fire now."""
return self._fireReady == 0
def addTimer(self, period):
"""Add a timer with timeout period 'period'."""
def setLED(self):
self.led = True
if len(self._timers) >= self.SENSOR_LIMIT:
raise ValueError('You can only have 10 timers')
self._timers.append(None)
self._timerPeriods.append(period)
def resetTimer(self, key):
"""Reset, and start the given timer, but only if it is inactive.
If it is active, raise a ValueError."""
if self._timer[key] is None:
self._timer[key] = self._timerPeriods[key]
else:
raise ValueError("You can't reset an active timer (#%d)" % key)
def clearTimer(self, key):
"""Clear the timer."""
self._timer[key] = None
def checkTimer(self, key):
"""Returns True if the timer has expired."""
return self._timer[key] == 0
def _manageTimers(self):
"""Decrement each active timer."""
for i in range(len(self._timers)):
if self._timers[i] is not None and \
self._timers[i] > 0:
self._timers[i] = self._timers[i] - 1
def program(self, text):
def set_program(self, text):
"""Set the program for this tank."""
self._program = Program.Program(text)
self._program.setup(self)
self.program = Program.Program(self, text)
def execute(self):
"""Execute this tanks program."""
# Decrement the active timers
self._manageTimers()
self.led = False
self._program(self)
self.program.run()
self._move(self._nextMove[0], self._nextMove[1])
self._moveTurret()
if self._fireReady > 0:
self._fireReady = self._fireReady - 1
self._turn = self._turn + 1
def sense(self, near):
"""Detect collisions and trigger sensors. Returns the set of
tanks collided with, plus this one. We do both these steps at once
@ -324,24 +285,24 @@ class Tank(object):
# Handle acceleration
if self._lastSpeed[0] < lSpeed and \
self._lastSpeed[0] + self.ACCEL < lSpeed:
lSpeed = self._lastSpeed[0] + self.ACCEL
self._lastSpeed[0] + self.MAXACCEL < lSpeed:
lSpeed = self._lastSpeed[0] + self.MAXACCEL
elif self._lastSpeed[0] > lSpeed and \
self._lastSpeed[0] - self.ACCEL > lSpeed:
lSpeed = self._lastSpeed[0] - self.ACCEL
self._lastSpeed[0] - self.MAXACCEL > lSpeed:
lSpeed = self._lastSpeed[0] - self.MAXACCEL
if self._lastSpeed[1] < rSpeed and \
self._lastSpeed[1] + self.ACCEL < rSpeed:
rSpeed = self._lastSpeed[1] + self.ACCEL
self._lastSpeed[1] + self.MAXACCEL < rSpeed:
rSpeed = self._lastSpeed[1] + self.MAXACCEL
elif self._lastSpeed[1] > rSpeed and \
self._lastSpeed[1] - self.ACCEL > rSpeed:
rSpeed = self._lastSpeed[1] - self.ACCEL
self._lastSpeed[1] - self.MAXACCEL > rSpeed:
rSpeed = self._lastSpeed[1] - self.MAXACCEL
self._lastSpeed = lSpeed, rSpeed
# The simple case
if lSpeed == rSpeed:
fSpeed = self.SPEED*lSpeed/100
fSpeed = self.MAXSPEED*lSpeed/100
x = fSpeed*math.cos(self._angle)
y = fSpeed*math.sin(self._angle)
# Adjust our position
@ -354,9 +315,9 @@ class Tank(object):
# around the circle varies with the speed of each tread, and is
# such that each side of the tank moves an equal angle around the
# circle.
L = self.SPEED * lSpeed/100.0
R = self.SPEED * rSpeed/100.0
friction = .75 * abs(L-R)/(2.0*self.SPEED)
L = 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)

View File

@ -1,126 +0,0 @@
"""Define new action Functions here. They should inherit from the
Function.Function class. To make an action usable, add it to the
actions dictionary at the end of this file."""
import Function
class Move(Function.Function):
"""move(left tread speed, right tread speed)
Set the speeds for the tanks right and left treads. The speeds should
be a number (percent power) between -100 and 100."""
def __init__(self, left, right):
self._checkRange(left, 'left tread speed', min=-100)
self._checkRange(right, 'right tread speed', min=-100)
self._left = left
self._right = right
def __call__(self, tank):
tank.setMove(self._left, self._right)
class TurretCounterClockwise(Function.Function):
"""turretccw([percent speed])
Rotate the turret counter clockwise as a percentage of the max speed."""
def __init__(self, speed=100):
self._checkRange(speed, 'turret percent speed')
self._speed = speed/100.0
def __call__(self, tank):
tank.setTurretAngle(tank._tAngle - tank.TURRET_TURN_RATE*self._speed)
class TurretClockwise(Function.Function):
"""turretcw([percent speed])
Rotate the turret clockwise at a rate preportional to speed."""
def __init__(self, speed=100):
self._checkRange(speed, 'turret percent speed')
self._speed = speed/100.0
def __call__(self, tank):
tank.setTurretAngle(tank._tAngle + tank.TURRET_TURN_RATE*self._speed)
class TurretSet(Function.Function):
"""turretset([angle])
Set the turret to the given angle, in degrees, relative to the front of
the tank. Angles increase counterclockwise.
The angle can be left out; in that case, this locks the turret
to it's current position."""
def __init__(self, angle=None):
# Convert the angle to radians
if angle is not None:
angle = self._convertAngle(angle, 'turret angle')
self._angle = angle
def __call__(self, tank):
tank.setTurretAngle(self._angle)
class Fire(Function.Function):
"""fire()
Attempt to fire the tanks laser cannon."""
def __call__(self, tank):
tank.setFire()
class SetToggle(Function.Function):
"""settoggle(key, state)
Set toggle 'key' to 'state'.
"""
def __init__(self, key, state):
self._key = key
self._state = state
def __call__(self, tank):
tank.toggles[self._key] = self._state
class Toggle(Function.Function):
"""toggle(key)
Toggle the value of toggle 'key'.
"""
def __init__(self, key):
self._key = key
def __call__(self, tank):
try:
tank.toggles[self._key] = not tank.toggles[self._key]
except IndexError:
raise IndexError('Invalid toggle: %d' % self._key)
class LED(Function.Function):
"""led(state)
Set the tanks LED to state (true is on, false is off).
The led is a light that appears behind the tanks turret.
It remains on for a single turn."""
def __init__(self, state=1):
self._state = state
def __call__(self, tank):
tank.led = self._state
class StartTimer(Function.Function):
"""starttimer(#)
Start (and reset) the given timer, but only if it is inactive.
"""
def __init__(self, key):
self._key = key
def __call__(self, tank):
tank.resetTimer(key)
class ClearTimer(Function.Function):
"""cleartimer(#)
Clear the given timer such that it is no longer active (inactive timers
are always False)."""
def __init__(self, key):
self._key = key
def __call__(self, tank):
tank.clearTimer(self._key)
### When adding names to this dict, make sure they are lower case and alpha
### numeric.
actions = {'move': Move,
'turretccw': TurretCounterClockwise,
'turretcw': TurretClockwise,
'turretset': TurretSet,
'fire': Fire,
'settoggle': SetToggle,
'toggle': Toggle,
'led': LED,
'starttimer': StartTimer,
'cleartimer': ClearTimer}

View File

@ -1,126 +0,0 @@
"""Define new condition functions here. Add it to the conditions dictionary
at the end to make it usable by Program.Program. These should inherit from
Function.Function."""
import Function
import math
import random
class Sense(Function.Function):
"""sense(#, [invert])
Takes a Sensor number as an argument.
Returns True if the given sensor is currently activated, False otherwise.
If the option argument invert is set to true then logic is inverted,
and then sensor returns True when it is NOT activated, and False when
it is. Invert is false by default.
"""
def __init__(self, sensor, invert=0):
self._sensor = sensor
self._invert = invert
def __call__(self, tank):
state = tank.getSensorState(self._sensor)
if self._invert:
return not state
else:
return state
class Toggle(Function.Function):
"""toggle(#)
Returns True if the given toggle is set, False otherwise. """
def __init__(self, toggle):
self._toggle = toggle
def __call__(self, tank):
return tank.toggles[toggle]
class TimerCheck(Function.Function):
"""timer(#, [invert])
Checks the state of timer # 'key'. Returns True if time has run out.
If invert is given (and true), then True is returned if the timer has
yet to expire.
"""
def __init__(self, key, invert=0):
self._key = key
self._invert = invert
def __call__(self, tank):
state = tank.checkTimer(self._key)
if invert:
return not state
else:
return state
class Random(Function.Function):
"""random(n,m)
Takes two arguments, n and m. Generates a random number between 1
and m (inclusive) each time it's checked. If the random number is less
than or equal
to n, then the condition returns True. Returns False otherwise."""
def __init__(self, n, m):
self._n = n
self._m = m
def __call__(self, tank):
if random.randint(1,self._m) <= self._n:
return True
else:
return False
class Sin(Function.Function):
"""sin(T)
A sin wave of period T (in turns). Returns True when the wave is positive.
A wave with period 1 or 2 is always False (it's 0 each turn), only
at periods of 3 or more does this become useful."""
def __init__(self, T):
self._T = T
def __call__(self, tank):
turn = tank.turn
factor = math.pi/self._T
if math.sin(turn * factor) > 0:
return True
else:
return False
class Cos(Function.Function):
"""cos(T)
A cos wave with period T (in turns). Returns True when the wave is
positive. A wave of period 1 is always True. Period 2 is True every
other turn, etc."""
def __init__(self, T):
self._T = T
def __call__(self, tank):
turn = tank.turn
factor = math.pi/self._T
if math.cos(turn * factor) > 0:
return True
else:
return False
class FireReady(Function.Function):
"""fireready()
True when the tank can fire."""
def __call__(self, tank):
return tank.fireReady()
class FireNotReady(Function.Function):
"""firenotready()
True when the tank can not fire."""
def __call__(self, tank):
return not tank.fireReady()
### When adding names to this dict, make sure they are lower case and alpha
### numeric.
conditions = {'sense': Sense,
'random': Random,
'toggle': Toggle,
'sin': Sin,
'cos': Cos,
'fireready': FireReady,
'firenotready': FireNotReady,
'timer': TimerCheck }

290
tanks/forf.py Executable file
View File

@ -0,0 +1,290 @@
#! /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()

View File

@ -1,72 +0,0 @@
"""Each of these classes provides a function for configuring a tank.
They should inherit from Function.Function.
To make one available to the tank programmer, add it to the dictionary at
the end of this file."""
import Function
class AddSensor(Function.Function):
"""addsensor(range, angle, width, [turretAttached])
Add a new sensor to the tank. Sensors are an arc (pie slice) centered on
the tank that detect other tanks within their sweep.
A sensor is 'on' if another tank is within this arc.
Sensors are numbered, starting at 0, in the order they are added.
<p>
range - The range of the sensor, as a percent of the tanks max range.
angle - The angle of the center of the sensor, in degrees.
width - The width of the sensor, in percent (100 is a full circle).
turretAttached - Normally, the angle is relative to the front of the
tank. When this is set, the angle is relative to the current turret
direction.
<p>
Sensors are drawn for each tank, but not in the way you might expect.
Instead of drawing a pie slice (the actual shap of the sensor), an arc with
the end points connected by a line is drawn. Sensors with 0 width don't show
up, but still work.
"""
def __init__(self, range, angle, width, turretAttached=False):
self._checkRange(range, 'sensor range')
self._range = range / 100.0
self._width = self._convertAngle(width, 'sensor width')
self._angle = self._convertAngle(angle, 'sensor angle')
self._turretAttached = turretAttached
def __call__(self, tank):
tank.addSensor(self._range, self._angle, self._width,
self._turretAttached)
class AddToggle(Function.Function):
"""addtoggle([state])
Add a toggle to the tank. The state of the toggle defaults to 0 (False).
These essentially act as a single bit of memory.
Use the toggle() condition to check its state and the settoggle, cleartoggle,
and toggle actions to change the state. Toggles are named numerically,
starting at 0.
"""
def __init__(self, state=0):
self._state = state
def __call__(self, tank):
if len(tank.toggles) >= tank.SENSOR_LIMIT:
raise ValueError('You can not have more than 10 toggles.')
tank.toggles.append(self._state)
class AddTimer(Function.Function):
"""addtimer(timeout)
Add a new timer (they're numbered in the order added, starting from 0),
with the given timeout. The timeout is in number of turns. The timer
is created in inactive mode. You'll need to do a starttimer() action
to reset and start the timer. When the timer expires, the timer()
condition will begin to return True."""
def __init__(self, timeout):
self._timeout = timeout
def __call__(self, tank):
tank.addTimer(timeout)
setup = {'addsensor': AddSensor,
'addtoggle': AddToggle,
'addtimer': AddTimer}

View File

@ -84,7 +84,7 @@ function Tank(ctx, width, height, color, sensors) {
var s = this.sensors[i];
var adj = this.turret * s[3];
if (self.sensor_state & (1 << i)) {
if (this.sensor_state & (1 << i)) {
// Sensor is triggered
ctx.strokeStyle = "#000";
} else {