import random
from tkinter import *
from tkinter import messagebox


def mine_sweeper(colour="#39d972"):
    class MineSweeperCell(Label):
        '''Creates a minesweeper square that is part of a grid'''

        def __init__(self, master):
            '''Makes an unmarked minesweeper grid square; 0 represents blank and 9 represents bomb'''
            # create cell label
            Label.__init__(self, master, height=1, width=2, text='', bg=colour, bd=5, font=('Arial', 10), relief=RAISED)
            self.value = ''  # store cell value
            self.clicked = False  # store whether it is clicked
            self.flagged = False  # store whether it is flagged
            self.checked = False  # store whether it has been checked (for 0's)
            # color dictionary
            self.colorMap = ['', 'blue', 'darkgreen', 'red', 'purple', 'maroon', 'cyan', 'black', 'gray']
            # attaches mouse clicks to functions
            self.bind('<Button-1>', self.reveal)
            self.bind('<Button-3>', self.flag)

        def get_value(self):
            '''Returns value of cell'''
            return self.value

        def is_clicked(self):
            '''Tells if cell has been clicked'''
            return self.clicked

        def is_flagged(self):
            '''Tells if cell is flagged'''
            return self.flagged

        def flag(self, event):
            '''Flags the square if player believes there is a bomb'''
            if self.flagged == False and self.clicked == False and int(
                    self.master.flags) > 0 and self.master.winner == False:
                # if no flag
                self['text'] = '*'
                self.master.subFlag()  # subtract from remaining bombs
                self.flagged = True  # set self.flagged to true
            elif self.flagged == True and self.clicked == False and int(self.master.flags) < int(
                    self.master.bombs) and self.master.winner == False:  # if it is already flagged
                self['text'] = ''  # remove flag
                self.master.addFlag()  # add to remaining bombs
                self.flagged = False  # set self.flagged to False

        def set_value(self, value):
            '''Sets the value of the cell'''
            self.value = value

        def reveal(self, event):
            '''Reveals the hidden cell value'''
            if self.clicked == False:
                if 0 < self.value < 9:
                    if self.flagged == False and self.master.loser == False:  # checks if it is a valid click
                        self['relief'] = SUNKEN
                        # set text and color
                        self['text'] = str(self.value)
                        self['fg'] = self.colorMap[self.value]
                        self['bg'] = 'gray65'
                        self["bd"] = 5
                        self.clicked = True
                        self.master.CheckWin()
                elif self.value == 9:
                    if self.master.winner == False and self.flagged == False:  # if it is a bomb
                        self['text'] = '*'  # make text a bomb
                        self['bg'] = 'red'  # make background red
                        self["bd"] = 5
                        self['relief'] = SUNKEN
                        self.clicked = True
                        self.master.CheckLoss()
                elif self.value == 0:
                    if self.flagged == False and self.master.loser == False:
                        self['text'] = ''
                        self.master.uncover_other_blank(self)
                        self['relief'] = SUNKEN
                        self['bg'] = 'gray65'
                        self["bd"] = 5
                        self.clicked = True
                        self.master.CheckWin()

        def expose(self):
            '''Exposes surrounding zeros'''
            if self.clicked == False:
                if 0 < self.value < 9:
                    if self.flagged == False and self.master.loser == False:
                        self['text'] = str(self.value)  # set text and color
                        self['fg'] = self.colorMap[self.value]
                        self['relief'] = SUNKEN
                        self['bg'] = 'gray65'
                        self["bd"] = 5
                        self.clicked = True
                        self.master.CheckWin()
                elif self.value == 9:
                    if self.flagged == False:  # if it is a bomb
                        self['text'] = '*'  # make text a bomb
                        self['bg'] = 'red'  # make background red
                        self["bd"] = 5
                        self['relief'] = SUNKEN
                        self.clicked = True
                elif self.value == 0:
                    if self.flagged == False and self.master.loser == False:
                        self['text'] = ''
                        self.master.uncover_other_blank(self)
                        self['relief'] = SUNKEN
                        self['bg'] = 'gray65'
                        self["bd"] = 5
                        self.clicked = True
                        self.master.CheckWin()

    class MineSweeperGrid(Frame):
        '''Creates a minesweeper grid to play the game on'''

        def __init__(self, master, width, height, numBombs):
            '''Creates a grid with the given width, height, and number of bombs'''
            Frame.__init__(self, master, bg=colour, bd=5)
            self.grid()
            # sets the number of bombs
            self.bombs = numBombs
            # sets the number of flags
            self.flags = numBombs
            # create something to set the bombs up around each square
            self.count = 0
            self.winner = False
            self.loser = False
            # create the label that will keep track of flags
            self.flagTrack = Label(self, text=self.flags, bg=colour, height=1, width=2, font=("Arial", 36))
            self.flagTrack.grid(row=height, columnspan=width)
            # create the bombs
            self.cells = {}
            for i in range(int(numBombs)):
                y = random.randint(0, int(width) - 1)
                x = random.randint(0, int(height) - 1)
                while (x, y) in self.cells.keys():
                    y = random.randint(0, int(width) - 1)
                    x = random.randint(0, int(height) - 1)
                self.cells[(x, y)] = MineSweeperCell(self)
                self.cells[(x, y)].set_value(9)
                self.cells[(x, y)].grid(row=x, column=y)
            # create the other cells
            for row in range(int(height)):
                for column in range(int(width)):
                    if (row, column) not in self.cells.keys():
                        self.count = 0
                        if (row + 1, column) in self.cells.keys():
                            if self.cells[(row + 1, column)].get_value() == 9:
                                self.count = self.count + 1
                        if (row + 1, column + 1) in self.cells.keys():
                            if self.cells[(row + 1, column + 1)].get_value() == 9:
                                self.count = self.count + 1
                        if (row, column + 1) in self.cells.keys():
                            if self.cells[(row, column + 1)].get_value() == 9:
                                self.count = self.count + 1
                        if (row - 1, column + 1) in self.cells.keys():
                            if self.cells[(row - 1, column + 1)].get_value() == 9:
                                self.count = self.count + 1
                        if (row - 1, column) in self.cells.keys():
                            if self.cells[(row - 1, column)].get_value() == 9:
                                self.count = self.count + 1
                        if (row - 1, column - 1) in self.cells.keys():
                            if self.cells[(row - 1, column - 1)].get_value() == 9:
                                self.count = self.count + 1
                        if (row, column - 1) in self.cells.keys():
                            if self.cells[(row, column - 1)].get_value() == 9:
                                self.count = self.count + 1
                        if (row + 1, column - 1) in self.cells.keys():
                            if self.cells[(row + 1, column - 1)].get_value() == 9:
                                self.count = self.count + 1
                        self.cells[(row, column)] = MineSweeperCell(self)
                        self.cells[(row, column)].set_value(self.count)
                        self.cells[(row, column)].grid(row=row, column=column)

        def subFlag(self):
            '''Subtracts one from the flagcount'''
            self.flags = int(self.flags) - 1
            self.flagTrack['text'] = str(self.flags)

        def addFlag(self):
            '''Adds one from the flagcount'''
            self.flags = int(self.flags) + 1
            self.flagTrack['text'] = str(self.flags)

        def uncover_other_blank(self, cell):
            '''Uncovers all blank squares adjacent to the marked square'''
            for coords, mine in self.cells.items():
                if mine == cell:
                    c = coords
            x = int(float(c[0]))
            y = int(float(c[1]))
            cell.checked = True
            # checks all cells in radius
            if (x + 1, y) in self.cells.keys():
                if self.cells[(x + 1, y)].checked == False:
                    self.cells[(x + 1, y)].expose()
            if (x + 1, y + 1) in self.cells.keys():
                if self.cells[(x + 1, y + 1)].checked == False:
                    self.cells[(x + 1, y + 1)].expose()
            if (x, y + 1) in self.cells.keys():
                if self.cells[(x, y + 1)].checked == False:
                    self.cells[(x, y + 1)].expose()
            if (x - 1, y + 1) in self.cells.keys():
                if self.cells[(x - 1, y + 1)].checked == False:
                    self.cells[(x - 1, y + 1)].expose()
            if (x - 1, y) in self.cells.keys():
                if self.cells[(x - 1, y)].checked == False:
                    self.cells[(x - 1, y)].expose()
            if (x - 1, y - 1) in self.cells.keys():
                if self.cells[(x - 1, y - 1)].checked == False:
                    self.cells[(x - 1, y - 1)].expose()
            if (x, y - 1) in self.cells.keys():
                if self.cells[(x, y - 1)].checked == False:
                    self.cells[(x, y - 1)].expose()
            if (x + 1, y - 1) in self.cells.keys():
                if self.cells[(x + 1, y - 1)].checked == False:
                    self.cells[(x + 1, y - 1)].expose()

        def CheckWin(self):
            '''Checks if player won'''
            doneList = []
            for key in self.cells.keys():
                if self.cells[key].clicked == True and self.cells[key].value != 9:
                    doneList.append(self.cells[key])
            if len(doneList) == int(height) * int(width) - int(numBombs):
                messagebox.showinfo('Minesweeper', 'Congratulations -- you won!', parent=self)
                self.winner = True
                playagain()

        def CheckLoss(self):
            '''Checks if player lost'''
            self.loser = True
            self.flagTrack['text'] = '0'
            messagebox._show('Minesweeper', 'You Lose!', parent=self)
            for key in self.cells.keys():
                if self.cells[key].value == 9:
                    self.cells[key].flagged = False
                    self.cells[key].expose()
            playagain()

    def playagain():
        again = messagebox.askyesno("Minesweeper", "Do you want to play again?")
        if again == TRUE:
            root.destroy()
            __init__()

        else:
            root.destroy()

    def __init__():
        global width
        global height
        global numBombs
        width = 10
        height = 10
        numBombs = 30
        global root
        root = Tk()
        root.maxsize(270, 335)
        root.minsize(270, 335)
        root.title('Minesweeper')
        global game
        game = MineSweeperGrid(root, width, height, numBombs)
        game.mainloop()

    __init__()


mine_sweeper()
