Refactor how large screen quadrants are stored in grids

This commit is contained in:
Catobat
2026-02-01 12:15:08 +01:00
parent ad8dff3b2d
commit 87ba33852f
2 changed files with 252 additions and 229 deletions

View File

@@ -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]:

View File

@@ -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