def valueOfMove(self, board, move): board = self.game.preRotate(move, board) testGame = GameState() testGame.setBoard(board) testGame.setBoard(testGame.copyArr()) value = 0 # store previous information oldScore = testGame.getScore() oldMaxTile = testGame.getMaxTile() testGame.executeMove(Move.down) newScore = testGame.getScore() value += newScore - oldScore # check if the largest tile is in a corner after the move newMaxTile = testGame.getMaxTile() for corner in self.fourCorners: if testGame.gameArray[corner[0]][corner[1]] == newMaxTile: value *= self.maxInCornerMultiplier value += self.cornerBonusScaledByMax * newMaxTile value += self.enterCorner # penalty for moving down if move == Move.down: value -= self.moveDownPenalty * newMaxTile board = self.game.postRotate(move, board) return value
class RandomSolver(object): ''' classdocs ''' def __init__(self): ''' Constructor ''' self.game = GameState() self.numMoves = 0 pass def playGame(self): while (self.game.isGoing()): 'pick a move' move = randint(1, 4) 'execute move' if (self.game.isValid(Move(move))): self.game.takeMove(Move(move)) self.numMoves += 1 pass def getScore(self): return self.game.getScore() def getMaxTile(self): return self.game.getMaxTile() def getMoves(self): return self.numMoves def printGame(self): self.game.printState() pass
class SearchRandomComparison(object): ''' classdocs ''' def __init__(self, inDepth, zeroes): ''' Constructor ''' self.game = GameState() self.numMoves = 0 self.depth = inDepth self.zeroes = zeroes self.disagreements = 0 self.comparisons = 0 pass 'keeps searching for moves until the game is complete' def playGame(self): self.game.printState(self.game.gameArray) count = 0 disagreements = 0 comparisons = 0 while (self.game.isGoing()): print("\nStarting move: " + datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S')) bestMove = self.searchRandom(self.game.copyArr(), self.depth) print(bestMove[0]) if self.enoughZeroes(): self.comparisons += 1 noRandomMove = self.searchNoRandom(self.game.copyArr(), self.depth) if not noRandomMove == bestMove: self.disagreements += 1 # when at the end, all decisions might lead to an inevitable failure if (not self.game.isValid(bestMove)): pass #self.game.printState(self.game.gameArray) self.game.takeMove(bestMove[0]) self.game.printState(self.game.gameArray) self.numMoves = self.numMoves + 1 print(self.numMoves) pass 'determines whether or not a comparison should occur based on how full the board is' 'number of required 0s is determined at solver creation' def enoughZeroes(self): numZeroes = 0 # count number of 0-tiles for x in range (0, 4): for y in range (0, 4): if self.game.gameArray[x][y] == 0: numZeroes += 1 if numZeroes >= self.zeroes: return True return False 'returns best move and the value of that move factoring in random tiles' 'best move is only useful for the top-level call' def searchRandom(self, board, depth): if (depth == 0): return (Move.up, 0) bestMove = Move.up bestValue = -1 move = Move.up moveValue = self.searchDirectionRandom(board, depth, move) if (moveValue > bestValue): bestMove = move bestValue = moveValue move = Move.left moveValue = self.searchDirectionRandom(board, depth, move) if (moveValue > bestValue): bestMove = move bestValue = moveValue move = Move.right moveValue = self.searchDirectionRandom(board, depth, move) if (moveValue > bestValue): bestMove = move bestValue = moveValue move = Move.down moveValue = self.searchDirectionRandom(board, depth, move) if (moveValue > bestValue): bestMove = move bestValue = moveValue return (bestMove, bestValue) 'returns the number of matches that a given move would make' 'this only determines value of one move and no further searching' def valueOfMove(self, board, move): return value(self.game.preRotate(move, board), self.game, move) 'returns the expected value of a given move searching with the given depth factoring in random tiles' def searchDirectionRandom(self, board, depth, move): testGame = GameState() testGame.setBoard(board) testGame.setBoard(testGame.copyArr()) # if the move isn't valid, don't consider it if (not testGame.isValid(move)): return -1 #determine the value for making the move at this level ourValue = self.valueOfMove(testGame.gameArray, move) #'using that as the starting board, check a lot of possibilities' afterMove = testGame.executeMove(move) testGame.setBoard(afterMove) ev2 = [[0 for x in range(4)] for x in range(4)] ev4 = [[0 for x in range(4)] for x in range(4)] options = 0 searchValue = 0 # determine the value of each cell for x in range (0, 4): for y in range (0, 4): trialBoard = testGame.copyArr() if (trialBoard[x][y] == 0): options += 1 trialBoard[x][y] = 2 ev2[x][y] = self.searchRandom(trialBoard, depth - 1)[1] trialBoard[x][y] = 0 trialBoard[x][y] = 4 ev4[x][y] = self.searchRandom(trialBoard, depth - 1)[1] trialBoard[x][y] = 0 # adjust those cells for their likelihood for x in range (0, 4): for y in range (0, 4): searchValue += (ev2[x][y] * 0.9) / options searchValue += (ev4[x][y] * 0.1) / options return ourValue + searchValue 'returns best move and the value of that move' 'best move is only useful for the top-level call' def searchNoRandom(self, board, depth): if (depth == 0): return (Move.up, 0) bestMove = Move.up bestValue = -1 move = Move.up moveValue = self.searchDirectionNoRandom(board, depth, move) if (moveValue > bestValue): bestMove = move bestValue = moveValue move = Move.left moveValue = self.searchDirectionNoRandom(board, depth, move) if (moveValue > bestValue): bestMove = move bestValue = moveValue move = Move.right moveValue = self.searchDirectionNoRandom(board, depth, move) if (moveValue > bestValue): bestMove = move bestValue = moveValue move = Move.down moveValue = self.searchDirectionNoRandom(board, depth, move) if (moveValue > bestValue): bestMove = move bestValue = moveValue return (bestMove, bestValue) 'returns the expected value of a given move searching with the given depth' 'this ignores the new tiles appearing, which saves tons on complexity' def searchDirectionNoRandom(self, board, depth, move): testGame = GameState() testGame.setBoard(board) testGame.setBoard(testGame.copyArr()) # if the move isn't valid, don't consider it if (not testGame.isValid(move)): return -1 # determine the value for making the move at this level ourValue = self.valueOfMove(testGame.gameArray, move) # using that as the starting board, check the child's options afterMove = testGame.executeMove(move) #testGame.setBoard(afterMove) #trialBoard = testGame.copyArr() searchValue = self.searchNoRandom(afterMove, depth - 1)[1] return ourValue + searchValue 'class specific stats' def getComparisons(self): return self.comparisons def getDisagreements(self): return self.disagreements 'generic methods of every solver' def getScore(self): return self.game.getScore() def getMaxTile(self): return self.game.getMaxTile() def getMoves(self): return self.numMoves def printGame(self): self.game.printState() pass
class GreedySearchPercentageDepth(object): minimumDepth = 2 fourCorners = {(0, 0), (0, 3), (3, 0), (3, 3)} options = {Move.down, Move.left, Move.up, Move.right} enterCorner = 0 maxInCornerMultiplier = 1.0 cornerBonusScaledByMax = 0.8 moveDownPenalty = 0.0 def __init__(self, threshold): self.game = GameState() self.numMoves = 0 self.threshold = threshold pass 'keeps searching for moves until the game is complete' def playGame(self): self.game.printState(self.game.gameArray) count = 0 while (self.game.isGoing()): self.possibilities = 0 moveStart = time.time() print("Starting move: " + datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S')) testBoard = self.game.copyArr() bestMove = self.search(testBoard, 1, 0) moveEnd = time.time() totalTime = moveEnd - moveStart print(bestMove[0]) print("time to search " + str(self.possibilities) + " possibilities moves: " + str(totalTime)) print("time per possibility: " + str(totalTime / self.possibilities)) # when at the end, all decisions might lead to an inevitable failure if (not self.game.isValid(bestMove)): pass #self.game.printState(self.game.gameArray) self.game.takeMove(bestMove[0]) self.game.printState(self.game.gameArray) self.numMoves = self.numMoves + 1 print(self.numMoves) pass 'returns best move and the value of that move' 'best move is only useful for the top-level call' def search(self, board, likelihood, depth): if (depth >= self.minimumDepth and likelihood < self.threshold): return (Move.up, 0) self.possibilities += 1 bestMove = Move.up bestValue = -1 for move in self.options: moveValue = self.searchDirection(board, likelihood, move, depth + 1) if (moveValue > bestValue): bestMove = move bestValue = moveValue return (bestMove, bestValue) 'returns the number of matches that a given move would make' 'this only determines value of one move and no further searching' def valueOfMove(self, board, move): board = self.game.preRotate(move, board) testGame = GameState() testGame.setBoard(board) testGame.setBoard(testGame.copyArr()) value = 0 # store previous information oldScore = testGame.getScore() oldMaxTile = testGame.getMaxTile() testGame.executeMove(Move.down) newScore = testGame.getScore() value += newScore - oldScore # check if the largest tile is in a corner after the move newMaxTile = testGame.getMaxTile() for corner in self.fourCorners: if testGame.gameArray[corner[0]][corner[1]] == newMaxTile: value *= self.maxInCornerMultiplier value += self.cornerBonusScaledByMax * newMaxTile value += self.enterCorner # penalty for moving down if move == Move.down: value -= self.moveDownPenalty * newMaxTile board = self.game.postRotate(move, board) return value 'returns the expected value of a given move searching with the given likelihood' def searchDirection(self, board, likelihood, move, depth): testGame = GameState() testGame.setBoard(board) testGame.setBoard(testGame.copyArr()) # if the move isn't valid, don't consider it if (not testGame.isValid(move)): return -1 #determine the value for making the move at this level ourValue = self.valueOfMove(testGame.gameArray, move) #'using that as the starting board, check a lot of possibilities' afterMove = testGame.executeMove(move) testGame.setBoard(afterMove) ev2 = [[0 for x in range(4)] for x in range(4)] ev4 = [[0 for x in range(4)] for x in range(4)] options = 0 searchValue = 0 # determine which cells can have a new tile trialBoard = testGame.copyArr() for x in range (0, 4): for y in range (0, 4): if (trialBoard[x][y] == 0): options += 1 # determine the value of each cell for x in range (0, 4): for y in range (0, 4): trialBoard = testGame.copyArr() if (trialBoard[x][y] == 0): cellChance = likelihood / options trialBoard[x][y] = 2 ev2[x][y] = cellChance * 0.9 * self.search(trialBoard, likelihood * cellChance * 0.9, depth)[1] trialBoard[x][y] = 0 trialBoard[x][y] = 4 ev4[x][y] = cellChance * 0.9 * self.search(trialBoard, likelihood * cellChance * 0.1, depth)[1] trialBoard[x][y] = 0 return ourValue + searchValue 'generic methods of every solver' def getScore(self): return self.game.getScore() def getMaxTile(self): return self.game.getMaxTile() def getMoves(self): return self.numMoves def printGame(self): self.game.printState() pass
class RegionSearch(object): """ classdocs """ def __init__(self, inDepth): """ Constructor """ self.game = GameState() self.numMoves = 0 self.depth = inDepth pass "keeps searching for moves until the game is complete" def playGame(self): self.game.printState(self.game.gameArray) count = 0 while self.game.isGoing(): print("\nStarting move: " + datetime.datetime.fromtimestamp(time.time()).strftime("%Y-%m-%d %H:%M:%S")) testBoard = self.game.copyArr() bestMove = self.search(testBoard, self.depth) print(bestMove[0]) # when at the end, all decisions might lead to an inevitable failure if not self.game.isValid(bestMove): pass # self.game.printState(self.game.gameArray) self.game.takeMove(bestMove[0]) self.game.printState(self.game.gameArray) self.numMoves = self.numMoves + 1 print(self.numMoves) pass "returns best move and the value of that move" "best move is only useful for the top-level call" def search(self, board, depth): if depth == 0: return (Move.up, 0) bestMove = Move.up bestValue = -1 move = Move.up moveValue = self.searchDirection(board, depth, move) if moveValue > bestValue: bestMove = move bestValue = moveValue move = Move.left moveValue = self.searchDirection(board, depth, move) if moveValue > bestValue: bestMove = move bestValue = moveValue move = Move.right moveValue = self.searchDirection(board, depth, move) if moveValue > bestValue: bestMove = move bestValue = moveValue move = Move.down moveValue = self.searchDirection(board, depth, move) if moveValue > bestValue: bestMove = move bestValue = moveValue return (bestMove, bestValue) "returns the number of matches that a given move would make" "this only determines value of one move and no further searching" def valueOfMove(self, board, move): board = self.game.preRotate(move, board) value = 0 for x in range(0, 4): value += self.game.countSlideDownMatches(x, board) board = self.game.postRotate(move, board) return value "returns the expected value of a given move searching with the given depth" def searchDirection(self, board, depth, move): testGame = GameState() testGame.setBoard(board) testGame.setBoard(testGame.copyArr()) # if the move isn't valid, don't consider it if not testGame.isValid(move): return -1 # determine the value for making the move at this level ourValue = self.valueOfMove(testGame.gameArray, move) #'using that as the starting board, check a lot of possibilities' afterMove = testGame.executeMove(move) testGame.setBoard(afterMove) options = 0 searchValue = 0 # arrays ev2 = [[0 for x in range(2)] for x in range(2)] ev4 = [[0 for x in range(2)] for x in range(2)] optionsInRegion = [[0 for x in range(2)] for x in range(2)] # determine value of the region for x in range(0, 2): # location of region for y in range(0, 2): trialBoard = testGame.copyArr() validLocs = [] for rx in range(0, 2): # location within region for ry in range(0, 2): locX = (x * 2) + rx locY = (y * 2) + ry # take all 0s into account for likelihood if trialBoard[locX][locY] == 0: optionsInRegion[x][y] += 1 options += 1 validLocs += [(locX, locY)] # find a cell in the region to test possibilities = optionsInRegion[x][y] for loc in validLocs: rand = randrange(0, 100) # randomly select one cell in the region to care about if rand < ((1 / possibilities) * 100): # test whether or not this is the cell we care about trialBoard[loc[0]][loc[1]] = 2 ev2[x][y] = self.search(trialBoard, depth - 1)[1] trialBoard[loc[0]][loc[1]] = 4 ev4[x][y] = self.search(trialBoard, depth - 1)[1] trialBoard[loc[0]][loc[1]] = 0 break possibilities -= 1 # reset our locations for the next region validLocs.clear() # adjust those region evs for their likelihood for x in range(0, 2): for y in range(0, 2): searchValue += (ev2[x][y] * 0.9) * (optionsInRegion[x][y] / options) searchValue += (ev4[x][y] * 0.1) * (optionsInRegion[x][y] / options) return ourValue + searchValue "generic methods of every solver" def getScore(self): return self.game.getScore() def getMaxTile(self): return self.game.getMaxTile() def getMoves(self): return self.numMoves def printGame(self): self.game.printState() pass
class SearchSolver(object): ''' classdocs ''' def __init__(self, inDepth): ''' Constructor ''' self.depth = inDepth self.game = GameState() self.numMoves = 0 pass def playGame(self): while (self.game.isGoing()): self.startSearch(self.game.copyArr(), self.depth) break pass # determines the move with the highest ev with the given search depth def startSearch(self, board, depth): bestOption = None bestVal = -1 # base case, depth == 0 # in this case, estimate rest of the way greedy? if (depth == 0): return (Move.up, 0) upVal = self.estimateValue(depth - 1, board, Move.up) print(upVal) if (upVal > bestVal): bestOption = Move.up rightVal = self.estimateValue(depth - 1, board, Move.right) if (rightVal > bestVal): bestOption = Move.right return (bestOption, bestVal) def estimateValue(self, depth, board, move): ev2 = [[0 for x in range(4)] for x in range(4)] ev4 = [[0 for x in range(4)] for x in range(4)] ev = 0 numOptions = 0 # go through all options if(self.game.isValid(move)): baseGameUp = GameState() baseGameUp.setBoard(board) baseGameUp.executeMove(move) for x in range (0, 4): for y in range (0, 4): if(baseGameUp.gameArray[x][y] == 0): numOptions += 2 # per cell ev expecting 4 pretendBoard2 = baseGameUp.copyArr() pretendGame2 = GameState() pretendBoard2[x][y] = 2 pretendGame2.setBoard(pretendBoard2) searchEv = self.startSearch(pretendGame2.copyArr(), depth - 1) ev2[x][y] = searchEv[1] pretendGame4 = None # per cell ev expecting 4 pretendBoard4 = baseGameUp.copyArr() pretendGame4 = GameState() pretendBoard4[x][y] = 4 pretendGame4.setBoard(pretendBoard4) searchEv = self.startSearch(pretendGame4.copyArr(), depth - 1) ev4[x][y] = searchEv[1] pretendGame4 = None pass return ev def getScore(self): return self.game.getScore() def getMaxTile(self): return self.game.getMaxTile() def getMoves(self): return self.numMoves def printGame(self): self.game.printState() pass
class GreedySearch(object): fourCorners = {(0, 0), (0, 3), (3, 0), (3, 3)} possibilities = {Move.down, Move.left, Move.up, Move.right} enterCorner = 0 maxInCornerMultiplier = 1.0 cornerBonusScaledByMax = 0.8 moveDownPenalty = 0.0 def __init__(self, inDepth): ''' Constructor ''' self.game = GameState() self.numMoves = 0 self.depth = inDepth pass 'keeps searching for moves until the game is complete' def playGame(self): count = 0 while (self.game.isGoing()): print("\nStarting move: " + datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S')) testBoard = self.game.copyArr() bestMove = self.search(testBoard, self.depth) print(bestMove[0]) self.game.printState(self.game.gameArray) wasSuccessful = self.game.takeMove(bestMove[0]) self.numMoves = self.numMoves + 1 if not wasSuccessful: break self.game.printState(self.game.gameArray) print("number of moves in game: " + str(self.numMoves)) pass 'returns best move and the value of that move' 'best move is only useful for the top-level call' def search(self, board, depth): bestMove = Move.up bestValue = -1 for move in self.possibilities: moveValue = self.searchDirection(board, depth, move) if (moveValue > bestValue): bestMove = move bestValue = moveValue return (bestMove, bestValue) 'returns the number of matches that a given move would make' 'this only determines value of one move and no further searching' def valueOfMove(self, board, move): return value(self.game.preRotate(move, board), self.game, move) 'returns the expected value of a given move searching with the given depth' def searchDirection(self, board, depth, move): testGame = GameState() testGame.setBoard(board) testGame.setBoard(testGame.copyArr()) # if the move isn't valid, don't consider it if (not testGame.isValid(move)): return -1 # determine the value for making the move at this level ourValue = self.valueOfMove(testGame.gameArray, move) # if we have reached bottom depth, stop searching and return the heuristic value if depth == 1: return ourValue # using that as the starting board, check a lot of possibilities afterMove = testGame.executeMove(move) testGame.setBoard(afterMove) ev2 = [[0 for x in range(4)] for x in range(4)] ev4 = [[0 for x in range(4)] for x in range(4)] options = 0 searchValue = 0 # determine the value of each cell for x in range (0, 4): for y in range (0, 4): trialBoard = testGame.copyArr() if (trialBoard[x][y] == 0): options += 1 trialBoard[x][y] = 2 ev2[x][y] = self.search(trialBoard, depth - 1)[1] trialBoard[x][y] = 4 ev4[x][y] = self.search(trialBoard, depth - 1)[1] trialBoard[x][y] = 0 # adjust those cells for their likelihood for x in range (0, 4): for y in range (0, 4): searchValue += (ev2[x][y] * 0.9) / options searchValue += (ev4[x][y] * 0.1) / options return ourValue + searchValue 'generic methods of every solver' def getScore(self): return self.game.getScore() def getMaxTile(self): return self.game.getMaxTile() def getMoves(self): return self.numMoves def printGame(self): self.game.printState() pass
class GreedySearchNoRandom(object): def __init__(self, inDepth): self.game = GameState() self.numMoves = 0 self.depth = inDepth pass 'keeps searching for moves until the game is complete' def playGame(self): self.game.printState(self.game.gameArray) count = 0 while (self.game.isGoing()): testBoard = self.game.copyArr() bestMove = self.search(testBoard, self.depth) print(bestMove[0]) # when at the end, all decisions might lead to an inevitable failure if (not self.game.isValid(bestMove)): pass #self.game.printState(self.game.gameArray) self.game.takeMove(bestMove[0]) self.game.printState(self.game.gameArray) pass 'returns best move and the value of that move' 'best move is only useful for the top-level call' def search(self, board, depth): if (depth == 0): return (Move.up, 0) bestMove = Move.up bestValue = -1 move = Move.up moveValue = self.searchDirection(board, depth, move) if (moveValue > bestValue): bestMove = move bestValue = moveValue move = Move.left moveValue = self.searchDirection(board, depth, move) if (moveValue > bestValue): bestMove = move bestValue = moveValue move = Move.right moveValue = self.searchDirection(board, depth, move) if (moveValue > bestValue): bestMove = move bestValue = moveValue move = Move.down moveValue = self.searchDirection(board, depth, move) if (moveValue > bestValue): bestMove = move bestValue = moveValue return (bestMove, bestValue) 'returns the number of matches that a given move would make' 'this only determines value of one move and no further searching' def valueOfMove(self, board, move): return value(self.game.preRotate(move, board), self.game, move) 'returns the expected value of a given move searching with the given depth' 'this ignores the new tiles appearing, which saves tons on complexity' def searchDirection(self, board, depth, move): testGame = GameState() testGame.setBoard(board) testGame.setBoard(testGame.copyArr()) # if the move isn't valid, don't consider it if (not testGame.isValid(move)): return -1 # determine the value for making the move at this level ourValue = self.valueOfMove(testGame.gameArray, move) # using that as the starting board, check the child's options afterMove = testGame.executeMove(move) searchValue = self.search(afterMove, depth - 1)[1] return ourValue + searchValue 'generic methods of every solver' def getScore(self): return self.game.getScore() def getMaxTile(self): return self.game.getMaxTile() def getMoves(self): return self.numMoves def printGame(self): self.game.printState() pass