// Forbidden Island — static data. No Phaser, no game state here: just the board // silhouette, the tile/treasure/role catalog, and deck composition. Everything // dynamic lives in IslandLogic.js. // The island is a 6×6 grid with the four corners of each edge removed, giving // the classic 24-tile diamond: // // . . X X . . // . X X X X . // X X X X X X // X X X X X X // . X X X X . // . . X X . . // export const GRID = 6; // Playable cells in reading order (row, col). 24 of them. export const CELLS = [ [0, 2], [0, 3], [1, 1], [1, 2], [1, 3], [1, 4], [2, 0], [2, 1], [2, 2], [2, 3], [2, 4], [2, 5], [3, 0], [3, 1], [3, 2], [3, 3], [3, 4], [3, 5], [4, 1], [4, 2], [4, 3], [4, 4], [5, 2], [5, 3], ]; export function isCell(r, c) { return CELLS.some(([cr, cc]) => cr === r && cc === c); } // Treasures, each tied to two island tiles. `element` keys the treasure cards. export const TREASURES = { earth: { key: 'earth', name: 'The Earth Stone', tiles: ['temple-moon', 'temple-sun'], color: 0x8d6e3c }, wind: { key: 'wind', name: 'The Statue of the Wind', tiles: ['whispering-garden', 'howling-garden'], color: 0x9aa7b0 }, fire: { key: 'fire', name: 'The Crystal of Fire', tiles: ['cave-embers', 'cave-shadows'], color: 0xc0492f }, ocean: { key: 'ocean', name: "The Ocean's Chalice", tiles: ['coral-palace', 'tidal-palace'], color: 0x2f7fb0 }, }; export const TREASURE_KEYS = ['earth', 'wind', 'fire', 'ocean']; // All 24 tiles. `treasure` links a tile to a treasure; `landing` marks the // helipad you must escape from. The rest are neutral. export const TILES = [ { id: 'temple-moon', name: 'Temple of the Moon', treasure: 'earth' }, { id: 'temple-sun', name: 'Temple of the Sun', treasure: 'earth' }, { id: 'whispering-garden', name: 'Whispering Garden', treasure: 'wind' }, { id: 'howling-garden', name: 'Howling Garden', treasure: 'wind' }, { id: 'cave-embers', name: 'Cave of Embers', treasure: 'fire' }, { id: 'cave-shadows', name: 'Cave of Shadows', treasure: 'fire' }, { id: 'coral-palace', name: 'Coral Palace', treasure: 'ocean' }, { id: 'tidal-palace', name: 'Tidal Palace', treasure: 'ocean' }, { id: 'fools-landing', name: "Fools' Landing", landing: true }, { id: 'bronze-gate', name: 'Bronze Gate' }, { id: 'copper-gate', name: 'Copper Gate' }, { id: 'silver-gate', name: 'Silver Gate' }, { id: 'gold-gate', name: 'Gold Gate' }, { id: 'iron-gate', name: 'Iron Gate' }, { id: 'watchtower', name: 'Watchtower' }, { id: 'observatory', name: 'Observatory' }, { id: 'phantom-rock', name: 'Phantom Rock' }, { id: 'misty-marsh', name: 'Misty Marsh' }, { id: 'breakers-bridge', name: 'Breakers Bridge' }, { id: 'crimson-forest', name: 'Crimson Forest' }, { id: 'dunes-deception', name: 'Dunes of Deception' }, { id: 'lost-lagoon', name: 'Lost Lagoon' }, { id: 'twilight-hollow', name: 'Twilight Hollow' }, { id: 'cliffs-abandon', name: 'Cliffs of Abandon' }, ]; // The six adventurer roles. `power` is read by the engine/AI to branch special // abilities; `color` is the pawn colour. export const ROLES = { pilot: { key: 'pilot', name: 'Pilot', color: 0x4a90d9, colorHex: '#4a90d9', power: 'Once per turn, fly to any tile.' }, engineer: { key: 'engineer', name: 'Engineer', color: 0xd0473a, colorHex: '#d0473a', power: 'Shore up two tiles for one action.' }, messenger: { key: 'messenger', name: 'Messenger', color: 0xb9bfc6, colorHex: '#b9bfc6', power: 'Give Treasure cards to anyone, any distance.' }, navigator: { key: 'navigator', name: 'Navigator', color: 0xe2b53c, colorHex: '#e2b53c', power: 'Move another adventurer up to 2 tiles.' }, diver: { key: 'diver', name: 'Diver', color: 0x2b2b30, colorHex: '#2b2b30', power: 'Swim through flooded and missing tiles.' }, explorer: { key: 'explorer', name: 'Explorer', color: 0x49a25a, colorHex: '#49a25a', power: 'Move and shore up diagonally.' }, }; export const ROLE_KEYS = ['pilot', 'engineer', 'messenger', 'navigator', 'diver', 'explorer']; // Special (non-treasure) Treasure-deck cards. export const SPECIAL = { WATERS_RISE: 'waters-rise', HELICOPTER: 'helicopter', SANDBAGS: 'sandbags', }; // Treasure deck: 5 of each treasure (20) + 3 Waters Rise! + 3 Helicopter Lift + // 2 Sandbags = 28 cards. Cards are plain ids: `treasure:earth`, `waters-rise`, … export function buildTreasureDeck() { const deck = []; for (const key of TREASURE_KEYS) for (let i = 0; i < 5; i++) deck.push(`treasure:${key}`); for (let i = 0; i < 3; i++) deck.push(SPECIAL.WATERS_RISE); for (let i = 0; i < 3; i++) deck.push(SPECIAL.HELICOPTER); for (let i = 0; i < 2; i++) deck.push(SPECIAL.SANDBAGS); return deck; } // Water-level track. Index = level (1..10); value = flood cards drawn per turn. // Level 10 is the skull: reaching it loses the game. export const WATER_TRACK = [null, 2, 2, 3, 3, 3, 4, 4, 5, 5, 'skull']; export const MAX_WATER = 10; export const DIFFICULTY = { novice: { key: 'novice', name: 'Novice', start: 1 }, normal: { key: 'normal', name: 'Normal', start: 2 }, elite: { key: 'elite', name: 'Elite', start: 3 }, legendary: { key: 'legendary', name: 'Legendary', start: 4 }, }; export function floodDrawCount(waterLevel) { const v = WATER_TRACK[Math.min(waterLevel, MAX_WATER)]; return typeof v === 'number' ? v : 5; } // Cards needed to capture a treasure. export const CARDS_TO_CAPTURE = 4; // Hand limit; over this at end of turn you must discard. export const HAND_LIMIT = 5; // Actions per turn. export const ACTIONS_PER_TURN = 3; export function tileById(id) { return TILES.find((t) => t.id === id) ?? null; } // Tile id → its row in the `forbiddenisland-tiles` spritesheet. Dry side is // frame 2·row, flooded side is frame 2·row+1. export const TILE_FRAME_ROW = Object.fromEntries(TILES.map((t, i) => [t.id, i])); // Card id → frame in the `forbiddenisland-cards` spritesheet (320×420 cells, a // 4-col × 2-row grid). Top row: the four treasure cards. Bottom row: the three // special cards (frame 7 is unused / a card back). export const CARD_FRAME = { 'treasure:earth': 0, 'treasure:wind': 1, 'treasure:fire': 2, 'treasure:ocean': 3, [SPECIAL.WATERS_RISE]: 4, [SPECIAL.HELICOPTER]: 5, [SPECIAL.SANDBAGS]: 6, }; export function cardFrame(card) { return CARD_FRAME[card] ?? null; }