Show a player's hand in replays

This commit is contained in:
Andrio Celos
2023-03-11 01:26:50 +11:00
parent 06a555b057
commit 878d4e45cd
6 changed files with 199 additions and 103 deletions

View File

@@ -18,8 +18,8 @@ let enterGameTimeout: number | null = null;
let currentReplay: {
turns: Move[][],
placements: PlacementResults[],
initialDrawOrder: number[][],
drawOrder: number[][]
replayPlayerData: ReplayPlayerData[],
watchingPlayer: number
} | null = null;
const playerList = document.getElementById('playerList')!;

View File

@@ -20,8 +20,7 @@ const playRow = document.getElementById('playRow')!;
const spectatorRow = document.getElementById('spectatorRow')!;
const replayNextButton = document.getElementById('replayNextButton')!;
const replayPreviousButton = document.getElementById('replayPreviousButton')!;
const flipLabel = document.getElementById('flipLabel') as HTMLLabelElement;
const flipBox = document.getElementById('flipBox') as HTMLInputElement;
const flipButton = document.getElementById('flipButton') as HTMLButtonElement;
let replayAnimationAbortController: AbortController | null = null;
const shareReplayLinkButton = document.getElementById('shareReplayLinkButton') as HTMLButtonElement;
@@ -70,6 +69,7 @@ board.resize(cols);
function clear() {
testMode = false;
gamePage.classList.remove('replay');
gamePage.classList.remove('deckTest');
gamePage.classList.remove('gameEnded');
gameControls.hidden = true;
@@ -79,8 +79,7 @@ function clear() {
replayPreviousButton.hidden = true;
replayNextButton.hidden = true;
shareReplayLinkButton.hidden = true;
flipLabel.hidden = false;
flipBox.checked = false;
flipButton.hidden = false;
gameButtonsContainer.hidden = true;
testControls.hidden = true;
for (const playerBar of playerBars) {
@@ -104,27 +103,32 @@ function initSpectator() {
clear();
gameControls.hidden = false;
spectatorRow.hidden = false;
flipLabel.hidden = false;
flipBox.checked = false;
flipButton.hidden = false;
gameButtonsContainer.hidden = false;
showPage('game');
}
function initReplay() {
clear();
gamePage.classList.add('replay');
gameControls.hidden = false;
handContainer.hidden = false;
spectatorRow.hidden = false;
replayPreviousButton.hidden = false;
replayNextButton.hidden = false;
flipLabel.hidden = false;
flipBox.checked = false;
flipButton.hidden = false;
gameButtonsContainer.hidden = false;
canPlay = false;
showPage('game');
clearPlayContainers();
turnNumberLabel.setTurnNumber(1);
turnNumberLabel.setTurnNumber(null);
replayUpdateHand();
}
function initTest(stage: Stage) {
clear();
testMode = true;
gamePage.classList.add('deckTest');
currentGame = { id: 'test', maxPlayers: 2, players: [ ], webSocket: null, turnNumber: 1, me: { playerIndex: 0, move: null, deck: null, hand: null, cardsUsed: [ ] } };
board.resize(stage.copyGrid());
const startSpaces = stage.getStartSpaces(2);
@@ -135,14 +139,10 @@ function initTest(stage: Stage) {
for (var o of playerBars)
o.element.hidden = true;
testMode = true;
testPlacements.splice(0);
clearChildren(testPlacementList);
gamePage.classList.add('deckTest');
gamePage.classList.remove('gameEnded');
gamePage.dataset.myPlayerIndex = '0';
gamePage.dataset.uiBaseColourIsSpecialColour = 'true';
gameControls.hidden = true;
gameButtonsContainer.hidden = false;
testControls.hidden = false;
clearPlayContainers();
@@ -159,6 +159,7 @@ replayNextButton.addEventListener('click', _ => {
if (currentGame == null || currentReplay == null || currentGame.turnNumber > 12) return;
if (replayAnimationAbortController) {
replayUpdateHand();
replayAnimationAbortController.abort();
replayAnimationAbortController = null;
turnNumberLabel.setTurnNumber(currentGame.turnNumber);
@@ -168,86 +169,105 @@ replayNextButton.addEventListener('click', _ => {
}
}
const moves = currentReplay.turns[currentGame.turnNumber - 1];
const result = board.makePlacements(moves);
currentReplay.placements.push(result);
let anySpecialAttacks = false;
// Show the cards that were played.
clearPlayContainers();
for (let i = 0; i < currentGame.players.length; i++) {
const player = currentGame.players[i];
const move = moves[i];
const button = new CardButton('checkbox', move.card);
if ((move as PlayMove).isSpecialAttack) {
anySpecialAttacks = true;
player.specialPoints -= (move as PlayMove).card.specialCost;
button.element.classList.add('specialAttack');
} else if (move.isPass) {
player.passes++;
player.specialPoints++;
const el = document.createElement('div');
el.className = 'passLabel';
el.innerText = 'Pass';
button.element.appendChild(el);
}
button.inputElement.hidden = true;
playContainers[i].append(button.element);
}
for (const p of result.specialSpacesActivated) {
const space = board.grid[p.x][p.y];
const player2 = currentGame.players[space & 3];
player2.specialPoints++;
player2.totalSpecialPoints++;
}
currentGame.turnNumber++;
replayAnimationAbortController = new AbortController();
(async () => {
await playInkAnimations({ game: { state: GameState.Ongoing, board: null, turnNumber: currentGame.turnNumber, players: currentGame.players }, placements: result.placements, specialSpacesActivated: result.specialSpacesActivated }, anySpecialAttacks, replayAnimationAbortController.signal);
if (currentGame.turnNumber == 0) {
// Show redraw decisions.
currentGame.turnNumber++;
turnNumberLabel.setTurnNumber(currentGame.turnNumber);
clearPlayContainers();
if (currentGame.turnNumber > 12) {
gameButtonsContainer.hidden = true;
passButton.enabled = false;
specialButton.enabled = false;
gamePage.classList.add('gameEnded');
showResult();
replayUpdateHand();
} else {
const moves = currentReplay.turns[currentGame.turnNumber - 1];
const result = board.makePlacements(moves);
currentReplay.placements.push(result);
let anySpecialAttacks = false;
// Show the cards that were played.
const handButton = handButtons.find(b => b.card.number == moves[currentReplay!.watchingPlayer].card.number);
if (handButton) handButton.checked = true;
for (let i = 0; i < currentGame.players.length; i++) {
const player = currentGame.players[i];
const move = moves[i];
const button = new CardButton('checkbox', move.card);
if ((move as PlayMove).isSpecialAttack) {
anySpecialAttacks = true;
player.specialPoints -= (move as PlayMove).card.specialCost;
button.element.classList.add('specialAttack');
} else if (move.isPass) {
player.passes++;
player.specialPoints++;
const el = document.createElement('div');
el.className = 'passLabel';
el.innerText = 'Pass';
button.element.appendChild(el);
}
button.inputElement.hidden = true;
playContainers[i].append(button.element);
}
})();
for (const p of result.specialSpacesActivated) {
const space = board.grid[p.x][p.y];
const player2 = currentGame.players[space & 3];
player2.specialPoints++;
player2.totalSpecialPoints++;
}
currentGame.turnNumber++;
replayAnimationAbortController = new AbortController();
(async () => {
await playInkAnimations({ game: { state: GameState.Ongoing, board: null, turnNumber: currentGame.turnNumber, players: currentGame.players }, placements: result.placements, specialSpacesActivated: result.specialSpacesActivated }, anySpecialAttacks, replayAnimationAbortController.signal);
turnNumberLabel.setTurnNumber(currentGame.turnNumber);
clearPlayContainers();
if (currentGame.turnNumber > 12) {
gameButtonsContainer.hidden = true;
passButton.enabled = false;
specialButton.enabled = false;
gamePage.classList.add('gameEnded');
showResult();
} else
replayUpdateHand();
})();
}
});
replayPreviousButton.addEventListener('click', _ => {
if (currentGame == null || currentReplay == null) return;
if (currentGame == null || currentReplay == null || currentGame.turnNumber == 0) return;
replayAnimationAbortController?.abort();
replayAnimationAbortController = null;
const result = currentReplay.placements.pop();
if (!result) return;
if (currentGame.turnNumber > 1) {
const result = currentReplay.placements.pop();
if (!result) return;
clearPlayContainers();
for (let i = 0; i < currentGame.players.length; i++) {
const el = playerBars[i].resultElement;
el.innerText = '';
clearPlayContainers();
for (let i = 0; i < currentGame.players.length; i++) {
const el = playerBars[i].resultElement;
el.innerText = '';
}
undoTurn(result);
}
undoTurn(result);
currentGame!.turnNumber--;
currentGame.turnNumber--;
gamePage.classList.remove('gameEnded');
turnNumberLabel.setTurnNumber(currentGame!.turnNumber);
handContainer.hidden = false;
gameButtonsContainer.hidden = false;
for (let i = 0; i < currentGame.players.length; i++) {
const move = currentReplay.turns[currentGame.turnNumber - 1][i];
if (move.isPass) {
currentGame.players[i].passes--;
currentGame.players[i].specialPoints--;
} else if ((move as PlayMove).isSpecialAttack)
currentGame.players[i].specialPoints += (move as PlayMove).card.specialCost;
updateStats(i);
}
if (currentGame.turnNumber > 0) {
turnNumberLabel.setTurnNumber(currentGame.turnNumber);
for (let i = 0; i < currentGame.players.length; i++) {
const move = currentReplay.turns[currentGame.turnNumber - 1][i];
if (move.isPass) {
currentGame.players[i].passes--;
currentGame.players[i].specialPoints--;
} else if ((move as PlayMove).isSpecialAttack)
currentGame.players[i].specialPoints += (move as PlayMove).card.specialCost;
updateStats(i);
}
} else
turnNumberLabel.setTurnNumber(null);
replayUpdateHand();
});
function undoTurn(turn: PlacementResults) {
@@ -272,9 +292,7 @@ function undoTurn(turn: PlacementResults) {
board.refresh();
}
flipBox.addEventListener('input', flipBox_input);
function flipBox_input(e: Event) {
flipButton.addEventListener('click', () => {
if (currentGame == null) return;
if (replayAnimationAbortController) {
replayAnimationAbortController.abort();
@@ -285,11 +303,20 @@ function flipBox_input(e: Event) {
updateStats(i);
}
}
board.flip = (e.target as HTMLInputElement).checked;
if (currentReplay) {
currentReplay.watchingPlayer++;
if (currentReplay.watchingPlayer >= currentGame.players.length)
currentReplay.watchingPlayer = 0;
gamePage.dataset.myPlayerIndex = currentReplay.watchingPlayer.toString();
board.flip = currentReplay.watchingPlayer % 2 != 0;
clearShowDeck();
replayUpdateHand();
} else
board.flip = !board.flip;
if (board.flip) gamePage.classList.add('boardFlipped');
else gamePage.classList.remove('boardFlipped');
board.resize();
}
});
function addTestDeckCard(card: Card) {
const button = new CardButton('radio', cardDatabase.get(card.number));
@@ -527,14 +554,14 @@ function showResult() {
}
}
handContainer.hidden = true;
if (!currentReplay) {
handContainer.hidden = true;
playRow.hidden = true;
spectatorRow.hidden = false;
replayPreviousButton.hidden = true;
replayNextButton.hidden = true;
shareReplayLinkButton.hidden = false;
flipLabel.hidden = true;
flipButton.hidden = true;
canShareReplay = navigator.canShare && navigator.canShare({ url: window.location.href, title: 'Tableturf Battle Replay' });
shareReplayLinkButton.innerText = canShareReplay ? 'Share replay link' : 'Copy replay link';
}
@@ -547,14 +574,9 @@ function clearShowDeck() {
showDeckButtons.splice(0);
}
function updateHand(playerData: PlayerData) {
for (const button of handButtons) {
handContainer.removeChild(button.element);
}
handButtons.splice(0);
function populateShowDeck(deck: Card[]) {
if (showDeckButtons.length == 0) {
for (const card of playerData.deck!) {
for (const card of deck) {
const button = new CardButton('checkbox', card);
button.inputElement.hidden = true;
showDeckButtons.push(button);
@@ -564,6 +586,15 @@ function updateHand(playerData: PlayerData) {
showDeckListElement.appendChild(li);
}
}
}
function updateHand(playerData: PlayerData) {
for (const button of handButtons) {
handContainer.removeChild(button.element);
}
handButtons.splice(0);
populateShowDeck(playerData.deck!);
for (const button of showDeckButtons) {
const li = button.element.parentElement!;
@@ -643,6 +674,52 @@ function updateHand(playerData: PlayerData) {
}
}
function replayUpdateHand() {
if (currentGame == null || currentReplay == null) return;
for (const button of handButtons) {
handContainer.removeChild(button.element);
}
handButtons.splice(0);
const playerData = currentReplay.replayPlayerData[currentReplay.watchingPlayer];
populateShowDeck(playerData.deck);
for (const b of showDeckButtons)
b.element.parentElement!.className = '';
let indices;
if (currentGame.turnNumber == 0) {
indices = playerData.initialDrawOrder;
} else {
indices = playerData.drawOrder.slice(0, 4);
for (let i = 0; i < currentGame.turnNumber - 1; i++) {
const move = currentReplay.turns[i][currentReplay.watchingPlayer];
let j = indices.findIndex(k => playerData.deck[k].number == move.card.number);
if (j < 0) j = indices.findIndex(k => k < 0 || k >= 15);
if (j >= 0) {
showDeckButtons[indices[j]].element.parentElement!.className = 'used';
if (i < 11)
indices[j] = playerData.drawOrder[4 + i];
}
}
}
for (let i = 0; i < showDeckButtons.length; i++) {
const li = showDeckButtons[i].element.parentElement!;
if (!li.className)
li.className = indices.includes(i) ? 'inHand' : 'unused';
}
for (let i = 0; i < 4; i++) {
if (indices[i] >= 15) continue; // Accounts for an old bug in the server that corrupted the initialDrawOrder replay fields.
const card = playerData.deck[indices[i]];
const button = new CardButton('radio', card);
button.inputElement.hidden = true;
handButtons.push(button);
handContainer.appendChild(button.element);
}
}
(document.getElementById('redrawNoButton') as HTMLButtonElement).addEventListener('click', redrawButton_click);
(document.getElementById('redrawYesButton') as HTMLButtonElement).addEventListener('click', redrawButton_click);
function redrawButton_click(e: MouseEvent) {
@@ -804,9 +881,10 @@ gameDeckButton.addEventListener('click', toggleShowDeck);
showDeckCloseButton.addEventListener('click', toggleShowDeck);
document.addEventListener('keydown', e => {
if (!pages.get('game')!.hidden && !playRow.hidden) {
if (!pages.get('game')!.hidden) {
switch (e.key) {
case 'p':
if (playRow.hidden) return;
if (passButton.enabled) {
passButton.checked = !passButton.checked;
passButton_input();
@@ -815,6 +893,7 @@ document.addEventListener('keydown', e => {
e.preventDefault();
break;
case 's':
if (playRow.hidden) return;
if (specialButton.enabled) {
specialButton.checked = !specialButton.checked;
specialButton_input();
@@ -823,7 +902,8 @@ document.addEventListener('keydown', e => {
e.preventDefault();
break;
case 'd':
if (!gameButtonsContainer.hidden) toggleShowDeck();
if (testMode || gameButtonsContainer.hidden) return;
toggleShowDeck();
e.preventDefault();
break;
}

View File

@@ -230,7 +230,7 @@ function loadReplay(base64: string) {
let pos = 2;
const players = [ ];
currentReplay = { turns: [ ], placements: [ ], drawOrder: [ ], initialDrawOrder: [ ] };
currentReplay = { turns: [ ], placements: [ ], replayPlayerData: [ ], watchingPlayer: 0 };
for (let i = 0; i < numPlayers; i++) {
const len = dataView.getUint8(pos + 34);
const player: Player = {
@@ -245,8 +245,12 @@ function loadReplay(base64: string) {
};
players.push(player);
const deck = [ ];
const initialDrawOrder = [ ];
const drawOrder = [ ];
for (let j = 0; j < 15; j++) {
deck.push(cardDatabase.get(dataView.getUint8(pos + 9 + j)));
}
for (let j = 0; j < 2; j++) {
initialDrawOrder.push(dataView.getUint8(pos + 24 + j) & 0xF);
initialDrawOrder.push(dataView.getUint8(pos + 24 + j) >> 4 & 0xF);
@@ -258,9 +262,7 @@ function loadReplay(base64: string) {
else
drawOrder.push(dataView.getUint8(pos + 26 + j) >> 4 & 0xF);
}
currentReplay.initialDrawOrder.push(initialDrawOrder);
currentReplay.drawOrder.push(drawOrder);
currentReplay.replayPlayerData.push({ deck, initialDrawOrder, drawOrder });
pos += 35 + len;
}
@@ -287,7 +289,7 @@ function loadReplay(base64: string) {
me: null,
players: players,
maxPlayers: numPlayers,
turnNumber: 1,
turnNumber: 0,
webSocket: null
};

View File

@@ -5,3 +5,9 @@ interface PlayerData {
cardsUsed: number[];
move: Move | null;
}
interface ReplayPlayerData {
deck: Card[],
initialDrawOrder: number[],
drawOrder: number[]
}