# Created on 19 Apr 2020
# Created by: <NAME>
# This file defines class and methods used to control the mines GUI

"""
Defines the Gui class that uses pygame to visualize a game of mines

Public objects:
    * Class gui.Gui
"""

import pygame
import tile
import game
import solver


class Gui:
    """
This class controls the GUI to the mines game

# Start the gui
self.start(self):
    """

    CANVAS_SIZE = 900
    COMMANDS_BAR_SIZE = 200
    PADDING = 10
    SOLVER_DELAY = 100

    # colours
    BLACK = (0, 0, 0)
    WHITE = (255, 255, 255)
    LIGHT_GRAY = (240, 240, 240)
    MID_LIGHT_GRAY = (220, 220, 220)
    MID_GRAY = (190, 190, 190)
    GRAY_BLUE = (216, 227, 232)
    NAVY_BLUE = (21, 118, 171)
    RED = (194, 59, 56)
    GREEN = (60, 199, 85)

    # characters for minefield
    BLANK = ' ' # '\u25A1'
    COVERED = '\u25A8'
    FLAG = '\u2690'
    UNKNOWN = '?'
    MINE = '\u2737'

    def __init__(self):
        # size of the grid
        self.size = 16

        # number of mines on the grid
        self.mines = 40

        # game clock
        self.clock = pygame.time.Clock()

        # screen
        self.screen = pygame.display.set_mode((Gui.CANVAS_SIZE + Gui.COMMANDS_BAR_SIZE + 2 * Gui.PADDING,
                                               Gui.CANVAS_SIZE + 2 * Gui.PADDING))

        # Initialize the mines game
        self.game = game.Game()
        self.game.set_size(self.size)
        self.game.set_mines(self.mines)

        # Initialize the solver module
        self.solver = solver.Solver(self.game)

        # true when the Gui is using the solver module
        self.solving = False

        # tile that was last edited by the solver
        self.lastSolvedTile: (int, int) = (-1, -1)

        # the last mine tile where the left mouse was down
        self.last_left_down = (-1, -1)

        # the last mine tile where the right mouse was down
        self.last_right_down = (-1, -1)

        # tile that was pressed when the game was lost
        self.failed_tile = (-1, -1)

        # Initialize pygame
        pygame.init()

        pygame.display.set_caption("'Mines' made by mattlourenco27 on Github")
        # Icon made by Creaticca Creative Agency from www.flaticon.com
        icon = pygame.image.load("./assets/sprites/icon.png")
        pygame.display.set_icon(icon)

    # Start the gui
    def start(self):
        # start the game
        self.game.begin()

        # start the game loop
        self._game_loop()

    # restart the game and change the size and mines
    def restart(self, size, mines):
        self.solving = False
        self.lastSolvedTile = (-1, -1)
        pygame.time.set_timer(pygame.USEREVENT, 0) # turn off the timer
        self.game.reset()
        self.game.set_size(size)
        self.size = size
        self.game.set_mines(mines)
        self.mines = mines
        self.failed_tile = (-1, -1)
        self.solver = solver.Solver(self.game)
        self.game.begin()

    # draw the canvas where the game of mines will show
    def _draw_base_canvas(self):
        background = pygame.Rect(Gui.PADDING, Gui.PADDING, Gui.CANVAS_SIZE, Gui.CANVAS_SIZE)
        pygame.draw.rect(self.screen, Gui.WHITE, background)

        colour = Gui.BLACK

        # Draw the row dividers
        channel_width: float = Gui.CANVAS_SIZE / self.size
        line_width: int = 1
        for row_divider in range(0, self.size + 1):
            pygame.draw.line(self.screen, colour,
                             (Gui.PADDING, Gui.PADDING + int(row_divider * channel_width)),
                             (Gui.PADDING + Gui.CANVAS_SIZE, Gui.PADDING + int(row_divider * channel_width)),
                             line_width)

        # Draw the column dividers
        for col_divider in range(0, self.size + 1):
            pygame.draw.line(self.screen, colour,
                             (Gui.PADDING + int(col_divider * channel_width), Gui.PADDING),
                             (Gui.PADDING + int(col_divider * channel_width), Gui.PADDING + Gui.CANVAS_SIZE),
                             line_width)

    # draw the minefield that the game is played on
    def _draw_minefield(self, mouse_pos):
        channel_width = Gui.CANVAS_SIZE / self.size
        text = pygame.font.Font("assets/fonts/FreeSerif.ttf", int(channel_width * 0.6))

        mouse_x = (mouse_pos[0] - Gui.PADDING)
        mouse_y = (mouse_pos[1] - Gui.PADDING)

        if mouse_x < 0:
            mouse_x = -1
        else:
            mouse_x = int(mouse_x / channel_width)

        if mouse_y < 0:
            mouse_y = -1
        else:
            mouse_y = int(mouse_y / channel_width)

        for x in range(self.size):
            for y in range(self.size):
                tile_state = self.game.get_tile_state(x, y)

                # determine the colour fo the tile
                if x == mouse_x and y == mouse_y and tile_state is not tile.State.visible and not self.game.game_done():
                    colour = Gui.MID_GRAY
                elif (x, y) == self.failed_tile:
                    colour = Gui.RED
                else:
                    colour = Gui.BLACK

                # determine the character to print based on the tile state and value
                if tile_state is tile.State.covered:
                    character = Gui.COVERED
                elif tile_state is tile.State.visible:
                    value = self.game.get_tile_value(x, y)

                    if value == 0:
                        character = Gui.BLANK
                    elif value == -1:
                        character = Gui.MINE
                    else:
                        character = str(value)

                        # change colour based on the value
                        colour = pygame.Color(0, 0, 0)
                        colour.hsla = (((value - 1) * 200) % 360, 70, 40, 100)
                elif tile_state is tile.State.flag:
                    character = Gui.FLAG
                elif tile_state is tile.State.unknown:
                    character = Gui.UNKNOWN

                text_surface = text.render(character, True, colour)
                text_rect = text_surface.get_rect()
                text_rect.center = (Gui.PADDING + int((x + 0.5) * channel_width),
                                    Gui.PADDING + int((y + 0.5) * channel_width))
                self.screen.blit(text_surface, text_rect)

        # draw last edited tile by the solver
        if self.solving and self.lastSolvedTile != (-1, -1):
            x, y = self.lastSolvedTile
            colour = pygame.Color(0, 255, 0, 150)
            channel_width = Gui.CANVAS_SIZE / self.size
            rect = pygame.Rect(Gui.PADDING + x * channel_width, Gui.PADDING + y * channel_width,
                               channel_width, channel_width)

            # Transparent surface
            overlay = pygame.Surface((channel_width, channel_width), pygame.SRCALPHA)
            overlay.fill(colour)

            self.screen.blit(overlay, rect)

    # draw the command bar with buttons to click
    def _draw_command_bar(self, mouse_pos):
        x_off = Gui.CANVAS_SIZE + 2 * Gui.PADDING  # X offset to the command bar
        y_off = Gui.PADDING # Y offset to the command bar

        # indicate the number of flags placed
        text = pygame.font.Font("assets/fonts/FreeSerif.ttf", 40)
        message = str(self.game.get_flags()) + " / " + str(self.mines) + " " + Gui.FLAG
        rect = pygame.Rect(x_off + 15, y_off + 10, 170, 50)
        text_surface = text.render(message, True, Gui.BLACK)
        text_rect = text_surface.get_rect()
        text_rect.center = rect.center
        self.screen.blit(text_surface, text_rect)

        # Set up text for buttons
        text = pygame.font.Font("assets/fonts/FreeSans.ttf", 20)

        # Reset button
        rect = rect.move(0, 75)
        if rect.collidepoint(mouse_pos):
            pygame.draw.rect(self.screen, Gui.MID_LIGHT_GRAY, rect)
        else:
            pygame.draw.rect(self.screen, Gui.WHITE, rect)
        pygame.draw.rect(self.screen, Gui.BLACK, rect, 5)
        text_surface = text.render("Reset", True, Gui.BLACK)
        text_rect = text_surface.get_rect()
        text_rect.center = rect.center
        self.screen.blit(text_surface, text_rect)

        # 8x8 10 mines button
        rect = rect.move(0, 75)
        if rect.collidepoint(mouse_pos):
            pygame.draw.rect(self.screen, Gui.MID_LIGHT_GRAY, rect)
        else:
            pygame.draw.rect(self.screen, Gui.WHITE, rect)
        pygame.draw.rect(self.screen, Gui.BLACK, rect, 5)
        text_surface = text.render("8x8 10 mines", True, Gui.BLACK)
        text_rect = text_surface.get_rect()
        text_rect.center = rect.center
        self.screen.blit(text_surface, text_rect)

        # 16x16 40 mines button
        rect = rect.move(0, 75)
        if rect.collidepoint(mouse_pos):
            pygame.draw.rect(self.screen, Gui.MID_LIGHT_GRAY, rect)
        else:
            pygame.draw.rect(self.screen, Gui.WHITE, rect)
        pygame.draw.rect(self.screen, Gui.BLACK, rect, 5)
        text_surface = text.render("16x16 40 mines", True, Gui.BLACK)
        text_rect = text_surface.get_rect()
        text_rect.center = rect.center
        self.screen.blit(text_surface, text_rect)

        # 25x25 99 mines button
        rect = rect.move(0, 75)
        if rect.collidepoint(mouse_pos):
            pygame.draw.rect(self.screen, Gui.MID_LIGHT_GRAY, rect)
        else:
            pygame.draw.rect(self.screen, Gui.WHITE, rect)
        pygame.draw.rect(self.screen, Gui.BLACK, rect, 5)
        text_surface = text.render("25x25 99 mines", True, Gui.BLACK)
        text_rect = text_surface.get_rect()
        text_rect.center = rect.center
        self.screen.blit(text_surface, text_rect)

        # solve button
        rect = rect.move(0, 75)
        if rect.collidepoint(mouse_pos):
            pygame.draw.rect(self.screen, Gui.MID_LIGHT_GRAY, rect)
        else:
            pygame.draw.rect(self.screen, Gui.WHITE, rect)
        pygame.draw.rect(self.screen, Gui.BLACK, rect, 5)
        text_surface = text.render("Auto-Solve", True, Gui.BLACK)
        text_rect = text_surface.get_rect()
        text_rect.center = rect.center
        self.screen.blit(text_surface, text_rect)

        # pause solver button
        rect = rect.move(0, 75)
        if rect.collidepoint(mouse_pos):
            pygame.draw.rect(self.screen, Gui.MID_LIGHT_GRAY, rect)
        else:
            pygame.draw.rect(self.screen, Gui.WHITE, rect)
        pygame.draw.rect(self.screen, Gui.BLACK, rect, 5)
        text_surface = text.render("Pause Solver", True, Gui.BLACK)
        text_rect = text_surface.get_rect()
        text_rect.center = rect.center
        self.screen.blit(text_surface, text_rect)

    # draws victory text
    def _draw_win(self):
        message = "Victory"
        colour = Gui.GREEN

        text = pygame.font.Font("assets/fonts/FreeSerifBoldItalic.ttf", 127)
        text_surface = text.render(message, True, colour)
        text_rect = text_surface.get_rect()
        text_rect.center = (int((Gui.PADDING + Gui.CANVAS_SIZE) * 0.5), int((Gui.PADDING + Gui.CANVAS_SIZE) * 0.5))
        self.screen.blit(text_surface, text_rect)

    # draws the mines game and the command bar
    def _draw_screen(self, mouse_pos):
        self.screen.fill(Gui.LIGHT_GRAY)

        self._draw_base_canvas()
        self._draw_minefield(mouse_pos)
        self._draw_command_bar(mouse_pos)

        if self.game.victory():
            self._draw_win()

        pygame.display.update()

    # handles the pygame events 60 times per second
    def _event_handler(self, event):
        if event.type == pygame.MOUSEBUTTONDOWN:
            # mouse position at the current event
            mouse_pos = pygame.mouse.get_pos()

            # check if mouse intersects the minefield
            rect = pygame.Rect(Gui.PADDING, Gui.PADDING, Gui.CANVAS_SIZE, Gui.CANVAS_SIZE)

            if rect.collidepoint(mouse_pos) and not self.solving:
                channel_width = Gui.CANVAS_SIZE / self.size
                mouse_x = int((mouse_pos[0] - Gui.PADDING) / channel_width)
                mouse_y = int((mouse_pos[1] - Gui.PADDING) / channel_width)

                if pygame.mouse.get_pressed()[0]:
                    self.last_left_down = (mouse_x, mouse_y)
                elif pygame.mouse.get_pressed()[2]:
                    self.last_right_down = (mouse_x, mouse_y)

        elif event.type == pygame.MOUSEBUTTONUP:
            # mouse position at the current event
            mouse_pos = pygame.mouse.get_pos()

            # check if mouse intersects the minefield
            rect = pygame.Rect(Gui.PADDING, Gui.PADDING, Gui.CANVAS_SIZE, Gui.CANVAS_SIZE)

            if rect.collidepoint(mouse_pos) and not self.solving:
                channel_width = Gui.CANVAS_SIZE / self.size
                mouse_x = int((mouse_pos[0] - Gui.PADDING) / channel_width)
                mouse_y = int((mouse_pos[1] - Gui.PADDING) / channel_width)

                if not pygame.mouse.get_pressed()[0] and self.last_left_down != (-1, -1):
                    if (mouse_x, mouse_y) == self.last_left_down:
                        self.game.left_mouse_button(mouse_x, mouse_y)

                    self.last_left_down = (-1, -1)

                    if self.game.game_done() and not self.game.victory() and self.failed_tile == (-1, -1):
                        self.failed_tile = (mouse_x, mouse_y)
                elif not pygame.mouse.get_pressed()[2] and self.last_right_down != (-1, -1):
                    if (mouse_x, mouse_y) == self.last_right_down:
                        self.game.right_mouse_button(mouse_x, mouse_y)

                    self.last_right_down = (-1, -1)
            # check if the mouse is over the command bar
            rect = pygame.Rect(Gui.PADDING + Gui.CANVAS_SIZE, Gui.PADDING, Gui.COMMANDS_BAR_SIZE, Gui.CANVAS_SIZE)

            if rect.collidepoint(mouse_pos):
                self._mouse_command_handler(mouse_pos)
        elif event.type == pygame.USEREVENT:
            if not self.game.game_done():
                # event to edit one tile during the solving sequence
                pygame.time.set_timer(pygame.USEREVENT, 0)  # pause the timer
                try:
                    x, y, left_click = self.solver.best_click(self.game)
                except solver.AnalysisError:
                    left_click = True
                    x, y = self.solver.random_tile()
                self.lastSolvedTile = (x, y)
                if left_click:
                    self.game.left_mouse_button(x, y)
                else:
                    self.game.right_mouse_button(x, y)

                pygame.time.set_timer(pygame.USEREVENT, Gui.SOLVER_DELAY) # continue the timer
            else:
                self.solving = False
                self.lastSolvedTile = (-1, -1)
                pygame.time.set_timer(pygame.USEREVENT, 0)  # turn off the timer

    def _mouse_command_handler(self, mouse_pos):
        x_off = Gui.CANVAS_SIZE + 2 * Gui.PADDING  # X offset to the command bar
        y_off = Gui.PADDING  # Y offset to the command bar

        reset_rect = pygame.Rect(x_off + 15, y_off + 85, 170, 50)
        _8x8_rect = reset_rect.move(0, 75)
        _16x16_rect = _8x8_rect.move(0, 75)
        _25x25_rect = _16x16_rect.move(0, 75)
        solve_rect = _25x25_rect.move(0, 75)
        pause_solver_rect = solve_rect.move(0, 75)

        if reset_rect.collidepoint(mouse_pos):
            self.restart(self.size, self.mines)
        elif _8x8_rect.collidepoint(mouse_pos):
            self.restart(8, 10)
        elif _16x16_rect.collidepoint(mouse_pos):
            self.restart(16, 40)
        elif _25x25_rect.collidepoint(mouse_pos):
            self.restart(25, 99)
        elif solve_rect.collidepoint(mouse_pos):
            self.solving = True
            pygame.time.set_timer(pygame.USEREVENT, Gui.SOLVER_DELAY)
        elif pause_solver_rect.collidepoint(mouse_pos):
            self.solving = False
            pygame.time.set_timer(pygame.USEREVENT, 0) # pause timer
            self.lastSolvedTile = (-1, -1)

    # game loop that controls the buttons, game, and solver as well as drawing the screen
    def _game_loop(self):
        running = True
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    # Exit the game
                    pygame.quit()
                    quit()
                else:
                    self._event_handler(event)

            mouse_pos = (10000, 10000)
            if pygame.mouse.get_focused():
                mouse_pos = pygame.mouse.get_pos()
            self._draw_screen(mouse_pos)
            self.clock.tick(60)

# Created on 19 Apr 2020
# Created by: <NAME>
# This file defines class and methods used to control the mines GUI

"""
Defines the Gui class that uses pygame to visualize a game of mines

Public objects:
    * Class gui.Gui
"""

import pygame
import tile
import game
import solver


class Gui:
    """
This class controls the GUI to the mines game

# Start the gui
self.start(self):
    """

    CANVAS_SIZE = 900
    COMMANDS_BAR_SIZE = 200
    PADDING = 10
    SOLVER_DELAY = 100

    # colours
    BLACK = (0, 0, 0)
    WHITE = (255, 255, 255)
    LIGHT_GRAY = (240, 240, 240)
    MID_LIGHT_GRAY = (220, 220, 220)
    MID_GRAY = (190, 190, 190)
    GRAY_BLUE = (216, 227, 232)
    NAVY_BLUE = (21, 118, 171)
    RED = (194, 59, 56)
    GREEN = (60, 199, 85)

    # characters for minefield
    BLANK = ' ' # '\u25A1'
    COVERED = '\u25A8'
    FLAG = '\u2690'
    UNKNOWN = '?'
    MINE = '\u2737'

    def __init__(self):
        # size of the grid
        self.size = 16

        # number of mines on the grid
        self.mines = 40

        # game clock
        self.clock = pygame.time.Clock()

        # screen
        self.screen = pygame.display.set_mode((Gui.CANVAS_SIZE + Gui.COMMANDS_BAR_SIZE + 2 * Gui.PADDING,
                                               Gui.CANVAS_SIZE + 2 * Gui.PADDING))

        # Initialize the mines game
        self.game = game.Game()
        self.game.set_size(self.size)
        self.game.set_mines(self.mines)

        # Initialize the solver module
        self.solver = solver.Solver(self.game)

        # true when the Gui is using the solver module
        self.solving = False

        # tile that was last edited by the solver
        self.lastSolvedTile: (int, int) = (-1, -1)

        # the last mine tile where the left mouse was down
        self.last_left_down = (-1, -1)

        # the last mine tile where the right mouse was down
        self.last_right_down = (-1, -1)

        # tile that was pressed when the game was lost
        self.failed_tile = (-1, -1)

        # Initialize pygame
        pygame.init()

        pygame.display.set_caption("'Mines' made by mattlourenco27 on Github")
        # Icon made by Creaticca Creative Agency from www.flaticon.com
        icon = pygame.image.load("./assets/sprites/icon.png")
        pygame.display.set_icon(icon)

    # Start the gui
    def start(self):
        # start the game
        self.game.begin()

        # start the game loop
        self._game_loop()

    # restart the game and change the size and mines
    def restart(self, size, mines):
        self.solving = False
        self.lastSolvedTile = (-1, -1)
        pygame.time.set_timer(pygame.USEREVENT, 0) # turn off the timer
        self.game.reset()
        self.game.set_size(size)
        self.size = size
        self.game.set_mines(mines)
        self.mines = mines
        self.failed_tile = (-1, -1)
        self.solver = solver.Solver(self.game)
        self.game.begin()

    # draw the canvas where the game of mines will show
    def _draw_base_canvas(self):
        background = pygame.Rect(Gui.PADDING, Gui.PADDING, Gui.CANVAS_SIZE, Gui.CANVAS_SIZE)
        pygame.draw.rect(self.screen, Gui.WHITE, background)

        colour = Gui.BLACK

        # Draw the row dividers
        channel_width: float = Gui.CANVAS_SIZE / self.size
        line_width: int = 1
        for row_divider in range(0, self.size + 1):
            pygame.draw.line(self.screen, colour,
                             (Gui.PADDING, Gui.PADDING + int(row_divider * channel_width)),
                             (Gui.PADDING + Gui.CANVAS_SIZE, Gui.PADDING + int(row_divider * channel_width)),
                             line_width)

        # Draw the column dividers
        for col_divider in range(0, self.size + 1):
            pygame.draw.line(self.screen, colour,
                             (Gui.PADDING + int(col_divider * channel_width), Gui.PADDING),
                             (Gui.PADDING + int(col_divider * channel_width), Gui.PADDING + Gui.CANVAS_SIZE),
                             line_width)

    # draw the minefield that the game is played on
    def _draw_minefield(self, mouse_pos):
        channel_width = Gui.CANVAS_SIZE / self.size
        text = pygame.font.Font("assets/fonts/FreeSerif.ttf", int(channel_width * 0.6))

        mouse_x = (mouse_pos[0] - Gui.PADDING)
        mouse_y = (mouse_pos[1] - Gui.PADDING)

        if mouse_x < 0:
            mouse_x = -1
        else:
            mouse_x = int(mouse_x / channel_width)

        if mouse_y < 0:
            mouse_y = -1
        else:
            mouse_y = int(mouse_y / channel_width)

        for x in range(self.size):
            for y in range(self.size):
                tile_state = self.game.get_tile_state(x, y)

                # determine the colour fo the tile
                if x == mouse_x and y == mouse_y and tile_state is not tile.State.visible and not self.game.game_done():
                    colour = Gui.MID_GRAY
                elif (x, y) == self.failed_tile:
                    colour = Gui.RED
                else:
                    colour = Gui.BLACK

                # determine the character to print based on the tile state and value
                if tile_state is tile.State.covered:
                    character = Gui.COVERED
                elif tile_state is tile.State.visible:
                    value = self.game.get_tile_value(x, y)

                    if value == 0:
                        character = Gui.BLANK
                    elif value == -1:
                        character = Gui.MINE
                    else:
                        character = str(value)

                        # change colour based on the value
                        colour = pygame.Color(0, 0, 0)
                        colour.hsla = (((value - 1) * 200) % 360, 70, 40, 100)
                elif tile_state is tile.State.flag:
                    character = Gui.FLAG
                elif tile_state is tile.State.unknown:
                    character = Gui.UNKNOWN

                text_surface = text.render(character, True, colour)
                text_rect = text_surface.get_rect()
                text_rect.center = (Gui.PADDING + int((x + 0.5) * channel_width),
                                    Gui.PADDING + int((y + 0.5) * channel_width))
                self.screen.blit(text_surface, text_rect)

        # draw last edited tile by the solver
        if self.solving and self.lastSolvedTile != (-1, -1):
            x, y = self.lastSolvedTile
            colour = pygame.Color(0, 255, 0, 150)
            channel_width = Gui.CANVAS_SIZE / self.size
            rect = pygame.Rect(Gui.PADDING + x * channel_width, Gui.PADDING + y * channel_width,
                               channel_width, channel_width)

            # Transparent surface
            overlay = pygame.Surface((channel_width, channel_width), pygame.SRCALPHA)
            overlay.fill(colour)

            self.screen.blit(overlay, rect)

    # draw the command bar with buttons to click
    def _draw_command_bar(self, mouse_pos):
        x_off = Gui.CANVAS_SIZE + 2 * Gui.PADDING  # X offset to the command bar
        y_off = Gui.PADDING # Y offset to the command bar

        # indicate the number of flags placed
        text = pygame.font.Font("assets/fonts/FreeSerif.ttf", 40)
        message = str(self.game.get_flags()) + " / " + str(self.mines) + " " + Gui.FLAG
        rect = pygame.Rect(x_off + 15, y_off + 10, 170, 50)
        text_surface = text.render(message, True, Gui.BLACK)
        text_rect = text_surface.get_rect()
        text_rect.center = rect.center
        self.screen.blit(text_surface, text_rect)

        # Set up text for buttons
        text = pygame.font.Font("assets/fonts/FreeSans.ttf", 20)

        # Reset button
        rect = rect.move(0, 75)
        if rect.collidepoint(mouse_pos):
            pygame.draw.rect(self.screen, Gui.MID_LIGHT_GRAY, rect)
        else:
            pygame.draw.rect(self.screen, Gui.WHITE, rect)
        pygame.draw.rect(self.screen, Gui.BLACK, rect, 5)
        text_surface = text.render("Reset", True, Gui.BLACK)
        text_rect = text_surface.get_rect()
        text_rect.center = rect.center
        self.screen.blit(text_surface, text_rect)

        # 8x8 10 mines button
        rect = rect.move(0, 75)
        if rect.collidepoint(mouse_pos):
            pygame.draw.rect(self.screen, Gui.MID_LIGHT_GRAY, rect)
        else:
            pygame.draw.rect(self.screen, Gui.WHITE, rect)
        pygame.draw.rect(self.screen, Gui.BLACK, rect, 5)
        text_surface = text.render("8x8 10 mines", True, Gui.BLACK)
        text_rect = text_surface.get_rect()
        text_rect.center = rect.center
        self.screen.blit(text_surface, text_rect)

        # 16x16 40 mines button
        rect = rect.move(0, 75)
        if rect.collidepoint(mouse_pos):
            pygame.draw.rect(self.screen, Gui.MID_LIGHT_GRAY, rect)
        else:
            pygame.draw.rect(self.screen, Gui.WHITE, rect)
        pygame.draw.rect(self.screen, Gui.BLACK, rect, 5)
        text_surface = text.render("16x16 40 mines", True, Gui.BLACK)
        text_rect = text_surface.get_rect()
        text_rect.center = rect.center
        self.screen.blit(text_surface, text_rect)

        # 25x25 99 mines button
        rect = rect.move(0, 75)
        if rect.collidepoint(mouse_pos):
            pygame.draw.rect(self.screen, Gui.MID_LIGHT_GRAY, rect)
        else:
            pygame.draw.rect(self.screen, Gui.WHITE, rect)
        pygame.draw.rect(self.screen, Gui.BLACK, rect, 5)
        text_surface = text.render("25x25 99 mines", True, Gui.BLACK)
        text_rect = text_surface.get_rect()
        text_rect.center = rect.center
        self.screen.blit(text_surface, text_rect)

        # solve button
        rect = rect.move(0, 75)
        if rect.collidepoint(mouse_pos):
            pygame.draw.rect(self.screen, Gui.MID_LIGHT_GRAY, rect)
        else:
            pygame.draw.rect(self.screen, Gui.WHITE, rect)
        pygame.draw.rect(self.screen, Gui.BLACK, rect, 5)
        text_surface = text.render("Auto-Solve", True, Gui.BLACK)
        text_rect = text_surface.get_rect()
        text_rect.center = rect.center
        self.screen.blit(text_surface, text_rect)

        # pause solver button
        rect = rect.move(0, 75)
        if rect.collidepoint(mouse_pos):
            pygame.draw.rect(self.screen, Gui.MID_LIGHT_GRAY, rect)
        else:
            pygame.draw.rect(self.screen, Gui.WHITE, rect)
        pygame.draw.rect(self.screen, Gui.BLACK, rect, 5)
        text_surface = text.render("Pause Solver", True, Gui.BLACK)
        text_rect = text_surface.get_rect()
        text_rect.center = rect.center
        self.screen.blit(text_surface, text_rect)

    # draws victory text
    def _draw_win(self):
        message = "Victory"
        colour = Gui.GREEN

        text = pygame.font.Font("assets/fonts/FreeSerifBoldItalic.ttf", 127)
        text_surface = text.render(message, True, colour)
        text_rect = text_surface.get_rect()
        text_rect.center = (int((Gui.PADDING + Gui.CANVAS_SIZE) * 0.5), int((Gui.PADDING + Gui.CANVAS_SIZE) * 0.5))
        self.screen.blit(text_surface, text_rect)

    # draws the mines game and the command bar
    def _draw_screen(self, mouse_pos):
        self.screen.fill(Gui.LIGHT_GRAY)

        self._draw_base_canvas()
        self._draw_minefield(mouse_pos)
        self._draw_command_bar(mouse_pos)

        if self.game.victory():
            self._draw_win()

        pygame.display.update()

    # handles the pygame events 60 times per second
    def _event_handler(self, event):
        if event.type == pygame.MOUSEBUTTONDOWN:
            # mouse position at the current event
            mouse_pos = pygame.mouse.get_pos()

            # check if mouse intersects the minefield
            rect = pygame.Rect(Gui.PADDING, Gui.PADDING, Gui.CANVAS_SIZE, Gui.CANVAS_SIZE)

            if rect.collidepoint(mouse_pos) and not self.solving:
                channel_width = Gui.CANVAS_SIZE / self.size
                mouse_x = int((mouse_pos[0] - Gui.PADDING) / channel_width)
                mouse_y = int((mouse_pos[1] - Gui.PADDING) / channel_width)

                if pygame.mouse.get_pressed()[0]:
                    self.last_left_down = (mouse_x, mouse_y)
                elif pygame.mouse.get_pressed()[2]:
                    self.last_right_down = (mouse_x, mouse_y)

        elif event.type == pygame.MOUSEBUTTONUP:
            # mouse position at the current event
            mouse_pos = pygame.mouse.get_pos()

            # check if mouse intersects the minefield
            rect = pygame.Rect(Gui.PADDING, Gui.PADDING, Gui.CANVAS_SIZE, Gui.CANVAS_SIZE)

            if rect.collidepoint(mouse_pos) and not self.solving:
                channel_width = Gui.CANVAS_SIZE / self.size
                mouse_x = int((mouse_pos[0] - Gui.PADDING) / channel_width)
                mouse_y = int((mouse_pos[1] - Gui.PADDING) / channel_width)

                if not pygame.mouse.get_pressed()[0] and self.last_left_down != (-1, -1):
                    if (mouse_x, mouse_y) == self.last_left_down:
                        self.game.left_mouse_button(mouse_x, mouse_y)

                    self.last_left_down = (-1, -1)

                    if self.game.game_done() and not self.game.victory() and self.failed_tile == (-1, -1):
                        self.failed_tile = (mouse_x, mouse_y)
                elif not pygame.mouse.get_pressed()[2] and self.last_right_down != (-1, -1):
                    if (mouse_x, mouse_y) == self.last_right_down:
                        self.game.right_mouse_button(mouse_x, mouse_y)

                    self.last_right_down = (-1, -1)
            # check if the mouse is over the command bar
            rect = pygame.Rect(Gui.PADDING + Gui.CANVAS_SIZE, Gui.PADDING, Gui.COMMANDS_BAR_SIZE, Gui.CANVAS_SIZE)

            if rect.collidepoint(mouse_pos):
                self._mouse_command_handler(mouse_pos)
        elif event.type == pygame.USEREVENT:
            if not self.game.game_done():
                # event to edit one tile during the solving sequence
                pygame.time.set_timer(pygame.USEREVENT, 0)  # pause the timer
                try:
                    x, y, left_click = self.solver.best_click(self.game)
                except solver.AnalysisError:
                    left_click = True
                    x, y = self.solver.random_tile()
                self.lastSolvedTile = (x, y)
                if left_click:
                    self.game.left_mouse_button(x, y)
                else:
                    self.game.right_mouse_button(x, y)

                pygame.time.set_timer(pygame.USEREVENT, Gui.SOLVER_DELAY) # continue the timer
            else:
                self.solving = False
                self.lastSolvedTile = (-1, -1)
                pygame.time.set_timer(pygame.USEREVENT, 0)  # turn off the timer

    def _mouse_command_handler(self, mouse_pos):
        x_off = Gui.CANVAS_SIZE + 2 * Gui.PADDING  # X offset to the command bar
        y_off = Gui.PADDING  # Y offset to the command bar

        reset_rect = pygame.Rect(x_off + 15, y_off + 85, 170, 50)
        _8x8_rect = reset_rect.move(0, 75)
        _16x16_rect = _8x8_rect.move(0, 75)
        _25x25_rect = _16x16_rect.move(0, 75)
        solve_rect = _25x25_rect.move(0, 75)
        pause_solver_rect = solve_rect.move(0, 75)

        if reset_rect.collidepoint(mouse_pos):
            self.restart(self.size, self.mines)
        elif _8x8_rect.collidepoint(mouse_pos):
            self.restart(8, 10)
        elif _16x16_rect.collidepoint(mouse_pos):
            self.restart(16, 40)
        elif _25x25_rect.collidepoint(mouse_pos):
            self.restart(25, 99)
        elif solve_rect.collidepoint(mouse_pos):
            self.solving = True
            pygame.time.set_timer(pygame.USEREVENT, Gui.SOLVER_DELAY)
        elif pause_solver_rect.collidepoint(mouse_pos):
            self.solving = False
            pygame.time.set_timer(pygame.USEREVENT, 0) # pause timer
            self.lastSolvedTile = (-1, -1)

    # game loop that controls the buttons, game, and solver as well as drawing the screen
    def _game_loop(self):
        running = True
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    # Exit the game
                    pygame.quit()
                    quit()
                else:
                    self._event_handler(event)

            mouse_pos = (10000, 10000)
            if pygame.mouse.get_focused():
                mouse_pos = pygame.mouse.get_pos()
            self._draw_screen(mouse_pos)
            self.clock.tick(60)

# Created on 19 Apr 2020 # Created by: <NAME> # This file defines class and methods used to control the mines GUI Defines the Gui class that uses pygame to visualize a game of mines  Public objects:     * Class gui.Gui This class controls the GUI to the mines game  # Start the gui self.start(self): # colours # characters for minefield # '\u25A1' # size of the grid # number of mines on the grid # game clock # screen # Initialize the mines game # Initialize the solver module # true when the Gui is using the solver module # tile that was last edited by the solver # the last mine tile where the left mouse was down # the last mine tile where the right mouse was down # tile that was pressed when the game was lost # Initialize pygame # Icon made by Creaticca Creative Agency from www.flaticon.com # Start the gui # start the game # start the game loop # restart the game and change the size and mines # turn off the timer # draw the canvas where the game of mines will show # Draw the row dividers # Draw the column dividers # draw the minefield that the game is played on # determine the colour fo the tile # determine the character to print based on the tile state and value # change colour based on the value # draw last edited tile by the solver # Transparent surface # draw the command bar with buttons to click # X offset to the command bar # Y offset to the command bar # indicate the number of flags placed # Set up text for buttons # Reset button # 8x8 10 mines button # 16x16 40 mines button # 25x25 99 mines button # solve button # pause solver button # draws victory text # draws the mines game and the command bar # handles the pygame events 60 times per second # mouse position at the current event # check if mouse intersects the minefield # mouse position at the current event # check if mouse intersects the minefield # check if the mouse is over the command bar # event to edit one tile during the solving sequence # pause the timer # continue the timer # turn off the timer # X offset to the command bar # Y offset to the command bar # pause timer # game loop that controls the buttons, game, and solver as well as drawing the screen # Exit the game