在MiniMax中重置Tree对象Python 3 TicTacToe算法拒绝重置? Tkinter的

时间:2018-06-07 10:37:51

标签: python-3.x tkinter minimax

我一直在讨论这个问题已经有一个星期左右的时间了,但现在却无法弄清楚为什么当我"重置"时,我无法正常重置回根状态。我的TTT与我的minimax玩家的比赛。

我的游戏确实完全重置回那个状态,但出于某种原因,它会记住以前的游戏第一次播放。

在我的TTTGame类的重置方法中,我做错了什么?

我知道这个程序中使用了很多不好的技术,虽然我希望对这些有一些建议,我只是想要一个工作版本的重置按钮:(

感谢您提供任何帮助!

minimax.py

MIN="O"
MAX="X"
BLANK=" "

class Tree(object):
    def __init__(self,startingPlayer=MAX):
        self.startingPlayer=startingPlayer
        self.root=State(startingPlayer)#986409 States! this isn't 9! though?
    def reset(self):
        self.__init__()
class State(object):
    """
    A State contains a symbol, whos turn it is, and the state of the board. It also knows
    who its children are if it has any
    """
    counter=0
    def __init__(self,symbol,board=[BLANK for i in range(9)]):
        self.value=None
        self.board=board
        self.symbol=symbol # symbol is the next to play, but is not the player whom just created this state but the opponent
        self.children=[]
        if self.symbol==MAX:
            self.opposingSymbol=MIN
        else:
            self.opposingSymbol=MAX

        if self.isWinner()==False:
            #if this state is not a winning state, iterate its children recursively in a DFS style creation
            self.findAndSetChildren() # Now 549945 states instead of 986409
        else: #this is a winner for the opposing symbol
            if self.opposingSymbol==MAX:
                self.value=1
                #print(self.opposingSymbol, ":",self.value,":",self.board)
            else:
                self.value=-1
                #print(self.opposingSymbol, ":",self.value,":",self.board)
        #now I wish to set via backtracking all the terminal states, the values of each intermediate state
    def __repr__(self):
        op="\n"
        for i in range(0,9,3):
            op+="|".join(self.board[i:i+3])+"\n"
        return op

    def findBlanks(self):
        return [i for i in range(len(self.board)) if self.board[i]==BLANK]

    def findAndSetChildren(self):
        if self.findBlanks()!=[]:
            for pos in self.findBlanks():
                #for each blank position, copy the board and play the current symbol in that position
                #make a state for this new board and ask it to recursively make its children
                #until there are no more children to make!
                childBoard=self.board.copy()
                childBoard[pos]=self.symbol

                #print(self.symbol,":",childBoard)                
                self.children.append(State(self.opposingSymbol,childBoard))
                State.counter+=1



        else:
            #by virtue of being in this method, I already know this state is not a winner
            #if it has no more blanks - there is nothing left to play. This state is a dud
            self.value=0
            #print(self.opposingSymbol, ":",self.value,":",self.board)

    def calcValue(self):
        '''
        Method to calculate the value of a state if all child states play perfectly
        '''
        if self.value in [-1,0,1]:
            return self.value
        else:
            if self.symbol==MAX:
                val=-1
                for child in self.children:
                    if child.calcValue()>val: # this is a recursive call while returning the largest child from children
                        val=child.calcValue()
                return val
            elif self.symbol==MIN:
                val=1
                for child in self.children:
                    if child.calcValue()<val: # similar but smallest child
                        val=child.calcValue()
                return val

    def nextChoice(self):
        if self.children!=[]:
            if self.symbol==MAX:
                choice=0
                value=-1
                for i in range(len(self.children)):
                    if self.children[i].calcValue()>value:
                        value=self.children[i].calcValue()
                        choice=i

            elif self.symbol==MIN:
                choice=0
                value=1
                for i in range(len(self.children)):
                    if self.children[i].calcValue()<value:
                        value=self.children[i].calcValue()
                        choice=i
            return choice
        return None






    def isWinner(self):
        # Horizontal win check
        for i in range(0,7,3):
            if self.board[i] == self.board[i + 1] == self.board[i + 2] and self.board[i]!=BLANK:
                return True
        for i in range(3):
            if self.board[i] == self.board[i + 3] == self.board[i + 6] and self.board[i]!=BLANK:
                return True
        if self.board[0] == self.board[4] == self.board[8] and self.board[0]!=BLANK:
            return True
        if self.board[2] == self.board[4] == self.board[6] and self.board[2]!=BLANK:
            return True
        return False



if __name__ == '__main__':
    myTree=Tree()
    #the rest is a load of test conditions for seeing how the data strucutre works
    print(myTree.root.counter)
    #this shows that the terminal states have a win criteria
    print("Printing an example terminal state where X has won")
    print(myTree.root.children[2].children[2].children[2].children[2].children[2].board)
    print(myTree.root.children[2].children[2].children[2].children[2].children[2].value)

    print("Testing next choice")
    state=myTree.root.children[2].children[2].children[2].children[2]

    state=myTree.root.children[3]
    print(state.board)
    print("Player:",state.symbol)
    print("Children:", state.children)
    print("Children boards:")
    for child in state.children:
        print(child.board, child.calcValue())
    print("My Choice:")
    print(state.nextChoice())
    state=state.children[0]
    print("Children boards:")
    for child in state.children:
        print(child.board, child.calcValue())
    print("My Choice:")
    print(state.nextChoice())

ttt.py

from tkinter import *
from minimax import *
from random import choice
from time import sleep

MARGIN = 20  # Pixels around the board
SIDE = 150  # Width of every board cell.
WIDTH = MARGIN * 4 + SIDE * 3
HEIGHT = MARGIN * 4 + SIDE * 4  # Width and height of the whole frame
BLANK=" "

class tttUI(Frame):
    """
    The Tkinter UI, responsible for drawing the board and accepting user input.
    """
    def __init__(self, master, game):
        """ Initialize Frame. """
        self.game=game
        super(tttUI, self).__init__(master)  
        self.grid()
        self.__initUI()
        self.__draw_grid()

    def __initUI(self):
        self.grid_propagate(False) # stop tkinter shrinking to fit contents
        self.configure(width=WIDTH, height=HEIGHT)

        # create title
        Label(self,
              text = "1 Player Tic Tac Toe",font=("Helvetica", 16)
              ).grid(row = 0, column = 0, columnspan = 5, sticky = NSEW,padx=10, pady=10)

        #create radio
        #self.players = IntVar()
        #Radiobutton(self, text="1 Player", variable=self.players, value=1).grid(row = 1, column = 1,padx=10, pady=10)
        #Radiobutton(self, text="2 Player", variable=self.players, value=2).grid(row = 1, column = 2,padx=10, pady=10)

        #buttons
        Button(self,text="Play",command=self.game.start).grid(row = 1, column = 2,padx=10, pady=10)
        Button(self,text="Reset",command=self.game.reset).grid(row = 1, column = 3,padx=10, pady=10)

        #create canvas
        self.canvas = Canvas(self,
                             width=SIDE*3+MARGIN*2,
                             height=SIDE*3+MARGIN*2,bg="white")
        self.canvas.grid(row = 2, column = 0,columnspan = 5, sticky = NSEW, padx=20, pady=20)
        self.canvas.bind("<Button-1>", self.__cellClick)

        #Output label
        self.op=StringVar()
        self.op.set("Press play when ready")
        Label(self,
              textvariable=self.op,font=("Helvetica", 16)
              ).grid(row = 3, column = 0, columnspan = 5, sticky = NSEW)

    def __cellClick(self,event):

        col=(event.x-MARGIN)//SIDE
        row=(event.y-MARGIN)//SIDE
        #print(event.x, event.y)
        #print(col,row)
        if self.game.gameRunning==True:
            self.game.cellClicked(col,row)



    def __draw_grid(self):
        """Draws grid divided with black lines into 3x3 squares"""
        for i in range(1,3):

            x0 = MARGIN
            y0 = MARGIN+i*SIDE
            x1 = x0+3*SIDE
            y1 = y0
            self.canvas.create_line(x0, y0, x1, y1, fill="black",tags="grid")

            x0 = MARGIN+i*SIDE
            y0 = MARGIN
            x1 = x0
            y1 = y0+3*SIDE 
            self.canvas.create_line(x0, y0, x1, y1, fill="black",tags="grid")
    def updateUI(self):
        """wipes all canvas symbols and redraws them"""
        self.canvas.delete("symbols")
        board=[]

        #make 2D "board" from games current 1D state board, this is stupid but I can change from 1D 0-8 to 2D later...
        board.append(self.game.currentBoard.board[0:3])
        board.append(self.game.currentBoard.board[3:6])        
        board.append(self.game.currentBoard.board[6:9])

        for i in range(3):
            for j in range(3):
                self.drawSymbol(i,j,board[i][j])

    def drawSymbol(self, x, y, symbol):
        """draws an X or O in board position"""
        if symbol!=BLANK:
            #get to the centre of the boxes to draw the right symbol
            x=MARGIN+x*SIDE+0.5*SIDE
            y=MARGIN+y*SIDE+0.5*SIDE

            if symbol=="X":
                self.canvas.create_line(x-SIDE/5,y-SIDE/5,x+SIDE/5,y+SIDE/5,width=8,tags="symbols")
                self.canvas.create_line(x-SIDE/5,y+SIDE/5,x+SIDE/5,y-SIDE/5,width=8,tags="symbols") 
            elif symbol=="O":
                self.canvas.create_oval(x-SIDE/5,y-SIDE/5,x+SIDE/5,y+SIDE/5,width=8,tags="symbols") 



class TttGame():
    """the Game controller"""
    def __init__(self,master):
        #set up data structure
        self.myTree=Tree(startingPlayer="X")
        self.currentBoard=self.myTree.root #starting player is X, the root state (empty board) is the currentBoard
        self.gameRunning=False
        #make the UI
        self.UI = tttUI(master,self)#make the UI and give it a copy of this game object so it can interact with and call its methods
        self.currentBoard=self.myTree.root
    def start(self):
        self.currentBoard=self.myTree.root
        self.gameRunning=True
        self.UI.op.set("It's your turn")

    def reset(self):
        self.gameRunning=False
        self.myTree=Tree(startingPlayer="X")
        self.currentBoard=self.myTree.root        
        self.UI.updateUI()
        self.UI.op.set("Press play when ready")
        print(self.myTree.root)
        #for child in self.myTree.root.children:
        #    print(child)

    def cellClicked(self,row,col): #event is called from canvas clicks, row and col are already floored quotient, 0,1,2
        """
        This method is called by the GUI, it represents that the player has clicked on an empty cell
        This is the event that will kick off the player pushing to a child state, and then cause the
        AI to have a turn, upon which the player can have another turn!
        """
        #convert row/col to index (2D visual of 1D list)
        if row==0:
            i = col
        elif row==1:
            i = col+3
        elif row==2:
            i = col+6

        self.currentBoard.board[i]=self.currentBoard.symbol  #play the symbol in the current board

        #work out which child was played - this isn't well written but I need a way of manually shunting to another state
        for child in self.currentBoard.children:
            if child.board == self.currentBoard.board: # Success! This was the one that was played
                #update current board to this one so that AI can have a turn, move to child state
                self.currentBoard=child
                break
        #updateUI
        self.UI.updateUI()

        #check if win / terminal dud?
        if self.currentBoard.value == 1:
            self.gameRunning=False
            self.UI.op.set("You win")
        elif self.currentBoard.value == 0:
            self.gameRunning=False
            self.UI.op.set("Draw")            

        if self.gameRunning:
            choices=["That's a tough one","Nice move","Hmmm, let me think..."]
            self.UI.op.set(choice(choices))
            self.UI.after(200, lambda: self.AITurn()) # after 2000ms

    def AITurn(self):
        #now the AI has a turn, use the current boards nextChoice method to find out which child it should use
        self.currentBoard=self.currentBoard.children[self.currentBoard.nextChoice()]

        #check if win / terminal dud?
        if self.currentBoard.value == -1:
            self.gameRunning=False
            self.UI.op.set("I win!")
        elif self.currentBoard.value == 0:
            self.gameRunning=False
            self.UI.op.set("Draw") 

        #updateUI again
        self.UI.updateUI()
        if self.gameRunning:
            self.UI.op.set("It's your turn")


if __name__ == '__main__':
    root=Tk()
    root.title("TicTacToe")
    game = TttGame(root)
    root.mainloop()

0 个答案:

没有答案