Permalink
Browse files

Merge pull request #44 from elachuni/master

Updated sulu
  • Loading branch information...
2 parents f922f7a + 0a6907c commit 122c51b40c7e941433d598bb8da5a1dbc935084f @elachuni elachuni committed Jul 8, 2012
Showing with 120 additions and 59 deletions.
  1. +120 −59 bots/sulu.py
View
@@ -2,14 +2,12 @@
from twisted.internet import reactor
from random import shuffle, choice
from spacecraft.client_helpers import relative_angle
-from spacecraft.euclid import LineSegment2, Point2
+from spacecraft.euclid import LineSegment2, Point2, Matrix3
from math import sqrt, atan2, pi
import spacecraft
TILE_SIZE = 10
-INITIAL_FOCUS = 50
TWO_PI = 2 * pi
-HALF_A_DEGREE = pi / 360
class Wall(object):
def __init__(self, x, y, w, h):
@@ -22,8 +20,11 @@ def __init__(self, x, y, w, h):
self.segment = LineSegment2(Point2(xavg, y), Point2(xavg, y + h))
def blocks(self, other):
- result = self.segment.intersect(other)
- return result is not None
+ return self.intersect(other) is not None
+
+ def intersect(self, other):
+ return self.segment.intersect(other)
+
class Tile(object):
def __init__(self, x, y, size):
@@ -58,6 +59,16 @@ def __str__(self):
__repr__ = __str__
+class Guesstimate(object):
+ def __init__(self, x, y, speedx, speedy):
+ self.x, self.y, self.speedx, self.speedy = x, y, speedx, speedy
+ self.certainty = 50
+
+ def step(self):
+ self.x += self.speedx
+ self.y += self.speedy
+ self.certainty -= 1
+
class NavigatorClient(spacecraft.server.ClientBase):
@property
def name(self):
@@ -69,66 +80,88 @@ def __init__(self):
self.tiles = []
self.walls = []
self.going = None
- self.focus = 0
+ self.guesstimate = None
def messageReceived(self, message):
- #~ print message
- if message.get('type') == 'sensor':
- if 'gps' not in message:
- return
- self.command("throttle", value=1)
- x, y = message['gps']['position']
- angle = message['gps']['angle']
- speedx, speedy = message['gps']['velocity']
- tracking = False
- # Increase visited tile count
- self.visit(x, y)
-
- # Check if we should track
- for obj in message.get('proximity', []):
- if obj['object_type'] in ['powerup', 'player']:
- trackx, tracky = obj['position']
- trackspeedx, trackspeedy = obj['velocity']
- if self.has_line_of_fire(x, y, trackx, tracky):
- tracking = True
- d = sqrt((x - trackx)**2 + (y - tracky)**2)
+ parser = getattr(self, 'parse_' + message.get('type', ''), None)
+ if parser:
+ parser(message)
+
+ def parse_sensor(self, message):
+ if 'gps' not in message:
+ return
+ x, y = message['gps']['position']
+ angle = message['gps']['angle']
+ speedx, speedy = message['gps']['velocity']
+ tracking = False
+ # Increase visited tile count
+ self.visit(x, y)
+
+ # Check if we should track
+ for obj in message.get('proximity', []):
+ # FIXME: If there's a player, forget the powerup!
+ if obj['object_type'] in ['powerup', 'player']:
+ trackx, tracky = obj['position']
+ trackspeedx, trackspeedy = obj['velocity']
+ if self.has_line_of_fire(x, y, trackx, tracky):
+ tracking = True
+ d = sqrt((x - trackx)**2 + (y - tracky)**2)
+ if obj['object_type'] == 'player':
+ turn = relative_angle(x, y, trackx, tracky, angle)
+ self.command('turn', value=turn)
+ self.guesstimate = Guesstimate(trackx, tracky, trackspeedx, trackspeedy)
+ self.command("fire")
+ else:
# (x + speedx, y + speedy) here, to compensate the "orbit" effect
turn = relative_angle(
x + speedx * d / 70, y + speedy * d / 70,
trackx, tracky, angle)
self.command('turn', value=turn)
- if obj['object_type'] == 'player':
- self.command("fire")
- break
- if not tracking:
- # Pick a good next exploration tile
- if (self.going is None or self.focus <= 0 or
- not self.has_line_of_fire(x, y, self.going.x, self.going.y)):
- options = [t for t in self.tiles
- if self.has_line_of_fire(x, y, t.x, t.y)]
- if not options:
- return
- options.sort(key=lambda x: x.visits)
- options = [o for o in options if o.visits == options[0].visits]
- #~ print "Options:", options
- self.going = max(options, key=lambda t:t.distance_to(x, y))
- # Random exploration
- #~ shuffle(options)
- #~ self.going = min(options, key=lambda t:t.visits)
- self.focus = INITIAL_FOCUS
- print "Going to: %s, focus=%s" % (self.going, self.focus)
-
+ self.command("throttle", value=1)
+ #~ else:
+ #~ print obj
+ if not tracking:
+ if self.guesstimate and self.guesstimate.certainty > 0:
+ turn = relative_angle(x, y, self.guesstimate.x, self.guesstimate.y, angle)
+ self.command('turn', value=turn)
+ self.command("fire")
+ self.guesstimate.step()
+ return
+ # Pick a good next exploration tile
+ if self.going is None or not self.has_line_of_fire(x, y, self.going.x, self.going.y):
+ options = [t for t in self.tiles
+ if self.has_line_of_fire(x, y, t.x, t.y)]
+ if not options:
+ return
+ options.sort(key=lambda x: x.visits)
+ options = [o for o in options if o.visits == options[0].visits]
+ #~ print "Options:", options
+ self.going = max(options, key=lambda t:t.distance_to(x, y))
+ # Random exploration
+ #~ shuffle(options)
+ #~ self.going = min(options, key=lambda t:t.visits)
+ speed = speedx**2 + speedy**2
+ speedangle = atan2(speedy, speedx)
+ targetangle = atan2(self.going.y - y, self.going.x - x)
+ #~ print speed, angle, speedangle
+ divergence = (targetangle - speedangle) % TWO_PI
+ divergence = min(divergence, TWO_PI - divergence)
+ if speed > 500 and divergence < 0.1:
+ fireangle = self.pick_fireangle(x, y, speedx, speedy, angle)
+ if fireangle is not None:
+ self.command("turn", value=fireangle)
+ if abs(fireangle) < 1:
+ self.command('fire') # Take that!
+ else:
+ #~ if speed <= 1590:
+ #~ print "Not bang because speed = %.3f" % speed
+ #~ elif divergence >= 0.1:
+ #~ print "Not bang because divergence = %.3f" % divergence
+ #~ else:
+ #~ print "EH!? OGG WANT BANG!!1! Y U NO BANG!??!?1!?"
turn = relative_angle(x, y, self.going.x + speedx, self.going.y + speedy, angle)
- speed = speedx**2 + speedy**2
- speedangle = atan2(speedy, speedx) % TWO_PI
- #~ self.focus -= 1
- print turn, speed, angle, speedangle
- if abs(turn) < 0.005 and speed > 1590 and abs(angle - speedangle) < HALF_A_DEGREE:
- print "bang!"
- else:
- self.command("turn", value=turn)
- elif message.get('type') == 'map_description':
- self.parse_terrain(message)
+ self.command("throttle", value=1)
+ self.command("turn", value=turn)
def has_line_of_fire(self, x, y, trackx, tracky):
segment = LineSegment2(Point2(x, y), Point2(trackx, tracky))
@@ -137,7 +170,7 @@ def has_line_of_fire(self, x, y, trackx, tracky):
return False
return True
- def parse_terrain(self, message):
+ def parse_map_description(self, message):
for wall in message['terrain']:
if wall['type'] != 'wall':
continue
@@ -161,11 +194,39 @@ def parse_terrain(self, message):
def visit(self, x, y):
visited = min(self.tiles, key=lambda t:t.distance_to(x, y))
- print "At", visited
+ #~ print "At", visited
visited.visits += 1
if visited == self.going:
self.going = None
+ def pick_fireangle(self, x, y, speedx, speedy, angle):
+ origin = Point2(x, y)
+ delta = Point2(speedx, speedy)
+ leftangle = Matrix3.new_rotate(pi / 2)
+ rightangle = Matrix3.new_rotate(-pi / 2)
+ targetleft = origin + leftangle * delta * 100
+ targetright = origin + rightangle * delta * 100
+ left = LineSegment2(origin, targetleft)
+ right = LineSegment2(origin, targetright)
+ dleft = dright = 10000000
+ for w in self.walls:
+ ileft = w.intersect(left)
+ if ileft:
+ dleft = min(dleft, ileft.distance(origin))
+ iright = w.intersect(right)
+ if iright:
+ dright = min(dright, iright.distance(origin))
+ if max(dleft, dright) < 25:
+ return
+ COMPENSATE = pi / 8
+ if dleft > dright:
+ targetleft = Matrix3.new_rotate(COMPENSATE) * targetleft
+ result = relative_angle(x, y, targetleft.x, targetleft.y, angle)
+ else:
+ targetright = Matrix3.new_rotate(-COMPENSATE) * targetright
+ result = relative_angle(x, y, targetright.x, targetright.y, angle)
+ return result
+
def main():
factory = ClientFactory()
factory.protocol = NavigatorClient

0 comments on commit 122c51b

Please sign in to comment.