import API from "../../api.js"; import TableBasePage from "../base_table.js"; import DOM from "../../dom.js"; import Events from "../../lib/events.js"; export default class PageMtgGame extends TableBasePage { static hash = hashPageMtgGame; static attrIdRowObject = attrGameId; callSaveTableContent = API.saveGame; constructor(router) { super(router); } initialize() { this.sharedInitialize(); this.hookupTcgGame(); } hookupFilters() { // this.sharedHookupFilters(); } loadRowTable(rowJson) { return; } getJsonRow(row) { return; } initialiseRowNew(tbody, row) { } postInitialiseRowNewCallback(tbody) { } hookupTableMain() { super.hookupTableMain(); } hookupTcgGame() { this.initGamePage(); let pageHeading = document.querySelector('.container.company-name .tcg-title.company-name'); pageHeading.innerText = `MTG Game #${gameId}`; } initGamePage() { // Load existing game state from API or show setup PageMtgGame.updatePlayerSetup(); if (typeof gameId !== 'undefined' && gameId) { this.loadGameFromServer(); } /* else { PageMtgGame.updatePlayerSetup(); } */ PageMtgGame.hookupResetButton(); PageMtgGame.hookupPlayerCountInput(); this.hookupStartGameButton(); /* this.hookupCommanderDeathIncrementButtons(); this.hookupEliminateCommanderButtons(); this.hookupPlayerLifeIncrementButtons(); this.hookupCommanderDamageIncrementButtons(); */ } static hookupResetButton() { const resetGameButton = document.querySelector('header.game-header .header-right .btn-tcg.btn-tcg-secondary'); if (resetGameButton) { resetGameButton.addEventListener('click', PageMtgGame.resetGame); } } static hookupPlayerCountInput() { const playerCountInput = document.getElementById('playerCount'); if (playerCountInput) { playerCountInput.addEventListener('change', PageMtgGame.updatePlayerSetup); } } hookupStartGameButton() { const startGameButton = document.querySelector('.setup-section .setup-actions .btn-tcg'); if (startGameButton) { startGameButton.addEventListener('click', () => { this.startGame(); }); } } /* hookupCommanderDeathIncrementButtons() { const commanderDeathIncremementButtons = document.querySelectorAll('#players-grid .player-card .commander-deaths .death-btn'); if (commanderDeathIncremementButtons) { commanderDeathIncremementButtons.forEach((button) => { button.addEventListener('click', PageMtgGame.changeCommanderDeaths); }); } } hookupEliminateCommanderButtons() { const eliminateCommanderButtons = document.querySelector('#players-grid .player-card .eliminate-btn'); if (eliminateCommanderButtons) { eliminateCommanderButtons.forEach((button) => { button.addEventListener('click', PageMtgGame.toggleEliminate); }); } } hookupPlayerLifeIncrementButtons() { const playerLifeIncrementButtons = document.querySelector('#players-grid .player-card .eliminate-btn'); if (playerLifeIncrementButtons) { playerLifeIncrementButtons.forEach((button) => { button.addEventListener('click', PageMtgGame.changeLife); }); } } hookupCommanderDamageIncrementButtons() { const commanderDamageIncrementButtons = document.querySelector('#players-grid .player-card .eliminate-btn'); if (commanderDamageIncrementButtons) { commanderDamageIncrementButtons.forEach((button) => { button.addEventListener('click', PageMtgGame.changeCommanderDamage); }); } } */ async loadGameFromServer() { console.log("loading game from server"); try { // Fetch players, rounds, and damage records from API const [playersResponse, roundsResponse, damageResponse] = await Promise.all([ API.getGamePlayers(gameId) , API.getGameRounds(gameId) , API.getGameDamageRecords(gameId) ]); console.log({ playersResponse, damageResponse }); let setupSection = document.getElementById('setupSection'); let gameSection = document.getElementById('gameSection'); setupSection.classList.remove('hidden'); gameSection.classList.add('hidden'); if (playersResponse.status !== 'success') { console.error('Failed to load players:', playersResponse.message); return; } const savedPlayers = playersResponse.data || []; const savedRounds = roundsResponse.status === 'success' ? (roundsResponse.data || []) : []; const savedDamageRecords = damageResponse.status === 'success' ? (damageResponse.data || []) : []; players = savedPlayers; rounds = savedRounds; damageRecords = savedDamageRecords; if (savedPlayers.length === 0) { // No players yet, show setup section return; } // Hide setup, show game setupSection.classList.add('hidden'); gameSection.classList.remove('hidden'); console.log({ savedPlayers, damageRecords }); // Render players to DOM const latestRoundId = PageMtgGame.getLatestRoundId(); const latestRound = rounds.filter(round => round[attrRoundId] == latestRoundId)[0]; const roundDisplayOrderLabel = PageMtgGame.getRoundDisplayOrderLabel(); DOM.setElementValuesCurrentAndPrevious(roundDisplayOrderLabel, latestRound[flagDisplayOrder]); this.renderPlayers(); } catch (error) { console.error('Error loading game from server:', error); } } static getRoundDisplayOrderLabel() { return document.querySelector([ '#gameSection' , ' > .' , flagRow , '.' , flagRound , ' > .' , flagRow , '.' , flagRound , ' > .' , flagRound , '.' , flagDisplayOrder , ' > span.' , flagRound , '.' , flagDisplayOrder ].join('')); } renderPlayers() { const grid = document.getElementById('playersGrid'); grid.innerHTML = ''; // let activeRoundId = PageMtgGame.getActiveRoundId(); const roundDisplayOrderLabel = PageMtgGame.getRoundDisplayOrderLabel(); const currentRoundDisplayOrder = Number(DOM.getElementValueCurrent(roundDisplayOrderLabel)); let activeRound = rounds.filter(round => round[flagDisplayOrder] == currentRoundDisplayOrder)[0]; if (activeRound == null) { activeRound = PageMtgGame.makeDefaultGameRound(currentRoundDisplayOrder); rounds.push(activeRound); } DOM.setElementValueCurrent(roundDisplayOrderLabel, activeRound[flagDisplayOrder]); const previousRoundIds = rounds.filter(round => round[flagDisplayOrder] <= currentRoundDisplayOrder) .map(round => round[attrRoundId]); players.forEach((player, index) => { // Build display name: prefer user_name + deck_name, fallback to player name const playerId = player[attrPlayerId]; let displayName = PageMtgGame.makePlayerDisplayName(playerId, index); let damagePlayerPairs = [...players, { [attrPlayerId]: null }]; let maxCommanderDamageReceived = 0; damagePlayerPairs.forEach(damagePlayerPair => { const sourceId = damagePlayerPair[attrPlayerId]; const filteredPlayerDamages = damageRecords.filter(damage => ( damage[attrRoundId] == activeRound[attrRoundId] // previousRoundIds.includes(damage[attrRoundId]) && damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == sourceId )); //[playerId] || {}; if (filteredPlayerDamages.length == 0) { damageRecords.push(PageMtgGame.makeDefaultGameRoundPlayerDamage(playerId, sourceId)); } maxCommanderDamageReceived = Math.max( maxCommanderDamageReceived , damageRecords.filter(damage => ( damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == sourceId && damage[attrReceivedFromCommanderPlayerId] != null && previousRoundIds.includes(damage[attrRoundId]) )) .map(damage => damage[flagLifeLoss]) .reduce((a, b) => a + b, 0) ); }); const totalDamage = damageRecords.filter(damage => ( damage[attrPlayerId] == playerId && previousRoundIds.includes(damage[attrRoundId]) )) .map(damage => damage[flagLifeLoss] - damage[flagLifeGain]) .reduce((a, b) => a + b, 0); let life = startingLife - totalDamage; let isEliminatedByForce = damageRecords.filter(damage => ( damage[attrPlayerId] == playerId && previousRoundIds.includes(damage[attrRoundId]) )) .map(damage => damage[flagIsEliminated]) .some(Boolean); console.log("renderPlayers"); console.log({isEliminatedByForce, player, life, maxCommanderDamageReceived}); const isEliminated = ( isEliminatedByForce || !player[flagActive] || life < 1 || maxCommanderDamageReceived >= 21 ); const totalCommanderDeaths = damageRecords.filter(damage => ( damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == null && damage[attrRoundId] == activeRound[attrRoundId] )) .map(damage => damage[flagCommanderDeaths]) .reduce((a, b) => a + b, 0); const card = document.createElement('div'); card.className = `player-card ${isEliminated ? 'eliminated' : ''}`; card.style.animationDelay = `${index * 0.1}s`; card.dataset.playerId = playerId; card.dataset.userName = player.user_name || ''; card.dataset.deckName = player.deck_name || ''; card.innerHTML = `
${displayName}
Commander Deaths:
${totalCommanderDeaths}
${life}
Commander Damage Taken
${PageMtgGame.renderCommanderDamageRows( playerId // playerId , player[attrDeckId] // deckId )}
`; grid.appendChild(card); }); this.reorderPlayerCards(); PageMtgGame.renderCommanderDamageLog(); // Hookup all event handlers this.hookupGameRoundEvents(); this.hookupPlayerCardEvents(); } static renderCommanderDamageLog() { const currentRoundDisplayOrder = PageMtgGame.getActiveRoundDisplayOrder(); const damageTableBody = document.querySelector('.' + flagDamageLog + '.' + flagContainer + ' table tbody'); damageTableBody.innerHTML = ''; const previousRoundIds = rounds.filter(round => round[flagDisplayOrder] <= currentRoundDisplayOrder) .map(round => round[attrRoundId]); let newTableBodyHtml = ''; damageRecords.forEach((damage) => { if ( damage[flagActive] && ( damage[flagCommanderDeaths] > 0 || damage[flagLifeGain] != 0 || damage[flagLifeLoss] != 0 || damage[flagIsEliminated] ) // && rounds.filter(r => r[attrRoundId] == damage[attrRoundId])[0][flagDisplayOrder] <= currentRoundDisplayOrder && previousRoundIds.includes(damage[attrRoundId]) ) { let round = rounds.filter(r => r[attrRoundId] == damage[attrRoundId])[0]; let player = players.filter(p => p[attrPlayerId] == damage[attrPlayerId])[0]; let receivedFromPlayer = (damage[attrReceivedFromCommanderPlayerId] == null) ? { [flagName]: ''} : players.filter(p => p[attrPlayerId] == damage[attrReceivedFromCommanderPlayerId])[0]; newTableBodyHtml += ` ${round[flagDisplayOrder]} ${player[flagName]} ${receivedFromPlayer[flagName]} ${damage[flagLifeGain]} ${damage[flagLifeLoss]} ${damage[flagCommanderDeaths]} ${damage[flagIsEliminated]} `; } }); damageTableBody.innerHTML = newTableBodyHtml; } static makeDefaultGameRoundPlayerDamage(playerId, receivedFromCommanderPlayerId) { let roundId = PageMtgGame.getActiveRoundId(); return { [attrDamageId]: -1 - damageRecords.length , [attrRoundId]: roundId , [attrPlayerId]: playerId , [attrReceivedFromCommanderPlayerId]: receivedFromCommanderPlayerId , [flagLifeGain]: 0 , [flagLifeLoss]: 0 , [flagCommanderDeaths]: 0 , [flagIsEliminated]: false , [flagActive]: true }; } static getLatestRoundId() { let roundId = -1; if (rounds.length > 0) { const highestRoundDisplayOrder = rounds.map(round => { return round[flagDisplayOrder]; }) .reduce((acc, cur) => Math.max(acc, cur), 0); const filteredRounds = rounds.filter(round => round[flagDisplayOrder] == highestRoundDisplayOrder); if (filteredRounds.length > 0) { roundId = filteredRounds[0][attrRoundId]; } console.log({ "method": "getLatestRoundId", highestRoundDisplayOrder, filteredRounds, roundId }); } return roundId; } static getActiveRoundDisplayOrder() { const roundDisplayOrderLabel = PageMtgGame.getRoundDisplayOrderLabel(); return Number(DOM.getElementValueCurrent(roundDisplayOrderLabel)); } static getActiveRoundId() { const currentRoundDisplayOrder = PageMtgGame.getActiveRoundDisplayOrder(); let roundId = 0; if (rounds.length > 0) { let filteredRounds = rounds.filter(round => round[flagDisplayOrder] == currentRoundDisplayOrder); if (filteredRounds.length > 0) roundId = filteredRounds[0][attrRoundId]; console.log({ "method": "getActiveRoundId", filteredRounds, roundId }); } return roundId; } static makePlayerDisplayName(playerId, index) { if (playerId == null) { return `Player ${index + 1}`; } const player = players.filter(player => player[attrPlayerId] == playerId)[0]; const deckId = player[attrDeckId]; const deck = (deckId == null) ? null : decks.filter(deck => deck[attrDeckId] == deckId)[0]; const user = (playerId == null) ? null : users[player[attrUserId]]; return player[flagName] || `${(user == null) ? 'Error' : user[flagName]} - ${(deck == null) ? 'Error' : deck[flagName]}`; } static renderCommanderDamageRows(playerId) { const activeRoundDisplayOrder = PageMtgGame.getActiveRoundDisplayOrder(); const previousRoundIds = rounds.filter(round => round[flagDisplayOrder] <= activeRoundDisplayOrder) .map(round => round[attrRoundId]); return players .filter(otherPlayer => otherPlayer[attrPlayerId] !== playerId) .map(otherPlayer => { const sourceId = otherPlayer[attrPlayerId]; let otherPlayerDisplayName = PageMtgGame.makePlayerDisplayName(sourceId); const totalDamage = damageRecords.filter(damage => ( damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == sourceId // && damage[attrRoundId] == roundId && previousRoundIds.includes(damage[attrRoundId]) )) .map(damage => damage[flagLifeLoss]) .reduce((acc, curr) => acc + curr, 0); const isLethal = (totalDamage >= 21); return `
from ${otherPlayerDisplayName}
${totalDamage}
`; }) .join(''); } hookupGameRoundEvents() { let incrementRoundButtonSelector = '#gameSection .' + flagRow + '.' + flagRound + ' button.' + flagRoundDisplayOrderButton; Events.hookupEventHandler("click", incrementRoundButtonSelector, (event, button) => { const amount = button.classList.contains(flagRoundDisplayOrderPlus) ? 1 : -1; const roundDisplayOrderButtonContainer = button.parentElement; const roundDisplayOrderLabel = roundDisplayOrderButtonContainer.querySelector('span.' + flagRound + '.' + flagDisplayOrder); const currentRoundDisplayOrder = Number(DOM.getElementValueCurrent(roundDisplayOrderLabel)); const newDisplayOrder = currentRoundDisplayOrder + amount; DOM.setElementValueCurrent(roundDisplayOrderLabel, newDisplayOrder); DOM.isElementDirty(roundDisplayOrderLabel); this.updateAndToggleShowButtonsSaveCancel(); this.renderPlayers(); }); } static makeDefaultGameRound(displayOrder) { const newDisplayOrder = (displayOrder != null) ? displayOrder : 1 + Math.max(rounds.map(round => round[flagDisplayOrder])); return { [attrRoundId]: -newDisplayOrder , [attrGameId]: gameId , [flagNotes]: null , [flagDisplayOrder]: newDisplayOrder , [flagActive]: true }; } hookupPlayerCardEvents() { // Life gain buttons let lifeGainButtonSelector = '.life-gain-btn'; Events.hookupEventHandler("click", lifeGainButtonSelector, (event, button) => { const playerId = button.dataset.playerId; const amount = parseInt(button.dataset.amount); const activeRoundId = PageMtgGame.getActiveRoundId(); const damageIndex = damageRecords.findIndex(damage => ( damage[attrRoundId] == activeRoundId && damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == null )); this.changeLife( playerId // playerId , amount // amount , true // isLifeGainNotLoss , true // updateDamage , damageIndex // damageIndex ); }); // Life loss buttons let lifeLossButtonSelector = '.life-loss-btn'; Events.hookupEventHandler("click", lifeLossButtonSelector, (event, button) => { const playerId = button.dataset.playerId; const amount = parseInt(button.dataset.amount); const activeRoundId = PageMtgGame.getActiveRoundId(); const damageIndex = damageRecords.findIndex(damage => ( damage[attrRoundId] == activeRoundId && damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == null )); this.changeLife( playerId // playerId , amount // amount , false // isLifeGainNotLoss , true // updateDamage , damageIndex // damageIndex ); }); // Commander death buttons let commanderDeathButtonSelector = '.death-btn'; Events.hookupEventHandler("click", commanderDeathButtonSelector, (event, button) => { const playerId = button.dataset.playerId; const isMinusButton = button.classList.contains('death-minus'); const amount = (isMinusButton) ? -1 : 1; this.changeCommanderDeaths(playerId, amount); }); // Commander damage buttons let commmanderDamageButtonSelector = '.damage-btn'; Events.hookupEventHandler("click", commmanderDamageButtonSelector, (event, button) => { const playerId = button.dataset.playerId; const sourceId = button.dataset.sourceId; const isMinusButton = button.classList.contains('damage-minus'); const amount = (isMinusButton) ? -1 : 1; this.changeCommanderDamage(playerId, sourceId, amount); }); // Eliminate buttons let eliminatePlayerButtonSelector = '.eliminate-btn'; Events.hookupEventHandler("click", eliminatePlayerButtonSelector, (event, button) => { const playerId = button.dataset.playerId; this.toggleEliminate(playerId); }); } changeLife(playerId, amount, isLifeGainNotLoss = false, updateDamage = false, damageIndex = null) { const card = document.querySelector(`.player-card[data-player-id="${playerId}"]`); // if (!card || card.classList.contains('eliminated')) return; const lifeInput = card.querySelector(`.life-value[data-player-id="${playerId}"]`); const lifeDisplay = card.querySelector(`.life-display[data-player-id="${playerId}"]`); const currentLife = parseInt(lifeInput.value) || 0; const newLife = currentLife + amount * ((isLifeGainNotLoss) ? 1 : -1); DOM.setElementAttributeValueCurrent(lifeDisplay, newLife); DOM.isElementDirty(lifeDisplay); lifeInput.value = newLife; lifeDisplay.textContent = newLife; if (updateDamage) { let fieldFlag = (isLifeGainNotLoss) ? flagLifeGain : flagLifeLoss; damageRecords[damageIndex][fieldFlag] += amount; } PageMtgGame.renderCommanderDamageLog(); // PageMtgGame.debouncedSave(); this.updateAndToggleShowButtonsSaveCancel(); } changeCommanderDamage(playerId, sourceId, amount) { const card = document.querySelector(`.player-card[data-player-id="${playerId}"]`); // if (!card || card.classList.contains('eliminated')) return; const damageInput = card.querySelector(`.damage-value[data-player-id="${playerId}"][data-source-id="${sourceId}"]`); const damageDisplay = card.querySelector(`.damage-display[data-player-id="${playerId}"][data-source-id="${sourceId}"]`); const currentDamage = parseInt(damageInput.value) || 0; const newDamage = Math.max(0, currentDamage + amount); amount = newDamage - currentDamage; DOM.setElementAttributeValueCurrent(damageDisplay, newDamage); DOM.isElementDirty(damageDisplay); damageInput.value = newDamage; damageDisplay.textContent = newDamage; // Update lethal class if (newDamage >= 21) { damageDisplay.classList.add('lethal'); } else { damageDisplay.classList.remove('lethal'); } const activeRoundId = PageMtgGame.getActiveRoundId(); const damageIndex = damageRecords.findIndex(damageRecord => ( damageRecord[attrRoundId] == activeRoundId && damageRecord[attrPlayerId] == playerId && damageRecord[attrReceivedFromCommanderPlayerId] == sourceId )); damageRecords[damageIndex][flagLifeLoss] += amount; let isLifeGainNotLoss = false; this.changeLife( playerId // playerId , -amount // amount , isLifeGainNotLoss // isLifeGainNotLoss , false // updateDamage , damageIndex // damageIndex ); // PageMtgGame.debouncedSave(); } changeCommanderDeaths(playerId, amount) { const card = document.querySelector(`.player-card[data-player-id="${playerId}"]`); // if (!card || card.classList.contains('eliminated')) return; const deathDisplay = card.querySelector(`.death-display[data-player-id="${playerId}"]`); const currentDeaths = parseInt(deathDisplay.textContent) || 0; const newDeaths = Math.max(0, currentDeaths + amount); deathDisplay.textContent = newDeaths; DOM.setElementAttributeValueCurrent(deathDisplay, newDeaths); DOM.isElementDirty(deathDisplay); const activeRoundId = PageMtgGame.getActiveRoundId(); const damageIndex = damageRecords.findIndex(damage => ( damage[attrRoundId] == activeRoundId && damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == null )); damageRecords[damageIndex][flagCommanderDeaths] = newDeaths; PageMtgGame.renderCommanderDamageLog(); // PageMtgGame.debouncedSave(); this.updateAndToggleShowButtonsSaveCancel(); } toggleEliminate(playerId) { const card = document.querySelector(`.player-card[data-player-id="${playerId}"]`); if (!card) return; const eliminateBtn = card.querySelector(`.eliminate-btn[data-player-id="${playerId}"]`); const wasEliminated = card.classList.contains('eliminated'); if (wasEliminated) { card.classList.remove('eliminated'); eliminateBtn.textContent = 'Eliminate'; } else { card.classList.add('eliminated'); eliminateBtn.textContent = 'Revive'; } const isEliminated = card.classList.contains('eliminated'); const activeRoundId = PageMtgGame.getActiveRoundId(); const damageIndex = damageRecords.findIndex(damage => ( damage[attrRoundId] == activeRoundId && damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == null )); damageRecords[damageIndex][flagIsEliminated] = isEliminated; DOM.setElementAttributeValueCurrent(eliminateBtn, isEliminated); DOM.isElementDirty(eliminateBtn); this.reorderPlayerCards(); PageMtgGame.renderCommanderDamageLog(); // PageMtgGame.debouncedSave(); this.updateAndToggleShowButtonsSaveCancel(); } reorderPlayerCards() { let playerGrid = document.getElementById('playersGrid'); let currentPlayerCards = playerGrid.querySelectorAll('.player-card'); let newPlayerCards = []; let playerCardMetas = []; currentPlayerCards.forEach((playerCard, index) => { newPlayerCards.push(playerCard.cloneNode(true)); playerCardMetas.push({ [flagIsEliminated]: playerCard.classList.contains(flagIsEliminated) , [attrPlayerId]: playerCard.dataset["playerId"] , [flagDisplayOrder]: index }); }); let activeRoundDisplayOrder = PageMtgGame.getActiveRoundDisplayOrder(); let newPlayerGridInnerHTML = ''; let playerIdA, playerIdB, isEliminatedAsIntA, isEliminatedAsIntB, playerA, playerB, indexPlayerCard; playerCardMetas.sort((a, b) => { playerIdA = a[attrPlayerId]; playerIdB = b[attrPlayerId]; isEliminatedAsIntA = PageMtgGame.isPlayerEliminated(playerIdA, activeRoundDisplayOrder) ? 1 : 0; isEliminatedAsIntB = PageMtgGame.isPlayerEliminated(playerIdB, activeRoundDisplayOrder) ? 1 : 0; playerA = players.filter(p => p[attrPlayerId] == playerIdA)[0]; playerB = players.filter(p => p[attrPlayerId] == playerIdB)[0]; return ( players.length * isEliminatedAsIntA + playerA[flagDisplayOrder] ) - ( players.length * isEliminatedAsIntB + playerB[flagDisplayOrder] ); }).forEach((playerCardMeta) => { indexPlayerCard = playerCardMeta[flagDisplayOrder]; newPlayerGridInnerHTML += newPlayerCards[indexPlayerCard].outerHTML; }); playerGrid.innerHTML = newPlayerGridInnerHTML; playerGrid.querySelectorAll('.' + flagInitialised).forEach((initialisedElement) => { initialisedElement.classList.remove(flagInitialised); }); this.hookupPlayerCardEvents(); } static isPlayerEliminated(playerId, roundDisplayOrder = null) { if (roundDisplayOrder == null) roundDisplayOrder = PageMtgGame.getActiveRoundDisplayOrder(); const filteredRoundIds = rounds.filter(round => round[flagDisplayOrder] <= roundDisplayOrder) .map(round => round[attrRoundId]); let hasDamageWithIsEliminated = damageRecords.filter(damage => ( // damage[attrRoundId] <= roundDisplayOrder filteredRoundIds.includes(damage[attrRoundId]) && damage[attrPlayerId] == playerId && damage[flagIsEliminated] )).length > 0; let damageFromOtherPlayers = {}; let otherPlayerId; damageRecords.filter(damage => ( // damage[attrRoundId] <= roundId filteredRoundIds.includes(damage[attrRoundId]) && damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] != null )) .forEach((damage) => { otherPlayerId = damage[attrReceivedFromCommanderPlayerId]; damageFromOtherPlayers[otherPlayerId] = damage[flagLifeLoss] + ((damageFromOtherPlayers[otherPlayerId] == null) ? 0 : damageFromOtherPlayers[otherPlayerId]); }); let maxDamageFromOtherCommander = Object.keys(damageFromOtherPlayers) .map((playerId) => damageFromOtherPlayers[playerId]) .reduce((acc, cur) => Math.max(acc, cur), 0); let totalDamageTaken = damageRecords.filter(damage => ( // damage[attrRoundId] <= roundId filteredRoundIds.includes(damage[attrRoundId]) && damage[attrPlayerId] == playerId )) .map((damage) => damage[flagLifeLoss] - damage[flagLifeGain]) .reduce((a, b) => a + b, 0); console.log({ roundDisplayOrder, filteredRoundIds, hasDamageWithIsEliminated, maxDamageFromOtherCommander, totalDamageTaken }); return ( hasDamageWithIsEliminated || maxDamageFromOtherCommander >= 21 || totalDamageTaken >= startingLife ); } static updatePlayerSetup() { const playerCountInput = document.getElementById('playerCount'); if (!playerCountInput) return; const playerCount = parseInt(playerCountInput.value); const grid = document.getElementById('playerSetupGrid'); if (!grid) return; grid.innerHTML = ''; const wrapperTemplate = document.getElementById(playerSetupWrapperTemplateId); let player, wrapper, wrapperHeading, userDdl, deckDdl, nameInput; for (let i = 0; i < playerCount; i++) { if (i < players.length) { player = players[i]; } else { player = PageMtgGame.makeDefaultGamePlayer(); players.push(player); } wrapper = wrapperTemplate.cloneNode(true); wrapper.removeAttribute("id"); wrapper.setAttribute(flagDisplayOrder, i + 1); wrapper.classList.remove(flagIsCollapsed); wrapperHeading = wrapper.querySelector('label'); wrapperHeading.innerText = 'Player ' + (i + 1); userDdl = wrapper.querySelector('.playerUser select'); DOM.setElementValuesCurrentAndPrevious(userDdl, player[attrUserId]); deckDdl = wrapper.querySelector('.playerDeck select'); DOM.setElementValuesCurrentAndPrevious(deckDdl, player[attrDeckId]); nameInput = wrapper.querySelector('.playerName input'); DOM.setElementValuesCurrentAndPrevious(nameInput, player[flagName]); console.log('player: ', player); grid.appendChild(wrapper); } } static makeDefaultGamePlayer() { return { [attrPlayerId]: -players.length , [attrGameId]: gameId , [attrUserId]: user[attrUserId] , [attrDeckId]: 0 , [flagName]: "" , [flagNotes]: null , [flagDisplayOrder]: players.length , [flagActive]: true }; } async startGame() { const playerCountInput = document.getElementById('playerCount'); if (!playerCountInput) return; const playerCount = parseInt(playerCountInput.value); const playersToSave = []; let playerSetupWrapper, playerId, player, userDdl, userId, deckDdl, deckId, nameInput, name; for (let i = 0; i < playerCount; i++) { playerSetupWrapper = document.querySelector('.player-name-input-wrapper[' + flagDisplayOrder + '="' + (i + 1) + '"]'); userDdl = playerSetupWrapper.querySelector('.playerUser select'); deckDdl = playerSetupWrapper.querySelector('.playerDeck select'); nameInput = playerSetupWrapper.querySelector('.playerName input'); userId = DOM.getElementValueCurrent(userDdl); deckId = DOM.getElementValueCurrent(deckDdl); name = nameInput ? nameInput.value.trim() || null : null; // `Player ${i + 1}` : `Player ${i + 1}`; playerId = playerSetupWrapper.getAttribute(attrPlayerId); player = players.filter(p => p[attrPlayerId] == playerId)[0]; playersToSave.push({ ...player , [attrGameId]: gameId , [attrUserId]: userId , [attrDeckId]: deckId , [flagName]: name , [flagDisplayOrder]: i + 1 , [flagActive]: true }); } // Save players to server const comment = 'Save players'; const self = this; API.saveGamePlayers(playersToSave, null, comment) .then(data => { if (data[flagStatus] == flagSuccess) { self.leave(); window.location.reload(); } else { console.error('Failed to save players:', data[flagMessage]); PageMtgGame.showError('An error occurred while creating the game'); } }) .catch(error => { console.error('Error creating game:', error); PageMtgGame.showError('An error occurred while creating the game'); }) .finally(() => { }); } static resetGame() { if (confirm('Are you sure you want to start a new game? Current game will be lost.')) { localStorage.removeItem(`mtgGame_${gameId}`); window.location.href = hashPageGames; } } async saveGame() { /* const gameState = { [flagPlayer]: players , [flagRound]: rounds , [flagDamage]: damageRecords }; if (gameState[flagPlayer].length > 0) { localStorage.setItem(`mtgGame_${gameId}`, JSON.stringify(gameState)); PageMtgGame.showSaveIndicator(); } */ const comment = 'Save player damage'; const self = this; API.saveGameRoundPlayerDamages(rounds, damageRecords, null, comment) .then(data => { if (data[flagStatus] == flagSuccess) { self.leave(); window.location.reload(); } else { console.error('Failed to save player damages:', data[flagMessage]); PageMtgGame.showError('An error occurred while saving player damages'); } }) .catch(error => { console.error('Error saving player damages:', error); PageMtgGame.showError('An error occurred while saving player damages'); }) .finally(() => { }); } /* static debouncedSave() { clearTimeout(PageMtgGame._saveTimeout); PageMtgGame._saveTimeout = setTimeout(() => PageMtgGame.saveGame(), 500); } static showSaveIndicator() { const indicator = document.getElementById('saveIndicator'); if (indicator) { indicator.classList.add('show'); setTimeout(() => { indicator.classList.remove('show'); }, 2000); } } */ saveRecordsTableDirty() { this.saveGame(); } static showError(message) { // Check if there's an overlay error element const errorOverlay = document.getElementById('overlayError'); if (errorOverlay) { const errorLabel = errorOverlay.querySelector('.error-message, #labelError'); if (errorLabel) { errorLabel.textContent = message; } errorOverlay.classList.remove('hidden'); errorOverlay.style.display = 'flex'; } else { // Fallback to alert alert(message); } } leave() { super.leave(); } } // Static timeout reference for debouncing PageMtgGame._saveTimeout = null;