So, I've tried to write a gui for Tic Tac Toe or noughts and crosses, as well as an AI opponent to play against. This is by far the longest program I've written, having mainly just done bash scripts as part of my job, and occasionally using a few lines of python to do something that is unpleasant to do in Bash, but I do have to read and understand a fair bit of Python code at work.I would appreciate any feedback, and do plan to implement some sort of recursive search into the engine, rather than the current fixed depth, as my real goal is to write a gui and engine for Tak, a much more complicated game.import tkinter as tkimport randomimport numpy as npimport gamecfg# The main classclass Square(tk.Canvas):    A Square is a canvas on which nought or cross can be played    # Cross starts, board of length m, and the game has no result yet    crossToPlay = True    result = None    m = gamecfg.n    # moveList, squareDict and state are all representations of the board    moveList = []    squareDict = {}    state = np.zeros((m, m))    def __init__(self, name, master=None, size=None):        super().__init__(master, width=size, height=size)        self.bind(<Button-1>, self.tic)        self.config(highlightbackground=Black)        self.config(highlightthickness=1)        self.symbol = None        self.name = name        self.topLeft = size * 0.15        self.bottomRight = size * 0.85        # Add itself to the dict of squares        Square.squareDict[self.name] = self    def draw(self):        This will draw a nought or cross on itself,        depending on who is to play.        if not self.symbol and not self.result:            tl = self.topLeft            br = self.bottomRight            if Square.crossToPlay:                self.create_line(tl, tl, br, br)                self.create_line(tl, br, br, tl)                self.symbol = 'X'                Square.state[self.name] = 1            else:                self.create_oval(tl, tl, br, br)                self.symbol = 'O'                Square.state[self.name] = Square.m + 1            Square.crossToPlay = not Square.crossToPlay            Square.moveList.append(self)            Square.print()            Square.setResult(winCheck(Square.state))    def tic(self, event):        A tic is a player clicking on a square        self.draw()        computerMove()    def tac(self):        A tac is the computer playing        self.master.update()        self.after(gamecfg.engineWait, self.draw())    def clear(self):        This will clear the selected Square.        if self.symbol:            self.delete(all)            self.symbol = None            Square.result = None            Square.crossToPlay = not Square.crossToPlay            Square.print()            Square.state[self.name] = 0    @classmethod    def undo(cls):        cls.moveList.pop().clear()    @classmethod    def print(cls):        print('Moves:', *[square.name for square in cls.moveList])    @classmethod    def setResult(cls, result):        if result:            cls.result = result             print(Result: {}.format(result))def winCheck(state):    Takes a position, and returns the outcome of that game    # Sums which correspond to a line across a column    winNums = list(state.sum(axis=0))    # Sums which correspond to a line across a row    winNums.extend(list(state.sum(axis=1)))    # Sums which correspond to a line across the main diagonal    winNums.append(state.trace())    # Sums which correspond to a line across the off diagonal    winNums.append(np.flipud(state).trace())    if Square.m in winNums:        return 'X'    elif (Square.m**2 + Square.m) in winNums:        return 'O'    elif np.count_nonzero(state) == Square.m**2:        return 'D'    else:        return None# This function is called safely anytime it might be the computer's turndef computerMove():    # Decide whether or not the computer is to play    if gamecfg.engineIsCross == Square.crossToPlay and not Square.result:        # Set the value of engine and opponent's pieces in state        if gamecfg.engineIsCross:            e = 1; o = gamecfg.n + 1; victory = 'X'; loss = 'O'        else:            e = gamecfg.n + 1; o = 1; victory = 'O'; loss = 'X'        # Use Square.state to determine the legal moves        gameState = Square.state.copy()        moveChoices = []        moveScores = {}        # Iterate over state, to determine which squares are empty        it = np.nditer(gameState, flags=['multi_index'])        while not it.finished:            if it[0] == 0:                moveChoices.append(it.multi_index)            it.iternext()        for move in moveChoices:            # Before evaluation, all squares are equal            moveScores[move] = 0        if gamecfg.engineLevel >= 2:            for move in moveChoices:                moveState = gameState.copy()                moveState[move] = e                #print(I am considering {}.format(move))                if winCheck(moveState) == victory:                    # Winning is always the best possible move                    print(I will play {} to win!.format(move))                    moveScores[move] = 100                    break                if gamecfg.engineLevel >= 3:                    movesLeft = moveChoices[:]                    movesLeft.remove(move)                    for next in movesLeft:                        nextState = moveState.copy()                        nextState[next] = o                        if winCheck(nextState) == loss:                            # The only thing preferable to blocking a loss is winning                            print(I will play {} to not lose!.format(next))                            moveScores[next] = 10                            break        # Choose the highest value, or a random move if all are tied        if all(moveScore == 0 for moveScore in moveScores.values()):            Square.squareDict[random.choice(moveChoices)].tac()        else:            Square.squareDict[max(moveScores.keys(), key=(lambda k: moveScores[k]))].tac()def clearAll():    while Square.moveList:        Square.moveList.pop().clear()    computerMove()def main():    root = tk.Tk()    root.title(Tic Tac Toe)    # Creating the board, 600 x 600 pixels, n squares across    m = gamecfg.n    size = 600 // m    squares = [(rank, file) for rank in range(m) for file in range(m)]    for (rank, file) in squares:        square = Square((rank, file), master=root, size=size)        square.grid(row=rank, column=file)    # Creating File Menu    menu = tk.Menu(root)    root.config(menu=menu)    fileMenu = tk.Menu(menu)    menu.add_cascade(label=File, menu=fileMenu)    # Undo calls the clear function on the most recently played square.    fileMenu.add_command(label=Undo, command=lambda: Square.moveList.pop().clear())    fileMenu.add_command(label=State, command=lambda: print(Square.state))    fileMenu.add_command(label=Result, command=lambda: print(Square.result))    fileMenu.add_command(label=Restart, command=lambda: clearAll())    computerMove()    root.mainloop()if __name__ == '__main__':    main()And gamecfg.py is not really any code, just some config settings:    # Length of board    n = 3    #Engine player: True - X, False - O, None - No Engine    engineIsCross = True    # Level 1: Random legal move, Level 2: Wins if possible, Level 3: Avoids loss if possible as well    engineLevel = 3    # How long in miliseconds the engine should sleep between calculating it's move and drawing it    engineWait = 500    
Python Tic Tac Toe Gui and AI
python;tic tac toe;gui;ai