def convert_mission_graph_to_spatial_subgraph_form(solution_node_order): def is_mission_node_spatial_subgraph_node(node): return not (isinstance(node, Key) or isinstance(node, End) or isinstance(node, Collectable)) subgraph_nodes = set() solution_nodes_to_subgraph_nodes = dict() for node in solution_node_order: if is_mission_node_spatial_subgraph_node(node): subgraph_node = Node(node.name) subgraph_nodes.add(subgraph_node) solution_nodes_to_subgraph_nodes[node] = subgraph_node for node in solution_node_order: if not is_mission_node_spatial_subgraph_node(node): solution_nodes_to_subgraph_nodes[ node] = solution_nodes_to_subgraph_nodes[next( iter(node.parent_s))] else: subgraph_node = solution_nodes_to_subgraph_nodes[node] for child in node.child_s: if is_mission_node_spatial_subgraph_node(child): subgraph_adjacent_node = solution_nodes_to_subgraph_nodes[ child] subgraph_node.add_adjacent_nodes( subgraph_adjacent_node) return subgraph_nodes, solution_nodes_to_subgraph_nodes
def fill_dead_ends(self): collectables = [] def visit_method(node, visited_nodes): if len(node.child_s) == 0 and isinstance(node, Lock): collectable = self.add_collectable(node) collectables.append(collectable) Node.traverse_nodes_breadth_first(self.start, visit_method) return collectables
def get_node_layout(start_node): def only_visit_children_of_non_keys(node, child, visited_nodes): return not isinstance(node, Key) def sort_keys_last(node): if isinstance(node, Collectable): return 2 elif isinstance(node, Key): return 1 else: return 0 nodes = Node.find_all_nodes( node=start_node, method="depth-first", will_traverse_method=only_visit_children_of_non_keys, child_sort_method=sort_keys_last) node_positions = dict() rows = defaultdict(lambda: -1) for node in nodes: node_depth = GraphVisualizer.get_node_depth(node) if not isinstance(node, Key): row_width = GraphVisualizer.get_max_width_below_row( rows, node_depth) else: row_width = rows[node_depth] node_width = np.maximum(row_width + 1, rows[node_depth - 1]) rows[node_depth] = node_width node_positions[node] = np.array([node_width, node_depth]) return node_positions
def _get_lock_water_fire_lock_graph(): start = Start() key_red = Key("red") lock_red = Lock("red") flippers = Key("flippers") water1 = Lock("water1") water2 = Lock("water2") key_green = Key("green") lock_green = Lock("green") fire_boots = Key("fireboots") fire1 = Lock("fire1") fire2 = Lock("fire2") end = End() start.add_child_s([fire2, key_red, lock_red]) key_red.add_lock_s(lock_red) lock_red.add_child_s([flippers, water1]) flippers.add_lock_s([water1, water2]) water1.add_child_s(water2) water2.add_child_s([fire_boots, fire1]) fire_boots.add_lock_s([fire1, fire2]) fire1.add_child_s(key_green) key_green.add_lock_s(lock_green) fire2.add_child_s(lock_green) lock_green.add_child_s(end) return Node.find_all_nodes(start, method="topological-sort")
def test_add(self): n0 = GNode("0") n1 = GNode("1") n2 = GNode("2") n3 = GNode("3") n4 = GNode("4") n5 = GNode("5") Node._add(n0, [n1], lambda x: x.child_s, lambda x: x.parent_s) self.assertEqual(n0.child_s, {n1}) self.assertEqual(n1.parent_s, {n0}) Node._add(n0, [n2, n3, n4, n5], lambda x: x.child_s, lambda x: x.parent_s) self.assertEqual(n0.child_s, {n1, n2, n3, n4, n5}) self.assertEqual(n1.parent_s, {n0}) self.assertEqual(n2.parent_s, {n0}) self.assertEqual(n3.parent_s, {n0}) self.assertEqual(n4.parent_s, {n0}) self.assertEqual(n5.parent_s, {n0})
def get_random_descendant(self, node): def is_valid_descendant(child): return not isinstance(child, Key) and not isinstance(child, End) possible_descendants = [ node for node in Node.find_all_nodes(node) if is_valid_descendant(node) ] descendant = np.random.choice(possible_descendants) return descendant
def _get_simple_graph(): start = Start() key = Key() lock = Lock() end = End() start.add_child_s([key, lock]) key.add_lock_s(lock) lock.add_child_s(end) return Node.find_all_nodes(start, method="topological-sort")
def insert_rooms(self, aesthetic): nodes = Node.find_all_nodes(self.start, method="topological-sort") for node in nodes: keys = [n for n in node.child_s if isinstance(n, Key)] if len(keys) > 0 and np.random.random( ) < aesthetic.insert_room_probability: room = Room(str(self.get_room_id()), parent_s=node) for key in keys: key.remove_parent_s(node) key.add_parent_s(room)
def test_find_all_nodes(self): a, all_nodes = TestGraphs.get_man_graph() nodes = Node.find_all_nodes(a) self.assertEqual(set(nodes), all_nodes) a, all_nodes = TestGraphs.get_house_graph() nodes = Node.find_all_nodes(a) self.assertEqual(set(nodes), all_nodes) a, all_nodes = TestGraphs.get_graph_b() nodes = Node.find_all_nodes(a) self.assertEqual(set(nodes), all_nodes) a, all_nodes = TestGraphs.get_graph_a() nodes = Node.find_all_nodes(a) self.assertEqual(set(nodes), all_nodes) a, all_nodes = TestGraphs.get_triangle_graph() nodes = Node.find_all_nodes(a) self.assertEqual(set(nodes), all_nodes)
def test_topological_sort_bug(self): start = Start() key = Key("key") lock = Lock("lock") end = End() start.add_child_s(key) key.add_lock_s(lock) lock.add_child_s(end) nodes = Node.find_all_nodes(start, method="topological-sort") self.assertEqual(nodes, [start, key, lock, end])
def test_remove_by_name(self): n1 = GNode("1") n2 = GNode("2") n3 = GNode("3") n4 = GNode("4") n5 = GNode("5") n0 = GNode("0") n0.child_s = {n1, n2, n3, n4, n5} n1.parent_s.add(n0) n2.parent_s.add(n0) n2.parent_s.add(n0) n3.parent_s.add(n0) n4.parent_s.add(n0) n5.parent_s.add(n0) Node._remove_by_name(n0, n2.name, lambda x: x.child_s, lambda x: x.parent_s) self.assertEqual(n0.child_s, {n1, n3, n4, n5}) self.assertEqual(n1.parent_s, {n0}) self.assertEqual(n2.parent_s, set()) self.assertEqual(n3.parent_s, {n0}) self.assertEqual(n4.parent_s, {n0}) self.assertEqual(n5.parent_s, {n0})
def init_spatial_graph(mission_graph_start_node, random_initial_positions=True): nodes = Node.find_all_nodes(mission_graph_start_node) nodes = sorted(nodes, key=lambda x: x.name) if random_initial_positions: node_positions = np.random.random([len(nodes), 2]) * 10 else: node_positions = np.transpose(np.stack([np.arange(len(nodes)), np.zeros(len(nodes))])) adjacency_matrix = np.zeros([len(nodes), len(nodes)]) for i, node_start in enumerate(nodes): for j, node_end in enumerate(nodes): if node_end in node_start.child_s: adjacency_matrix[(i, j)] = 1 adjacency_matrix[(j, i)] = 1 return nodes, node_positions, adjacency_matrix
def _get_water_lock_graph(): start = Start() key_red = Key("red") lock_red = Lock("red") flippers = Key("flippers") water = Lock("water") end = End() start.add_child_s([flippers, water, lock_red]) flippers.add_lock_s(water) water.add_child_s(key_red) key_red.add_lock_s(lock_red) lock_red.add_child_s(end) return Node.find_all_nodes(start, method="topological-sort")
def test_find_all_nodes_topological_sort(self): top_sort = "topological-sort" n0 = GNode("0") n1 = GNode("1") n2 = GNode("2") n3 = GNode("3") n4 = GNode("4") n5 = GNode("5") n5.add_child_s([n0, n2, n4]) n2.add_child_s(n3) n3.add_child_s(n1) n4.add_child_s([n0, n1]) nodes = Node.find_all_nodes(n5, method=top_sort) self.assertTrue( self.assert_ordered(nodes, [(n5, [n4, n3, n2, n1, n0]), (n4, [n0, n1]), (n2, [n3, n1]), (n3, [n1]), (n0, []), (n1, [])])) n0 = GNode("0") n1 = GNode("1") n2 = GNode("2") n3 = GNode("3") n4 = GNode("4") n5 = GNode("5") n0.add_child_s([n1, n2, n3]) n1.add_child_s(n4) n2.add_child_s(n5) n3.add_child_s(n1) n4.add_child_s(n2) nodes = Node.find_all_nodes(n0, method=top_sort) self.assertTrue( self.assert_ordered(nodes, [(n0, [n1, n2, n3, n4, n5]), (n1, [n4, n2, n5]), (n2, [n5]), (n3, [n1, n4, n2, n5]), (n4, [n2, n5]), (n5, [])]))
def test_works_with_branched_graphs(self): # return # S # |----------- # | | | # L1 K2 L2 # | | # E K1 start = Start() key1 = Key("1") key2 = Key("2") lock1 = Lock("1") lock2 = Lock("2") end = End() start.add_child_s([lock1, key2, lock2]) lock1.add_child_s(end) lock2.add_child_s(key1) key1.add_child_s(lock1) key2.add_child_s(lock2) key1.add_lock_s(lock1) key2.add_lock_s(lock2) np.random.seed(4) level = Level() w = Tiles.wall e = Tiles.empty layer = np.array([[w, w, w, w, w, w, w, w], [w, e, e, e, e, e, e, w], [w, e, e, e, e, e, e, w], [w, w, w, w, w, w, w, w], [w, e, e, w, e, e, e, w], [w, e, e, w, e, e, e, w], [w, e, e, w, e, e, e, w], [w, w, w, w, w, w, w, w]], dtype=object) solution_node_order = Node.find_all_nodes(start, method="topological-sort") aesthetic_settings = AestheticSettings() was_successful = Generator.generate( level=level, size=layer.shape, aesthetic_settings=aesthetic_settings, max_retry_count=10, pregenerated_level_layer=layer, pregenerated_solution_node_order=solution_node_order) self.assertTrue(was_successful) Log.print(level)
def test_traverse_breadth_first(self): node, all_nodes = TestGraphs.get_man_graph() visited_expected = ["Start", "b", "c", "d", "e", "End", "g", "h", "i"] def visit_method(node, visited_nodes): visited.append(node) def will_traverse_method(node, visited_nodes): return True # Traverse with no will_traverse_method specified visited = [] Node.traverse_nodes_breadth_first(node, visit_method) visited_names = [x.name for x in visited] self.assertTrue( self.assert_ordered( visited_names, [("Start", ["b", "c", "d", "e", "End", "g", "h", "i"]), ("b", ["e", "End", "g", "h", "i"]), ("c", ["e", "End", "g", "h", "i"]), ("d", ["e", "End", "g", "h", "i"]), ("h", []), ("g", []), ("e", []), ("End", [])])) # Traverse with will_traverse_method that allows all nodes visited = [] Node.traverse_nodes_breadth_first(node, visit_method, will_traverse_method) visited_names = [x.name for x in visited] self.assertTrue( self.assert_ordered( visited_names, [("Start", ["b", "c", "d", "e", "End", "g", "h", "i"]), ("b", ["e", "End", "g", "h", "i"]), ("c", ["e", "End", "g", "h", "i"]), ("d", ["e", "End", "g", "h", "i"]), ("h", []), ("g", []), ("e", []), ("End", [])])) # Traverse with will_traverse_method that ignores node "c" def will_traverse_method2(node, visited_nodes): return node.name != "c" visited = [] Node.traverse_nodes_breadth_first(node, visit_method, will_traverse_method2) visited_names = [x.name for x in visited] visited_expected = ["Start", "b", "d", "e"] self.assertTrue( self.assert_ordered(visited_names, [("Start", ["b", "d", "e"]), ("b", ["e"]), ("d", []), ("e", [])])) self.assertFalse( self.are_values_in_list(visited_names, ["c", "g", "h", "i"]))
def generate_mission(level, mission_aesthetic): node_to_tile = dict() solution_node_order = Node.find_all_nodes(level.mission, method="topological-sort") spatial_graph = SpatialGraph(level.upper_layer) mission_spatial_nodes, mission_to_mission_spatial = MissionGenerator.convert_mission_graph_to_spatial_subgraph_form( solution_node_order) mission_to_spaces_mapping = SubgraphFinder.get_subgraph_mapping( spatial_graph.nodes, mission_spatial_nodes) if mission_to_spaces_mapping is not None: MissionGenerator.apply_mission_mapping_to_level( level, solution_node_order, mission_to_mission_spatial, mission_to_spaces_mapping, node_to_tile, mission_aesthetic) level.required_collectable_count = np.count_nonzero( level.upper_layer == Tiles.collectable) return Solver.does_level_follow_mission(level) return False, None
def add_multi_lock(self, lock_count=2): def is_node_multilock_candidate(node): is_candidate = (isinstance(node, Key) and len(node.lock_s) == 1 and len(next(iter(node.lock_s)).key_s) == 1) return is_candidate multilock_key_candidates = [ node for node in Node.find_all_nodes(self.start) if is_node_multilock_candidate(node) ] multilock_key = np.random.choice(multilock_key_candidates) lock = next(iter(multilock_key.lock_s)) for _ in range(lock_count): current_node = self.get_random_descendant(lock) child = None if len(current_node.child_s) > 0: child = np.random.choice(list(current_node.child_s)) lock = self.add_lock(current_node, multilock_key, child)
def show_graph(start_node, draw_straight_lines=True, draw_key_connections=True): sorted_nodes = Node.find_all_nodes(start_node, method="topological-sort") node_positions = GraphVisualizer.get_node_layout(start_node) im = Image.new('RGB', GraphVisualizer.get_image_size(node_positions), GraphVisualizer.background_color) draw = ImageDraw.Draw(im) # Draw Connections for node in sorted_nodes: parent_node = GraphVisualizer.get_not_key_parent(node) if parent_node is not None: GraphVisualizer.draw_connection(draw, node_positions[parent_node], node_positions[node], straight=draw_straight_lines, is_key_connection=False) if isinstance(node, Key) and draw_key_connections: for lock in node.lock_s: GraphVisualizer.draw_connection(draw, node_positions[node], node_positions[lock], straight=True, is_key_connection=True) # Draw Nodes for node in sorted_nodes: if isinstance(node, Key): node_type = "key" else: node_type = "lock" GraphVisualizer.draw_node(draw, node_positions[node], node.name, n_type=node_type) im.show()
def get_efficient_node_order(mission_start_node): def sort_keys_last(node): if isinstance(node, Collectable): return 5 elif isinstance(node, SokobanKey): return 3 elif isinstance(node, Key): return 4 elif isinstance(node, SokobanLock): return 2 elif isinstance(node, Lock): return 1 else: return 0 solution_node_order = Node.find_all_nodes( mission_start_node, method="topological-sort", child_sort_method=sort_keys_last) solution_node_order = [ n for n in solution_node_order if not isinstance(n, Room) ] return solution_node_order
def fill_rooms_with_collectables(self, aesthetic): def can_node_contain_collectable(node): return isinstance(node, Start) or isinstance( node, Lock) or isinstance(node, Room) collectables = [] possible_collectable_room_nodes = [ node for node in Node.find_all_nodes(self.start) if can_node_contain_collectable(node) ] collectable_rooms = [ room for room in possible_collectable_room_nodes if np.random.random() < aesthetic.collectable_in_room_probability ] for collectable_room in collectable_rooms: collectable = self.add_collectable(collectable_room) collectables.append(collectable) collectables.extend(self.fill_dead_ends()) end_parent = next(iter(self.end.parent_s)) self.end.remove_parent_s(end_parent) collectable_barrier = CollectableBarrier("B", end_parent, self.end, collectables)
def test_works_with_difficult_graph(self): return start = Start() key_red = Key("red") lock_red = Lock("red") flippers = Key("flippers") water1 = Lock("water1") water2 = Lock("water2") key_green = Key("green") lock_green = Lock("green") fire_boots = Key("fireboots") fire1 = Lock("fire1") fire2 = Lock("fire2") end = End() start.add_child_s([fire2, key_red, lock_red]) key_red.add_lock_s(lock_red) lock_red.add_child_s([flippers, water1]) flippers.add_lock_s([water1, water2]) water1.add_child_s(water2) water2.add_child_s([fire_boots, fire1]) fire_boots.add_lock_s([fire1, fire2]) fire1.add_child_s(key_green) key_green.add_lock_s(lock_green) fire2.add_child_s(lock_green) lock_green.add_child_s(end) np.random.seed(12) level = Level() w = Tiles.wall e = Tiles.empty layer = np.array( [[w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w], [w, e, e, w, e, e, w, e, e, w, e, e, w, e, e, w, e, e, w], [w, e, e, w, e, e, w, e, e, w, e, e, w, e, e, w, e, e, w], [w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w], [w, e, e, w, e, e, w, e, e, w, e, e, w, e, e, w, e, e, w], [w, e, e, w, e, e, w, e, e, w, e, e, w, e, e, w, e, e, w], [w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w], [w, e, e, w, e, e, w, e, e, w, e, e, w, e, e, w, e, e, w], [w, e, e, w, e, e, w, e, e, w, e, e, w, e, e, w, e, e, w], [w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w, w]], dtype=object) solution_node_order = Node.find_all_nodes(start, method="topological-sort") start_time = time.time() aesthetic_settings = AestheticSettings() aesthetic_settings.mission_aesthetic.single_lock_is_hazard_probability = 0 aesthetic_settings.mission_aesthetic.hazard_spread_probability[ Tiles.water] = 0 aesthetic_settings.mission_aesthetic.hazard_spread_probability[ Tiles.fire] = 0 was_successful = Generator.generate( level=level, size=layer.shape, aesthetic_settings=aesthetic_settings, max_retry_count=10, pregenerated_level_layer=layer, pregenerated_solution_node_order=solution_node_order) end_time = time.time() self.assertTrue(was_successful) Log.print(level) Log.print("Generated in {} seconds".format(end_time - start_time))