from tkinter import *
import math, random

def distance(x1, y1, x2, y2):
    return ((x1-x2)**2 + (y1-y2)**2)**0.5

class Kerry(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.familyMembers = 10
        self.angle = 0

    def rotate(self, dangle):
        maxAngle = 90
        minAngle = 0
        newAngle = self.angle + dangle
        if newAngle > maxAngle:
            newAngle = maxAngle
        self.angle = max(newAngle, minAngle)

    def draw(self, canvas):
        # draw cannon
        cannon = 100
        cx = self.x + cannon * math.cos(math.radians(self.angle))
        cy = self.y - cannon * math.sin(math.radians(self.angle))
        canvas.create_line(self.x, self.y, cx, cy, width=20)

        # draw body
        height = 40
        thiccness = 50
        canvas.create_rectangle(self.x-thiccness, self.y-height,
                                self.x+thiccness, self.y+height, fill="blue")

        # draw family
        canvas.create_text(self.x, self.y, text=self.familyMembers, fill="white")

    def shoot(self):
        return Broccoli(self.x, self.y, self.angle)

class Projectile(object):
    def __init__(self, x, y, r, dx, dy, color):
        self.x = x
        self.y = y
        self.dx = dx
        self.dy = dy
        self.color = color
        self.r = r

    def move(self):
        self.x += self.dx
        self.y -= self.dy

    def draw(self, canvas):
        canvas.create_oval(self.x - self.r, self.y - self.r,
                           self.x + self.r, self.y + self.r, fill=self.color)

    def collidesWith(self, other):
        return (isinstance(other, Projectile)
            and distance(self.x, self.y, other.x, other.y) <= self.r + other.r)

class Missile(Projectile):
    color = "red"
    def __init__(self, x, y, r):
        dx, dy = -10, 0
        super().__init__(x, y, r, dx, dy, Missile.color)

    def collidesWithAnyBroccoli(self, broccolis):
        for broccoli in broccolis:
            if self.collidesWith(broccoli):
                return True
        return False

class Broccoli(Projectile):
    color = "green"
    def __init__(self, x, y, angle):
        speed = 40
        dx = speed * math.cos(math.radians(angle))
        dy = speed * math.sin(math.radians(angle))
        r = 10
        super().__init__(x, y, r, dx, dy, Broccoli.color)

    def move(self):
        super().move()
        self.dy -= 1

def init(data):
    data.kerry = Kerry(50, data.height-50)
    data.missiles = []
    data.broccoli = []
    data.count = 0
    data.gameOver = False

def mousePressed(event, data):
    data.broccoli.append(data.kerry.shoot())

def keyPressed(event, data):
    if event.keysym == "Up":
        data.kerry.rotate(10)
    elif event.keysym == "Down":
        data.kerry.rotate(-10)

def removeHitMissiles(data):
    newMissiles = []
    for missile in data.missiles:
        if not missile.collidesWithAnyBroccoli(data.broccoli):
            newMissiles.append(missile)
    data.missiles = newMissiles

def timerFired(data):
    if data.gameOver: return
    # spawn a new missile every 500 ms
    data.count += 1
    if data.count % 5 == 0:
        y = random.randint(0, data.width//2)
        r = random.randint(20, 50)
        data.missiles.append(Missile(data.width, y, r))

    # move everything
    for broccoli in data.broccoli:
        broccoli.move()

    newMissiles = []
    for missile in data.missiles:
        missile.move()
        if missile.x + missile.r <= 0:
            data.kerry.familyMembers -= 1 # :'(
            if data.kerry.familyMembers <= 0:
                data.gameOver = True
            elif data.kerry.familyMembers <= 5:
                Broccoli.color = "purple"
        else:
            newMissiles.append(missile)
    data.missiles = newMissiles

    # remove missiles that got hit with a broccoli
    removeHitMissiles(data)

def redrawAll(canvas, data):
    data.kerry.draw(canvas)
    for broccoli in data.broccoli:
        broccoli.draw(canvas)
    for missile in data.missiles:
        missile.draw(canvas)

####################################
# use the run function as-is
####################################

# from the 15-112 website

def run(width=300, height=300):
    def redrawAllWrapper(canvas, data):
        canvas.delete(ALL)
        canvas.create_rectangle(0, 0, data.width, data.height,
                                fill='white', width=0)
        redrawAll(canvas, data)
        canvas.update()    

    def mousePressedWrapper(event, canvas, data):
        mousePressed(event, data)
        redrawAllWrapper(canvas, data)

    def keyPressedWrapper(event, canvas, data):
        keyPressed(event, data)
        redrawAllWrapper(canvas, data)

    def timerFiredWrapper(canvas, data):
        timerFired(data)
        redrawAllWrapper(canvas, data)
        # pause, then call timerFired again
        canvas.after(data.timerDelay, timerFiredWrapper, canvas, data)
    # Set up data and call init
    class Struct(object): pass
    data = Struct()
    data.width = width
    data.height = height
    data.timerDelay = 100 # milliseconds
    root = Tk()
    root.resizable(width=False, height=False) # prevents resizing window
    init(data)
    # create the root and the canvas
    canvas = Canvas(root, width=data.width, height=data.height)
    canvas.configure(bd=0, highlightthickness=0)
    canvas.pack()
    # set up events
    root.bind("<Button-1>", lambda event:
                            mousePressedWrapper(event, canvas, data))
    root.bind("<Key>", lambda event:
                            keyPressedWrapper(event, canvas, data))
    timerFiredWrapper(canvas, data)
    # and launch the app
    root.mainloop()  # blocks until window is closed
    print("bye!")

run(800, 800)