# Homework 6 Solutions
# Software Design
# Allen Downey

import math, sys
from random import randint
from World import *

class Missed(Exception):
    """this the exception raised when a turtle tries to tag
    someone too far away"""

class Tagger(Turtle):
    def __init__(self, world, speed=1, clumsiness=60, color='red'):
        Turtle.__init__(self, world)
        self.delay = 0
        self.speed = speed
        self.clumsiness = clumsiness
        self.color = color
        self.it = 0
        self.sulking = 0
        self.goal = None

        # move to the starting position
        self.pu()
        self.rt(randint(0,360))
        self.fd(150)
        self.turn_toward()

    def distance(self, x=0, y=0):
        """compute the distance from this turtle to the given point"""
        dx = self.x - x
        dy = self.y - y
        return math.sqrt(dx**2 + dy**2)

    def distance_from(self, other):
        """compute the distance between turtles"""
        return self.distance(other.x, other.y)

    def youre_it(self):
        """make this turtle 'it' """
        self.it = 1
        self.old_color = self.color
        self.color = 'blue'
        self.sulking = 200
        self.redraw()

    def not_it(self):
        """make this turtle not 'it' """
        self.it = 0
        self.color = self.old_color
        self.redraw()

    def away(self, x=0, y=0):
        """compute the angle (in degrees) that faces away from
        the given point"""
        dx = self.x - x
        dy = self.y - y
        heading = math.atan2(dy, dx)
        return heading * 180 / math.pi

    def flee(self, other):
        """set the goal to face away from the other turtle"""
        self.goal = self.away(other.x, other.y)
        
    def chase(self, other):
        """set the goal to face the other turtle"""
        self.goal = self.away(other.x, other.y) + 180

    def turn_toward(self, x=0, y=0):
        """turn to face the given point"""
        self.heading = self.away(x, y) + 180
        self.redraw()

    def closest(self, others):
        """return the animal in the list that is closest to self
        (not including self!)"""
        t = [(self.distance_from(animal), animal)
              for animal in others if animal is not self]
        (distance, animal) = min(t)
        return animal

    def apply_tag(self, other):
        """try to tag the other turtle.  if it is too far away,
        let the other turtle flee and raise an exception"""
        if self.distance_from(other) < 10:
            self.not_it()
            other.youre_it()
        else:
            other.flee(self)
            raise Missed

    def step(self):
        """step is invoked whenever the turtle is supposed to move"""

        # if we're sulking, skip a turn
        if self.sulking > 0:
            self.sulking -= 1
            if self.sulking == 0:
                self.color = 'red'
            return

        # if we're it, find try to tag the closest animal.
        # if the tag misses, chase!
        if self.it:
            target = self.closest(world.animals)
            try:
                self.apply_tag(target)
            except Missed:
                self.chase(target)

        # have to stay in bounds!!!
        if self.distance() > 200:
            self.turn_toward()

        # if we have a goal, we get two chances to turn toward it;
        # otherwise, make a random turn
        if self.goal:
            dirs = [self.randangle() for i in range(2)]
            dir = self.best_choice(dirs)
            self.goal = None
        else:
            dir = self.randangle()

        # make the turn, but keep heading in bounds
        self.lt(dir)
        self.heading = self.heading % 360
        
        # move forward according to the speed attribute
        self.fd(self.speed)

    def randangle(self):
        """choose a random angle according to the clumsiness attribute"""
        return randint(0,self.clumsiness) - randint(0,self.clumsiness)


    def best_choice(self, t):
        """find the turn in the list that would bring the turtle
        closest to the goal"""
        diff = [(diff_dir(self.heading+dt, self.goal), dt)
                 for dt in t]
        return min(diff)[1]

def diff_dir(angle1, angle2):
    """compute the difference between two angles expressed in degrees"""
    t1, t2 = min(angle1, angle2), max(angle1, angle2)
    return min(t2-t1, t1+360-t2)

# create a new TurtleWorld
world = TurtleWorld()
world.delay = .01
world.setup_run()

# make three Taggers with different speed and clumsiness attributes
color = [None, 'orange', 'green', 'purple' ]
for i in range(1,4):
    Tagger(world, i, i*30, color[i])

# the middle turtle is "it"
world.animals[1].youre_it()

# play!
world.mainloop()

