Handle trimming and wrapping for large pieces

This commit is contained in:
Catobat
2026-02-05 21:11:24 +01:00
parent 086d1bbc85
commit f5d547cc5e

View File

@@ -449,6 +449,12 @@ def create_piece_list(world: World, player: int, options: LayoutGeneratorOptions
if world.mode[player] == 'standard': if world.mode[player] == 'standard':
piece_list = merge_pieces(piece_list, [[0x23, 0x24], [0x2B, 0x2C]], world, player, overworld_screens) piece_list = merge_pieces(piece_list, [[0x23, 0x24], [0x2B, 0x2C]], world, player, overworld_screens)
# Trim pieces by removing empty rows/columns on edges
piece_list = [trim_piece(piece) for piece in piece_list]
# Validate piece sizes and apply wrapping if needed
piece_list = validate_and_wrap_pieces(piece_list, options, world, player, overworld_screens)
# Phase 3: Add piece data # Phase 3: Add piece data
for piece in piece_list: for piece in piece_list:
add_piece_data(world, player, piece, large_screen_quadrant_info, large_screen_quadrant_info_land, large_screen_quadrant_info_water) add_piece_data(world, player, piece, large_screen_quadrant_info, large_screen_quadrant_info_land, large_screen_quadrant_info_water)
@@ -536,6 +542,59 @@ def get_piece_cells(piece: Piece) -> Set[int]:
cells.add(cell) cells.add(cell)
return cells return cells
def trim_piece(piece: Piece) -> Piece:
"""
Trim a piece by removing any full rows or columns on the edges that only consist of -1.
Adjusts position restrictions when present.
"""
# Find the bounds of non-empty cells
min_row, max_row = piece.height, -1
min_col, max_col = piece.width, -1
for i in range(piece.height):
for j in range(piece.width):
has_content = piece.main.grid[i][j] != -1
if piece.parallel:
has_content = has_content or piece.parallel.grid[i][j] != -1
if has_content:
min_row = min(min_row, i)
max_row = max(max_row, i)
min_col = min(min_col, j)
max_col = max(max_col, j)
if max_row < 0 or (min_row == 0 and max_row == piece.height - 1 and min_col == 0 and max_col == piece.width - 1):
return piece
new_height = max_row - min_row + 1
new_width = max_col - min_col + 1
piece.width = new_width
piece.height = new_height
# Trim piece
piece.main.grid = [row[min_col:max_col + 1] for row in piece.main.grid[min_row:max_row + 1]]
piece.main.screens = [row[min_col:max_col + 1] for row in piece.main.screens[min_row:max_row + 1]]
piece.main.width = new_width
piece.main.height = new_height
if piece.parallel:
piece.parallel.grid = [row[min_col:max_col + 1] for row in piece.parallel.grid[min_row:max_row + 1]]
piece.parallel.screens = [row[min_col:max_col + 1] for row in piece.parallel.screens[min_row:max_row + 1]]
piece.parallel.width = new_width
piece.parallel.height = new_height
# Adjust restrictions if present
if piece.restriction is not None:
adjusted_restrictions = []
for pos in piece.restriction:
old_row = pos // 8
old_col = pos % 8
new_row = (old_row + min_row) % 8
new_col = (old_col + min_col) % 8
adjusted_restrictions.append(new_row * 8 + new_col)
piece.restriction = adjusted_restrictions
return piece
def expand_arrangement(arrangement: List[List[int]], pieces: List[Piece]) -> List[List[int]]: def expand_arrangement(arrangement: List[List[int]], pieces: List[Piece]) -> List[List[int]]:
""" """
Expand an arrangement to include all cells from the pieces being merged. Expand an arrangement to include all cells from the pieces being merged.
@@ -546,19 +605,25 @@ def expand_arrangement(arrangement: List[List[int]], pieces: List[Piece]) -> Lis
Raises an exception if the relative positions of cells within pieces conflict Raises an exception if the relative positions of cells within pieces conflict
with the requested arrangement (e.g., contradictory merge operations). with the requested arrangement (e.g., contradictory merge operations).
Note: This function uses wrap-aware position checking. Positions that differ
by multiples of 8 are considered equivalent (for wrapping support). This allows
arrangements like [[0x10, 0x11, 0x12, 0x13, 0x14]] and [[0x14, 0x15, 0x16, 0x17, 0x10]]
to be merged into a valid horizontal loop.
""" """
# Build a mapping of cell_id -> (row, col) for all cells in all pieces # Build a mapping of cell_id -> (row, col) for all cells in all pieces
# relative to a common coordinate system # relative to a common coordinate system
cell_positions: Dict[int, Tuple[int, int]] = {} cell_positions: Dict[int, Tuple[int, int]] = {}
# Also track position -> cell_id to detect when two cells would occupy the same position # Track wrapped_position -> cell_id to detect when two different cells would occupy the same position after wrapping
position_to_cell: Dict[Tuple[int, int], int] = {} wrapped_position_to_cell: Dict[Tuple[int, int], int] = {}
# First, map cells from the original arrangement # First, map cells from the original arrangement
for i, row in enumerate(arrangement): for i, row in enumerate(arrangement):
for j, cell in enumerate(row): for j, cell in enumerate(row):
if cell != -1: if cell != -1:
cell_positions[cell] = (i, j) cell_positions[cell] = (i, j)
position_to_cell[(i, j)] = cell wrapped_pos = (i % 8, j % 8)
wrapped_position_to_cell[wrapped_pos] = cell
# For each piece, determine where its cells should go # For each piece, determine where its cells should go
for piece in pieces: for piece in pieces:
@@ -584,27 +649,33 @@ def expand_arrangement(arrangement: List[List[int]], pieces: List[Piece]) -> Lis
for j, cell in enumerate(row): for j, cell in enumerate(row):
if cell != -1: if cell != -1:
new_pos = (i + offset_row, j + offset_col) new_pos = (i + offset_row, j + offset_col)
# Normalize position for wrapping (positions differing by 8 are equivalent)
wrapped_pos = (new_pos[0] % 8, new_pos[1] % 8)
if cell in cell_positions: if cell in cell_positions:
# Cell already has a position - verify it's consistent # Cell already has a position - verify it's consistent after wrapping
if cell_positions[cell] != new_pos: existing_pos = cell_positions[cell]
existing_wrapped = (existing_pos[0] % 8, existing_pos[1] % 8)
if existing_wrapped != wrapped_pos:
raise GenerationException( raise GenerationException(
f"Cannot merge: cell 0x{cell:02X} has conflicting positions. " f"Cannot merge: cell 0x{cell:02X} has conflicting positions. "
f"Existing position {cell_positions[cell]} conflicts with " f"Existing position {existing_pos} (wrapped: {existing_wrapped}) conflicts with "
f"position {new_pos} from piece containing cells " f"position {new_pos} (wrapped: {wrapped_pos}) from piece containing cells "
f"{[c for row in piece.main.grid for c in row if c != -1]}. " f"{[c for row in piece.main.grid for c in row if c != -1]}. "
f"This indicates contradictory merge operations." f"This indicates contradictory merge operations."
) )
elif new_pos in position_to_cell: # Same cell at same wrapped position - this is fine (loop detected)
# Position is already occupied by a different cell elif wrapped_pos in wrapped_position_to_cell:
existing_cell = position_to_cell[new_pos] # Position is already occupied by a different cell after wrapping
existing_cell = wrapped_position_to_cell[wrapped_pos]
raise GenerationException( raise GenerationException(
f"Cannot merge: cell 0x{cell:02X} would be placed at position {new_pos}, " f"Cannot merge: cell 0x{cell:02X} would be placed at position {new_pos} "
f"but that position is already occupied by cell 0x{existing_cell:02X}. " f"(wrapped: {wrapped_pos}), but that position is already occupied by "
f"This indicates contradictory merge operations." f"cell 0x{existing_cell:02X}. This indicates contradictory merge operations."
) )
else: else:
cell_positions[cell] = new_pos cell_positions[cell] = new_pos
position_to_cell[new_pos] = cell wrapped_position_to_cell[wrapped_pos] = cell
# Find the bounding box of all cells # Find the bounding box of all cells
if not cell_positions: if not cell_positions:
@@ -684,10 +755,9 @@ def calculate_merged_restrictions(pieces: List[Piece], arrangement: List[List[in
for r in piece.restriction: for r in piece.restriction:
r_row = r // 8 r_row = r // 8
r_col = r % 8 r_col = r % 8
new_r_row = r_row - offset_row new_r_row = (r_row - offset_row) % 8
new_r_col = r_col - offset_col new_r_col = (r_col - offset_col) % 8
if 0 <= new_r_row < 8 and 0 <= new_r_col < 8: translated.append(new_r_row * 8 + new_r_col)
translated.append(new_r_row * 8 + new_r_col)
translated_restrictions.append(set(translated)) translated_restrictions.append(set(translated))
# Intersection of all translated restrictions # Intersection of all translated restrictions
@@ -756,6 +826,61 @@ def merge_pieces(piece_list: List[Piece], arrangement: List[List[int]], world: W
remaining_pieces.append(merged_piece) remaining_pieces.append(merged_piece)
return remaining_pieces return remaining_pieces
def validate_and_wrap_pieces(piece_list: List[Piece], options: LayoutGeneratorOptions, world: World, player: int, overworld_screens: Dict[int, Screen]) -> List[Piece]:
"""
Validate that all pieces are at most 8x8 in size.
If a piece is too large, attempt to reduce its size using wrapping.
"""
result_pieces = []
for piece in piece_list:
if piece.width <= 8 and piece.height <= 8:
result_pieces.append(piece)
continue
# Piece is too large, need to apply wrapping
if piece.width > 8 and not options.horizontal_wrap:
raise GenerationException(
f"Piece has width {piece.width} which exceeds 8, but horizontal wrapping is not enabled. "
f"Cells: {[c for row in piece.main.grid for c in row if c != -1]}"
)
if piece.height > 8 and not options.vertical_wrap:
raise GenerationException(
f"Piece has height {piece.height} which exceeds 8, but vertical wrapping is not enabled. "
f"Cells: {[c for row in piece.main.grid for c in row if c != -1]}"
)
# Calculate wrapped dimensions
wrapped_width = min(piece.width, 8)
wrapped_height = min(piece.height, 8)
# Create new wrapped grid, checking for conflicts
wrapped_grid = [[-1] * wrapped_width for _ in range(wrapped_height)]
for i in range(piece.height):
wrapped_i = i % 8
for j in range(piece.width):
wrapped_j = j % 8
cell = piece.main.grid[i][j]
if cell != -1:
existing = wrapped_grid[wrapped_i][wrapped_j]
if existing != -1 and existing != cell:
raise GenerationException(
f"Wrapping conflict: cell 0x{cell:02X} at position ({i}, {j}) "
f"would wrap to ({wrapped_i}, {wrapped_j}) which already contains cell 0x{existing:02X}. "
f"Piece cells: {[c for row in piece.main.grid for c in row if c != -1]}"
)
wrapped_grid[wrapped_i][wrapped_j] = cell
# Create the wrapped piece
wrapped_piece = create_piece(world, player, wrapped_grid, overworld_screens)
wrapped_piece.restriction = piece.restriction
result_pieces.append(wrapped_piece)
return result_pieces
def add_piece_data(world: World, player: int, piece: Piece, large_screen_quadrant_info: Dict[int, Dict], large_screen_quadrant_info_land: Dict[int, Dict], large_screen_quadrant_info_water: Dict[int, Dict]) -> None: def add_piece_data(world: World, player: int, piece: Piece, large_screen_quadrant_info: Dict[int, Dict], large_screen_quadrant_info_land: Dict[int, Dict], large_screen_quadrant_info_water: Dict[int, Dict]) -> None:
""" """
Add computed data to piece Add computed data to piece