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.