class BreakoutGraphics:

    def __init__(self, ball_radius=BALL_RADIUS, paddle_width=PADDLE_WIDTH,
                 paddle_height=PADDLE_HEIGHT, paddle_offset=PADDLE_OFFSET,
                 brick_rows=BRICK_ROWS, brick_cols=BRICK_COLS,
                 brick_width=BRICK_WIDTH, brick_height=BRICK_HEIGHT,
                 brick_offset=BRICK_OFFSET, brick_spacing=BRICK_SPACING,
                 title='Breakout', __dy=INITIAL_Y_SPEED):
        # Create a graphical window, with some extra space.
        self.window_width = brick_cols * (brick_width + brick_spacing) - brick_spacing
        self.window_height = brick_offset + 3 * (brick_rows * (brick_height + brick_spacing) - brick_spacing)
        self.window = GWindow(width=self.window_width, height=self.window_height, title=title)

        # Create a paddle.
        self.paddle = GRect(width=paddle_width, height=paddle_height, x=(self.window_width-paddle_width)/2, y=(self.window_height-paddle_offset-paddle_height))
        self.paddle.filled = True
        self.paddle.color = '#513743'
        self.paddle.fill_color = '#513743'
        self.window.add(self.paddle)

        self.life_label = GLabel('● ● ●')
        self.life_label.font = '-15'
        self.life_label.color = '#c53d43'

        self.score_label = GLabel('score: 0')
        self.score_label.font = '-15'
        self.score_label.color = '#455765'

        self.brick_rows = BRICK_ROWS
        self.brick_cols = BRICK_COLS

        # Initialize our mouse listeners.
        onmousemoved(self.reset_paddle_location)

        # Draw bricks.
        self.create_bricks()

    def create_bricks(self, brick_offset=BRICK_OFFSET, brick_width=BRICK_WIDTH, brick_height=BRICK_HEIGHT, brick_spacing=BRICK_SPACING):
        d_y = brick_offset
        color = ['#96514d', '#d3381c', '#2c4f54', '#00a381', '#008899', '#3e62ad', '#316745', '#028760', '#fabf14', '#274a78']
        for i in range(self.brick_rows):
            c = color[random.randint(0, 9)]
            d_x = 0
            for j in range(self.brick_cols - 1):
                brick = GRect(brick_width, brick_height, x=d_x, y=d_y)
                brick.filled = True
                brick.color = c
                brick.fill_color = brick.color
                self.window.add(brick)
                d_x = d_x + brick_width + brick_spacing
            brick = GRect(brick_width, brick_height, x=d_x, y=d_y)
            brick.filled = True
            brick.color = c
            brick.fill_color = brick.color
            self.window.add(brick)
            d_y = d_y + brick_height + brick_spacing

    def reset_paddle_location(self, mouse):
        if mouse.x >= self.window.width:
            self.paddle.x = self.window.width - self.paddle.width/2
        elif mouse.x <= 0:
            self.paddle.x = -self.paddle.width/2
        else:
            self.paddle.x = mouse.x - self.paddle.width/2

        # Default initial velocity for the ball.
    def get_dx(self):
        __dx = random.randint(1, MAX_X_SPEED)
        if random.random() > 0.5:
            __dx = -__dx
        return __dx

    def get_dy(self):
        __dy = INITIAL_Y_SPEED
        return __dy

    def ball_collide_paddle(self):
        if self.window.get_object_at(self.ball.x, self.ball.y+self.ball.height) is self.paddle:
            return True
        if self.window.get_object_at(self.ball.x+self.ball.width, self.ball.y+self.ball.height) is self.paddle:
            return True
        if self.window.get_object_at(self.ball.x+self.ball.width, self.ball.y) is self.paddle:
            return True
        if self.window.get_object_at(self.ball.x, self.ball.y) is self.paddle:
            return True

    def ball_collide_brick(self):
        if self.window.get_object_at(self.ball.x, self.ball.y+self.ball.height) is not None and self.window.get_object_at(self.ball.x, self.ball.y+self.ball.height) is not self.paddle and self.window.get_object_at(self.ball.x, self.ball.y+self.ball.height) is not self.life_label and self.window.get_object_at(self.ball.x, self.ball.y+self.ball.height) is not self.score_label:
            self.window.remove(self.window.get_object_at(self.ball.x, self.ball.y+self.ball.height))
            return True
        if self.window.get_object_at(self.ball.x+self.ball.width, self.ball.y+self.ball.height) is not self.paddle and self.window.get_object_at(self.ball.x+self.ball.width, self.ball.y+self.ball.height) is not None and self.window.get_object_at(self.ball.x+self.ball.width, self.ball.y+self.ball.height) is not self.life_label and self.window.get_object_at(self.ball.x+self.ball.width, self.ball.y+self.ball.height) is not self.score_label:
            self.window.remove(self.window.get_object_at(self.ball.x+self.ball.width, self.ball.y+self.ball.height))
            return True
        if self.window.get_object_at(self.ball.x+self.ball.width, self.ball.y) is not self.paddle and self.window.get_object_at(self.ball.x+self.ball.width, self.ball.y) is not None and self.window.get_object_at(self.ball.x+self.ball.width, self.ball.y) is not self.life_label and self.window.get_object_at(self.ball.x+self.ball.width, self.ball.y) is not self.score_label:
            self.window.remove(self.window.get_object_at(self.ball.x+self.ball.width, self.ball.y))
            return True
        if self.window.get_object_at(self.ball.x, self.ball.y) is not self.paddle and self.window.get_object_at(self.ball.x, self.ball.y) is not None and self.window.get_object_at(self.ball.x, self.ball.y) is not self.life_label and self.window.get_object_at(self.ball.x, self.ball.y) is not self.score_label:
            self.window.remove(self.window.get_object_at(self.ball.x, self.ball.y))
            return True

    # Center a filled ball in the graphical window.
    def reset_ball_location(self, ball_radius=BALL_RADIUS):
        self.ball = GOval(ball_radius*2, ball_radius*2, x=self.window_width/2-ball_radius, y=self.window_height/2-ball_radius)
        self.ball.filled = True
        self.ball.color = '#705b67'
        self.ball.fill_color = self.ball.color
        self.window.add(self.ball)

    def lose(self):
        self.window.clear()
        lose_background = GRect(self.window.width, self.window.height)
        lose_background.filled = True
        lose_background.color = '#a6a5c4'
        lose_background.fill_color = '#a6a5c4'
        lose_label = GLabel('GAME OVER')
        lose_label.font = '-50'
        lose_label.color = '#e7e7eb'
        self.window.add(lose_background)
        self.window.add(lose_label, self.window.width/2-lose_label.width/2, self.window.height/2.5)
        self.window.add(self.score_label, (self.window_width-self.score_label.width)/2, lose_label.y+50)

    def win(self):
        self.window.clear()
        win_background = GRect(self.window.width, self.window.height)
        win_background.filled = True
        win_background.color = '#e5abbe'
        win_background.fill_color = '#e5abbe'
        win_label = GLabel('WIN')
        win_label.font = '-50'
        win_label.color = '#fdeff2'
        self.window.add(win_background)
        self.window.add(win_label, self.window.width/2-win_label.width/2, self.window.height/2.5)
        self.window.add(self.score_label, (self.window_width-self.score_label.width)/2, win_label.y+50)
class BreakoutGraphics:
    def __init__(self, ball_radius=BALL_RADIUS, paddle_width=PADDLE_WIDTH,
                 paddle_height=PADDLE_HEIGHT, paddle_offset=PADDLE_OFFSET,
                 brick_rows=BRICK_ROWS, brick_cols=BRICK_COLS,
                 brick_width=BRICK_WIDTH, brick_height=BRICK_HEIGHT,
                 brick_offset=BRICK_OFFSET, brick_spacing=BRICK_SPACING,
                 title='Breakout'):
        """
        Parameter settings.
        :param ball_radius: Controls the radius of the ball.
        :param paddle_width: Controls the width of the paddle.
        :param paddle_height: Controls the height of the paddle.
        :param paddle_offset: Controls the y-position of the paddle.
        :param brick_rows: Controls the number of rows of bricks.
        :param brick_cols: Controls the number of columns of bricks.
        :param brick_width: Controls the width of a single brick.
        :param brick_height: Controls the height of a single brick.
        :param brick_offset: Controls the y-position of the highest row of bricks.
        :param brick_spacing: Controls the interval between any two bricks.
        :param title: Set the title of the canvas.
        """
        self.ball_radius = ball_radius
        self.paddle_width = paddle_width
        self.paddle_height = paddle_height
        self.paddle_offset = paddle_offset
        self.brick_cols = brick_cols
        self.brick_rows = brick_rows
        self.brick_width = brick_width
        self.brick_height = brick_height
        self.brick_offset = brick_offset
        self.brick_spacing = brick_spacing

        # Create a label for usernames before create a window.
        self.name = GLabel('Player: ' + input('Nickname: '))
        self.name.font = '-15-bold'

        # Create a graphical window, with some extra space.
        self.window_width = self.brick_cols * (brick_width+brick_spacing) - brick_spacing
        self.window_height = brick_offset + 3 * (brick_rows * (brick_height+brick_spacing) - brick_spacing)
        self.window = GWindow(width=self.window_width, height=self.window_height, title=title)

        # Initial settings before the game starts.
        self.score = 0
        self.score_label = GLabel('Score: ' + str(self.score))
        self.score_label.font = '-15-bold'
        self.lives = 3
        self.lives_label = GLabel('Lives: ' + str(self.lives))
        self.lives_label.font = '-15-bold'
        self.click2start = GLabel('Click to Start !')
        self.click2start.font = '-20-bold'
        self.window.add(self.name, 5, self.brick_offset - 25)
        self.window.add(self.click2start, 130, self.window.height-150)
        self.window.add(self.score_label, 5, self.window.height)
        self.window.add(self.lives_label, self.window_width-100, self.window_height-5)

        # Create a paddle.
        self.count = 1  # For paddle width resetting after losing one live.
        self.paddle = GRect(self.paddle_width, self.paddle_height)
        self.paddle.filled = True
        self.window.add(self.paddle, x=(self.window_width-self.paddle_width)/2, y=self.window_height-paddle_offset)

        # Center a filled ball in the graphical window.
        self.ball = GOval(ball_radius*2, ball_radius*2)
        self.ball.filled = True
        self.window.add(self.ball, (self.window_width-self.ball.width)/2, (self.window_height-brick_height)/2)

        # Default initial velocity for the ball.
        self.__dx = 0
        self.__dy = INITIAL_Y_SPEED
        self.set_x_velocity()

        # Initialize our mouse listeners.
        self.is_game_started = False
        onmouseclicked(self.handle_click)

        # Draw bricks.
        self.brick_left = self.brick_cols * self.brick_rows
        for i in range(self.brick_rows):
            for j in range(self.brick_cols):
                # Different colors for every two rows.
                self.brick = GRect(self.brick_width, self.brick_height)
                self.brick.filled = True
                if i < 2:
                    self.brick.fill_color = 'lightcoral'
                elif 2 <= i < 4:
                    self.brick.fill_color = 'peachpuff'
                elif 4 <= i < 6:
                    self.brick.fill_color = 'lightyellow'
                elif 6 <= i < 8:
                    self.brick.fill_color = 'palegreen'
                elif 8 <= i < 10:
                    self.brick.fill_color = 'aqua'
                self.window.add(self.brick, x=j*(self.brick_width+self.brick_spacing),
                                y=self.brick_offset+i*(self.brick_height+self.brick_spacing))

    def paddle_move(self, event):
        """
        This function will execute if the the game is started. If the game is started,
        it will detect the position of the mouse so that players can control the paddle
        with their mouse.
        :param event: Detects position of the mouse.
        """
        if self.is_game_started:
            # If the mouse is out of the range of the width of the window, the paddle will still remain in the window.
            if event.x <= self.paddle.width/2:
                self.paddle.x = 0
            elif event.x >= self.window_width - self.paddle_width/2:
                self.paddle.x = self.window_width - self.paddle_width
            else:
                self.paddle.x = event.x - self.paddle_width/2

    def get_x_velocity(self):
        """
        Since self.__dx is private instance variable, we need to create a getter for the user-end.
        :return: The value of self.__dx (the x-velocity)
        """
        return self.__dx

    def get_y_velocity(self):
        """
        Since self.__dy is private instance variable, we need to create a getter for the user-end.
        :return: The value of self.__dy (the y-velocity)
        """
        return self.__dy

    def set_x_velocity(self):
        """
        Setting a random x-velocity and y-velocity for the ball. The y-velocity is a constant, and
        the x-velocity is set between 1 and MAX_X_SPEED, There is a 50% chance that the ball will have
        opposite direction in x dimension.
        """
        self.__dx = random.randint(2, MAX_X_SPEED)
        if random.random() < 0.5:
            self.__dx = -self.__dx

    def handle_click(self, _):
        """
        This function will execute if a click on the mouse. After a single click,
        the game will start, and any clicks can no longer affect the game.
        :param _: Detects a click on the mouse.
        :return: self.count
        """
        self.window.remove(self.click2start)
        self.is_game_started = True
        onmousemoved(self.paddle_move)

    def probe_obj(self):
        """
        This function will detect any collision between the ball with other objects.
        :return: True, if the ball bump into the paddle or the bricks.
        """
        # The variable 'obj' will change to the next corner if the checked None.
        obj1 = self.window.get_object_at(self.ball.x, self.ball.y)
        obj2 = self.window.get_object_at(self.ball.x + self.ball.width, self.ball.y)
        obj3 = self.window.get_object_at(self.ball.x, self.ball.y + self.ball.height)
        obj4 = self.window.get_object_at(self.ball.x + self.ball.width, self.ball.y + self.ball.height)
        point = obj1
        if point is None or point is self.name and point is self.score_label and point is self.lives_label:
            point = obj2
        if point is None or point is self.name and point is self.score_label and point is self.lives_label:
            point = obj3
        if point is None or point is self.name and point is self.score_label and point is self.lives_label:
            point = obj4
        if point is None or point is self.name and point is self.score_label and point is self.lives_label:
            pass
        if point is self.paddle:
            # To avoid the ball from sticking on the paddle, I move the ball above the paddle.
            self.ball.move(0, -self.paddle_height - self.ball.height)
            # When the ball touches the paddle, the paddle will change its color to the ball's color.
            self.paddle.color = self.ball.color
            self.paddle.fill_color = self.ball.fill_color
            return True
        if point is not self.paddle and point is not None and point is not self.name\
                and point is not self.score_label and point is not self.lives_label:
            # Every time when the ball bump into a brick, the score will reset.
            self.window.remove(point)
            self.brick_left -= 1
            # The formula of score counting.
            self.score = int((self.brick_rows * self.brick_cols - self.brick_left) ** 1.5)
            if self.brick_left == (self.brick_rows * self.brick_cols) * 1/2:
                self.lives += 1
                self.window.remove(self.lives_label)
                self.lives_label = GLabel('Lives: ' + str(self.lives))
                self.lives_label.font = '-15-bold'
                self.window.add(self.lives_label, self.window_width-100, self.window_height-5)
            self.window.remove(self.score_label)
            self.score_label = GLabel('Score: ' + str(self.score))
            self.score_label.font = '-15-bold'
            self.window.add(self.score_label, 5, self.window.height)
            # The color of the ball depends on how many bricks are left.
            if self.brick_rows * self.brick_cols * 3/5 < self.brick_left <= self.brick_rows * self.brick_cols * 4/5:
                self.ball.color = 'palegreen'
                self.ball.fill_color = 'palegreen'
            elif self.brick_rows * self.brick_cols * 2/5 < self.brick_left <= self.brick_rows * self.brick_cols * 3/5:
                self.ball.color = 'lightyellow'
                self.ball.fill_color = 'lightyellow'
            elif self.brick_rows * self.brick_cols * 1/5 < self.brick_left <= self.brick_rows * self.brick_cols * 2/5:
                self.ball.color = 'peachpuff'
                self.ball.fill_color = 'peachpuff'
            # More difficult to see the ball.
            elif self.brick_left <= self.brick_rows * self.brick_cols * 1/5:
                self.ball.color = 'black'
                self.ball.fill_color = 'black'
            return True
        # Lives will reset when the ball fall out of the lower margin of the window.

    def fall_out(self):
        self.is_game_started = False
        # Every chance loses, the variable 'count', which affects the length of the paddle, will add 0.15.
        self.count += 0.15
        self.lives -= 1
        self.window.remove(self.lives_label)
        self.lives_label = GLabel('Lives: ' + str(self.lives))
        self.lives_label.font = '-15-bold'
        self.window.add(self.lives_label, self.window_width - 100, self.window_height - 5)
        self.set_x_velocity()
        return self.count, self.lives, self.__dx

    def build_ball(self):
        """
        This function will first remove the original ball and create a new ball. The color of the ball depends
        on the number of remaining bricks.
        """
        self.window.remove(self.ball)
        self.ball = GOval(self.ball_radius*2, self.ball_radius*2)
        self.ball.filled = True
        # The color of the ball depends on how many bricks are left.
        if self.brick_rows * self.brick_cols * 3 / 5 < self.brick_left <= self.brick_rows * self.brick_cols * 4 / 5:
            self.ball.color = 'palegreen'
            self.ball.fill_color = 'palegreen'
        elif self.brick_rows * self.brick_cols * 2 / 5 < self.brick_left <= self.brick_rows * self.brick_cols * 3 / 5:
            self.ball.color = 'lightyellow'
            self.ball.fill_color = 'lightyellow'
        elif self.brick_rows * self.brick_cols * 1 / 5 < self.brick_left <= self.brick_rows * self.brick_cols * 2 / 5:
            self.ball.color = 'peachpuff'
            self.ball.fill_color = 'peachpuff'
        # More difficult to see the ball.
        elif self.brick_left <= self.brick_rows * self.brick_cols * 1 / 5:
            self.ball.color = 'black'
            self.ball.fill_color = 'black'
        self.window.add(self.ball, (self.window_width-self.ball.width)/2, (self.window_height-self.brick_height)/2)

    def build_paddle(self):
        """
        This function will first remove the original paddle and create a new one. The width of the new paddle
        depends on the variable of count.
        """
        self.window.remove(self.paddle)
        self.paddle = GRect(self.paddle_width/self.count, self.paddle_height, x=(self.window_width-self.paddle_width)/2,
                            y=self.window_height-self.paddle_offset)
        self.paddle.filled = True
        self.paddle.color = self.ball.color
        self.paddle.fill_color = self.ball.fill_color
        self.window.add(self.paddle)

    def game_over(self):
        """
        This function execute when no lives are remaining. The window will show the game is over
        and the final score. The score depends on the number of remaining bricks and the variable
        count.
        """
        self.is_game_started = False
        self.window.clear()
        game_over = GLabel('Game Over')
        game_over.font = '-60-bold'
        lose = GLabel('You Lose...')
        lose.font = '-30-bold'
        score_label = GLabel('Your Score:' + str(self.score))
        score_label.font = 'courier-25-bold'
        background = GRect(self.window.width, self.window.height)
        background.filled = True
        background.color = 'red'
        background.fill_color = 'red'
        self.window.add(background)
        self.window.add(game_over, 2, (self.window.height/2)+30)
        self.window.add(lose, 120, (self.window.height/2+120))
        self.window.add(score_label, 100, self.window.height)

    def winner(self):
        """
        This function will execute if and only if a player successfully eliminates
        all the bricks. After all bricks are eliminated, the window will show
        'You win!!!!' and the final score. The score depends on the number of remaining
        bricks and the variable count.
        """
        self.window.clear()
        you_win = GLabel('Love Cindy!!!!')
        you_win.font = '-60-bold'
        # One live remaining worth 100 points.
        self.score += self.lives * 100
        score_label = GLabel('Your Score is:' + str(self.score), 0, 20)
        score_label.font = 'courier-25-bold'
        background = GRect(self.window.width, self.window.height)
        background.filled = True
        background.color = 'pink'
        background.fill_color = 'pink'
        self.window.add(background)
        self.window.add(you_win, 5, (self.window.height/2)+30)
        self.window.add(score_label, 50, self.window.height)
class BreakoutGraphics:
    def __init__(self,
                 ball_radius=BALL_RADIUS,
                 paddle_width=PADDLE_WIDTH,
                 paddle_height=PADDLE_HEIGHT,
                 paddle_offset=PADDLE_OFFSET,
                 brick_rows=BRICK_ROWS,
                 brick_cols=BRICK_COLS,
                 brick_width=BRICK_WIDTH,
                 brick_height=BRICK_HEIGHT,
                 brick_offset=BRICK_OFFSET,
                 brick_spacing=BRICK_SPACING,
                 title='Breakout'):
        num_bricks = 100
        # Create a graphical window, with some extra space.
        window_width = brick_cols * (brick_width +
                                     brick_spacing) - brick_spacing
        window_height = brick_offset + 3 * (brick_rows *
                                            (brick_height + brick_spacing) -
                                            brick_spacing)
        self.window = GWindow(width=window_width,
                              height=window_height,
                              title=title)
        self.paddle = GRect(paddle_width, paddle_height, x=(window_width - paddle_width)/2, y = window_height - \
                            paddle_offset)

        # initializing the paddle
        self.paddle.filled = True
        self.paddle.fill_color = 'black'
        self.window.add(self.paddle)

        # initializing the ball
        self.ball = GOval(width=ball_radius * 2,
                          height=ball_radius * 2,
                          x=window_width / 2 - BALL_RADIUS,
                          y=window_height / 2 - BALL_RADIUS)
        self.ball.filled = True
        self.ball.fill_color = 'black'
        self.window.add(self.ball)

        # initial velocity
        self.vx = 0
        self.vy = INITIAL_Y_SPEED

        # draw bricks
        self.draw_bricks()

        # number of lives left
        self.num_lives = 3

        # running? big question mark
        self.running = False

        # brick count
        self.brick_count = 100

        # mouse listeners
        onmouseclicked(self.start)
        onmousemoved(self.move_paddle)

    def start(self, event):
        """
        Starts game on mouse click.
        """
        self.running = True

    def draw_bricks(self):
        """
        Draws bricks by row and with color changing for each turn.
        """
        x_init = 0
        curr_y = BRICK_OFFSET
        color_index = 0
        for row in range(1, BRICK_ROWS + 1):
            curr_x = x_init
            for col in range(BRICK_COLS):
                self.brick = GRect(BRICK_WIDTH,
                                   BRICK_HEIGHT,
                                   x=curr_x,
                                   y=curr_y)
                self.brick.filled = True
                self.brick.fill_color = COLORS[color_index]
                self.window.add(self.brick)
                curr_x += BRICK_WIDTH + BRICK_SPACING
            curr_y += BRICK_HEIGHT + BRICK_SPACING
            if row % 2 == 0:
                color_index += 1

    def move_paddle(self, event):
        """
        Moves paddle with mouse event.
        """
        self.paddle.x = event.x - PADDLE_WIDTH / 2

    def move_ball(self):
        """
        Moves ball with proper x and y velocities.
        """
        self.ball.move(self.vx, self.vy)

    def handle_collisions(self):
        """
        Handles collisions as necessary per animation loop.
        """
        # changing for wall collisions
        if self.side_wall_hit(
        ):  # checks for side wall hits and changes x-component
            print('oop hit a side wall')
            self.vx = -self.vx
        elif self.top_wall_paddle_hit(
        ):  # checks for top/paddle hits and changes y-component
            print('oop hit the top or a paddle')
            self.vy = -self.vy
        # changing for block collisions
        elif self.brick_paddle_hit(
        ) != -1 and self.ball.y != self.window.height - 2 * BALL_RADIUS - PADDLE_OFFSET - PADDLE_HEIGHT:
            loc_x, loc_y = self.brick_paddle_hit()
            print(loc_x, loc_y)
            self.remove_brick(loc_x, loc_y)
            self.vy = -self.vy
            self.vx = random.uniform(-MAX_X_SPEED, MAX_X_SPEED)
        # changing for bottom collision
        elif self.bottom_hit():
            self.num_lives -= 1
            print('the number of lives is', self.num_lives)
            self.reset()

    def bottom_hit(self):
        """
        Checks if the bottom was hit.
        """
        if self.ball.y + 2 * BALL_RADIUS >= self.window.height:
            return True
        else:
            return False

    def side_wall_hit(self):
        """
        Checks if the left or right walls were hit.
        """
        if self.ball.x <= 0 or self.ball.x >= self.window.width - 2 * BALL_RADIUS:
            return True
        else:
            return False

    def top_wall_paddle_hit(self):
        """
        Checks if the top wall or paddle was hit.
        """
        if self.ball.y <= 0:
            return True
        elif self.brick_paddle_hit(
        ) != -1 and self.ball.y >= self.window.height - 2 * BALL_RADIUS - PADDLE_OFFSET:
            return True
        else:
            return False

    def brick_paddle_hit(self):
        """
        Checks, according to the guidelines in the handout, if a collision has occurred with the ball and a brick or
        the paddle.
        """
        if self.window.get_object_at(self.ball.x, self.ball.y) != None:
            return self.ball.x, self.ball.y
        elif self.window.get_object_at(self.ball.x + 2 * BALL_RADIUS,
                                       self.ball.y) != None:
            return self.ball.x + 2, self.ball.y
        elif self.window.get_object_at(self.ball.x,
                                       self.ball.y + 2 * BALL_RADIUS) != None:
            return self.ball.x, self.ball.y + 2 * BALL_RADIUS
        elif self.window.get_object_at(self.ball.x + 2 * BALL_RADIUS,
                                       self.ball.y + 2 * BALL_RADIUS) != None:
            return self.ball.x + 2 * BALL_RADIUS, self.ball.y + 2 * BALL_RADIUS
        else:
            return -1

    def remove_brick(self, loc_x, loc_y):
        """
        Remove a brick when hit.
        """
        if self.window.get_object_at(loc_x, loc_y) != self.paddle:
            self.window.remove(self.window.get_object_at(loc_x, loc_y))
            self.brick_count -= 1

    def reset(self):
        """
        Resets the game when the bottom is hit.
        """
        self.window.clear()
        self.window.remove(self.ball)

        self.ball = GOval(width=BALL_RADIUS * 2,
                          height=BALL_RADIUS * 2,
                          x=self.window.width / 2 - BALL_RADIUS,
                          y=self.window.height / 2 - BALL_RADIUS)
        self.ball.filled = True
        self.ball.fill_color = 'black'
        self.window.add(self.ball)
        self.draw_bricks()
        self.brick_count = 100
        self.vx = 0
        self.vy = INITIAL_Y_SPEED
        self.paddle.filled = True
        self.paddle.fill_color = 'black'
        self.window.add(self.paddle)
        self.running = False

    def game_over(self):
        game_over_label = GLabel('GAME OVER',
                                 x=self.window.width / 2 - 40,
                                 y=self.window.height / 2)
        self.window.add(game_over_label)
Exemple #4
0
class BreakoutGraphics:
    def __init__(self,
                 ball_radius=BALL_RADIUS,
                 paddle_width=PADDLE_WIDTH,
                 paddle_height=PADDLE_HEIGHT,
                 title='Breakout'):
        # window
        self._window = GWindow(
            (BRICK_WIDTH + BRICK_SPACING) * BRICK_COLS + BRICK_SPACING,
            BRICK_OFFSET + 3 *
            (BRICK_ROWS * (BRICK_HEIGHT + BRICK_SPACING) - BRICK_SPACING),
            title=title)

        self._page = 0  # control which page to show

        # start page
        self._start_label = GLabel('')
        self._start_label.font = 'Chalkduster-40'
        self._start_label.color = 'black'
        self._highscore = 0

        # game page
        self._ball = GOval(ball_radius, ball_radius)
        self._ball.filled = True

        self._paddle = GRect(paddle_width, paddle_height)
        self._paddle.filled = True

        self._score_board = GLabel('Score: 0')
        self._score_board.font = 'Chalkduster-20'
        self._score_board.color = 'black'

        self._num_lives = 3
        self._live_board = GLabel(f'Lives: {self._num_lives}')
        self._live_board.font = 'Chalkduster-20'
        self._live_board.color = 'black'

        self._wind_vane = GLabel(f'Wind direction: ◎ ')
        self._wind_vane.font = 'Chalkduster-15'
        self._wind_vane.color = 'black'

        self._wind_sign = GLabel(f'Next wind: 10s')
        self._wind_sign.font = 'Chalkduster-15'
        self._wind_sign.color = 'black'

        self._last_wind_time = 0
        self._next_wind_time = 0
        self._wind_time_pass = 0
        self._next_wind = 0
        self._wind_change = False

        self._vx = 0
        self._vy = 0
        self._remove_ct = 0
        self._ing = False

        self._protect_object = [
            self._paddle, self._score_board, self._live_board, self._wind_vane,
            self._wind_sign
        ]
        self._invisible_object = [
            self._score_board, self._live_board, self._wind_vane,
            self._wind_sign
        ]

        # mouse event
        onmouseclicked(self.click_event)
        onmousemoved(self.move_event)

    def draw_bricks(self):
        """
        Draw bricks on the window.
        """
        color_counter = -1
        for row in range(BRICK_ROWS):
            if row % 2 == 0:
                color_counter += 1
            for col in range(BRICK_COLS):
                brick = GRect(BRICK_WIDTH, BRICK_HEIGHT)
                brick.filled = True
                color = COLOR_LIST[color_counter % len(COLOR_LIST)]
                brick.fill_color = color
                brick.color = color
                self._window.add(
                    brick, BRICK_SPACING + (BRICK_WIDTH + BRICK_SPACING) * col,
                    BRICK_OFFSET + (BRICK_HEIGHT + BRICK_SPACING) * row)

    def game_reset(self):
        """
        You can use this method to reset the game.
        """
        self._window.clear()
        self._window.add(
            self._paddle, (self._window.width - self._paddle.width) // 2,
            self._window.height - PADDLE_OFFSET - self._paddle.height)
        self._window.add(
            self._ball,
            self._paddle.x + self._paddle.width // 2 - self._ball.width // 2,
            self._paddle.y - self._ball.height)
        self._score_board.text = f'Score: 0'
        self._window.add(self._live_board, LABEL_SPACING, self._window.height)
        self._window.add(
            self._score_board,
            self._live_board.x + self._live_board.width + LABEL_SPACING,
            self._window.height)
        self._window.add(self._wind_vane, self._window.width // 2,
                         self._window.height)
        self._window.add(
            self._wind_sign,
            self._wind_vane.x + self._wind_vane.width + LABEL_SPACING,
            self._window.height)
        self._vx = 0
        self._vy = 0
        self._remove_ct = 0
        self._ing = False
        self._wind_change = True
        self._wind_time_pass = 0

    def detect_collision(self):
        """
        Use four corners of the ball to check if collide with any other object.
        """
        if 0 > self._vy:
            top_left = self._window.get_object_at(self._ball.x, self._ball.y)
            top_right = self._window.get_object_at(
                self._ball.x + self._ball.width, self._ball.y)
            if top_left is not None and top_right is not None:
                if top_left is top_right:
                    if top_left not in self._protect_object:
                        self._window.remove(top_left)
                        self._remove_ct += 1
                else:
                    if top_left not in self._protect_object:
                        self._window.remove(top_left)
                        self._remove_ct += 1
                    if top_right not in self._protect_object:
                        self._window.remove(top_right)
                        self._remove_ct += 1
                if top_left not in self._invisible_object and top_right not in self._invisible_object:
                    self.set_ball_velocity(y=-1)
            elif top_left is None and top_right is not None:
                if self._vx > 0:
                    if top_right not in self._protect_object:
                        self._window.remove(top_right)
                        self._remove_ct += 1
                    if abs(self._ball.x + self._ball.width -
                           top_right.x) >= abs(self._ball.y - top_right.y +
                                               top_right.height):
                        if top_right not in self._invisible_object:
                            self.set_ball_velocity(y=-1)
                    else:
                        if top_right not in self._invisible_object:
                            self.set_ball_velocity(x=-1)
                else:
                    if top_right not in self._protect_object:
                        self._window.remove(top_right)
                        self._remove_ct += 1
                    if top_right not in self._invisible_object:
                        self.set_ball_velocity(y=-1)
            elif top_left is not None and top_right is None:
                if self._vx > 0:
                    if top_left not in self._protect_object:
                        self._window.remove(top_left)
                        self._remove_ct += 1
                    if top_left not in self._invisible_object:
                        self.set_ball_velocity(y=-1)
                else:
                    if top_left not in self._protect_object:
                        self._window.remove(top_left)
                        self._remove_ct += 1
                    if abs(self._ball.x - top_left.x +
                           top_left.width) >= abs(self._ball.y - top_left.y +
                                                  top_left.height):
                        if top_left not in self._invisible_object:
                            self.set_ball_velocity(y=-1)
                    else:
                        if top_left not in self._invisible_object:
                            self.set_ball_velocity(x=-1)
        else:
            bottom_left = self._window.get_object_at(
                self._ball.x, self._ball.y + self._ball.height)
            bottom_right = self._window.get_object_at(
                self._ball.x + self._ball.width,
                self._ball.y + self._ball.height)
            if bottom_left is not None and bottom_right is not None:
                if bottom_left is bottom_right:
                    if bottom_left not in self._protect_object:
                        self._window.remove(bottom_left)
                        self._remove_ct += 1
                else:
                    if bottom_left not in self._protect_object:
                        self._window.remove(bottom_left)
                        self._remove_ct += 1
                    if bottom_right not in self._protect_object:
                        self._window.remove(bottom_right)
                        self._remove_ct += 1
                if bottom_left not in self._invisible_object and bottom_right not in self._invisible_object:
                    self.set_ball_velocity(y=-1)
            elif bottom_left is not None and bottom_right is None:
                if self._vx < 0:
                    if bottom_left not in self._protect_object:
                        self._window.remove(bottom_left)
                        self._remove_ct += 1
                    if abs(bottom_left.x + bottom_left.width -
                           self._ball.x) >= abs(self._ball.y +
                                                self._ball.height -
                                                bottom_left.y):
                        if bottom_left not in self._invisible_object:
                            self.set_ball_velocity(y=-1)
                    else:
                        if bottom_left not in self._invisible_object:
                            self.set_ball_velocity(x=-1)
                else:
                    if bottom_left not in self._protect_object:
                        self._window.remove(bottom_left)
                        self._remove_ct += 1
                    if bottom_left not in self._invisible_object:
                        self.set_ball_velocity(y=-1)
            elif bottom_left is None and bottom_right is not None:
                if self._vx < 0:
                    if bottom_right not in self._protect_object:
                        self._window.remove(bottom_right)
                        self._remove_ct += 1
                    if bottom_right not in self._invisible_object:
                        self.set_ball_velocity(y=-1)
                else:
                    if bottom_right not in self._protect_object:
                        self._window.remove(bottom_right)
                        self._remove_ct += 1
                    if abs(self._ball.x + self._ball.width -
                           bottom_right.x) >= abs(self._ball.y +
                                                  self._ball.height -
                                                  bottom_right.y):
                        if bottom_right not in self._invisible_object:
                            self.set_ball_velocity(y=-1)
                    else:
                        if bottom_right not in self._invisible_object:
                            self.set_ball_velocity(x=-1)
        self.update_score()

    def update_score(self):
        """
        Update the score on the window
        """
        score = self._remove_ct * 10
        self._score_board.text = f'Score: {score}'
        if score > self._highscore:
            self._highscore = score

    def wind(self):
        """
        This method create random wind speed and direction to influent the movement of ball.
        """
        def random_wind():
            speed = random.randint(1, 10)
            dir_ = random.randrange(-1, 2, 2)
            return speed, dir_

        if self._ing:
            self._wind_time_pass += (time.time() - self._last_wind_time)
            self._wind_sign.text = f'Next wind: {10 - round(self._wind_time_pass)}s'
            self._last_wind_time = time.time()

            if self._wind_change:
                self._next_wind = random_wind()
                self._wind_change = False

            wind_speed = self._next_wind[0]
            wind_dir = self._next_wind[1]

            if wind_dir > 0:
                self._wind_vane.text = f'Next wind direction: →{wind_speed}'
                self._wind_sign.x = self._wind_vane.x + self._wind_vane.width + LABEL_SPACING
            elif wind_dir < 0:
                self._wind_vane.text = f'Next wind direction: ←{wind_speed}'
                self._wind_sign.x = self._wind_vane.x + self._wind_vane.width + LABEL_SPACING
            if self._wind_time_pass >= 10:
                self._vx = wind_speed * wind_dir
                self._wind_change = True
                self._wind_time_pass = 0
        else:
            self._wind_sign.text = f'Next wind: 10s'
            self._last_wind_time = time.time()

    def start_page(self):
        """
        This method draw the start page and restart page.
        """
        if self._page == 0:
            check = self._window.get_object_at(self._window.width // 2,
                                               self._window.height // 2)
            if check is None:
                progress_rate_board = GLabel(f'Loading...0%')
                progress_rate_board.font = 'Chalkduster-15'
                self._window.add(
                    progress_rate_board,
                    (self._window.width - progress_rate_board.width) // 2,
                    (self._window.height + PROGRESS_BAR_SIZE) // 2 +
                    progress_rate_board.height + LABEL_SPACING)
                pause_time = 300
                for i in range(10):
                    color = COLOR_LIST[i % len(COLOR_LIST)]
                    progress_bar = GRect(PROGRESS_BAR_SIZE * (i + 1),
                                         PROGRESS_BAR_SIZE)
                    progress_bar.filled = True
                    progress_bar.fill_color = color
                    progress_bar.color = color
                    self._window.add(
                        progress_bar,
                        self._window.width // 2 - PROGRESS_BAR_SIZE * 5,
                        self._window.height // 2 - PROGRESS_BAR_SIZE // 2)
                    progress_rate_board.text = f'Loading...{10*(i+1)}'
                    pause(pause_time)
                    pause_time += 100

            self._window.clear()
            self.draw_bricks()
            self._start_label.text = f'Click to start'
            self._window.add(
                self._start_label,
                (self._window.width - self._start_label.width) // 2,
                (self._window.height + self._start_label.height) // 2)
        elif self._page == 2:
            self._window.clear()
            # self.draw_bricks()
            self._start_label.text = f'Click to restart'
            self._window.add(
                self._start_label,
                (self._window.width - self._start_label.width) // 2,
                (self._window.height + self._start_label.height) // 2)
            highscore_board = GLabel(f'High score: {self._highscore}')
            highscore_board.font = 'Chalkduster-60'
            highscore_board.color = 'navy'
            self._window.add(
                highscore_board,
                (self._window.width - highscore_board.width) // 2,
                self._start_label.y - self._start_label.height -
                LABEL_SPACING * 3)

    def check_over(self):
        """
        Check if the game is over.
        """
        if self._remove_ct == BRICK_COLS * BRICK_ROWS or self._num_lives == 0:
            return True

    def click_event(self, m):
        """
        Control the behavior of mouse click.
        """
        if 0 < m.x < self._window.width and 0 < m.y < self._window.height:
            # start page
            if self._page == 0 or self._page == 2:
                if self._start_label.x <= m.x <= self._start_label.x + self._start_label.width and self._start_label.y - self._start_label.height <= m.y <= self._start_label.y:
                    self._page = 1
                    self._ing = False
                    self._num_lives = 3
                    self._live_board.text = f'Lives: {self._num_lives}'
            # game page
            elif self._page == 1:
                if not self._ing:
                    self._vx = random.randint(
                        1, MAX_X_SPEED) * random.randrange(-1, 2, 2)
                    self._vy = -INITIAL_Y_SPEED
                    self._ing = True

    def move_event(self, m):
        """
        Control the behavior of mouse move.
        """
        if self._page == 0 or self._page == 2:
            if self._start_label.x <= m.x <= self._start_label.x + self._start_label.width and self._start_label.y - self._start_label.height <= m.y <= self._start_label.y:
                self._start_label.color = 'magenta'
            else:
                self._start_label.color = 'black'
        elif self._page == 1:
            if self._paddle.width // 2 < m.x < self._window.width - self._paddle.width // 2:
                self._paddle.x = m.x - self._paddle.width // 2
            elif m.x <= self._paddle.width // 2:
                self._paddle.x = 0
            elif m.x >= self._window.width - self._paddle.width // 2:
                self._paddle.x = self._window.width - self._paddle.width
            if self._vx == 0 and self._vy == 0:
                self._ball.x = self._paddle.x + (self._paddle.width -
                                                 self._ball.width) // 2

    def get_page(self):
        return self._page

    def set_page(self, page_num):
        self._page = page_num

    def get_ball(self):
        return self._ball

    def get_ball_velocity(self):
        return self._vx, self._vy

    def set_ball_velocity(self, x=1, y=1):
        self._vx *= x
        self._vy *= y

    def get_window(self):
        return self._window

    def get_lives(self):
        return self._num_lives

    def set_lives(self):
        self._num_lives -= 1
        self._live_board.text = f'Lives: {self._num_lives}'

    def get_game_start(self):
        return self._ing
class BreakoutGraphics:
    def __init__(self,
                 ball_radius=BALL_RADIUS,
                 paddle_width=PADDLE_WIDTH,
                 paddle_height=PADDLE_HEIGHT,
                 paddle_offset=PADDLE_OFFSET,
                 brick_rows=BRICK_ROWS,
                 brick_cols=BRICK_COLS,
                 brick_width=BRICK_WIDTH,
                 brick_height=BRICK_HEIGHT,
                 brick_offset=BRICK_OFFSET,
                 brick_spacing=BRICK_SPACING,
                 title='Breakout'):

        # Create a graphical window, with some extra space.
        self.window_width = brick_cols * (brick_width +
                                          brick_spacing) - brick_spacing
        self.window_height = brick_offset + 3 * (
            brick_rows * (brick_height + brick_spacing) - brick_spacing)
        self.window = GWindow(width=self.window_width,
                              height=self.window_height,
                              title=title)

        # set up the paddle.
        self.paddle_width = paddle_width
        self.paddle_height = paddle_height
        self.paddle = GRect(paddle_width,
                            paddle_height,
                            x=(self.window_width - PADDLE_WIDTH) / 2,
                            y=self.window_height - paddle_offset -
                            paddle_height)
        self.paddle.filled = True
        self.paddle.color = 'slategrey'
        self.paddle.fill_color = 'slategrey'

        # set up a the ball.
        self.ball_radius = ball_radius
        self.ball = GOval(self.ball_radius * 2,
                          self.ball_radius * 2,
                          x=(self.window_width - self.ball_radius * 2) / 2,
                          y=(self.window_height - self.ball_radius * 2) / 2)
        self.ball.filled = True
        self.ball.color = 'crimson'
        self.ball.fill_color = 'crimson'

        # Default initial velocity for the ball.
        self.__dx = random.randint(MIN_X_SPEED, MAX_X_SPEED)
        if random.random() > 0.5:
            self.__dx = -self.__dx
        self.__dy = INITIAL_Y_SPEED

        # Initialize our mouse listeners.
        onmousemoved(self.move_paddle)
        onmouseclicked(self.switch)

        # Control whether the user can start the game
        self.__start = False

        # Determine the lives the user gets
        self.__num_lives = 0

        # Attributes relate to the score
        self.__score = 0
        self.__win_score = BRICK_COLS * BRICK_ROWS * 10
        self.score_sign = GLabel(f'SCORE : {self.__score}')
        self.can_move = False
        self.can_drop = True
        self.reward = 0

        # Attribute relate to live sign
        self.live_2 = GOval(self.ball_radius * 2,
                            self.ball_radius * 2,
                            x=self.window_width - (self.ball_radius * 2 + 8),
                            y=self.window_height - (self.ball_radius * 2 + 8))
        self.live_1 = GOval(self.ball_radius * 2,
                            self.ball_radius * 2,
                            x=self.window_width -
                            (self.ball_radius * 2 + 8) * 2,
                            y=self.window_height - (self.ball_radius * 2 + 8))

        # Attributes relate to extension opening
        self.o_ball = GOval(30, 30, x=(self.window.width - 30) / 2, y=240)
        self.start_button = GLabel('S T A R T')
        self.__enter_game = False

        # Attributes relate to extension 'game over' scene
        self.game_over_w = GLabel('G A M E   O V E R')

        # Attributes relate to rewards
        self.hint_sign = 0
        self.reverse_paddle = False
        self.paddle_adding = False
        self.adding_count = 0

        # To store the mouse event
        self.paddle_x = 0

    def set_game(self):
        """
        This method set up the starting interface for the game.
        Including materials such as: paddle, window, score sign and bricks.
        """
        # Create a paddle.
        self.window.add(self.paddle)

        # Center a filled ball in the graphical window.
        self.window.add(self.ball)

        # Build the score sign
        self.score_sign = GLabel(f'SCORE : {self.__score}')
        self.score_sign.color = 'crimson'
        self.score_sign.font = 'Mamelon-20'
        self.window.add(self.score_sign, x=8, y=self.window.height - 8)

        # Build lives sign
        self.live_2.filled = True
        self.live_2.color = 'crimson'
        self.live_2.fill_color = 'crimson'
        self.window.add(self.live_2)

        self.live_1.filled = True
        self.live_1.color = 'crimson'
        self.live_1.fill_color = 'crimson'
        self.window.add(self.live_1)

        # Draw bricks.
        for i in range(BRICK_ROWS):
            for j in range(BRICK_COLS):
                brick = GRect(BRICK_WIDTH,
                              BRICK_HEIGHT,
                              x=i * (BRICK_WIDTH + BRICK_SPACING),
                              y=BRICK_OFFSET + j *
                              (BRICK_HEIGHT + BRICK_SPACING))
                brick.filled = True
                if j == 0 or j == 1:
                    brick.color = 'darkslategrey'
                    brick.fill_color = 'darkslategrey'
                elif j == 2 or j == 3:
                    brick.color = 'teal'
                    brick.fill_color = 'teal'
                elif j == 4 or j == 5:
                    brick.color = 'cadetblue'
                    brick.fill_color = 'cadetblue'
                elif j == 6 or j == 7:
                    brick.color = 'lightseagreen'
                    brick.fill_color = 'lightseagreen'
                else:
                    brick.color = 'mediumturquoise'
                    brick.fill_color = 'mediumturquoise'
                self.window.add(brick)

    def move_paddle(self, event):
        """
        This method keep mouse event regarding reverse and not reverse situation of the paddle.
        Also, it keeps the paddle within the window .
        :param event: mouse event
        """
        if self.reverse_paddle:
            # reverse paddle movement
            event_x = self.window_width - event.x
        else:
            # Normal paddle movement
            event_x = event.x

        # Keeping the paddle within the window
        if event_x - self.paddle_width / 2 < 0:
            self.paddle.x = 0
        elif event_x + self.paddle_width / 2 > self.window.width:
            self.paddle.x = self.window.width - self.paddle_width

        # Control the paddle when the mouse is in the window
        else:
            self.paddle.x = event_x - self.paddle_width / 2

        # To store the mouse event
        self.paddle_x = self.paddle.x

    def reset_ball(self):
        """
        This method resets the ball at the starting position,
        and will not drop unless the the user clicks.
        """
        self.ball = GOval(self.ball_radius * 2,
                          self.ball_radius * 2,
                          x=(self.window_width - self.ball_radius * 2) / 2,
                          y=(self.window_height - self.ball_radius * 2) / 2)
        self.ball.filled = True
        self.ball.color = 'crimson'
        self.ball.fill_color = 'crimson'
        self.window.add(self.ball)
        self.__start = False

    # getter for the ball's moving speed.
    def get_dx(self):
        return self.__dx

    # getter for the ball's moving speed.
    def get_dy(self):
        return self.__dy

    # getter, see if the ball is ready to be click and fall
    def start(self):
        return self.__start

    # getter, see if the start button in the opening scene is clicked
    def get_enter_game(self):
        return self.__enter_game

    # setter, the user can set up the initial lives.
    def set_num_lives(self, num_lives):
        self.__num_lives = num_lives

    def switch(self, e):
        """
        This method is the switch for 2 purpose.

        1. whether the user can start the game.
        In the start of each round, if user's lives > 0, the switch will set to open,
        the ball will move after the user click the window.

        2. whether the start button in the opening scene is clicked.
        If clicked, set up the game.
        If not, wait for the click.
        """
        if self.__num_lives > 0:
            self.__start = True

        if self.window.get_object_at(e.x, e.y) is self.o_ball or \
                self.window.get_object_at(e.x, e.y) is self.start_button:
            self.__enter_game = True

    # To get the position of the 4 corners of the ball
    def corner_hits(self):
        """
        This method check from corner_1 to corner_4,
        to see if any corner of the ball has encounter obstacles.

        If so, return the method to remove object encountered.
        If not, move on to the next corner.

        :return: method, remove the object on the encountered corner.
        """
        # To get position of each corner of the ball
        corner_1 = self.window.get_object_at(self.ball.x, self.ball.y)
        corner_2 = self.window.get_object_at(
            self.ball.x + 2 * self.ball_radius, self.ball.y)
        corner_3 = self.window.get_object_at(
            self.ball.x, self.ball.y + 2 * self.ball_radius)
        corner_4 = self.window.get_object_at(
            self.ball.x + 2 * self.ball_radius,
            self.ball.y + 2 * self.ball_radius)

        # To check which corner hits things
        if corner_1 is not None:
            return corner_1
        elif corner_2 is not None:
            return corner_2
        elif corner_3 is not None:
            return corner_3
        elif corner_4 is not None:
            return corner_4
        else:
            pass

    # To check whether the ball hits the wall
    def if_hits_walls(self):
        """
        This method checks whether the ball hits the side walls and the the top wall.
        If so, the ball will bounce back.
        """
        # The ball hits the side walls and the the top wall
        if self.ball.x <= 0 or self.ball.x + 2 * self.ball_radius >= self.window.width:
            self.__dx *= -1
        # The ball hits the the top wall
        if self.ball.y <= 0:
            self.__dy *= -1

    # To check what the ball hits
    def if_hits_things(self):
        """
        This method determines what kind of object the ball hits,
        and run the actions after hitting certain objects.
        """
        # The ball hits the paddle
        if self.corner_hits() is self.paddle:
            # bounce back
            if self.__dy >= 0:
                self.__dy *= -1
            else:
                # debug: keep the ball from sticking on the paddle
                pass
            # Clean up the hint sign
            self.window.remove(self.hint_sign)

        # the ball hits other item, pass
        elif self.corner_hits() is self.score_sign or self.corner_hits() is self.hint_sign or self.corner_hits() is self.reward \
                or self.corner_hits() is self.live_2 or self.corner_hits() is self.live_1:
            pass

        # The ball hits the paddle
        elif self.corner_hits() is not None:

            # The thing hit by the ball need to be removed
            self.window.remove(self.corner_hits())

            # Speed up and bounce
            self.__dy *= -1.005
            self.__score += 10

            # Show score
            self.show_score()

            # The reward will dropped everytime the user get 70 more score.
            if self.__score % 70 == 0 and self.can_drop:

                # Choose the dropping x position randomly
                t_x = random.choice(range(1, 10))

                # Set up the reward object
                self.reward = GRect(10,
                                    10,
                                    x=self.window.width * t_x // 10,
                                    y=self.window.height / 3)
                self.reward.filled = True
                self.reward.fill_color = 'darkslateblue'
                self.reward.color = 'darkslateblue'
                self.window.add(self.reward)
                self.can_move = True
        else:
            pass

    def show_score(self):
        """
        This method remove the previous score sign and create a new one.
        """
        self.window.remove(self.score_sign)
        self.score_sign = GLabel(f'SCORE : {self.__score}')
        self.score_sign.color = 'crimson'
        self.score_sign.font = 'Mamelon-20'
        self.window.add(self.score_sign, x=8, y=self.window.height - 8)

    # To check: if the reward is allowed to drop / if it was obtained or missed.
    def test_move(self):
        if self.can_move:
            self.can_drop = False
            self.reward.move(0, 5)

            # The user got the reward
            if self.window.get_object_at(self.reward.x + 7,
                                         self.reward.y + 15) is self.paddle:
                self.window.remove(self.reward)
                self.run_reward()
                self.can_move = False
                self.can_drop = True

            # The user miss the reward -- clean up
            if self.reward.y + 15 >= self.window.height:
                self.window.remove(self.reward)
                self.can_move = False
                self.can_drop = True

    def run_reward(self):
        """
        This method uses random to choose a reward to run randomly.
        Also, it shows a sign for the running reward.

        Possible situation are listed below:
        1. Make the paddle longer
        2. Make the paddle shorter
        3. Reverse the paddle's movement
        4. Speed up the ball
        5. Slow down the ball
        """
        # Choose a reward randomly
        choice = random.choice(range(6))
        self.window.remove(self.hint_sign)

        # paddle width ++
        if choice == 0:
            self.window.remove(self.paddle)
            self.paddle_width += 20
            self.paddle = GRect(self.paddle_width,
                                self.paddle_height,
                                x=self.paddle_x - self.paddle_width / 2,
                                y=self.window_height - PADDLE_OFFSET -
                                self.paddle_height)
            self.build_paddle()
            self.hint_sign = GLabel('ADD UP')
            self.show_hint()

        # paddle width --
        elif choice == 1:
            self.paddle_adding = False
            if self.paddle_width <= 30:
                pass
            else:
                self.window.remove(self.paddle)
                self.paddle_width -= 30
                self.paddle = GRect(self.paddle_width,
                                    self.paddle_height,
                                    x=self.paddle_x - self.paddle_width / 2,
                                    y=self.window_height - PADDLE_OFFSET -
                                    self.paddle_height)
                self.build_paddle()
                self.hint_sign = GLabel('WATCH OUT')
                self.show_hint()

        # Set reverse paddle movement
        elif choice == 2 or choice == 3:
            if self.reverse_paddle:
                self.reverse_paddle = False
                self.hint_sign = GLabel('REVERSE AGAIN')
                self.show_hint()
            else:
                self.reverse_paddle = True
                self.hint_sign = GLabel('REVERSE')
                self.show_hint()

        # Speed up the ball
        elif choice == 4:
            self.__dy *= 1.1
            self.hint_sign = GLabel('SPEED UP')
            self.show_hint()

        # Slow down the ball
        else:
            self.__dy *= 0.9
            self.hint_sign = GLabel('SLOW DOWN')
            self.show_hint()

    def build_paddle(self):
        """
        This methods build up paddle for each reward.
        """
        self.window.add(self.paddle)
        self.paddle.filled = True
        self.paddle.color = 'slategrey'
        self.paddle.fill_color = 'slategrey'

    def show_hint(self):
        """
        This method shows a sign to tell user that clicking can earn rewards
        """
        self.hint_sign.color = 'crimson'
        self.hint_sign.font = 'Mamelon-20'
        self.window.add(self.hint_sign,
                        x=(self.window.width - self.hint_sign.width) / 2,
                        y=self.window_height - PADDLE_OFFSET - PADDLE_HEIGHT -
                        30)

    def if_lose_life(self):
        """
        This method check if the user lost life.
        If so, will reset the ball if there's still lives remain.
        """
        if self.ball.y >= self.window.height:
            self.window.remove(self.ball)
            self.window.remove(self.hint_sign)
            self.__num_lives -= 1

            if self.__num_lives == 2:
                # Build 1 point
                self.window.remove(self.live_1)

            elif self.__num_lives == 1:
                # empty
                self.window.remove(self.live_2)

            # Avoid being too hard for players to get ball with reverse paddle
            self.reverse_paddle = False
            self.__dy = INITIAL_Y_SPEED

            if self.__num_lives > 0:
                # User still has chances
                self.reset_ball()

    # Getter, to check if it's the end of the game (either win or lose)
    def end_game(self):
        if self.__num_lives <= 0 or self.__score >= self.__win_score:
            return True

    # To check if the game is over
    def game_over(self):
        if self.__num_lives <= 0:
            self.__enter_game = False
            return True

    def build_game_over(self):
        """
        This method builds the 'Game Over' scene
        """

        # delete the score
        line = GLine(4, self.window.height - 18, 12 + self.score_sign.width,
                     self.window.height - 18)
        line.color = 'crimson'
        self.window.add(line)

        # Clean up
        self.window.remove(self.reward)
        self.window.remove(self.hint_sign)

        # Build up the 'Game Over' sign
        self.game_over_w.font = "Mamelon-35"
        self.game_over_w.color = 'midnightblue'
        self.window.add(self.game_over_w,
                        x=(self.window.width - self.game_over_w.width) / 2,
                        y=self.window.height / 2)

        # Create the movement of the 'Game Over' sign
        speed = self.__dy
        while True:
            self.game_over_w.move(0, speed)
            speed += 0.5
            if self.game_over_w.y >= self.window.height * 2 // 3:
                speed *= -0.7
            pause(10)
        # The sign will end with a shaking animation.

    # To check if the user won the game
    def win_game(self):
        if self.__score >= self.__win_score:
            return True

    def build_win(self):
        """
        This method builds the 'Winning' scene
        """
        win_sign_1 = GLabel('S A V A G E')
        win_sign_1.font = 'Mamelon-40'
        win_sign_1.color = 'mediumturquoise'
        win_sign_2 = GLabel('S A V A G E')
        win_sign_2.font = 'Mamelon-40'
        win_sign_2.color = 'mediumturquoise'
        win_sign_3 = GLabel('S A V A G E')
        win_sign_3.font = 'Mamelon-40'
        win_sign_3.color = 'mediumturquoise'

        # To create the flashing animation
        for i in range(12):
            if i % 2 != 0:
                self.window.add(win_sign_1,
                                x=(self.window.width - win_sign_1.width) / 2,
                                y=(self.window.height - win_sign_1.height) / 2)
                self.window.add(
                    win_sign_2,
                    x=(self.window.width - win_sign_2.width) / 2,
                    y=(self.window.height - win_sign_2.height) / 2 + 50)
                self.window.add(
                    win_sign_3,
                    x=(self.window.width - win_sign_3.width) / 2,
                    y=(self.window.height - win_sign_3.height) / 2 + 100)
            else:
                self.window.remove(win_sign_1)
                self.window.remove(win_sign_2)
                self.window.remove(win_sign_3)

            pause(100)

        self.fire_work()

    def fire_work(self):
        """
        This method creates a firework animation.
        """

        # Numbers of the firework
        for i in range(10):
            f_x = random.randint(self.window.width // 8,
                                 self.window.width * 7 // 8)
            f_y = random.randint(self.window.height // 10,
                                 self.window.height * 9 // 10)
            size = random.randint(4, 7)

            # The size of the firework
            for j in range(size):
                fire = GOval(10 + 20 * j,
                             10 + 20 * j,
                             x=f_x - 10 * j,
                             y=f_y - 10 * j)

                # Choose color randomly
                fire.color = self.choose_color()
                self.window.add(fire)
                pause(100)

                self.window.remove(fire)

            pause(500)

    @staticmethod
    def choose_color():
        """
        This method help choose the color for each circle of the firework randomly
        """
        num = random.choice(range(6))
        if num == 0:
            return "crimson"
        elif num == 1:
            return "midnightblue"
        elif num == 2:
            return "limegreen"
        elif num == 3:
            return "cyan"
        elif num == 4:
            return 'darkviolet'
        else:
            return "gold"

    def window_clear(self):
        """
        This method clean up the whole window after the opening scene is over.
        """
        self.window.clear()

    def set_opening(self):
        """
        This method set up the whole opening scene.
        """

        # To create the tube
        start_button_1 = GRect(60, 211, x=(self.window.width - 60) / 2, y=-1)
        start_button_1.color = 'slategrey'
        start_button_1.filled = True
        start_button_1.fill_color = 'slategrey'
        start_button_2 = GRect(50, 206, x=(self.window.width - 50) / 2)
        start_button_2.color = 'gainsboro'
        start_button_2.filled = True
        start_button_2.fill_color = 'gainsboro'
        self.window.add(start_button_1)
        self.window.add(start_button_2)
        head = GPolygon()
        head.add_vertex(((self.window.width - 60) / 2, 210))
        head.add_vertex(((self.window.width - 60) / 2 + 60, 210))
        head.add_vertex(((self.window.width - 60) / 2 + 60 + 20, 240))
        head.add_vertex(((self.window.width - 60) / 2 - 20, 240))
        head.color = 'slategrey'
        head.filled = True
        head.fill_color = 'slategrey'
        self.window.add(head)

        # Loading animation
        for i in range(10):
            load = GRect(40,
                         15,
                         x=(self.window.width - 60) / 2 + 10,
                         y=5 + 20 * i)
            load.filled = 'True'
            load.color = 'slategrey'
            load.fill_color = 'slategrey'
            self.window.add(load)
            pause(100)

        # Bouncing ball
        self.o_ball.filled = 'True'
        self.o_ball.color = 'crimson'
        self.o_ball.fill_color = 'crimson'
        self.window.add(self.o_ball)
        ball_vy = 5
        count = 0
        while True:
            self.o_ball.move(0, ball_vy)
            ball_vy += 1
            if self.o_ball.y + 30 >= self.window.height:
                ball_vy *= -0.9
                count += 1
            if count == 5 and ball_vy >= 0:
                break
            pause(10)
        self.window.remove(self.o_ball)

        # Blowing balloon animation
        for i in range(55):
            self.o_ball = GOval(30 + i,
                                30 + i,
                                x=(self.window.width - 30 + i) / 2 - i,
                                y=415 - i)
            self.o_ball.filled = 'True'
            self.o_ball.color = 'crimson'
            self.o_ball.fill_color = 'crimson'
            self.window.add(self.o_ball)
            pause(7)

        # Flashing start sign animation
        for i in range(10):
            self.start_button.font = 'Mamelon-20'
            if i % 2 == 0:
                self.start_button.color = 'crimson'
            else:
                self.start_button.color = 'snow'
            self.window.add(self.o_ball)
            self.window.add(self.start_button,
                            x=(self.window.width - 64) / 2,
                            y=413)
            pause(100)

        # Show the rules
        rule_1 = GLabel('3 LIVES.')
        rule_1.color = 'darkslategrey'
        rule_1.font = 'Mamelon-20'
        rule_2 = GLabel("CATCH '    ' FOR RANDOM EFFECTS.")
        rule_2.color = 'darkslategrey'
        rule_2.font = 'Mamelon-20'
        rule_3 = GLabel('SCORE 1000 ---> FIREWORKS.')
        rule_3.color = 'crimson'
        rule_3.font = 'Mamelon-20'
        square = GRect(10,
                       10,
                       x=self.window.width / 10 + 63,
                       y=self.window.height * 4 // 5 + 10)
        square.filled = True
        square.fill_color = 'darkslateblue'
        square.color = 'darkslateblue'
        self.window.add(square)
        self.window.add(rule_1,
                        x=self.window.width / 10,
                        y=self.window.height * 4 // 5)
        self.window.add(rule_2,
                        x=self.window.width / 10,
                        y=self.window.height * 4 // 5 + 25)
        self.window.add(rule_3,
                        x=self.window.width / 10,
                        y=self.window.height * 4 // 5 + 50)