├── .gitgnore ├── README.md └── main.py /.gitgnore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solver for http://www.dogbunnypuzzle.com/ 2 | 3 | Run `python3 main.py` 4 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | START_NODES = { 2 | "house": ["bunny"], 3 | "bone": [], 4 | "boat": ["bunny"], 5 | "flower": [], 6 | "well": [], 7 | "carrot": [], 8 | "tree": ["dog"] 9 | } 10 | 11 | TARGET_NODES = { 12 | "house": [], 13 | "bone": ["dog"], 14 | "boat": [], 15 | "flower": [], 16 | "well": [], 17 | "carrot": ["bunny", "bunny"], 18 | "tree": [] 19 | } 20 | # From, To, MustBeOn, MustBeOff 21 | EDGES = [ 22 | ("bone", "boat", (), ()), 23 | ("bone", "house", ("carrot",), ()), 24 | ("house", "bone", ("carrot",), ()), 25 | ("house", "boat", ("tree",), ()), 26 | ("house", "tree", ("bone", "flower"), ()), 27 | ("tree", "house", ("bone", "flower"), ()), 28 | ("tree", "well", (), ()), 29 | ("carrot", "tree", (), ()), 30 | ("carrot", "well", (), ("bone",)), 31 | ("well", "carrot", (), ("bone",)), 32 | ("well", "tree", (), ()), 33 | ("well", "flower", (), ()), 34 | ("flower", "well", (), ()), 35 | ("flower", "boat", (), ()), 36 | ("boat", "house", ("tree",), ()), 37 | ] 38 | 39 | EDGES_OF_NODE = {node: [edge[1:] for edge in EDGES if edge[0] == node] for node in START_NODES} 40 | 41 | 42 | class DogBunnyGraph: 43 | def __init__(self, nodes=None, description=""): 44 | self.nodes = START_NODES if nodes is None else nodes 45 | self.description = description 46 | 47 | def __str__(self): 48 | return str({node: self.nodes[node] for node in sorted(self.nodes)}) 49 | 50 | def __hash__(self): 51 | return str(self) 52 | 53 | def copy(self): 54 | nodes = {node: pieces[:] for node, pieces in self.nodes.items()} 55 | return DogBunnyGraph(nodes) 56 | 57 | def neighbors(self): 58 | neighbors = [] 59 | for (node, contents) in self.nodes.items(): 60 | if not len(contents): 61 | continue # Nothing to move 62 | for piece in set(contents): 63 | for (to_node, on_constraint, off_constraint) in EDGES_OF_NODE[node]: 64 | constraints_okay = True 65 | for must_be_on in on_constraint: 66 | if not len(self.nodes[must_be_on]): 67 | constraints_okay = False 68 | break 69 | for must_be_off in off_constraint: 70 | if len(self.nodes[must_be_off]): 71 | constraints_okay = False 72 | break 73 | 74 | if not constraints_okay: 75 | continue 76 | 77 | next_nodes = {node: pieces[:] for node, pieces in self.nodes.items()} 78 | next_nodes[node].remove(piece) 79 | next_nodes[to_node].append(piece) 80 | next_nodes[to_node].sort() 81 | 82 | neighbors.append(DogBunnyGraph(next_nodes, f"Move {piece} from {node} --> {to_node}")) 83 | 84 | return neighbors 85 | 86 | 87 | from collections import deque 88 | 89 | 90 | def search(): 91 | init = DogBunnyGraph(START_NODES) 92 | target = DogBunnyGraph(TARGET_NODES) 93 | target_str = str(target) 94 | q = deque([(init, str(init), [])]) 95 | seen = set() 96 | while len(q): 97 | (graph, graph_str, history) = q.popleft() 98 | if graph_str in seen: 99 | continue 100 | seen.add(graph_str) 101 | if graph_str == target_str: 102 | print("Found! ", len(history)) 103 | for step in history: 104 | print(step) 105 | print(graph.description) 106 | 107 | break 108 | for n in graph.neighbors(): 109 | q.append((n, str(n), history + [graph.description])) 110 | 111 | 112 | if __name__ == '__main__': 113 | search() 114 | --------------------------------------------------------------------------------