def greedySearch(game, cap=-1): ''' A search algorithm that takes a freecell game and tries to solve it. Uses the Greedy Best First Search algorithm. Heuristic function based off number of cards in foundations and number of cycles (patterns that make it hard to clear the cascades) Arguments: game -- a freecell game object cap -- a maximum for the number of states to consider in trying to find a solution Return: if successful, will return a list of all moves to solve the game. If unsuccessful, users still have the option to see a list of all moves to the closest state to the solution. If users decline, returns None. ''' def gameOver(parentState=None): ''' Handles what to do when a game ends (either if won, cap is reached, all states are investigated -- an almost impossibility --, or user quits via KeyboardInterrupt). If game is won, will display the game after every subsequent move till the end. If game is lost, will ask users if they would like to see the closest state to solved. ''' isWon = True if not parentState: # only called with an argument when game is won (parentState=solve) print('Game could not be solved :(') toContinue = input('See the best the program could do? (y or n): ') if toContinue is not 'y': return None isWon = False parentState = S.findLowestState(statesToCheck.union\ (statesChecked), S.hFunctionBasic) # search through set of states for one that produces lowest value # for a given function. Reduce is much more expensive for time. # Using hFunctionBasic will find state with most foundation cards answerLst = S.constructPath(parentState, parentAndPathDict) # take a state and a dictionary linking states to ancestors, produce a # list of all moves till parentState. totalTime = time() - timeA numOfMoves = len(answerLst) statesEval = counter S.remakeGame(firstState, answerLst) # plays a whole game given list of moves and initial state print(f'Number of moves in solution: {numOfMoves}') print(f'Total time: {totalTime} seconds') print(f'States evaluated: {counter}') return answerLst timeA = time() buckets = [set() for i in range(200)] # buckets is priority queue necessary for both Greedy BestFirstSearch # and A*. Since fixed number of integer values for either # the hFunctions (Greedy BFS) or f functions(A*), buckets is a list of sets # indexed by these values. statesToCheck = set() # has same states as buckets, but easier to use the in operator with statesChecked = set() # set of all visited nodes, prevents looping parentAndPathDict = {} # states --> (predecessor state, move to go from predecessor to state) cyclesDict = {} # states --> set of cycles in that state (see Note on cycles) hScores = {} # states --> score according to a heuristic function (distance to goal) counter = 0 # counts number of states visited firstState = G.makeImmutableState(game) S.printState(firstState) parentAndPathDict[firstState] = None firstCycles = S.makeCycles(firstState) # makeCycles only used here. Too long to calculate cycles from a # given state, adds too much time complexity. Instead, take parent's cycles, # modify based on move to get to child to find child's cycles cyclesDict[firstState] = firstCycles hVal = S.hFunction(firstState, firstCycles) hScores[firstState] = hVal buckets[hVal].add(firstState) statesToCheck.add(firstState) while True: try: isStateToCheck = False for i in range(200): # in the buckets version of a priority que, first nonempty set # will have the best heuristic if buckets[i]: parentState = buckets[i].pop() isStateToCheck = True break counter += 1 print(counter) # 3/4 conditions to end a game. Last one is KeyboardInterrupt if not isStateToCheck: # impossible return gameOver() if S.game_is_won(parentState): return gameOver(parentState) if counter == cap: return gameOver() # To conserve memory, progressively wipe all nonessential # states from the data structures i = counter % 500000 + 70 if i < 200 and counter > 200: for state in buckets[i]: del cyclesDict[state] del hScores[state] del parentAndPathDict[state] statesToCheck.remove(state) buckets[i].clear() # Exceptions is statesChecked, cuz still want to prevent looping statesToCheck.remove(parentState) statesChecked.add(parentState) nodesGenerated = S.findSuccessors(parentState,\ cyclesDict[parentState]) for (child, move, cycles) in nodesGenerated: if child in statesChecked: # already seen, no looping continue if child not in statesToCheck: # if state not yet generated parentAndPathDict[child] = (parentState, move) statesToCheck.add(child) cyclesDict[child] = cycles hVal = S.hFunction(child, cycles) hScores[child] = hVal buckets[hVal].add(child) except KeyboardInterrupt: # Allows user to stop the loop anytime. Stopping kills a few states # though, because most likely the parentState already added to # statesChecked, but no kids produced.(See note on KeyboardInterrupts) print(f'States Evaluated: {counter}') print(f'Time: {time()-timeA} seconds') toQuit = input('Would you like to quit? (y or n): ') if toQuit is not 'y': continue return gameOver()
def AStarSearch(game, cap): ''' Uses the A* algorithm, with the same heuristic function as greedySearch. ''' # For more detailed docstrings, comments, see greedySearch def gameOver(parentState=None): ''' Handles what to do when a game ends. ''' isWon = True if not parentState: print('Game could not be solved :(') toContinue = input('See the best the program could do? (y or n): ') if toContinue is not 'y': return None isWon = False parentState = S.findLowestState(statesToCheck.union\ (statesChecked), S.hFunctionBasic) answerLst = S.constructPath(parentState, parentAndPathDict) totalTime = time() - timeA numOfMoves = len(answerLst) statesEval = counter S.remakeGame(firstState, answerLst) print(f'Number of moves in solution: {numOfMoves}') print(f'Total time: {totalTime} seconds') print(f'States evaluated: {counter}') return answerLst timeA = time() buckets = [set() for i in range(300)] # Bigger range than buckets in greedySearch since A* has fScores > # hScores in greedy BFS statesToCheck = set() statesChecked = set() parentAndPathDict = {} cyclesDict = {} fScores = {} # states --> fScores = gScores + hScores # hScores are same as those calculated in greedySearch gScores = {} # states --> gScores (number of moves to reach that state) counter = 0 firstState = G.makeImmutableState(game) S.printState(firstState) parentAndPathDict[firstState] = None firstCycles = S.makeCycles(firstState) cyclesDict[firstState] = firstCycles gScores[firstState] = 0 # since 0 moves to reach first state hVal = S.hFunction(firstState, firstCycles) fScores[firstState] = hVal # since fScore = gScore + hScore = 0 + hVal buckets[hVal].add(firstState) statesToCheck.add(firstState) while True: try: isStateToCheck = False for i in range(300): if buckets[i]: parentState = buckets[i].pop() isStateToCheck = True break counter += 1 print(counter) if not isStateToCheck: # almost impossible return gameOver() if S.game_is_won(parentState): return gameOver(parentState) if counter == cap: return gameOver() i = counter % 500000 + 160 if i < 300 and counter > 300: for state in buckets[i]: del cyclesDict[state] del fScores[state] del gScores[state] del parentAndPathDict[state] statesToCheck.remove(state) buckets[i].clear() statesToCheck.remove(parentState) statesChecked.add(parentState) nodesGenerated = S.findSuccessors(parentState,\ cyclesDict[parentState]) for (child, move, cycles) in nodesGenerated: if child in statesChecked: continue if child not in statesToCheck: parentAndPathDict[child] = (parentState, move) statesToCheck.add(child) cyclesDict[child] = cycles gVal = gScores[parentState] + 1 # Since only 1 extra move gScores[child] = gVal hVal = S.hFunction(child, cycles) fScores[child] = gVal + hVal buckets[gVal + hVal].add(child) except KeyboardInterrupt: print(f'States Evaluated: {counter}') print(f'Time: {time()-timeA} seconds') toQuit = input('Would you like to quit? (y or n): ') if toQuit is not 'y': continue return gameOver()