Esempio n. 1
0
def make_level(image):
    level_array = np.zeros(image.size, dtype="int")
    player_before_pos = None
    player_after_pos = None
    lowest_air = -float("inf")
    highest_liquid = float("inf")
    liquid_type = LiquidType.NONE
    item_locations = []
    for xy in Rect(Coord(0,0), Coord(image.size[0], image.size[1])):
        p = image.getpixel((xy.x, xy.y))
        m = color_to_abstract[p]
        if p in player_before_colors and player_before_pos is None:
            player_before_pos = xy
        if p in player_after_colors and player_after_pos is None:
            player_after_pos = xy
        if p in air_colors and xy.y > lowest_air:
            lowest_air = xy.y
        if p in water_colors and xy.y < highest_liquid:
            liquid_type = LiquidType.WATER
            highest_liquid = xy.y
        if p == item_color:
            item_locations.append(xy)
        level_array[(xy.x, xy.y)] = m
    #TODO: other liquids
    # Water level has to be /strictly/ below the lowest air,
    # and at least as high as the highest liquid
    liquid_interval = Interval(lowest_air + 1, highest_liquid)
    # Put the liquid as low as possible
    level = LevelState(Coord(0,0), level_array, LiquidType.WATER, highest_liquid, {}, {})
    if player_after_pos is None:
        player_after_pos = player_before_pos.copy()
    return player_before_pos, player_after_pos, level, liquid_interval, liquid_type, item_locations
Esempio n. 2
0
 def collide(self, ds, debug=False):
     s = self.copy()
     v = self.velocity
     for d in ds:
         # Landing on the ground
         if d == Coord(0, 1):
             vh = HVelocity(VType.RUN, 0)
             vv = 0
             v = Velocity(vv, vh)
             s.velocity = v
             # Any non-morph pose leaves you standing
             #TODO: what if you're in spin in a 2-high gap -- this will clip you into the floor!!
             if s.pose == SamusPose.SPIN:
                 s.pose = SamusPose.STAND
                 s.position += Coord(0, -1)
             elif s.pose != SamusPose.MORPH:
                 s.pose = SamusPose.STAND
         # Colliding with the ceiling
         elif d == Coord(0, -1):
             vv = 0
             v = Velocity(vv, s.velocity.vh.copy())
             s.velocity = v
         # Colliding with a wall (kill horizontal velocity)
         else:
             vh = HVelocity(VType.RUN, 0)
             v = Velocity(s.velocity.vv, vh)
             s.velocity = v
     if debug:
         print("Collided")
         print(s)
     return s
Esempio n. 3
0
 def horizontal_flip(self):
     vnew = self.vfunction.horizontal_flip()
     # Flip the after position horizontally too
     pnew = Coord(-self.after_position.x, self.after_position.y)
     # Flip the set of items obtained
     new_items = [Coord(-g.x, g.y) for g in self.gain_items]
     return SamusFunction(vnew, self.required_items, new_items,
                          self.before_pose, self.after_pose, pnew)
Esempio n. 4
0
def extent(coords):
    """Returns the smallest rectangle that contains each of the coords"""
    if len(coords) == 0:
        return None
    minx = min(coords, key=lambda item: item.x).x
    miny = min(coords, key=lambda item: item.y).y
    maxx = max(coords, key=lambda item: item.x).x
    maxy = max(coords, key=lambda item: item.y).y
    return Rect(Coord(minx, miny), Coord(maxx, maxy) + Coord(1, 1))
Esempio n. 5
0
def find_all_rects(space):
    all_rects = set()
    for p in space:
        p_rects = find_all_rects_at(space,
                                    p,
                                    directions=[Coord(1, 0),
                                                Coord(0, 1)])
        all_rects |= p_rects
    return all_rects
Esempio n. 6
0
 def horizontal_flip(self):
     pos = Coord(-self.pos.x, self.pos.y)
     walls = [Coord(-w.x, w.y) for w in self.walls]
     airs = [(Coord(-d.x, d.y), [Coord(-t.x, t.y) for t in tiles])
             for d, tiles in self.airs]
     if self.samusfunction is None:
         sf = None
     else:
         sf = self.samusfunction.horizontal_flip()
     return IntermediateState(pos, walls, airs, sf)
Esempio n. 7
0
def get_cross(p):
    """ Cross-shaped group of coords centered on p """
    directions = [
        Coord(0, 0),
        Coord(1, 0),
        Coord(-1, 0),
        Coord(0, 1),
        Coord(0, -1)
    ]
    return [p + d for d in directions]
Esempio n. 8
0
 def pretty_print(self, samus_states, image_folder):
     tile_size = 16
     scale_factor = Coord(tile_size, tile_size)
     center = Coord(tile_size // 2, tile_size // 2)
     image_dict = parse_image_folder(image_folder)
     i = Image.new("RGB",
                   (self.shape[0] * tile_size, self.shape[1] * tile_size))
     pixels = i.load()
     # Draw the level
     for xy in self.rect:
         pos = (xy - self.origin) * scale_factor
         # Have to convert pos from coord to tuple for some reason
         i.paste(image_dict[abstract_to_image[self[xy]]], (pos[0], pos[1]))
     # Draw the samusstates
     # Draw s0 arbitrarily first
     if len(samus_states) > 0:
         s0_i = image_dict[samus_state_to_image(samus_states[0], None)]
         pos = (samus_states[0].position - self.origin) * scale_factor
         i.paste(s0_i, (pos[0], pos[1]))
     # Samus facing direction depends on context of previous state
     for s1, s2 in pairwise(samus_states):
         s_i = image_dict[samus_state_to_image(s2, s1)]
         pos = (s2.position - self.origin) * scale_factor
         i.paste(s_i, (pos[0], pos[1]))
     # Draw the items (over the samuses, to make them visible, under the arrows)
     for pos, item in self.items.items():
         if item in item_to_image:
             item_image = image_dict[item_to_image[item]]
         else:
             # Handles bosses, etc.
             item_image = image_dict["item_unknown.png"]
         real_pos = pos * scale_factor
         i.paste(item_image, (real_pos[0], real_pos[1]))
     # Convert to numpy to draw arrows using CV2
     numpy_i = np.array(i)
     # Draw the arrows connecting the samusstates
     for index, (s1, s2) in enumerate(pairwise(samus_states)):
         p1 = s1.position * scale_factor + center
         p2 = s2.position * scale_factor + center
         # cv2 does not have fixed size tiplengths
         length = p2.euclidean(p1)
         if length == 0:
             tiplength = 0
         else:
             tiplength = 10 / length
         percent = index / len(samus_states)
         c_index = int(255 * percent)
         color = (c_index, 0, 255 - c_index)
         cv2.arrowedLine(numpy_i, p1, p2, color, 2, tipLength=tiplength)
     # Convert back to PIL
     i = Image.fromarray(numpy_i)
     return i
Esempio n. 9
0
def get_extra_fixed_tiles(rect, fixed_tiles, extra_similarity):
    assert extra_similarity >= 0
    assert extra_similarity <= 1
    border_tiles = set()
    for x in range(rect.start.x, rect.end.x):
        border_tiles.add(Coord(x, rect.start.y))
        border_tiles.add(Coord(x, rect.end.y - 1))
    for y in range(rect.start.y, rect.end.y):
        border_tiles.add(Coord(rect.start.x, y))
        border_tiles.add(Coord(rect.end.x - 1, y))
    free_tiles = rect.as_set() - (fixed_tiles | border_tiles)
    n_fixed = int(len(free_tiles) * extra_similarity)
    extra_fixed_tiles = set(random.sample(free_tiles, n_fixed))
    return extra_fixed_tiles | border_tiles
Esempio n. 10
0
def wfc_level_data(room_header,
                   auto_rect=False,
                   rects=None,
                   extra_similarity=0,
                   seed=None):
    if seed is not None:
        random.seed(seed)
    # Get fns before messing with the level data
    fns = get_state_functions(room_header)
    fixed_tiles, screen_map = get_fixed_tiles(room_header)
    default_level_data = room_header.state_chooser.default.level_data
    level_shape = Coord(*default_level_data.level_array.layer1.shape)
    level_bits = bit_array_from_bytes(default_level_data.level_bytes,
                                      level_shape)
    # Get the rectangularization either automatically, by input, or just the whole level at once
    if auto_rect:
        assert rects is None
        # Scale them back up to size 16x16 (rects found are rects of screens)
        #TODO: choose tiles based on ratio of tiles / pattern (higher is better)
        rects = [
            rect.scale(16)
            for rect in wfc_rectangularize(screen_map, max_area=9)
        ]
        print("Found rectangles: {}".format(rects))
    # Use one big rect if none is satisfied
    elif rects is None:
        offset = Coord(0, 0)
        size = Coord(*level_shape)
        rects = [Rect(offset, size)]
    # Use WFC to reconstruct each rect using tiles from within that rect
    #TODO: how to use tiles from the entire room?
    for rect in rects:
        print("For rectangle {}:".format(rect))
        # Add borders etc.
        extra_fixed_tiles = get_extra_fixed_tiles(rect, fixed_tiles,
                                                  extra_similarity)
        all_fixed_tiles = fixed_tiles | extra_fixed_tiles
        rect_bits = level_bits[rect.start[0]:rect.end[0],
                               rect.start[1]:rect.end[1]]
        rect_fixed_tiles = rel_fixed_tiles(rect, all_fixed_tiles)
        #print(rect_fixed_tiles)
        print("Fixed tile ratio: {}".format(len(rect_fixed_tiles) / rect.area))
        wfc_bits = bit_wfc(rect_bits, rect_fixed_tiles)
        # Overwrite level_bits with the new data
        level_bits[rect.start[0]:rect.end[0],
                   rect.start[1]:rect.end[1]] = wfc_bits
    level = level_from_bits(level_bits)
    return level, fns
Esempio n. 11
0
def random_node_place(graph, dimensions, up_es, down_es):
    """
    Returns a dictionary of node -> location by choosing locations for the nodes
    at random.
    """
    r = Rect(Coord(0, 0), dimensions)
    # Use as_list since set() is forbidden for ordering
    xys = r.as_list()
    locs = random.sample(xys, graph.nnodes)
    node_list = graph.nodes.keys()
    node_locs = {}
    # choose elevator locations: down elevators are the lowest locs, and up are the highest locs
    #TODO: seems like it doesn't always return the lowest n or highest n points
    sorted_locs = sorted(locs, key=lambda n: n.y)
    for node in node_list:
        if node in up_es:
            node_locs[node] = sorted_locs.pop(
                0)  # highest y coordinate is further down
        elif node in down_es:
            node_locs[node] = sorted_locs.pop()
    node_list = [
        node for node in node_list if node not in up_es and node not in down_es
    ]

    random.shuffle(node_list)
    #TODO: this is where we can make sure there is an item behind Draygon or some such??
    # -> pick an item node later in the order than Draygon and put it past Draygon?
    # -> default to supers
    for i in range(len(node_list)):
        if node_list[i] not in node_locs:
            node_locs[node_list[i]] = sorted_locs[i]
    return node_locs
Esempio n. 12
0
 def compose(self, other, offset=Coord(0,0), collision_policy="error"):
     """Returns a new cmap which is a composition of self and other.
     If self and other share a maptile, then the collision policy decides."""
     new_tiles = {}
     for c, t in self.items():
         # Use a copy to avoid duplicates
         new_tiles[c] = t.copy()
     for c, t in other.items():
         c_o = c + offset
         if not self.in_bounds(c_o) or c_o in new_tiles:
             # 'defer' means tiles from self are preferred over tiles from other
             if collision_policy == "defer":
                 continue
             # 'error' means bomb out when there's a conflict
             elif collision_policy == "error":
                 assert False, "Collision in compose: " + str(c)
             # 'none' means to not produce a composed cmap if there is a conflict
             elif collision_policy == "none":
                 return None
             else:
                 assert False, "Bad collision policy: " + collision_policy
         else:
             new_tiles[c_o] = t.copy()
     #TODO: which dimensions does it get?
     return ConcreteMap(self.dimensions, _tiles=new_tiles)
Esempio n. 13
0
def parse_statefunction(name, level_image, d):
    #print("Parsing rule: {}".format(name))
    b_pos, a_pos, level, liquid_interval, liquid_type, item_locations = make_level(level_image)
    rel_pos = a_pos - b_pos
    scan_direction = (a_pos - b_pos).sign()
    r = Rect(Coord(0,0), Coord(level.shape[0], level.shape[1]))
    vf = parse_vfunction(d)
    p_initial = Coord(0,0)
    v_final = vf.domain
    pose_initial = pose_str[d["b_pose"]]
    pose_final = pose_str[d["a_pose"]]
    items_initial = parse_items(d["items"])
    sfunction = SamusFunction(vf, items_initial, item_locations, pose_initial, pose_final, rel_pos)
    #TODO: certain (i.e. the first of each rule) IntermediateStates hold the sfunction so that samus' state
    # can be inferred within the rule
    #TODO: verify by checking that the inferred end state is the same as the end state achieved through function
    # composition
    i_states = []
    for xy in r.iter_direction(scan_direction):
        s_t = samus_tiles(xy, pose_final)
        level_t = [index_level(level, t) for t in s_t]
        #If samus is here through the rule
        if all([t == AbstractTile.AIR for t in level_t]):
            # Note the position, nearby airs, and nearby walls
            all_adj = get_all_adj(xy, pose_final)
            walls = get_all(level, all_adj, AbstractTile.SOLID)
            # Normalized
            walls = normalize_list(walls, xy)
            airs = []
            # Collect the necessary airs for each direction
            for direction in [Coord(scan_direction.x, 0), Coord(0, scan_direction.y)]:
                airs_in_d = get_all(level, get_adj(xy, pose_final, direction), AbstractTile.AIR)
                airs_in_d = normalize_list(airs_in_d, xy)
                airs.append((direction, airs_in_d))
            # The position of this intermediate state relative to the origin
            rel_pos = xy - b_pos
            i_states.append(IntermediateState(rel_pos, walls, airs, None))
    if len(i_states) > 0:
        # Applies the function at the beginning of the rule
        i_states[0].samusfunction = sfunction
    lfunction = LevelFunction(liquid_type, liquid_interval, i_states)
    cost = int(d["cost"])
    s = StateFunction(name, sfunction, lfunction, cost)
    if "Symmetric" not in d:
        return [s, s.horizontal_flip()]
    else:
        return [s]
Esempio n. 14
0
def get_tile(level, origin, tile):
    index = tile - origin
    try:
        # Do not allow negative indexing
        if Coord(0, 0) > index:
            return None
        return level[index]
    # Do not allow oob indexing
    except IndexError:
        return None
Esempio n. 15
0
def get_item_locations(plms):
    item_locations = {}
    plm_to_item = mk_plm_to_item(item_definitions.make_item_definitions())
    for plm in plms.l:
        print(hex(plm.plm_id))
        if plm.plm_id in plm_to_item:
            iset = plm_to_item[plm.plm_id]
            c = Coord(plm.xpos, plm.ypos)
            item_locations[c] = iset
    return item_locations
Esempio n. 16
0
 def sub(self, rect, relative=False):
     """Returns a subcmap which is the cmap defined by the [start, end) rectangle."""
     new_tiles = {}
     if relative:
         offset = rect.start
     else:
         offset = Coord(0, 0)
     for c in rect.as_list():
         if c in self:
             new_tiles[c - offset] = self[c]
     # new cmap's dimensions are end-start
     return ConcreteMap(rect.size_coord(), _tiles=new_tiles), rect.start
Esempio n. 17
0
def find_all_rects_at(positions, pos, directions=coord_directions):
    assert pos in positions
    i_rect = Rect(pos, pos + Coord(1, 1))
    finished = set([i_rect])
    q = [i_rect]
    while len(q) > 0:
        r = q.pop()
        expands = expand_rect(positions, r, directions)
        for e in expands:
            if e not in finished:
                finished.add(e)
                q.append(e)
    return finished
Esempio n. 18
0
def parse_pattern(pattern_filename):
    f = open(pattern_filename, "r")
    tiles = {}
    max_tile = Coord(0, 0)
    max_area = 0
    for y, line in enumerate(f.readlines()):
        for x, entry in enumerate(line.split()):
            assert entry[0] == "["
            assert entry[-1] == "]"
            entry = entry[1:-1]
            es = entry.split(",")
            # Assume BTS
            if len(es) == 2:
                bts = 0
            elif len(es) == 3:
                bts = int(es[2], 16)
            else:
                assert False, "Bad entry: " + entry
            # Find the texture
            if es[0] == "_":
                texture = Texture(0, 0, is_any=True)
            else:
                tex_index, flips = find_flips(es[0])
                texture = Texture(tex_index, flips)
            # Find the type
            if es[1] == "_":
                tile_type = Type(0, 0, is_any=True)
            else:
                ty_index = int(es[1], 16)
                ty = Type(ty_index, bts)
            c = Coord(x, y)
            a = c.area()
            tiles[c] = Tile(texture, ty)
            if a >= max_area:
                max_tile = c
                max_area = a
    f.close()
    level = Level(max_tile + Coord(1, 1), tiles=tiles)
    return level
Esempio n. 19
0
def spring_model(node_locs, graph, n_iterations, spring_constant,
                 spring_equilibrium, dt, damping):
    """Changes node placement based on a simple spring model.
    Node locs is the dictionary of initial node placements and is edited by the function.
    graph is the graph of how the  nodes are connected to each other
        (i.e. where to place springs).
    n_iterations is how many time steps to run the model for.
        A few should suffice for my purposes.
    spring_constant is the k in -kx.
    spring_equilibrium is the distance where the spring is at rest.
    dt is the time step.
    Returns a lower potential-energy node_locs."""
    # node locs is node_name -> node_position
    # node_name -> node_velocity
    # using mcoords as a vector
    node_v = {n: Coord(0, 0) for n in node_locs}
    # node_name -> node_acceleration
    node_a = {n: Coord(0, 0) for n in node_locs}
    iteration = 0
    while iteration < n_iterations:
        for n in node_locs:
            # Update node_v
            for e in graph.nodes[n].edges:
                t = e.terminal
                # The amount of stretching, possibly negative
                x = euclidean(node_locs[n], node_locs[t]) - spring_equilibrium
                # Direction
                n_to_t = (node_locs[t] - node_locs[n]).to_unit()
                #-kx
                node_v[n] = node_v[n] + n_to_t.scale(spring_constant * x * dt)
                node_v[t] = node_v[t] + n_to_t.scale(-spring_constant * x * dt)
            # update node_locs
            node_locs[n] = node_locs[n] + node_v[n].scale(dt)
        iteration = iteration + 1
    # resolve to an int
    # TODO: is there a dict map function?
    node_locs = {n: node_locs[n].resolve_int() for n in node_locs}
    return node_locs
Esempio n. 20
0
def load_patterns(path):
    # Get all the filenames in path
    fnames = [
        f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))
    ]
    patterns = {}
    for f in fnames:
        name, ext = f.split(".")
        if ext == "txt":
            p = parse_pattern(os.path.join(path, f))
            #TODO: create a flag that can be written in the file for whether to include a flip
            patterns[name + "_l"] = p
            patterns[name + "_r"] = p.reflect(Coord(1, 0))
    return patterns
Esempio n. 21
0
def expand_rect(positions, rect, directions=coord_directions):
    """
    Helper for find_rect
    """
    #TODO: randomize order?
    rects = []
    for d in directions:
        if d < Coord(0, 0):
            r = Rect(rect.start + d, rect.end)
        else:
            r = Rect(rect.start, rect.end + d)
        # Need the resulting expanded rect to be a subset of the space
        if r.as_set() <= positions:
            rects.append(r)
    return rects
Esempio n. 22
0
def node_place(graph, dimensions, up_es, down_es, settings):
    """
    Find a placement for nodes where their respective areas do not violate
    each other.
        Random initially, with elevators guaranteed to be at the top and bottom (initially)
        Subjected to a spring model which reduces the total potential energy (moves far nodes closer)
        Subject to a search so that nodes can be placed without violating each other's areas.
    """
    initial = random_node_place(graph, dimensions, up_es, down_es)
    spring = spring_model(initial, graph, settings["n_iterations"],
                          settings["spring_constant"], settings["equilibrium"],
                          settings["spring_dt"], settings["spring_damping"])
    trunc_spring = {
        n: xy.truncate(Coord(0, 0), dimensions)
        for (n, xy) in spring.items()
    }
    # Now do a search for a good placement for each nod.
    cmap = ConcreteMap(dimensions)
    return find_placement(trunc_spring, cmap, up_es, down_es)
Esempio n. 23
0
def level_from_bytes(levelbytes, dimensions):
    # First two bytes are the amount of level1 data
    levelsize = int.from_bytes(levelbytes[0:2], byteorder='little')
    # Cut off the size
    levelbytes = levelbytes[2:]
    # Make sure everything matches
    assert levelsize % 2 == 0, "Purported level size {} is not even!".format(
        levelsize)
    assert levelsize == dimensions.x * dimensions.y * 2, "Level data length {} does not match specified room dimensions {}".format(
        levelsize, dimensions.x * dimensions.y * 2)
    # The level might not include level2 data
    if len(levelbytes) == int(2.5 * levelsize):
        has_level2 = True
    elif len(levelbytes) == int(1.5 * levelsize):
        has_level2 = False
    else:
        assert False, "Purported level size does not match actual level size"
    level = Level(dimensions)
    for y in range(dimensions.y):
        for x in range(dimensions.x):
            index = y * dimensions.x + x
            level1index = index * 2
            level1 = int.from_bytes(levelbytes[level1index:level1index + 2],
                                    byteorder='little')
            btsindex = index + levelsize
            bts = int.from_bytes(levelbytes[btsindex:btsindex + 1],
                                 byteorder='little')
            if has_level2:
                level2index = index + (3 * levelsize // 2)
                level2 = int.from_bytes(levelbytes[level2index:level2index +
                                                   2],
                                        byteorder='little')
            else:
                level2 = 0
            #TODO: level2 info dropped on the floor
            ttype = level1 >> 12
            hflip = (level1 >> 11) & 1
            vflip = (level1 >> 10) & 1
            tindex = level1 & 0b1111111111
            texture = Texture(tindex, (hflip, vflip))
            tiletype = Type(ttype, bts)
            level[Coord(x, y)] = Tile(texture, tiletype)
    return level
Esempio n. 24
0
def make_level_from_room(room_header):
    #TODO: is there a way to know which state is currently in use?
    state = room_header.state_chooser.default
    # Get layer1:
    larray = state.level_data.level_array
    layer1 = larray.layer1
    bts = larray.bts
    level_array = np.zeros(layer1.shape, dtype="int")
    it = np.nditer(layer1, flags=["multi_index", "refs_ok"])
    # Abstractify level data
    for tile in it:
        tile = tile.item()
        i = it.multi_index
        # Get the up and left tiles for v and h copy
        x = i[0]
        y = i[1]
        up = None
        left = None
        if y > 0:
            up = level_array[x, y-1]
        if x > 0:
            left = level_array[x-1, y]
        tile_bts = bts[i]
        level_array[i] = tile_to_abstract(tile, tile_bts, up, left, i)
    #TODO: May not use default FX depending on door
    #TODO: Door cap PLMs change level data!
    fx = state.fx.default_fx
    if fx is not None and fx.layer3_type in layer3_to_liquid:
        liquid_type = layer3_to_liquid[fx.layer3_type]
        #TODO - tile or pixel?
        liquid_level = fx.liquid_target_y // 16
    else:
        liquid_type = LiquidType.NONE
        liquid_level = None
    #TODO
    item_locations = get_item_locations(state.plms)
    level = LevelState(Coord(0,0), level_array, liquid_type, liquid_level, item_locations, {})
    return level
Esempio n. 25
0
def rectangularize(subroom_state, cmap):
    """
    Finds a set of initial rectangular subrooms for the given concrete map
    """
    position_order = list(cmap.keys())
    random.shuffle(position_order)
    positions = set(cmap.keys())
    # Gross way of getting the main set of tiles
    assert len(subroom_state.g.nodes.keys()) == 1
    # Main subroom is always initially subroom 0
    main_subroom = subroom_state[0]
    # Find rectangles until the entire cmap is covered
    rects = []
    print("RECT: Finding rectangles")
    while len(positions) > 0:
        pos = position_order[0]
        rect = find_rect_at(positions, pos)
        # Only need to create a subroom if the remaining cmap component that
        # contains it also contains other cells
        cmap_components = find_components(positions)
        r_set = rect.as_set()
        r_components = [c for c in cmap_components if r_set <= c]
        assert len(r_components) == 1
        r_component = r_components[0]
        #TODO: rects are not relative to the room position, but they should be...
        print(rect)
        rects.append(rect)
        if r_set < r_component:
            # Find the subroom id for the component in question
            t = next(iter(r_component)).scale(16) + Coord(8, 8)
            component_parent_sid = subroom_state.tile_to_subroom(t)
            rect_room = rect.scale(
                16).as_set() & subroom_state[component_parent_sid].tiles
            subroom_state.place_subroom(rect_room)
        # Update positions
        for c in rect:
            positions.remove(c)
            position_order.remove(c)
Esempio n. 26
0
 def paste(self, level_origin, level):
     assert self.origin + self.shape <= Coord(level.shape[0],
                                              level.shape[1])
     o = self.origin - level_origin
     assert o >= Coord(0, 0), o
     level[o.x:o.x + self.shape.x, o.y:o.y + self.shape.y] = self.level
Esempio n. 27
0
def get_fixed_tiles(room_header):
    """Compute the set of tiles that should be fixed for WFC for a given room header"""
    fixed_tiles = set()
    screen_map = set()
    all_states = room_header.all_states()
    #all_states = [s.state for s in room_header.state_chooser.conditions] + [room_header.state_chooser.default]
    shapes = [
        state.level_data.level_array.layer1.shape for state in all_states
    ]
    # Hopefully all level datas have the same shape...
    assert len(set(shapes)) == 1
    for state in all_states:
        # Add doors
        layer1 = state.level_data.level_array.layer1

        def inbounds(c):
            if c[0] < 0 or c[1] < 0:
                return False
            if c[0] >= layer1.shape[0] or c[1] >= layer1.shape[1]:
                return False
            return True

        it = np.nditer(layer1, flags=["multi_index", "refs_ok"])
        for tile in it:
            tile = tile.item()
            if tile.tile_type == 9:
                fixed_tiles.add(Coord(*it.multi_index))
        # Add all enemy positions
        #TODO: Some enemies may take up more than one tile...
        #TODO: PLMs may want to have more than one tile allocated too
        for enemy in state.enemy_list.enemies:
            e_pos = Coord(enemy.x_pos // 16, enemy.y_pos // 16)
            enemy_locale = get_cross(e_pos)
            for p in enemy_locale:
                if inbounds(p):
                    fixed_tiles.add(p)
        # Add PLM positions
        for plm in state.plms.l:
            plm_pos = Coord(plm.x_pos, plm.y_pos)
            plm_locale = get_cross(plm_pos)
            for p in plm_locale:
                if inbounds(p):
                    fixed_tiles.add(p)
        # Add screens that are either fully solid or fully air
        for x in range(layer1.shape[0] // 16):
            for y in range(layer1.shape[1] // 16):
                screen_x = x * 16
                screen_y = y * 16
                screen = layer1[screen_x:screen_x + 16, screen_y:screen_y + 16]
                test_solid = np.vectorize(lambda x: x.tile_type == 8)
                test_air = np.vectorize(lambda x: x.tile_type == 0)
                if np.all(test_solid(screen)) or np.all(test_air(screen)):
                    # Add this screen and its borders
                    for x_sub in range(-1, 17):
                        for y_sub in range(-1, 17):
                            c = (screen_x + x_sub, screen_y + y_sub)
                            if inbounds(c):
                                fixed_tiles.add(Coord(*c))
                # Add all "valid" screens to the screen map
                else:
                    screen_map.add(Coord(x, y))
        # Add level borders
        for x in range(layer1.shape[0]):
            fixed_tiles.add(Coord(x, 0))
            fixed_tiles.add(Coord(x, layer1.shape[1] - 1))
        for y in range(layer1.shape[1]):
            fixed_tiles.add(Coord(0, y))
            fixed_tiles.add(Coord(layer1.shape[0] - 1, y))
        #TODO: constrain scroll boundaries?
    return fixed_tiles, screen_map
Esempio n. 28
0
def level_from_bits(bits):
    new_bytes = bytes_from_bit_array(bits)
    new_arrays = level_array_from_bytes(new_bytes, Coord(*bits.shape[:-1]))
    return new_bytes, new_arrays
Esempio n. 29
0
 def __getitem__(self, index):
     internal_index = index - self.origin
     # Do not allow negative indexing
     if Coord(0, 0) > internal_index:
         raise IndexError(internal_index)
     return self.level[internal_index]
Esempio n. 30
0
 def horizontal_flip(self, level):
     position = self.position.flip_in_rect(level.rect, Coord(1, 0))
     v = self.velocity.horizontal_flip()
     i = self.items.copy()
     p = self.pose
     return SamusState(position, v, i, p)