def __generate_random_checks(self): min_x = Constants.border_padding() min_y = Constants.border_padding() max_x = Constants.world_x() - Constants.border_padding() max_y = Constants.world_y() - Constants.border_padding() min_dist_sq = Constants.check_spacing() * Constants.check_spacing() self.checkpoints = [] num_checks = random.randrange(Constants.min_checks(), Constants.max_checks()) while len(self.checkpoints) < num_checks: check = Vec2(random.randrange(min_x, max_x, 1), random.randrange(min_y, max_y, 1)) too_close = next((True for x in self.checkpoints if (x - check).square_length() < min_dist_sq), False) if not too_close: self.checkpoints.append(check)
def play(self, pod: PodState) -> PlayOutput: return PlayOutput( Vec2(random() * Constants.world_x(), random() * Constants.world_y()), math.ceil(random() * Constants.max_thrust()) )
import math from typing import Tuple, Callable, List from pod.ai.ai_utils import MAX_DIST from pod.board import PodBoard from pod.constants import Constants from pod.util import PodState, clean_angle ################################################# # Reward functions: signature is # func(board, state) -> float ################################################# RewardFunc = Callable[[PodBoard, PodState], float] DIST_BASE = math.sqrt(Constants.world_x() * Constants.world_y()) def pgr(board: PodBoard, pod: PodState) -> float: """ Pretty Good Reward Attempts to estimate the distance without using a SQRT calculation. """ pod_to_check = board.checkpoints[pod.nextCheckId] - pod.pos prev_to_next_check = board.checkpoints[pod.nextCheckId] - board.get_check(pod.nextCheckId - 1) pod_dist_estimate = (math.fabs(pod_to_check.x) + math.fabs(pod_to_check.y)) / 2 check_dist_estimate = (math.fabs(prev_to_next_check.x) + math.fabs(prev_to_next_check.y)) / 2 dist_estimate = pod_dist_estimate / check_dist_estimate checks_hit = len(board.checkpoints) * pod.laps + pod.nextCheckId return 2*checks_hit - dist_estimate + 1
def animate(self, max_turns: int = 1000, max_laps: int = 5, as_gif=False, filename='/tmp/pods', reset: bool = True, trail_len: int = 20, highlight_checks: bool = False, show_vel: bool = False, fps: int = 10): """ Generate an animated GIF of the players running through the game :param show_vel Whether to draw a vector showing each Pod's velocity :param highlight_checks If true, a pod's next check will change to the pod's color :param trail_len Number of turns behind the pod to show its path :param as_gif If True, generate a GIF, otherwise an HTML animation :param max_turns Max number of turns to play :param max_laps Max number of laps for any player :param filename Where to store the generated file :param reset Whether to reset the state of each Player first :param fps Frames per second """ if len(self.hist) < 1: self.record(max_turns, max_laps, reset) _prepare_size() self.__prepare_for_world() ######################################### # Create the objects for display ######################################### art = { 'check': [], 'pod': [], 'color': [], 'trails': [], 'vel': [], 'count': 0, 'log': JupyterLog(), 'turnCounter': self.ax.text(-PADDING, Constants.world_y() + PADDING, 'Turn 0', fontsize=14) } fa = _get_field_artist() self.ax.add_artist(fa) for (idx, check) in enumerate(self.board.checkpoints): ca = self.__draw_check(check, idx) self.ax.add_artist(ca) art['check'].append(ca) for (idx, p) in enumerate(self.players): color = _gen_color(idx) pa = _get_pod_artist(p.pod, color) self.ax.add_artist(pa) art['pod'].append(pa) art['color'].append(color) plt.legend(art['pod'], self.labels, loc='lower right') if trail_len > 0: for i in range(len(self.players)): lc = LineCollection([], colors=art['color'][i]) lc.set_segments([]) lc.set_linestyle(':') self.ax.add_collection(lc) art['trails'].append(lc) if show_vel: for p in self.players: xy = _vel_coords(p.pod) line = self.ax.plot(xy[0], xy[1])[0] art['vel'].append(line) all_updates = [ fa, *art['check'], *art['pod'], *art['trails'], *art['vel'], art['turnCounter'] ] ######################################### # Define the animation function ######################################### def do_animate(frame_idx: int): art['log'].replace("Drawing frame {}".format(art['count'])) art['count'] += 1 check_colors = [ 'royalblue' for _ in range(len(self.board.checkpoints)) ] frame_data = self.hist[frame_idx] # Update the pods for (p_idx, player_log) in enumerate(frame_data): pod = player_log['pod'] theta1, theta2, center = _pod_wedge_info(pod) art['pod'][p_idx].set_center((center.x, center.y)) art['pod'][p_idx].set_theta1(theta1) art['pod'][p_idx].set_theta2(theta2) art['pod'][p_idx]._recompute_path() # pylint: disable=protected-access check_colors[pod.nextCheckId] = art['color'][p_idx] # Update the velocities if show_vel: xy = _vel_coords(pod) art['vel'][p_idx].set_xdata(xy[0]) art['vel'][p_idx].set_ydata(xy[1]) # Update the trails if frame_idx > 0 and trail_len > 0: for p_idx in range(len(self.players)): line = _to_line(self.hist[frame_idx - 1][p_idx]['pod'].pos, frame_data[p_idx]['pod'].pos) segs = art['trails'][p_idx].get_segments() + [line] art['trails'][p_idx].set_segments(segs[-trail_len:]) # Update the check colors if highlight_checks: for col, check_art in zip(check_colors, art['check']): check_art.set_color(col) # Update the turn counter art['turnCounter'].set_text('Turn ' + str(frame_idx)) return all_updates ######################################### # Create the animation ######################################### anim = FuncAnimation( plt.gcf(), do_animate, frames=len(self.hist), ) plt.close(self.fig) if as_gif: if not filename.endswith(".gif"): filename = filename + ".gif" anim.save(filename, writer=PillowWriter(fps=fps)) return Image(filename=filename) else: if not filename.endswith(".html"): filename = filename + ".html" anim.save(filename, writer=HTMLWriter(fps=fps, embed_frames=True, default_mode='loop')) path = Path(filename) return HTML(path.read_text())
def __prepare_for_world(self): self.fig = plt.figure() self.ax = plt.axes(xlim=(-PADDING, Constants.world_x() + PADDING), ylim=(-PADDING, Constants.world_y() + PADDING)) self.ax.invert_yaxis()
def random() -> 'PodState': return PodState(pos=Vec2.random(Constants.world_x(), Constants.world_y()), vel=UNIT.rotate(2 * math.pi * random()) * (random() * Constants.max_vel()), angle=2 * math.pi * random())
def _get_reward(self) -> int: reward = Constants.world_x() * Constants.world_y() reward += self._player.pod.nextCheckId * 10000 reward -= (self._world.checkpoints[self._player.pod.nextCheckId] - self._player.pod.pos).square_length() return np.asarray(reward, dtype=np.float32)
import math from typing import List import numpy as np from pod.constants import Constants from pod.controller import Controller from pod.util import PodState, clean_angle from vec2 import Vec2, UNIT # Distance to use for scaling inputs MAX_DIST = Vec2(Constants.world_x(), Constants.world_y()).length() def gen_pods(checks: List[Vec2], pos_angles: List[float], pos_dists: List[float], angles: List[float], vel_angles: List[float], vel_mags: List[float]): """ Generate pods in various states :param checks: Checkpoints around which to generate :param pos_angles: Angles from check to pod :param pos_dists: Distances from check to pod :param angles: Orientations of pods. This will be rotated so that 0 points toward the check! :param vel_angles: Angles of velocity. Also rotated so that 0 points toward the check. :param vel_mags: Magnitudes of velocity :return: One pod for each combination of parameters """ relative_poss = [ UNIT.rotate(ang) * dist for ang in pos_angles for dist in pos_dists ] relative_vels = [ UNIT.rotate(ang) * mag for ang in vel_angles for mag in vel_mags