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 @@ -"""
-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 '
-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 {