diff --git a/Block.py b/Block.py index 3ebdabf96a39c06bfb54fab8fba842d56595c97e..04d521c90cbefb881a0bbb804daccea4c2033aee 100644 --- a/Block.py +++ b/Block.py @@ -1,4 +1,4 @@ class Block: def __init__(self, pos): self.pos = pos - self.visited = [] + diff --git a/Board.py b/Board.py index 15f7968000d2f692e7b3317b0ab63fb8352aaef8..41b1924f7abbb5962353d50ed39e704d34489d11 100644 --- a/Board.py +++ b/Board.py @@ -1,13 +1,18 @@ import Tile class Board: + # List of exit tiles. Only red at the moment exit_tiles = [(3,-3), (3,-2), (3,-1), (3,0)] """ 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 self.tiles = [[None for i in range(7)] for j in range(7)] self.tiles[0][0] = 0 self.tiles[0][1] = 0 @@ -26,9 +31,12 @@ class Board: self.f = 0 self.parent = None + # Returns a list of valid moves for this board state in the form: ((pos_from),(pos_to)) def getMoves(self): valid_moves = [] for piece in self.pieces: + + # If the piece is on an exit tile, return that piece if piece.pos in self.exit_tiles: return piece p_r = piece.pos[1]+3 @@ -45,13 +53,15 @@ class Board: valid_moves.append(((p_q-3, p_r-3),(p_q-3 + move[0]*2, p_r-3 + move[1]*2))) return valid_moves - + + # Moves a pice from one position to another def move(self, t_from, t_to): piece = self.tiles[t_from[1]+3][t_from[0]+3] self.tiles[t_from[1]+3][t_from[0]+3] = None self.tiles[t_to[1]+3][t_to[0]+3] = piece self.tiles[t_to[1]+3][t_to[0]+3].pos = t_to - + + # Calculate the distance from one position to another def distance(self, start, end): q1 = start[0] q2 = end[0] diff --git a/game.py b/game.py index a958bdc1df8f2c0d4d443010c2069dacb424712e..d0cb9c03ce08475d97d9ab7abdf4822e3f2d2aab 100644 --- a/game.py +++ b/game.py @@ -10,8 +10,12 @@ exit_tiles = {'red':[(3,-3),(3,-2),(3,-1),(3,0)]} 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']) board.tiles[piece.pos[1]+3][piece.pos[0]+3] = piece @@ -21,6 +25,7 @@ def main(): block = Block.Block(tuple(b)) board.tiles[block.pos[1]+3][block.pos[0]+3] = block + # Run a* search and print solution if one exists path = A_Star(board) if path is None: print("No path found") @@ -31,56 +36,69 @@ def main(): def A_Star(start): + + # Initialise open and closed sets closedSet = [] openSet = [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 minF = 1000000 for node in openSet: if node.f <= minF: current = node minF = current.f - + + # 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 openSet.remove(current) closedSet.append(current) - + + #Find all neighbors of the current node neighbors = getNeighbors(current) - + + # Iterate through the neighbors of the current node for neighbor in neighbors: - """ + + # If the neighbor is already in the closed set, skip it inClosed = False for node in closedSet: if listCompare(neighbor.tiles, node.tiles): inClosed = True if inClosed: continue - """ - - #if neighbor.tiles == node.tiles: - # continue + tentative_gScore = current.g + 1 + # Check if the neighbor is in the open set inOpen = False for node in openSet: if listCompare(neighbor.tiles, node.tiles): inOpen = True - + + # If not, add it if not inOpen: openSet.append(neighbor) - + + # If it is, but the neighbor has a higher g score than the node already in the list, skip it elif tentative_gScore >= neighbor.g: continue + # Set the parent and g and f scores of the node neighbor.parent = current neighbor.g = tentative_gScore neighbor.f = neighbor.g + heuristic(neighbor) +# Compare two board states def listCompare(list1, list2): for i in range(0,7): for j in range(0,7): @@ -89,6 +107,7 @@ def listCompare(list1, list2): return True +# Calculates the heuristic for a given board state def heuristic(node): h = 0 for piece in node.pieces: @@ -99,12 +118,14 @@ def heuristic(node): h += (min_dist + 1) / 2 return h +# 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 = [] cur = goal @@ -114,10 +135,13 @@ def retrace(goal): path.insert(0,cur) return path +# Find the neighbor states of a given board state def getNeighbors(current): 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 if isinstance(moves, Piece.Piece): neighbor = copy.deepcopy(current) @@ -130,13 +154,16 @@ def getNeighbors(current): 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 for move in moves: neighbor = copy.deepcopy(current) neighbor.move(move[0], move[1]) neighbors.append(neighbor) return neighbors +# Functions for printing board state def make_bdict(state): bdict = {}