mirror of
https://github.com/AndrioCelos/TableturfBattleApp.git
synced 2026-04-22 08:37:21 -05:00
Minor fixes and refactoring
This commit is contained in:
parent
7a24364c7a
commit
d042d0e79b
|
|
@ -1,5 +1,3 @@
|
|||
/// <reference path="../CardList.ts"/>
|
||||
|
||||
const deckNameLabel2 = document.getElementById('deckName2')!;
|
||||
const deckEditSize = document.getElementById('deckEditSize')!;
|
||||
const deckCardListEdit = document.getElementById('deckCardListEdit')!;
|
||||
|
|
@ -17,6 +15,82 @@ const deckEditCardButtons: (CardButton | HTMLLabelElement)[] = [ ];
|
|||
|
||||
let selectedDeckCardIndex: number | null = null;
|
||||
|
||||
function deckEditInitCardDatabase(cards: Card[]) {
|
||||
for (const card of cards) {
|
||||
const button = new CardButton('radio', card);
|
||||
button.inputElement.name = 'deckEditorCardList';
|
||||
cardList.add(button);
|
||||
button.inputElement.addEventListener('input', () => {
|
||||
if (button.inputElement.checked) {
|
||||
for (const button2 of cardList.cardButtons) {
|
||||
if (button2 != button)
|
||||
button2.checked = false;
|
||||
}
|
||||
|
||||
if (selectedDeckCardIndex == null) return;
|
||||
const oldButton = deckEditCardButtons[selectedDeckCardIndex];
|
||||
|
||||
const button3 = createDeckEditCardButton(selectedDeckCardIndex, card.number);
|
||||
button3.checked = true;
|
||||
|
||||
const oldElement = (oldButton as CardButton).element ?? (oldButton as Element);
|
||||
deckCardListEdit.insertBefore(button3.element, oldElement);
|
||||
deckCardListEdit.removeChild(oldElement);
|
||||
|
||||
deckEditCardButtons[selectedDeckCardIndex] = button3;
|
||||
deckEditUpdateSize();
|
||||
|
||||
cardList.listElement.parentElement!.classList.remove('selecting');
|
||||
if (!deckModified) {
|
||||
deckModified = true;
|
||||
window.addEventListener('beforeunload', onBeforeUnload_deckEditor);
|
||||
}
|
||||
}
|
||||
});
|
||||
addTestCard(card);
|
||||
}
|
||||
}
|
||||
|
||||
function deckEditInitStageDatabase(stages: Stage[]) {
|
||||
for (const stage of stages) {
|
||||
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);
|
||||
|
||||
if (editingDeck) {
|
||||
for (const el of deckEditCardButtons) {
|
||||
const card = (el as CardButton).card;
|
||||
if (card) {
|
||||
addTestDeckCard(card);
|
||||
}
|
||||
}
|
||||
} else if (selectedDeck) {
|
||||
for (const cardNumber of selectedDeck.cards) {
|
||||
if (cardNumber > 0 && cardNumber <= cardDatabase.cards!.length) {
|
||||
addTestDeckCard(cardDatabase.get(cardNumber));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testStageSelectionDialog.close();
|
||||
initTest(stage);
|
||||
}
|
||||
});
|
||||
button.setStartSpaces(2);
|
||||
testStageSelectionList.appendChild(button.element);
|
||||
}
|
||||
}
|
||||
|
||||
function editDeck() {
|
||||
if (selectedDeck == null) return;
|
||||
|
||||
|
|
@ -124,42 +198,6 @@ function deckEditUpdateSize() {
|
|||
deckEditSize.innerText = size.toString();
|
||||
}
|
||||
|
||||
function initCardDatabase(cards: Card[]) {
|
||||
for (const card of cards) {
|
||||
const button = new CardButton('radio', card);
|
||||
button.inputElement.name = 'deckEditorCardList';
|
||||
cardList.add(button);
|
||||
button.inputElement.addEventListener('input', () => {
|
||||
if (button.inputElement.checked) {
|
||||
for (const button2 of cardList.cardButtons) {
|
||||
if (button2 != button)
|
||||
button2.checked = false;
|
||||
}
|
||||
|
||||
if (selectedDeckCardIndex == null) return;
|
||||
const oldButton = deckEditCardButtons[selectedDeckCardIndex];
|
||||
|
||||
const button3 = createDeckEditCardButton(selectedDeckCardIndex, card.number);
|
||||
button3.checked = true;
|
||||
|
||||
const oldElement = (oldButton as CardButton).element ?? (oldButton as Element);
|
||||
deckCardListEdit.insertBefore(button3.element, oldElement);
|
||||
deckCardListEdit.removeChild(oldElement);
|
||||
|
||||
deckEditCardButtons[selectedDeckCardIndex] = button3;
|
||||
deckEditUpdateSize();
|
||||
|
||||
cardList.listElement.parentElement!.classList.remove('selecting');
|
||||
if (!deckModified) {
|
||||
deckModified = true;
|
||||
window.addEventListener('beforeunload', onBeforeUnload_deckEditor);
|
||||
}
|
||||
}
|
||||
});
|
||||
addTestCard(card);
|
||||
}
|
||||
}
|
||||
|
||||
deckCardListBackButton.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
for (const o of deckEditCardButtons) {
|
||||
|
|
@ -186,44 +224,6 @@ function onBeforeUnload_deckEditor(e: BeforeUnloadEvent) {
|
|||
return 'You have unsaved changes to your deck that will be lost.';
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (editingDeck) {
|
||||
for (const el of deckEditCardButtons) {
|
||||
const card = (el as CardButton).card;
|
||||
if (card) {
|
||||
addTestDeckCard(card);
|
||||
}
|
||||
}
|
||||
} else if (selectedDeck) {
|
||||
for (const cardNumber of selectedDeck.cards) {
|
||||
if (cardNumber > 0 && cardNumber <= cardDatabase.cards!.length) {
|
||||
addTestDeckCard(cardDatabase.get(cardNumber));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testStageSelectionDialog.close();
|
||||
initTest(stage);
|
||||
}
|
||||
});
|
||||
button.setStartSpaces(2);
|
||||
testStageSelectionList.appendChild(button.element);
|
||||
}
|
||||
|
||||
deckTestButton.addEventListener('click', _ => {
|
||||
for (const button of testStageButtons)
|
||||
button.checked = false;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
/// <reference path="../TimeLabel.ts"/>
|
||||
|
||||
const gamePage = document.getElementById('gamePage')!;
|
||||
const board = new Board(document.getElementById('gameBoard') as HTMLTableElement);
|
||||
const turnNumberLabel = new TurnNumberLabel(document.getElementById('turnNumberContainer')!, document.getElementById('turnNumberLabel')!);
|
||||
|
|
@ -91,6 +89,7 @@ function clear() {
|
|||
}
|
||||
}
|
||||
|
||||
/** Shows the game page for playing in a game. */
|
||||
function initGame() {
|
||||
clear();
|
||||
gameControls.hidden = false;
|
||||
|
|
@ -100,6 +99,7 @@ function initGame() {
|
|||
showPage('game');
|
||||
}
|
||||
|
||||
/** Shows the game page for spectating a game. */
|
||||
function initSpectator() {
|
||||
clear();
|
||||
gameControls.hidden = false;
|
||||
|
|
@ -109,6 +109,7 @@ function initSpectator() {
|
|||
showPage('game');
|
||||
}
|
||||
|
||||
/** Shows the game page for viewing a replay. */
|
||||
function initReplay() {
|
||||
clear();
|
||||
gamePage.classList.add('replay');
|
||||
|
|
@ -127,6 +128,7 @@ function initReplay() {
|
|||
replayUpdateHand();
|
||||
}
|
||||
|
||||
/** Shows the game page for deck editor testing. */
|
||||
function initTest(stage: Stage) {
|
||||
clear();
|
||||
testMode = true;
|
||||
|
|
@ -148,6 +150,7 @@ function initTest(stage: Stage) {
|
|||
gameButtonsContainer.hidden = false;
|
||||
testControls.hidden = false;
|
||||
clearPlayContainers();
|
||||
timeLabel.hide();
|
||||
turnNumberLabel.setTurnNumber(null);
|
||||
showPage('game');
|
||||
|
||||
|
|
@ -274,6 +277,7 @@ replayPreviousButton.addEventListener('click', _ => {
|
|||
replayUpdateHand();
|
||||
});
|
||||
|
||||
/** Undoes the last turn of the current replay, returning the view to the game state before that turn. */
|
||||
function undoTurn(turn: PlacementResults) {
|
||||
for (const p of turn.specialSpacesActivated) {
|
||||
const space = board.grid[p.x][p.y];
|
||||
|
|
@ -334,6 +338,7 @@ function addTestDeckCard(card: Card) {
|
|||
if (button2 != button)
|
||||
button2.element.classList.remove('checked');
|
||||
}
|
||||
board.autoHighlight = true;
|
||||
board.cardPlaying = card;
|
||||
if (isNaN(board.highlightX) || isNaN(board.highlightY)) {
|
||||
board.highlightX = board.startSpaces[board.playerIndex!].x - (board.flip ? 4 : 3);
|
||||
|
|
@ -356,6 +361,7 @@ function addTestCard(card: Card) {
|
|||
if (button2 != button)
|
||||
button2.element.classList.remove('checked');
|
||||
}
|
||||
board.autoHighlight = true;
|
||||
board.cardPlaying = card;
|
||||
if (isNaN(board.highlightX) || isNaN(board.highlightY)) {
|
||||
board.highlightX = board.startSpaces[board.playerIndex!].x - (board.flip ? 4 : 3);
|
||||
|
|
@ -413,6 +419,7 @@ document.getElementById('testAllCardsMobileButton')!.addEventListener('click', _
|
|||
testCardListBackdrop.hidden = false;
|
||||
});
|
||||
|
||||
/** Updates the game view with received player data. */
|
||||
function loadPlayers(players: Player[]) {
|
||||
gamePage.dataset.players = players.length.toString();
|
||||
const scores = board.getScores();
|
||||
|
|
@ -442,6 +449,7 @@ function updateStats(playerIndex: number, scores: number[]) {
|
|||
playerBars[playerIndex].statPassesElement.innerText = currentGame.players[playerIndex].passes.toString();
|
||||
}
|
||||
|
||||
/** Shows the ready indication for the specified player. */
|
||||
function showReady(playerIndex: number) {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'cardBack';
|
||||
|
|
@ -455,14 +463,13 @@ function clearPlayContainers() {
|
|||
}
|
||||
}
|
||||
|
||||
function setupControlsForPlay() {
|
||||
/** Clears the game page controls for a new turn. */
|
||||
function resetPlayControls() {
|
||||
passButton.checked = false;
|
||||
specialButton.checked = false;
|
||||
board.specialAttack = false;
|
||||
board.cardPlaying = null;
|
||||
if (canPlay && currentGame?.me?.hand != null) {
|
||||
passButton.enabled = true;
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
canPlayCard[i] = board.canPlayCard(currentGame.me.playerIndex, currentGame.me.hand[i], false);
|
||||
canPlayCardAsSpecialAttack[i] = currentGame.players[currentGame.me.playerIndex].specialPoints >= currentGame.me.hand[i].specialCost
|
||||
|
|
@ -470,16 +477,29 @@ function setupControlsForPlay() {
|
|||
handButtons[i].enabled = canPlayCard[i];
|
||||
}
|
||||
|
||||
passButton.enabled = true;
|
||||
specialButton.enabled = canPlayCardAsSpecialAttack.includes(true);
|
||||
board.autoHighlight = true;
|
||||
focusFirstEnabledHandCard();
|
||||
} else {
|
||||
for (const button of handButtons) {
|
||||
button.enabled = false;
|
||||
lockGamePage();
|
||||
if (currentGame?.me?.move) {
|
||||
for (const button of handButtons)
|
||||
button.checked = button.card.number == currentGame.me.move.card.number;
|
||||
passButton.checked = currentGame.me.move.isPass;
|
||||
specialButton.checked = (currentGame.me.move as PlayMove).isSpecialAttack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Disables controls on the game page after a play is submitted. */
|
||||
function lockGamePage() {
|
||||
canPlay = false;
|
||||
board.autoHighlight = false;
|
||||
for (const el of handButtons) el.enabled = false;
|
||||
passButton.enabled = false;
|
||||
specialButton.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function playInkAnimations(data: {
|
||||
game: { state: GameState, board: Space[][] | null, turnNumber: number, players: Player[] },
|
||||
|
|
@ -596,7 +616,8 @@ function populateShowDeck(deck: Card[]) {
|
|||
}
|
||||
}
|
||||
|
||||
function updateHand(playerData: PlayerData) {
|
||||
/** Handles an update to the player's hand and/or deck during a game. */
|
||||
function updateHandAndDeck(playerData: PlayerData) {
|
||||
for (const button of handButtons) {
|
||||
handContainer.removeChild(button.element);
|
||||
}
|
||||
|
|
@ -629,19 +650,9 @@ function updateHand(playerData: PlayerData) {
|
|||
}
|
||||
if (passButton.checked) {
|
||||
if (canPlay) {
|
||||
canPlay = false;
|
||||
timeLabel.faded = true;
|
||||
// Send the play to the server.
|
||||
let req = new XMLHttpRequest();
|
||||
req.open('POST', `${config.apiBaseUrl}/games/${currentGame!.id}/play`);
|
||||
req.addEventListener('error', () => communicationError());
|
||||
let data = new URLSearchParams();
|
||||
data.append('clientToken', clientToken);
|
||||
data.append('cardNumber', card.number.toString());
|
||||
data.append('isPass', 'true');
|
||||
req.send(data.toString());
|
||||
|
||||
board.autoHighlight = false;
|
||||
lockGamePage();
|
||||
sendPlay({ clientToken, cardNumber: card.number.toString(), isPass: 'true' });
|
||||
}
|
||||
} else {
|
||||
board.cardPlaying = card;
|
||||
|
|
@ -683,6 +694,7 @@ function updateHand(playerData: PlayerData) {
|
|||
}
|
||||
}
|
||||
|
||||
/** Handles an update to the player's hand and/or deck during a replay. */
|
||||
function replayUpdateHand() {
|
||||
if (currentGame == null || currentReplay == null) return;
|
||||
|
||||
|
|
@ -781,6 +793,25 @@ function specialButton_input() {
|
|||
}
|
||||
specialButton.input.addEventListener('input', specialButton_input);
|
||||
|
||||
function sendPlay(data: { clientToken: string, [k: string]: string }) {
|
||||
if (!currentGame) throw new Error('No game in progress.');
|
||||
let req = new XMLHttpRequest();
|
||||
req.open('POST', `${config.apiBaseUrl}/games/${currentGame.id}/play`);
|
||||
req.addEventListener('load', _ => {
|
||||
if (req.status != 204) {
|
||||
alert(`The server rejected the play. This is probably a bug.\n${req.responseText}`);
|
||||
canPlay = true;
|
||||
board.autoHighlight = true;
|
||||
for (let i = 0; i < handButtons.length; i++) handButtons[i].enabled = passButton.checked || (specialButton.checked ? canPlayCardAsSpecialAttack : canPlayCard)[i];
|
||||
passButton.enabled = true;
|
||||
specialButton.enabled = canPlayCardAsSpecialAttack.includes(true);
|
||||
board.clearHighlight();
|
||||
}
|
||||
});
|
||||
req.addEventListener('error', () => communicationError());
|
||||
req.send(new URLSearchParams(data).toString());
|
||||
}
|
||||
|
||||
board.onsubmit = (x, y) => {
|
||||
if (board.cardPlaying == null || !currentGame?.me)
|
||||
return;
|
||||
|
|
@ -813,29 +844,25 @@ board.onsubmit = (x, y) => {
|
|||
board.cardPlaying = null;
|
||||
testUndoButton.disabled = false;
|
||||
} else if (canPlay) {
|
||||
canPlay = false;
|
||||
timeLabel.faded = true;
|
||||
// Send the play to the server.
|
||||
lockGamePage();
|
||||
let req = new XMLHttpRequest();
|
||||
req.open('POST', `${config.apiBaseUrl}/games/${currentGame.id}/play`);
|
||||
req.addEventListener('load', e => {
|
||||
req.addEventListener('load', _ => {
|
||||
if (req.status != 204) {
|
||||
alert(req.responseText);
|
||||
board.clearHighlight();
|
||||
board.autoHighlight = true;
|
||||
}
|
||||
});
|
||||
req.addEventListener('error', () => communicationError());
|
||||
let data = new URLSearchParams();
|
||||
data.append('clientToken', clientToken);
|
||||
data.append('cardNumber', board.cardPlaying.number.toString());
|
||||
data.append('isSpecialAttack', specialButton.checked.toString());
|
||||
data.append('x', board.highlightX.toString());
|
||||
data.append('y', board.highlightY.toString());
|
||||
data.append('r', board.cardRotation.toString());
|
||||
req.send(data.toString());
|
||||
|
||||
board.autoHighlight = false;
|
||||
sendPlay({
|
||||
clientToken,
|
||||
cardNumber: board.cardPlaying.number.toString(),
|
||||
isSpecialAttack: specialButton.checked.toString(),
|
||||
x: board.highlightX.toString(),
|
||||
y: board.highlightY.toString(),
|
||||
r: board.cardRotation.toString()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -846,6 +873,7 @@ board.oncancel = () => {
|
|||
if (button.checked) {
|
||||
button.checked = false;
|
||||
button.inputElement.focus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -864,11 +892,7 @@ timeLabel.ontimeout = () => {
|
|||
let req = new XMLHttpRequest();
|
||||
req.open('POST', `${config.apiBaseUrl}/games/${currentGame!.id}/redraw`);
|
||||
req.addEventListener('error', () => communicationError());
|
||||
let data = new URLSearchParams();
|
||||
data.append('clientToken', clientToken);
|
||||
data.append('redraw', 'false');
|
||||
data.append('isTimeout', 'true');
|
||||
req.send(data.toString());
|
||||
req.send(new URLSearchParams({ clientToken, redraw: 'false', isTimeout: 'true' }).toString());
|
||||
redrawModal.hidden = true;
|
||||
} else {
|
||||
// When time runs out, automatically discard the largest card in the player's hand.
|
||||
|
|
@ -877,24 +901,10 @@ timeLabel.ontimeout = () => {
|
|||
if (handButtons[i].card.size > button.card.size)
|
||||
button = handButtons[i];
|
||||
}
|
||||
for (const el of handButtons) {
|
||||
el.enabled = false;
|
||||
el.checked = false;
|
||||
}
|
||||
for (const el of handButtons) el.checked = false;
|
||||
button.checked = true;
|
||||
canPlay = false;
|
||||
// Send the play to the server.
|
||||
let req = new XMLHttpRequest();
|
||||
req.open('POST', `${config.apiBaseUrl}/games/${currentGame!.id}/play`);
|
||||
req.addEventListener('error', () => communicationError());
|
||||
let data = new URLSearchParams();
|
||||
data.append('clientToken', clientToken);
|
||||
data.append('cardNumber', button.card.number.toString());
|
||||
data.append('isPass', 'true');
|
||||
data.append('isTimeout', 'true');
|
||||
req.send(data.toString());
|
||||
|
||||
board.autoHighlight = false;
|
||||
lockGamePage();
|
||||
sendPlay({ clientToken, cardNumber: button.card.number.toString(), isPass: 'true', isTimeout: 'true' });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
/// <reference path="../CardDatabase.ts"/>
|
||||
/// <reference path="../StageDatabase.ts"/>
|
||||
|
||||
const stageButtons: StageButton[] = [ ];
|
||||
const shareLinkButton = document.getElementById('shareLinkButton') as HTMLButtonElement;
|
||||
const showQrCodeButton = document.getElementById('showQrCodeButton') as HTMLButtonElement;
|
||||
|
|
@ -27,6 +24,27 @@ let lobbyShareData: ShareData | null;
|
|||
|
||||
let selectedStageButton = null as StageButton | null;
|
||||
|
||||
function lobbyInitStageDatabase(stages: Stage[]) {
|
||||
const stageList = document.getElementById('stageList')!;
|
||||
for (const stage of stages) {
|
||||
const button = new StageButton(stage);
|
||||
stageButtons.push(button);
|
||||
button.inputElement.name = 'stage';
|
||||
button.inputElement.addEventListener('input', () => {
|
||||
if (button.inputElement.checked) {
|
||||
stageRandomLabel.classList.remove('checked');
|
||||
for (const button2 of stageButtons) {
|
||||
if (button2 != button)
|
||||
button2.element.classList.remove('checked');
|
||||
}
|
||||
}
|
||||
});
|
||||
button.setStartSpaces(2);
|
||||
stageList.appendChild(button.element);
|
||||
}
|
||||
document.getElementById('stageListLoadingSection')!.hidden = true;
|
||||
}
|
||||
|
||||
function initLobbyPage(url: string) {
|
||||
stageSelectionFormSubmitButton.disabled = false;
|
||||
stageSelectionFormLoadingSection.hidden = true;
|
||||
|
|
@ -199,28 +217,6 @@ deckSelectionForm.addEventListener('submit', e => {
|
|||
}
|
||||
});
|
||||
|
||||
function initStageDatabase(stages: Stage[]) {
|
||||
const stageList = document.getElementById('stageList')!;
|
||||
for (const stage of stages) {
|
||||
const button = new StageButton(stage);
|
||||
stageButtons.push(button);
|
||||
button.inputElement.name = 'stage';
|
||||
button.inputElement.addEventListener('input', () => {
|
||||
if (button.inputElement.checked) {
|
||||
stageRandomLabel.classList.remove('checked');
|
||||
for (const button2 of stageButtons) {
|
||||
if (button2 != button)
|
||||
button2.element.classList.remove('checked');
|
||||
}
|
||||
}
|
||||
});
|
||||
button.setStartSpaces(2);
|
||||
stageList.appendChild(button.element);
|
||||
addTestStage(stage);
|
||||
}
|
||||
document.getElementById('stageListLoadingSection')!.hidden = true;
|
||||
}
|
||||
|
||||
stageRandomButton.addEventListener('input', () => {
|
||||
if (stageRandomButton.checked) {
|
||||
stageRandomLabel.classList.add('checked');
|
||||
|
|
|
|||
|
|
@ -64,6 +64,20 @@ gameSetupForm.addEventListener('submit', e => {
|
|||
createRoom(true);
|
||||
});
|
||||
|
||||
function uiParseGameID(s: string, fromInitialLoad: boolean) {
|
||||
const gameID = parseGameID(s);
|
||||
if (!gameID) {
|
||||
alert("Invalid game ID or link");
|
||||
if (fromInitialLoad)
|
||||
clearPreGameForm(true);
|
||||
else {
|
||||
gameIDBox.focus();
|
||||
gameIDBox.setSelectionRange(0, gameIDBox.value.length);
|
||||
}
|
||||
}
|
||||
return gameID;
|
||||
}
|
||||
|
||||
function createRoom(useOptionsForm: boolean) {
|
||||
const name = nameBox.value;
|
||||
let request = new XMLHttpRequest();
|
||||
|
|
@ -99,40 +113,21 @@ function createRoom(useOptionsForm: boolean) {
|
|||
}
|
||||
|
||||
function spectate(fromInitialLoad: boolean) {
|
||||
const gameID = parseGameID(gameIDBox.value);
|
||||
if (!gameID) {
|
||||
alert("Invalid game ID or link");
|
||||
if (fromInitialLoad)
|
||||
clearPreGameForm(true);
|
||||
else {
|
||||
gameIDBox.focus();
|
||||
gameIDBox.setSelectionRange(0, gameIDBox.value.length);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const gameID = uiParseGameID(gameIDBox.value, fromInitialLoad);
|
||||
if (!gameID) return;
|
||||
setGameUrl(gameID);
|
||||
getGameInfo(gameID, null);
|
||||
}
|
||||
|
||||
function tryJoinGame(name: string, idOrUrl: string, fromInitialLoad: boolean) {
|
||||
const gameID = parseGameID(idOrUrl);
|
||||
if (!gameID) {
|
||||
alert("Invalid game ID or link");
|
||||
if (fromInitialLoad)
|
||||
clearPreGameForm(true);
|
||||
else {
|
||||
gameIDBox.focus();
|
||||
gameIDBox.setSelectionRange(0, gameIDBox.value.length);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const gameID = uiParseGameID(idOrUrl, fromInitialLoad);
|
||||
if (!gameID) return;
|
||||
|
||||
if (!fromInitialLoad)
|
||||
setGameUrl(gameID);
|
||||
setGameUrl(gameID);
|
||||
|
||||
let request = new XMLHttpRequest();
|
||||
request.open('POST', `${config.apiBaseUrl}/games/${gameID}/join`);
|
||||
request.addEventListener('load', e => {
|
||||
request.addEventListener('load', () => {
|
||||
if (request.status == 200) {
|
||||
let response = JSON.parse(request.responseText);
|
||||
if (!clientToken)
|
||||
|
|
@ -152,10 +147,7 @@ function tryJoinGame(name: string, idOrUrl: string, fromInitialLoad: boolean) {
|
|||
request.addEventListener('error', () => {
|
||||
joinGameError('Unable to join the room.', fromInitialLoad);
|
||||
});
|
||||
let data = new URLSearchParams();
|
||||
data.append('name', name);
|
||||
data.append('clientToken', clientToken);
|
||||
request.send(data.toString());
|
||||
request.send(new URLSearchParams({ name, clientToken }).toString());
|
||||
setLoadingMessage('Joining the game...');
|
||||
}
|
||||
|
||||
|
|
@ -178,7 +170,7 @@ function getGameInfo(gameID: string, myPlayerIndex: number | null) {
|
|||
|
||||
showDeckButtons.splice(0);
|
||||
clearShowDeck();
|
||||
myPlayerIndex = setupWebSocket(gameID, myPlayerIndex);
|
||||
setupWebSocket(gameID);
|
||||
}
|
||||
|
||||
function backPreGameForm(updateUrl: boolean) {
|
||||
|
|
@ -238,108 +230,15 @@ preGameReplayButton.addEventListener('click', e => {
|
|||
loadReplay(m[1]);
|
||||
});
|
||||
|
||||
function loadReplay(base64: string) {
|
||||
if (stageDatabase.stages == null)
|
||||
throw new Error('Game data not loaded');
|
||||
|
||||
base64 = base64.replaceAll('-', '+');
|
||||
base64 = base64.replaceAll('_', '/');
|
||||
const bytes = Base64.base64DecToArr(base64);
|
||||
const dataView = new DataView(bytes.buffer);
|
||||
if (dataView.getUint8(0) != 1) {
|
||||
alert('Unknown replay data version');
|
||||
return;
|
||||
}
|
||||
const n = dataView.getUint8(1);
|
||||
const stage = stageDatabase.stages[n & 0x1F];
|
||||
const numPlayers = n >> 5;
|
||||
|
||||
let pos = 2;
|
||||
const players = [ ];
|
||||
currentReplay = { turns: [ ], placements: [ ], replayPlayerData: [ ], watchingPlayer: 0 };
|
||||
for (let i = 0; i < numPlayers; i++) {
|
||||
const len = dataView.getUint8(pos + 34);
|
||||
const player: Player = {
|
||||
name: new TextDecoder().decode(new DataView(bytes.buffer, pos + 35, len)),
|
||||
specialPoints: 0,
|
||||
isReady: false,
|
||||
colour: { r: dataView.getUint8(pos + 0), g: dataView.getUint8(pos + 1), b: dataView.getUint8(pos + 2) },
|
||||
specialColour: { r: dataView.getUint8(pos + 3), g: dataView.getUint8(pos + 4), b: dataView.getUint8(pos + 5) },
|
||||
specialAccentColour: { r: dataView.getUint8(pos + 6), g: dataView.getUint8(pos + 7), b: dataView.getUint8(pos + 8) },
|
||||
totalSpecialPoints: 0,
|
||||
passes: 0
|
||||
};
|
||||
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);
|
||||
}
|
||||
for (let j = 0; j < 8; j++) {
|
||||
drawOrder.push(dataView.getUint8(pos + 26 + j) & 0xF);
|
||||
if (j == 7)
|
||||
player.uiBaseColourIsSpecialColour = (dataView.getUint8(pos + 26 + j) & 0x80) != 0;
|
||||
else
|
||||
drawOrder.push(dataView.getUint8(pos + 26 + j) >> 4 & 0xF);
|
||||
}
|
||||
currentReplay.replayPlayerData.push({ deck, initialDrawOrder, drawOrder });
|
||||
pos += 35 + len;
|
||||
}
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const turn = [ ]
|
||||
for (let j = 0; j < numPlayers; j++) {
|
||||
const cardNumber = dataView.getUint8(pos);
|
||||
const b = dataView.getUint8(pos + 1);
|
||||
const x = dataView.getInt8(pos + 2);
|
||||
const y = dataView.getInt8(pos + 3);
|
||||
if (b & 0x80)
|
||||
turn.push({ card: cardDatabase.get(cardNumber), isPass: true, isTimeout: (b & 0x20) != 0 });
|
||||
else {
|
||||
const move: PlayMove = { card: cardDatabase.get(cardNumber), isPass: false, isTimeout: (b & 0x20) != 0, x, y, rotation: b & 0x03, isSpecialAttack: (b & 0x40) != 0 };
|
||||
turn.push(move);
|
||||
}
|
||||
pos += 4;
|
||||
}
|
||||
currentReplay.turns.push(turn);
|
||||
}
|
||||
|
||||
currentGame = {
|
||||
id: 'replay',
|
||||
state: GameState.Redraw,
|
||||
me: null,
|
||||
players: players,
|
||||
maxPlayers: numPlayers,
|
||||
turnNumber: 0,
|
||||
turnTimeLimit: null,
|
||||
turnTimeLeft: null,
|
||||
webSocket: null
|
||||
};
|
||||
|
||||
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;
|
||||
board.refresh();
|
||||
|
||||
loadPlayers(players);
|
||||
setUrl(`replay/${encodeToUrlSafeBase64(bytes)}`)
|
||||
initReplay();
|
||||
}
|
||||
|
||||
let playerName = localStorage.getItem('name');
|
||||
(document.getElementById('nameBox') as HTMLInputElement).value = playerName || '';
|
||||
|
||||
let settingUrl = false;
|
||||
window.addEventListener('popstate', e => {
|
||||
window.addEventListener('popstate', () => {
|
||||
if (!settingUrl)
|
||||
processUrl();
|
||||
});
|
||||
|
||||
if (!canPushState)
|
||||
preGameDeckEditorButton.href = '#deckeditor';
|
||||
setLoadingMessage('Loading game data...');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
/// <reference path="Pages/PreGamePage.ts"/>
|
||||
|
||||
declare var baseUrl: string;
|
||||
|
||||
const errorDialog = document.getElementById('errorDialog') as HTMLDialogElement;
|
||||
|
|
@ -41,6 +39,14 @@ function onInitialise(callback: () => void) {
|
|||
initialiseCallback = callback;
|
||||
}
|
||||
|
||||
function initCardDatabase(cards: Card[]) {
|
||||
deckEditInitCardDatabase(cards);
|
||||
}
|
||||
function initStageDatabase(stages: Stage[]) {
|
||||
lobbyInitStageDatabase(stages);
|
||||
deckEditInitStageDatabase(stages);
|
||||
}
|
||||
|
||||
// Pages
|
||||
const pages = new Map<string, HTMLDivElement>();
|
||||
for (var id of [ 'noJS', 'preGame', 'lobby', 'game', 'deckList', 'deckEdit' ]) {
|
||||
|
|
@ -137,7 +143,7 @@ function onGameStateChange(game: any, playerData: PlayerData | null) {
|
|||
board.autoHighlight = false;
|
||||
redrawModal.hidden = true;
|
||||
if (playerData) {
|
||||
updateHand(playerData);
|
||||
updateHandAndDeck(playerData);
|
||||
initGame();
|
||||
} else
|
||||
initSpectator();
|
||||
|
|
@ -161,7 +167,7 @@ function onGameStateChange(game: any, playerData: PlayerData | null) {
|
|||
canPlay = currentGame.me != null && !currentGame.players[currentGame.me.playerIndex].isReady;
|
||||
timeLabel.faded = !canPlay;
|
||||
timeLabel.paused = false;
|
||||
setupControlsForPlay();
|
||||
resetPlayControls();
|
||||
break;
|
||||
case GameState.Ended:
|
||||
clearConfirmLeavingGame();
|
||||
|
|
@ -201,7 +207,7 @@ function playerDataReviver(key: string, value: any) {
|
|||
: value;
|
||||
}
|
||||
|
||||
function setupWebSocket(gameID: string, myPlayerIndex: number | null) {
|
||||
function setupWebSocket(gameID: string) {
|
||||
const webSocket = new WebSocket(`${config.apiBaseUrl.replace(/(http)(s)?\:\/\//, 'ws$2://')}/websocket?gameID=${gameID}&clientToken=${clientToken}`);
|
||||
webSocket.addEventListener('open', _ => {
|
||||
enterGameTimeout = setTimeout(() => {
|
||||
|
|
@ -355,13 +361,13 @@ function setupWebSocket(gameID: string, myPlayerIndex: number | null) {
|
|||
button.inputElement.hidden = true;
|
||||
playContainers[i].append(button.element);
|
||||
}
|
||||
timeLabel.paused = true;
|
||||
if (payload.data.game.turnTimeLeft)
|
||||
timeLabel.setTime(payload.data.game.turnTimeLeft);
|
||||
|
||||
(async () => {
|
||||
timeLabel.paused = true;
|
||||
if (payload.data.game.turnTimeLeft)
|
||||
timeLabel.setTime(payload.data.game.turnTimeLeft);
|
||||
await playInkAnimations(payload.data, anySpecialAttacks);
|
||||
if (payload.playerData) updateHand(payload.playerData);
|
||||
if (payload.playerData) updateHandAndDeck(payload.playerData);
|
||||
turnNumberLabel.setTurnNumber(payload.data.game.turnNumber);
|
||||
clearPlayContainers();
|
||||
if (payload.event == 'gameEnd') {
|
||||
|
|
@ -374,9 +380,9 @@ function setupWebSocket(gameID: string, myPlayerIndex: number | null) {
|
|||
timeLabel.hide();
|
||||
showResult();
|
||||
} else {
|
||||
canPlay = myPlayerIndex != null;
|
||||
canPlay = currentGame.me != null;
|
||||
board.autoHighlight = canPlay;
|
||||
setupControlsForPlay();
|
||||
resetPlayControls();
|
||||
timeLabel.faded = !canPlay;
|
||||
timeLabel.paused = false;
|
||||
if (payload.data.game.turnTimeLeft)
|
||||
|
|
@ -389,12 +395,106 @@ function setupWebSocket(gameID: string, myPlayerIndex: number | null) {
|
|||
}
|
||||
});
|
||||
webSocket.addEventListener('close', webSocket_close);
|
||||
return myPlayerIndex;
|
||||
}
|
||||
function webSocket_close() {
|
||||
communicationError();
|
||||
}
|
||||
|
||||
function loadReplay(base64: string) {
|
||||
if (stageDatabase.stages == null)
|
||||
throw new Error('Game data not loaded');
|
||||
|
||||
base64 = base64.replaceAll('-', '+');
|
||||
base64 = base64.replaceAll('_', '/');
|
||||
const bytes = Base64.base64DecToArr(base64);
|
||||
const dataView = new DataView(bytes.buffer);
|
||||
if (dataView.getUint8(0) != 1) {
|
||||
alert('Unknown replay data version');
|
||||
return;
|
||||
}
|
||||
const n = dataView.getUint8(1);
|
||||
const stage = stageDatabase.stages[n & 0x1F];
|
||||
const numPlayers = n >> 5;
|
||||
|
||||
let pos = 2;
|
||||
const players = [ ];
|
||||
currentReplay = { turns: [ ], placements: [ ], replayPlayerData: [ ], watchingPlayer: 0 };
|
||||
for (let i = 0; i < numPlayers; i++) {
|
||||
const len = dataView.getUint8(pos + 34);
|
||||
const player: Player = {
|
||||
name: new TextDecoder().decode(new DataView(bytes.buffer, pos + 35, len)),
|
||||
specialPoints: 0,
|
||||
isReady: false,
|
||||
colour: { r: dataView.getUint8(pos + 0), g: dataView.getUint8(pos + 1), b: dataView.getUint8(pos + 2) },
|
||||
specialColour: { r: dataView.getUint8(pos + 3), g: dataView.getUint8(pos + 4), b: dataView.getUint8(pos + 5) },
|
||||
specialAccentColour: { r: dataView.getUint8(pos + 6), g: dataView.getUint8(pos + 7), b: dataView.getUint8(pos + 8) },
|
||||
totalSpecialPoints: 0,
|
||||
passes: 0
|
||||
};
|
||||
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);
|
||||
}
|
||||
for (let j = 0; j < 8; j++) {
|
||||
drawOrder.push(dataView.getUint8(pos + 26 + j) & 0xF);
|
||||
if (j == 7)
|
||||
player.uiBaseColourIsSpecialColour = (dataView.getUint8(pos + 26 + j) & 0x80) != 0;
|
||||
else
|
||||
drawOrder.push(dataView.getUint8(pos + 26 + j) >> 4 & 0xF);
|
||||
}
|
||||
currentReplay.replayPlayerData.push({ deck, initialDrawOrder, drawOrder });
|
||||
pos += 35 + len;
|
||||
}
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const turn = [ ];
|
||||
for (let j = 0; j < numPlayers; j++) {
|
||||
const cardNumber = dataView.getUint8(pos);
|
||||
const b = dataView.getUint8(pos + 1);
|
||||
const x = dataView.getInt8(pos + 2);
|
||||
const y = dataView.getInt8(pos + 3);
|
||||
if (b & 0x80)
|
||||
turn.push({ card: cardDatabase.get(cardNumber), isPass: true, isTimeout: (b & 0x20) != 0 });
|
||||
else {
|
||||
const move: PlayMove = { card: cardDatabase.get(cardNumber), isPass: false, isTimeout: (b & 0x20) != 0, x, y, rotation: b & 0x03, isSpecialAttack: (b & 0x40) != 0 };
|
||||
turn.push(move);
|
||||
}
|
||||
pos += 4;
|
||||
}
|
||||
currentReplay.turns.push(turn);
|
||||
}
|
||||
|
||||
currentGame = {
|
||||
id: 'replay',
|
||||
state: GameState.Redraw,
|
||||
me: null,
|
||||
players: players,
|
||||
maxPlayers: numPlayers,
|
||||
turnNumber: 0,
|
||||
turnTimeLimit: null,
|
||||
turnTimeLeft: null,
|
||||
webSocket: null
|
||||
};
|
||||
|
||||
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;
|
||||
board.refresh();
|
||||
|
||||
loadPlayers(players);
|
||||
setUrl(`replay/${encodeToUrlSafeBase64(bytes)}`)
|
||||
initReplay();
|
||||
}
|
||||
|
||||
function setConfirmLeavingGame() {
|
||||
if (!shouldConfirmLeavingGame) {
|
||||
shouldConfirmLeavingGame = true;
|
||||
|
|
@ -549,7 +649,5 @@ Promise.all([ cardDatabase.loadAsync().then(initCardDatabase), stageDatabase.loa
|
|||
communicationError('Unable to load game data from the server.', false);
|
||||
});
|
||||
|
||||
if (!canPushState)
|
||||
preGameDeckEditorButton.href = '#deckeditor';
|
||||
showPage('preGame');
|
||||
processUrl();
|
||||
|
|
|
|||
|
|
@ -574,7 +574,7 @@ dialog::backdrop {
|
|||
:is(#playRow, #testControlsHeader) > label:hover { background: var(--player-ui-highlight-colour); }
|
||||
:is(#playRow, #testControlsHeader) > label:focus-within { outline: 2px solid var(--player-ui-highlight2-colour); }
|
||||
:is(#playRow, #testControlsHeader) > label:is(:active, .checked) { background: var(--player-ui-highlight2-colour); }
|
||||
:is(#playRow, #testControlsHeader) > label.disabled { background: grey; }
|
||||
:is(#playRow, #testControlsHeader) > label.disabled:not(.checked) { background: grey; }
|
||||
|
||||
#spectatorRow:not([hidden]) {
|
||||
display: grid;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user