function setActiveView(view) { currentView = view; document.getElementById('setupScreen').classList.toggle('hidden', view !== 'setup'); document.getElementById('gameScreen').classList.toggle('hidden', view !== 'game'); loadHeaderActions(view); } function selectPlayer(playerIdx) { selectedPlayerIndex = playerIdx; // Update nav button active states document.querySelectorAll('.players-nav-btn').forEach(btn => { btn.classList.toggle('active', parseInt(btn.dataset.playerIdx) === playerIdx); }); // Show only selected player card document.querySelectorAll('.player-card').forEach(card => { const isSelected = parseInt(card.dataset.playerIdx) === playerIdx; card.classList.toggle('hidden', !isSelected); card.classList.toggle('selected', isSelected); }); } function openReadmeLink() { window.open(README_URL, '_blank', 'noopener,noreferrer'); } function loadHeaderActions(mode) { const actions = document.getElementById('headerActions'); actions.classList.toggle('game', mode === 'game'); if (mode === 'setup') { actions.innerHTML = ` `; } else if (mode === 'game') { actions.innerHTML = `
`; } } function renderGame() { renderRoundSetup(); renderScoreboard(); renderHistory(); } function renderScoreboard() { const container = document.getElementById('scoreboard'); const sortedPlayers = [...gameState.players] .map((p, idx) => ({...p, originalIndex: idx})) .sort((a, b) => b.score - a.score); container.innerHTML = `

Current Standings

${sortedPlayers.map((player, idx) => `
#${idx + 1}
${escapeHtml(player.name)}${player.active === false ? ' (inactive)' : ''}
${player.score}
`).join('')}
`; } function renderHistory() { const container = document.getElementById('historySection'); const completedRounds = gameState.rounds.filter((r, idx) => r.scored); if (completedRounds.length === 0) { container.innerHTML = ''; return; } // Create array with round indices for proper mapping const roundsWithIndices = gameState.rounds .map((round, idx) => ({round, idx})) .filter(r => r.round.scored) .reverse(); container.innerHTML = `

Score History

${roundsWithIndices.map(({round, idx}) => { const roundNum = idx + 1; return `
Round ${roundNum} - Hand Size: ${round.handSize}${round.trump ? ` • šŸƒ ${round.trump}` : ''}
${gameState.players.map((player, pIdx) => { const pdata = round.playerData[pIdx]; if (!pdata || (!pdata.participating && pdata.absentReason !== 'joined-late')) { return ''; } if (!pdata.participating && pdata.absentReason === 'joined-late') { return `
${escapeHtml(player.name)}: ā±ļø Joined later
+${pdata.score}
`; } return `
${escapeHtml(player.name)}: ${pdata.gotSet ? 'āŒ' : 'āœ…'} Bid: ${pdata.bid} ${pdata.tax > 0 ? ` | Tax: ${pdata.tax}` : ''} ${pdata.deferred ? ' | Def: āœ“' : ''} | Conf: ${pdata.confidence}
${pdata.score > 0 ? '+' : ''}${pdata.score}
`; }).join('')}
`; }).join('')}
`; } function endGame() { const container = document.getElementById('currentRoundSetup'); const stickyInfo = document.getElementById('stickyRoundInfo'); const sortedPlayers = [...gameState.players].sort((a, b) => b.score - a.score); stickyInfo.innerHTML = ''; container.innerHTML = `

šŸ† GAME OVER šŸ†

${sortedPlayers.map((player, idx) => `
${idx === 0 ? 'šŸ„‡' : idx === 1 ? '🄈' : idx === 2 ? 'šŸ„‰' : '#' + (idx + 1)}
${escapeHtml(player.name)}${player.active === false ? ' (inactive)' : ''}
${player.score}
`).join('')}
`; } function renderRoundSetup() { const round = gameState.rounds[gameState.currentRound]; const container = document.getElementById('currentRoundSetup'); const stickyInfo = document.getElementById('stickyRoundInfo'); syncTrumpParts(round); const totalBids = round.playerData.reduce((sum, p) => sum + (p && p.participating ? p.bid : 0), 0); let bidStatus = ''; let bidClass = ''; if (totalBids < round.handSize) { bidStatus = 'šŸ¦† Under'; bidClass = 'under'; } else if (totalBids > round.handSize) { bidStatus = '🦢 Over'; bidClass = 'over'; } else { bidStatus = 'āŒ Exact'; bidClass = 'exact'; } const sortedPlayers = [...gameState.players] .map((p, idx) => ({...p, originalIndex: idx})) .sort((a, b) => b.score - a.score); const leader = sortedPlayers[0]; const lowPlayer = sortedPlayers[sortedPlayers.length - 1]; const dealer = gameState.players[round.dealerIndex] || leader; // Check if low player is unique (no ties) const lowScore = lowPlayer.score; const playersWithLowScore = gameState.players.filter(p => p.score === lowScore); const hasUniqueLow = playersWithLowScore.length === 1; // Pre-compute position for each player (O(n) instead of O(n²)) const positionMap = {}; sortedPlayers.forEach((p, idx) => { positionMap[p.originalIndex] = idx + 1; }); stickyInfo.innerHTML = `
šŸ‘‘ ${escapeHtml(leader.name)} ${leader.score}
${totalBids}/${round.handSize} ${bidStatus}
R${gameState.currentRound + 1} šŸŽ“ ${escapeHtml(dealer.name)} ${round.reverseValue} ${hasUniqueLow ? `↓ ${escapeHtml(lowPlayer.name)}` : ''} ${round.trump ? `šŸƒ ${round.trump}` : ''}
`; container.innerHTML = `

Round ${gameState.currentRound + 1}

${round.handSize}
${totalBids}/${round.handSize} ${bidStatus}
-- Rank --
${TRUMP_RANKS.map(rank => `
${rank}
`).join('')}
-- Suit --
${TRUMP_SUITS.map(suit => `
${suit}
`).join('')}
${REVERSE_VALUE.map(value => `
${value === 'R' ? 'R - Reverse' : 'N - Normal'}
`).join('')}
${round.trump ? `
${round.trump}
` : ''}
${gameState.players.map((player, idx) => { const pdata = round.playerData[idx]; if (!pdata || !pdata.participating) { return ''; } const isActive = selectedPlayerIndex === idx; return ``; }).join('')}
${gameState.players.map((player, idx) => { const pdata = round.playerData[idx]; if (!pdata || !pdata.participating) { return ''; } const madePrediction = calculatePlayerRoundScore(round, round.playerData[idx], false); const setPrediction = calculatePlayerRoundScore(round, round.playerData[idx], true); const existingRoundScore = round.scored ? (round.playerData[idx].score || 0) : 0; const madeTotal = calculateProjectedTotal(player.score, madePrediction, existingRoundScore); const setTotal = calculateProjectedTotal(player.score, setPrediction, existingRoundScore); // Get position from pre-computed map const currentPosition = positionMap[idx]; const previousPosition = player.previousPosition || currentPosition; let positionIndicator = ''; if (round.scored && currentPosition < previousPosition) { positionIndicator = 'ā†—ļø'; } else if (round.scored && currentPosition > previousPosition) { positionIndicator = 'ā†˜ļø'; } const isLeader = currentPosition === 1; const leaderCrown = isLeader ? 'šŸ‘‘ ' : ''; // Set default player on first render if (selectedPlayerIndex === null && idx === (function() { const dealerIdx = round.dealerIndex; for (let i = 0; i < gameState.players.length; i++) { const checkIdx = (dealerIdx + 1 + i) % gameState.players.length; if (round.playerData[checkIdx]?.participating) return checkIdx; } return dealerIdx; })()) { selectedPlayerIndex = idx; } const isSelectedCard = selectedPlayerIndex === idx; return `
${leaderCrown}${escapeHtml(player.name)} ${positionIndicator} ${idx === round.dealerIndex ? 'šŸŽ“ Dealer' : ''}
${player.score}
${round.playerData[idx].bid}
${[5, 10].map(value => ` `).join('')}
${round.playerData[idx].tax}
${[5, 10, 15, 25].map(value => ` `).join('')}
MAX
${(round.playerData[idx].bid > 0 ? [0, 5, 10] : [0, 5]).map(n => `
${n}
` ).join('')}
If made: ${madeTotal} (${formatSignedValue(madePrediction)}) If set: ${setTotal} (${formatSignedValue(setPrediction)})
`}).join('')}
${!round.scored ? ` ` : `
`}
`; }