Files
logic-gates/js/world/maps.js
Jose Luis b60edc49af fix: exits place player in front of door, not on it
When exiting the lab, appear one tile below the town's entrance door
instead of ON the door tile, which caused an infinite re-trigger loop.
Same pattern for all map transitions — land adjacent to the exit, not
on top of it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 16:49:40 +01:00

273 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 };