def _makeAMove(self, board: str) -> int: # The first few moves are hard-wired into HardWiredPlayer. move = ( super()._makeAMove(board) if emptyCellsCount(board) >= 7 else # minimax returns (val, move, count). Extract move. self.minimax(board)[1]) return move
def _makeAMove(self, prev_move, board: str) -> int: move = ( super()._makeAMove(prev_move, board) if emptyCellsCount(board) >= 7 else # minimax returns (val, move, count). Extract move. self.minimax(board)) return move
def _makeAMove(self, board: str) -> int: """ If this player can win, it will. If not, it blocks if the other player can win. Otherwise it makes a random valid move. :param board: :return: selected move """ (myWins, otherWins, _, _) = self.winsBlocksForks(board) move = choice( [self.findEmptyCell(board, myWin) for myWin in myWins] if myWins else [self.findEmptyCell(board, otherWin) for otherWin in otherWins] if otherWins else list( self.otherMove(board, emptyCellsCount(board)))) return move
def gameLoop(self, isATestGame: bool = True) -> (PlayerDict, str): board = NEWBOARD # X always makes the first move. currentPlayerDict: PlayerDict = self.XDict winnerDict: Optional[PlayerDict] = None done = False while not done: player: Player = currentPlayerDict['player'] reward: int = currentPlayerDict['cachedReward'] move: int = player.makeAMove(reward, board, isATestGame) (winnerDict, board) = self.step(board, move) done = winnerDict is not None or emptyCellsCount(board) == 0 currentPlayerDict = self.otherDict(currentPlayerDict) # Tell the players the final reward for the game. currentPlayerDict['player'].finalReward( currentPlayerDict['cachedReward']) otherPlayerDict = self.otherDict(currentPlayerDict) otherPlayerDict['player'].finalReward(otherPlayerDict['cachedReward']) return (winnerDict, board)
def step(self, board: str, move: int) -> (Optional[PlayerDict], str): """ Make the move and return (winnerDict, updatedBoard). If no winner, winnerDict will be None. :param board: :param move: :return: (winnerDict, updatedBoard) """ currentPlayerDict: PlayerDict = self.XDict if whoseMove( board) is XMARK else self.ODict otherPlayerDict: PlayerDict = self.otherDict(currentPlayerDict) # The following are all game-ending cases. if not isAvailable(board, move): # Illegal move. currentPlayerDict loses. # Illegal moves should be blocked and should not occur. currentPlayerDict['cachedReward'] = -100 otherPlayerDict['cachedReward'] = 100 print(f'\n\nInvalid move by {currentPlayerDict["mark"]}: {move}.', end='') return (otherPlayerDict, board) updatedBoard = setMove(board, move, currentPlayerDict['mark']) if theWinner(updatedBoard): # The current player just won the game with its current move. currentPlayerDict['cachedReward'] = 100 otherPlayerDict['cachedReward'] = -100 return (currentPlayerDict, updatedBoard) if emptyCellsCount(updatedBoard) == 0: # The game is over. It's a tie. currentPlayerDict['cachedReward'] = 0 otherPlayerDict['cachedReward'] = 0 return (None, updatedBoard) # The game is not over. # Get a reward for extending the game. currentPlayerDict['cachedReward'] = 1 return (None, updatedBoard)
def makeAndEvaluateMove(self, board: str, move: int, mark: str, count: int) -> Tuple[int, int, int]: """ Make the move and evaluate the board. :param board: :param move: :param mark: :param count: A longer game is better. :return: 'X' is maximizer; 'O' is minimizer """ boardCopy = setMove(board, move, mark) winner = theWinner(boardCopy) (val, nextCount) = ( (1, count) if winner == XMARK else (-1, count) if winner == OMARK else # winner == None. Is the game a tie because board is full? (0, count) if emptyCellsCount(boardCopy) == 0 else # The game is not over. Minimax is is called as the argument to this lambda function. # Minimax returns (val, move, count). Select and return val and count. # move is the next player's best move, which we don't return. (lambda mmResult: (mmResult[0], mmResult[2]))(self.minimax(boardCopy, count))) return (val, move, nextCount)
def _makeAMove(self, board: str) -> int: (myWins, otherWins, myForks, otherForks) = self.winsBlocksForks(board) availCorners = [pos for pos in CORNERS if isAvailable(board, pos)] availCenter = [pos for pos in [CENTER] if isAvailable(board, pos)] availSides = [pos for pos in SIDES if isAvailable(board, pos)] myOppositeCorners = [ pos for pos in CORNERS if isAvailable(board, pos) and board[oppositeCorner(pos)] == self.myMark ] move = choice( [self.findEmptyCell(board, myWin) for myWin in myWins] if myWins else [self.findEmptyCell(board, otherWin) for otherWin in otherWins] if otherWins else myForks if myForks else # If emptyCellsCount(board) == 6, I'm playing 'O'. If a diagonal is XOX, don't take corner. availSides if board[CENTER] == self.myMark and emptyCellsCount(board) == 6 else otherForks if otherForks else availCorners if availCorners and self.myMark == 'X' and len(availCorners) % 2 == 0 else availCenter if availCenter else myOppositeCorners if board[CENTER] == self.opMark and myOppositeCorners else availCorners if availCorners else validMoves(board)) return move
def printQTable(self) -> NoReturn: print(f'\n\nQTable contains {len(self.qTable)} entries.') for (qBoard, qValuesDicts) in sorted(self.qTable.items(), key=lambda bv: 9 - emptyCellsCount(bv[0])): self.printQValuesForPattern(qBoard, qValuesDicts)