Python类自变量被覆盖

问题描述 投票:0回答:0

我正在制作一个递归的井字游戏搜索播放器,并且有一个名为 board 的对象,每次我模拟一个新的棋盘时都会创建它。但是,原始板正在被未来的板覆盖。我不确定这是怎么发生的。

我有下面重要的代码。发生的事情是这样的: 我启动 ai 播放器并调用 getMove(),它应该返回一个数字。 在 getMove 中,我启动了一个递归 ai,它在启动时开始构建每个棋盘状态的数组。该列表是全局的,因此 build frontier 不会返回任何内容。它只说“如果这是一个结束状态,x/o/tie,然后将它添加到列表中。否则,制作一个新的 ai 播放器,这个位置已填充并检查结束状态,等等。没有在哪里它引用了 aiPlayer.board,但不知何故它被覆盖了。

当 ai.getMove 返回一个随机移动时,(找到棋盘上的所有点并从这些点中随机返回一个)线

print("the game board this sees")
    self.board.printBoard()

打印我第一次调用 ai 播放器时输入的棋盘。如果我标记一个点,然后调用 ai 来决定下一个点,这个打印函数会在我标记一个点之后但在它被填充之前打印板。这很好。

然而,当使用实际算法时,同一行打印一个填充板。我从不触及 ai 的 self.board 对象来改变它,我只复制 c_board 对象并从中构建迭代。

我的问题是:虽然我从来没有碰过self.board,为什么它被覆盖了?我稍后制作的 c_boards 是指向同一个原始板的指针吗?到底是怎么回事? (PS. 很抱歉称它为 ai,这只是一次蛮力尝试。我试图在没有任何其他人方法教程的情况下了解我是如何思考和实施它的。{但我一直在寻找解决方案来解释代码的原因是越野车,我的第一个答案不是转向这个论坛。谢谢你的帮助!})

    global overallFrontier
    overallFrontier = []
    
    class aiPlayer:
      def __init__(self, field, currentTurn,firstPlayer,width,path,aiMode):
        self.board = c_board(False,field,width,path,currentTurn) # <-- this board is getting overwritten
        self.mode = aiMode
        self.firstPlayer = firstPlayer
    
        overallFrontier = []
   

     def getMove(self):
           
            self.bestTileNum = self.smartSearchGetMove()
            print("getMove: ", self.bestTileNum)
            print("the game board this sees")
            self.board.printBoard() # <-- When I print this, I don't see the original board.
        #instead, I see a hypothetical board created later
            #return 
            return self.bestTileNum #int
   

     def smartSearchGetMove(self): 
                recurse_buildFrontier(self.board.field,self.board.width,self.board.path,self.board.currentTurn,0)
          return bestNumber #any number 1-9, the implementation isn't where my issue is
#Sorry about the weird indenting, I can't hit tab. The actual code  # lines up properly
        
    
   

 class recurse_buildFrontier:
          def __init__(self,field,width,path,currentTurn,iteration):
            print("RecurseAI: ", iteration)
            self.board = c_board(False,field,width,path,currentTurn)
            self.iteration = iteration
            self.buildFrontier()
          def buildFrontier(self):  
            print("================ BuildFrontierCall =====================")
            print("iteration: ", self.iteration + 1)
            print("frontier: ", overallFrontier)
        
            for row in self.board.field:
              for spot in row:
                if(spot in range(1,11) and spot not in self.board.path):#the not path bit is implied, just for clarity its written
                  spotFrontierBoard = c_board(False,self.board.field,self.board.width,self.board.path,not self.board.currentTurn)
                  spotFrontierBoard.fillBoard(spot)
                  
                  print("-- spot: ", spot)
                  print("! spotFrontierBoard print !")
                  spotFrontierBoard.printBoard()
                  print("! self board print !")
                  self.board.printBoard()
        
                  spotFrontierBoard.evaluatePosition()
                  evaluate = spotFrontierBoard.winState
                  evaluatePrinter = "X" if evaluate == c_board.winType["x"] else "O" if evaluate == c_board.winType["o"] else "TIE" if evaluate == c_board.winType["tie"] else "none" if evaluate == c_board.winType["none"] else "evaluate error"
                  print("evaluate: ",evaluatePrinter )
                  if (evaluate in [c_board.winType["x"],c_board.winType["o"],c_board.winType["tie"]]):
                    overallFrontier.append(spotFrontierBoard)
                  else:
                    recurse_buildFrontier(spotFrontierBoard.field,self.board.width,spotFrontierBoard.path,not self.board.currentTurn,self.iteration+1).buildFrontier()
        
            print("++++++++++ END BuildFrontierCall +++++++++++")
              
          

c_board类的代码是:

from math import trunc

global boardWidth 
boardWidth = 0

class c_board:
  firstPlayer = True  
  winType = {
      "x": 0,
      "tie": 2,
      "o": 4,
      "none": -2,
    }#first player win type
  def __init__(self,isNewField,ifNewFieldThisIsEmptyListElseThisIsBoardField,widthZEROIfNewBoard,path,newTurnNumber):
    
    self.path = path
    self.currentTurn = newTurnNumber #not lastTurn
    boardWidth = widthZEROIfNewBoard
    
    if boardWidth == 0:
      self.width = self._getBoardSize()
    else:
      self.width = boardWidth
      
    if isNewField:
      self.field = self.newField()
    else:
      self.field = ifNewFieldThisIsEmptyListElseThisIsBoardField
   
    self.winState = self.evaluatePosition()
  
  def printBoard(self):
    spotNum = 1
    for column in self.field:
      line = ""
      spotNum = 1
      for val in column:
        if(val == "x" or val == "o"):
          val = val.upper()
        line += str(val)
        if(spotNum  != 3):
          line += " | "
        spotNum +=1
      print("  ",line)
    print("path: ",self.path)


  def evaluatePosition(self):
    #returns either 0 1 or 2 depending on the winType(loose tie or win) 
    hitNum = False
    for i in self.field:
      for j in i:
        for k in range(self.width * self.width):
          if (str(j) == str(k)):
            hitNum = True

    #          x1 x2 x3  o1 o2 o3
    downXO = [[0, 0, 0], [0, 0, 0]]
    for column in self.field:
      #count accross
      
      if(column.count("x") == self.width):
        print("x, accross")
        self.winState = self.winType["x"]  
        return self.winState
      
      elif(column.count("o") == self.width):
        print("o, accross")
        self.winState = self.winType["o"]
        return self.winState
     
      for i in range(self.width):
        if (column[i] == "x" or column[i] == "o"):
          #count down
          xo = 0 if (column[i] == "x") else 1
          downXO[xo][i] += 1
          #down win
          if (downXO[xo][i] == self.width):
            if(not bool(xo) == self.currentTurn): #In xo, 0 is x, whereas everywhere else True(num>0) is x
              print("down x")
              self.winState = self.winType["x"]   
              return self.winState
            else:
              print("down o")
              self.winState = self.winType["o"]
              return self.winState

    #count diagonal tl to br
    centerTile = self.field[trunc(self.width / 2)][trunc(self.width / 2)]
    if (str(centerTile) not in [
        "1", "2", "3", "4", "5", "6", "7", "8"
    ]):  #TODO: make this a loop iterable generated by the boardsize
      xo = 0 if (centerTile == "x") else 1

      #this first i loop section does top left to bottom right, as x and y positions are the same number in this order
      Lxo = [0, 0]
      for i in range(self.width):
        if (
            Lxo[0] > 0 and Lxo[1] > 0
        ):  #if the diagonal is already not pure, aka there are both x and o on the diagonal
          break
        if (self.field[i][i] == centerTile):
          Lxo[xo] += 1
          if (Lxo[xo] == self.width):
            if(not bool(xo) == self.currentTurn):
              print("tl acc x")
              self.winState = self.winType["x"]
              return self.winState
            else:
              print("tl acc o")
              self.winState = self.winType["o"]
              return self.winState

      Rxo = [0, 0]
      rlistCoords = list(zip(range(self.width), range(self.width - 1, -1, -1)))
      for item in rlistCoords:
        if (self.field[item[0]][item[1]] == centerTile):
          Rxo[xo] += 1
        if (Rxo[xo] == self.width):
          if(not bool(xo) == self.currentTurn):
            self.winState = self.winType["x"]
            return self.winState
          else:
            self.winState = self.winType["o"]
            return self.winState
    if (hitNum == False):
      self.winState = self.winType["tie"]
      return self.winState
    self.winState = self.winType["none"]
    return self.winState
    
  def fillBoard(self,tile):

    i = 0
    j = 0
    for column in self.field:
      for val in column:
        if (val == tile):
          self.field[i][j] = ("x" if (self.currentTurn == self.firstPlayer) else "o")
          self.currentTurn = not self.currentTurn
          self.path.append(tile)
          return self.field
        j += 1
      j = 0
      i += 1
    return False
    
  def resetBoard(self):
    self.field = self.newField()
    self.winState = self.winType["none"]
    self.currentTurn = True
    self.path = []
    
  def newField(self):
    self.field = [[int((1 + i + self.width * j)) for i in range(self.width)]
                  for j in range(self.width)]
    return self.field

  def _getBoardSize(self):
    #in init, replace width from const to self.getBoardSize()
    while (True):
      try:
        boardSize = int(input("board square size? 3 is best, odd only: "))
        if (boardSize % 2 == 1):
          break
      except:
        continue
    #return boardSize #have some issues with larger boards, gotta fix and then this is possible
    return boardSize

以防万一,这是 main.py

#V3 might be working better then v2. All changes are in the build frontier

import sys, os, time

from ai import *
from board import *


# minimax version

#features for the future:
#     - use pygaem to display (might not work on person running code's computer)
#     - have winning image sequence
#     - when there are no possible moves that can win, tie 


class TicTacToeGame:
  validationInputs = {
    "y": True,
    "yes": True,
    "sure": True,
    "of course": True,
    "definatly": True,
    "affirmative": True,
    "yup": True,
    "ya": True,
    "yuh": True,
    "certainly": True,
    "n": False,
    "no": False,
    "nope": False,
    "nadda": False,
    "never": False,
  }
 
  def __init__(self):
    self.board = c_board(True,[],0,[],True)
    print("press anything to start a game")
    input("> ")
    self.getPlayMode()
    self.startGame()
  
  def startGame(self):
    #os.system("clear")
    
    self.board.resetBoard()

    self.playGame()

  
  def playGame(self):
    endGameStates = [c_board.winType["x"],c_board.winType["o"],c_board.winType["tie"]]
    self.display()
    if (self.board.winState in endGameStates):
    
      valid = False
      while (not valid):
        try:
          print(" continue on same game mode:       1")
          print(" try playing a different gamemode: 2")
          try:
            answer = int(input(">"))
          except:
            raise Exception("not a number")
          if (answer == 2):
            valid = True
            self.getPlayMode()
          elif (answer == 1):
            valid = True
          else:
            valid = False
            raise Exception("answer is not 1 or 2")
        except Exception as e:
          print(e)
     # os.system("clear")
      self.startGame()
    elif(self.board.winState == c_board.winType["none"]):
      if (self.playAi == False):
        self.playerMark()
      else:
        if (self.playerFirst == self.board.currentTurn):
          print("player")
          self.playerMark()
        else:
          print("ai")

          aiMove = aiPlayer(self.board.field,self.board.currentTurn,self.playerFirst,self.board.width,[],self.aiMode).getMove()
          print('aiMove is', aiMove)
          
          self.board.fillBoard(aiMove)

      self.board.winState = self.board.evaluatePosition()
      print("wS: ",self.board.winState)
      self.playGame()
  
  
  def printWinner(self):
    if (self.playAi):
      if((self.playerFirst == self.board.currentTurn)):
        print("you win")
      else:
        print("ai wins")
    elif(not self.playAi):
      if(self.board.currentTurn):
        print("p1 wins")
      else:
        print("p2 wins")
 
  def display(self):
    #os.system("clear")
    self.board.printBoard()
    print(self.board.path)
    if(self.board.winState == c_board.winType["tie"]):
      print("tie")
    elif(self.board.winState == c_board.winType["x"] or self.board.winState == c_board.winType["o"]):
      self.board.currentTurn = not self.board.currentTurn
      self.printWinner()
      self.board.newField()
  
 
  def playerMark(self):  #either x or o
    self.foundTile = False
    valid = False
    while(not valid):
      try:
        answer = input("tileNum: ")
        if(answer == "e"):
          self.resetScreen()
          self.board.newField()
          break
          
        tileNum = int(answer)
        for row in self.board.field:
          for val in row:
            if val == tileNum:
              valid = True
        
        if(not valid):
          raise Exception("no tile found")
      except Exception as e:
        print(e)
      self.board.fillBoard(tileNum)
    
  def resetScreen(self):
    while(True):
      print("sit here to relax")
      time.sleep(1)
      print("we are zen")
      time.sleep(1)
      print("press enter to start a new game")
      time.sleep(1)
      print("but take your time")
      input(">")
      self.startGame()
   
    
  def getPlayMode(self):
    self.board.resetBoard()
    allValid = False
    while (allValid == False):
      try:
        print("play against AI? y/n")
        answer = input("> ").lower().strip()

        if (answer not in self.validationInputs):
          raise Exception("not a valid input")

        self.playAi = self.validationInputs[answer]
        if (not self.playAi):
          allValid = True
          break

        
        print("AI Mode: 1) next!")
        print("         2) random!")
        print("         3) Smart")
        answer = input(">").strip()
        if(answer not in ["1","2","3"]):
          raise Exception("not a valid input")
        self.aiMode = int(answer)
        
        print("play first? y/n")
        answer = input("> ").lower().strip()
        if (answer not in self.validationInputs):
          raise Exception("not a valid input")

        self.playerFirst = self.validationInputs[answer]
        allValid = True
      except Exception as e:
        print(e)


def main():
  global game
  game = TicTacToeGame()
  
main()

并且(可能是不必要的)这是整个计算机播放器代码,而不是我选择显示的内容:

from board import *
import random

global overallFrontier
overallFrontier = []

global listOfAiPlayers
listOfAiPlayers = []

class aiPlayer:
  def __init__(self, field, currentTurn,firstPlayer,width,path,aiMode):
    self.board = c_board(False,field,width,path,currentTurn)
    self.mode = aiMode
    self.firstPlayer = firstPlayer
    overallFrontier = []
     
  def getFirstOpenSpotFromFieldForBasicCompPlayer(self):
    for row in self.board.field:
      for item in row:
        if(item not in ["x","o"]):
          return item
  def playRandomMove(self):
    listOfSpaces = []
    for row in self.board.field:
      for item in row:
        if(item not in ["x","o"]):
          listOfSpaces.append(item)
    if(len(listOfSpaces)>0):
      return random.choice(listOfSpaces)
    else:
      return
  
  def getMove(self):
    if(self.mode == 1):
      self.bestTileNum = self.getFirstOpenSpotFromFieldForBasicCompPlayer()
    if(self.mode == 2):
      self.bestTileNum = self.playRandomMove()
    if(self.mode == 3):
      self.bestTileNum = self.smartSearchGetMove()
    print("getMove: ", self.bestTileNum)
    print("the game board this sees")
    self.board.printBoard()
    #return 
    return self.bestTileNum #int
    #TODO: after the best case is found, return the first number that leads down that case
  
  def smartSearchGetMove(self): 
    recurse_buildFrontier(self.board.field,self.board.width,self.board.path,self.board.currentTurn,0)

    print(" ***** ")
    print(" ")
    print(" ")
    for board in overallFrontier:
      
      print(board)

      print(" ")
      print(" ")
      print(" ")
    print(" ***** ")
    
    return self.playRandomMove()
    
    """for ai in listOfAiPlayers:
      print("ai address: ", ai)
      
    print("Final frontier: ")
    
    for board in overallFrontier:
      board.printBoard()
      
    if(not self.board.currentTurn):
      print("Optimize for X")
      
    else:
      print("Optimize for O")
"""

    #TODO: optimizedBoard.path
   

class recurse_buildFrontier:
  def __init__(self,field,width,path,currentTurn,iteration):
    print("RecurseAI: ", iteration)
    self.board = c_board(False,field,width,path,currentTurn)
    self.iteration = iteration
    self.buildFrontier()
  def buildFrontier(self):  
    print("================ BuildFrontierCall =====================")
    print("iteration: ", self.iteration + 1)
    print("frontier: ", overallFrontier)

    for row in self.board.field:
      for spot in row:
        if(spot in range(self.board.width * self.board.width) and spot not in self.board.path):#the not path bit is implied, just for clarity its written
          spotFrontierBoard = c_board(False,self.board.field,self.board.width,self.board.path,not self.board.currentTurn)
          spotFrontierBoard.fillBoard(spot)
          
          print("-- spot: ", spot)
          print("! spotFrontierBoard print !")
          spotFrontierBoard.printBoard()
          print("! self board print !")
          self.board.printBoard()

          spotFrontierBoard.evaluatePosition()
          evaluate = spotFrontierBoard.winState
          evaluatePrinter = "X" if evaluate == c_board.winType["x"] else "O" if evaluate == c_board.winType["o"] else "TIE" if evaluate == c_board.winType["tie"] else "none" if evaluate == c_board.winType["none"] else "evaluate error"
          print("evaluate: ",evaluatePrinter )
          if (evaluate in [c_board.winType["x"],c_board.winType["o"],c_board.winType["tie"]]):
            overallFrontier.append(spotFrontierBoard)
          else:
            recurse_buildFrontier(spotFrontierBoard.field,self.board.width,spotFrontierBoard.path,not self.board.currentTurn,self.iteration+1).buildFrontier()

    print("++++++++++ END BuildFrontierCall +++++++++++")
      
   

此屏幕截图显示了debug output where each board has a unique id, which increments each time a new board object is created. We can see that the board of the first iteration of the search algorighm (referenced by self.board) is overwritten by the board that is created when exploring paths(referenced as iterationBoard

中的问题

recurse_buildFrontier类中的这段代码显示:

code that prints showing the issue

self.board 应该看起来像迭代板之前的移动,因为迭代板正在探索 self.board 中的每个开放点。迭代板取一个点并填充它,但两者都打印出相同的东西。

调用并进行此更改的 fillBoard 函数仅引用 self.field

def fillBoard(self,tile):
    i = 0
    j = 0
    for column in self.field:
      for val in column:
        if (val == tile):
          self.field[i][j] = ("x" if (self.currentTurn == self.firstPlayer) else "o")
          self.currentTurn = not self.currentTurn
          self.path.append(tile)
          return self.field
        j += 1
      j = 0
      i += 1
    return False
python class overwrite
© www.soinside.com 2019 - 2024. All rights reserved.