diff --git a/Block.py b/Block.py index 04d521c90cbefb881a0bbb804daccea4c2033aee..8e71f77316b5833418f32f61e5441134785c1d07 100644 --- a/Block.py +++ b/Block.py @@ -1,3 +1,4 @@ +# Class for block objects class Block: def __init__(self, pos): self.pos = pos diff --git a/Board.py b/Board.py index 2e6ff1a0a3e2ac2a4a0ae5ebfc89de48a3a84406..ce97771d218908d4caa804c9428e1b339698096c 100644 --- a/Board.py +++ b/Board.py @@ -1,21 +1,18 @@ -import Tile class Board: - # List of exit tiles. Only red at the moment - """ - directions = ['l', 'tl', 'tr', 'r', 'br', 'bl'] - moves = {'l':(-1,0), 'tl':(0,-1), 'tr':(1,-1), 'r':(1,0), - 'br':(0,1), 'bl':(-1,1)} - """ # List of move directions moves = [(-1,0), (0,-1), (1,-1), (1,0), (0,1), (-1,1)] def __init__(self): - # Create board state array - # None means an empty tile and 0 means a tile not on the board + # Marks whether the state is a duplicate within the open set self.duplicate = False + + # List of exit tiles for each colour self.exit_tiles = {'red':[(3,-3), (3,-2), (3,-1), (3,0)], 'green':[(-3,3),(-2,3),(-1,3),(0,3)], 'blue':[(0,-3),(-1,-2),(-2,-1),(-3,0)]} + + # Create board state array + # None means an empty tile and 0 means a tile not on the board self.tiles = [[None for i in range(7)] for j in range(7)] self.tiles[0][0] = 0 self.tiles[0][1] = 0 @@ -29,17 +26,25 @@ class Board: self.tiles[6][4] = 0 self.tiles[6][5] = 0 self.tiles[6][6] = 0 + + # List of pieces on the board self.pieces = [] + + # g and f values for a star self.g = 0 self.f = 0 + + # The move applied to reach this board state self.toMove = [] + + # The previous board state self.parent = None - self.intermediate_goal = None def __lt__(self, other): return (self.f < other.f) - # Returns a list of valid moves for this board state in the form: ((pos_from),(pos_to)) + # Returns a list of valid moves for this board state in the form: (move_type, (pos_from), (pos_to)) + # move_type is either m, j or e for move jump and exit def getMoves(self): valid_moves = [] for piece in self.pieces: @@ -49,14 +54,26 @@ class Board: return piece p_r = piece.pos[1]+3 p_q = piece.pos[0]+3 + + # Otherwise, try all moves and return valid ones for move in self.moves: - if p_r + move[1] > 6 or p_r + move[1] < 0 or p_q + move[0] > 6 or p_q + move[0] < 0: + if ( + p_r + move[1] > 6 or + p_r + move[1] < 0 or + p_q + move[0] > 6 or + p_q + move[0] < 0 + ): continue if self.tiles[p_r + move[1]][p_q + move[0]] is None: valid_moves.append(('m',(p_q-3, p_r-3),(p_q-3 + move[0], p_r-3 + move[1]))) - elif p_r + move[1]*2 <= 6 and p_r + move[1]*2 >= 0 and p_q + move[0]*2 <= 6 and p_q + move[0]*2 >= 0: + elif ( + p_r + move[1]*2 <= 6 and + p_r + move[1]*2 >= 0 and + p_q + move[0]*2 <= 6 and + p_q + move[0]*2 >= 0 + ): if self.tiles[p_r + move[1]*2][p_q + move[0]*2] is None: valid_moves.append(('j',(p_q-3, p_r-3),(p_q-3 + move[0]*2, p_r-3 + move[1]*2))) diff --git a/Piece.py b/Piece.py index 9331642655506a59f5c8637566fd99748575f3d9..1cbc18e1b5890c69692b35fd55417aa28e53a9c3 100644 --- a/Piece.py +++ b/Piece.py @@ -1,3 +1,4 @@ +# Class for piece objects class Piece: def __init__(self, pos, colour): self.pos = pos diff --git a/__pycache__/Board.cpython-36.pyc b/__pycache__/Board.cpython-36.pyc index 41baf69eb89908f10a344bfcfa8e0fc229062c60..c26ce3731904d5d22ae95c5da189501f39f3f40c 100644 Binary files a/__pycache__/Board.cpython-36.pyc and b/__pycache__/Board.cpython-36.pyc differ diff --git a/game2.py b/game2.py index 6e9ddf6547bb59a2dac3f32c175c21847b3cc054..38aa3ff50741f8da17d19f546196377a4bb88ee9 100644 --- a/game2.py +++ b/game2.py @@ -1,4 +1,3 @@ -import cProfile import pickle import sys import heapq @@ -8,25 +7,19 @@ import Board import Piece import Block - -board = Board.Board() exit_tiles = {'red':[(3,-3),(3,-2),(3,-1),(3,0)], 'green':[(-3,3),(-2,3),(-1,3),(0,3)], 'blue':[(0,-3),(-1,-2),(-2,-1),(-3,0)]} -tile_map = {"0-3":1,"1,-3":2,"2-3":3,"3-3":4,"-1-2":5,"0-2":6,"1-2":7,"2-2":8, - "3-2":9,"-2-1":10,"-1-1":11,"0-1":12,"1-1":13,"2-1":14,"3-1":15, - "-30":16,"-20":17,"-10":18,"00":19,"10":20,"20":21,"30":22,"-31":23, - "-21":24,"-11":25,"01":26,"11":27,"21":28,"-32":29,"-22":30,"-12":31, - "02":32,"12":33,"-33":34,"-23":35,"-13":36,"03":37} +# Initialize board +board = Board.Board() def main(): with open(sys.argv[1]) as file: # Load initial board state data = json.load(file) - bdict = {} - + # Add pieces and blocks to the board for p in data['pieces']: piece = Piece.Piece(tuple(p), data['colour']) @@ -38,9 +31,10 @@ def main(): board.tiles[block.pos[1]+3][block.pos[0]+3] = block colour = data['colour'] - bdict = make_bdict(board) - print_board(bdict) - + + + # Remove from exit tiles list any tile with a block on it + # because a piece can't exit from them nonBlockTiles = [] for tile in exit_tiles[colour]: if type(board.tiles[tile[1]+3][tile[0]+3]) == Block.Block: @@ -48,17 +42,16 @@ def main(): else: nonBlockTiles.append(tile) exit_tiles[colour] = nonBlockTiles - board.exit_tiles[colour] = nonBlockTiles - print(exit_tiles[colour]) - + board.exit_tiles[colour] = nonBlockTiles # Run a* search and print solution if one exists path = A_Star(board) + if path is None: print("No path found") + else: for node in path: - if not node.toMove: continue if node.toMove[0] == 'e': @@ -71,19 +64,6 @@ def main(): print ("JUMP from " + str(node.toMove[1]) + " to " + str(node.toMove[2]) + ".") -def makeHash(node): - h = str(len(node.pieces)) - spos = [] - for p in node.pieces: - s = str(p.pos[0]) + str(p.pos[1]) - spos.append(s) - spos.sort() - for piece in spos: - h += piece - return h - - - def A_Star(start): @@ -102,6 +82,7 @@ def A_Star(start): while openSet: # Find node in open set with lowest f value and set it as the "current" node + # ignoring duplicate nodes current = heapq.heappop(openSet) while current.duplicate == True: current = heapq.heappop(openSet) @@ -111,66 +92,57 @@ def A_Star(start): return retrace(current) # Remove the current node from the open set and add it to the closed set - #openSet.remove(current) idx = makeHash(current) del openSet2[idx] closedSet[idx] = current - #Find all neighbors of the current node + # Find all neighbors of the current node, excluding those already in + # the closed set neighbors = getNeighbors(current, closedSet) # Iterate through the neighbors of the current node for neighbor in neighbors: - - # If the neighbor is already in the closed set, skip it - + tentative_gScore = current.g + 1 # Check if the neighbor is in the open set + # If it is, and the new neighbor g score is less than the existing + # node's score, mark the existing node as a duplicate and insert + # the new neighbor idx = makeHash(neighbor) if idx in openSet2: node = openSet2[idx] if tentative_gScore < node.g: del openSet2[idx] - #openSet.remove(node) node.duplicate = True neighbor.parent = current neighbor.g = tentative_gScore neighbor.f = neighbor.g + heuristic(neighbor) - #heapq.heapify(openSet) heapq.heappush(openSet, neighbor) openSet2[idx] = neighbor - + # If the neighbor is not in the open set, add it else: neighbor.parent = current neighbor.g = tentative_gScore neighbor.f = neighbor.g + heuristic(neighbor) heapq.heappush(openSet, neighbor) openSet2[idx] = neighbor - - -# Compare two board states -def listCompare(list1, list2): - for i in range(0,7): - for j in range(0,7): - if type(list1[i][j]) is not type(list2[i][j]): - return False - - return True -# Calculates the heuristic for a given board state -""" -def heuristic(node): - h = 0 - for piece in node.pieces: - min_dist = 10 - for tile in exit_tiles[piece.colour]: - if node.distance(piece.pos, tile) <= min_dist: - min_dist = node.distance(piece.pos, tile) - h += (min_dist / 2) + 1 +# Generate a unique string for a given board state +# for use in the closed and open sets +# in the format: no. pieces on board then q and r +# coordinates for each piece +def makeHash(node): + h = str(len(node.pieces)) + spos = [] + for p in node.pieces: + s = str(p.pos[0]) + str(p.pos[1]) + spos.append(s) + spos.sort() + for piece in spos: + h += piece return h -""" # Calculates the heuristic for a given board state def heuristic(node): @@ -180,6 +152,8 @@ def heuristic(node): h += (min_dist(piece)-position_value(node, piece) / 2) + 1 return h +# Calculates a "score" for a position based on the number of available jumps +# that get you closer to the goal state def position_value(node, piece): adjacent_piece = pickle.loads(pickle.dumps(piece)) value = 0 @@ -197,11 +171,14 @@ def position_value(node, piece): if adjacent_min == current_min+1: value += 0.15 - if on_board(adjacent_piece.pos) and type(node.tiles[adjacent_piece.pos[1]+3][adjacent_piece.pos[0]+3]) == Piece.Piece: + if ( + on_board(adjacent_piece.pos) and + type(node.tiles[adjacent_piece.pos[1]+3][adjacent_piece.pos[0]+3]) == Piece.Piece + ): value += 0.1 return value - +# Calculates the distance of a piece to the nearest exit tile def min_dist(piece): min_dist = 10 for tile in exit_tiles[piece.colour]: @@ -209,17 +186,23 @@ def min_dist(piece): min_dist = board.distance(piece.pos, tile) return min_dist +# Check if a position is on the board def on_board(position): if position[0] >= -3 and position[0] <= 3 and position[1] >= -3 and position[1] <= 3: return True else: return False +# Check if a jump is possible from a position in a given direction def checkJump(node, position, move): new = (position[0] + move[0], position[1] + move[1]) new_jump = (position[0] + 2*move[0], position[1] + 2*move[1]) if on_board(new) and on_board(new_jump): - if type(board.tiles[new[0]+3][new[1]+3]) == Block.Block or type(board.tiles[new[0]+3][new[1]+3]) == Piece.Piece and type(board.tiles[new_jump[0]+3][new_jump[1]+3]) == None: + if ( + type(board.tiles[new[0]+3][new[1]+3]) == Block.Block or + type(board.tiles[new[0]+3][new[1]+3]) == Piece.Piece and + type(board.tiles[new_jump[0]+3][new_jump[1]+3]) == None + ): return True else: return False @@ -238,8 +221,6 @@ def retrace(goal): while cur.parent is not None: cur = cur.parent path.insert(0,cur) - #cur = cur.parent - # path.insert(0,cur) return path # Find the neighbor states of a given board state @@ -249,8 +230,8 @@ def getNeighbors(current, closedSet): # getMoves returns only a Piece object if a piece is on an exit tile # In that case, the only neighbor is the same board with that piece removed + # which is an exit move if isinstance(moves, Piece.Piece): - #neighbor = copy.deepcopy(current) neighbor = pickle.loads(pickle.dumps(current)) neighbor.toMove = None neighbor.toMove = ['e'] + list(moves.pos) @@ -267,19 +248,13 @@ def getNeighbors(current, closedSet): # Otherwise, it returns a list of possible moves # In that case, return a list of board states with those moves applied + # excluding states found in the closed set for move in moves: current.move(move[1], move[2]) idx = makeHash(current) - """ - idx = str(len(current.pieces)) + ":" - for p in current.pieces: - idx += str(p.pos[0]) + ":" - idx += str(p.pos[1]) - """ if idx not in closedSet: neighbor = pickle.loads(pickle.dumps(current)) - #neighbor = copy.deepcopy(current) neighbor.toMove = None neighbor.toMove = list(move) if current.toMove == neighbor.toMove: @@ -290,104 +265,5 @@ def getNeighbors(current, closedSet): return neighbors -# Functions for printing board state -def make_bdict(state): - - bdict = {} - - for row in state.tiles: - for col in row: - if isinstance(col, Piece.Piece): - bdict[col.pos] = 'p' - elif isinstance(col, Block.Block): - bdict[col.pos] = 'b' - - return bdict - -def print_board(board_dict, message="", debug=False, **kwargs): - """ - Helper function to print a drawing of a hexagonal board's contents. - - Arguments: - - * `board_dict` -- dictionary with tuples for keys and anything printable - for values. The tuple keys are interpreted as hexagonal coordinates (using - the axial coordinate system outlined in the project specification) and the - values are formatted as strings and placed in the drawing at the corres- - ponding location (only the first 5 characters of each string are used, to - keep the drawings small). Coordinates with missing values are left blank. - - Keyword arguments: - - * `message` -- an optional message to include on the first line of the - drawing (above the board) -- default `""` (resulting in a blank message). - * `debug` -- for a larger board drawing that includes the coordinates - inside each hex, set this to `True` -- default `False`. - * Or, any other keyword arguments! They will be forwarded to `print()`. - """ - - # Set up the board template: - if not debug: - # Use the normal board template (smaller, not showing coordinates) - template = """# {0} -# .-'-._.-'-._.-'-._.-'-. -# |{16:}|{23:}|{29:}|{34:}| -# .-'-._.-'-._.-'-._.-'-._.-'-. -# |{10:}|{17:}|{24:}|{30:}|{35:}| -# .-'-._.-'-._.-'-._.-'-._.-'-._.-'-. -# |{05:}|{11:}|{18:}|{25:}|{31:}|{36:}| -# .-'-._.-'-._.-'-._.-'-._.-'-._.-'-._.-'-. -# |{01:}|{06:}|{12:}|{19:}|{26:}|{32:}|{37:}| -# '-._.-'-._.-'-._.-'-._.-'-._.-'-._.-'-._.-' -# |{02:}|{07:}|{13:}|{20:}|{27:}|{33:}| -# '-._.-'-._.-'-._.-'-._.-'-._.-'-._.-' -# |{03:}|{08:}|{14:}|{21:}|{28:}| -# '-._.-'-._.-'-._.-'-._.-'-._.-' -# |{04:}|{09:}|{15:}|{22:}| -# '-._.-'-._.-'-._.-'-._.-'""" - else: - # Use the debug board template (larger, showing coordinates) - template = """# {0} -# ,-' `-._,-' `-._,-' `-._,-' `-. -# | {16:} | {23:} | {29:} | {34:} | -# | 0,-3 | 1,-3 | 2,-3 | 3,-3 | -# ,-' `-._,-' `-._,-' `-._,-' `-._,-' `-. -# | {10:} | {17:} | {24:} | {30:} | {35:} | -# | -1,-2 | 0,-2 | 1,-2 | 2,-2 | 3,-2 | -# ,-' `-._,-' `-._,-' `-._,-' `-._,-' `-._,-' `-. -# | {05:} | {11:} | {18:} | {25:} | {31:} | {36:} | -# | -2,-1 | -1,-1 | 0,-1 | 1,-1 | 2,-1 | 3,-1 | -# ,-' `-._,-' `-._,-' `-._,-' `-._,-' `-._,-' `-._,-' `-. -# | {01:} | {06:} | {12:} | {19:} | {26:} | {32:} | {37:} | -# | -3, 0 | -2, 0 | -1, 0 | 0, 0 | 1, 0 | 2, 0 | 3, 0 | -# `-._,-' `-._,-' `-._,-' `-._,-' `-._,-' `-._,-' `-._,-' -# | {02:} | {07:} | {13:} | {20:} | {27:} | {33:} | -# | -3, 1 | -2, 1 | -1, 1 | 0, 1 | 1, 1 | 2, 1 | -# `-._,-' `-._,-' `-._,-' `-._,-' `-._,-' `-._,-' -# | {03:} | {08:} | {14:} | {21:} | {28:} | -# | -3, 2 | -2, 2 | -1, 2 | 0, 2 | 1, 2 | key: -# `-._,-' `-._,-' `-._,-' `-._,-' `-._,-' ,-' `-. -# | {04:} | {09:} | {15:} | {22:} | | input | -# | -3, 3 | -2, 3 | -1, 3 | 0, 3 | | q, r | -# `-._,-' `-._,-' `-._,-' `-._,-' `-._,-'""" - - # prepare the provided board contents as strings, formatted to size. - ran = range(-3, +3+1) - cells = [] - for qr in [(q,r) for q in ran for r in ran if -q-r in ran]: - if qr in board_dict: - cell = str(board_dict[qr]).center(5) - else: - cell = " " # 5 spaces will fill a cell - cells.append(cell) - - # fill in the template to create the board drawing, then print! - board = template.format(message, *cells) - print(board, **kwargs) - if __name__ == '__main__': - pr = cProfile.Profile() - pr.enable() main() - pr.disable() - pr.print_stats(sort="time") diff --git a/search.py b/search.py new file mode 100644 index 0000000000000000000000000000000000000000..38aa3ff50741f8da17d19f546196377a4bb88ee9 --- /dev/null +++ b/search.py @@ -0,0 +1,269 @@ +import pickle +import sys +import heapq +import json +import copy +import Board +import Piece +import Block + +exit_tiles = {'red':[(3,-3),(3,-2),(3,-1),(3,0)], + 'green':[(-3,3),(-2,3),(-1,3),(0,3)], + 'blue':[(0,-3),(-1,-2),(-2,-1),(-3,0)]} + +# Initialize board +board = Board.Board() + +def main(): + with open(sys.argv[1]) as file: + + # Load initial board state + data = json.load(file) + + # Add pieces and blocks to the board + for p in data['pieces']: + piece = Piece.Piece(tuple(p), data['colour']) + board.tiles[piece.pos[1]+3][piece.pos[0]+3] = piece + board.pieces.append(piece) + + for b in data['blocks']: + block = Block.Block(tuple(b)) + board.tiles[block.pos[1]+3][block.pos[0]+3] = block + + colour = data['colour'] + + + # Remove from exit tiles list any tile with a block on it + # because a piece can't exit from them + nonBlockTiles = [] + for tile in exit_tiles[colour]: + if type(board.tiles[tile[1]+3][tile[0]+3]) == Block.Block: + pass + else: + nonBlockTiles.append(tile) + exit_tiles[colour] = nonBlockTiles + board.exit_tiles[colour] = nonBlockTiles + + # Run a* search and print solution if one exists + path = A_Star(board) + + if path is None: + print("No path found") + + else: + for node in path: + if not node.toMove: + continue + if node.toMove[0] == 'e': + print("EXIT from (" + str(node.toMove[1]) + ", " + str(node.toMove[2]) + ").") + + elif node.toMove[0] == 'm': + print("MOVE from " + str(node.toMove[1]) + " to " + str(node.toMove[2]) + ".") + + elif node.toMove[0] == 'j': + print ("JUMP from " + str(node.toMove[1]) + " to " + str(node.toMove[2]) + ".") + + + +def A_Star(start): + + # Initialise open and closed sets + closedSet = {} + openSet = [start] + openSet2 = {} + idx = makeHash(start) + openSet2[idx] = start + + # Initial path length and heuristic + start.g = 0 + + start.f = heuristic(start) + + while openSet: + + # Find node in open set with lowest f value and set it as the "current" node + # ignoring duplicate nodes + current = heapq.heappop(openSet) + while current.duplicate == True: + current = heapq.heappop(openSet) + + # If found node is the goal, retrace the path, and return the solution + if checkGoal(current): + return retrace(current) + + # Remove the current node from the open set and add it to the closed set + idx = makeHash(current) + del openSet2[idx] + closedSet[idx] = current + + # Find all neighbors of the current node, excluding those already in + # the closed set + neighbors = getNeighbors(current, closedSet) + + # Iterate through the neighbors of the current node + for neighbor in neighbors: + + tentative_gScore = current.g + 1 + + # Check if the neighbor is in the open set + # If it is, and the new neighbor g score is less than the existing + # node's score, mark the existing node as a duplicate and insert + # the new neighbor + idx = makeHash(neighbor) + if idx in openSet2: + node = openSet2[idx] + if tentative_gScore < node.g: + del openSet2[idx] + node.duplicate = True + neighbor.parent = current + neighbor.g = tentative_gScore + neighbor.f = neighbor.g + heuristic(neighbor) + heapq.heappush(openSet, neighbor) + openSet2[idx] = neighbor + + # If the neighbor is not in the open set, add it + else: + neighbor.parent = current + neighbor.g = tentative_gScore + neighbor.f = neighbor.g + heuristic(neighbor) + heapq.heappush(openSet, neighbor) + openSet2[idx] = neighbor + +# Generate a unique string for a given board state +# for use in the closed and open sets +# in the format: no. pieces on board then q and r +# coordinates for each piece +def makeHash(node): + h = str(len(node.pieces)) + spos = [] + for p in node.pieces: + s = str(p.pos[0]) + str(p.pos[1]) + spos.append(s) + spos.sort() + for piece in spos: + h += piece + return h + +# Calculates the heuristic for a given board state +def heuristic(node): + h = 0 + for piece in node.pieces: + + h += (min_dist(piece)-position_value(node, piece) / 2) + 1 + return h + +# Calculates a "score" for a position based on the number of available jumps +# that get you closer to the goal state +def position_value(node, piece): + adjacent_piece = pickle.loads(pickle.dumps(piece)) + value = 0 + if piece.pos in exit_tiles[piece.colour]: + return value + for move in node.moves: + adjacent_piece.pos = (piece.pos[0] + move[0], piece.pos[1] + move[1]) + adjacent_min = min_dist(adjacent_piece) + current_min = min_dist(piece) + if checkJump(node, piece.pos, move) and on_board(adjacent_piece.pos): + if adjacent_min < current_min: + value += 0.5 + if adjacent_min == current_min: + value += 0.25 + + if adjacent_min == current_min+1: + value += 0.15 + if ( + on_board(adjacent_piece.pos) and + type(node.tiles[adjacent_piece.pos[1]+3][adjacent_piece.pos[0]+3]) == Piece.Piece + ): + value += 0.1 + return value + +# Calculates the distance of a piece to the nearest exit tile +def min_dist(piece): + min_dist = 10 + for tile in exit_tiles[piece.colour]: + if board.distance(piece.pos, tile) <= min_dist: + min_dist = board.distance(piece.pos, tile) + return min_dist + +# Check if a position is on the board +def on_board(position): + if position[0] >= -3 and position[0] <= 3 and position[1] >= -3 and position[1] <= 3: + return True + else: + return False + +# Check if a jump is possible from a position in a given direction +def checkJump(node, position, move): + new = (position[0] + move[0], position[1] + move[1]) + new_jump = (position[0] + 2*move[0], position[1] + 2*move[1]) + if on_board(new) and on_board(new_jump): + if ( + type(board.tiles[new[0]+3][new[1]+3]) == Block.Block or + type(board.tiles[new[0]+3][new[1]+3]) == Piece.Piece and + type(board.tiles[new_jump[0]+3][new_jump[1]+3]) == None + ): + return True + else: + return False + +# Check if the goal has been reached +def checkGoal(state): + if not state.pieces: + return True + else: + return False + +# Retrace the path from the goal state to the initial state +def retrace(goal): + path = [goal] + cur = goal + while cur.parent is not None: + cur = cur.parent + path.insert(0,cur) + return path + +# Find the neighbor states of a given board state +def getNeighbors(current, closedSet): + neighbors = [] + moves = current.getMoves() + + # getMoves returns only a Piece object if a piece is on an exit tile + # In that case, the only neighbor is the same board with that piece removed + # which is an exit move + if isinstance(moves, Piece.Piece): + neighbor = pickle.loads(pickle.dumps(current)) + neighbor.toMove = None + neighbor.toMove = ['e'] + list(moves.pos) + + for piece in neighbor.pieces: + if piece.pos == moves.pos: + neighbor.pieces.remove(piece) + break + + neighbor.tiles[moves.pos[1]+3][moves.pos[0]+3] = None + + neighbors.append(neighbor) + return neighbors + + # Otherwise, it returns a list of possible moves + # In that case, return a list of board states with those moves applied + # excluding states found in the closed set + for move in moves: + current.move(move[1], move[2]) + idx = makeHash(current) + + if idx not in closedSet: + neighbor = pickle.loads(pickle.dumps(current)) + neighbor.toMove = None + neighbor.toMove = list(move) + if current.toMove == neighbor.toMove: + print(current.toMove) + neighbors.append(neighbor) + + current.move(move[2], move[1]) + + return neighbors + +if __name__ == '__main__': + main()