diff --git a/source/overworld/LayoutGenerator.py b/source/overworld/LayoutGenerator.py index 467941ab..d2245ac7 100644 --- a/source/overworld/LayoutGenerator.py +++ b/source/overworld/LayoutGenerator.py @@ -1,4 +1,3 @@ -import copy import logging import RaceRandom as random import random as _random @@ -8,7 +7,7 @@ from OverworldShuffle import connect_two_way, validate_layout ENABLE_KEEP_SIMILAR_SPECIAL_HANDLING = False PREVENT_WRAPPED_LARGE_SCREENS = False -DRAW_IMAGE = False +DRAW_IMAGE = True large_screen_ids = [0x00, 0x03, 0x05, 0x18, 0x1B, 0x1E, 0x30, 0x35] + [0x40, 0x43, 0x45, 0x58, 0x5B, 0x5E, 0x70, 0x75] @@ -49,7 +48,7 @@ class WorldPiece: def __init__( self, - screens: List[List[Optional[Screen]]], + screens: Optional[List[List[Optional[Screen]]]] = None, grid: Optional[List[List[int]]] = None, width: int = 0, height: int = 0, @@ -62,7 +61,7 @@ class WorldPiece: west_edges_water: Optional[List[List[List[OWEdge]]]] = None, east_edges_water: Optional[List[List[List[OWEdge]]]] = None ): - self.screens = screens + self.screens = screens if screens is not None else [] self.grid = grid if grid is not None else [] self.width = width self.height = height @@ -154,7 +153,7 @@ class LayoutGeneratorOptions: """ Configuration options for layout generation. """ - __slots__ = ('horizontal_wrap', 'vertical_wrap', + __slots__ = ('horizontal_wrap', 'vertical_wrap', 'split_large_screens', 'large_screen_pool', 'distortion_chance', 'random_order', 'multi_choice', 'max_delay', 'first_ignore_bonus_points', 'penalty_full_edge_mismatch', 'penalty_partial_edge_mismatch', 'bonus_partial_edge_match', @@ -168,6 +167,7 @@ class LayoutGeneratorOptions: self, horizontal_wrap: bool = True, vertical_wrap: bool = True, + split_large_screens = False, large_screen_pool: bool = False, distortion_chance: float = 0.0, random_order: int = 0, @@ -194,6 +194,7 @@ class LayoutGeneratorOptions: ): self.horizontal_wrap = horizontal_wrap self.vertical_wrap = vertical_wrap + self.split_large_screens = split_large_screens self.large_screen_pool = large_screen_pool self.distortion_chance = distortion_chance self.random_order = random_order @@ -418,8 +419,8 @@ def create_piece_list(world: World, player: int, options: LayoutGeneratorOptions if castle_screen and central_bonk_screen and links_house_screen: piece = create_piece(world, player, [ - [0x1B, 0x1B], - [0x1B, 0x1B], + [0x1B, 0x1C], + [0x23, 0x24], [0x2B, 0x2C] ], overworld_screens) @@ -439,10 +440,18 @@ def create_piece_list(world: World, player: int, options: LayoutGeneratorOptions # Add large screens for screen in all_large_screens: if screen not in used_screens_set: - piece = create_piece(world, player, [[screen.id, screen.id], [screen.id, screen.id]], overworld_screens) - if options.large_screen_pool: - piece.restriction = [0x00, 0x03, 0x05, 0x18, 0x1B, 0x1E, 0x30, 0x35] - piece_list.append(piece) + base_id = screen.id + if options.split_large_screens: + for quadrant_offset in [0x00, 0x01, 0x08, 0x09]: + piece = create_piece(world, player, [[base_id + quadrant_offset]], overworld_screens) + if options.large_screen_pool: + piece.restriction = [large_screen_id + quadrant_offset for large_screen_id in [0x00, 0x03, 0x05, 0x18, 0x1B, 0x1E, 0x30, 0x35]] + piece_list.append(piece) + else: + piece = create_piece(world, player, [[base_id, base_id + 0x01], [base_id + 0x08, base_id + 0x09]], overworld_screens) + if options.large_screen_pool: + piece.restriction = [0x00, 0x03, 0x05, 0x18, 0x1B, 0x1E, 0x30, 0x35] + piece_list.append(piece) used_screens_set.add(screen) if world.owParallel[player]: used_screens_set.add(screen.parallel) @@ -478,43 +487,50 @@ def create_piece_list(world: World, player: int, options: LayoutGeneratorOptions for k in range(piece.height): for l in range(piece.width): piece.crossed_groups[k].append(-1) - screen_id = piece.main.grid[k][l] - if screen_id != -1: - piece.crossed_groups[k][l] = 1 if screen_id in crossed_group_b else 0 + screen = piece.main.screens[k][l] + if screen: + piece.crossed_groups[k][l] = 1 if screen.id in crossed_group_b else 0 else: if piece.parallel and piece.parallel.screens[k][l]: - piece.crossed_groups[k][l] = 1 if piece.parallel.grid[k][l] in crossed_group_b else 0 + piece.crossed_groups[k][l] = 1 if piece.parallel.screens[k][l].id in crossed_group_b else 0 return piece_list def create_piece(world: World, player: int, grid: List[List[int]], overworld_screens: Dict[int, Screen]) -> Piece: """ - Create piece from grid of screen IDs - Takes 2D array of screen IDs and creates main and parallel pieces + Create piece from grid of cell IDs + Takes 2D array of cell IDs and creates main and parallel pieces """ piece = Piece( - main=WorldPiece(screens=[]), + main=WorldPiece(), width=len(grid[0]), height=len(grid) ) if world.owParallel[player]: - piece.parallel = WorldPiece(screens=[]) + piece.parallel = WorldPiece() found_screens = set() for i in range(piece.height): new_row = [] + new_screen_row = [] new_row_parallel = [] - piece.main.screens.append(new_row) + new_screen_row_parallel = [] + piece.main.grid.append(new_row) + piece.main.screens.append(new_screen_row) if world.owParallel[player]: - piece.parallel.screens.append(new_row_parallel) + piece.parallel.grid.append(new_row_parallel) + piece.parallel.screens.append(new_screen_row_parallel) for j in range(piece.width): - screen = overworld_screens.get(grid[i][j]) - new_row.append(screen) - if world.owParallel[player]: - new_row_parallel.append(screen.parallel if screen else None) + cell_id = grid[i][j] + new_row.append(cell_id) + screen = overworld_screens.get(get_screen_id_from_cell(cell_id)) + new_screen_row.append(screen) + if world.owParallel[player] and screen: + new_row_parallel.append(cell_id - screen.id + screen.parallel.id) + new_screen_row_parallel.append(screen.parallel) if screen and screen not in found_screens: found_screens.add(screen) @@ -532,14 +548,14 @@ def create_piece(world: World, player: int, grid: List[List[int]], overworld_scr 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 - Calls add_piece_grid_info for main and parallel pieces + Calls add_world_piece_edge_info for main and parallel pieces """ num_pieces = 2 if piece.parallel else 1 for p in range(num_pieces): world_piece = piece.main if p == 0 else piece.parallel - world_piece.width = len(world_piece.screens[0]) - world_piece.height = len(world_piece.screens) - add_piece_grid_info(world, player, world_piece, large_screen_quadrant_info, large_screen_quadrant_info_land, large_screen_quadrant_info_water) + world_piece.width = piece.width + world_piece.height = piece.height + add_world_piece_edge_info(world, player, world_piece, large_screen_quadrant_info, large_screen_quadrant_info_land, large_screen_quadrant_info_water) piece.width = piece.main.width piece.height = piece.main.height @@ -572,12 +588,11 @@ def add_piece_data(world: World, player: int, piece: Piece, large_screen_quadran piece.edge_sides = 0 piece.max_edges_per_side = 0 -def add_piece_grid_info(world: World, player: int, piece: WorldPiece, 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_world_piece_edge_info(world: World, player: int, piece: WorldPiece, large_screen_quadrant_info: Dict[int, Dict], large_screen_quadrant_info_land: Dict[int, Dict], large_screen_quadrant_info_water: Dict[int, Dict]) -> None: """ Populate piece edge information Initializes 8x8 edge arrays and extracts edges from screens """ - piece.grid = [[] for _ in range(8)] piece.north_edges = [[] for _ in range(8)] piece.south_edges = [[] for _ in range(8)] piece.west_edges = [[] for _ in range(8)] @@ -591,7 +606,6 @@ def add_piece_grid_info(world: World, player: int, piece: WorldPiece, large_scre for k in range(piece.height): for l in range(piece.width): - piece.grid[k].append(piece.screens[k][l].id if piece.screens[k][l] else -1) piece.north_edges[k].append([]) piece.south_edges[k].append([]) piece.west_edges[k].append([]) @@ -603,38 +617,41 @@ def add_piece_grid_info(world: World, player: int, piece: WorldPiece, large_scre piece.west_edges_water[k].append([]) piece.east_edges_water[k].append([]) - done_large = set() for k in range(piece.height): for l in range(piece.width): screen = piece.screens[k][l] if not screen: continue + cell_id = piece.grid[k][l] + if screen.big: - if screen.id not in done_large: - done_large.add(screen.id) - quadrant_info = (large_screen_quadrant_info[screen.id] if world.owTerrain[player] - else large_screen_quadrant_info_land[screen.id]) + # Determine quadrant by subtracting cell ID from screen ID + # 0x00 = NW (top-left), 0x01 = NE (top-right), 0x08 = SW (bottom-left), 0x09 = SE (bottom-right) + quadrant_offset = cell_id - screen.id + if quadrant_offset == 0x00: + quadrant_name = "NW" + elif quadrant_offset == 0x01: + quadrant_name = "NE" + elif quadrant_offset == 0x08: + quadrant_name = "SW" + else: + quadrant_name = "SE" - piece.north_edges[k][l] = [e for e in quadrant_info["NW"][Direction.North] if not e.dest] - piece.north_edges[k][l + 1] = [e for e in quadrant_info["NE"][Direction.North] if not e.dest] - piece.south_edges[k + 1][l] = [e for e in quadrant_info["SW"][Direction.South] if not e.dest] - piece.south_edges[k + 1][l + 1] = [e for e in quadrant_info["SE"][Direction.South] if not e.dest] - piece.west_edges[k][l] = [e for e in quadrant_info["NW"][Direction.West] if not e.dest] - piece.west_edges[k + 1][l] = [e for e in quadrant_info["SW"][Direction.West] if not e.dest] - piece.east_edges[k][l + 1] = [e for e in quadrant_info["NE"][Direction.East] if not e.dest] - piece.east_edges[k + 1][l + 1] = [e for e in quadrant_info["SE"][Direction.East] if not e.dest] + quadrant_info = (large_screen_quadrant_info[screen.id] if world.owTerrain[player] + else large_screen_quadrant_info_land[screen.id]) - if not world.owTerrain[player]: - quadrant_info = large_screen_quadrant_info_water[screen.id] - piece.north_edges_water[k][l] = [e for e in quadrant_info["NW"][Direction.North] if not e.dest] - piece.north_edges_water[k][l + 1] = [e for e in quadrant_info["NE"][Direction.North] if not e.dest] - piece.south_edges_water[k + 1][l] = [e for e in quadrant_info["SW"][Direction.South] if not e.dest] - piece.south_edges_water[k + 1][l + 1] = [e for e in quadrant_info["SE"][Direction.South] if not e.dest] - piece.west_edges_water[k][l] = [e for e in quadrant_info["NW"][Direction.West] if not e.dest] - piece.west_edges_water[k + 1][l] = [e for e in quadrant_info["SW"][Direction.West] if not e.dest] - piece.east_edges_water[k][l + 1] = [e for e in quadrant_info["NE"][Direction.East] if not e.dest] - piece.east_edges_water[k + 1][l + 1] = [e for e in quadrant_info["SE"][Direction.East] if not e.dest] + piece.north_edges[k][l] = [e for e in quadrant_info[quadrant_name][Direction.North] if not e.dest] + piece.south_edges[k][l] = [e for e in quadrant_info[quadrant_name][Direction.South] if not e.dest] + piece.west_edges[k][l] = [e for e in quadrant_info[quadrant_name][Direction.West] if not e.dest] + piece.east_edges[k][l] = [e for e in quadrant_info[quadrant_name][Direction.East] if not e.dest] + + if not world.owTerrain[player]: + quadrant_info_water = large_screen_quadrant_info_water[screen.id] + piece.north_edges_water[k][l] = [e for e in quadrant_info_water[quadrant_name][Direction.North] if not e.dest] + piece.south_edges_water[k][l] = [e for e in quadrant_info_water[quadrant_name][Direction.South] if not e.dest] + piece.west_edges_water[k][l] = [e for e in quadrant_info_water[quadrant_name][Direction.West] if not e.dest] + piece.east_edges_water[k][l] = [e for e in quadrant_info_water[quadrant_name][Direction.East] if not e.dest] else: for edge in sorted(screen.edges.values(), key=lambda e: e.midpoint): if not edge.dest: @@ -651,6 +668,19 @@ def add_piece_grid_info(world: World, player: int, piece: WorldPiece, large_scre target = piece.east_edges[k][l] if world.owTerrain[player] or edge.terrain != Terrain.Water else piece.east_edges_water[k][l] target.append(edge) +def get_screen_id_from_cell(cell_id: int) -> int: + """Get the base screen ID from a cell ID. + + For large screens, returns the top-left corner ID. + For small screens, returns the cell ID unchanged. + """ + base_id = cell_id & 0xBF # Remove world bit if present + # Check if this is a quadrant of a large screen + for large_id in large_screen_ids: + if base_id in [large_id, large_id + 0x01, large_id + 0x08, large_id + 0x09]: + return large_id | (cell_id & 0x40) # Preserve world bit + return cell_id + # ============================================================================ # PLACEMENT ALGORITHM # ============================================================================ @@ -1316,11 +1346,22 @@ def format_grid_for_spoiler(grid: List[List[int]]) -> str: return "\n".join(lines) def is_same_large_screen(grid: List[List[int]], row1: int, col1: int, row2: int, col2: int) -> bool: - id1 = grid[row1 % 8][col1 % 8] - id2 = grid[row2 % 8][col2 % 8] + """Checks if two adjacent cells belong to the same large screen with correct quadrant positions.""" + id1, id2 = grid[row1 % 8][col1 % 8], grid[row2 % 8][col2 % 8] if id1 == -1 or id2 == -1: return False - return id1 == id2 and id1 in large_screen_ids + base1, base2 = get_screen_id_from_cell(id1), get_screen_id_from_cell(id2) + if base1 != base2 or base1 not in large_screen_ids: + return False + # Get quadrant offsets (0x00=NW, 0x01=NE, 0x08=SW, 0x09=SE) + q1, q2 = (id1 & 0xBF) - (base1 & 0xBF), (id2 & 0xBF) - (base2 & 0xBF) + # Swap if cell2 is before cell1 + if col1 > col2 or row1 > row2: + q1, q2 = q2, q1 + # Check valid adjacency: east (0x00->0x01, 0x08->0x09) or south (0x00->0x08, 0x01->0x09) + if col1 != col2: + return (q1, q2) in [(0x00, 0x01), (0x08, 0x09)] + return (q1, q2) in [(0x00, 0x08), (0x01, 0x09)] # ============================================================================ # MAIN EXECUTION @@ -1347,6 +1388,7 @@ def generate_random_grid_layout(world: World, player: int, connected_edges: List options = LayoutGeneratorOptions( horizontal_wrap=horizontal_wrap, vertical_wrap=vertical_wrap, + split_large_screens=False, large_screen_pool=False, distortion_chance=0.0, random_order=6 if world.owParallel[player] else 12, @@ -1385,19 +1427,9 @@ def generate_random_grid_layout(world: World, player: int, connected_edges: List connect_edges_for_screen_layout(world, player, result.grid_info, options, connected_edges, prio_edges, overworld_screens, True) grid = result.grid_info.grid - # Make new grid containing cell IDs for the overworld map - map_grid = copy.deepcopy(grid) - for w in range(2): - for i in range(8): - for j in range(8): - screen_id = map_grid[w][i][j] - if screen_id in large_screen_ids and map_grid[w][i][(j + 1) % 8] == screen_id and map_grid[w][(i + 1) % 8][j] == screen_id and map_grid[w][(i + 1) % 8][(j + 1) % 8] == screen_id: - map_grid[w][i][(j + 1) % 8] = screen_id + 0x01 - map_grid[w][(i + 1) % 8][j] = screen_id + 0x08 - map_grid[w][(i + 1) % 8][(j + 1) % 8] = screen_id + 0x09 - world.owgrid[player] = map_grid - world.owlayoutmap_lw[player] = {id & 0xBF: i for i, id in enumerate(sum(map_grid[0], []))} - world.owlayoutmap_dw[player] = {id & 0xBF: i for i, id in enumerate(sum(map_grid[1], []))} + world.owgrid[player] = grid + world.owlayoutmap_lw[player] = {id & 0xBF: i for i, id in enumerate(sum(grid[0], []))} + world.owlayoutmap_dw[player] = {id & 0xBF: i for i, id in enumerate(sum(grid[1], []))} world.spoiler.set_map('layout_grid_lw', format_grid_for_spoiler(grid[0]), grid[0], player) if not world.owParallel[player]: diff --git a/source/overworld/LayoutVisualizer.py b/source/overworld/LayoutVisualizer.py index 8dbdea34..f76ddc58 100644 --- a/source/overworld/LayoutVisualizer.py +++ b/source/overworld/LayoutVisualizer.py @@ -4,7 +4,18 @@ from datetime import datetime from typing import Dict, List from PIL import Image, ImageDraw from BaseClasses import Direction, OWEdge -from source.overworld.LayoutGenerator import Screen +from source.overworld.LayoutGenerator import Screen, get_screen_id_from_cell + +def get_quadrant_from_cell_id(cell_id: int, screen_id: int) -> str: + offset = (cell_id & 0xBF) - (screen_id & 0xBF) + if offset == 0x00: + return "NW" + elif offset == 0x01: + return "NE" + elif offset == 0x08: + return "SW" + else: + return "SE" def get_edge_lists(grid: List[List[List[int]]], overworld_screens: Dict[int, Screen], @@ -13,7 +24,7 @@ def get_edge_lists(grid: List[List[List[int]]], Get list of edges for each cell and direction. Args: - grid: 3D list [world][row][col] containing screen IDs + grid: 3D list [world][row][col] containing cell IDs overworld_screens: Dict of screen_id -> Screen objects large_screen_quadrant_info: Dict of screen_id -> quadrant info for large screens @@ -24,47 +35,27 @@ def get_edge_lists(grid: List[List[List[int]]], GRID_SIZE = 8 edge_lists = {} - # Large screen base IDs - large_screen_base_ids = [0x00, 0x03, 0x05, 0x18, 0x1B, 0x1E, 0x30, 0x35, - 0x40, 0x43, 0x45, 0x58, 0x5B, 0x5E, 0x70, 0x75] - for world_idx in range(2): - # Build a map of screen_id -> list of (row, col) positions for large screens - large_screen_positions = {} for row in range(GRID_SIZE): for col in range(GRID_SIZE): - screen_id = grid[world_idx][row][col] - if screen_id != -1 and screen_id in large_screen_base_ids: - if screen_id not in large_screen_positions: - large_screen_positions[screen_id] = [] - large_screen_positions[screen_id].append((row, col)) + cell_id = grid[world_idx][row][col] - for row in range(GRID_SIZE): - for col in range(GRID_SIZE): - screen_id = grid[world_idx][row][col] - - if screen_id == -1: + if cell_id == -1: # Empty cell - no edges for direction in [Direction.North, Direction.South, Direction.East, Direction.West]: edge_lists[(world_idx, row, col, direction)] = [] continue + screen_id = get_screen_id_from_cell(cell_id) screen = overworld_screens.get(screen_id) if not screen: for direction in [Direction.North, Direction.South, Direction.East, Direction.West]: edge_lists[(world_idx, row, col, direction)] = [] continue - is_large = screen_id in large_screen_base_ids - - if is_large: - # For large screens, determine which quadrant this cell is - # Find all positions of this large screen and determine quadrant - positions = large_screen_positions.get(screen_id, [(row, col)]) - - # Determine quadrant by finding relative position - # The quadrant is determined by which cells are adjacent - quadrant = determine_large_screen_quadrant(row, col, positions, GRID_SIZE) + if screen.big: + # For large screens, determine quadrant from cell ID + quadrant = get_quadrant_from_cell_id(cell_id, screen_id) # Get edges for this quadrant if screen_id in large_screen_quadrant_info: @@ -85,45 +76,6 @@ def get_edge_lists(grid: List[List[List[int]]], return edge_lists -def determine_large_screen_quadrant(row: int, col: int, positions: List[tuple], grid_size: int) -> str: - """ - Determine which quadrant (NW, NE, SW, SE) a cell is in for a large screen. - Handles wrapping correctly by checking adjacency patterns. - - Args: - row: Current cell row - col: Current cell column - positions: List of all (row, col) positions for this large screen - grid_size: Size of the grid (8) - - Returns: - Quadrant string: "NW", "NE", "SW", or "SE" - """ - positions_set = set(positions) - - # Check which adjacent cells also belong to this large screen - has_right = ((row, (col + 1) % grid_size) in positions_set) - has_below = (((row + 1) % grid_size, col) in positions_set) - has_left = ((row, (col - 1) % grid_size) in positions_set) - has_above = (((row - 1) % grid_size, col) in positions_set) - - # Determine quadrant based on adjacency - # NW: has right and below neighbors - # NE: has left and below neighbors - # SW: has right and above neighbors - # SE: has left and above neighbors - - if has_right and has_below: - return "NW" - elif has_left and has_below: - return "NE" - elif has_right and has_above: - return "SW" - elif has_left and has_above: - return "SE" - else: - raise Exception("?") - def is_crossed_edge(edge: OWEdge, overworld_screens: Dict[int, Screen]) -> bool: if edge.dest is None: return False @@ -132,6 +84,40 @@ def is_crossed_edge(edge: OWEdge, overworld_screens: Dict[int, Screen]) -> bool: dest_screen = overworld_screens.get(edge.dest.owIndex) return source_screen.dark_world != dest_screen.dark_world +def are_large_screen_cells_connected(cell_id1: int, cell_id2: int, quadrant1: str, quadrant2: str, direction: str) -> bool: + """ + Check if two cells of a large screen are connected (should have no border between them). + + For cells to be connected: + 1. They must be from the same large screen (same base screen ID) + 2. Their quadrants must be adjacent in the expected direction + + Args: + cell_id1: Cell ID of the first cell + cell_id2: Cell ID of the second cell + quadrant1: Quadrant of the first cell ("NW", "NE", "SW", "SE") + quadrant2: Quadrant of the second cell + direction: Direction from cell1 to cell2 ("east", "south") + + Returns: + True if the cells should have no border between them + """ + # Must be from the same large screen + screen_id1 = get_screen_id_from_cell(cell_id1) + screen_id2 = get_screen_id_from_cell(cell_id2) + if screen_id1 != screen_id2: + return False + + # Check if quadrants are properly adjacent + if direction == "east": + # For east connection: NW->NE or SW->SE + return (quadrant1 == "NW" and quadrant2 == "NE") or (quadrant1 == "SW" and quadrant2 == "SE") + elif direction == "south": + # For south connection: NW->SW or NE->SE + return (quadrant1 == "NW" and quadrant2 == "SW") or (quadrant1 == "NE" and quadrant2 == "SE") + + return False + def visualize_layout(grid: List[List[List[int]]], output_dir: str, overworld_screens: Dict[int, Screen], large_screen_quadrant_info: Dict[int, Dict]) -> None: @@ -162,78 +148,42 @@ def visualize_layout(grid: List[List[List[int]]], output_dir: str, output_height = world_height output_img = Image.new('RGB', (output_width, output_height), color='black') - # Large screen base IDs (defined once for reuse) - large_screen_base_ids = [0x00, 0x03, 0x05, 0x18, 0x1B, 0x1E, 0x30, 0x35, - 0x40, 0x43, 0x45, 0x58, 0x5B, 0x5E, 0x70, 0x75] - # Process both worlds for world_idx in range(2): x_offset = 0 if world_idx == 0 else (world_width + gap) - # Build a map of screen_id -> list of (row, col) positions for large screens - large_screen_positions = {} - for row in range(GRID_SIZE): - for col in range(GRID_SIZE): - screen_id = grid[world_idx][row][col] - if screen_id != -1 and screen_id in large_screen_base_ids: - if screen_id not in large_screen_positions: - large_screen_positions[screen_id] = [] - large_screen_positions[screen_id].append((row, col)) - # Process each cell in the grid individually - # This handles wrapped large screens correctly by drawing each quadrant separately for row in range(GRID_SIZE): for col in range(GRID_SIZE): - screen_id = grid[world_idx][row][col] + cell_id = grid[world_idx][row][col] - if screen_id == -1: + if cell_id == -1: # Empty cell - fill with black (already black from initialization) continue - is_large = screen_id in large_screen_base_ids + screen_id = get_screen_id_from_cell(cell_id) + screen = overworld_screens.get(screen_id) + if not screen: + continue - # Calculate source position in the world image - source_row = (screen_id % 0x40) >> 3 - source_col = screen_id % 0x08 - world_img = lightworld_img if screen_id < 0x40 else darkworld_img + is_large = screen.big - if is_large: - # For large screens, determine which quadrant this cell represents - positions = large_screen_positions.get(screen_id, [(row, col)]) - quadrant = determine_large_screen_quadrant(row, col, positions, GRID_SIZE) + # Calculate source position in the world image based on cell_id + # For large screens, cell_id already encodes the quadrant position + source_row = (cell_id % 0x40) >> 3 + source_col = cell_id % 0x08 + world_img = lightworld_img if cell_id < 0x40 else darkworld_img - # Map quadrant to source offset within the 2x2 large screen - quadrant_offsets = { - "NW": (0, 0), - "NE": (1, 0), - "SW": (0, 1), - "SE": (1, 1) - } - q_col_offset, q_row_offset = quadrant_offsets[quadrant] + source_x = source_col * SOURCE_CELL_SIZE + source_y = source_row * SOURCE_CELL_SIZE - # Calculate source position for this quadrant - source_x = (source_col + q_col_offset) * SOURCE_CELL_SIZE - source_y = (source_row + q_row_offset) * SOURCE_CELL_SIZE - - # Crop single cell from source (the specific quadrant) - cropped = world_img.crop(( - source_x, - source_y, - source_x + SOURCE_CELL_SIZE, - source_y + SOURCE_CELL_SIZE - )) - else: - # Small screen (1x1) - source_x = source_col * SOURCE_CELL_SIZE - source_y = source_row * SOURCE_CELL_SIZE - - # Crop single cell from source - cropped = world_img.crop(( - source_x, - source_y, - source_x + SOURCE_CELL_SIZE, - source_y + SOURCE_CELL_SIZE - )) + # Crop single cell from source + cropped = world_img.crop(( + source_x, + source_y, + source_x + SOURCE_CELL_SIZE, + source_y + SOURCE_CELL_SIZE + )) # Resize to output size (64x64 pixels) resized = cropped.resize( @@ -257,52 +207,93 @@ def visualize_layout(grid: List[List[List[int]]], output_dir: str, for world_idx in range(2): x_offset = 0 if world_idx == 0 else (world_width + gap) - # Build large screen positions map for this world - large_screen_positions = {} - for row in range(GRID_SIZE): - for col in range(GRID_SIZE): - screen_id = grid[world_idx][row][col] - if screen_id != -1 and screen_id in large_screen_base_ids: - if screen_id not in large_screen_positions: - large_screen_positions[screen_id] = [] - large_screen_positions[screen_id].append((row, col)) - # Draw borders for each cell + # For large screens, only draw borders where cells are not connected for row in range(GRID_SIZE): for col in range(GRID_SIZE): - screen_id = grid[world_idx][row][col] + cell_id = grid[world_idx][row][col] - if screen_id == -1: + if cell_id == -1: continue - is_large = screen_id in large_screen_base_ids + screen_id = get_screen_id_from_cell(cell_id) + screen = overworld_screens.get(screen_id) + if not screen: + continue + + is_large = screen.big dest_x = x_offset + col * OUTPUT_CELL_SIZE dest_y = row * OUTPUT_CELL_SIZE if is_large: - # For large screens, determine which quadrant this cell is - positions = large_screen_positions.get(screen_id, [(row, col)]) - quadrant = determine_large_screen_quadrant(row, col, positions, GRID_SIZE) + quadrant = get_quadrant_from_cell_id(cell_id, screen_id) - # Draw border only on the outer edges of the large screen - # (not on internal edges between quadrants) - # NW: draw top and left borders - # NE: draw top and right borders - # SW: draw bottom and left borders - # SE: draw bottom and right borders - - if quadrant in ["NW", "NE"]: - # Draw top border - draw.line([(dest_x, dest_y), (dest_x + OUTPUT_CELL_SIZE - 1, dest_y)], fill='black', width=BORDER_WIDTH) + # Check each border direction + # Top border: draw if this is a north quadrant OR if the cell above is not connected + draw_top = True if quadrant in ["SW", "SE"]: - # Draw bottom border - draw.line([(dest_x, dest_y + OUTPUT_CELL_SIZE - 1), (dest_x + OUTPUT_CELL_SIZE - 1, dest_y + OUTPUT_CELL_SIZE - 1)], fill='black', width=BORDER_WIDTH) - if quadrant in ["NW", "SW"]: - # Draw left border - draw.line([(dest_x, dest_y), (dest_x, dest_y + OUTPUT_CELL_SIZE - 1)], fill='black', width=BORDER_WIDTH) + # Check if cell above is connected + above_row = (row - 1) % GRID_SIZE + above_cell_id = grid[world_idx][above_row][col] + if above_cell_id != -1: + above_screen_id = get_screen_id_from_cell(above_cell_id) + above_screen = overworld_screens.get(above_screen_id) + if above_screen and above_screen.big: + above_quadrant = get_quadrant_from_cell_id(above_cell_id, above_screen_id) + if are_large_screen_cells_connected(above_cell_id, cell_id, above_quadrant, quadrant, "south"): + draw_top = False + + # Bottom border: draw if this is a south quadrant OR if the cell below is not connected + draw_bottom = True + if quadrant in ["NW", "NE"]: + # Check if cell below is connected + below_row = (row + 1) % GRID_SIZE + below_cell_id = grid[world_idx][below_row][col] + if below_cell_id != -1: + below_screen_id = get_screen_id_from_cell(below_cell_id) + below_screen = overworld_screens.get(below_screen_id) + if below_screen and below_screen.big: + below_quadrant = get_quadrant_from_cell_id(below_cell_id, below_screen_id) + if are_large_screen_cells_connected(cell_id, below_cell_id, quadrant, below_quadrant, "south"): + draw_bottom = False + + # Left border: draw if this is a west quadrant OR if the cell to the left is not connected + draw_left = True if quadrant in ["NE", "SE"]: - # Draw right border + # Check if cell to the left is connected + left_col = (col - 1) % GRID_SIZE + left_cell_id = grid[world_idx][row][left_col] + if left_cell_id != -1: + left_screen_id = get_screen_id_from_cell(left_cell_id) + left_screen = overworld_screens.get(left_screen_id) + if left_screen and left_screen.big: + left_quadrant = get_quadrant_from_cell_id(left_cell_id, left_screen_id) + if are_large_screen_cells_connected(left_cell_id, cell_id, left_quadrant, quadrant, "east"): + draw_left = False + + # Right border: draw if this is an east quadrant OR if the cell to the right is not connected + draw_right = True + if quadrant in ["NW", "SW"]: + # Check if cell to the right is connected + right_col = (col + 1) % GRID_SIZE + right_cell_id = grid[world_idx][row][right_col] + if right_cell_id != -1: + right_screen_id = get_screen_id_from_cell(right_cell_id) + right_screen = overworld_screens.get(right_screen_id) + if right_screen and right_screen.big: + right_quadrant = get_quadrant_from_cell_id(right_cell_id, right_screen_id) + if are_large_screen_cells_connected(cell_id, right_cell_id, quadrant, right_quadrant, "east"): + draw_right = False + + # Draw the borders + if draw_top: + draw.line([(dest_x, dest_y), (dest_x + OUTPUT_CELL_SIZE - 1, dest_y)], fill='black', width=BORDER_WIDTH) + if draw_bottom: + draw.line([(dest_x, dest_y + OUTPUT_CELL_SIZE - 1), (dest_x + OUTPUT_CELL_SIZE - 1, dest_y + OUTPUT_CELL_SIZE - 1)], fill='black', width=BORDER_WIDTH) + if draw_left: + draw.line([(dest_x, dest_y), (dest_x, dest_y + OUTPUT_CELL_SIZE - 1)], fill='black', width=BORDER_WIDTH) + if draw_right: draw.line([(dest_x + OUTPUT_CELL_SIZE - 1, dest_y), (dest_x + OUTPUT_CELL_SIZE - 1, dest_y + OUTPUT_CELL_SIZE - 1)], fill='black', width=BORDER_WIDTH) else: # Small screen - draw border around single cell @@ -315,8 +306,8 @@ def visualize_layout(grid: List[List[List[int]]], output_dir: str, # Draw edge connection indicators for each cell for row in range(GRID_SIZE): for col in range(GRID_SIZE): - screen_id = grid[world_idx][row][col] - if screen_id == -1: + cell_id = grid[world_idx][row][col] + if cell_id == -1: continue dest_x = x_offset + col * OUTPUT_CELL_SIZE