Source code for player

"""
:author: Laurens Koppenol

Module to handle players. See :doc:`ai-players` on how to create new players

"""
from abc import abstractmethod, ABC
import pygame


[docs]class Player(ABC): """ Abstract class. Subclass this if you want to make a new player. See readthedocs for more info. """ def __init__(self): """ Set initial values for player """ self.position = (0, 0) self.speed = 0 self.rotation = 90 self.score = 0 self.alive = True self.sensors = []
[docs] @abstractmethod def sense(self, track, keys): """ Meant to get information about the environment from the track / key input :param track: Environment object :param keys: dictionary of keys :return: percepts """ pass
[docs] @abstractmethod def plan(self, percepts): """ Use percepts to get an action :param percepts: output of sense() function :return: acceleration_command, rotation_command. both in range [-1, 1] """ pass
[docs] def set_position(self, coordinate): """ Set new player position :param coordinate: new coordinate (x, y) :return: Nothing """ self.position = coordinate
[docs] def get_position(self, pixel=False, scale=1): """ Get player position :param pixel: wether to round down to full pixels :param scale: whether to scale (for drawing purposes) :return: coordinates (x, y) """ if pixel: position = [int(round(p)) * scale for p in self.position] return position else: position = [p * scale for p in self.position] return position
[docs] def change_position(self, delta_coordinate): """ Adjust player position :param delta_coordinate: incremental coordaintes (dx, dy) :return: Nothing """ self.position = ( self.position[0] + delta_coordinate[0], self.position[1] + delta_coordinate[1] )
[docs]class HumanPlayer(Player): """ Human Player that does not have sensors but responds to key input """ def __init__(self): super().__init__()
[docs] def sense(self, track, keys): """ Nothing is sensed, keys are passed on to plan() function. :param track: Environment object :param keys: dictionary of keys :return: keys dictionary """ return keys
[docs] def plan(self, percepts): """ Listen to key input. :param percepts: dict of keys that are pressed :return: acceleration_command, rotation_command """ # use boolean as int trick to get -1, 0 or 1 from keys (both keys => 0) acceleration_command = percepts[pygame.K_UP] - percepts[pygame.K_DOWN] rotation_command = percepts[pygame.K_RIGHT] - percepts[pygame.K_LEFT] return acceleration_command, rotation_command
[docs]class NaiveAi(Player): """ Super simple Naive AI that will try to stay away from the walls. User ray-tracing sensors (DistanceSensor). """ SENSOR_DISTANCE = 60 def __init__(self): """ Initialize and add 2 ray-tracing sensors. """ super().__init__() self.sensors += [DistanceSensor(self, a, NaiveAi.SENSOR_DISTANCE) for a in [-30, 30]]
[docs] def sense(self, track, keys): """ Calls its sensors using the track information to find distance to the walls. :param track: Environment object :param keys: Not used :return: a list with distances per sensor """ percepts = [s.perceive(track) for s in self.sensors] return percepts
[docs] def plan(self, percepts): """ Use the percepts to choose actions. This naive AI will match a certain speed and rotate away from the nearest visible wall. :param percepts: :return: acceleration_command, rotation_command """ if percepts[0] > percepts[1]: rotation_command = -1 else: rotation_command = 1 acceleration_command = self.speed < 1 # accelerate if going slow return acceleration_command, rotation_command
[docs]class DistanceSensor(object): """ Linear distance sensor using a simple raytracing algorithm. This sensor is drawable because it has percept, depth and a get_absolute_angle function. At the moment this is required to be drawn. """ def __init__(self, player, angle, depth): """ :param player: The player the sensor belongs to :param angle: angle offset in degrees :param depth: depth of vision """ self.player = player self.angle = angle self.depth = depth self.percept = None self.is_drawable = True # Sensor must have percept, depth and get_absolute_angle() to be drawable
[docs] def perceive(self, track): """ Update the sensor values. Returns them and sets them as internal value. Returns max depth if nothing is perceived :param track: Environment object :return: distance value """ percept = track.ray_trace_to_wall( self.player.position, self.player.rotation + self.angle, self.depth ) if percept is None: percept = self.depth self.percept = percept return percept
[docs] def get_absolute_angle(self): """ Use the player's angle and the sensor's offset to return the absolute angle of the sensor. :return: angle in degrees """ absolute_angle = self.player.rotation + self.angle return absolute_angle