From 1e607e5802abfb3dd8e5e372147c24418e73c14e Mon Sep 17 00:00:00 2001 From: Brian Fertig Date: Fri, 12 Jun 2026 09:46:30 -0600 Subject: [PATCH] feat(catan): add debug mode for free roads/ships and improve fog revelation - Add DEBUG_FREE_ROADS_AND_SHIPS flag to bypass resource costs for testing - Extend fog hex revelation to include corner-adjacent hexes, not just edge-adjacent - Restructure button enable logic to support debug mode in UI - Initialize hexTileFrames map in CatanGame setup --- public/src/games/catan/CatanGame.js | 6 ++++-- public/src/games/catan/CatanLogic.js | 18 ++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/public/src/games/catan/CatanGame.js b/public/src/games/catan/CatanGame.js index 45724d0..851ca58 100644 --- a/public/src/games/catan/CatanGame.js +++ b/public/src/games/catan/CatanGame.js @@ -132,6 +132,7 @@ export default class CatanGame extends Phaser.Scene { this.hexImgs = []; this.hexLabels.forEach((t) => t.destroy()); this.hexLabels = []; + this.hexTileFrames = {}; // Inset a convex hex polygon toward its center. // s=0 collapses to point; s=1 is original. Uses inradius (79.7px) as scale reference. @@ -1551,12 +1552,13 @@ export default class CatanGame extends Phaser.Scene { const action = me && s.phase === 'action'; const set = (k, on) => this.buttons[k]?.setEnabled(!!on); const hasSettleSpot = action && L.legalSettlementNodes(s, 0, false).length > 0; + const DEBUG_FREE_ROADS_AND_SHIPS = false; const hasRoadSpot = action && L.legalRoadEdges(s, 0, false).length > 0; set('roll', me && s.phase === 'rollPhase'); - set('road', (action && hasRoadSpot && L.canAfford(p, COSTS.road)) || (action && s.freeRoads > 0 && hasRoadSpot)); + set('road', action && hasRoadSpot && (DEBUG_FREE_ROADS_AND_SHIPS && me || L.canAfford(p, COSTS.road) || s.freeRoads > 0)); const hasShipSpot = action && L.legalShipEdges(s, 0).length > 0; const sCost = L.shipCost(s); - set('ship', hasShipSpot && ((action && sCost && L.canAfford(p, sCost)) || (action && s.freeShips > 0))); + set('ship', hasShipSpot && action && (DEBUG_FREE_ROADS_AND_SHIPS && me || !sCost || L.canAfford(p, sCost) || s.freeShips > 0)); set('settlement', hasSettleSpot && L.canAfford(p, COSTS.settlement)); set('city', action && p.settlements.length > 0 && L.canAfford(p, COSTS.city)); set('buyDev', action && s.devDeck.length > 0 && L.canAfford(p, COSTS.devCard)); diff --git a/public/src/games/catan/CatanLogic.js b/public/src/games/catan/CatanLogic.js index 3466614..5393041 100644 --- a/public/src/games/catan/CatanLogic.js +++ b/public/src/games/catan/CatanLogic.js @@ -414,13 +414,15 @@ export function moveRobber(state, hexId, targetSeat = null) { } // ── building ─────────────────────────────────────────────────────────────────── + const DEBUG_FREE_ROADS_AND_SHIPS = false; + export function buildRoad(state, seat, edgeId) { const s = cloneState(state); if (s.phase !== 'action' || s.currentPlayer !== seat) return s; if (!legalRoadEdges(s, seat, false).includes(edgeId)) return s; const free = s.freeRoads > 0; - if (!free && !canAfford(s.players[seat], COSTS.road)) return s; - if (free) s.freeRoads--; else pay(s, s.players[seat], COSTS.road); + if (!free && !canAfford(s.players[seat], COSTS.road) && !(DEBUG_FREE_ROADS_AND_SHIPS && seat === 0)) return s; + if (free) s.freeRoads--; else if (!(DEBUG_FREE_ROADS_AND_SHIPS && seat === 0)) pay(s, s.players[seat], COSTS.road); s.players[seat].roads.push(edgeId); revealFogHexes(s, edgeId); recomputeLongestRoad(s); @@ -428,10 +430,14 @@ export function buildRoad(state, seat, edgeId) { return s; } -// Reveal any fog hexes that share an edge with edgeId. +// Reveal any fog hexes that share an edge or a corner (node) with edgeId. function revealFogHexes(s, edgeId) { const geo = geoFor(s); - for (const hexId of geo.edges[edgeId].hexes) { + const [a, b] = geo.edges[edgeId].nodes; + // Check all hexes touching either endpoint node — this catches fog hexes + // that share only a corner with the road/ship edge. + const hexIds = new Set([...geo.nodes[a].hexes, ...geo.nodes[b].hexes]); + for (const hexId of hexIds) { const hex = s.hexes[hexId]; if (!hex || hex.kind !== 'fog') continue; if (hex.fogData) { @@ -496,8 +502,8 @@ export function buildShip(state, seat, edgeId) { if (!legalShipEdges(s, seat).includes(edgeId)) return s; const cost = shipCost(s); const free = s.freeShips > 0; - if (!free && !canAfford(s.players[seat], cost)) return s; - if (free) s.freeShips--; else pay(s, s.players[seat], cost); + if (!free && !canAfford(s.players[seat], cost) && !(DEBUG_FREE_ROADS_AND_SHIPS && seat === 0)) return s; + if (free) s.freeShips--; else if (!(DEBUG_FREE_ROADS_AND_SHIPS && seat === 0)) pay(s, s.players[seat], cost); s.players[seat].ships.push(edgeId); revealFogHexes(s, edgeId); recomputeLongestRoad(s);