/** * maps.js - PNG-based world maps with wall coordinate arrays * * Each map has a pre-rendered PNG background image and defines: * - walls: Set of "x,y" strings for collision (built from coordinate data) * - npcs, interactions, exits as position-based objects * * Map images are drawn at 3x scale (16px native → 48px on screen) */ // ==================== Wall builder helpers ==================== function buildWallSet(widthTiles, heightTiles, wallData) { const set = new Set(); for (const [row, cols] of Object.entries(wallData)) { for (const col of cols) { set.add(`${col},${row}`); } } return set; } function wallRange(from, to) { const cols = []; for (let c = from; c <= to; c++) cols.push(c); return cols; } // ==================== Map definitions ==================== /** * LAB (10×12 tiles — lab.png 160×192) * * Visual layout from PNG: * Row 0: Top wall — machinery/equipment * Row 1: Machines on left, big computer right * Row 2: Open area, desk/machine on far right * Row 3-4: Table in center-left area * Row 5: Open corridor * Row 6-7: Two rows of bookshelves with gap in middle (col 4) * Row 8-9: Open floor * Row 10: Open floor near exit * Row 11: Bottom wall, door at cols 4-5 */ const labWalls = { 0: wallRange(0, 9), // solid top wall 1: [0, 1, 7, 8, 9], // machines on sides 2: [0, 7, 8, 9], // desk/machine right 3: [0, 2, 3, 9], // table center-left 4: [0, 2, 3, 9], // table continues 5: [0, 9], // open corridor 6: [0, 1, 2, 6, 7, 8, 9], // bookshelves, gap at 3-5 7: [0, 1, 2, 6, 7, 8, 9], // bookshelves, gap at 3-5 8: [0, 9], // open 9: [0, 9], // open 10: [0, 9], // open 11: [0, 1, 2, 3, 6, 7, 8, 9] // bottom wall, door at 4-5 }; const labMap = { id: 'lab', name: 'Circuit Lab', image: 'map:lab', widthTiles: 10, heightTiles: 12, spawn: { x: 4, y: 10 }, wallSet: buildWallSet(10, 12, labWalls), exits: [ // Exit door — appear one tile BELOW the town door (not ON it, to avoid re-trigger) { x: 4, y: 11, targetMap: 'town', targetX: 10, targetY: 8 }, { x: 5, y: 11, targetMap: 'town', targetX: 10, targetY: 8 } ], npcs: [ { id: 'professor', x: 5, y: 1, facing: 'down', dialog: [ 'Welcome to the Circuit Lab!', 'I\'m the Professor. We study logic gates here.', 'Use the workshop tables to design circuits.', 'Press TAB to open the Workshop anytime!' ] } ], interactions: [ // Workshop tables (the table at rows 3-4) { x: 2, y: 3, type: 'workshop', label: 'Workshop Table' }, { x: 3, y: 3, type: 'workshop', label: 'Workshop Table' }, { x: 2, y: 4, type: 'workshop', label: 'Workshop Table' }, { x: 3, y: 4, type: 'workshop', label: 'Workshop Table' }, // Bookshelves (interact from adjacent) { x: 1, y: 6, type: 'sign', label: 'Bookshelf', dialog: ['A collection of logic circuit manuals.'] }, { x: 7, y: 6, type: 'sign', label: 'Bookshelf', dialog: ['Advanced boolean algebra textbooks.'] }, // Machines at top { x: 8, y: 1, type: 'terminal', label: 'Terminal', dialog: ['Circuit analysis terminal.', 'Connect components to solve puzzles.'] }, // Puzzle door (machine at top-right) { x: 8, y: 2, type: 'puzzle_door', puzzleId: 'lab_door_1', requiredOutputs: [1, 0, 1, 1], label: 'Locked Door' } ] }; /** * TOWN (20×18 tiles — pallet-town.png 320×288) * * Visual layout from PNG: * Row 0: Top border — trees * Row 1: Trees, fence * Row 2-5: Left house (cols 1-5), Right big building (cols 12-18) * Row 6: Open area, house doors * Row 7-8: Sign, fences, open path area * Row 9-10: Garden/flowers left, path center, fence right * Row 11-13: Open area, another building right side * Row 14-16: Water (left), trees (right), path center * Row 17: Bottom border — trees/fence */ const townWalls = (() => { const w = {}; function add(row, cols) { if (!w[row]) w[row] = []; w[row].push(...cols); } // Top border add(0, wallRange(0, 19)); add(1, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 18, 19]); // Left house (rows 2-5, cols 1-5) for (let r = 2; r <= 4; r++) add(r, [0, 1, 2, 3, 4, 5, 19]); add(5, [0, 1, 2, 4, 5, 19]); // door gap at col 3 // Right big building (rows 2-5, cols 12-18) for (let r = 2; r <= 5; r++) add(r, [0, 12, 13, 14, 15, 16, 17, 18, 19]); // Open path rows with side borders add(6, [0, 19]); add(7, [0, 19]); add(8, [0, 1, 18, 19]); // Garden/flowers left side + fence right add(9, [0, 1, 2, 3, 4, 5, 18, 19]); add(10, [0, 1, 2, 3, 4, 5, 18, 19]); // Open area add(11, [0, 1, 18, 19]); add(12, [0, 1, 18, 19]); // Building right + water left (rows 13-16) add(13, [0, 1, 14, 15, 16, 17, 18, 19]); add(14, [0, 1, 2, 3, 14, 15, 16, 17, 18, 19]); add(15, [0, 1, 2, 3, 14, 15, 16, 17, 18, 19]); add(16, [0, 1, 2, 3, 14, 15, 16, 17, 18, 19]); // Bottom border add(17, wallRange(0, 19)); return w; })(); const townMap = { id: 'town', name: 'Neon Town', image: 'map:pallet-town', widthTiles: 20, heightTiles: 18, spawn: { x: 10, y: 8 }, wallSet: buildWallSet(20, 18, townWalls), exits: [ // Door into lab — appear one tile ABOVE the lab exit (not ON it, to avoid re-trigger) { x: 10, y: 7, targetMap: 'lab', targetX: 4, targetY: 10 } ], npcs: [ { id: 'merchant', x: 8, y: 10, facing: 'right', dialog: [ 'Welcome to Neon Town!', 'I trade in rare logic components.', 'Craft some circuits in the Lab workshop!', 'Some doors need special output patterns to open.' ] }, { id: 'guide', x: 11, y: 12, facing: 'down', dialog: [ 'The Circuit Lab is in the big building up north.', 'Talk to the Professor — he\'ll teach you about logic gates!', 'Press TAB anytime to open your Workshop.' ] } ], interactions: [ // Left house door { x: 3, y: 5, type: 'door', label: 'House', dialog: ['The door is locked.', 'Nobody seems to be home.'] }, // Sign in town center { x: 10, y: 9, type: 'sign', label: 'Sign', dialog: ['Welcome to Neon Town!', 'Circuit Lab ↑'] }, // Right building sign { x: 12, y: 6, type: 'sign', label: 'Sign', dialog: ['CIRCUIT LAB', 'Open for research!'] } ] }; // ==================== Map registry ==================== const maps = { lab: labMap, town: townMap }; // ==================== Public API ==================== export function getMap(id) { return maps[id] || null; } /** * Check if a tile position is a wall (using Set for O(1) lookup) */ export function isWall(mapId, x, y) { const map = maps[mapId]; if (!map) return true; if (x < 0 || x >= map.widthTiles || y < 0 || y >= map.heightTiles) return true; return map.wallSet.has(`${x},${y}`); } /** * Check if a tile is walkable (not a wall and no NPC blocking) */ export function isWalkable(mapId, x, y) { if (isWall(mapId, x, y)) return false; if (getNPC(mapId, x, y)) return false; return true; } export function getInteraction(mapId, x, y) { const map = maps[mapId]; if (!map) return null; return map.interactions.find(i => i.x === x && i.y === y) || null; } export function getNPC(mapId, x, y) { const map = maps[mapId]; if (!map) return null; return map.npcs.find(npc => npc.x === x && npc.y === y) || null; } export function getExit(mapId, x, y) { const map = maps[mapId]; if (!map) return null; return map.exits.find(e => e.x === x && e.y === y) || null; } // Backward compat export function getTile(mapId, x, y) { return isWall(mapId, x, y) ? 1 : 0; } export { maps };