mirror of
https://github.com/AndrioCelos/TableturfBattleApp.git
synced 2026-08-27 05:14:47 -05:00
@@ -202,6 +202,33 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="testControls">
|
||||
<div id="testControlsHeader" class="testControls">
|
||||
<label for="testDeckButton">
|
||||
<input type="radio" id="testDeckButton" name="testPage"/>Deck
|
||||
</label>
|
||||
<label for="testAllCardsButton">
|
||||
<input type="radio" id="testAllCardsButton" name="testPage"/>All cards
|
||||
</label>
|
||||
</div>
|
||||
<div id="testDeckContainer" class="testControls">
|
||||
<button id="testAllCardsMobileButton">All cards</button>
|
||||
<div id="testDeckList"></div>
|
||||
</div>
|
||||
<div id="testAllCardsContainer" class="testControls" hidden>
|
||||
<label for="testAllCardsListSortBox">
|
||||
Sort by
|
||||
<select id="testAllCardsListSortBox" autocomplete="off"></select>
|
||||
</label>
|
||||
<div id="testAllCardsList" class="cardListGrid"></div>
|
||||
</div>
|
||||
<div id="testControlsFooter" class="testControls">
|
||||
<button type="button" id="testUndoButton" disabled>Undo</button>
|
||||
<button type="button" id="testBackButton">End test</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="testCardListBackdrop" hidden></div>
|
||||
|
||||
<div id="turnNumberContainer" hidden>
|
||||
<p>Turns left</p>
|
||||
<div id="turnNumberLabel">12</div>
|
||||
@@ -233,6 +260,8 @@
|
||||
<div class="playContainer" data-index="3"></div>
|
||||
<div class="playContainer" data-index="2"></div>
|
||||
<div class="playContainer" data-index="0"></div>
|
||||
|
||||
<div id="testPlacementList"></div>
|
||||
</div>
|
||||
<div id="deckListPage" hidden>
|
||||
<section id="deckEditordeckListPage">
|
||||
@@ -295,16 +324,18 @@
|
||||
<a id="deckCardListBackButton" href="#">Back</a>
|
||||
<label for="cardListSortBox">
|
||||
Sort by
|
||||
<select id="cardListSortBox" autocomplete="off">
|
||||
<option value="number">number</option>
|
||||
<option value="name">name</option>
|
||||
<option value="size">size</option>
|
||||
<option value="rarity">rarity</option>
|
||||
</select>
|
||||
<select id="cardListSortBox" autocomplete="off"></select>
|
||||
</label>
|
||||
<div id="cardList"></div>
|
||||
<div id="cardList" class="cardListGrid"></div>
|
||||
</section>
|
||||
<div id="deckEditorCardListBackdrop"></div>
|
||||
<dialog id="testStageSelectionDialog">
|
||||
<h3>Select a stage.</h3>
|
||||
<form id="testStageSelectionForm" method="dialog">
|
||||
<div id="testStageSelectionList"></div>
|
||||
<button type="submit">Cancel</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
<dialog id="errorDialog">
|
||||
<div id="errorMessage"></div>
|
||||
|
||||
@@ -3,7 +3,7 @@ class Card {
|
||||
name: string;
|
||||
rarity: Rarity;
|
||||
specialCost: number;
|
||||
grid: Space[][];
|
||||
grid: readonly (readonly Space[])[];
|
||||
size: number;
|
||||
|
||||
private minX: number;
|
||||
|
||||
41
TableturfBattleClient/src/CardList.ts
Normal file
41
TableturfBattleClient/src/CardList.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -213,6 +213,7 @@ function initStageDatabase(stages: Stage[]) {
|
||||
});
|
||||
button.setStartSpaces(2);
|
||||
stageList.appendChild(button.element);
|
||||
addTestStage(stage);
|
||||
}
|
||||
document.getElementById('stageListLoadingSection')!.hidden = true;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -2,3 +2,8 @@ interface Placement {
|
||||
players: number[],
|
||||
spacesAffected: { space: Point, newState: Space, oldState?: Space }[]
|
||||
}
|
||||
|
||||
interface PlacementResults {
|
||||
placements: Placement[],
|
||||
specialSpacesActivated: Point[]
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,9 @@ body {
|
||||
--primary-colour-4 : hsl(155, 95%, 50%);
|
||||
--special-colour-4 : hsl(120, 95%, 50%);
|
||||
--special-accent-colour-4: hsl(135, 95%, 85%);
|
||||
--player-primary-colour: var(--primary-colour-1);
|
||||
--player-special-colour: var(--special-colour-1);
|
||||
--player-special-accent-colour: var(--special-accent-colour-1);
|
||||
}
|
||||
|
||||
#gamePage {
|
||||
@@ -410,23 +413,34 @@ dialog::backdrop {
|
||||
/* Card list */
|
||||
|
||||
#cardList {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
overflow-y: scroll;
|
||||
grid-row: 2;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.cardListControl {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
}
|
||||
.cardListGrid:not([hidden]) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(11em, 1fr));
|
||||
overflow-y: auto;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
/* Game page */
|
||||
|
||||
#gamePage:not([hidden]) {
|
||||
--controls-width: 11em;
|
||||
display: grid;
|
||||
grid-template-columns: 11em 11em [score-column] 1fr [board-column] auto 1fr [play-column-1] 10em [play-column-2] 10em;
|
||||
grid-template-columns: var(--controls-width) var(--controls-width) [score-column] 1fr [board-column] auto 1fr [play-column-1] 10em [play-column-2] 10em;
|
||||
grid-template-rows: [player-row-1] auto [player-row-3] auto [hand-row] 1fr [player-row-2] auto [player-row-0] auto;
|
||||
height: 100vh;
|
||||
}
|
||||
#gamePage.deckTest {
|
||||
--controls-width: 14em;
|
||||
}
|
||||
#gamePage:where([data-players="2"]):not([hidden]) {
|
||||
grid-template-columns: 11em 11em [score-column] 1fr [board-column] auto 1fr [play-column-1 play-column-2] 20em;
|
||||
grid-template-columns: var(--controls-width) var(--controls-width) [score-column] 1fr [board-column] auto 1fr [play-column-1 play-column-2] 20em;
|
||||
}
|
||||
|
||||
.playerBar[data-index="0"] {
|
||||
@@ -469,7 +483,8 @@ dialog::backdrop {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
#scoreSection {
|
||||
#gamePage.deckTest #scoreSection { display: none; }
|
||||
#scoreSection:not([hidden]) {
|
||||
grid-column: score-column;
|
||||
grid-row: 2 / -2;
|
||||
display: flex;
|
||||
@@ -501,7 +516,7 @@ dialog::backdrop {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#playControls > label {
|
||||
:is(#playControls, #testControlsHeader) > label {
|
||||
grid-row: button-row;
|
||||
margin: 0.5em;
|
||||
padding: 0 0.5em;
|
||||
@@ -510,14 +525,14 @@ dialog::backdrop {
|
||||
text-shadow: 0 0 4px black;
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
#playControls > label input {
|
||||
:is(#playControls, #testControlsHeader) > label input {
|
||||
position: absolute;
|
||||
left: -333px;
|
||||
}
|
||||
#playControls > label:hover { background: var(--player-ui-highlight-colour); }
|
||||
#playControls > label:focus-within { outline: 2px solid var(--player-ui-highlight2-colour); }
|
||||
#playControls > label:is(:active, .checked) { background: var(--player-ui-highlight2-colour); }
|
||||
#playControls > label.disabled { background: grey; }
|
||||
:is(#playControls, #testControlsHeader) > label:hover { background: var(--player-ui-highlight-colour); }
|
||||
:is(#playControls, #testControlsHeader) > label:focus-within { outline: 2px solid var(--player-ui-highlight2-colour); }
|
||||
:is(#playControls, #testControlsHeader) > label:is(:active, .checked) { background: var(--player-ui-highlight2-colour); }
|
||||
:is(#playControls, #testControlsHeader) > label.disabled { background: grey; }
|
||||
|
||||
#showDeckContainer {
|
||||
grid-column: 1 / span 3;
|
||||
@@ -531,7 +546,7 @@ dialog::backdrop {
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
#showDeckList {
|
||||
#showDeckList, #testDeckList {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto;
|
||||
list-style-type: none;
|
||||
@@ -540,6 +555,7 @@ dialog::backdrop {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
}
|
||||
#showDeckList > li {
|
||||
margin: 0.25em;
|
||||
@@ -552,22 +568,76 @@ dialog::backdrop {
|
||||
#showDeckList > li.used {
|
||||
opacity: 0.35;
|
||||
}
|
||||
#showDeckCloseButton {
|
||||
#showDeckCloseButton, #testControlsFooter button {
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
color: inherit;
|
||||
width: 40%;
|
||||
grid-row: button-row;
|
||||
margin: 1em;
|
||||
margin: 0.5em;
|
||||
padding: 0 0.5em;
|
||||
text-align: center;
|
||||
background: var(--player-ui-base-colour);
|
||||
text-shadow: 0 0 4px black;
|
||||
border-radius: 0.5em;
|
||||
font-size: inherit;
|
||||
}
|
||||
#showDeckCloseButton {
|
||||
width: 40%;
|
||||
grid-row: button-row;
|
||||
}
|
||||
:is(#showDeckCloseButton, #testControlsFooter button):hover { background: var(--player-ui-highlight-colour); }
|
||||
:is(#showDeckCloseButton, #testControlsFooter button):focus-within { outline: 2px solid var(--player-ui-highlight2-colour); }
|
||||
:is(#showDeckCloseButton, #testControlsFooter button):active { background: var(--player-ui-highlight2-colour); }
|
||||
|
||||
#testControls:not([hidden]) {
|
||||
grid-column: 1 / span 2;
|
||||
grid-row: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-rows: [header] auto [list] 1fr [buttons] auto;
|
||||
}
|
||||
#testControlsHeader, #testControlsFooter {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
#testDeckContainer {
|
||||
grid-row: list;
|
||||
overflow: hidden;
|
||||
}
|
||||
#testDeckList {
|
||||
height: 100%;
|
||||
}
|
||||
#testAllCardsContainer:not([hidden]) {
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
grid-row: 2;
|
||||
}
|
||||
#testAllCardsMobileButton {
|
||||
display: none;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
background: transparent;
|
||||
border: 0.25em solid var(--player-ui-base-colour);
|
||||
}
|
||||
#testAllCardsMobileButton:hover { border-color: var(--player-ui-highlight-colour); }
|
||||
#testAllCardsMobileButton:focus-within { outline: 2px solid var(--player-special-accent-colour); }
|
||||
#testAllCardsMobileButton:active { border-color: var(--player-special-accent-colour); }
|
||||
|
||||
#testDeckList, #testAllCardsList {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#testAllCardsList:not([hidden]) {
|
||||
font-size: 70%;
|
||||
}
|
||||
#testCardListBackdrop {
|
||||
background: #00000080;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
#showDeckCloseButton:hover { background: var(--player-ui-highlight-colour); }
|
||||
#showDeckCloseButton:focus-within { outline: 2px solid var(--player-ui-highlight2-colour); }
|
||||
#showDeckCloseButton:active { background: var(--player-ui-highlight2-colour); }
|
||||
|
||||
.playContainer { text-align: center; }
|
||||
|
||||
@@ -585,6 +655,25 @@ dialog::backdrop {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#testPlacementList {
|
||||
display: none;
|
||||
}
|
||||
#gamePage.deckTest #testPlacementList {
|
||||
grid-column: -3 / -1;
|
||||
grid-row: 1 / -1;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
margin: 1em;
|
||||
gap: 1em;
|
||||
}
|
||||
#testPlacementList div {
|
||||
background: #222;
|
||||
padding: 0.5em;
|
||||
}
|
||||
#testPlacementList div.deckCard {
|
||||
background: #246;
|
||||
}
|
||||
|
||||
#gameButtonsContainer {
|
||||
grid-column: score-column;
|
||||
grid-row: 1 / -1;
|
||||
@@ -613,6 +702,9 @@ dialog::backdrop {
|
||||
opacity: 1;
|
||||
color: var(--player-ui-highlight-colour);
|
||||
}
|
||||
#gamePage.deckTest #gameDeckButton {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#boardSection {
|
||||
grid-column: board-column;
|
||||
@@ -866,7 +958,7 @@ dialog::backdrop {
|
||||
:is(#deckListPage, #deckEditPage):not([hidden]) {
|
||||
height: 100vh;
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
|
||||
.deckList > label, #addDeckControls > button {
|
||||
@@ -920,7 +1012,7 @@ dialog::backdrop {
|
||||
#deckCardListView, #deckCardListEdit {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
grid-column: 2;
|
||||
grid-row: 1 / 5;
|
||||
overflow-y: scroll;
|
||||
@@ -976,6 +1068,13 @@ dialog::backdrop {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#testStageSelectionList {
|
||||
max-width: 52em;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Small display layout */
|
||||
@media (max-width: 89rem) or (max-height: 42rem) {
|
||||
#gamePage, #deckCardListView, #deckCardListEdit {
|
||||
@@ -988,7 +1087,7 @@ dialog::backdrop {
|
||||
}
|
||||
|
||||
#gamePage:not([hidden]) {
|
||||
grid-template-columns: 11em 11em [score-column] 1fr [board-column] auto 1fr [play-column-1 play-column-2] 10em;
|
||||
grid-template-columns: var(--controls-width) var(--controls-width) [score-column] 1fr [board-column] auto 1fr [play-column-1 play-column-2] 10em;
|
||||
}
|
||||
|
||||
.playContainer { position: relative; }
|
||||
@@ -1019,9 +1118,15 @@ dialog::backdrop {
|
||||
@media (max-width: 40rem) {
|
||||
.card {
|
||||
margin: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.cardListGrid:not([hidden]) {
|
||||
justify-items: stretch;
|
||||
}
|
||||
.cardListGrid .card {
|
||||
width: auto;
|
||||
justify-self: stretch;
|
||||
}
|
||||
|
||||
/* Lobby - Mobile layout */
|
||||
|
||||
#lobbyPage:not([hidden]) {
|
||||
@@ -1034,14 +1139,13 @@ dialog::backdrop {
|
||||
height: initial;
|
||||
}
|
||||
|
||||
#stageList {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
font-size: x-small;
|
||||
#stageList, #testStageSelectionList {
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
.stage, .stageRandom {
|
||||
width: auto;
|
||||
flex-basis: 10em;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* Game - Mobile layout */
|
||||
@@ -1085,7 +1189,7 @@ dialog::backdrop {
|
||||
width: initial;
|
||||
}
|
||||
|
||||
#scoreSection {
|
||||
#scoreSection:not([hidden]) {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
grid-template-rows: 1fr;
|
||||
@@ -1108,7 +1212,7 @@ dialog::backdrop {
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
grid-template-rows: auto;
|
||||
width: 100vw;
|
||||
overflow: scroll visible;
|
||||
overflow: scroll hidden;
|
||||
}
|
||||
|
||||
#showDeckContainer {
|
||||
@@ -1149,6 +1253,14 @@ dialog::backdrop {
|
||||
#gameButtonsContainer button {
|
||||
font-size: 250%;
|
||||
}
|
||||
|
||||
#gamePage > #testAllCardsContainer:not([hidden]) {
|
||||
grid-row: 1 / -1;
|
||||
grid-column: 1 / -1;
|
||||
background: black;
|
||||
z-index: 2;
|
||||
margin-top: 5em;
|
||||
}
|
||||
|
||||
/* Deck editor - Mobile layout */
|
||||
|
||||
@@ -1220,14 +1332,51 @@ dialog::backdrop {
|
||||
}
|
||||
|
||||
#cardList {
|
||||
font-size: 50%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||
font-size: 55%;
|
||||
}
|
||||
|
||||
#cardList .card {
|
||||
height: 13em;
|
||||
}
|
||||
|
||||
#testControls:not([hidden]) {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: -2;
|
||||
}
|
||||
#testControlsHeader {
|
||||
display: none;
|
||||
}
|
||||
#testAllCardsMobileButton {
|
||||
display: inline-block;
|
||||
width: 7em;
|
||||
}
|
||||
#testDeckContainer:not([hidden]) {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
#testDeckList {
|
||||
width: max-content;
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
overflow: initial;
|
||||
}
|
||||
|
||||
#gamePage.deckTest #testPlacementList {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 1;
|
||||
height: 7em;
|
||||
flex-flow: row wrap;
|
||||
align-content: flex-start;
|
||||
margin: 0.5em;
|
||||
gap: 0.5em;
|
||||
}
|
||||
#testPlacementList div {
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
/* Loading spinner - based on loading.io */
|
||||
|
||||
Reference in New Issue
Block a user