mirror of https://github.com/dirtbags/moth.git
Change tanks language to forf
This commit is contained in:
parent
ca6e158fc8
commit
22c6e09647
|
@ -1,5 +1,18 @@
|
||||||
random(1,10): move(50, 100);
|
Name: berzerker
|
||||||
random(1,10): move(100, 50);
|
Author: Neale
|
||||||
random(1,10): turretcw();
|
|
||||||
random(1,10): turretccw();
|
2 random
|
||||||
random(1,30): fire();
|
0 =
|
||||||
|
{ 50 100 move }
|
||||||
|
{ 100 50 move }
|
||||||
|
ifelse
|
||||||
|
|
||||||
|
4 random
|
||||||
|
0 =
|
||||||
|
{ 360 random setturret }
|
||||||
|
if
|
||||||
|
|
||||||
|
30 random
|
||||||
|
0 =
|
||||||
|
{ fire }
|
||||||
|
if
|
||||||
|
|
|
@ -1,8 +1,31 @@
|
||||||
>addsensor(50, 0, 0, 1);
|
Name: Rabbit With Gun
|
||||||
>addsensor(70, 0, 50); # 1-Anti-collision sensor
|
Author: Neale
|
||||||
|
Sensor: 50 0 0 1
|
||||||
|
Sensor: 70 0 50 0
|
||||||
|
|
||||||
: move(100, 100) . turretset(180);
|
100 100 move
|
||||||
random(1, 8): move(70, 100);
|
|
||||||
random(1, 8): move(100, 70);
|
( Always set turret to 180 degrees )
|
||||||
sense(0) : fire();
|
180 setturret
|
||||||
sense(1) : move(-0, 100);
|
|
||||||
|
( 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
|
||||||
|
|
|
@ -1,57 +1,55 @@
|
||||||
# 3
|
Name: crashmaster
|
||||||
# 000 33
|
Author: Neale
|
||||||
# 2
|
Sensor: 50 0 8 1
|
||||||
# 2
|
Sensor: 30 0 50 0
|
||||||
# 2
|
Sensor: 50 0 10 0
|
||||||
# 11111 4
|
Sensor: 100 315 100 1
|
||||||
# 4
|
Sensor: 100 45 100 1
|
||||||
# 4
|
Sensor: 60 180 180 0
|
||||||
# @@/
|
|
||||||
# @@@
|
|
||||||
# @@@
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#
|
|
||||||
|
|
||||||
|
( Mem 0: Turn number )
|
||||||
|
0 fetch
|
||||||
|
1 +
|
||||||
|
0 store
|
||||||
|
|
||||||
>addsensor(50, 0, 05, 1); # 0 Fire Sensor
|
( Mem 1: Move turret (procedure) )
|
||||||
>addsensor(30, 0, 50); # 1 Anti-collision sensor
|
{
|
||||||
>addsensor(50, 0, 10); # 2 Anti-collision sensor
|
getturret
|
||||||
>addsensor(100, 315, 100, 1); # 3 Turret ccw
|
-
|
||||||
>addsensor(100, 45, 100, 1); # 4 Turret cw
|
setturret
|
||||||
>addsensor(60, 180, 180, 0); # 5 Ass
|
} 1 store
|
||||||
|
|
||||||
##
|
0 fetch 30 % 10 / ( [0..2], changes every 10 turns )
|
||||||
## Add "ears" so the tank is easy to pick out.
|
dup 0 = { 80 80 move } if
|
||||||
##
|
dup 1 = { 60 80 move } if
|
||||||
>addsensor(20, 90, 30, 0);
|
dup 2 = { 80 60 move } if
|
||||||
>addsensor(20, 270, 30, 0);
|
pop
|
||||||
|
|
||||||
# Can't fire
|
0 setturret
|
||||||
: 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);
|
|
||||||
|
|
||||||
sense(0) : led(0) . move(10, 20) . turretset(0);
|
fireready
|
||||||
sense(1) : led(0) . move(10, 10) . turretset(0);
|
{
|
||||||
sense(2) : led(0) . move(10, 20) . turretset(0);
|
( Behavior for when we can shoot )
|
||||||
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);
|
|
||||||
|
|
||||||
|
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
|
setled
|
||||||
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);
|
|
||||||
|
|
||||||
fireready() & sense(3) : led(1) . move(0, 60) . turretccw(50);
|
0 sensoractive { 10 20 move } if
|
||||||
fireready() & sense(4) : led(1) . move(60, 0) . turretcw(50);
|
1 sensoractive { 10 10 move } if
|
||||||
fireready() & sense(3) & sense(4): led(1) . move(100, 100) . turretset();
|
2 sensoractive { 10 20 move } if
|
||||||
fireready() & sense(1) : led(1) . turretset(0) . move(10, 10);
|
3 sensoractive { 70 50 move } if
|
||||||
fireready() & sense(2) : led(1) . turretset(0) . move(10, 10);
|
4 sensoractive { 50 70 move } if
|
||||||
fireready() & sense(0) : led(1) . turretset() . fire();
|
3 sensoractive 4 sensoractive & { -100 20 move } if
|
||||||
|
5 sensoractive { 100 50 move } if
|
||||||
fireready() & sense(5) : led(1) . move(100, 40);
|
}
|
||||||
|
ifelse
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -66,11 +66,11 @@ class Pflanzarr:
|
||||||
players.remove(player)
|
players.remove(player)
|
||||||
color = '#' + teams.color(player)
|
color = '#' + teams.color(player)
|
||||||
tank = Tank.Tank( player, (startX, startY), color,
|
tank = Tank.Tank( player, (startX, startY), color,
|
||||||
self._board, testMode=True)
|
self._board)
|
||||||
if player == None:
|
if player == None:
|
||||||
tank.program(random.choice(defaultAIs))
|
tank.set_program(random.choice(defaultAIs))
|
||||||
else:
|
else:
|
||||||
tank.program(AIs[player])
|
tank.set_program(AIs[player])
|
||||||
self._tanks.append(tank)
|
self._tanks.append(tank)
|
||||||
|
|
||||||
# We only want to make these once, so we do it here.
|
# We only want to make these once, so we do it here.
|
||||||
|
@ -247,19 +247,15 @@ class Pflanzarr:
|
||||||
|
|
||||||
def _outputErrors(self, tank):
|
def _outputErrors(self, tank):
|
||||||
"""Output errors for each team."""
|
"""Output errors for each team."""
|
||||||
|
|
||||||
|
out = tank.program.get_output()
|
||||||
|
print 'Errors %r: %r' % (tank, out)
|
||||||
|
|
||||||
if tank.name == None:
|
if tank.name == None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if tank._program.errors:
|
|
||||||
print tank.name, 'has errors'
|
|
||||||
|
|
||||||
|
|
||||||
fileName = os.path.join(self._errorDir, quote(tank.name, ''))
|
fileName = os.path.join(self._errorDir, quote(tank.name, ''))
|
||||||
file = open(fileName, 'w')
|
open(fileName, 'w').write(tank.program.get_output())
|
||||||
for error in tank._program.errors:
|
|
||||||
file.write(error)
|
|
||||||
file.write('\n')
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
def _getNear(self):
|
def _getNear(self):
|
||||||
"""A dictionary of the set of tanks nearby each tank. Nearby is
|
"""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.
|
# Setup all the directories we'll need.
|
||||||
self._resultsDir = os.path.join(dir, 'results')
|
self._resultsDir = os.path.join(dir, 'results')
|
||||||
self._errorDir = os.path.join(dir, 'errors')
|
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):
|
def _getDefaultAIs(self, basedir):
|
||||||
"""Load all the house bot AIs."""
|
"""Load all the house bot AIs."""
|
||||||
defaultAIs = []
|
defaultAIs = []
|
||||||
|
|
||||||
path = os.path.join(basedir, 'ai', 'house')
|
path = os.path.join(basedir, 'house')
|
||||||
files = os.listdir(path)
|
files = os.listdir(path)
|
||||||
for fn in files:
|
for fn in files:
|
||||||
if fn.startswith('.'):
|
if fn.startswith('.'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
fn = os.path.join(path, fn)
|
fn = os.path.join(path, fn)
|
||||||
file = open(fn)
|
f = open(fn)
|
||||||
defaultAIs.append(file.read())
|
defaultAIs.append(f.read())
|
||||||
|
|
||||||
return defaultAIs
|
return defaultAIs
|
||||||
|
|
||||||
|
|
|
@ -1,234 +1,100 @@
|
||||||
"""<H2>Introduction</H2>
|
#! /usr/bin/python
|
||||||
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.
|
|
||||||
|
|
||||||
<H2>Programming Your Tank</H2>
|
import forf
|
||||||
Your tanks are programmed using the Super Useful Command and Kontrol language,
|
import random
|
||||||
the very best in laser tank AI languages. It includes amazing features such
|
import rfc822
|
||||||
as comments (Started by a #, ended at EOL), logic, versatility, and
|
from cStringIO import StringIO
|
||||||
semi-colons (all lines must end in one). As with all new military systems
|
from math import pi
|
||||||
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>
|
|
||||||
|
|
||||||
The AI section will act as the brain of your tank. Each AI line is
|
def deg2rad(deg):
|
||||||
separated into a group of conditions functions and a group of action
|
return float(deg) * pi / 180
|
||||||
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>
|
|
||||||
|
|
||||||
Your tank will check its program each turn, and attempt to the best of its
|
def rad2deg(rad):
|
||||||
abilities to carry out its orders (or die trying). Like any military mind,
|
return int(rad * 180 / pi)
|
||||||
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
|
class Environment(forf.Environment):
|
||||||
subsystem is to have that subsystem follow only the last order given each turn.
|
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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import traceback
|
message = rfc822.Message(f)
|
||||||
import conditions
|
print 'reading tank %s' % message['Name']
|
||||||
import actions
|
sensors = message.getallmatchingheaders('Sensor')
|
||||||
import setup
|
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()
|
||||||
|
|
||||||
class Statement(object):
|
def run(self):
|
||||||
"""Represents a single program statement. If all the condition Functions
|
self.env.eval()
|
||||||
evaluate to True, the actions are all executed in order."""
|
|
||||||
|
|
||||||
def __init__(self, lineNum, line, conditions, actions):
|
|
||||||
self.lineNum = lineNum
|
|
||||||
self.line = line
|
|
||||||
self._conditions = conditions
|
|
||||||
self._actions = actions
|
|
||||||
|
|
||||||
def __call__(self, tank):
|
|
||||||
success = True
|
|
||||||
for condition in self._conditions:
|
|
||||||
if not condition(tank):
|
|
||||||
success = False
|
|
||||||
break
|
|
||||||
|
|
||||||
if success:
|
|
||||||
for action in self._actions:
|
|
||||||
action(tank)
|
|
||||||
|
|
||||||
class Program(object):
|
|
||||||
"""This parses and represents a Tank program."""
|
|
||||||
CONDITION_SEP = '&'
|
|
||||||
ACTION_SEP = '.'
|
|
||||||
|
|
||||||
def __init__(self, text):
|
|
||||||
"""Initialize this program, parsing the given text."""
|
|
||||||
self.errors = []
|
|
||||||
|
|
||||||
self._program, self._setup = self._parse(text)
|
|
||||||
|
|
||||||
def setup(self, tank):
|
|
||||||
"""Execute all the setup actions."""
|
|
||||||
for action in self._setup:
|
|
||||||
try:
|
|
||||||
action(tank)
|
|
||||||
except Exception, msg:
|
|
||||||
self.errors.append("Bad setup action, line %d, msg: %s" % \
|
|
||||||
(action.lineNum, msg))
|
|
||||||
|
|
||||||
def __call__(self, tank):
|
|
||||||
"""Execute this program on the given tank."""
|
|
||||||
for statement in self._program:
|
|
||||||
try:
|
|
||||||
statement(tank)
|
|
||||||
except Exception, msg:
|
|
||||||
traceback.print_exc()
|
|
||||||
self.errors.append('Error executing program. \n'
|
|
||||||
'(%d) - %s\n'
|
|
||||||
'msg: %s\n' %
|
|
||||||
(statement.lineNum, statement.line, msg) )
|
|
||||||
|
|
||||||
def _parse(self, text):
|
|
||||||
"""Parse the text of the given program."""
|
|
||||||
program = []
|
|
||||||
setup = []
|
|
||||||
inSetup = True
|
|
||||||
lines = text.split(';')
|
|
||||||
lineNum = 0
|
|
||||||
for line in lines:
|
|
||||||
lineNum = lineNum + 1
|
|
||||||
|
|
||||||
originalLine = line
|
|
||||||
|
|
||||||
# Remove Comments
|
|
||||||
parts = line.split('\n')
|
|
||||||
for i in range(len(parts)):
|
|
||||||
comment = parts[i].find('#')
|
|
||||||
if comment != -1:
|
|
||||||
parts[i] = parts[i][:comment]
|
|
||||||
# Remove all whitespace
|
|
||||||
line = ''.join(parts)
|
|
||||||
line = line.replace('\r', '')
|
|
||||||
line = line.replace('\t', '')
|
|
||||||
line = line.replace(' ', '')
|
|
||||||
|
|
||||||
if line == '':
|
|
||||||
continue
|
|
||||||
|
|
||||||
if line.startswith('>'):
|
|
||||||
if inSetup:
|
|
||||||
if '>' in line[1:] or ':' in line:
|
|
||||||
self.errors.append('(%d) Missing semicolon: %s' %
|
|
||||||
(lineNum, line))
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
setupAction = self._parseSection(line[1:], 'setup')[0]
|
|
||||||
setupAction.lineNum = lineNum
|
|
||||||
setup.append(setupAction)
|
|
||||||
except Exception, msg:
|
|
||||||
self.errors.append('(%d) Error parsing setup line: %s'
|
|
||||||
'\nThe error was: %s' %
|
|
||||||
(lineNum, originalLine, msg))
|
|
||||||
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
self.errors.append('(%d) Setup lines aren\'t allowed '
|
|
||||||
'after the first command: %s' %
|
|
||||||
(lineNum, originalLine))
|
|
||||||
else:
|
|
||||||
# We've hit the first non-blank, non-comment, non-setup
|
|
||||||
# line
|
|
||||||
inSetup = False
|
|
||||||
|
|
||||||
semicolons = line.count(':')
|
|
||||||
if semicolons > 1:
|
|
||||||
self.errors.append('(%d) Missing semicolon: %s' %
|
|
||||||
(lineNum, line))
|
|
||||||
continue
|
|
||||||
elif semicolons == 1:
|
|
||||||
conditions, actions = line.split(':')
|
|
||||||
else:
|
|
||||||
self.errors.append('(%d) Invalid Line, no ":" seperator: %s'%
|
|
||||||
(lineNum, line) )
|
|
||||||
|
|
||||||
try:
|
|
||||||
conditions = self._parseSection(conditions, 'condition')
|
|
||||||
except Exception, msg:
|
|
||||||
self.errors.append('(%d) %s - "%s"' %
|
|
||||||
(lineNum, msg, line) )
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
actions = self._parseSection(actions, 'action')
|
|
||||||
except Exception, msg:
|
|
||||||
self.errors.append('(%d) %s - "%s"' %
|
|
||||||
(lineNum, msg, originalLine) )
|
|
||||||
continue
|
|
||||||
program.append(Statement(lineNum, line, conditions, actions))
|
|
||||||
|
|
||||||
return program, setup
|
|
||||||
|
|
||||||
def _parseSection(self, section, sectionType):
|
|
||||||
"""Parses either the action or condition section of each command.
|
|
||||||
@param section: The text of the section of the command to be parsed.
|
|
||||||
@param sectionType: The type of section to be parsed. Should be:
|
|
||||||
'condition', 'action', or 'setup'.
|
|
||||||
@raises ValueError: Raises ValueErrors when parsing errors occur.
|
|
||||||
@returns: Returns a list of parsed section components (Function objects).
|
|
||||||
"""
|
|
||||||
|
|
||||||
if sectionType == 'condition':
|
|
||||||
parts = section.split(self.CONDITION_SEP)
|
|
||||||
functions = conditions.conditions
|
|
||||||
if section == '':
|
|
||||||
return []
|
|
||||||
elif sectionType == 'action':
|
|
||||||
parts = section.split(self.ACTION_SEP)
|
|
||||||
functions = actions.actions
|
|
||||||
if section == '':
|
|
||||||
raise ValueError("The action section cannot be empty.")
|
|
||||||
elif sectionType == 'setup':
|
|
||||||
parts = [section]
|
|
||||||
functions = setup.setup
|
|
||||||
else:
|
|
||||||
raise ValueError('Invalid section Type - Contact Contest Admin')
|
|
||||||
|
|
||||||
parsed = []
|
|
||||||
for part in parts:
|
|
||||||
|
|
||||||
pos = part.find('(')
|
|
||||||
if pos == -1:
|
|
||||||
raise ValueError("Missing open paren in %s: %s" %
|
|
||||||
(sectionType, part) )
|
|
||||||
funcName = part[:pos]
|
|
||||||
|
|
||||||
if funcName not in functions:
|
|
||||||
raise ValueError("%s function %s is not accepted." %
|
|
||||||
(sectionType.capitalize(), funcName) )
|
|
||||||
|
|
||||||
if part[-1] != ')':
|
|
||||||
raise ValueError("Missing closing paren in %s: %s" %
|
|
||||||
(condition, sectionType) )
|
|
||||||
|
|
||||||
args = part[pos+1:-1]
|
|
||||||
if args != '':
|
|
||||||
args = args.split(',')
|
|
||||||
for i in range(len(args)):
|
|
||||||
args[i] = int(args[i])
|
|
||||||
else:
|
|
||||||
args = []
|
|
||||||
|
|
||||||
parsed.append(functions[funcName](*args))
|
|
||||||
|
|
||||||
return parsed
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ from sets import Set as set
|
||||||
|
|
||||||
import GameMath as gm
|
import GameMath as gm
|
||||||
import Program
|
import Program
|
||||||
|
from cStringIO import StringIO
|
||||||
|
|
||||||
class Tank(object):
|
class Tank(object):
|
||||||
|
|
||||||
|
@ -15,9 +16,9 @@ class Tank(object):
|
||||||
# This is what is used for collision and hit detection.
|
# This is what is used for collision and hit detection.
|
||||||
RADIUS = 7.5
|
RADIUS = 7.5
|
||||||
# Max speed, in pixels
|
# Max speed, in pixels
|
||||||
SPEED = 7.0
|
MAXSPEED = 7.0
|
||||||
# Max acceleration, as a fraction of speed.
|
# Max acceleration, as a fraction of speed.
|
||||||
ACCEL = 35
|
MAXACCEL = 35
|
||||||
# Sensor range, in pixels
|
# Sensor range, in pixels
|
||||||
SENSOR_RANGE = 90.0
|
SENSOR_RANGE = 90.0
|
||||||
# Max turret turn rate, in radians
|
# Max turret turn rate, in radians
|
||||||
|
@ -26,8 +27,7 @@ class Tank(object):
|
||||||
# The max number of sensors/timers/toggles
|
# The max number of sensors/timers/toggles
|
||||||
SENSOR_LIMIT = 10
|
SENSOR_LIMIT = 10
|
||||||
|
|
||||||
def __init__(self, name, pos, color, boardSize, angle=None, tAngle=None,
|
def __init__(self, name, pos, color, boardSize, angle=None, tAngle=None):
|
||||||
testMode=True):
|
|
||||||
"""Create a new tank.
|
"""Create a new tank.
|
||||||
@param name: The name name of the tank. Stored in self.name.
|
@param name: The name name of the tank. Stored in self.name.
|
||||||
@param pos: The starting position of the tank (x,y)
|
@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 boardSize: The size of the board. (maxX, maxY)
|
||||||
@param angle: The starting angle of the tank, defaults to random.
|
@param angle: The starting angle of the tank, defaults to random.
|
||||||
@param tAngle: The starting turretAngle 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.name = name
|
||||||
self._testMode = testMode
|
|
||||||
|
|
||||||
assert len(pos) == 2 and pos[0] > 0 and pos[1] > 0, \
|
assert len(pos) == 2 and pos[0] > 0 and pos[1] > 0, \
|
||||||
'Bad starting position: %s' % str(pos)
|
'Bad starting position: %s' % str(pos)
|
||||||
|
@ -100,18 +93,17 @@ class Tank(object):
|
||||||
|
|
||||||
# Is this tank dead?
|
# Is this tank dead?
|
||||||
self.isDead = False
|
self.isDead = False
|
||||||
# The frame of the death animation.
|
|
||||||
self._deadFrame = 10
|
|
||||||
# Death reason
|
# Death reason
|
||||||
self.deathReason = 'survived'
|
self.deathReason = 'survived'
|
||||||
|
|
||||||
|
# Something to log to
|
||||||
|
self.stdout = StringIO()
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<tank: %s, (%d, %d)>' % (self.name, self.pos[0], self.pos[1])
|
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):
|
def fire(self, near):
|
||||||
"""Shoots, if ordered to and able. Returns the set of tanks
|
"""Shoots, if ordered to and able. Returns the set of tanks
|
||||||
destroyed."""
|
destroyed."""
|
||||||
|
@ -143,7 +135,7 @@ class Tank(object):
|
||||||
"""Add a sensor to this tank.
|
"""Add a sensor to this tank.
|
||||||
@param angle: The angle, from the tanks front and going clockwise,
|
@param angle: The angle, from the tanks front and going clockwise,
|
||||||
of the center of the sensor. (radians)
|
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 range: The range of the sensor (percent)
|
||||||
@param attachedTurret: If set, the sensor moves with the turret.
|
@param attachedTurret: If set, the sensor moves with the turret.
|
||||||
"""
|
"""
|
||||||
|
@ -170,6 +162,9 @@ class Tank(object):
|
||||||
|
|
||||||
self._nextMove = left, right
|
self._nextMove = left, right
|
||||||
|
|
||||||
|
def getTurretAngle(self):
|
||||||
|
return self._tAngle
|
||||||
|
|
||||||
def setTurretAngle(self, angle=None):
|
def setTurretAngle(self, angle=None):
|
||||||
"""Set the desired angle of the turret. No angle means the turret
|
"""Set the desired angle of the turret. No angle means the turret
|
||||||
should remain stationary."""
|
should remain stationary."""
|
||||||
|
@ -187,60 +182,26 @@ class Tank(object):
|
||||||
"""Returns True if the tank can fire now."""
|
"""Returns True if the tank can fire now."""
|
||||||
return self._fireReady == 0
|
return self._fireReady == 0
|
||||||
|
|
||||||
def addTimer(self, period):
|
def setLED(self):
|
||||||
"""Add a timer with timeout period 'period'."""
|
self.led = True
|
||||||
|
|
||||||
if len(self._timers) >= self.SENSOR_LIMIT:
|
def set_program(self, text):
|
||||||
raise ValueError('You can only have 10 timers')
|
|
||||||
|
|
||||||
self._timers.append(None)
|
|
||||||
self._timerPeriods.append(period)
|
|
||||||
|
|
||||||
def resetTimer(self, key):
|
|
||||||
"""Reset, and start the given timer, but only if it is inactive.
|
|
||||||
If it is active, raise a ValueError."""
|
|
||||||
if self._timer[key] is None:
|
|
||||||
self._timer[key] = self._timerPeriods[key]
|
|
||||||
else:
|
|
||||||
raise ValueError("You can't reset an active timer (#%d)" % key)
|
|
||||||
|
|
||||||
def clearTimer(self, key):
|
|
||||||
"""Clear the timer."""
|
|
||||||
self._timer[key] = None
|
|
||||||
|
|
||||||
def checkTimer(self, key):
|
|
||||||
"""Returns True if the timer has expired."""
|
|
||||||
return self._timer[key] == 0
|
|
||||||
|
|
||||||
def _manageTimers(self):
|
|
||||||
"""Decrement each active timer."""
|
|
||||||
for i in range(len(self._timers)):
|
|
||||||
if self._timers[i] is not None and \
|
|
||||||
self._timers[i] > 0:
|
|
||||||
self._timers[i] = self._timers[i] - 1
|
|
||||||
|
|
||||||
def program(self, text):
|
|
||||||
"""Set the program for this tank."""
|
"""Set the program for this tank."""
|
||||||
|
|
||||||
self._program = Program.Program(text)
|
self.program = Program.Program(self, text)
|
||||||
self._program.setup(self)
|
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
"""Execute this tanks program."""
|
"""Execute this tanks program."""
|
||||||
|
|
||||||
# Decrement the active timers
|
|
||||||
self._manageTimers()
|
|
||||||
self.led = False
|
self.led = False
|
||||||
|
|
||||||
self._program(self)
|
self.program.run()
|
||||||
|
|
||||||
self._move(self._nextMove[0], self._nextMove[1])
|
self._move(self._nextMove[0], self._nextMove[1])
|
||||||
self._moveTurret()
|
self._moveTurret()
|
||||||
if self._fireReady > 0:
|
if self._fireReady > 0:
|
||||||
self._fireReady = self._fireReady - 1
|
self._fireReady = self._fireReady - 1
|
||||||
|
|
||||||
self._turn = self._turn + 1
|
|
||||||
|
|
||||||
def sense(self, near):
|
def sense(self, near):
|
||||||
"""Detect collisions and trigger sensors. Returns the set of
|
"""Detect collisions and trigger sensors. Returns the set of
|
||||||
tanks collided with, plus this one. We do both these steps at once
|
tanks collided with, plus this one. We do both these steps at once
|
||||||
|
@ -324,24 +285,24 @@ class Tank(object):
|
||||||
|
|
||||||
# Handle acceleration
|
# Handle acceleration
|
||||||
if self._lastSpeed[0] < lSpeed and \
|
if self._lastSpeed[0] < lSpeed and \
|
||||||
self._lastSpeed[0] + self.ACCEL < lSpeed:
|
self._lastSpeed[0] + self.MAXACCEL < lSpeed:
|
||||||
lSpeed = self._lastSpeed[0] + self.ACCEL
|
lSpeed = self._lastSpeed[0] + self.MAXACCEL
|
||||||
elif self._lastSpeed[0] > lSpeed and \
|
elif self._lastSpeed[0] > lSpeed and \
|
||||||
self._lastSpeed[0] - self.ACCEL > lSpeed:
|
self._lastSpeed[0] - self.MAXACCEL > lSpeed:
|
||||||
lSpeed = self._lastSpeed[0] - self.ACCEL
|
lSpeed = self._lastSpeed[0] - self.MAXACCEL
|
||||||
|
|
||||||
if self._lastSpeed[1] < rSpeed and \
|
if self._lastSpeed[1] < rSpeed and \
|
||||||
self._lastSpeed[1] + self.ACCEL < rSpeed:
|
self._lastSpeed[1] + self.MAXACCEL < rSpeed:
|
||||||
rSpeed = self._lastSpeed[1] + self.ACCEL
|
rSpeed = self._lastSpeed[1] + self.MAXACCEL
|
||||||
elif self._lastSpeed[1] > rSpeed and \
|
elif self._lastSpeed[1] > rSpeed and \
|
||||||
self._lastSpeed[1] - self.ACCEL > rSpeed:
|
self._lastSpeed[1] - self.MAXACCEL > rSpeed:
|
||||||
rSpeed = self._lastSpeed[1] - self.ACCEL
|
rSpeed = self._lastSpeed[1] - self.MAXACCEL
|
||||||
|
|
||||||
self._lastSpeed = lSpeed, rSpeed
|
self._lastSpeed = lSpeed, rSpeed
|
||||||
|
|
||||||
# The simple case
|
# The simple case
|
||||||
if lSpeed == rSpeed:
|
if lSpeed == rSpeed:
|
||||||
fSpeed = self.SPEED*lSpeed/100
|
fSpeed = self.MAXSPEED*lSpeed/100
|
||||||
x = fSpeed*math.cos(self._angle)
|
x = fSpeed*math.cos(self._angle)
|
||||||
y = fSpeed*math.sin(self._angle)
|
y = fSpeed*math.sin(self._angle)
|
||||||
# Adjust our position
|
# Adjust our position
|
||||||
|
@ -354,9 +315,9 @@ class Tank(object):
|
||||||
# around the circle varies with the speed of each tread, and is
|
# 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
|
# such that each side of the tank moves an equal angle around the
|
||||||
# circle.
|
# circle.
|
||||||
L = self.SPEED * lSpeed/100.0
|
L = self.MAXSPEED * lSpeed/100.0
|
||||||
R = self.SPEED * rSpeed/100.0
|
R = self.MAXSPEED * rSpeed/100.0
|
||||||
friction = .75 * abs(L-R)/(2.0*self.SPEED)
|
friction = .75 * abs(L-R)/(2.0*self.MAXSPEED)
|
||||||
L = L * (1 - friction)
|
L = L * (1 - friction)
|
||||||
R = R * (1 - friction)
|
R = R * (1 - friction)
|
||||||
|
|
||||||
|
|
126
tanks/actions.py
126
tanks/actions.py
|
@ -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}
|
|
|
@ -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 }
|
|
|
@ -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()
|
|
@ -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}
|
|
|
@ -84,7 +84,7 @@ function Tank(ctx, width, height, color, sensors) {
|
||||||
var s = this.sensors[i];
|
var s = this.sensors[i];
|
||||||
var adj = this.turret * s[3];
|
var adj = this.turret * s[3];
|
||||||
|
|
||||||
if (self.sensor_state & (1 << i)) {
|
if (this.sensor_state & (1 << i)) {
|
||||||
// Sensor is triggered
|
// Sensor is triggered
|
||||||
ctx.strokeStyle = "#000";
|
ctx.strokeStyle = "#000";
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue