diff --git a/frontend/src/components/BoardCanvas.tsx b/frontend/src/components/BoardCanvas.tsx index 6a0dcca..b6793e8 100644 --- a/frontend/src/components/BoardCanvas.tsx +++ b/frontend/src/components/BoardCanvas.tsx @@ -161,9 +161,21 @@ export const BoardCanvas: React.FC = ({ } // ========================================== - // Draw Impassable Obstacles (Mountains & Valleys) + // Draw Impassable Obstacles (Mountains & Forests) // ========================================== + const obstacleMap = new Map(); + if (boardState.obstacles) { + for (const obs of boardState.obstacles) { + obstacleMap.set(`${obs.x},${obs.y}`, obs.type); + } + } + (boardState.obstacles || []).forEach((obs) => { + const hasTop = obstacleMap.get(`${obs.x},${obs.y - 1}`) === obs.type; + const hasBottom = obstacleMap.get(`${obs.x},${obs.y + 1}`) === obs.type; + const hasLeft = obstacleMap.get(`${obs.x - 1},${obs.y}`) === obs.type; + const hasRight = obstacleMap.get(`${obs.x + 1},${obs.y}`) === obs.type; + const cx = startX + (obs.x - min_x) * cellSize; const cy = startY + (obs.y - min_y) * cellSize; const x0 = cx - cellSize / 2; @@ -176,10 +188,23 @@ export const BoardCanvas: React.FC = ({ if (obs.type === 'mountain') { // Mountain base ctx.fillStyle = '#1e293b'; - ctx.fillRect(x0, y0, cellSize, cellSize); + ctx.fillRect(x0, y0, cellSize + 0.5, cellSize + 0.5); // overlapping slightly to remove gaps + ctx.strokeStyle = '#475569'; - ctx.lineWidth = 0.5; - ctx.strokeRect(x0, y0, cellSize, cellSize); + ctx.lineWidth = Math.max(1, cellSize * 0.05); + ctx.beginPath(); + if (!hasTop) { ctx.moveTo(x0, y0); ctx.lineTo(x0 + cellSize, y0); } + if (!hasBottom) { ctx.moveTo(x0, y0 + cellSize); ctx.lineTo(x0 + cellSize, y0 + cellSize); } + if (!hasLeft) { ctx.moveTo(x0, y0); ctx.lineTo(x0, y0 + cellSize); } + if (!hasRight) { ctx.moveTo(x0 + cellSize, y0); ctx.lineTo(x0 + cellSize, y0 + cellSize); } + ctx.stroke(); + + // Shadow drop for 3D board game look + if (!hasBottom || !hasRight) { + ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'; + if (!hasBottom) ctx.fillRect(x0, y0 + cellSize, cellSize, Math.max(2, cellSize * 0.1)); + if (!hasRight) ctx.fillRect(x0 + cellSize, y0, Math.max(2, cellSize * 0.1), cellSize); + } if (cellSize >= 6) { // Lit rock face (slate gray) @@ -225,10 +250,23 @@ export const BoardCanvas: React.FC = ({ } else { // Forest / Woodland Grove (Green & Trees) ctx.fillStyle = '#022c22'; - ctx.fillRect(x0, y0, cellSize, cellSize); + ctx.fillRect(x0, y0, cellSize + 0.5, cellSize + 0.5); // overlap slightly + ctx.strokeStyle = '#064e3b'; - ctx.lineWidth = 0.5; - ctx.strokeRect(x0, y0, cellSize, cellSize); + ctx.lineWidth = Math.max(1, cellSize * 0.05); + ctx.beginPath(); + if (!hasTop) { ctx.moveTo(x0, y0); ctx.lineTo(x0 + cellSize, y0); } + if (!hasBottom) { ctx.moveTo(x0, y0 + cellSize); ctx.lineTo(x0 + cellSize, y0 + cellSize); } + if (!hasLeft) { ctx.moveTo(x0, y0); ctx.lineTo(x0, y0 + cellSize); } + if (!hasRight) { ctx.moveTo(x0 + cellSize, y0); ctx.lineTo(x0 + cellSize, y0 + cellSize); } + ctx.stroke(); + + // Shadow drop for 3D board game look + if (!hasBottom || !hasRight) { + ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'; + if (!hasBottom) ctx.fillRect(x0, y0 + cellSize, cellSize, Math.max(2, cellSize * 0.1)); + if (!hasRight) ctx.fillRect(x0 + cellSize, y0, Math.max(2, cellSize * 0.1), cellSize); + } if (cellSize >= 6) { // Sturdy timber tree trunk