"""
Created on Oct 12, 2022

@author: Robert Duvall

This module represents the game of Pong, the first popular arcode game!

This version shows how to use the Arcade module instead of the Turtle module.
"""
import arcade
import random

# choose a name for your game to appear in the title bar of the game window
GAME_NAME = 'Pong with Arcade'
# choose the size of your game window
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
# choose how fast paddles move
PADDLE_SPEED = 20
# choose where paddles are positioned
PADDLE_OFFSET = 50
# choose where scores are positioned
SCORE_XOFFSET = 50
SCORE_YOFFSET = 80
# colors representing different players so different sprites match
PLAYER1_COLOR = arcade.color.YELLOW
PLAYER2_COLOR = arcade.color.ORANGE
# sounds to play during the game
BOUNCE_SOUND = arcade.load_sound(':resources:sounds/error2.wav')
MISS_SOUND = arcade.load_sound(':resources:sounds/gameover4.wav')


class Paddle(arcade.SpriteSolidColor):
    """
    This class represents a user controlled paddle that blocks the ball from going off the screen.
    """
    def __init__(self, color, x, y):
        # create with width and height in pixels and its color
        super().__init__(20, 100, color)
        # center_x and center_y are inherited variables from Sprite superclass
        self.center_x = x
        self.center_y = y
        # our own variables to reset the paddle back to its starting position
        self.starting_x = x
        self.starting_y = y

    def reset(self):
        """
        Reset the paddle back to its starting position.
        """
        self.center_x = self.starting_x
        self.center_y = self.starting_y

    def move(self, dy):
        """
        Move the paddle only up or down.
        """
        self.center_y += dy


class Ball(arcade.Sprite):
    """
    This class represents a ball that moves around the screen bouncing off things.
    """
    def __init__(self, x, y):
        # create with image to display and scale its size
        super().__init__('ball.gif', 0.2)
        # center_x and center_y are inherited variables from Sprite superclass
        self.center_x = x
        self.center_y = y
        # our own variables to reset the ball back to its starting position
        self.starting_x = x
        self.starting_y = y

    def reset(self):
        """
        Reset the ball back to its starting position and randomize its velocity.
        """
        self.center_x = self.starting_x
        self.center_y = self.starting_y
        # inherited variables from Sprite superclass that represent movement if update() method is called
        self.change_x = self.random_nonzero(4)
        self.change_y = self.random_nonzero(4)

    def bounce_x(self):
        """
        Bounce off vertical surface by reversing only the X velocity (so it keeps traveling up or down as before).
        """
        self.change_x = -self.change_x

    def bounce_y(self):
        """
        Bounce off horizontal surface by reversing only the Y velocity (so it keeps traveling left or right as before).
        """
        self.change_y = -self.change_y

    def random_nonzero(self, maxValue):
        """
        Return a random value between -maxValue and +maxValue that cannot be 0.
        """
        value = random.randint(1, maxValue)
        if random.randint(1, 2) == 1:
            value = -value
        return value


class Score(arcade.Text):
    """
    This class represents a score displayed to the players and updated when a player misses the ball.
    """
    def __init__(self, color, x, y):
        # create with text to display at the position (x, y) and a font size
        super().__init__('0', x, y, color, 64)
        # our own variable to keep track of the score as a number (not just text)
        self.score = 0

    def reset(self):
        """
        Reset the score back to 0.
        """
        self.score = 0
        self.text = str(self.score)

    def increment(self):
        """
        Update the score's value, and the text, so it will be drawn correctly.
        """
        self.score += 1
        # also update text string for drawing
        self.text = str(self.score)
        # also play a sound
        arcade.play_sound(MISS_SOUND)


class Game(arcade.Window):
    """
    This class represents the entire game:
    - setting up any objects to appear in game
    - updating their values (in method on_update)
    - drawing them (in method on_draw)
    - responding to key input (in method on_key_press)
    - responding to mouse input (in method on_mouse_press)
    """
    def __init__(self):
        # create with size (width, height) and a title
        super().__init__(SCREEN_WIDTH, SCREEN_HEIGHT, GAME_NAME)
        # create our own game objects to show during the game
        self.score1Text = Score(PLAYER1_COLOR, SCREEN_WIDTH // 2 - SCORE_XOFFSET, SCREEN_HEIGHT - SCORE_YOFFSET)
        self.score2Text = Score(PLAYER2_COLOR, SCREEN_WIDTH // 2 + SCORE_XOFFSET, SCREEN_HEIGHT - SCORE_YOFFSET)
        self.paddle1 = Paddle(PLAYER1_COLOR, PADDLE_OFFSET, SCREEN_HEIGHT // 2)
        self.paddle2 = Paddle(PLAYER2_COLOR, SCREEN_WIDTH - PADDLE_OFFSET, SCREEN_HEIGHT // 2)
        self.ball = Ball(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2)
        # make a list of all the game sprites to make it easier to use in some cases
        self.theGameObjects = [
            self.score1Text,
            self.score2Text,
            self.paddle1,
            self.paddle2,
            self.ball,
        ]

    def setup(self):
        """
        Set up the initial game scene at the beginning of the game or to allow playing again after a win or loss
        """
        arcade.set_background_color(arcade.color.BLACK)
        # reset game objects by looping through the list and calling their reset method
        for obj in self.theGameObjects:
            obj.reset()

    def on_key_press(self, key, modifiers):
        """
        Called whenever a key is pressed.
        """
        if key == arcade.key.UP:
            self.paddle2.move(PADDLE_SPEED)
        elif key == arcade.key.DOWN:
            self.paddle2.move(-PADDLE_SPEED)
        elif key == arcade.key.W:
            self.paddle1.move(PADDLE_SPEED)
        elif key == arcade.key.S:
            self.paddle1.move(-PADDLE_SPEED)
        elif key == arcade.key.SPACE:
            # reset the entire game back to starting configuration
            self.setup()

    def on_mouse_press(self, x, y, button, modifiers):
        """
        Called whenever a mouse button is pressed.
        """
        self.ball.reset()

    def on_draw(self):
        """
        Render all the game objects on the screen.
        """
        # DO NOT CHANGE -- always clear the screen as the FIRST step
        self.clear()
        # draw game objects by looping through the list and calling their draw method
        for obj in self.theGameObjects:
            obj.draw()

    def on_update(self, dt):
        """
        Handle game "rules" for every step (i.e., frame or "moment"):
         - movement: move the ball each step
         - collisions: check if the ball collided with the sides or paddles and then bounce or reset it
        """
        # always move ball based on its speed
        self.ball.update()

        # check if ball hits top or bottom side, bounce it by reversing theBall's dy variable
        if (self.ball.center_y + self.ball.height/2) > SCREEN_HEIGHT or (self.ball.center_y - self.ball.height / 2) < 0:
            self.ball.bounce_y()

        # check if the ball got past player 1's paddle (hit the right edge), then reset ball and update player 2's score
        if self.ball.center_x > SCREEN_WIDTH:
            self.score1Text.increment()
            self.ball.reset()

        # check if the ball got past player 2's paddle (hit the left edge), then reset ball and update player 1's score
        if self.ball.center_x < 0:
            self.score2Text.increment()
            self.ball.reset()

        # check if the ball hits thePaddle1, bounce it by reversing theBall's dx variable
        if arcade.check_for_collision(self.ball, self.paddle1):
            self.ball.bounce_x()
            arcade.play_sound(BOUNCE_SOUND)

        # check if the ball hits thePaddle2, bounce it by reversing theBall's dx variable
        if arcade.check_for_collision(self.ball, self.paddle2):
            self.ball.bounce_x()
            arcade.play_sound(BOUNCE_SOUND)


# Set up the game classes
game = Game()
game.setup()
# Play the game forever
arcade.run()
