from random import randrange as rnd, choice
import random
import tkinter as tk
import math
import time

root = tk.Tk()
fr = tk.Frame(root)
root.geometry('800x600')
canv = tk.Canvas(root, bg='white')
canv.pack(fill=tk.BOTH, expand=1)


class Ball:
    total_live = 100
    y_max = 550

    def __init__(self, x=40, y=450):
        """ Конструктор класса ball

        Args:
        x - начальное положение мяча по горизонтали
        y - начальное положение мяча по вертикали
        """
        self.x = x
        self.y = y
        self.r = 10
        self.vx = 0
        self.vy = 0
        self.color = choice(['blue', 'green', 'red', 'brown'])
        self.id = canv.create_oval(
            self.x - self.r,
            self.y - self.r,
            self.x + self.r,
            self.y + self.r,
            fill=self.color
        )
        self.live = Ball.total_live

    def set_coords(self):
        canv.coords(
            self.id,
            self.x - self.r,
            self.y - self.r,
            self.x + self.r,
            self.y + self.r
        )

    def del_ball(self):
        canv.delete(self.id)

    def move(self):
        """Переместить мяч по прошествии единицы времени.

        Метод описывает перемещение мяча за один кадр перерисовки. То есть, обновляет значения
        self.x и self.y с учетом скоростей self.vx и self.vy, силы гравитации, действующей на мяч,
        и стен по краям окна (размер окна 800х600).
        """

        if self.x + self.vx > 780:
            self.vx = - self.vx
        self.vx = self.vx * (Ball.total_live - 2) / Ball.total_live

        if self.y - self.vy > Ball.y_max:
            self.vy = - self.vy
        if not ((abs(self.vy) < 10) & (abs(self.y - Ball.y_max) < 10)):
            self.vy -= (Ball.total_live - self.live) / 5
        else:
            self.vy = 0
            self.y = Ball.y_max

        self.x += self.vx
        self.y -= self.vy

        self.set_coords()
        self.live -= 1

    def hittest(self, obj):
        """Функция проверяет сталкивалкивается ли данный обьект с целью, описываемой в обьекте obj.

        Args:
            obj: Обьект, с которым проверяется столкновение.
        Returns:
            Возвращает True в случае столкновения мяча и цели. В противном случае возвращает False.
        """
        if ((self.x - obj.x) ** 2 + (self.y - obj.y) ** 2) ** (1 / 2) < self.r + obj.r:
            return True
        else:
            return False


class Gun:
    def __init__(self):
        self.f2_power = 10
        self.f2_on = 0
        self.an = 1
        self.id = canv.create_line(20, 450, 50, 420, width=7)

    def fire2_start(self, event):
        self.f2_on = 1

    def fire2_end(self, event):
        """Выстрел мячом.

        Происходит при отпускании кнопки мыши.
        Начальные значения компонент скорости мяча vx и vy зависят от положения мыши.
        """
        global balls, bullet
        bullet += 1
        new_ball = Ball()
        new_ball.r += 5
        self.an = math.atan((event.y - new_ball.y) / (event.x - new_ball.x))
        new_ball.vx = self.f2_power * math.cos(self.an)
        new_ball.vy = - self.f2_power * math.sin(self.an)
        balls += [new_ball]
        self.f2_on = 0
        self.f2_power = 10

    def targeting(self, event):
        """Прицеливание. Зависит от положения мыши."""
        if event:
            if event.x != 20:
                self.an = math.atan((event.y - 450) / (event.x - 20))
            else:
                self.an = math.pi / 2
        if self.f2_on:
            canv.itemconfig(self.id, fill='orange')
        else:
            canv.itemconfig(self.id, fill='black')
        canv.coords(
            self.id,
            20,
            450,
            20 + max(self.f2_power, 20) * math.cos(self.an),
            450 + max(self.f2_power, 20) * math.sin(self.an)
        )

    def power_up(self):
        if self.f2_on:
            if self.f2_power < 100:
                self.f2_power += 1
            canv.itemconfig(self.id, fill='orange')
        else:
            canv.itemconfig(self.id, fill='black')


class Target:
    points = 0
    id_points = canv.create_text(30, 30, text=points, font='28')

    x_left = 200
    x_right = 780
    y_left = 100
    y_right = 550

    def __init__(self):
        self.live = 1

        self.speed = rnd(1, 6)
        self.angle = random.uniform(0, 2 * math.pi)

        self.id = canv.create_oval(0, 0, 0, 0)

        self.r = rnd(10, 50)
        self.x = rnd(Target.x_left + self.r, Target.x_right - self.r)
        self.y = rnd(Target.y_left + self.r, Target.y_right - self.r)
        self.color = 'red'
        canv.coords(
            self.id,
            self.x - self.r,
            self.y - self.r,
            self.x + self.r,
            self.y + self.r
        )
        canv.itemconfig(self.id, fill=self.color)

    def hit(self, points=1):
        """Попадание шарика в цель."""
        canv.coords(self.id, -10, -10, -10, -10)
        Target.points += points
        canv.itemconfig(Target.id_points, text=Target.points)

    def move(self):
        """ Задает движение цели."""

        if self.live:
            if self.x - self.r + self.speed * math.cos(self.angle) < self.x_left:
                self.angle = math.pi - self.angle
            elif self.x + self.r + self.speed * math.cos(self.angle) > self.x_right:
                self.angle = math.pi - self.angle
            elif self.y - self.r + self.speed * math.sin(self.angle) < self.y_left:
                self.angle = 2 * math.pi - self.angle
            elif self.y + self.r + self.speed * math.sin(self.angle) > self.y_right:
                self.angle = 2 * math.pi - self.angle

            self.x = self.x + self.speed * math.cos(self.angle)
            self.y = self.y + self.speed * math.sin(self.angle)

            canv.coords(
                self.id,
                self.x - self.r,
                self.y - self.r,
                self.x + self.r,
                self.y + self.r
            )
            canv.itemconfig(self.id, fill=self.color)


screen1 = canv.create_text(400, 300, text='', font='28')
g1 = Gun()
bullet = 0
balls = []


def new_game():
    global screen1, balls, bullet
    bullet = 0
    balls = []

    canv.bind('<Button-1>', g1.fire2_start)
    canv.bind('<ButtonRelease-1>', g1.fire2_end)
    canv.bind('<Motion>', g1.targeting)

    n_targets = 10
    targets = []
    for k in range(n_targets):
        targ = Target()
        targets.append(targ)
    canv.itemconfig(screen1, text='')

    target_lives_sum = sum([t.live for t in targets])
    while (target_lives_sum > 0) or balls:

        for t1 in targets:
            t1.move()

        for b in balls:
            b.move()
            for t1 in targets:
                if b.hittest(t1) and t1.live:
                    t1.hit()
                    t1.live = 0

                    target_lives_sum -= 1
                    if target_lives_sum == 0:
                        canv.itemconfig(screen1, text='Вы уничтожили цели за ' +
                                                      str(bullet) +
                                                      ' выстрелов')
                        canv.bind('<Button-1>', '')
                        canv.bind('<ButtonRelease-1>', '')
                        canv.bind('<Motion>', g1.targeting)

            if b.live == 0:
                b.del_ball()
                balls.remove(b)
        canv.update()
        time.sleep(0.03)
        g1.targeting(0)
        g1.power_up()
    root.after(750, new_game)


new_game()

root.mainloop()
