diff --git a/Board.py b/Board.py index 3ca9c7a80b7c720f050f0acb4abacdf2dba5aa1c..2e6ff1a0a3e2ac2a4a0ae5ebfc89de48a3a84406 100644 --- a/Board.py +++ b/Board.py @@ -12,7 +12,7 @@ class Board: def __init__(self): # Create board state array # None means an empty tile and 0 means a tile not on the board - + self.duplicate = False 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)]} @@ -34,6 +34,7 @@ class Board: self.f = 0 self.toMove = [] self.parent = None + self.intermediate_goal = None def __lt__(self, other): return (self.f < other.f) diff --git a/__pycache__/Board.cpython-36.pyc b/__pycache__/Board.cpython-36.pyc index 16ddc245c98bb8521a0a6d91764c5285997893a0..41baf69eb89908f10a344bfcfa8e0fc229062c60 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 d06f48ccede005249296b4a391867dc08a1861d3..6e9ddf6547bb59a2dac3f32c175c21847b3cc054 100644 --- a/game2.py +++ b/game2.py @@ -40,7 +40,7 @@ def main(): colour = data['colour'] bdict = make_bdict(board) print_board(bdict) - + nonBlockTiles = [] for tile in exit_tiles[colour]: if type(board.tiles[tile[1]+3][tile[0]+3]) == Block.Block: @@ -50,7 +50,7 @@ def main(): exit_tiles[colour] = nonBlockTiles board.exit_tiles[colour] = nonBlockTiles print(exit_tiles[colour]) - + # Run a* search and print solution if one exists path = A_Star(board) @@ -92,12 +92,6 @@ def A_Star(start): openSet = [start] openSet2 = {} idx = makeHash(start) - """ - idx = str(len(start.pieces)) + ":" - for p in start.pieces: - idx += str(p.pos[0]) + ":" - idx += str(p.pos[1]) - """ openSet2[idx] = start # Initial path length and heuristic @@ -108,14 +102,9 @@ def A_Star(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 - """ 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): @@ -124,12 +113,6 @@ def A_Star(start): # Remove the current node from the open set and add it to the closed set #openSet.remove(current) idx = makeHash(current) - """ - idx = str(len(current.pieces)) + ":" - for p in current.pieces: - idx += str(p.pos[0]) + ":" - idx += str(p.pos[1]) - """ del openSet2[idx] closedSet[idx] = current @@ -140,67 +123,21 @@ def A_Star(start): 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 - break - if inClosed: - continue - - idx = str(len(neighbor.pieces)) + ":" - for p in neighbor.pieces: - idx += str(p.pos[0]) + ":" - idx += str(p.pos[1]) - if idx in closedSet: - 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 tentative_gScore < node.g: - node.parent = current - node.g = tentative_gScore - node.f = node.g + heuristic(node) - heapq.heapify(openSet) - break - - # If not, add it - if not inOpen: - neighbor.parent = current - neighbor.g = tentative_gScore - neighbor.f = neighbor.g + heuristic(neighbor) - heapq.heappush(openSet, neighbor) - #openSet.append(neighbor) - """ idx = makeHash(neighbor) - """ - idx = str(len(neighbor.pieces)) + ":" - for p in neighbor.pieces: - idx += str(p.pos[0]) + ":" - idx += str(p.pos[1]) - """ if idx in openSet2: node = openSet2[idx] if tentative_gScore < node.g: - """ - node.parent = current - node.g = tentative_gScore - node.f = node.g + heuristic(node) - heapq.heapify(openSet)""" del openSet2[idx] - openSet.remove(node) + #openSet.remove(node) + node.duplicate = True neighbor.parent = current neighbor.g = tentative_gScore neighbor.f = neighbor.g + heuristic(neighbor) - heapq.heapify(openSet) + #heapq.heapify(openSet) heapq.heappush(openSet, neighbor) openSet2[idx] = neighbor @@ -246,15 +183,22 @@ def heuristic(node): 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): + if checkJump(node, piece.pos, move) and on_board(adjacent_piece.pos): if adjacent_min < current_min: - value += 0.75 + value += 0.5 if adjacent_min == current_min: - value += 0.3 + 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 diff --git a/game3.py b/game3.py new file mode 100644 index 0000000000000000000000000000000000000000..3abe4a902f3e6aeeba6a0ca3b4dc0963d5aa0061 --- /dev/null +++ b/game3.py @@ -0,0 +1,446 @@ +import cProfile +import pickle +import sys +import heapq +import json +import copy +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} + +target_intermediate = False + +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 + 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'] + bdict = make_bdict(board) + print_board(bdict) + + 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 + print(exit_tiles[colour]) + + newBoard = pickle.loads(pickle.dumps(board)) + for piece in newBoard.pieces: + newBoard.tiles[piece.pos[1]+3][piece.pos[0]+3] = None + + paths = [] + for i in range(len(board.pieces)): + newBoard.pieces = [pickle.loads(pickle.dumps(piece))] + newBoard.tiles[piece.pos[1]+3][piece.pos[0]+3] = piece + path = A_Star(newBoard) + 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]) + ".") + + paths.append(path) + newBoard.tiles[piece.pos[1]+3][piece.pos[0]+3] = None +""" + co_ords=[] + for i in range(len(paths)): + for j in range(len(path[0])): + co_ords[i][j]=paths[i][j].pieces[0].pos + print(co-ords[i][j]) + + + + for i in range(len(paths[0])): + value = 0 + for j in range(len(paths)): + if paths[0][i].pieces and paths[j][i].pieces: + print(paths[0][i].pieces[0].pos, paths[j][i].pieces[0].pos) + if paths[0][i].pieces[0].pos == paths[j][i].pieces[0].pos: + value += 1 + print(value) + + if value == len(paths): + board.intermediate_goal = paths[0][i].pieces[0].pos + target_intermediate = True + print(board.intermediate_goal) + else: + break +""" + + # 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 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): + + # 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 + 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 + #openSet.remove(current) + idx = makeHash(current) + del openSet2[idx] + closedSet[idx] = current + + #Find all neighbors of the current node + 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 + 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 + + + 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 + return h +""" + +# Calculates the heuristic for a given board state +def heuristic(node): + h = 0 + for piece in node.pieces: + + h += (min_dist(node, piece)-position_value(node, piece) / 2) + 1 + return h + +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(node, adjacent_piece) + current_min = min_dist(node, piece) + if checkJump(node, piece.pos, move): + if adjacent_min < current_min: + value += 0.6 + if adjacent_min == current_min: + value += 0.35 + + if adjacent_min == current_min+1: + value += 0.15 + return value + + +def min_dist(node, piece): + if target_intermediate == True: + return board.distance(piece.pos, node.intermediate_goal) + 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 + +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 + +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) + #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 + if isinstance(moves, Piece.Piece): + #neighbor = copy.deepcopy(current) + 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 + 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)) + if move[2] == neighbor.intermediate_goal: + target_intermediate = False + #neighbor = copy.deepcopy(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 + +# 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/game4.py b/game4.py new file mode 100644 index 0000000000000000000000000000000000000000..d225b073dfb55a549e65cc286a7bdcd9d8fa9f79 --- /dev/null +++ b/game4.py @@ -0,0 +1,401 @@ +import cProfile +import pickle +import sys +import heapq +import json +import copy +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} + +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 + 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'] + bdict = make_bdict(board) + print_board(bdict) + + 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 + print(exit_tiles[colour]) + + + # 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 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): + + # 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 + 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 + #openSet.remove(current) + idx = makeHash(current) + del openSet2[idx] + closedSet[idx] = current + + #Find all neighbors of the current node + 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 + 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 + + + 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 + return h +""" + +# Calculates the heuristic for a given board state +def heuristic(node): + h = 0 + for piece in node.pieces: + + h += (min_dist(node, piece)-position_value(node, piece) / 2) + 1 + return h + +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]) + if checkJump(node, piece.pos, move) and on_board(adjacent_piece.pos): + + adjacent_min = min_dist(node, adjacent_piece) + current_min = min_dist(node, piece) + if adjacent_min < current_min: + value += 0.6 + if adjacent_min == current_min: + value += 0.35 + + if adjacent_min == current_min+1: + value += 0.15 + return value + + +def min_dist(node, piece): + min_dist = 10 + if len(node.pieces) > 1: + newBoard = pickle.loads(pickle.dumps(node)) + newPiece = pickle.loads(pickle.dumps(piece)) + for piece2 in newBoard.pieces: + newBoard.tiles[piece2.pos[1]+3][piece2.pos[0]+3] = None + newBoard.pieces = [newPiece] + newBoard.tiles[newPiece.pos[1]+3][newPiece.pos[0]+3] = newPiece + path = A_Star(newBoard) + return len(path) + else: + 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 + +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 + +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) + #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 + if isinstance(moves, Piece.Piece): + #neighbor = copy.deepcopy(current) + 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 + 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) + neighbors.append(neighbor) + + current.move(move[2], move[1]) + + 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/input10.json b/input10.json index 5fcca17a2fc649ef9a8d89853558048d2fb1731d..7b3ce6fe11ef3c44be311927c768c72d354eb00a 100644 --- a/input10.json +++ b/input10.json @@ -1,5 +1,5 @@ { "colour": "red", "pieces": [[0,-3],[1,-3],[2,-3],[-3,0]], - "blocks": [[3,-3],[3,-2],[3,-1],[2,-1],[2,0],[1,0],[1,1],[0,1],[0,2],[-1,2]] + "blocks": [[3,-3],[3,-2],[3,-1],[2,-1],[2,0],[1,0],[1,1],[0,1]] } diff --git a/input11.json b/input11.json new file mode 100644 index 0000000000000000000000000000000000000000..9b61c4b5fc584417dc4671edfcf14238d70efd6b --- /dev/null +++ b/input11.json @@ -0,0 +1,5 @@ +{ + "colour": "red", + "pieces": [[0,-3],[1,-3],[2,-3],[-3,0]], + "blocks": [[3,-3],[3,-2],[3,-1],[2,0],[1,0],[1,1],[0,1],[0,2],[-1,2]] +} diff --git a/input12.json b/input12.json new file mode 100644 index 0000000000000000000000000000000000000000..7b3ce6fe11ef3c44be311927c768c72d354eb00a --- /dev/null +++ b/input12.json @@ -0,0 +1,5 @@ +{ + "colour": "red", + "pieces": [[0,-3],[1,-3],[2,-3],[-3,0]], + "blocks": [[3,-3],[3,-2],[3,-1],[2,-1],[2,0],[1,0],[1,1],[0,1]] +} diff --git a/input13.json b/input13.json new file mode 100644 index 0000000000000000000000000000000000000000..ba8c801a5b28d7984c125ac681e8e6b362f53a80 --- /dev/null +++ b/input13.json @@ -0,0 +1,5 @@ +{ + "colour": "red", + "pieces": [[0,-3], [-2,-1], [-1,-2], [-3,3]], + "blocks": [[1,-3],[2,-3],[1,-2],[2,-2],[1,-1],[2,-1],[1,0],[2,0],[2,1]] +} diff --git a/input14.json b/input14.json new file mode 100644 index 0000000000000000000000000000000000000000..7450c41d32336c56a2c7ec85c59de6c3057685df --- /dev/null +++ b/input14.json @@ -0,0 +1,5 @@ +{ + "colour": "red", + "pieces": [[0,-3], [-3,1], [-3,2], [0,3]], + "blocks": [[3,-2],[3,0],[2,-2],[1,-2],[1,-1],[2,-1],[2,0],[2,1],[-1,2],[0,2],[-1,3],[1,2],[1,1],[0,1]] +} diff --git a/out13 b/out13 new file mode 100644 index 0000000000000000000000000000000000000000..70cad28f2ff8ef84af98a1ceedcdd0fe7291e95f --- /dev/null +++ b/out13 @@ -0,0 +1,21 @@ +# +# .-'-._.-'-._.-'-._.-'-. +# | p | b | b | | +# .-'-._.-'-._.-'-._.-'-._.-'-. +# | p | | b | b | | +# .-'-._.-'-._.-'-._.-'-._.-'-._.-'-. +# | p | | | b | b | | +# .-'-._.-'-._.-'-._.-'-._.-'-._.-'-._.-'-. +# | | | | | b | b | | +# '-._.-'-._.-'-._.-'-._.-'-._.-'-._.-'-._.-' +# | | | | | | b | +# '-._.-'-._.-'-._.-'-._.-'-._.-'-._.-' +# | | | | | | +# '-._.-'-._.-'-._.-'-._.-'-._.-' +# | p | | | | +# '-._.-'-._.-'-._.-'-._.-' +[(3, -3), (3, -2), (3, -1), (3, 0)] +None +None +None +None