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 = `
${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;