Files
logic-gates/js/world/maps.js
Jose Luis c836ccbb21 refactor: migrate world rendering from programmatic sprites to PNG assets
Replace pixel-art drawing with pre-rendered PNG map backgrounds and
character/NPC sprite images from pokemon-js reference. Maps now use
coordinate-based wall arrays instead of tile grids.

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

253 lines
7.0 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: coordinate-based collision data { row: [col1, col2, ...] }
* - npcs, interactions, exits as position-based objects
*
* Map images are drawn at 3x scale (16px native → 48px on screen)
*/
// ==================== Map definitions ====================
/**
* MAP: LAB (10×12 tiles — lab.png is 160×192)
* Pokemon professor's lab interior
*/
const labMap = {
id: 'lab',
name: 'Circuit Lab',
image: 'map:lab',
widthTiles: 10,
heightTiles: 12,
spawn: { x: 4, y: 10 },
// Walls: { row: [col, col, ...] }
// Row 0-1: top shelves/machines
walls: {
0: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
2: [0, 1, 2, 7, 8, 9],
3: [0, 3, 4, 5, 6, 9],
4: [0, 3, 4, 5, 6, 9],
5: [0, 3, 4, 5, 6, 9],
6: [0, 9],
7: [0, 9],
8: [0, 1, 2, 7, 8, 9],
9: [0, 1, 8, 9],
10: [0, 1, 2, 3, 6, 7, 8, 9],
11: [0, 1, 2, 3, 4, 6, 7, 8, 9]
},
exits: [
// Exit at bottom center — door to town
{ x: 4, y: 11, targetMap: 'town', targetX: 12, targetY: 10 },
{ x: 5, y: 11, targetMap: 'town', targetX: 12, targetY: 10 }
],
npcs: [
{
id: 'professor',
x: 4, y: 3,
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 big table in the middle of lab)
{ x: 3, y: 5, type: 'workshop', label: 'Workshop Table' },
{ x: 4, y: 5, type: 'workshop', label: 'Workshop Table' },
{ x: 5, y: 5, type: 'workshop', label: 'Workshop Table' },
{ x: 6, y: 5, type: 'workshop', label: 'Workshop Table' },
// Machine/bookshelf
{ x: 8, y: 2, type: 'terminal', label: 'Terminal',
dialog: ['Circuit analysis terminal.', 'Connect components to solve puzzles.'] },
// Puzzle door in top-right area
{ x: 8, y: 3, type: 'puzzle_door', puzzleId: 'lab_door_1',
requiredOutputs: [1, 0, 1, 1], label: 'Locked Door' }
]
};
/**
* MAP: TOWN (20×18 tiles — pallet-town.png is 320×288)
* Pokemon-style starting town with houses and paths
*/
const townMap = {
id: 'town',
name: 'Neon Town',
image: 'map:pallet-town',
widthTiles: 20,
heightTiles: 18,
spawn: { x: 9, y: 9 },
// Walls based on pallet-town visual layout
// Trees around border, houses, fences, water
walls: (() => {
const w = {};
// Helper to add walls
function addWall(row, cols) {
if (!w[row]) w[row] = [];
w[row].push(...cols);
}
function addRange(row, from, to) {
const cols = [];
for (let c = from; c <= to; c++) cols.push(c);
addWall(row, cols);
}
function addRect(rowStart, rowEnd, colStart, colEnd) {
for (let r = rowStart; r <= rowEnd; r++) addRange(r, colStart, colEnd);
}
// Top border (trees/fence) — rows 0-1
addRange(0, 0, 19);
addRange(1, 0, 19);
// Left border trees
for (let r = 2; r <= 17; r++) addWall(r, [0, 1]);
// Right border trees
for (let r = 2; r <= 17; r++) addWall(r, [18, 19]);
// Bottom border
addRange(17, 0, 19);
// House 1 (top-left area) — roughly rows 3-6, cols 3-7
addRect(3, 5, 3, 7);
// House 2 (top-right area) — rows 3-6, cols 12-16
addRect(3, 5, 12, 16);
// Fence segments
addRange(7, 2, 7);
addRange(7, 12, 17);
// Water/pond (bottom-left)
addRect(13, 15, 2, 5);
// Some trees/obstacles in bottom area
addWall(16, [2, 3, 4, 5, 6, 7]);
addWall(16, [12, 13, 14, 15, 16, 17]);
return w;
})(),
exits: [
// North exit — goes to lab
{ x: 12, y: 9, targetMap: 'lab', targetX: 4, targetY: 10 },
// Route 1 south (future)
// { x: 9, y: 17, targetMap: 'route1', targetX: 10, targetY: 0 }
],
npcs: [
{
id: 'merchant',
x: 9, y: 11,
facing: 'down',
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: 14, y: 9,
facing: 'left',
dialog: [
'The Circuit Lab is just up ahead.',
'Professor Oak.. I mean, the Professor can teach you about logic gates!',
'Press TAB anytime to open your Workshop.'
]
}
],
interactions: [
// House 1 door
{ x: 5, y: 6, type: 'door', label: 'House',
dialog: ['The door is locked.', 'Nobody seems to be home.'] },
// House 2 door
{ x: 14, y: 6, type: 'door', label: 'House',
dialog: ['This is the component shop.', 'Coming soon!'] },
// Sign
{ x: 10, y: 8, type: 'sign', label: 'Sign',
dialog: ['Welcome to Neon Town!', 'Circuit Lab ↑'] }
]
};
// ==================== 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
*/
export function isWall(mapId, x, y) {
const map = maps[mapId];
if (!map) return true;
// Out of bounds = wall
if (x < 0 || x >= map.widthTiles || y < 0 || y >= map.heightTiles) return true;
const row = map.walls[y];
if (!row) return false;
return row.includes(x);
}
/**
* 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;
}
/**
* Get interaction at position
*/
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;
}
/**
* Get NPC at position
*/
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;
}
/**
* Get exit at position
*/
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;
}
// No longer needed but keep for compat — returns null always
export function getTile(mapId, x, y) {
return isWall(mapId, x, y) ? 1 : 0;
}
export { maps };