Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ channels:
- cogsci
- defaults
dependencies:
- python=3.6
- pandas
- ipykernel
- nb_conda
- jupyter
- matplotlib
- scikit-learn
- scikit-image
- tensorflow
- keras
- python=3.6.8
- pandas=0.23.4
- ipykernel=5.1.0
- nb_conda=2.2.1
- jupyter=1.0.0
- matplotlib=3.0.2
- scikit-learn=0.20.2
- scikit-image=0.14.1
- keras=2.2.4
- pip:
- pygame
- PyGeodesy
- pygame==1.9.4
- PyGeodesy==18.10.29
- gym==0.10.9
- keras-rl==0.4.2
Binary file added fonts/B612-Regular.ttf
Binary file not shown.
16 changes: 4 additions & 12 deletions race.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
# import project code
from environment import Environment
from polar import Polar
from race_simulator import RaceSimulator
from simulators.race_simulator import RaceSimulator
from boat import SimBoat
from settings import Settings

# import configuration
if os.path.isfile('config.py'):
Expand All @@ -19,22 +20,13 @@
# load polar
polar = Polar(os.path.join('data', 'polars', 'first-27.csv'))

# create some buoys
scale = 0.05
buoys = [
(52.3721693, 5.0750607),
(52.3721693 + 0.01 * scale, 5.0750607),
(52.3721693 + 0.008 * scale, 5.0750607 + 0.005 * scale),
(52.3721693 + 0.002 * scale, 5.0750607 + 0.005 * scale),
]

# create environment
env = Environment(buoys=buoys)
env = Environment(buoys=Settings.BUOYS)

# instantiate all strategies
strategies = []
for strategy in strategy_list:
boat = SimBoat(env, polar=polar, random_color=True).set_waypoint(1)
boat = SimBoat(env, polar=polar, random_color=True, name=strategy.__name__).set_waypoint(1)
strategies.append(strategy(boat, env))

# start the simulator
Expand Down
93 changes: 57 additions & 36 deletions src/boat.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ class Boat:
# distance in meters from waypoint to skip to next waypoint
DIST_NEXT_WAYPOINT = 2

def __init__(self, env, random_color=False, name='no-name'):
def __init__(self, env, random_color=False, name='no-name', keep_log=True):
self._keep_log = keep_log
self._name = name
self.rudder_angle = 0.
self.target_rudder_angle = 0.
self.boat_angle = 0.
Expand All @@ -40,11 +42,7 @@ def __init__(self, env, random_color=False, name='no-name'):
self._env = env
self.history = pd.DataFrame()
self.windspeed_shuffle = True
self._name = name
self._position = (52.3721693, 5.0750607)
self._waypoint = None
self._bearing = 0.
self._distance = 0.
self._marks_passed = 0

# set a boat color
Expand All @@ -57,6 +55,19 @@ def __init__(self, env, random_color=False, name='no-name'):
self._draw_fps = Settings.DRAW_FPS

self._strategy = None
self._position = None

# set initial position of boat
self.reset_boat_position()

def reset_boat_position(self):
self._position = (52.3721693, 5.0750607)

def set_heading(self, heading):
self.boat_angle = heading

def get_heading(self):
return self.boat_angle

def set_strategy(self, strategy):
self._strategy = strategy
Expand Down Expand Up @@ -97,6 +108,10 @@ def calculate_speed(self):
speed += 1
return speed

def reset_rudder(self):
self.target_rudder_angle = 0
self.rudder_angle = 0

def move(self):
"""Simulates or fetches movement of boat"""

Expand Down Expand Up @@ -138,42 +153,43 @@ def update(self):
# simulate or fetch boat movements
self.move()

# run navigation
self.nav()
# run navigation when steering strategy is active
if self._strategy is not None:
self.nav()

# skip to next waypoint if we're there
if self._waypoint is not None:
if self.get_distance_to_waypoint() < self.DIST_NEXT_WAYPOINT:
logging.info("hit waypoint")
self._marks_passed += 1
self._waypoint = self._waypoint + 1 if self._waypoint < len(self._env.get_buoys()) - 1 else 0

# save history
self.history = self.history.append([{
'datetime': dt.now(),
'boat_angle': self.boat_angle + np.random.normal(0, 1),
'boat_heel': self.boat_heel if np.random.uniform(0, 1) < 0.99 else np.nan,
'boat_speed': self.speed + np.random.normal(0, 0.25),
'target_angle': self.target_angle if np.random.uniform(0, 1) < 0.99 else np.nan,
'course_error': self.get_course_error(),
'rudder_angle': self.rudder_angle,
'wind_direction': self._env.wind_direction,
'wind_speed': self._env.wind_speed if np.random.uniform(0, 1) < 0.99 else np.random.randint(100, 150),
'angle_of_attack': self.get_angle_of_attack()
}])
if self._keep_log:
self.history = self.history.append([{
'datetime': dt.now(),
'boat_angle': self.boat_angle + np.random.normal(0, 1),
'boat_heel': self.boat_heel if np.random.uniform(0, 1) < 0.99 else np.nan,
'boat_speed': self.speed + np.random.normal(0, 0.25),
'target_angle': self.target_angle if np.random.uniform(0, 1) < 0.99 else np.nan,
'course_error': self.get_course_error(),
'rudder_angle': self.rudder_angle,
'wind_direction': self._env.wind_direction,
'wind_speed': self._env.wind_speed if np.random.uniform(0, 1) < 0.99 else np.random.randint(100, 150),
'angle_of_attack': self.get_angle_of_attack()
}])

def nav(self):
""" Update navigation variables and determine new course """

if self._waypoint is None:
return

buoys = self._env.get_buoys()

# get target position from waypoint
target_pos = buoys[self._waypoint]

# determine bearing to waypoint
self._bearing = geo.bearing(self._position[0], self._position[1], target_pos[0], target_pos[1])

# distance to waypoint
self._distance = geo.haversine(self._position[0], self._position[1], target_pos[0], target_pos[1])
bearing = self.get_bearing_to_waypoint()

# calculate new true wind angle to steer
new_twa = calc_angle(self._env.wind_direction, self._bearing)
new_twa = calc_angle(self._env.wind_direction, bearing)

# get angles for optimal vmg
upwind_twa = self._strategy.get_upwind_twa()
Expand All @@ -189,12 +205,7 @@ def nav(self):

# otherwise, steer directly to waypoint
else:
self.set_target_angle(self._bearing)

# skip to next waypoint if we're there
if self._distance < self.DIST_NEXT_WAYPOINT:
self._marks_passed += 1
self._waypoint = self._waypoint + 1 if self._waypoint < len(buoys)-1 else 0
self.set_target_angle(bearing)

def set_twa(self, twa, tack=False):
""" steer a true wind angle on the current (target) tack """
Expand All @@ -220,7 +231,10 @@ def get_position(self):
return self._position

def set_waypoint(self, waypoint):
"""set new waypoint, also resets number of marks passed"""

self._waypoint = waypoint
self._marks_passed = 0
return self

def get_boat_color(self):
Expand All @@ -230,7 +244,14 @@ def get_marks_passed(self):
return self._marks_passed

def get_distance_to_waypoint(self):
return self._distance
buoys = self._env.get_buoys()
target_pos = buoys[self._waypoint]
return geo.haversine(self._position[0], self._position[1], target_pos[0], target_pos[1])

def get_bearing_to_waypoint(self):
buoys = self._env.get_buoys()
target_pos = buoys[self._waypoint]
return geo.bearing(self._position[0], self._position[1], target_pos[0], target_pos[1])

def get_name(self):
return self._name
Expand Down
61 changes: 57 additions & 4 deletions src/drawers/race_drawer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import pygame
import pandas as pd
import os.path

from tools import rotate_point
from environment import Environment


class RaceDrawer:
Expand All @@ -9,7 +13,7 @@ class RaceDrawer:
RACE_CANVAS_COLOR = (33, 66, 99)

ARROW_SHAPE = [(0, 100), (0, 200), (200, 200), (200, 300), (300, 150), (200, 0), (200, 100)]
ARROW_COLOR = (0, 255, 0)
ARROW_COLOR = (9, 209, 97)
ARROW_POS = [350, 250]
ARROW_ORIGIN = [150, 100]
ARROW_SCALE = 0.2
Expand All @@ -19,11 +23,26 @@ class RaceDrawer:
BOAT_COLOR = (255, 255, 255)
BOAT_SCALE = 0.1

def __init__(self, screen):
self._screen = screen
SIZE = 1100, 730
BG_COLOR = 0, 0, 0
TEXT_COLOR = 255, 255, 255

def __init__(self, boats: list, env: Environment):
self._boats = boats
self._env = env
self._offset = (0, 0)
self._scale = 0

pygame.init()
pygame.font.init()

self._font = pygame.font.Font(os.path.join('fonts', 'B612-Regular.ttf'), 20)
self._smallfont = pygame.font.Font(os.path.join('fonts', 'B612-Regular.ttf'), 15)
self._screen = pygame.display.set_mode(self.SIZE)

# scale the race canvas
self.autoscale(self._env.get_buoys())

def autoscale(self, buoys):
""" Scale race canvas to fit all buoys """
lat, lon = zip(*buoys)
Expand Down Expand Up @@ -98,4 +117,38 @@ def draw_buoys(self, buoys):
x, y = self.translate_pos(position)
pygame.draw.circle(self._screen, self.BUOY_COLOR, (x, y), 5)


def write_text(self, text, row, color=(255, 255, 255)):
pos = 740, 30 + (row * 30)
textsurface = self._font.render(text, True, color)
self._screen.blit(textsurface, pos)

def draw(self):
self._screen.fill(self.BG_COLOR)
self.draw_env(self._env)
self.draw_buoys(self._env.get_buoys())

# draw all boats
scoreboard = []
for boat in self._boats:

# update boat and draw
self.draw_boat(boat)

# update scoreboard
scoreboard.append({
'name': boat.get_name(),
'color': boat.get_boat_color(),
'marks_passed': boat.get_marks_passed(),
'dtw': boat.get_distance_to_waypoint()
})

# show scoreboard
scoreboard = pd.DataFrame(scoreboard).sort_values(by=['marks_passed', 'dtw'], ascending=[False, True])
i = 0
for _, row in scoreboard.iterrows():
text = "%s (DTW: %dm)" % (row['name'], row.dtw)
self.write_text(text, i, row.color)
i += 1

# display new frame
pygame.display.flip()
55 changes: 53 additions & 2 deletions src/drawers/sim_drawer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import pygame

from boat import Boat
from environment import Environment
from tools import rotate_point, add_vector, rotate_vectors


Expand All @@ -19,11 +22,21 @@ class SimDrawer:

CENTER = (250, 250)

def __init__(self, screen):
self._screen = screen
TEXT_COLOR = 255, 255, 255
SIZE = 800, 600
BG_COLOR = 0, 0, 255

def __init__(self):
self._offset = (0, 0)
self._scale = 0

pygame.init()
pygame.font.init()

self._font = pygame.font.SysFont('Arial', 30)
self._smallfont = pygame.font.SysFont('Arial', 20)
self._screen = pygame.display.set_mode(self.SIZE)

def draw_boat(self, boat):
# draw boat
vectors = self.BOAT_SHAPE.copy()
Expand Down Expand Up @@ -64,3 +77,41 @@ def draw_env(self, env):

pygame.draw.polygon(self._screen, self.ARROW_COLOR, vectors)
pygame.draw.circle(self._screen, (100, 100, 100), self.CENTER, 200, 5)

def write_text(self, text, row):
pos = 500, 30 + (row * 30)
textsurface = self._font.render(text, True, self.TEXT_COLOR)
self._screen.blit(textsurface, pos)

def draw_stats(self, boat, env, strategy_name):
# calculate mean of absolute course error
if boat.history.shape[0] > 0:
mae = boat.history.course_error.abs().mean()
else:
mae = 0

self.write_text("Boat angle: %.1f°" % boat.boat_angle, 0)
self.write_text("Target angle: %.1f°" % boat.target_angle, 1)
self.write_text("Current deviation: %.1f°" % boat.get_course_error(), 2)
self.write_text("Boat heel: %.1f°" % boat.boat_heel, 3)
self.write_text("Rudder angle: %.1f°" % boat.rudder_angle, 4)
self.write_text("Boat speed: %.1f knots" % boat.speed, 5)
self.write_text("Angle of attack: %.1f°" % boat.get_angle_of_attack(), 6)
self.write_text("Wind direction: %.1f°" % env.wind_direction, 8)
self.write_text("Wind speed: %.1f knots" % env.wind_speed, 9)
self.write_text("MAE: %.1f°" % mae, 11)
self.write_text("Strategy: %s" % strategy_name, 12)

textsurface = self._smallfont.render(
"Press keys to change: 1/2 for target angle, 3/4 for wind direction, 5/6 for wind speed, s to change strategy, q to quit", True, self.TEXT_COLOR)
self._screen.blit(textsurface, (20, 565))

def draw(self, boat: Boat, env: Environment, strategy_name='Undefined'):
# redraw objects
self._screen.fill(self.BG_COLOR)
self.draw_boat(boat)
self.draw_env(env)
self.draw_stats(boat, env, strategy_name)

# display new frame
pygame.display.flip()
Loading