// MODIBU - Board (40 tiles + center panel) const { useState: useStateB, useEffect: useEffectB, useMemo: useMemoB, useRef: useRefB } = React; // Map tile index → grid position (row, col) on 11x11 grid // Map tile index → grid position (row, col) on 9x9 grid (32 tiles) function tileGridPosition(i) { // Bottom row: i = 0..8 → row 9, col = 9 - i if (i <= 8) return { row: 9, col: 9 - i }; // Left col: i = 9..15 → col 1, row = 17 - i if (i <= 15) return { row: 17 - i, col: 1 }; // Top row: i = 16..24 → row 1, col = i - 15 if (i <= 24) return { row: 1, col: i - 15 }; // Right col: i = 25..31 → col 9, row = i - 23 return { row: i - 23, col: 9 }; } function tileRotation(i) { if (i <= 8) return 0; if (i <= 15) return 90; if (i <= 24) return 180; return 270; } // Group label color per type const TYPE_INFO = { edukasi: { bg: 'var(--c-edukasi)', deep: 'var(--c-edukasi-deep)', short: 'EDUKASI' }, fakta: { bg: 'var(--c-fakta)', deep: 'var(--c-fakta-deep)', short: 'FAKTA' }, tantangan: { bg: 'var(--c-tantangan)', deep: 'var(--c-tantangan-deep)', short: 'TANTANGAN' }, refleksi: { bg: 'var(--c-refleksi)', deep: 'var(--c-refleksi-deep)', short: 'REFLEKSI' }, kesempatan: { bg: 'var(--c-kesempatan)', deep: 'var(--c-kesempatan-deep)', short: 'KESEMPATAN' }, hukuman: { bg: 'var(--c-hukuman)', deep: 'var(--c-hukuman-deep)', short: 'HUKUMAN' }, start: { bg: 'var(--c-start)', deep: 'var(--c-start-deep)', short: 'START' }, finish: { bg: 'var(--c-finish)', deep: 'var(--c-finish-deep)', short: 'FINISH' }, penjara: { bg: '#495057', deep: '#212529', short: 'PENJARA' }, bonus: { bg: '#FAB005', deep: '#F08C00', short: 'BONUS' }, }; function BoardTile({ tile, players, isCurrent }) { const info = TYPE_INFO[tile.type] || TYPE_INFO.edukasi; const isStart = tile.i === 0; const isCorner = [0, 8, 16, 24].includes(tile.i); const cornerTile = isCorner; // Pawn slots on tile const here = (players || []).filter(p => p && p.position === tile.i); return (
{/* Header strip - category */} {/* Price at Top */} {!cornerTile && tile.price && (
{tile.price}
)} {/* Body */}
{cornerTile ? ( ) : ( <>
{/* Category at Bottom */} {!cornerTile && (
{info.short}
)} )}
{/* Position number (Very Small & Subtle) */}
{tile.i}
{here.length > 0 && (
1 ? 'repeat(2, auto)' : 'auto', gap: 'clamp(2px, 1cqi, 6px)', justifyContent: 'center', alignItems: 'center', zIndex: 3, pointerEvents: 'none', }}> {here.map((p) => (
))}
)}
); } function CornerArt({ tile, info }) { // Custom images for corners 8, 16, 24 if (tile.i === 8) { return (
Hukuman
HUKUMAN
); } if (tile.i === 16) { return (
Penjara
PENJARA
); } if (tile.i === 24) { return (
Edukasi
EDUKASI
); } if (tile.type === 'start') { return (
START
); } // Fallback for other corners if any return (
{tile.label}
); } function Board({ players = [], currentTurnIdx = 0, diceValue, diceValues, isRolling, isAnimating, onRoll, onPause, onHelp, sessionCode, round, onEndGame, onOpenGuru }) { const tiles = BOARD_TILES; const safePlayers = Array.isArray(players) ? players : []; const currentPlayer = safePlayers[currentTurnIdx] || safePlayers[0] || { name: '...', color: 'gray', stats: {} }; const d = diceValues || [diceValue || 1, diceValue || 1]; return (
{tiles.map(t => ( ))}
{/* Action Sidebar - Forced outside to the right of the board */}
{onPause && } {onOpenGuru && } {onEndGame && }
); } function BoardCenter({ players = [], currentTurnIdx = 0, diceValues, isRolling, isAnimating, onRoll, sessionCode, round }) { const currentPlayer = players[currentTurnIdx] || players[0] || { name: '...', color: 'gray', stats: {} }; const isDouble = diceValues && diceValues[0] === diceValues[1] && !isRolling; return (
Illustration Ronde {round || 1} · {sessionCode}
{players.map((p, idx) => ( ))}
{isDouble &&
🎯 DOUBLE! Giliran ekstra!
}
Giliran: {currentPlayer?.name || '...'}
); } function PlayerPanel({ player, active }) { return (
{player.name}
{player.score} poin
); } Object.assign(window, { Board, BoardCenter, BoardTile, TYPE_INFO, });