#1 Implement deck editor test mode. Resolves #1.

This commit is contained in:
Andrio Celos
2022-12-29 19:20:24 +11:00
parent 0236d75e6d
commit 708ad27356
11 changed files with 517 additions and 95 deletions

View File

@@ -3,7 +3,7 @@ class Card {
name: string;
rarity: Rarity;
specialCost: number;
grid: Space[][];
grid: readonly (readonly Space[])[];
size: number;
private minX: number;

View File

@@ -0,0 +1,41 @@
class CardList {
readonly listElement: HTMLElement;
readonly sortBox: HTMLSelectElement;
readonly cardButtons: CardButton[] = [ ];
static readonly cardSortOrders: { [key: string]: ((a: Card, b: Card) => number) | undefined } = {
'number': (a, b) => a.number - b.number,
'name': (a, b) => a.name.localeCompare(b.name),
'size': (a, b) => a.size != b.size ? a.size - b.size : a.number - b.number,
'rarity': (a, b) => a.rarity != b.rarity ? a.rarity - b.rarity : a.number - b.number,
}
constructor(listElement: HTMLElement, sortBox: HTMLSelectElement) {
this.listElement = listElement;
this.sortBox = sortBox;
sortBox.addEventListener('change', () => {
const sortOrder = CardList.cardSortOrders[sortBox.value];
if (sortOrder) {
clearChildren(listElement);
this.cardButtons.sort((a, b) => sortOrder(a.card, b.card));
for (const button of this.cardButtons)
listElement.appendChild(button.element);
}
});
for (const label in CardList.cardSortOrders) {
const option = document.createElement('option');
option.value = label;
option.innerText = label;
sortBox.appendChild(option);
}
}
static fromId(id: string, sortBoxId: string) { return new CardList(document.getElementById(id)!, document.getElementById(sortBoxId) as HTMLSelectElement); }
add(button: CardButton) {
this.cardButtons.push(button);
this.listElement.appendChild(button.element);
}
}

View File

@@ -17,7 +17,7 @@ let currentGame: {
let enterGameTimeout: number | null = null;
let currentReplay: {
turns: Move[][],
placements: { placements: Placement[], specialSpacesActivated: Point[] }[],
placements: PlacementResults[],
initialDrawOrder: number[][],
drawOrder: number[][]
} | null = null;

View File

@@ -1,14 +1,18 @@
/// <reference path="../CardList.ts"/>
const deckNameLabel2 = document.getElementById('deckName2')!;
const deckEditSize = document.getElementById('deckEditSize')!;
const deckCardListEdit = document.getElementById('deckCardListEdit')!;
const cardList = document.getElementById('cardList')!;
const cardList = CardList.fromId('cardList', 'cardListSortBox');
const deckTestButton = document.getElementById('deckTestButton') as HTMLButtonElement;
const deckSaveButton = document.getElementById('deckSaveButton') as HTMLButtonElement;
const deckCancelButton = document.getElementById('deckCancelButton') as HTMLButtonElement;
const deckCardListBackButton = document.getElementById('deckCardListBackButton') as HTMLLinkElement;
const cardListSortBox = document.getElementById('cardListSortBox') as HTMLSelectElement;
const testStageSelectionList = document.getElementById('testStageSelectionList')!;
const testStageButtons: StageButton[] = [ ];
const testStageSelectionDialog = document.getElementById('testStageSelectionDialog') as HTMLDialogElement;
const cardButtons: CardButton[] = [ ];
const deckEditCardButtons: (CardButton | HTMLLabelElement)[] = [ ];
let selectedDeckCardIndex: number | null = null;
@@ -54,10 +58,10 @@ function createDeckEditCardButton(index: number, card: number) {
}
selectedDeckCardIndex = index;
for (const button2 of cardButtons) {
for (const button2 of cardList.cardButtons) {
button2.checked = button2.card.number == card;
}
cardList.parentElement!.classList.add('selecting');
cardList.listElement.parentElement!.classList.add('selecting');
}
});
return button;
@@ -82,10 +86,10 @@ function createDeckEditEmptySlotButton(index: number) {
}
selectedDeckCardIndex = index;
for (const button2 of cardButtons) {
for (const button2 of cardList.cardButtons) {
button2.checked = false;
}
cardList.parentElement!.classList.add('selecting');
cardList.listElement.parentElement!.classList.add('selecting');
}
});
element.appendChild(input);
@@ -122,10 +126,10 @@ function initCardDatabase(cards: Card[]) {
for (const card of cards) {
const button = new CardButton('radio', card);
button.inputElement.name = 'deckEditorCardList';
cardButtons.push(button);
cardList.add(button);
button.inputElement.addEventListener('input', () => {
if (button.inputElement.checked) {
for (const button2 of cardButtons) {
for (const button2 of cardList.cardButtons) {
if (button2 != button)
button2.checked = false;
}
@@ -143,14 +147,14 @@ function initCardDatabase(cards: Card[]) {
deckEditCardButtons[selectedDeckCardIndex] = button3;
deckEditUpdateSize();
cardList.parentElement!.classList.remove('selecting');
cardList.listElement.parentElement!.classList.remove('selecting');
if (!deckModified) {
deckModified = true;
window.addEventListener('beforeunload', onBeforeUnload_deckEditor);
}
}
});
cardList.appendChild(button.element);
addTestCard(card);
}
}
@@ -164,24 +168,7 @@ deckCardListBackButton.addEventListener('click', e => {
(o as HTMLElement).classList.remove('checked');
}
}
cardList.parentElement!.classList.remove('selecting');
});
const cardSortOrders: { [key: string]: ((a: Card, b: Card) => number) | undefined } = {
'number': (a, b) => a.number - b.number,
'name': (a, b) => a.name.localeCompare(b.name),
'size': (a, b) => a.size != b.size ? a.size - b.size : a.number - b.number,
'rarity': (a, b) => a.rarity != b.rarity ? a.rarity - b.rarity : a.number - b.number,
}
cardListSortBox.addEventListener('change', () => {
const sortOrder = cardSortOrders[cardListSortBox.value];
if (sortOrder) {
clearChildren(cardList);
cardButtons.sort((a, b) => sortOrder(a.card, b.card));
for (const button of cardButtons)
cardList.appendChild(button.element);
}
cardList.listElement.parentElement!.classList.remove('selecting');
});
function stopEditingDeck() {
@@ -195,3 +182,39 @@ function onBeforeUnload_deckEditor(e: BeforeUnloadEvent) {
e.preventDefault();
return 'You have unsaved changes to your deck that will be lost. Are you sure you want to continue?';
}
function addTestStage(stage: Stage) {
const button = new StageButton(stage);
testStageButtons.push(button);
button.inputElement.name = 'stage';
button.inputElement.addEventListener('input', () => {
if (button.inputElement.checked) {
stageRandomLabel.classList.remove('checked');
for (const button2 of testStageButtons) {
if (button2 != button)
button2.element.classList.remove('checked');
}
clearChildren(testDeckList);
testDeckCardButtons.splice(0);
for (const el of deckEditCardButtons) {
const card = (el as CardButton).card;
if (card) {
addTestDeckCard(card);
}
}
testStageSelectionDialog.close();
initTest(stage);
}
});
button.setStartSpaces(2);
testStageSelectionList.appendChild(button.element);
}
deckTestButton.addEventListener('click', _ => {
for (const button of testStageButtons)
button.checked = false;
testStageSelectionDialog.showModal();
});

View File

@@ -38,6 +38,20 @@ const showDeckCloseButton = document.getElementById('showDeckCloseButton') as HT
const playContainers = Array.from(document.getElementsByClassName('playContainer')) as HTMLDivElement[];
playContainers.sort((a, b) => parseInt(a.dataset.index || '0') - parseInt(b.dataset.index || '0'));
const testControls = document.getElementById('testControls')!;
const testDeckList = document.getElementById('testDeckList')!;
const testAllCardsList = CardList.fromId('testAllCardsList', 'testAllCardsListSortBox');
const testPlacementList = document.getElementById('testPlacementList')!;
const testDeckButton = CheckButton.fromId('testDeckButton');
const testDeckContainer = document.getElementById('testDeckContainer')!;
const testAllCardsButton = CheckButton.fromId('testAllCardsButton');
const testAllCardsContainer = document.getElementById('testAllCardsContainer')!;
const testUndoButton = document.getElementById('testUndoButton') as HTMLButtonElement;
const testBackButton = document.getElementById('testBackButton') as HTMLButtonElement;
const testDeckCardButtons: CardButton[] = [ ];
const testPlacements: { card: Card, placementResults: PlacementResults }[] = [ ];
const testCardListBackdrop = document.getElementById('testCardListBackdrop')!;
let testMode = false;
let canPlay = false;
@@ -55,23 +69,60 @@ cols[4][4] = Space.SpecialInactive2;
board.resize(cols);
function initGame() {
testMode = false;
gamePage.classList.remove('deckTest');
playControls.hidden = false;
resultContainer.hidden = true;
replayControls.hidden = true;
gameButtonsContainer.hidden = false;
testControls.hidden = true;
showPage('game');
}
function initReplay() {
testMode = false;
gamePage.classList.remove('deckTest');
playControls.hidden = true;
resultContainer.hidden = true;
replayControls.hidden = false;
gameButtonsContainer.hidden = true;
testControls.hidden = true;
canPlay = false;
showPage('game');
clearPlayContainers();
turnNumberLabel.setTurnNumber(1);
}
function initTest(stage: Stage) {
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);
for (let i = 0; i < 2; i++)
board.grid[startSpaces[i].x][startSpaces[i].y] = Space.SpecialInactive1 | i;
board.refresh();
for (var o of playerBars)
o.element.hidden = true;
testMode = true;
testPlacements.splice(0);
clearChildren(testPlacementList);
gamePage.classList.add('deckTest');
gamePage.dataset.myPlayerIndex = '0';
gamePage.dataset.uiBaseColourIsSpecialColour = 'true';
playControls.hidden = true;
resultContainer.hidden = true;
replayControls.hidden = true;
gameButtonsContainer.hidden = false;
testControls.hidden = false;
clearPlayContainers();
turnNumberLabel.setTurnNumber(null);
showPage('game');
testDeckButton.checked = true;
testAllCardsButton.checked = false;
setTestListPage(false);
}
replayNextButton.addEventListener('click', _ => {
if (currentGame == null || currentReplay == null || currentGame.turnNumber > 12) return;
@@ -152,27 +203,10 @@ replayPreviousButton.addEventListener('click', _ => {
el.innerText = '';
}
// Unwind the turn.
for (const p of result.specialSpacesActivated) {
const space = board.grid[p.x][p.y];
const player2 = currentGame.players[space & 3];
player2.specialPoints--;
player2.totalSpecialPoints--;
board.grid[p.x][p.y] &= ~4;
}
currentGame.turnNumber--;
for (let i = result.placements.length - 1; i >= 0; i--) {
const placement = result.placements[i];
for (const p of placement.spacesAffected) {
if (p.oldState == undefined) throw new TypeError('oldState missing');
board.grid[p.space.x][p.space.y] = p.oldState;
}
}
undoTurn(result);
currentGame!.turnNumber--;
gamePage.classList.remove('gameEnded');
turnNumberLabel.setTurnNumber(currentGame.turnNumber);
board.refresh();
turnNumberLabel.setTurnNumber(currentGame!.turnNumber);
for (let i = 0; i < currentGame.players.length; i++) {
const move = currentReplay.turns[currentGame.turnNumber - 1][i];
@@ -186,6 +220,28 @@ replayPreviousButton.addEventListener('click', _ => {
}
});
function undoTurn(turn: PlacementResults) {
for (const p of turn.specialSpacesActivated) {
const space = board.grid[p.x][p.y];
const player2 = currentGame!.players[space & 3];
if (player2) {
player2.specialPoints--;
player2.totalSpecialPoints--;
}
board.grid[p.x][p.y] &= ~4;
}
for (let i = turn.placements.length - 1; i >= 0; i--) {
const placement = turn.placements[i];
for (const p of placement.spacesAffected) {
if (p.oldState == undefined) throw new TypeError('oldState missing');
board.grid[p.space.x][p.space.y] = p.oldState;
}
}
board.refresh();
}
replayFlipBox.addEventListener('input', _ => {
if (currentGame == null || currentReplay == null) return;
if (replayAnimationAbortController) {
@@ -203,6 +259,96 @@ replayFlipBox.addEventListener('input', _ => {
board.resize();
});
function addTestDeckCard(card: Card) {
const button = new CardButton('radio', cardDatabase.get(card.number));
button.inputElement.name = 'deckEditorTestSelectedCard'
testDeckList.appendChild(button.element);
testDeckCardButtons.push(button);
button.inputElement.addEventListener('input', () => {
if (button.checked) {
for (const button2 of testDeckCardButtons.concat(testAllCardsList.cardButtons)) {
if (button2 != button)
button2.element.classList.remove('checked');
}
board.cardPlaying = card;
if (isNaN(board.highlightX) || isNaN(board.highlightY)) {
board.highlightX = board.startSpaces[board.playerIndex!].x - (board.flip ? 4 : 3);
board.highlightY = board.startSpaces[board.playerIndex!].y - (board.flip ? 4 : 3);
}
board.cardRotation = board.flip ? 2 : 0;
board.refreshHighlight();
board.table.focus();
}
});
}
function addTestCard(card: Card) {
const button = new CardButton('radio', cardDatabase.get(card.number));
button.inputElement.name = 'deckEditorTestSelectedCard'
testAllCardsList.add(button);
button.inputElement.addEventListener('input', () => {
if (button.checked) {
for (const button2 of testDeckCardButtons.concat(testAllCardsList.cardButtons)) {
if (button2 != button)
button2.element.classList.remove('checked');
}
board.cardPlaying = card;
if (isNaN(board.highlightX) || isNaN(board.highlightY)) {
board.highlightX = board.startSpaces[board.playerIndex!].x - (board.flip ? 4 : 3);
board.highlightY = board.startSpaces[board.playerIndex!].y - (board.flip ? 4 : 3);
}
board.cardRotation = board.flip ? 2 : 0;
board.refreshHighlight();
board.table.focus();
if (!testCardListBackdrop.hidden) {
testCardListBackdrop.hidden = true;
gamePage.removeChild(testAllCardsContainer);
testControls.appendChild(testAllCardsContainer);
setTestListPage(false);
}
}
});
}
testUndoButton.addEventListener('click', _ => {
const turn = testPlacements.pop();
if (turn) {
undoTurn(turn.placementResults);
testPlacementList.removeChild(testPlacementList.firstChild!);
for (const button of testDeckCardButtons.concat(testAllCardsList.cardButtons)) {
if (button.card.number == turn.card.number)
button.enabled = true;
}
testUndoButton.disabled = testPlacements.length == 0;
}
});
testBackButton.addEventListener('click', _ => {
showPage('deckEdit');
});
testDeckButton.input.addEventListener('input', _ => {
if (testDeckButton.checked) setTestListPage(false);
});
testAllCardsButton.input.addEventListener('input', _ => {
if (testAllCardsButton.checked) setTestListPage(true);
});
function setTestListPage(showAllCards: boolean) {
testDeckButton.checked = !showAllCards;
testAllCardsButton.checked = showAllCards;
testDeckContainer.hidden = showAllCards
testAllCardsContainer.hidden = !showAllCards;
}
document.getElementById('testAllCardsMobileButton')!.addEventListener('click', _ => {
setTestListPage(true);
testControls.removeChild(testAllCardsContainer);
gamePage.appendChild(testAllCardsContainer);
testCardListBackdrop.hidden = false;
});
function loadPlayers(players: Player[]) {
gamePage.dataset.players = players.length.toString();
for (let i = 0; i < players.length; i++) {
@@ -359,6 +505,7 @@ function showResult() {
function clearShowDeck() {
showDeckContainer.hidden = true;
testCardListBackdrop.hidden = true;
clearChildren(showDeckListElement);
showDeckButtons.splice(0);
}
@@ -522,7 +669,26 @@ board.onclick = (x, y) => {
if (testMode) {
const move: PlayMove = { card: board.cardPlaying, isPass: false, x, y, rotation: board.cardRotation, isSpecialAttack: false };
const r = board.makePlacements([ move ]);
testPlacements.push({ card: board.cardPlaying, placementResults: r });
board.refresh();
var li = document.createElement('div');
li.innerText = board.cardPlaying.name;
if (testDeckCardButtons.find(b => b.card.number == board.cardPlaying!.number))
li.classList.add('deckCard');
else
li.classList.add('externalCard');
testPlacementList.insertBefore(li, testPlacementList.firstChild);
for (const button of testDeckCardButtons.concat(testAllCardsList.cardButtons)) {
if (button.card.number == board.cardPlaying.number) {
button.checked = false;
button.enabled = false;
}
}
board.cardPlaying = null;
testUndoButton.disabled = false;
} else if (canPlay) {
canPlay = false;
// Send the play to the server.
@@ -580,9 +746,11 @@ function focusFirstEnabledHandCard() {
function toggleShowDeck() {
if (showDeckContainer.hidden) {
showDeckContainer.hidden = false;
testCardListBackdrop.hidden = false;
showDeckCloseButton.focus();
} else {
showDeckContainer.hidden = true;
testCardListBackdrop.hidden = true;
focusFirstEnabledHandCard();
}
}
@@ -596,7 +764,7 @@ rotateRightButton.addEventListener('click', () => {
board.refreshHighlight();
});
gameDeckButton.addEventListener('click', toggleShowDeck);
showDeckCloseButton.addEventListener('click', () => showDeckContainer.hidden = true);
showDeckCloseButton.addEventListener('click', toggleShowDeck);
document.addEventListener('keydown', e => {
if (!pages.get('game')!.hidden) {

View File

@@ -213,6 +213,7 @@ function initStageDatabase(stages: Stage[]) {
});
button.setStartSpaces(2);
stageList.appendChild(button.element);
addTestStage(stage);
}
document.getElementById('stageListLoadingSection')!.hidden = true;
}

View File

@@ -255,7 +255,7 @@ function loadReplay(base64: string) {
webSocket: null
};
board.resize(stage.grid);
board.resize(stage.copyGrid());
const startSpaces = stage.getStartSpaces(numPlayers);
for (let i = 0; i < numPlayers; i++)
board.grid[startSpaces[i].x][startSpaces[i].y] = Space.SpecialInactive1 | i;

View File

@@ -2,3 +2,8 @@ interface Placement {
players: number[],
spacesAffected: { space: Point, newState: Space, oldState?: Space }[]
}
interface PlacementResults {
placements: Placement[],
specialSpacesActivated: Point[]
}

View File

@@ -1,6 +1,6 @@
class Stage {
name: string;
grid: Space[][];
grid: readonly (readonly Space[])[];
startSpaces: Point[][];
constructor(name: string, grid: Space[][], startSpaces: Point[][]) {
@@ -21,4 +21,8 @@ class Stage {
}
return list!;
}
copyGrid() {
return Array.from(this.grid, a => a.slice(0));
}
}