예제 #1
0
def at_least(value: Vector2, minimum: [int, float]) -> Vector2:
    """
    :param value - vector to be truncated
    :param minimum - vector minimum length
    :return pygame.math.Vector2 object
    Returns a vector object that has at least the given length.
    Vector will inherit the angle of the given value vector.
    """
    if value.length() < minimum:
        value.scale_to_length(minimum)
    return value
예제 #2
0
def truncate(value: Vector2, maximum: [int, float]) -> Vector2:
    """
    :param value - vector to be truncated
    :param maximum - vector length limit
    :return pygame.math.Vector2 object
    Returns a vector object that is limited a given length.
    Vector will inherit the angle of the given value vector.
    """
    if value.length() > maximum:
        value.scale_to_length(maximum)
    return value
예제 #3
0
파일: classes.py 프로젝트: ZRunner/tipe
    def raytrace(self, angle: int, max_distance: int = 100, use_absolute_angle: bool = False,
                 return_real_distance: bool = False):
        """Vérifie si le rayon d'angle donné rencontre un mur avant une certaine distance

        Parameters
        ----------
        angle:
            Angle du rayon en degrés, 0 étant l'avant de la voiture
        max_distance:
            Distance maximum à prendre en compte [par défaut 100]
        use_absolute_angle:
            Si l'angle donné est relatif au plan (1) ou à la voiture (0) [par défaut False]
        return_real_distance:
            Si la valeur retournée doit être la distance réelle, et non entre 0
            et 1 [par défaut False]

        Returns
        -------
        :class:`float`:
            Retourne la distance entre 0 et 'max', 0 étant une collision immédiate et 'max' à la
            distance maximum, ou -1 si aucune collision. La valeur de 'max' est définie par le
            paramètre `max_distance` si `return_real_distance = True`, 1 sinon."""
        assert all([isinstance(x, Border) for x in self.circuit]
                   ), "La liste du circuit ne doit contenir que des objets de type Border"
        if not use_absolute_angle:
            angle = self.abs_rotation + angle
        angle = math.radians(angle)
        # direction = vector(round(math.cos(angle), 5),
        #                    round(math.sin(angle), 5))
        ray_direction = Vector(2 * math.cos(angle), 2 * math.sin(angle))
        ray_direction.scale_to_length(max_distance)
        distances = []
        for line in self.circuit:
            distance1 = Vector(
                line.start[0]-self.position[0], line.start[1]-self.position[1]).length()
            distance2 = Vector(
                line.end[0]-self.position[0], line.end[1]-self.position[1]).length()
            if distance1 < max_distance or distance2 < max_distance:
                distances.append(line_ray_intersection_point(self.position, ray_direction,
                                                             line.start, line.end))
        distances = [Vector(x[0]-self.position[0], x[1]-self.position[1]).length()
                     for x in distances if len(x) != 0]
        if len(distances) == 0:
            return -1
        shortest_distance = min(distances)
        if shortest_distance > max_distance:
            return -1
        if return_real_distance:
            return shortest_distance
        return shortest_distance/max_distance
예제 #4
0
    def __init__(self, mouse_or_enemy_position, game_area, hero, type):
        super().__init__()
        self.position = Vector2()
        self.velocity = Vector2()
        self.acceleration = Vector2()

        self.type = type
        if self.type == "hero":
            self.position = hero.position
            self.velocity = Vector2(mouse_or_enemy_position) - (
                Vector2(hero.position) + Vector2(hero.camera))
            Vector2.scale_to_length(self.velocity, 4)
        elif self.type == "enemy":
            self.position = mouse_or_enemy_position
            self.velocity = (
                Vector2(hero.position) +
                Vector2(random.randint(5, 100), random.randint(5, 100)) -
                Vector2(mouse_or_enemy_position))
            Vector2.scale_to_length(self.velocity, 5)
        # self.acceleration =

        self.assetAnimation = [
            pygame.image.load("Assets/Sprites/Projectiles/Projectile1.png"),
            pygame.image.load("Assets/Sprites/Projectiles/Projectile2.png"),
            pygame.image.load("Assets/Sprites/Projectiles/Projectile3.png")
        ]

        self.current_sprite = 0
        self.growth_factor = 0.4
        self.projectile_fired_time = pygame.time.get_ticks()
        self.projectile_live_time = 5000
        self.image = self.assetAnimation[self.current_sprite]
        self.rect = self.image.get_rect()
        self.rect.center = self.position
        self.hero = hero
        self.game_area = game_area
예제 #5
0
class Missile(Entity):
    def __init__(self, player, entities, xpos, ypos):
        super().__init__(entities, xpos, ypos, 2 * missile_prox,
                         2 * missile_prox)
        self.target = player
        self.a = Vec(0, 0)
        self.v = Vec(0, 0)
        self.prox = missile_prox
        self.state_counter = 0
        self.explosion_radius = missile_explosion_radius
        self.max_speed = missile_max_speed
        self.sfd = False
        # Drawing surface and state counter
        self.set_state(MissileState.WAITING)

    def get_bbox(self):
        return (self.pos - (missile_radius, missile_radius),
                self.pos + (missile_radius, missile_radius))

    def set_state(self, state):
        if state == MissileState.WAITING:
            self.a = Vec(0, 0)
            self.v = Vec(0, 0)
            self._surface = mk_wait_surface(self.xsize, self.ysize,
                                            self.center)
        elif state == MissileState.TARGETING:
            self.a = Vec(0, 0)
            self.v = Vec(0, 0)
            self._surface = mk_target_surface(self.xsize, self.ysize,
                                              self.center)
        elif state == MissileState.EXPLODING:
            self.a = Vec(0, 0)
            self.v = Vec(0, 0)
            self._surface = mk_exploding_surface(self.xsize, self.ysize,
                                                 self.center)
        self.state = state

    def update(self, time, entities):
        if (self.state == MissileState.WAITING):
            # Trigger if the player is close
            if get_dist(self, self.target) < self.prox:
                self.set_state(MissileState.TARGETING)
            # Trigger if the player clicks on something close
            elif self.target.target is not None:
                if get_dist(self, self.target.target) < self.prox:
                    self.set_state(MissileState.TARGETING)
        elif (self.state == MissileState.TARGETING):
            # Collision with solids
            for e in entities:
                if e.is_solid and collide(self, e):
                    self.die("")
            # Explode near the player
            if vec_from(self, self.target).length(
            ) < missile_explosion_radius + self.target.xsize / 2:
                self.die("")
            # Accelerate towards the player
            da = vec_from(self, self.target)
            da += self.target.v * time
            da = da.normalize()
            da *= missile_homingness
            self.a = da
        elif (self.state == MissileState.EXPLODING):
            # TODO: lower velocity
            self.state_counter += 1
            # 10 frames of missile explosion
            # TODO: time-based?
            if self.state_counter == 10:
                self.sfd = True
            # Kill player if they are close
            if get_dist(self, self.target
                        ) < missile_explosion_radius + self.target.xsize:
                self.target.die("Blown up by a missile.")
        # Physics
        self.v += self.a
        if self.v.length() > missile_max_speed:
            self.v.scale_to_length(missile_max_speed)
        self.pos += self.v * time
        self.pos = wrap_around(self.pos)

    def draw(self, screen):
        if self.target is not None and self.state == MissileState.TARGETING:
            draw_line(screen, self.pos, self.target.pos,
                      missile_explosion_color)
        super().draw(screen)

    def die(self, msg):
        self.set_state(MissileState.EXPLODING)
예제 #6
0
class MovingSprite(CenteredSprite):
    """
	A class compatible with pygame.Sprite which tracks position, motion, rotation in 2d space.
	"""

    max_speed = 100.0  # Absolute speed limit for this thing
    min_speed = 0.5  # ...and the slowest its allowed to go
    accel_rate = 1.0  # Value added to speed each frame when accelerating
    decel_rate = 1.0  # Value subtracted from speed each frame when decelerating
    turning_speed = 0.0  # Current turning speed (rotation in degrees per frame)
    max_turning_speed = 360  # Max degrees per frame this can turn; 360 = unlimited
    destination = None  # Necessary for seek_motion
    _motion_function = None  # Function called to move this on Sprite.update()
    _arrival_function = None  # Function called when destination reached by seeking motion

    def __init__(self, x=0.0, y=0.0, speed=None, direction=None):
        """
		MovingSprite constructor.
		"x" and "y" must be float values which define the position of the object.
		"speed" must be a float value which sets the magnitude of the motion vector of the object.
		"direction" must be a float value which sets the motion direction of the object in degrees.

		If you would like to use a motion vector made up of x/y values, create a
		"motion" vector with the values you need, and skip this function completely,
		like so:

			class MySprite(MovingSprite):
				def __init__(self):
					CenteredSprite.__init__(self, <x>, <y>)			# Sets Sprite.rect
					self.motion = Vector(<x>, <y>)					# Cartesian
					self._motion_function = self.cartesian_motion	# (example)

		"""
        CenteredSprite.__init__(self, x, y)
        self._motion_function = self.cartesian_motion
        self.motion = Vector()
        if speed is None or direction is None: return
        self.motion.from_polar((speed, direction))

    @property
    def speed(self):
        """
		Get the "magnitude" of this object's motion vector.
		"""
        return self.motion.magnitude()

    @speed.setter
    def speed(self, value):
        """
		Sets the "magnitude" of this object's motion vector.
		"""
        try:
            self.motion.scale_to_length(value)
        except ValueError as e:
            self.motion.from_polar((value, 0.0))

    @property
    def direction(self):
        """
		Gets the screen direction of this MovingSprite in degrees
		"""
        mag, deg = self.motion.as_polar()
        return deg

    @direction.setter
    def direction(self, degrees):
        """
		Sets the screen direction of this MovingSprite to the given degrees.
		"""
        mag = self.motion.magnitude()
        if mag == 0.0:
            self.motion.from_polar((2.22e-222, degrees))
        else:
            self.motion.from_polar((mag, degrees))

    def update(self):
        """
		Regular cyclic update task, as called from pygame.Sprite.
		Calls the current move function, which by default is "cartesian_motion".
		"""
        self._motion_function()

    def shift_position(self, x, y):
        """
		Does a relative cartesian move the position of the object by x, y.
		"""
        self.position.x += x
        self.position.y += y
        self.rect.centerx = int(self.position.x)
        self.rect.centery = int(self.position.y)
        return self

    def set_motion_polar(self, magnitude, degrees):
        """
		Set the current motion vector from the given magnitude, degrees.
		"""
        self.motion.from_polar((magnitude, degrees))

    def travel_to(self, target, on_arrival=None):
        """
		High-level command which sets this MovingSprite on a path towards a given target.
		Each subsequent call to "move()" will move the Thing one frame closer to the target.
		When travel is complete, the optional "on_arrival" function will be called.
		"""
        self.destination = vector(target)
        self._motion_function = self.seek_motion
        self._arrival_function = on_arrival
        return self.make_heading()

    def make_heading(self):
        """
		Sets the initial speed and direction of an object provided a destination
		using the "travel_to()" function.

		The default implementation sets this object's direction to the direction to its
		destination, without taking into consideration turning speed. The speed of this
		MovingSprite is set to it's acceration rate, unless it's already moving faster.

		A true physical moving thing most likely cannot change speed and direction in
		such an immediate fashion, unless the things you hear about "off world" vehicles
		visiting our planet are really true. You're free to model that, I suppose.

		This implementation is sufficient for game pieces such as used in BoardGame,
		but should be overridden in order to create more sophisticated motion.
		"""
        speed = max(self.speed, self.accel_rate)
        self.motion = (self.destination - self.position)
        self.motion.scale_to_length(speed)
        return self

    ####################################################################################
    # Move routines which may be called in the "update()" function of the parent Sprite:
    # Various movement methods, called from the "update" method, depending upon the
    # instance and circumstances. The "update" method calls the "_motion_function"
    # which is a reference to one of these methods, or a method in a derived class.
    ####################################################################################

    def no_motion(self):
        """
		A "_motion_function" which does nothing.
		"""
        pass

    def cartesian_motion(self):
        """
		Moves one frame by adding the x/y values of this MovingSprite's "motion" vector to the
		x/y values of this MovingSprite's "position" vector.
		This is the default "_motion_function".
		"""
        self.position.x += self.motion.x
        self.position.y += self.motion.y
        return self.update_rect()

    def seek_motion(self):
        """
		One of the "_motion_function" methods, called from "update". This method points
		the sprite towards its current "destination" vector and accelerates towards it,
		decelerating to zero when it approaches.

		This is the move function set in the "travel_to" method.

		This implementation is sufficient for game pieces as used in BoardGame,
		but should be overridden in order to create more sophisticated motion.
		"""
        remaining_distance = (self.destination - self.position).magnitude()
        if remaining_distance <= self.decel_rate:
            self.position = self.destination
            self.rect.centerx = int(self.position.x)
            self.rect.centery = int(self.position.y)
            self.speed = 0.0
            self._motion_function = self.no_motion
            if self._arrival_function:
                self._arrival_function()
        elif remaining_distance <= triangular(self.speed, self.decel_rate):
            # coming up on target_pos; decelerate:
            self.speed = max(self.min_speed, self.speed - self.decel_rate)
        else:
            # still far away from target_pos; accelate towards it:
            self.speed = min(self.max_speed, self.speed + self.accel_rate)
        return self.cartesian_motion()

    ####################################################################################
    # High-level movement functions
    ####################################################################################

    def turn_towards(self, vector):
        """
		Turn incrementally towards the given position vector, taking into account this
		MovingSprite's current and max turning speed.
		"""
        self.increment_turning_speed(self.direction_to_vector(vector))
        self.direction += self.turning_speed

    def turn_away_from(self, vector):
        """
		Turn incrementally away from the given position vector, taking into account
		this MovingSprite's current and max turning speed.
		"""
        self.increment_turning_speed(self.direction_to_vector(vector) + 180)
        self.direction += self.turning_speed

    def increment_turning_speed(self, direction):
        """
		Incrementally adjust this MovingSprite's "turning_speed" attribute towards or
		away from the given screen direction in degrees, taking into account this
		MovingSprite's current and max turning speed.

		Affects only the "turning_speed" attribute - does NOT change the "motion" vector.
		"""
        desired_turn = turning_degrees(self.direction - direction)
        turn_mag = abs(desired_turn)
        if turn_mag < self.max_turning_speed:
            self.direction = direction
            self.turning_speed = 0.0
        elif abs(self.turning_speed) < self.max_turning_speed:
            self.turning_speed -= math.copysign(self.max_turning_speed,
                                                desired_turn)

    ####################################################################################
    # Relative position methods:
    ####################################################################################

    def distance_to_vector(self, vector):
        """
		Returns a float - the distance in pixels from this MovingSprite to the given
		position vector.
		"""
        return self.position.distance_to(vector)

    def distance_to_thing(self, thing):
        """
		Returns a float - the distance in pixels from this MovingSprite to the given
		"thing-like" object.
		"""
        return self.position.distance_to(thing.position)

    def direction_to_vector(self, vector):
        """
		Returns the world angle in degrees from this MovingSprite's position to the
		given position vector. Returns non-normalized degrees (float).

		If you need a Vector instead of an angle in radians, use Vector subtraction.
		"""
        return (vector - self.position).as_polar()[1]

    def direction_to_thing(self, thing):
        """
		Returns the world angle in degrees from this MovingSprite's position to the
		given "thing-like" object's position. Returns non-normalized degrees (float).

		If you need a Vector instead of an angle in radians, use Vector subtraction.
		"""
        return (thing.position - self.position).as_polar()[1]

    def angle_to_vector(self, vector):
        """
		Returns the world angle in degrees from this MovingSprite's position to the
		given position vector. Returns non-normalized degrees (float).

		If you need a Vector instead of an angle in radians, use Vector subtraction.

		Alias of "direction_to_vector".
		"""
        return (vector - self.position).as_polar()[1]

    def angle_to_thing(self, thing):
        """
		Returns the world angle in degrees from this MovingSprite's position to the
		given "thing-like" object's position. Returns non-normalized degrees (float).

		If you need a Vector instead of an angle in radians, use Vector subtraction.

		Alias of "direction_to_thing".
		"""
        return (thing.position - self.position).as_polar()[1]

    def radians_to_vector(self, vector):
        """
		Returns the world angle in radians from this MovingSprite's position to the
		given position vector. Returns non-normalized radians (float).

		If you need a Vector, simply subtract this MovingSprite's position from the vector's.
		"""
        return math.radians((vector - self.position).as_polar()[1])

    def radians_to_thing(self, vector):
        """
		Returns the world angle in radians from this MovingSprite's position to the
		given "thing-like" object's position. Returns non-normalized radians (float).

		If you need a Vector, simply subtract this MovingSprite's position from the vector's.
		"""
        return math.radians((thing.position - self.position).as_polar()[1])

    ####################################################################################
    # Status methods:
    ####################################################################################

    def is_offscreen(self, screen_rect):
        """
		Returns one of the "OFFSCREEN_<direction>" constants if this MovingSprite's position
		if outside of the boundaries of the Surface it is to be rendered on.
		"""
        if self.rect.colliderect(screen_rect):
            bits = 0
            if self.position.x < 0:
                bits |= OFFSCREEN_LEFT
            elif self.position.x > screen_rect.width:
                bits |= OFFSCREEN_RIGHT
            if self.position.y < 0:
                bits |= OFFSCREEN_TOP
            elif self.position.y > screen_rect.height:
                bits |= OFFSCREEN_BOTTOM
            return bits
        return 0

    def is_directionless(self):
        """
		Returns boolean "True" if radians and magnitude of this MovingSprite's motion Vector
		cannot be determined.
		"""
        return self.motion.x == 0 and self.motion.y == 0

    def ns_direction(self):
        """
		Returns COMPASS_NORTH or COMPASS_SOUTH, depending on if the y-axis of movement is positive or negative
		"""
        return COMPASS_NORTH if self.motion.y < 0 else COMPASS_SOUTH

    def ew_direction(self):
        """
		Returns COMPASS_EAST or COMPASS_WEST, depending on if the y-axis of movement is positive or negative
		"""
        return COMPASS_WEST if self.motion.x < 0 else COMPASS_EAST

    def __str__(self):
        if self.is_directionless():
            return "<Motionless %s at %.1f / %.1f>" % (
                type(self).__name__, self.position.x, self.position.y)
        return "<%s at %.1f / %.1f, moving %d degrees at %.1f pixels-per-second>" % \
         (type(self).__name__, self.position.x, self.position.y, self.motion.degrees, self.motion.magnitude())
예제 #7
0
    def update(self, hero):
        if self.is_moving:
            self.position += self.velocity
            self.rect.center = self.position
            self.current_sprite += 0.3
            if self.current_sprite >= len(self.assetAnimation):
                self.current_sprite = 0
            self.image = self.assetAnimation[int(self.current_sprite)]

        # if not self.game_area.contains(self.rect):
        #    self.kill()

        self.velocity = Vector2(self.hero.position) - Vector2(self.position)
        if self.enemy_type == "Mushroom":
            Vector2.scale_to_length(self.velocity, 1)
        if self.enemy_type == "Skeletor":
            Vector2.scale_to_length(self.velocity, 2)
        if self.enemy_type == "Hydra":
            Vector2.scale_to_length(self.velocity, 2)
        if self.enemy_type == "Cacodemon":
            Vector2.scale_to_length(self.velocity, 2)
        if self.enemy_type == "Invulnro":
            Vector2.scale_to_length(self.velocity, 1)
        if self.enemy_type == "Tetro":
            Vector2.scale_to_length(self.velocity, 4)
        if self.enemy_type == "Mergamoth":
            Vector2.scale_to_length(self.velocity, self.scale_factor)

        if self.enemy_type == "Cacodemon" or self.enemy_type == "Mergamoth":
            if random.randint(1, 100) > 99:
                self.velocity.scale_to_length(0)
                # self.fired_time = pygame.time.get_ticks()
                gamestate.all_sprites.add(
                    projectile.Projectile(self.rect.center,
                                          gamestate.game_area, hero, "enemy"))
예제 #8
0
파일: circuit.py 프로젝트: ZRunner/tipe
def add_width(pathway: typing.List[tuple], colors: typing.Dict[str,
                                                               pygame.Color],
              screen_size: typing.Tuple[int]) -> dict:
    """Elargit le circuit à partir du tracé de base

    Pour chaque segment du tracé, on calcule la médiatrice du segment puis on trouve deux points
    sur cette médiatrice dont la distance respecte les constantes posées. Un nettoyage est ensuite
    réalisé pour supprimer les angles trop bruts ou inutiles, par la fonction :func:`check_angles`.

    Parameters
    ----------
    pathway:
        Liste des points du tracé de base
    colors:
        Dictionnaire des couleurs à utiliser
    screen_size: (:class:`int`, :class:`int`)
        Taille en X,Y de la fenêtre

    Returns
    -------
    :class:`dict`:
        Dictionnaire contenant le premier point supérieur ('point1'), le premier point inférieur
        ('point2') et toutes les :class:`classes.Border` du circuit ('bordures')
    """
    points_over = list()
    points_under = list()
    result = list()
    delta = -round(MIN_PATH_WIDTH / 12), round(MIN_PATH_WIDTH / 12)
    new_delta = min(MIN_PATH_WIDTH + random.randrange(*delta), MAX_PATH_WIDTH)
    # First point
    vect = Vector(pathway[1][0] - pathway[0][0], pathway[1][1] - pathway[0][1])
    vect.rotate_ip(90)
    vect.scale_to_length(new_delta)
    points_over.append(
        (pathway[0][0] - vect.x / 2, pathway[0][1] - vect.y / 2))
    points_under.append(
        (pathway[0][0] + vect.x / 2, pathway[0][1] + vect.y / 2))
    # Other points
    for enum in range(1, len(pathway) - 1):
        point1, point2, point3 = pathway[enum -
                                         1], pathway[enum], pathway[enum + 1]
        vect = Vector(point2[0]-point1[0], point2[1]-point1[1]) \
            + Vector(point3[0]-point2[0], point3[1]-point2[1])
        vect.rotate_ip(90)
        new_delta = max(
            min(new_delta + random.randrange(*delta), MAX_PATH_WIDTH),
            MIN_PATH_WIDTH)
        vect.scale_to_length(new_delta)
        # points_over.append(point2)
        points_over.append((point2[0] - vect.x / 2, point2[1] - vect.y / 2))
        points_under.append((point2[0] + vect.x / 2, point2[1] + vect.y / 2))
    # Last point
    vect = Vector(pathway[-1][0] - pathway[-2][0],
                  pathway[-1][1] - pathway[-2][1])
    vect.rotate_ip(90)
    vect.scale_to_length(new_delta)
    points_over.append(
        (pathway[-1][0] - vect.x / 2, pathway[-1][1] - vect.y / 2))
    points_under.append(
        (pathway[-1][0] + vect.x / 2, pathway[-1][1] + vect.y / 2))
    # Cleanup of points
    check_angles(points_over)
    check_angles(points_under)
    for path in (points_over, points_under):
        for index in range(len(path) - 1):
            if colors is None:  # debug only - couleur aléatoire
                color = ((index * 100 + 70) % 255, (index * 90 + 20) % 255,
                         (index * 50 + 40) % 255)
            else:
                color = colors["borders"]
            if path[index][1] > screen_size[1] - 10:
                path[index][1] = screen_size[1] - 10
            elif path[index][1] < 10:
                path[index][1] = 10
            result.append(Border(path[index], path[index + 1], color))
    black = (10, 10, 10)
    result.append(
        Border(points_over[0], points_under[0],
               colors["border-begin"] if colors is not None else black))
    result.append(
        Border(points_over[-1], points_under[-1],
               colors["border-end"] if colors is not None else black))
    return {
        "bordures": result,
        "point1": points_under[0],
        "point2": points_over[0]
    }
예제 #9
0
class Entity(Sprite):
    """Base class for all moving Entities"""

    # Keep a group of all entities for the purpose of updating and drawing
    group = SpriteGroup()

    def __init__(self, size=(1, 1), pos=(0, 0)):
        Sprite.__init__(self)

        # Radius attribute for collision detection, circle centered on pos
        self.radius = size[0] / 2

        self.image = Surface(size).convert()
        self.image.set_colorkey(COLOR.BLACK)  # set black as transparency color

        self.orig_img = self.image  # Keep an original copy for rotation purposes

        self.rect = self.image.get_rect(center=pos)

        self.position = Vector(pos)
        self.velocity = Vector(0, 0)
        self.angle = 0
        self.rotation_speed = 0
        self.max_velocity = 10
        self.max_rotation = 10

        Entity.group.add(self)

    def calc_position(self):
        """Calculates the next position based on velocity"""
        if self.velocity.length() > self.max_velocity:
            # Scale velocity to max
            self.velocity.scale_to_length(self.max_velocity)
        return self.position + self.velocity

    def calc_rotation(self):
        """Calculates the next angle in the rotation"""
        if self.rotation_speed > self.max_rotation:
            self.rotation_speed = self.max_rotation
        if self.rotation_speed < -self.max_rotation:
            self.rotation_speed = -self.max_rotation
        return self.angle + self.rotation_speed

    def move(self, pos=Vector(0, 0)):
        """Moves the position to the Vector2 given"""
        # Wrap around the screen
        if pos.x > SCREEN_WIDTH:
            pos.x = 0
        elif pos.x < 0:
            pos.x = SCREEN_WIDTH
        if pos.y > SCREEN_HEIGHT:
            pos.y = 0
        elif pos.y < 0:
            pos.y = SCREEN_HEIGHT
        self.position = pos
        # Set the center of the rect to the position
        self.rect.center = self.position

    def rotate(self, angle=0):
        """Rotates the angle and the sprite image"""
        # Normalize angle into 360 deg
        if angle > 360:
            angle -= 360
        elif angle < 360:
            angle += 360

        self.angle = angle
        self.image = rotate(self.orig_img, -self.angle)
        self.rect = self.image.get_rect(center=self.rect.center)

    def update(self):
        """Called every tick to update the state of the Entity"""
        self.rotate(self.calc_rotation())
        self.move(self.calc_position())