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()