Python Forum
Adding a single player mode to my wxOthello game
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Adding a single player mode to my wxOthello game
#19
It seems all I've managed to do was make the code look nicer and get rid of some repetition. Many of the same problems still exist. I suspected that checks and score changes designed to help the bot at earlier stages of the game were interfering with later gameplay and vice versa. Basically, the score value that I was getting from the previous version of the scoreGameState method was garbage but this time, it seems like the bot just calls assessMidGame and assessLateGame methods when it's still early game. I have absolutely no idea how getStage could be screwing this up. It's simple. If there are no valid moves in zone 1, tell the caller it's time to move on the zone 2, and if there are no valid moves in zone 2, tell the caller it's time to move on to zone 3. I know I still haven't implemented minMax yet, but I should at least start to see the bot making somewhat sensible moves. It still runs out of the center when it's still early game and dives into the corners at the first opportunity.

here's my code:
class PlayerBot:

    """
    Instances of this class play as player 2 in single player mode.
    class PlayerBot:
        __init__(self, parent, difficulty)
        parent - an OthelloGameFrame instance
        difficulty - an integer between 0 and 100
    """
    bdCode = 0 # Constants for encoding empty board spaces, player 1 and player 2 in an internal
    p1Code = 1 # representation of the current game state.
    p2Code = 2
    # Constants for early game, mid game and late game used by the scoring heuristic.
    ST_EARLY = 3
    ST_MID = 4
    ST_LATE = 5
    # Constants for top, left, right, and bottom used by the trap and entrance detection and assessment
    # methods.
    TOP = (0, 2)
    LEFT = (2, 0)
    RIGHT = (2, 6)
    BOTTOM = (6, 2)

    def __init__ (self, parent, difficulty):
        self.parent = parent
        self.difficulty = difficulty
        self.gameState = self.getInitialGameState()
        # A list of moves potentially detrimental to the bots midgame.
        self.movesDetrimentalToMidgame = []

    def getInitialGameState (self):
        """
        PlayerBot.getInitialGameState (self):
        Returns 8 integer lists nested inside a list representing the initial state of a game
        of Othello, where 0 is an empty space, 1 is player 1, and 2 is player 2.
        """
        gameState = []
        for row in range(8):
            currentRow = []
            for col in range(8):
                currentRow.append(self.bdCode)
            gameState.append(currentRow)
            currentRow = []
        gameState[2][2] = self.p2Code
        gameState[2][3] = self.p1Code
        gameState[3][2] = self.p1Code
        gameState[3][3] = self.p2Code

        return gameState

    def makeMove(self):
        """
        PlayerBot.makeMove (self)
        This method makes a move in its parent as player2 and surrenders its move to player 1 if there are no
        moves available to it.
        """
        self.updateGameState() # Update the objects internal representation of the current state of the game.
        
        validMoves = self.getAllValidMoves(self.gameState, self.p2Code, self.p1Code)

        if validMoves == []: # If there are no moves available to the bot, give player 1 its move.
            self.parent.player1ButtonClicked()
            return

        if random.randint(0, 100) <= self.difficulty: # This outer if statement is used to make the occasional 'mistake'; a move
                                                      # selected randomly from the set of all valid moves. The frequency of these
                                                      # 'mistakes' is determined by the difficulty setting.
            bestMove = validMoves[0]
            bestScore = -inf
            for move in validMoves:
                _, gameStateAfterMove = self.scoreMove(move, self.gameState, self.p2Code, self.p1Code)
                moveScore = self.scoreGameState(gameStateAfterMove)
                if moveScore > bestScore:
                    bestMove = move
                    bestScore = moveScore
                    print(bestScore)
            print("")

            self.parent.gameboardButtonClicked(row = bestMove[0], col = bestMove[1])
            return

        else:
            # Make a 'mistake'.
            randomMove = validMoves[random.randint(0, len(validMoves) - 1)]
            self.parent.gameboardButtonClicked(row = randomMove[0], col = randomMove[1])

        self.updateGameState()

    def getAllValidMoves (self, gameState, me, opponent):
        """
        PlayerBot.getAllValidMoves(self, gameState, me, opponent)
        Return a list of all moves which are valid for the player 'me'
        in the given game state.
        """
        validMoves = []
        for row in range(8):
            for col in range(8):
                if self.isValidMove(row, col, me, opponent, gameState): validMoves.append( (row, col) )

        return validMoves

    def scoreGameState (self, gameState):
        """
        PlayerBot.scoreGameState(self, gameState)
        Used by the minMax algorithm to score a game state using a series of heuristics,
        returning positive score values to game states which are favorable to the bot and
        negative score values for game states which are unfavorable.
        """
        score = 0 # Initialize score to zero.
        me = self.p2Code        # I use the terms 'me' and 'opponent' in this function
        opponent = self.p1Code  # for code clarity. These variables serve no other purpose.
        validMoves = self.getAllValidMoves(gameState, me, opponent)
        gameStage = self.getStage(gameState)
        # Penalize actions by the bot which result in its options being limited.
        if len(validMoves) <= 2: score -= 300_000
        # If I don't have any valid moves:
        if len(validMoves) == 0:
            # Check whether the game has reached a terminal state:
            if len(self.getAllValidMoves(gameState, opponent, me)) == 0:
                myScore = 0     # Find out who won.
                oppScore = 0
                for row in range(8):
                    for col in range(8):
                        if gameState[row][col] == me: myScore += 1
                        if gameState[row][col] == opponent: oppScore += 1
                if myScore > oppScore: return 1_000_000_000_000     # If the bot won in this scenario:
                if myScore == oppScore: return 0                    # If the game was a draw:
                if myScore < oppScore: return -1_000_000_000_000    # If the bot lost in this scenario:

            # If the player has options and the bot has none:
            else:
                score -= 999_999
                
                if gameStage == self.ST_EARLY:
                    score += self.assessEarlyGame(gameState)

                elif gameStage == self.ST_MID:
                    score += self.assessMidGame(gameState)

                elif gameStage == self.ST_LATE:
                    score += self.assessLateGame(gameState)

                return score

        # Determine whether the game state is early game, midgame, or late game.
        
        if gameStage == self.ST_EARLY:
            score += self.assessEarlyGame(gameState)

        elif gameStage == self.ST_MID:
            score += self.assessMidGame(gameState)

        elif gameStage == self.ST_LATE:
            score += self.assessLateGame(gameState)

        return score

    def assessEarlyGame (self, gameState):
        """
        PlayerBot.assessEarlyGame(self, gameState)
        This method is used by scoreGameState to compute a score for early game.
        """
        # Scores range from 100_000 to 150_000
        score = 0
        me = self.p2Code
        opponent = self.p1Code
        # Check whether the bot placed/was forced to place any pieces outside the center of the board.
        for row in range(8):
            for col in range(8):
                if row not in range(2, 6) and col not in range(2, 6):
                    if gameState[row][col] == me: score -= 150_000
                    # If the bot forced the player to place outside the center of the board, that's a good
                    # thing.
                    if gameState[row][col] == opponent: score += 150_000

        # Check whether the bot placed any pieces in the inside corners.
        if gameState[2][2] == me: score += 150_000
        if gameState[2][5] == me: score += 150_000
        if gameState[5][2] == me: score += 150_000
        if gameState[5][5] == me: score += 150_000

        # Check whether the player placed any pieces in the inside corners.
        if gameState[2][2] == opponent: score -= 150_000
        if gameState[2][5] == opponent: score -= 150_000
        if gameState[5][2] == opponent: score -= 150_000
        if gameState[5][5] == opponent: score -= 150_000

        # Check whether the bot can place any pieces in the inside corners.
        if self.isValidMove(2, 2, me, opponent, gameState): score += 110_000
        if self.isValidMove(2, 5, me, opponent, gameState): score += 110_000
        if self.isValidMove(5, 2, me, opponent, gameState): score += 110_000
        if self.isValidMove(5, 5, me, opponent, gameState): score += 110_000

        # Check whether the player can place any pieces in the inside corners.
        if self.isValidMove(2, 2, opponent, me, gameState): score -= 110_000
        if self.isValidMove(2, 5, opponent, me, gameState): score -= 110_000
        if self.isValidMove(5, 2, opponent, me, gameState): score -= 110_000
        if self.isValidMove(5, 5, opponent, me, gameState): score -= 110_000

        score += self.evaluateCornerAreas(gameState)

        return score

    def assessMidGame (self, gameState):
        """
        PlayerBot.assessMidGame(self, gameState)
        This method is used by scoreGameState to evaluate games which have progressed to the middle stage.
        """
        # Scores range from 50_000 to 100_000.
        score = 0
        me = self.p2Code
        opponent = self.p1Code
        # Check whether the bot set any traps and add a score appropriate to the quality of the trap.
        if self.isTrap(self.TOP, me, opponent, gameState):
            score += self.evaluateTrap(self.TOP, me, opponent, gameState)
            
        if self.isTrap(self.LEFT, me, opponent, gameState):
            score += self.evaluateTrap(self.LEFT, me, opponent, gameState)
            
        if self.isTrap(self.RIGHT, me, opponent, gameState):
            score += self.evaluateTrap(self.RIGHT, me, opponent, gameState)
            
        if self.isTrap(self.BOTTOM, me, opponent, gameState):
            score += self.evaluateTrap(self.BOTTOM, me, opponent, gameState)

        # Check whether the player set any traps and subtract a score appropriate to the quality of the trap.
        if self.isTrap(self.TOP, opponent, me, gameState):
            score -= self.evaluateTrap(self.TOP, opponent, me, gameState)
            
        if self.isTrap(self.LEFT, opponent, me, gameState):
            score -= self.evaluateTrap(self.LEFT, opponent, me, gameState)
            
        if self.isTrap(self.RIGHT, opponent, me, gameState):
            score -= self.evaluateTrap(self.RIGHT, opponent, me, gameState)
            
        if self.isTrap(self.BOTTOM, opponent, me, gameState):
            score -= self.evaluateTrap(self.BOTTOM, opponent, me, gameState)

        # Check whether the bot created any entrances and add a value appropriate to the quality of the entrance.
        # An entrance is a way to enter the buffer around the corner without fear of the player taking the corner.
        # Filling an edge is always beneficial whether it's a proper entrance or not, so a lesser value is added
        # to score if the edge is filled.
        if self.isEntrance(self.TOP, me, opponent, gameState):
            score += self.evaluateEntrance(self.TOP, me, opponent, gameState)
        elif gameState[0][2] == me and gameState[0][3] == me and gameState[0][4] == me and gameState[0][5] == me:
            score += 50_000

        if self.isEntrance(self.RIGHT, me, opponent, gameState):
            score += self.evaluateEntrance(self.LEFT, me, opponent, gameState)
        elif gameState[0][2] == me and gameState[0][3] == me and gameState[0][4] == me and gameState[0][5] == me:
            score += 50_000

        if self.isEntrance(self.LEFT, me, opponent, gameState):
            score += self.evaluateEntrance(self.RIGHT, me, opponent, gameState)
        elif gameState[0][2] == me and gameState[0][3] == me and gameState[0][4] == me and gameState[0][5] == me:
            score += 50_000

        if self.isEntrance(self.BOTTOM, me, opponent, gameState):
            score += self.evaluateEntrance(self.BOTTOM, me, opponent, gameState)
        elif gameState[0][2] == me and gameState[0][3] == me and gameState[0][4] == me and gameState[0][5] == me:
            score += 50_000

        # Check whether the player created any entrances or filled any edges and subtract value from score accordingly.
        if self.isEntrance(self.TOP, opponent, me, gameState):
            score -= self.evaluateEntrance(self.TOP, opponent, me, gameState)
        elif gameState[0][2] == opponent and gameState[0][3] == opponent and gameState[0][4] == opponent and gameState[0][5] == opponent:
            score -= 50_000

        if self.isEntrance(self.LEFT, opponent, me, gameState):
            score -= self.evaluateEntrance(self.LEFT, opponent, me, gameState)
        elif gameState[2][0] == opponent and gameState[3][0] == opponent and gameState[4][0] == opponent and gameState[5][0] == opponent:
            score -= 50_000

        if self.isEntrance(self.RIGHT, opponent, me, gameState):
            score -= self.evaluateEntrance(self.RIGHT, opponent, me, gameState)
        elif gameState[2][7] == opponent and gameState[3][7] == opponent and gameState[4][7] == opponent and gameState[5][7] == opponent:
            score -= 50_000

        if self.isEntrance(self.BOTTOM, opponent, me, gameState):
            score -= self.evaluateEntrance(self.BOTTOM, opponent, me, gameState)
        elif gameState[7][2] == opponent and gameState[7][3] == opponent and gameState[7][4] == opponent and gameState[7][5] == opponent:
            score -= 50_000

        score += self.evaluateCornerAreas(gameState)

        return score
    def evaluateCornerAreas (self, gameState):
        """
        PlayerBot.evaluateCornerAreas(self, gameState)
        Used by game state scoring methods to assess the state of the corners and the buffers around them.
        """
        score = 0
        me = self.p2Code
        opponent = self.p1Code
        # Dole out massive rewards for capturing the corners and massive penalties for giving the corners to the opponent.

        # Check whether the bot filled any of the corners and add 999_999 to score for each corner it's taken.
        if gameState[0][0] == me: score += 999_999
        if gameState[0][7] == me: score += 999_999
        if gameState[7][0] == me: score += 999_999
        if gameState[7][7] == me: score += 999_999

        # Check whether the player filled any of the corners and subtract 999_999 from score for each corner they've taken.
        if gameState[0][0] == opponent: score -= 999_999
        if gameState[0][7] == opponent: score -= 999_999
        if gameState[7][0] == opponent: score -= 999_999
        if gameState[7][7] == opponent: score -= 999_999

        # Check whether the bot put/was forced to put pieces in any of the corner buffers and subtract 999_999 for each
        # buffer position filled starting with the top left.
        if gameState[0][1] == me: score -= 999_999
        if gameState[1][0] == me: score -= 999_999
        if gameState[1][1] == me: score -= 999_999
        # Top Right:
        if gameState[0][6] == me: score -= 999_999
        if gameState[1][6] == me: score -= 999_999
        if gameState[6][1] == me: score -= 999_999
        # Bottom Left:
        if gameState[6][0] == me: score -= 999_999
        if gameState[6][1] == me: score -= 999_999
        if gameState[7][1] == me: score -= 999_999
        # Bottom Right:
        if gameState[6][6] == me: score -= 999_999
        if gameState[6][7] == me: score -= 999_999
        if gameState[7][6] == me: score -= 999_999

        # Check whether the player put/was forced to put pieces in any of the corner buffers and add 40000 for each buffer
        # position filled starting with the top left.
        if gameState[0][1] == opponent: score += 999_999
        if gameState[1][0] == opponent: score += 999_999
        if gameState[1][1] == opponent: score += 999_999
        # Top Right:
        if gameState[0][6] == opponent: score += 999_999
        if gameState[1][6] == opponent: score += 999_999
        if gameState[6][1] == opponent: score += 999_999
        # Bottom Left:
        if gameState[6][0] == opponent: score += 999_999
        if gameState[6][1] == opponent: score += 999_999
        if gameState[7][1] == opponent: score += 999_999
        # Bottom Right:
        if gameState[6][6] == opponent: score += 999_999
        if gameState[6][7] == opponent: score += 999_999
        if gameState[7][6] == opponent: score += 999_999

        return score

    def isEntrance (self, position, me, opponent, gameState):
        """
        PlayerBot.isEntrance(self, position, me, opponent, gameState)
        This is a boolean method used to determine whether an entrance was created
        at the specified position.
        """
        # How to assess whether a pattern is an entrance is dependent on the position and orientation of the supposed entrance.
        if position == self.TOP:
            if gameState[0][2] == me and gameState[0][3] == me and gameState[0][4] == me and gameState[0][5] == me:
                if self.isValidMove(0, 1, me, opponent, gameState) or self.isValidMove(0, 6, me, opponent, gameState):
                    return True

        elif position == self.LEFT:
            if gameState[2][0] == me and gameState[3][0] == me and gameState[4][0] == me and gameState[5][0] == me:
                if self.isValidMove(1, 0, me, opponent, gameState) or self.isValidMove(6, 0, me, opponent, gameState):
                    return True

        elif position == self.RIGHT:
            if gameState[2][7] == me and gameState[3][7] == me and gameState[4][7] == me and gameState[5][7] == me:
                if self.isValidMove(1, 7, me, opponent, gameState) or self.isValidMove(6, 7, me, opponent, gameState):
                    return True

        elif position == self.BOTTOM:
            if gameState[7][2] == me and gameState[7][3] == me and gameState[7][4] == me and gameState[7][5] == me:
                if self.isValidMove(7, 1, me, opponent, gameState) or self.isValidMove(7, 6, me, opponent, gameState):
                    return True

        else:
            raise ValueError("The position parameter of isEntrance must be a tuple with the values: (0, 2), (2, 0), (2, 6) or (6, 2) or the class constants TOP, LEFT, RIGHT, or BOTTOM!")

        return False

    def evaluateEntrance (self, position, me, opponent, gameState):
        """
        PlayerBot.evaluateEntrance(self, position, me, opponent, gameState)
        This method is used by assessMidGame to evaluate the quality of
        the entrance and return a score indicative of the quality of the same.
        """
        score = 50_000
        # The way the quality of an entrance is evaluated is dependent on its position and orientation.
        if position == self.TOP:
            # We already know by checking whether this is an entrance that it performs the basic function of an entrance,
            # however, it is also possible for an entrance to serve as a trap, provided that the opponent can make a
            # move at either end of the entrance, giving me access to a corner. Here, we check whether that is the case.
            if self.isValidMove(0, 1, opponent, me, gameState) and not self.isValidMove(0, 6, opponent, me, gameState):
                score += 4950
            elif self.isValidMove(0, 6, opponent, me, gameState) and not self.isValidMove(0, 1, opponent, me, gameState):
                score += 4950

        elif position == self.LEFT:
            if self.isValidMove(1, 0, opponent, me, gameState) and not self.isValidMove(6, 0, opponent, me, gameState):
                score += 4950
            elif self.isValidMove(6, 0, opponent, me, gameState) and not self.isValidMove(1, 0, opponent, me, gameState):
                score += 4950

        elif position == self.RIGHT:
            if self.isValidMove(1, 7, opponent, me, gameState) and not self.isValidMove(6, 7, opponent, me, gameState):
                score += 4950
            elif self.isValidMove(6, 7, opponent, me, gameState) and not self.isValidMove(1, 7, opponent, me, gameState):
                score += 4950

        elif position == self.BOTTOM:
            if self.isValidMove(7, 1, opponent, me, gameState) and not self.isValidMove(7, 6, opponent, me, gameState):
                score += 4950
            elif self.isValidMove(7, 6, opponent, me, gameState) and not self.isValidMove(7, 1, opponent, me, gameState):
                score += 4950

        else:
            raise ValueError("The position parameter of evaluateEntrance must be a tuple with the values: (0, 2), (2, 0), (2, 6) or (6, 2) or the class constants TOP, LEFT, RIGHT, or BOTTOM!")

        return score

    def isTrap (self, position, me, opponent, gameState):
        """
        PlayerBot.isTrap(self, position, me, opponent, gameState)
        This is a boolean method used to assess whether there is a trap at the specified position.
        """
        # How to assess whether a pattern of pieces is a trap is dependent on the position and orientation of the supposed trap.
        if position == self.TOP:
            if gameState[0][2] == me and gameState[0][5] == me and (gameState[0][3] == opponent or gameState[0][4] == opponent): return True

        elif position == self.LEFT:
            if gameState[2][0] == me and gameState[5][0] == me and (gameState[3][0] == opponent or gameState[4][0] == opponent): return True

        elif position == self.RIGHT:
            if gameState[2][7] == me and gameState[5][7] == me and (gameState[3][7] == opponent or gameState[4][7] == opponent): return True

        elif position == self.BOTTOM:
            if gameState[7][2] == me and gameState[7][5] == me and (gameState[7][3] == opponent or gameState[7][4] == opponent): return True
        else:
            raise ValueError("The position parameter of isTrap must be a tuple with the values: (0, 2), (2, 0), (2, 6) or (6, 2) or the class constants TOP, LEFT, RIGHT, or BOTTOM!")

        return False

    def evaluateTrap(self, position, me, opponent, gameState):
        """
        PlayerBot.evaluateTrap(self, position, me, opponent, gameState)
        This method is used by assessMidGame to assess the quality of a trap at a
        given position.
        """
        score = 50_000

        # As in the isTrap method, in order to evaluate the quality of the trap, we must
        # know the position of the trap.
        if position == self.TOP:
            # Check whether the trap is complete. An incomplete trap is ineffective but still has
            # the potential to be completed as gameplay progresses.
            if gameState[0][3] != self.bdCode and gameState[0][4] != self.bdCode: score += 2000
            # Check whether the trap is failure protected. If the bot renders it impossible to place
            # a piece in the buffer around the trap, the player cannot force it to do so.
            if gameState[1][2] == me: score += 2500
            if gameState[1][5] == me: score += 2500

        elif position == self.LEFT:
            if gameState[3][0] != self.bdCode and gameState[4][0] != self.bdCode: score += 2000
            if gameState[2][1] == me: score += 2500
            if gameState[5][1] == me: score += 2500

        elif position == self.RIGHT:
            if gameState[3][7] != self.bdCode and gameState[4][7] != self.bdCode: score += 2000
            if gameState[2][6] == me: score += 2500
            if gameState[5][6] == me: score += 2500

        elif position == self.BOTTOM:
            if gameState[7][3] != self.bdCode and gameState[7][4] != self.bdCode: score += 2000
            if gameState[6][2] == me: score += 2500
            if gameState[6][5] == me: score += 2500

        else:
            raise ValueError("The position parameter of evaluateTrap must be a tuple with the values: (0, 2), (2, 0), (2, 6) or (6, 2) or the class constants TOP, LEFT, RIGHT, or BOTTOM!")

        return score

    def assessLateGame (self, gameState):
        """
        PlayerBot.assessLateGame(self, gameState)
        This method is used by scoreGameState to evaluate games which have progressed to late stage.
        """
        return self.evaluateCornerAreas(gameState)

    def getStage (self, gameState):

        for row in range(2, 5):
            for col in range(2, 5):
                if self.isValidMove(row, col, self.p2Code, self.p1Code, gameState): return self.ST_EARLY
                
        for row in range(2, 5):
            for col in range(0, 7):
                if col in range(0, 2) or col in range(6, 8):
                    if self.isValidMove(row, col, self.p2Code, self.p1Code, gameState): return self.ST_MID
        for row in range(0, 7):
            for col in range(2, 5):
                if row in range(0, 2) or row in range(6, 8):
                    if self.isValidMove(row, col, self.p2Code, self.p1Code, gameState): return self.ST_MID

        return self.ST_LATE
                    

    def isValidMove (self, row, col, me, opponent, gameState):
        """
        PlayerBot.isValidMove(self, row, col, me, opponent, gamestate)
        Returns True if the given move is valid for the given gamestate and
        False if the given move is invalid for the given gamestate.
        """
        if gameState[row][col] != self.bdCode:
            return False # If the space where we're trying to move isn't empty, we already know this move is invalid.

        scanningDirections = ((-1, 0), (0, 1), (1, 0), (0, -1), # A series of scanning vectors.
                              (-1, -1), (-1, 1), (1, 1), (1, -1))
        for SDRow, SDCol in scanningDirections: # Iterate over the scanning vectors.
            currentRow = row + SDRow # Start scanning at a position offset from the move by one
            currentCol = col + SDCol # along the current scanning vector.
            sawOpponent = False      # The opponents gamepieces haven't yet been seen on this vector.
            while currentRow in range(0, 8) and currentCol in range(0, 8):
                if gameState[currentRow][currentCol] == self.bdCode: break # If the gamespace is empty, we know there are no pieces to flip on this vector.
                if gameState[currentRow][currentCol] == opponent: sawOpponent = True # The opponents gamepieces have been seen.
                if gameState[currentRow][currentCol] == me and sawOpponent:
                    return True # There are at least pieceses on this vector that can be flipped. The move is valid.
                if gameState[currentRow][currentCol] == me and not sawOpponent: break # There are no pieces to flip along this vector. Proceed to the next.
                
                currentRow += SDRow # Proceed to the next gamespace in the current vector.
                currentCol += SDCol

        return False # If we've fallen out of the vector scanning loop, we know the move is invalid.

    def updateGameState (self):
        """
        PlayerBot.updateGameState(self)
        Synchronizes the objects gameState attribute with the current state of parent.gameboard.
        """
        for row in range(8):
            for col in range(8): # Iterate over the parents gameboard and insert integer values into self.gameState
                                 # corresponding to black pieces, white pieces and empty spaces.
                if self.parent.gameboard[row][col].GetBackgroundColour() == self.parent.bgAndBoardColor:
                    self.gameState[row][col] = self.bdCode
                elif self.parent.gameboard[row][col].GetBackgroundColour() == self.parent.player1Color:
                    self.gameState[row][col] = self.p1Code
                elif self.parent.gameboard[row][col].GetBackgroundColour() == self.parent.player2Color:
                    self.gameState[row][col] = self.p2Code

    def scoreMove (self, possibleMove, gameState, me, opponent):
        """
        PlayerBot.scoreMove (self, possibleMove, gameState, me, opponent)
        Calculate the number of pieces captured by a given move in a given game state
        and return a tuple containing the number of captures at index 0 and the state
        of the game after the move at index 1
        """
        gameState = gameState.copy() # We wouldn't want to alter the value of self.gameState now, would we?
        row, col = possibleMove # Unpack the move parameter to make the code more readable.
        moveScore = 1 # We already know that we at least have the grid space where we placed our piece.
        scanningDirections = ((-1, 0), (0, 1), (1, 0), (0, -1), # A series of scanning vectors
                              (-1, -1), (-1, 1), (1, 1), (1, -1))
        for SDRow, SDCol in scanningDirections: # Scann along all 8 vectors.
            currentRow = row + SDRow # Start at a position offset from the position of the move along the current
            currentCol = col + SDCol # scanning vector.
            sawOpponent = False # None of the opponents gamepieces have been seen on the current scanning vector at this time.
            canCountPieces = False # No row of my opponents pieces with another of my pieces at the other end has been seen on
                                   # on this scanning vector at this time.
            while currentRow in range(0, 8) and currentCol in range(0, 8):
                if gameState[currentRow][currentCol] == self.bdCode: break # If we see an empty space, we know we can't flip pieces on this vector.
                if gameState[currentRow][currentCol] == self.p1Code: sawOpponent = True
                if gameState[currentRow][currentCol] == me and sawOpponent:
                    canCountPieces = True # We now know we can flip pieces on this vector.
                    break # There is no need to continue scanning this vector.
                if gameState[currentRow][currentCol] == me and not sawOpponent: break # If I see another of my pieces without seeing an opponents piece,
                                                                                      # there are no pieces to flip on this vector.
                currentRow += SDRow
                currentCol += SDCol

            currentRow = row + SDRow
            currentCol = col + SDCol
            while canCountPieces and currentRow in range(0, 8) and currentCol in range(0, 8):
                if gameState[currentRow][currentCol] == opponent:
                    gameState[currentRow][currentCol] = me # Flip the pieces on this vector and increment the move score.
                    moveScore += 1
                elif gameState[currentRow][currentCol] == me:
                    break
                
                currentRow += SDRow
                currentCol += SDCol

        return moveScore, gameState # Return the tuple
I'm going to try to fix the getStage method and adding rewards for moves which can help the bot toward strategic goals, but any advice would be greatly appreciated.
Reply


Messages In This Thread
RE: Adding a single player mode to my wxOthello game - by keames - Apr-26-2019, 06:41 PM

Possibly Related Threads…
Thread Author Replies Views Last Post
  [PyGame] adding mouse control to game flash77 7 708 May-21-2024, 01:05 AM
Last Post: XavierPlatinum
  get a game to run in full screen mode agencyclearly 1 548 May-12-2024, 11:23 AM
Last Post: menator01
  Creating a “Player” class, and then importing it into game onizuka 4 3,199 Sep-01-2020, 06:06 PM
Last Post: onizuka
  Adding an inventory and a combat system to a text based adventure game detkitten 2 7,091 Dec-17-2019, 03:40 AM
Last Post: detkitten
  Adding persistent multiple levels to game michael1789 2 2,516 Nov-16-2019, 01:15 AM
Last Post: michael1789
  Can a player play game created with Python without installing Python? hsunteik 3 5,474 Feb-23-2017, 10:44 AM
Last Post: Larz60+

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020