"""Breakout!"""

___title___        = "Breakout"
___license___      = "MIT"
___categories___   = ["Games"]
___dependencies___ = ["app", "ugfx_helper", "buttons"]

from tilda import Buttons
import ugfx, ugfx_helper, dialogs
import time
import app
import random
import math

background_colour = ugfx.BLACK
framerate = 60

SCREEN_WIDTH = 240
SCREEN_HEIGHT = 320

class Ball:

    def __init__(self, x = 5.0, y = 5.0, dx = 2, dy = 2):
        self.colour = ugfx.WHITE
        self.diameter = 4
        self.x = x
        self.y = y
        self.dy = dx
        self.dx = dy
    
    def centerX(self):
        return self.x + self.diameter / 2
    
    def centerY(self):
        return self.y + self.diameter / 2
    
    def left(self):
        return self.x
    
    def right(self):
        return self.x + self.diameter
    
    def top(self):
        return self.y
    
    def bottom(self):
        return self.y + self.diameter
    
    def draw(self):
        ugfx.fill_ellipse(int(self.x), int(self.y), self.diameter, self.diameter, self.colour)

    def clear(self):
        ugfx.fill_ellipse(int(self.x), int(self.y), self.diameter, self.diameter, background_colour)

    def bounceX(self):
        self.dx *= -1

    def bounceY(self):
        self.dy *= -1
    
    def bounceUpwards(self, ratioFromMiddle):
        speed = math.sqrt(self.dx * self.dx + self.dy * self.dy)
        self.dx = math.sin(ratioFromMiddle) * speed
        self.dy = math.cos(ratioFromMiddle) * speed * -1

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

        if self.x < 0 or self.x + self.diameter > SCREEN_WIDTH:
            self.bounceX()

        if self.y < 0 or self.y + self.diameter > SCREEN_HEIGHT:
            self.bounceY()

    def hasCollidedWith(self, item):
        return self.right() >= item.left() and self.left() <= item.right() and self.top() <= item.bottom() and self.bottom() >= item.top()
    
    def isHorizontalCollision(self, item):
        return self.centerY() >= item.top() and self.centerY() <= item.bottom()

    def isVerticalCollision(self, item):
        return self.centerX() >= item.left() and self.centerX() <= item.right()

    def hasHitTop(self, item):
        return self.y + self.diameter >= item.top()
    
    def horizontalPositionFromMiddle(self, item):
        return min(1, max(0, (self.centerX() - item.left()) / (item.right() - item.left()))) - 1

class Paddle:

    def __init__(self, x = SCREEN_WIDTH / 2, width = SCREEN_WIDTH // 4, dx = 10):
        self.x = x
        self.dx = dx
        self.width = width
        self.height = 4
        self.colour = ugfx.WHITE

    def left(self):
        return self.x - self.width / 2

    def right(self):
        return self.x + self.width / 2

    def top(self):
        return self.bottom() - self.height
    
    def bottom(self):
        return SCREEN_HEIGHT
    
    def draw(self):
        ugfx.area(int(self.left()), int(self.top()), self.width, self.height, self.colour)

    def clear(self):
        ugfx.area(int(self.left()), int(self.top()), self.width, self.height, background_colour)

    def tick(self):
        if Buttons.is_pressed(Buttons.JOY_Right) and self.right() < SCREEN_WIDTH:
            self.x += self.dx
        if Buttons.is_pressed(Buttons.JOY_Left) and self.left() > 0:
            self.x -= self.dx

class Block:

    def __init__(self, x, y, width, height, colour = ugfx.WHITE):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.colour = colour
        self.visible = True
    
    def left(self):
        return self.x
    
    def right(self):
        return self.x + self.width
    
    def top(self):
        return self.y
    
    def bottom(self):
        return self.y + self.height
    
    def draw(self):
        colour = self.colour if self.visible else background_colour
        ugfx.area(int(self.left()), int(self.top()), self.width, self.height, colour)
    
    def clear(self):
        ugfx.area(int(self.left()), int(self.top()), self.width, self.height, background_colour)

    def hide(self):
        self.visible = False
        self.clear()


# Clear LEDs
leds = Neopix()
leds.display([0,0,0])
leds.display([0,0,0])

ugfx_helper.init()
ugfx.clear(background_colour)

def randomColour():
    return random.randint(0, 0xffffff)

def gameEnd(score):
    ugfx.text(5, 5, str(score) + ' POINTS!!!', ugfx.WHITE)
    for i in range(0, 10):
        leds.display([randomColour(), 0])
        time.sleep(0.1)
        leds.display([0, randomColour()])
        time.sleep(0.1)
    leds.display([0, 0])
    time.sleep(1)

def gameOver(score):
    ugfx.text(5, 5, 'GAME OVER', ugfx.WHITE)
    ugfx.text(5, 30, str(score) + ' points', ugfx.WHITE)
    for i in range(0, 5):
        leds.display([0xff0000, 0])
        time.sleep(0.2)
        leds.display([0, 0xff0000])
        time.sleep(0.2)
    leds.display([0, 0])
    time.sleep(1)

def runGame():
    paddle = Paddle()
    direction = random.random() - 0.5
    initial_speed_up = 4
    ball = Ball(x = SCREEN_WIDTH / 2, y = SCREEN_HEIGHT / 2, dx = math.cos(direction) * initial_speed_up, dy = math.sin(direction) * initial_speed_up)
    blocks = \
        [Block(x = x, y = 30, width = 36, height = 10, colour = ugfx.RED) for x in range(24, SCREEN_WIDTH - 24, 40)] + \
        [Block(x = x, y = 44, width = 36, height = 10, colour = ugfx.GREEN) for x in range(24, SCREEN_WIDTH - 24, 40)] + \
        [Block(x = x, y = 58, width = 36, height = 10, colour = ugfx.BLUE) for x in range(24, SCREEN_WIDTH - 24, 40)] + \
        [Block(x = x, y = 72, width = 36, height = 10, colour = ugfx.YELLOW) for x in range(24, SCREEN_WIDTH - 24, 40)] + \
        [Block(x = x, y = 86, width = 36, height = 10, colour = ugfx.ORANGE) for x in range(24, SCREEN_WIDTH - 24, 40)]

    def invisibleBlocks():
        return [block for block in blocks if not(block.visible)]

    for block in blocks:
        block.draw()
    while True:
        paddle.draw()
        ball.draw()
        time.sleep(1.0 / framerate)
        paddle.clear()
        ball.clear()
        paddle.tick()
        ball.tick()
        if Buttons.is_pressed(Buttons.BTN_Menu):
            gameRunning = False
        if all([not(block.visible) for block in blocks]):
            gameEnd(score = 50 + len(invisibleBlocks()))
            break
        if ball.hasHitTop(paddle):
            if ball.hasCollidedWith(paddle):
                ball.bounceUpwards(ball.horizontalPositionFromMiddle(paddle))
            else:
                gameOver(score = len(invisibleBlocks()))
                break
        for block in blocks:
            if block.visible and ball.hasCollidedWith(block):
                block.hide()
                if ball.isHorizontalCollision(block):
                    ball.bounceX()
                if ball.isVerticalCollision(block):
                    ball.bounceY()
        
runGame()
app.restart_to_default()
