diff --git a/lib/tanks/easy/berzerker b/lib/tanks/easy/berzerker index e0351d0..b8189a9 100644 --- a/lib/tanks/easy/berzerker +++ b/lib/tanks/easy/berzerker @@ -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(); \ No newline at end of file +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 diff --git a/lib/tanks/easy/rabbitwithgun b/lib/tanks/easy/rabbitwithgun index b820488..bd6e02d 100644 --- a/lib/tanks/easy/rabbitwithgun +++ b/lib/tanks/easy/rabbitwithgun @@ -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); \ No newline at end of file +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 diff --git a/lib/tanks/hard/crashmaster b/lib/tanks/hard/crashmaster index ca94b98..344193e 100644 --- a/lib/tanks/hard/crashmaster +++ b/lib/tanks/hard/crashmaster @@ -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); \ No newline at end of file + 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 diff --git a/tanks/Function.py b/tanks/Function.py deleted file mode 100644 index 406e564..0000000 --- a/tanks/Function.py +++ /dev/null @@ -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 - diff --git a/tanks/Pflanzarr.py b/tanks/Pflanzarr.py index 04cad8d..58fdf4b 100644 --- a/tanks/Pflanzarr.py +++ b/tanks/Pflanzarr.py @@ -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 diff --git a/tanks/Program.py b/tanks/Program.py old mode 100644 new mode 100755 index cab13ab..cb2a824 --- a/tanks/Program.py +++ b/tanks/Program.py @@ -1,234 +1,100 @@ -"""

Introduction

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

Programming Your Tank

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

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

>addsensor(80, 90, 33);
->addsensor(50, 0, 10, 1);
->addtimer(3);
+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: -
-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);
+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 diff --git a/tanks/Tank.py b/tanks/Tank.py index c865564..906f183 100644 --- a/tanks/Tank.py +++ b/tanks/Tank.py @@ -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 '' % (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) diff --git a/tanks/actions.py b/tanks/actions.py deleted file mode 100644 index a03e7af..0000000 --- a/tanks/actions.py +++ /dev/null @@ -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} diff --git a/tanks/conditions.py b/tanks/conditions.py deleted file mode 100644 index 1401d54..0000000 --- a/tanks/conditions.py +++ /dev/null @@ -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 } diff --git a/tanks/forf.py b/tanks/forf.py new file mode 100755 index 0000000..e2c8b29 --- /dev/null +++ b/tanks/forf.py @@ -0,0 +1,290 @@ +#! /usr/bin/python + +"""A shitty FORTH interpreter + +15:58 WELCOME TO FORF! +15:58 *PUNCH* +""" + +import operator + +class ParseError(Exception): + pass + +class Overflow(Exception): + pass + +class Underflow(Exception): + pass + +class Stack: + def __init__(self, init=None, size=50): + self.size = size + self.stack = init or [] + + def __str__(self): + if not self.stack: + return '{}' + guts = ' '.join(repr(i) for i in self.stack) + return '{ %s }' % guts + __repr__ = __str__ + + def push(self, *values): + for val in values: + if len(self.stack) == self.size: + raise Overflow() + self.stack.append(val) + + def extend(self, other): + self.stack.extend(other.stack) + + def dup(self): + return Stack(init=self.stack[:], size=self.size) + + def pop(self): + if not self.stack: + raise Underflow() + return self.stack.pop() + + def mpop(self, n): + return [self.pop() for i in range(n)] + + def __nonzero__(self): + return bool(self.stack) + + +class Environment: + def __init__(self, ticks=2000, codelen=500): + self.ticks = ticks + self.codelen = codelen + self.registers = [0] * 10 + self.unfuncs = {'~' : operator.inv, + '!' : operator.not_, + 'abs': operator.abs, + } + self.binfuncs = {'+' : operator.add, + '-' : operator.sub, + '*' : operator.mul, + '/' : operator.div, + '%' : operator.mod, + '**': operator.pow, + '&' : operator.and_, + '|' : operator.or_, + '^' : operator.xor, + '<<': operator.lshift, + '>>': operator.rshift, + '>' : operator.gt, + '>=': operator.ge, + '<' : operator.lt, + '<=': operator.le, + '=' : operator.eq, + '<>': operator.ne, + '!=': operator.ne, + } + self.data = Stack() + + def get(self, s): + unfunc = self.unfuncs.get(s) + if unfunc: + return self.apply_unfunc(unfunc) + + binfunc = self.binfuncs.get(s) + if binfunc: + return self.apply_binfunc(binfunc) + + try: + return getattr(self, 'cmd_' + s) + except AttributeError: + return None + + def apply_unfunc(self, func): + """Apply a unary function""" + + def f(data): + a = data.pop() + data.push(int(func(a))) + return f + + def apply_binfunc(self, func): + """Apply a binary function""" + + def f(data): + a = data.pop() + b = data.pop() + data.push(int(func(b, a))) + return f + + def run(self, s): + self.parse_str(s) + self.eval() + + def parse_str(self, s): + tokens = s.strip().split() + tokens.reverse() # so .parse can tokens.pop() + self.code = self.parse(tokens) + + def parse(self, tokens, token=0, depth=0): + if depth > 4: + raise ParseError('Maximum recursion depth exceeded at token %d' % token) + code = [] + while tokens: + val = tokens.pop() + token += 1 + f = self.get(val) + if f: + code.append(f) + elif val == '(': + # Comment + while val != ')': + val = tokens.pop() + token += 1 + elif val == '{}': + # Empty block + code.append(Stack()) + elif val == '{': + block = self.parse(tokens, token, depth+1) + code.append(block) + elif val == '}': + break + else: + # The only literals we support are ints + try: + code.append(int(val)) + except ValueError: + raise ParseError('Invalid literal at token %d (%s)' % (token, val)) + if len(code) > self.codelen: + raise ParseError('Code stack overflow') + # Reverse so we can .pop() + code.reverse() + return Stack(code, size=self.codelen) + + def eval(self): + ticks = self.ticks + code_orig = self.code.dup() + while self.code and ticks: + ticks -= 1 + val = self.code.pop() + try: + if callable(val): + val(self.data) + else: + self.data.push(val) + except Underflow: + self.err('Stack underflow at proc %r' % (val)) + except Overflow: + self.err('Stack overflow at proc %r' % (val)) + if self.code: + self.err('Ran out of ticks!') + self.code = code_orig + + def err(self, msg): + print 'Error: %s' % msg + + def msg(self, msg): + print msg + + ## + ## Commands + ## + def cmd_print(self, data): + a = data.pop() + self.msg(a) + + def cmd_dumpstack(self, data): + a = data.pop() + self.msg('(dumpstack %d) %r' % (a, data.stack)) + + def cmd_dumpmem(self, data): + a = data.pop() + self.msg('(dumpmem %d) %r' % (a, self.registers)) + + def cmd_exch(self, data): + a, b = data.mpop(2) + data.push(a, b) + + def cmd_dup(self, data): + a = data.pop() + data.push(a, a) + + def cmd_pop(self, data): + data.pop() + + def cmd_store(self, data): + a, b = data.mpop(2) + self.registers[a % 10] = b + + def cmd_fetch(self, data): + a = data.pop() + data.push(self.registers[a % 10]) + + ## + ## Evaluation commands + ## + def eval_block(self, block): + try: + self.code.extend(block) + except TypeError: + # If it's not a block, just append it + self.code.push(block) + + def cmd_if(self, data): + block = data.pop() + cond = data.pop() + if cond: + self.eval_block(block) + + def cmd_ifelse(self, data): + elseblock = data.pop() + ifblock = data.pop() + cond = data.pop() + if cond: + self.eval_block(ifblock) + else: + self.eval_block(elseblock) + + def cmd_eval(self, data): + # Interestingly, this is the same as "1 exch if" + block = data.pop() + self.eval_block(block) + + def cmd_call(self, data): + # Shortcut for "fetch eval" + self.cmd_fetch(data) + self.cmd_eval(data) + + +def repl(): + env = Environment() + while True: + try: + s = raw_input('>8[= =] ') + except (KeyboardInterrupt, EOFError): + print + break + try: + env.run(s) + print env.data + except ParseError, err: + print r' \ nom nom nom, %s!' % err + print r' \ bye bye!' + +if __name__ == '__main__': + import sys + import time + try: + import readline + except ImportError: + pass + + if len(sys.argv) > 1: + s = open(sys.argv[1]).read() + env = Environment() + begin = time.time() + env.run(s) + end = time.time() + elapsed = end - begin + print 'Evaluated in %.2f seconds' % elapsed + else: + print 'WELCOME TO FORF!' + print '*PUNCH*' + repl() diff --git a/tanks/setup.py b/tanks/setup.py deleted file mode 100644 index 81a402c..0000000 --- a/tanks/setup.py +++ /dev/null @@ -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. -

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

-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} diff --git a/www/tanks/tanks.js b/www/tanks/tanks.js index 2760f50..f416423 100644 --- a/www/tanks/tanks.js +++ b/www/tanks/tanks.js @@ -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 {