Esempio n. 1
0
def parse_items(items_str):
    items = [i.strip() for i in items_str.split(",")]
    s = ItemSet()
    for i in items:
        if len(i) > 0:
            s = s.add(i)
    return s
Esempio n. 2
0
def parse_items_singleton(items_str):
    items = [i.strip() for i in items_str.split(",")]
    item_list = []
    for i in items:
        if i != "":
            s = ItemSet()
            s = s.add(i)
            item_list.append(s)
    return item_list
Esempio n. 3
0
def parse_starting_items(items):
    """Parses the CLI starting items into an ItemSet"""
    if items is None:
        return ItemSet()
    items = items.split()
    item_set = ItemSet()
    for item in items:
        item_def = item.rstrip("1234567890")
        item_set.add(item_def)
    return item_set
Esempio n. 4
0
def remove_loops(path, starting_items, item_nodes):
    """
    Simplify a path with cycles to create a minimal spoiler path
    """
    # Add bosses to item_nodes
    item_nodes.update(boss_items)
    # Node name -> node neighbors
    nodes = defaultdict(list)
    item_set = starting_items
    # Build a mini-graph of states
    for i, node in enumerate(path[:-1]):
        if node in item_nodes:
            item_set |= ItemSet([item_nodes[node]])
        state = (node, item_set)
        next_node = path[i + 1]
        if next_node in item_nodes:
            next_item_set = item_set | ItemSet([item_nodes[next_node]])
        else:
            next_item_set = item_set
        next_state = (next_node, next_item_set)
        nodes[state].append(next_state)
    print(item_set)
    print([n for n in nodes if n[0] == path[-1]])
    # Now BFS
    start = (path[0], starting_items)
    end = (path[-1], item_set)
    offers = {start: start}
    # Use a dict to avoid set hashing RNG (now that dicts order is determinisitic)
    finished = {start: None}
    queue = deque([start])
    while len(queue) > 0:
        node = queue.popleft()
        if node == end:
            break
        for neighbor in nodes[node]:
            if neighbor not in finished:
                queue.append(neighbor)
                finished[neighbor] = None
                offers[neighbor] = node
    # Now decode offers to get the path
    assert end in finished
    out_path = []
    current_node = end
    while current_node != start:
        out_path.append(current_node)
        current_node = offers[current_node]
    out_path.append(current_node)
    return out_path[::-1]
Esempio n. 5
0
def mk_plm_to_item(item_defs):
    plm_to_item = {}
    for item in item_defs:
        for presentation in item_defs[item]:
            plm_id = int.from_bytes(item_defs[item][presentation], byteorder="little")
            iset = ItemSet([item])
            plm_to_item[plm_id] = iset
    return plm_to_item
Esempio n. 6
0
 def __init__(self,
              node_,
              wildcards_=OrderedSet(),
              items_=ItemSet(),
              assignments_={}):
     self.node = node_
     self.items = items_
     self.wildcards = wildcards_
     self.assignments = assignments_
Esempio n. 7
0
    def BFS_optimized(self,
                      start_state,
                      end_state=None,
                      edge_pred=lambda x: True):
        """I don't care about every possible way to get everywhere -
        just BFS until you find end, noting that picking up items is
        always beneficial."""
        n = 0

        # key - node name
        # key - item set
        # value - state predecessor
        offers = collections.defaultdict(lambda: {})

        # key - node_name
        # value - set of item sets
        finished = collections.defaultdict(set)

        final_state = None

        # queue to hold node, item pairs
        queue = [start_state]

        while len(queue) > 0:
            n += 1
            state = queue.pop(0).copy()
            #if n % 10000 == 0:
            #    print(n)
            #    print(state)
            node = state.node
            items = state.items
            # we've reached the goal with at least the right items
            if end_state is not None and state >= end_state:
                final_state = state
                break
            # make an offer to pick up an item or defeat a boss
            node_data = self.name_node[node].data
            if isinstance(node_data, Item) or isinstance(node_data, Boss):
                new_items = items | ItemSet([node_data.type])
                # if we haven't already visited this node with the new item set...
                if new_items not in finished[node]:
                    offers[node][new_items] = state.copy()
                    finished[node].add(new_items)
                    # don't have to make a new queue item - pick up the item/boss is the only option
                    # the following for-loop handles creating the new queue items...
                    items = new_items
            # make an offer to every adjacent node reachable with this item set
            for edge in self.node_edges[node]:
                if edge.items.matches(items) and edge_pred(
                    (node, edge.terminal)):
                    # if we haven't already visited terminal with those items...
                    if items not in finished[edge.terminal]:
                        offers[edge.terminal][items] = state.copy()
                        finished[edge.terminal].add(items)
                        queue.append(BFSState(edge.terminal, items))
        return offers, finished, final_state is not None, final_state
Esempio n. 8
0
 def network_flow(self, edge_weights, source, sink, items=ItemSet()):
     assert source in self.name_node
     assert sink in self.name_node
     # Edge weights is (node1, node2) -> non-negative float
     current_edge_weights = edge_weights.copy()
     start_state = BFSState(source, items)
     # TODO: can you pick up new items during the search?
     # TODO: is it correct to use BFS_optimized when we wish not to collect items?
     end_state = BFSState(sink, items)
     # Both refer to the (mutable) current edge weights
     edge_inf = lambda x: current_edge_weights[x] == infinity
     edge_nonzero = lambda x: current_edge_weights[x] > 0
     # Check if there is a path of weight infinity from source to sink
     _, _, inf_path, _ = self.BFS_optimized(start_state,
                                            end_state,
                                            edge_pred=edge_inf)
     if inf_path:
         # No cut is possible since every cut has infinite weight
         return infinity, None
     # If there is no inf_path, then there is a finite-weight cut
     iteration = 0
     while True:
         iteration += 1
         o, f, p, _ = self.BFS_optimized(start_state,
                                         end_state,
                                         edge_pred=edge_nonzero)
         # No nonzero path means a cut has been found
         if not p:
             break
         # If there is a nonzero path, create regret along it
         path = bfs_backtrack(start_state, end_state, o)
         # List because we are going to re-use it
         path_pairs = list(zip(path, path[1:]))
         # The smallest edge limits the amount of possible flow
         regret = min([current_edge_weights[(u, v)] for u, v in path_pairs])
         # Update the edge weights with the regret:
         #TODO: assumes current_edge_weights is complete
         for u, v in path_pairs:
             current_edge_weights[(u, v)] -= regret
             current_edge_weights[(v, u)] += regret
     # Find the cut by first finding the nodes reachable from the source
     _, f, _, _ = self.BFS_optimized(start_state, edge_pred=edge_nonzero)
     source_nodes = set(f.keys())
     # All the edges which cross the boundary
     cut_edges = [(u, v.terminal) for u in source_nodes
                  for v in self.node_edges[u]
                  if v.terminal not in source_nodes]
     total_cut_weight = sum([edge_weights[e] for e in cut_edges])
     return total_cut_weight, cut_edges
Esempio n. 9
0
def parse_constraint(constraint):
    # BASE CASE
    # if it's not a symbol, then it's a variable
    if constraint[0] != "|" and constraint[0] != "&":
        # special case - bombs, power bombs, and springball all require morph ball
        if constraint == "B" or constraint == "PB" or constraint == "SPB":
            return MinSetSet(set([ItemSet(["MB", constraint])]))
        # special case - super missiles are sufficient for all missile requirements
        if constraint == "M":
            #TODO - does gravity suit stop environmental damage or not?
            return MinSetSet(set([ItemSet([constraint]), ItemSet(["S"])]))
        return MinSetSet(set([ItemSet([constraint])]))
    # RECURSIVE CASE
    else:
        symbol = constraint[0]
        # remove the symbol and the space, then find the top-level expressions
        constraint_list = find_expressions(constraint[2:])
        # now that constraint_list has every top-level expression, parse it recursively!
        minset_list = [parse_constraint(i) for i in constraint_list]
        # now combine them with either AND or OR
        if symbol == "|":
            return reduce(lambda x, y: x + y, minset_list)
        elif symbol == "&":
            return reduce(lambda x, y: x * y, minset_list)
Esempio n. 10
0
    def BFS_target(self, start_state, end_state=None):
        #TODO: review this - does it really process every combo only once?
        # key - node name
        # key - item set
        # value - state predecessor
        offers = collections.defaultdict(lambda: {})

        # key - node name
        # value - item set
        finished = collections.defaultdict(set)

        final_state = None

        # queue to hold node, item pairs
        queue = [start_state]

        while len(queue) > 0:
            state = queue.pop(0).copy()
            # we've reached the goal with at least the right items
            if end_state is not None and start_state >= end_state:
                final_state = state
                break
            node = state.node
            items = state.items
            # make an offer to every adjacent node reachable with this item set
            for edge in self.node_edges[node]:
                if edge.items.matches(items):
                    # if we haven't already visited terminal with those items...
                    if items not in finished[edge.terminal]:
                        offers[edge.terminal][items] = state
                        finished[edge.terminal].add(items)
                        queue.append(BFSState(edge.terminal, items))
            # make an offer to pick up an item or defeat a boss
            node_data = self.name_node[node].data
            if isinstance(node_data, Item) or isinstance(node_data, Boss):
                new_items = items | ItemSet([node_data.type])
                # if we haven't already visited this node with the new item set...
                if new_items not in finished[node]:
                    offers[node][new_items] = state
                    finished[node].add(new_items)
                    queue.append(BFSState(node, new_items))
        return offers, finished, final_state is not None, final_state
Esempio n. 11
0
def get_fixed_items():
    """get the set of items whose locations cannot be wildcarded"""
    return ItemSet(sm_global.boss_types) | ItemSet(sm_global.special_types)
Esempio n. 12
0
    def BFS_items(self, start_state, end_state=None, fixed_items=ItemSet()):
        """Finds a satsifying assignment of items to reach end from start. finished[end] will wind up with
        a list of (unassigned but reached items, item set needed, and possible item assignments). Each assignment
        is a dictionary, where key = item node name, and value = string value for item assigned there. Currently does
        not allow items to be fixed, but an already-assigned items dictionary can be passed."""

        #TODO: I think there's a way to make finished store less stuff - after all, we are only interested in keeping the
        # elements with a maximal NUMBER of wildcards for each item set.
        # Set of BFSItemsState
        # Ordered so that you can randomly choose from it without relying
        # on the internal hash function which is randomly salted.
        finished = OrderedSet()

        # Key - BFSItemsState (antecessor)
        # Value - BFSItemsState (predecessor)
        offers = {}

        # what items we actually needed to reach the end...
        final_state = None

        queue = [start_state]

        # queue search terms are:
        #       - node name
        #       - wildcard set
        #       - item set
        #       - assignment dictionary - key: item node, value: type assigned there
        # however two search terms are equal if the number of wildcards and the
        # obtained items are the same - that's why finished just includes the number
        # - add start node to the finished list
        #finished[start_state.node][start_state.items].append((start_state.wildcards.copy(), start_state.assignments.copy()))
        finished.add(start_state)
        while len(queue) > 0:
            state = queue.pop(0)
            #print("State: {}".format(state))
            node = state.node
            wildcards = state.wildcards
            items = state.items
            assignments = state.assignments
            if end_state is not None and state >= end_state:
                final_state = state
                break
            node_data = self.name_node[node].data
            # In addition to fixed items, pass an assigments list and check it
            if isinstance(node_data, Item):
                # If we don't already have this item, pick it up as a wildcard
                if node not in wildcards and node not in assignments:
                    new_state = BFSItemsState(node,
                                              wildcards | set([state.node]),
                                              items, assignments)
                    if new_state not in finished:
                        finished.add(new_state)
                        offers[new_state] = state
                        queue.append(new_state)
                    # There's no need to process edges - picking up that item won't prevent you from crossing an edge
                    continue
                # If we don't have the item but it was already assigned, pick it up as a fixed item
                elif node in assignments and assignments[node] not in items:
                    new_state = BFSItemsState(
                        node, wildcards, items | ItemSet([assignments[node]]),
                        assignments)
                    if new_state not in finished:
                        finished.add(new_state)
                        offers[new_state] = state
                        queue.append(new_state)
                    continue
            elif isinstance(node_data, Boss):
                # If we haven't defeated this boss yet, do so (as a fixed item)
                if node_data.type not in items:
                    new_state = BFSItemsState(
                        node, wildcards, items | ItemSet([node_data.type]),
                        assignments)
                    if new_state not in finished:
                        finished.add(new_state)
                        offers[new_state] = state
                        queue.append(new_state)
                    # There's no need to process edges - defeating that boss will allow you to cross strictly more edges
                    continue
            # Now cross edges
            for edge in self.node_edges[state.node]:
                # For each set, use some wildcards to cross it, then add that node with those assignments to the queue
                for item_set in edge.items.sets:
                    # Items in item set that we don't already have
                    need_items = item_set - items
                    #print("Need items: {}".format(need_items))
                    # Can cross the edge if we have enough wildcards to satisfy need_items and there are no fixed items that we do not already have (i.e. bosses)
                    if len(need_items) <= len(wildcards) and len(
                            need_items & fixed_items) == 0:
                        #print("Can cross")
                        wildcards_copy = wildcards.copy()
                        items_copy = state.items.copy()
                        assignments_copy = assignments.copy()
                        # Make an assignment that allows crossing that edge
                        for item in need_items:
                            # Get the last available wildcard -> the latest one the player got.
                            wildcard = wildcards_copy.pop()
                            assignments_copy[wildcard] = item
                            items_copy = items_copy.add(item)
                        new_state = BFSItemsState(edge.terminal,
                                                  wildcards_copy, items_copy,
                                                  assignments_copy)
                        #print("Items: {}, wildcards: {}".format(items_copy, wildcards_copy))
                        # If there's not already an entry for this item set with at least as many wildcards, then add it
                        if new_state not in finished:
                            finished.add(new_state)
                            offers[new_state] = state
                            queue.append(new_state)
        return offers, finished, final_state is not None, final_state
Esempio n. 13
0
def main(arg_list):
    args = get_args(arg_list)
    # Hijack stdout for output
    if args.logfile is not None:
        sys.stdout = open(args.logfile, "w")
    seed = rng.seed_rng(args.seed)
    spoiler_file = open(args.create + ".spoiler.txt", "w")
    # Update the settings from JSON files
    if args.settings is not None:
        settings_parse.get_settings(settings.setting_paths, args.settings)

    # Setup
    # Copy it to remove Bombs
    # TODO: GET RID OF Bombs
    all_items = sm_global.items[:]
    all_items = ItemSet(all_items)

    escape_timer = 0

    starting_items = parse_starting_items(args.starting_items)
    items_to_place = settings.items_to_item_list(settings.items)

    completable = False
    while not completable:
        #TODO: re-parsing rooms is quick and dirty...
        if args.hard_mode:
            rooms = parse_rooms.parse_rooms("encoding/dsl/rooms_hard.txt")
        else:
            rooms = parse_rooms.parse_rooms("encoding/dsl/rooms.txt")
        # Phantoon means an extra L door - mercilessly destroy the maridia map station
        if args.doubleboss:
            del rooms["Maridia_Map"]
        # Remove the double boss rooms
        else:
            second_boss_rooms = ["Kraid2", "Phantoon2", "Draygon2", "Ridley2"]
            for boss_room in second_boss_rooms:
                if boss_room in rooms:
                    del rooms[boss_room]
        door_changes, item_changes, graph, state, path = item_quota_rando(
            rooms, args.debug, starting_items, items_to_place[:])
        # Check completability - can reach statues?
        start_state = BFSState(state.node, state.items)
        # This takes too long
        #start_state = BFSState("Landing_Site_R2", ItemSet())
        end_state = BFSState("Statues_ET", ItemSet())
        path_to_statues = graph.check_completability(start_state, end_state)
        final_path = path_to_statues
        escape_path = None
        completable = path_to_statues is not None
        if completable:
            final_path = path + path_to_statues
            final_path = remove_loops(final_path, starting_items,
                                      {k: v
                                       for k, v in item_changes})
            print(final_path[-1])
            # Check completability - can escape?
            items = all_items | ItemSet(
                ["Kraid", "Phantoon", "Draygon", "Ridley"])
            prepare_for_escape(graph)
            escape_start = BFSState("Escape_4_R", items)
            escape_end = BFSState("Landing_Site_L2", items)
            escape_path = graph.check_completability(escape_start, escape_end)
            if escape_path is None:
                completable = False
            else:
                # One minute to get out of tourian, then 30 seconds per room
                #TODO: Is this fair? the player might need to farm and explore...
                #TODO: Simple node-length means intermediate nodes / etc. will cause problems
                # give the player time to defeat minibosses, or go through long cutscenes
                for node in escape_path:
                    if node in settings.escape:
                        escape_timer += (settings.escape[node] -
                                         2 * settings.escape["per_node"])
                escape_timer += settings.escape[
                    "tourian"] + settings.escape["per_node"] * len(escape_path)
        # Accept the seed regardless if we don't care about completability
        if not args.completable:
            break
        # Re-seed the rng for a new map (if we need to)
        if not completable and args.completable:
            print("Not Completable")
            seed = rng.seed_rng(None)

    print("Completable: " + str(completable))
    print("RNG SEED - " + str(seed))

    # Write the seed
    spoiler_file.write("RNG Seed: {}\n".format(str(seed)))
    spoiler_file.write("Items Placed: {}\n".format(str(items_to_place)))

    # Write the escape path
    if escape_path is not None:
        spoiler_file.write("Path to Escape:\n")
        spoiler_file.write(str(escape_path))
        spoiler_file.write("\n")
        spoiler_file.write("Esape Timer: {} seconds\n".format(escape_timer))

    # Write the path to the statues (including every boss)
    spoiler_file.write("Path to Statues:\n")
    if final_path is not None:
        pretty_print_out_path(spoiler_file, final_path)
    #spoiler_file.write(str(final_path))
    spoiler_file.write("\n")

    # Write the items, doors etc.
    spoiler_file.write("ITEMS:\n")
    write_item_assignments(item_changes, spoiler_file)

    spoiler_file.write("DOORS:\n")
    write_door_changes(door_changes, spoiler_file)
    spoiler_file.close()

    # Make the spoiler graph
    if args.graph:
        from door_rando import spoiler_graph
        spoiler_graph.make_spoiler_graph(door_changes, args.create)

    # Now that we have the door changes and the item changes, implement them!
    # First, make the new rom file:
    rom = RomManager(args.clean, args.create)

    # Make the rest of the necessary changes
    rom.set_escape_timer(escape_timer)
    if args.starting_items is not None:
        make_starting_items(args.starting_items, rom)

    # Apply teleportation patch
    if args.noescape:
        rom.apply_ips("patches/teleport_refill.ips")
    else:
        rom.apply_ips("patches/teleport.ips")

    # Then make the necessary changes
    make_items(item_changes, rom)
    extra_from, extra_to = logic_improvements(rom, args.g8, args.doubleboss)
    make_doors(door_changes, rom, extra_from, extra_to)
    make_saves(door_changes, rom, extra_from)
    fix_skyscroll(door_changes, rom, extra_from)

    # Logic improvements must happen last since they may
    # copy PLMs, which can be edited via prior changes

    # Save out the rom
    rom.save_and_close()

    # Collect output info
    out = {}
    out["seed"] = str(seed)
    return out
Esempio n. 14
0
	def __init__(self, set_=None):
		if set_ is None:
			self.sets = set([ItemSet()])
		else:
			self.sets = set_
Esempio n. 15
0
#TODO: how to handle super block in a tunnel? You don't necessarily need stand to destroy a super block...
#TODO: a "plant power bomb 'action' as part of the BFS? -> a fire super missile action...
# Gets very complex very quickly though...

any_velocity_type = set([VType.RUN, VType.SPEED, VType.WATER])
any_pose = set(
    [SamusPose.STAND, SamusPose.MORPH, SamusPose.JUMP, SamusPose.SPIN])
any_interval = Interval(-Infinity, Infinity)
any_velocity = VelocitySet(any_interval,
                           HVelocitySet(any_velocity_type, any_interval))

# The requirements to treat blocks as solid
# Cannot treat a tile as solid if either it is not in this data structure, or samus does not meet one of
# the necessary requirements
block_solid_requirements = {
    AbstractTile.SOLID: [(any_velocity, ItemSet([]), any_pose)],
    AbstractTile.BLOCK_CRUMBLE:
    "Reciprocal",
    # Can treat a shot block as either solid or air depending on the situation
    AbstractTile.BLOCK_SHOT: [(any_velocity, ItemSet([]), any_pose)],
}

speed_hv = HVelocitySet(set([VType.SPEED]), Interval(30, Infinity))
# Exposing a weakness of velocity sets as single-sided intervals
speed_velocity_l = VelocitySet(any_interval, speed_hv)
speed_velocity_r = VelocitySet(any_interval, speed_hv.horizontal_flip())
# Negative vertical speed and no horizontal speed
crumble_velocity = VelocitySet(Interval(0, Infinity),
                               HVelocitySet(any_velocity_type, Interval(0, 0)))

# The requirements to treat blocks as air
Esempio n. 16
0
 def __init__(self, node_, items_=ItemSet()):
     self.node = node_
     self.items = items_
Esempio n. 17
0
def get_exit_items(current_state, exits):
    exit_items = ItemSet()
    for exit in exits:
        new_items = exit.items - current_state.items
        exit_items |= new_items
    return exit_items