我一直在讨论这个问题已经有一个星期左右的时间了,但现在却无法弄清楚为什么当我"重置"时,我无法正常重置回根状态。我的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()