mirror of
https://github.com/AndrioCelos/TableturfBattleApp.git
synced 2026-07-05 10:21:00 -05:00
Rework sync procedure and add a loading indicator
The server will now send a sync message when the client connects via WebSocket. This removes the need to send another GET request to sync game data.
This commit is contained in:
parent
3ad1877a4c
commit
d4e8d86571
|
|
@ -29,7 +29,7 @@
|
|||
<form id="preGameForm">
|
||||
<p><label for="nameBox">Choose a nickname: <input type="text" id="nameBox" required minlength="1" maxlength="20"/></label></p>
|
||||
<div id="preGameDefaultSection">
|
||||
<input type="submit" id="preGameImplicitSubmitButton" tabindex="-1"/>
|
||||
<button type="submit" id="preGameImplicitSubmitButton" tabindex="-1"></button>
|
||||
<p>
|
||||
<button type="submit" id="newGameButton">Create a room</button>
|
||||
<label for="maxPlayersBox">
|
||||
|
|
@ -48,6 +48,9 @@
|
|||
<p><button type="submit" id="joinGameButton2">Join game</button></p>
|
||||
<a id="preGameBackButton" href="../..">Create or join a different room</a>
|
||||
</div>
|
||||
<div id="preGameLoadingSection" class="loadingContainer" hidden>
|
||||
<div class="lds-ripple"><div></div><div></div></div> <span id="preGameLoadingLabel"></span>
|
||||
</div>
|
||||
<p>
|
||||
<a id="preGameDeckEditorButton" href="deckeditor">Edit decks</a>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -1,31 +1,36 @@
|
|||
const cardDatabase = {
|
||||
cards: null as Card[] | null,
|
||||
loadAsync() {
|
||||
return new Promise<Card[]>((resolve, reject) => {
|
||||
if (cardDatabase.cards != null) {
|
||||
resolve(cardDatabase.cards);
|
||||
return;
|
||||
}
|
||||
const cardListRequest = new XMLHttpRequest();
|
||||
cardListRequest.open('GET', `${config.apiBaseUrl}/cards`);
|
||||
cardListRequest.addEventListener('load', e => {
|
||||
const cards = [ ];
|
||||
if (cardListRequest.status == 200) {
|
||||
const s = cardListRequest.responseText;
|
||||
const response = JSON.parse(s) as object[];
|
||||
for (const o of response) {
|
||||
cards.push(Card.fromJson(o));
|
||||
}
|
||||
cardDatabase.cards = cards;
|
||||
resolve(cards);
|
||||
} else {
|
||||
reject(new Error(`Error downloading card database: response was ${cardListRequest.status}`));
|
||||
}
|
||||
});
|
||||
cardListRequest.addEventListener('error', e => {
|
||||
reject(new Error('Error downloading card database: no further information.'))
|
||||
});
|
||||
cardListRequest.send();
|
||||
});
|
||||
}
|
||||
}
|
||||
const cardDatabase = {
|
||||
cards: null as Card[] | null,
|
||||
get(number: number) {
|
||||
if (cardDatabase.cards == null) throw new Error('Card database not loaded');
|
||||
if (number <= 0 || number > cardDatabase.cards.length) throw new RangeError(`No card with number ${number}`);
|
||||
return cardDatabase.cards[number - 1];
|
||||
},
|
||||
loadAsync() {
|
||||
return new Promise<Card[]>((resolve, reject) => {
|
||||
if (cardDatabase.cards != null) {
|
||||
resolve(cardDatabase.cards);
|
||||
return;
|
||||
}
|
||||
const cardListRequest = new XMLHttpRequest();
|
||||
cardListRequest.open('GET', `${config.apiBaseUrl}/cards`);
|
||||
cardListRequest.addEventListener('load', e => {
|
||||
const cards = [ ];
|
||||
if (cardListRequest.status == 200) {
|
||||
const s = cardListRequest.responseText;
|
||||
const response = JSON.parse(s) as object[];
|
||||
for (const o of response) {
|
||||
cards.push(Card.fromJson(o));
|
||||
}
|
||||
cardDatabase.cards = cards;
|
||||
resolve(cards);
|
||||
} else {
|
||||
reject(new Error(`Error downloading card database: response was ${cardListRequest.status}`));
|
||||
}
|
||||
});
|
||||
cardListRequest.addEventListener('error', e => {
|
||||
reject(new Error('Error downloading card database: no further information.'))
|
||||
});
|
||||
cardListRequest.send();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ let currentGame: {
|
|||
webSocket: WebSocket
|
||||
} | null;
|
||||
|
||||
let enterGameTimeout: number | null = null;
|
||||
|
||||
const playerList = document.getElementById('playerList')!;
|
||||
const playerListItems: HTMLElement[] = [ ];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,23 @@
|
|||
const preGameForm = document.getElementById('preGameForm') as HTMLFormElement;
|
||||
const newGameButton = document.getElementById('newGameButton')!;
|
||||
const joinGameButton = document.getElementById('joinGameButton')!;
|
||||
const nameBox = document.getElementById('nameBox') as HTMLInputElement;
|
||||
const gameIDBox = document.getElementById('gameIDBox') as HTMLInputElement;
|
||||
const maxPlayersBox = document.getElementById('maxPlayersBox') as HTMLSelectElement;
|
||||
const preGameDeckEditorButton = document.getElementById('preGameDeckEditorButton') as HTMLLinkElement;
|
||||
const preGameLoadingSection = document.getElementById('preGameLoadingSection')!;
|
||||
const preGameLoadingLabel = document.getElementById('preGameLoadingLabel')!;
|
||||
|
||||
let shownMaxPlayersWarning = false;
|
||||
|
||||
function setLoadingMessage(message: string | null) {
|
||||
if (message)
|
||||
preGameLoadingLabel.innerText = message;
|
||||
preGameLoadingSection.hidden = message == null;
|
||||
for (const button of preGameForm.getElementsByTagName('button'))
|
||||
button.disabled = message != null;
|
||||
}
|
||||
|
||||
maxPlayersBox.addEventListener('change', () => {
|
||||
if (!shownMaxPlayersWarning && maxPlayersBox.value != '2') {
|
||||
if (confirm('Tableturf Battle is designed for two players and may not be well-balanced for more. Do you want to continue?'))
|
||||
|
|
@ -16,7 +27,7 @@ maxPlayersBox.addEventListener('change', () => {
|
|||
}
|
||||
});
|
||||
|
||||
(document.getElementById('preGameForm') as HTMLFormElement).addEventListener('submit', e => {
|
||||
preGameForm.addEventListener('submit', e => {
|
||||
e.preventDefault();
|
||||
if (e.submitter?.id == 'newGameButton' || (e.submitter?.id == 'preGameImplicitSubmitButton' && !gameIDBox.value)) {
|
||||
const name = nameBox.value;
|
||||
|
|
@ -41,6 +52,7 @@ maxPlayersBox.addEventListener('change', () => {
|
|||
data.append('clientToken', clientToken);
|
||||
data.append('maxPlayers', maxPlayersBox.value);
|
||||
request.send(data.toString());
|
||||
setLoadingMessage('Creating a room...');
|
||||
} else {
|
||||
const name = nameBox.value;
|
||||
window.localStorage.setItem('name', name);
|
||||
|
|
@ -48,24 +60,6 @@ maxPlayersBox.addEventListener('change', () => {
|
|||
}
|
||||
});
|
||||
|
||||
function setUrl(path: string) {
|
||||
settingUrl = true;
|
||||
try {
|
||||
if (canPushState) {
|
||||
try {
|
||||
history.pushState(null, '', path);
|
||||
} catch {
|
||||
canPushState = false;
|
||||
location.hash = `#${path}`;
|
||||
}
|
||||
} else
|
||||
location.hash = `#${path}`;
|
||||
} finally {
|
||||
settingUrl = false;
|
||||
}
|
||||
}
|
||||
function setGameUrl(gameID: string) { setUrl(`game/${gameID}`); }
|
||||
|
||||
function tryJoinGame(name: string, idOrUrl: string, fromInitialLoad: boolean) {
|
||||
const m = /(?:^|[#/])([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i.exec(idOrUrl);
|
||||
if (!m) {
|
||||
|
|
@ -98,6 +92,8 @@ function tryJoinGame(name: string, idOrUrl: string, fromInitialLoad: boolean) {
|
|||
alert('The game has already started.');
|
||||
else
|
||||
alert('Unable to join the room.');
|
||||
clearUrlFromGame();
|
||||
setLoadingMessage(null);
|
||||
if (fromInitialLoad)
|
||||
clearPreGameForm(true);
|
||||
else {
|
||||
|
|
@ -106,10 +102,14 @@ function tryJoinGame(name: string, idOrUrl: string, fromInitialLoad: boolean) {
|
|||
}
|
||||
}
|
||||
});
|
||||
request.addEventListener('error', () => {
|
||||
communicationError();
|
||||
});
|
||||
let data = new URLSearchParams();
|
||||
data.append('name', name);
|
||||
data.append('clientToken', clientToken);
|
||||
request.send(data.toString());
|
||||
setLoadingMessage('Joining the game...');
|
||||
}
|
||||
|
||||
function getGameInfo(gameID: string, myPlayerIndex: number | null) {
|
||||
|
|
@ -124,15 +124,7 @@ function backPreGameForm(updateUrl: boolean) {
|
|||
document.getElementById('preGameJoinSection')!.hidden = true;
|
||||
|
||||
if (updateUrl) {
|
||||
if (canPushState) {
|
||||
try {
|
||||
history.pushState(null, '', '../..');
|
||||
} catch {
|
||||
canPushState = false;
|
||||
}
|
||||
}
|
||||
if (location.hash)
|
||||
location.hash = '';
|
||||
clearUrlFromGame();
|
||||
}
|
||||
}
|
||||
function clearPreGameForm(updateUrl: boolean) {
|
||||
|
|
|
|||
|
|
@ -1,31 +1,31 @@
|
|||
const stageDatabase = {
|
||||
stages: null as Stage[] | null,
|
||||
loadAsync() {
|
||||
return new Promise<Stage[]>((resolve, reject) => {
|
||||
if (stageDatabase.stages != null) {
|
||||
resolve(stageDatabase.stages);
|
||||
return;
|
||||
}
|
||||
const stageListRequest = new XMLHttpRequest();
|
||||
stageListRequest.open('GET', `${config.apiBaseUrl}/stages`);
|
||||
stageListRequest.addEventListener('load', e => {
|
||||
const stages = [ ];
|
||||
if (stageListRequest.status == 200) {
|
||||
const s = stageListRequest.responseText;
|
||||
const response = JSON.parse(s) as object[];
|
||||
for (const o of response) {
|
||||
stages.push(Stage.fromJson(o));
|
||||
}
|
||||
stageDatabase.stages = stages;
|
||||
resolve(stages);
|
||||
} else {
|
||||
reject(new Error(`Error downloading stage database: response was ${stageListRequest.status}`));
|
||||
}
|
||||
});
|
||||
stageListRequest.addEventListener('error', e => {
|
||||
reject(new Error('Error downloading stage database: no further information.'))
|
||||
});
|
||||
stageListRequest.send();
|
||||
});
|
||||
}
|
||||
}
|
||||
const stageDatabase = {
|
||||
stages: null as Stage[] | null,
|
||||
loadAsync() {
|
||||
return new Promise<Stage[]>((resolve, reject) => {
|
||||
if (stageDatabase.stages != null) {
|
||||
resolve(stageDatabase.stages);
|
||||
return;
|
||||
}
|
||||
const stageListRequest = new XMLHttpRequest();
|
||||
stageListRequest.open('GET', `${config.apiBaseUrl}/stages`);
|
||||
stageListRequest.addEventListener('load', e => {
|
||||
const stages = [ ];
|
||||
if (stageListRequest.status == 200) {
|
||||
const s = stageListRequest.responseText;
|
||||
const response = JSON.parse(s) as object[];
|
||||
for (const o of response) {
|
||||
stages.push(Stage.fromJson(o));
|
||||
}
|
||||
stageDatabase.stages = stages;
|
||||
resolve(stages);
|
||||
} else {
|
||||
reject(new Error(`Error downloading stage database: response was ${stageListRequest.status}`));
|
||||
}
|
||||
});
|
||||
stageListRequest.addEventListener('error', e => {
|
||||
reject(new Error('Error downloading stage database: no further information.'))
|
||||
});
|
||||
stageListRequest.send();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,35 @@ function setClientToken(token: string) {
|
|||
clientToken = token;
|
||||
}
|
||||
|
||||
function setUrl(path: string) {
|
||||
settingUrl = true;
|
||||
try {
|
||||
if (canPushState) {
|
||||
try {
|
||||
history.pushState(null, '', path);
|
||||
} catch {
|
||||
canPushState = false;
|
||||
location.hash = `#${path}`;
|
||||
}
|
||||
} else
|
||||
location.hash = `#${path}`;
|
||||
} finally {
|
||||
settingUrl = false;
|
||||
}
|
||||
}
|
||||
function setGameUrl(gameID: string) { setUrl(`game/${gameID}`); }
|
||||
function clearUrlFromGame() {
|
||||
if (canPushState) {
|
||||
try {
|
||||
history.pushState(null, '', '../..');
|
||||
} catch {
|
||||
canPushState = false;
|
||||
}
|
||||
}
|
||||
if (location.hash)
|
||||
location.hash = '';
|
||||
}
|
||||
|
||||
function onGameStateChange(game: any, playerData: any) {
|
||||
if (currentGame == null)
|
||||
throw new Error('currentGame is null');
|
||||
|
|
@ -97,142 +126,166 @@ function communicationError() {
|
|||
document.getElementById('errorModal')!.hidden = false;
|
||||
}
|
||||
|
||||
function playerDataReviver(key: string, value: any) {
|
||||
return !value ? value
|
||||
: key == 'hand' || key == 'deck'
|
||||
? (value as (Card | number)[]).map(v => typeof v == 'number' ? cardDatabase.get(v) : Card.fromJson(v))
|
||||
: key == 'card'
|
||||
? typeof value == 'number' ? cardDatabase.get(value) : Card.fromJson(value)
|
||||
: value;
|
||||
}
|
||||
|
||||
function setupWebSocket(gameID: string, myPlayerIndex: number | null) {
|
||||
const webSocket = new WebSocket(`${config.apiBaseUrl.replace(/(http)(s)?\:\/\//, 'ws$2://')}/websocket?gameID=${gameID}&clientToken=${clientToken}`);
|
||||
webSocket.addEventListener('open', e => {
|
||||
const request2 = new XMLHttpRequest();
|
||||
request2.open('GET', `${config.apiBaseUrl}/games/${gameID}/playerData?clientToken=${clientToken}`);
|
||||
request2.addEventListener('load', e => {
|
||||
if (request2.status == 200) {
|
||||
const response = JSON.parse(request2.responseText);
|
||||
const playerData = response.playerData as PlayerData | null;
|
||||
|
||||
currentGame = {
|
||||
id: gameID,
|
||||
me: playerData != null ? { playerIndex: playerData.playerIndex, hand: playerData.hand?.map(Card.fromJson) || null, deck: playerData.deck?.map(Card.fromJson) || null, cardsUsed: playerData.cardsUsed, move: playerData.move } : null,
|
||||
players: response.game.players,
|
||||
maxPlayers: response.game.maxPlayers,
|
||||
webSocket: webSocket
|
||||
};
|
||||
|
||||
for (const li of playerListItems)
|
||||
playerList.removeChild(li);
|
||||
playerListItems.splice(0);
|
||||
for (let i = 0; i < currentGame.maxPlayers; i++) {
|
||||
var el = document.createElement('li');
|
||||
playerListItems.push(el);
|
||||
playerList.appendChild(el);
|
||||
updatePlayerListItem(i);
|
||||
}
|
||||
|
||||
for (let i = 0; i < playerBars.length; i++) {
|
||||
playerBars[i].visible = i < currentGame.maxPlayers;
|
||||
}
|
||||
|
||||
for (const button of stageButtons)
|
||||
button.setStartSpaces(currentGame.maxPlayers);
|
||||
|
||||
onGameStateChange(response.game, playerData);
|
||||
|
||||
for (let i = 0; i < response.game.players.length; i++) {
|
||||
if (response.game.players[i].isReady)
|
||||
showReady(i);
|
||||
}
|
||||
|
||||
if (playerData) {
|
||||
myPlayerIndex = playerData.playerIndex;
|
||||
if (playerData.move) {
|
||||
canPlay = false;
|
||||
board.autoHighlight = false;
|
||||
if (!playerData.move.isPass) {
|
||||
const move = playerData.move as PlayMove;
|
||||
board.cardPlaying = Card.fromJson(move.card);
|
||||
board.highlightX = move.x;
|
||||
board.highlightY = move.y;
|
||||
board.cardRotation = move.rotation;
|
||||
board.specialAttack = move.isSpecialAttack;
|
||||
board.refreshHighlight();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
request2.send();
|
||||
enterGameTimeout = setTimeout(() => {
|
||||
webSocket.close(1002, 'Timeout waiting for a sync message');
|
||||
enterGameTimeout = null;
|
||||
communicationError();
|
||||
}, 30000);
|
||||
});
|
||||
webSocket.addEventListener('message', e => {
|
||||
if (currentGame == null)
|
||||
return;
|
||||
let s = e.data as string;
|
||||
console.log(`>> ${s}`);
|
||||
if (s) {
|
||||
let payload = JSON.parse(s);
|
||||
if (payload.event == 'join') {
|
||||
if (payload.data.playerIndex == currentGame.players.length) {
|
||||
currentGame.players.push(payload.data.player);
|
||||
playerListItems[payload.data.playerIndex].innerText = payload.data.player.name;
|
||||
updatePlayerListItem(payload.data.playerIndex);
|
||||
let payload = JSON.parse(s, playerDataReviver);
|
||||
if (payload.event == 'sync') {
|
||||
if (enterGameTimeout != null) {
|
||||
clearTimeout(enterGameTimeout);
|
||||
enterGameTimeout = null;
|
||||
}
|
||||
else
|
||||
communicationError();
|
||||
} else if (payload.event == 'playerReady') {
|
||||
currentGame.players[payload.data.playerIndex].isReady = true;
|
||||
updatePlayerListItem(payload.data.playerIndex);
|
||||
setLoadingMessage(null);
|
||||
if (!e.data) {
|
||||
webSocket.close();
|
||||
alert('The game was not found.');
|
||||
} else {
|
||||
currentGame = {
|
||||
id: gameID,
|
||||
me: payload.playerData,
|
||||
players: payload.data.players,
|
||||
maxPlayers: payload.data.maxPlayers,
|
||||
webSocket: webSocket
|
||||
};
|
||||
|
||||
if (payload.data.playerIndex == currentGame.me?.playerIndex) {
|
||||
lobbyStageSection.hidden = true;
|
||||
lobbyDeckSection.hidden = true;
|
||||
}
|
||||
for (const li of playerListItems)
|
||||
playerList.removeChild(li);
|
||||
playerListItems.splice(0);
|
||||
for (let i = 0; i < currentGame.maxPlayers; i++) {
|
||||
var el = document.createElement('li');
|
||||
playerListItems.push(el);
|
||||
playerList.appendChild(el);
|
||||
updatePlayerListItem(i);
|
||||
}
|
||||
|
||||
if (playContainers[payload.data.playerIndex].getElementsByTagName('div').length == 0) {
|
||||
showReady(payload.data.playerIndex);
|
||||
}
|
||||
} else if (payload.event == 'stateChange') {
|
||||
clearReady();
|
||||
onGameStateChange(payload.data, payload.playerData);
|
||||
} else if (payload.event == 'turn' || payload.event == 'gameEnd') {
|
||||
clearReady();
|
||||
board.autoHighlight = false;
|
||||
showPage('game');
|
||||
for (let i = 0; i < playerBars.length; i++) {
|
||||
playerBars[i].visible = i < currentGame.maxPlayers;
|
||||
}
|
||||
|
||||
for (const button of stageButtons)
|
||||
button.setStartSpaces(currentGame.maxPlayers);
|
||||
|
||||
onGameStateChange(payload.data, payload.playerData);
|
||||
|
||||
(async () => {
|
||||
let anySpecialAttacks = false;
|
||||
// Show the cards that were played.
|
||||
clearPlayContainers();
|
||||
for (let i = 0; i < currentGame.players.length; i++) {
|
||||
const player = currentGame.players[i];
|
||||
player.specialPoints = payload.data.game.players[i].specialPoints;
|
||||
player.totalSpecialPoints = payload.data.game.players[i].totalSpecialPoints;
|
||||
player.passes = payload.data.game.players[i].passes;
|
||||
if (currentGame.players[i].isReady)
|
||||
showReady(i);
|
||||
}
|
||||
|
||||
const move = payload.data.moves[i];
|
||||
const button = new CardButton('checkbox', move.card);
|
||||
if (move.isSpecialAttack) {
|
||||
anySpecialAttacks = true;
|
||||
button.element.classList.add('specialAttack');
|
||||
} else if (move.isPass) {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'passLabel';
|
||||
el.innerText = 'Pass';
|
||||
button.element.appendChild(el);
|
||||
if (currentGame.me) {
|
||||
if (currentGame.me.move) {
|
||||
canPlay = false;
|
||||
board.autoHighlight = false;
|
||||
if (!currentGame.me.move.isPass) {
|
||||
const move = currentGame.me.move as PlayMove;
|
||||
board.cardPlaying = move.card;
|
||||
board.highlightX = move.x;
|
||||
board.highlightY = move.y;
|
||||
board.cardRotation = move.rotation;
|
||||
board.specialAttack = move.isSpecialAttack;
|
||||
board.refreshHighlight();
|
||||
}
|
||||
}
|
||||
button.inputElement.hidden = true;
|
||||
playContainers[i].append(button.element);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (currentGame == null) {
|
||||
communicationError();
|
||||
return;
|
||||
}
|
||||
switch (payload.event) {
|
||||
case 'join':
|
||||
if (payload.data.playerIndex == currentGame.players.length) {
|
||||
currentGame.players.push(payload.data.player);
|
||||
playerListItems[payload.data.playerIndex].innerText = payload.data.player.name;
|
||||
updatePlayerListItem(payload.data.playerIndex);
|
||||
}
|
||||
else
|
||||
communicationError();
|
||||
break;
|
||||
case 'playerReady':
|
||||
currentGame.players[payload.data.playerIndex].isReady = true;
|
||||
updatePlayerListItem(payload.data.playerIndex);
|
||||
|
||||
if (payload.data.playerIndex == currentGame.me?.playerIndex) {
|
||||
lobbyStageSection.hidden = true;
|
||||
lobbyDeckSection.hidden = true;
|
||||
}
|
||||
|
||||
if (playContainers[payload.data.playerIndex].getElementsByTagName('div').length == 0) {
|
||||
showReady(payload.data.playerIndex);
|
||||
}
|
||||
break;
|
||||
case 'stateChange':
|
||||
clearReady();
|
||||
onGameStateChange(payload.data, payload.playerData);
|
||||
break;
|
||||
case 'turn':
|
||||
case 'gameEnd':
|
||||
clearReady();
|
||||
board.autoHighlight = false;
|
||||
showPage('game');
|
||||
|
||||
let anySpecialAttacks = false;
|
||||
// Show the cards that were played.
|
||||
clearPlayContainers();
|
||||
for (let i = 0; i < currentGame.players.length; i++) {
|
||||
const player = currentGame.players[i];
|
||||
player.specialPoints = payload.data.game.players[i].specialPoints;
|
||||
player.totalSpecialPoints = payload.data.game.players[i].totalSpecialPoints;
|
||||
player.passes = payload.data.game.players[i].passes;
|
||||
|
||||
await playInkAnimations(payload.data, anySpecialAttacks);
|
||||
updateHand(payload.playerData.hand);
|
||||
turnNumberLabel.setTurnNumber(payload.data.game.turnNumber);
|
||||
clearPlayContainers();
|
||||
if (payload.event == 'gameEnd') {
|
||||
gameButtonsContainer.hidden = true;
|
||||
gamePage.classList.add('gameEnded');
|
||||
showResult();
|
||||
} else {
|
||||
canPlay = myPlayerIndex != null;
|
||||
board.autoHighlight = canPlay;
|
||||
setupControlsForPlay();
|
||||
}
|
||||
})();
|
||||
const move = payload.data.moves[i];
|
||||
const button = new CardButton('checkbox', move.card);
|
||||
if (move.isSpecialAttack) {
|
||||
anySpecialAttacks = true;
|
||||
button.element.classList.add('specialAttack');
|
||||
} else if (move.isPass) {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'passLabel';
|
||||
el.innerText = 'Pass';
|
||||
button.element.appendChild(el);
|
||||
}
|
||||
button.inputElement.hidden = true;
|
||||
playContainers[i].append(button.element);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await playInkAnimations(payload.data, anySpecialAttacks);
|
||||
updateHand(payload.playerData.hand);
|
||||
turnNumberLabel.setTurnNumber(payload.data.game.turnNumber);
|
||||
clearPlayContainers();
|
||||
if (payload.event == 'gameEnd') {
|
||||
gameButtonsContainer.hidden = true;
|
||||
gamePage.classList.add('gameEnded');
|
||||
showResult();
|
||||
} else {
|
||||
canPlay = myPlayerIndex != null;
|
||||
board.autoHighlight = canPlay;
|
||||
setupControlsForPlay();
|
||||
}
|
||||
})();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -253,6 +306,7 @@ function processUrl() {
|
|||
if (location.pathname.endsWith('/deckeditor') || location.hash == '#deckeditor')
|
||||
showDeckList();
|
||||
else {
|
||||
showPage('preGame');
|
||||
const m = /^(.*)\/game\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/.exec(location.toString());
|
||||
if (m)
|
||||
presetGameID(m[2]);
|
||||
|
|
@ -261,7 +315,6 @@ function processUrl() {
|
|||
presetGameID(location.hash);
|
||||
} else {
|
||||
clearPreGameForm(false);
|
||||
showPage('preGame');
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1099,3 +1099,59 @@ dialog::backdrop {
|
|||
height: 13em;
|
||||
}
|
||||
}
|
||||
|
||||
/* Loading spinner - based on loading.io */
|
||||
|
||||
.loadingContainer:not([hidden]) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
}
|
||||
.lds-ripple {
|
||||
--border-width: 0.15em;
|
||||
position: relative;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
display: inline-block;
|
||||
}
|
||||
.lds-ripple div {
|
||||
position: absolute;
|
||||
border: var(--border-width) solid #fff;
|
||||
opacity: 1;
|
||||
border-radius: 50%;
|
||||
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
|
||||
}
|
||||
.lds-ripple div:nth-child(2) {
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
@keyframes lds-ripple {
|
||||
0% {
|
||||
top: calc(50% - var(--border-width));
|
||||
left: calc(50% - var(--border-width));
|
||||
right: calc(50% - var(--border-width));
|
||||
bottom: calc(50% - var(--border-width));
|
||||
opacity: 0;
|
||||
}
|
||||
4.9% {
|
||||
top: calc(50% - var(--border-width));
|
||||
left: calc(50% - var(--border-width));
|
||||
right: calc(50% - var(--border-width));
|
||||
bottom: calc(50% - var(--border-width));
|
||||
opacity: 0;
|
||||
}
|
||||
5% {
|
||||
top: calc(50% - var(--border-width));
|
||||
left: calc(50% - var(--border-width));
|
||||
right: calc(50% - var(--border-width));
|
||||
bottom: calc(50% - var(--border-width));
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
44
TableturfBattleServer/DataStructures.cs
Normal file
44
TableturfBattleServer/DataStructures.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace TableturfBattleServer.DTO;
|
||||
|
||||
internal class WebSocketPayload<T> {
|
||||
[JsonProperty("event")]
|
||||
public string EventName;
|
||||
[JsonProperty("data")]
|
||||
public T Payload;
|
||||
|
||||
public WebSocketPayload(string eventName, T payload) {
|
||||
this.EventName = eventName ?? throw new ArgumentNullException(nameof(eventName));
|
||||
this.Payload = payload;
|
||||
}
|
||||
}
|
||||
internal class WebSocketPayloadWithPlayerData<T> : WebSocketPayload<T> {
|
||||
[JsonProperty("playerData")]
|
||||
public PlayerData? PlayerData;
|
||||
|
||||
public WebSocketPayloadWithPlayerData(string eventName, T payload, PlayerData? playerData) : base(eventName, payload)
|
||||
=> this.PlayerData = playerData;
|
||||
}
|
||||
|
||||
public class PlayerData {
|
||||
[JsonProperty("playerIndex")]
|
||||
public int PlayerIndex;
|
||||
[JsonProperty("hand")]
|
||||
public Card[]? Hand;
|
||||
[JsonProperty("deck")]
|
||||
public Card[]? Deck;
|
||||
[JsonProperty("move")]
|
||||
public Move? Move;
|
||||
[JsonProperty("cardsUsed")]
|
||||
public List<int>? CardsUsed;
|
||||
|
||||
public PlayerData(int playerIndex, Card[]? hand, Card[]? deck, Move? move, List<int>? cardsUsed) {
|
||||
this.PlayerIndex = playerIndex;
|
||||
this.Hand = hand;
|
||||
this.Deck = deck;
|
||||
this.Move = move;
|
||||
this.CardsUsed = cardsUsed;
|
||||
}
|
||||
public PlayerData(int playerIndex, Player player) : this(playerIndex, player.Hand, player.Deck, player.Move, player.CardsUsed) { }
|
||||
}
|
||||
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace TableturfBattleServer;
|
||||
public struct Error {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
|
|
@ -177,6 +176,8 @@ public class Game {
|
|||
var placements = new List<Placement>();
|
||||
var specialSpacesActivated = new List<Point>();
|
||||
|
||||
if (this.Board == null) throw new InvalidOperationException("No board?!");
|
||||
|
||||
// Place the ink.
|
||||
(Placement placement, int cardSize)? placementData = null;
|
||||
foreach (var i in Enumerable.Range(0, this.Players.Count).Where(i => this.Players[i] != null).OrderByDescending(i => this.Players[i]!.Move!.Card.Size)) {
|
||||
|
|
@ -284,21 +285,21 @@ public class Game {
|
|||
|
||||
internal void SendPlayerReadyEvent(int playerIndex) => this.SendEvent("playerReady", new { playerIndex }, false);
|
||||
|
||||
internal void SendEvent(string eventType, object? data, bool includePlayerData) {
|
||||
foreach (var session in Program.httpServer.WebSocketServices.Hosts.First().Sessions.Sessions) {
|
||||
internal void SendEvent<T>(string eventType, T data, bool includePlayerData) {
|
||||
foreach (var session in Program.httpServer!.WebSocketServices.Hosts.First().Sessions.Sessions) {
|
||||
if (session is TableturfWebSocketBehaviour behaviour && behaviour.GameID == this.ID) {
|
||||
if (includePlayerData) {
|
||||
object? playerData = null;
|
||||
DTO.PlayerData? playerData = null;
|
||||
for (int i = 0; i < this.Players.Count; i++) {
|
||||
var player = this.Players[i];
|
||||
if (player.Token == behaviour.ClientToken) {
|
||||
playerData = new { playerIndex = i, hand = player.Hand, deck = player.Deck, move = player.Move, cardsUsed = player.CardsUsed };
|
||||
playerData = new(i, player);
|
||||
break;
|
||||
}
|
||||
}
|
||||
behaviour.SendInternal(JsonConvert.SerializeObject(new { @event = eventType, data, playerData }));
|
||||
behaviour.SendInternal(JsonConvert.SerializeObject(new DTO.WebSocketPayloadWithPlayerData<T>(eventType, data, playerData)));
|
||||
} else {
|
||||
behaviour.SendInternal(JsonConvert.SerializeObject(new { @event = eventType, data }));
|
||||
behaviour.SendInternal(JsonConvert.SerializeObject(new DTO.WebSocketPayload<T>(eventType, data)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ using System.Web;
|
|||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using TableturfBattleServer.DTO;
|
||||
|
||||
using WebSocketSharp.Server;
|
||||
|
||||
using HttpListenerRequest = WebSocketSharp.Net.HttpListenerRequest;
|
||||
|
|
@ -154,7 +156,7 @@ internal class Program {
|
|||
SetResponse(e.Response, (int) HttpStatusCode.OK, "application/json", JsonConvert.SerializeObject(new {
|
||||
game,
|
||||
playerData = game.GetPlayer(clientToken, out var playerIndex, out var player)
|
||||
? new { playerIndex, hand = player.Hand, deck = player.Deck, cardsUsed = player.CardsUsed, move = player.Move }
|
||||
? new PlayerData(playerIndex, player)
|
||||
: null
|
||||
}));
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace TableturfBattleServer;
|
||||
public class Stage {
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; }
|
||||
[JsonProperty("grid")]
|
||||
internal readonly Space[,] grid;
|
||||
/// <summary>
|
||||
/// The lists of starting spaces on this stage.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The smallest list with at least as many spaces as players in the game will be used.
|
||||
/// For example, if there is a list of 3 and a list of 4, the list of 3 will be used for 2 or 3 players, and the list of 4 will be used for 4 players.
|
||||
/// </remarks>
|
||||
[JsonProperty("startSpaces")]
|
||||
internal readonly Point[][] startSpaces;
|
||||
|
||||
public Stage(string name, Space[,] grid, Point[][] startSpaces) {
|
||||
this.Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
this.grid = grid ?? throw new ArgumentNullException(nameof(grid));
|
||||
this.startSpaces = startSpaces ?? throw new ArgumentNullException(nameof(startSpaces));
|
||||
}
|
||||
}
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace TableturfBattleServer;
|
||||
public class Stage {
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; }
|
||||
[JsonProperty("grid")]
|
||||
internal readonly Space[,] grid;
|
||||
/// <summary>
|
||||
/// The lists of starting spaces on this stage.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The smallest list with at least as many spaces as players in the game will be used.
|
||||
/// For example, if there is a list of 3 and a list of 4, the list of 3 will be used for 2 or 3 players, and the list of 4 will be used for 4 players.
|
||||
/// </remarks>
|
||||
[JsonProperty("startSpaces")]
|
||||
internal readonly Point[][] startSpaces;
|
||||
|
||||
public Stage(string name, Space[,] grid, Point[][] startSpaces) {
|
||||
this.Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
this.grid = grid ?? throw new ArgumentNullException(nameof(grid));
|
||||
this.startSpaces = startSpaces ?? throw new ArgumentNullException(nameof(startSpaces));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,132 +1,132 @@
|
|||
using System.Collections.ObjectModel;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace TableturfBattleServer;
|
||||
internal class StageDatabase {
|
||||
private const Space E = Space.Empty;
|
||||
private const Space o = Space.OutOfBounds;
|
||||
|
||||
private static readonly Stage[] stages = new Stage[] {
|
||||
new("Main Street", new Space[9, 26], new[] {
|
||||
new Point[] { new(4, 21), new(4, 4), new(4, 13) },
|
||||
new Point[] { new(2, 21), new(6, 4), new(2, 4), new(6, 21) }
|
||||
}),
|
||||
new("Thunder Point", new Space[,] {
|
||||
{ o, o, o, o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o, o, o, o },
|
||||
}, new[] {
|
||||
new Point[] { new(3, 18), new(12, 3), new(7, 11) },
|
||||
new Point[] { new(3, 18), new(12, 3), new(3, 10), new(12, 11) },
|
||||
}),
|
||||
new("X Marks the Garden", new Space[,] {
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
}, new[] { new Point[] { new(9, 19), new(x: 9, 3), new(3, 11), new(15, 11) } }),
|
||||
new("Square Squared", new Space[15, 15], new[] { new Point[] { new(3, 11), new(11, 3), new(3, 3), new(11, 11) }}),
|
||||
new("Lakefront Property", new Space[,] {
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, o, o, o, o, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, o, o, o, o, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, o, o, o, o, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, o, o, o, o, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
}, new[] { new Point[] { new(3, 12), new(12, 3), new(3, 3), new(12, 12) }}),
|
||||
new("Double Gemini", new Space[,] {
|
||||
{ o, o, o, o, o, o, o, o, E, o, o, o, o, o, o, o, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, E, E, E, o, o, o, o, o, E, E, E, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, E, E, E, E, E, o, o, o, E, E, E, E, E, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, E, E, E, E, E, E, E, o, E, E, E, E, E, E, E, o, o, o, o, o },
|
||||
{ o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o },
|
||||
{ o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o },
|
||||
{ o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o },
|
||||
{ o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o },
|
||||
{ o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o },
|
||||
{ o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o },
|
||||
{ o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o },
|
||||
{ o, o, o, o, o, E, E, E, E, E, E, E, o, E, E, E, E, E, E, E, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, E, E, E, E, E, o, o, o, E, E, E, E, E, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, E, E, E, o, o, o, o, o, E, E, E, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, o, o, o, o, o, o, o, E, o, o, o, o, o, o, o, o },
|
||||
}, new[] {
|
||||
new Point[] { new(8, 19), new(8, 5), new(8, 12) },
|
||||
new Point[] { new(5, 16), new(11, 8), new(5, 8), new(11, 16) }
|
||||
}),
|
||||
new("River Drift", new Space[,] {
|
||||
{ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o },
|
||||
}, new[] {
|
||||
new Point[] { new(3, 21), new(13, 3), new(8, 12) },
|
||||
new Point[] { new(3, 21), new(13, 3), new(8, 16), new(8, 8) }
|
||||
}),
|
||||
new("Box Seats", new Space[10, 10], new[] { new Point[] { new(2, 7), new(7, 2), new(2, 2), new(7, 7) }}),
|
||||
};
|
||||
|
||||
public static Version Version { get; } = new(1, 1, 0, 0);
|
||||
public static DateTime LastModified { get; } = new(2022, 10, 6, 6, 0, 0, DateTimeKind.Utc);
|
||||
public static string JSON { get; }
|
||||
public static ReadOnlyCollection<Stage> Stages { get; }
|
||||
|
||||
static StageDatabase() {
|
||||
Stages = Array.AsReadOnly(stages);
|
||||
JSON = JsonConvert.SerializeObject(stages);
|
||||
}
|
||||
}
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace TableturfBattleServer;
|
||||
internal class StageDatabase {
|
||||
private const Space E = Space.Empty;
|
||||
private const Space o = Space.OutOfBounds;
|
||||
|
||||
private static readonly Stage[] stages = new Stage[] {
|
||||
new("Main Street", new Space[9, 26], new[] {
|
||||
new Point[] { new(4, 21), new(4, 4), new(4, 13) },
|
||||
new Point[] { new(2, 21), new(6, 4), new(2, 4), new(6, 21) }
|
||||
}),
|
||||
new("Thunder Point", new Space[,] {
|
||||
{ o, o, o, o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o, o, o, o },
|
||||
}, new[] {
|
||||
new Point[] { new(3, 18), new(12, 3), new(7, 11) },
|
||||
new Point[] { new(3, 18), new(12, 3), new(3, 10), new(12, 11) },
|
||||
}),
|
||||
new("X Marks the Garden", new Space[,] {
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, E, E, E, E, E, E, o, o, o, o, o, o, o, o },
|
||||
}, new[] { new Point[] { new(9, 19), new(x: 9, 3), new(3, 11), new(15, 11) } }),
|
||||
new("Square Squared", new Space[15, 15], new[] { new Point[] { new(3, 11), new(11, 3), new(3, 3), new(11, 11) }}),
|
||||
new("Lakefront Property", new Space[,] {
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, o, o, o, o, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, o, o, o, o, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, o, o, o, o, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, o, o, o, o, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, },
|
||||
}, new[] { new Point[] { new(3, 12), new(12, 3), new(3, 3), new(12, 12) }}),
|
||||
new("Double Gemini", new Space[,] {
|
||||
{ o, o, o, o, o, o, o, o, E, o, o, o, o, o, o, o, E, o, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, E, E, E, o, o, o, o, o, E, E, E, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, E, E, E, E, E, o, o, o, E, E, E, E, E, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, E, E, E, E, E, E, E, o, E, E, E, E, E, E, E, o, o, o, o, o },
|
||||
{ o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o },
|
||||
{ o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o },
|
||||
{ o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o },
|
||||
{ o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o },
|
||||
{ o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o },
|
||||
{ o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o },
|
||||
{ o, o, o, o, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, o, o, o, o },
|
||||
{ o, o, o, o, o, E, E, E, E, E, E, E, o, E, E, E, E, E, E, E, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, E, E, E, E, E, o, o, o, E, E, E, E, E, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, E, E, E, o, o, o, o, o, E, E, E, o, o, o, o, o, o, o },
|
||||
{ o, o, o, o, o, o, o, o, E, o, o, o, o, o, o, o, E, o, o, o, o, o, o, o, o },
|
||||
}, new[] {
|
||||
new Point[] { new(8, 19), new(8, 5), new(8, 12) },
|
||||
new Point[] { new(5, 16), new(11, 8), new(5, 8), new(11, 16) }
|
||||
}),
|
||||
new("River Drift", new Space[,] {
|
||||
{ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, E, E, E, E, E, E, E },
|
||||
{ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E },
|
||||
{ E, E, E, E, E, E, E, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o },
|
||||
{ E, E, E, E, E, E, E, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o },
|
||||
}, new[] {
|
||||
new Point[] { new(3, 21), new(13, 3), new(8, 12) },
|
||||
new Point[] { new(3, 21), new(13, 3), new(8, 16), new(8, 8) }
|
||||
}),
|
||||
new("Box Seats", new Space[10, 10], new[] { new Point[] { new(2, 7), new(7, 2), new(2, 2), new(7, 7) }}),
|
||||
};
|
||||
|
||||
public static Version Version { get; } = new(1, 1, 0, 0);
|
||||
public static DateTime LastModified { get; } = new(2022, 10, 6, 6, 0, 0, DateTimeKind.Utc);
|
||||
public static string JSON { get; }
|
||||
public static ReadOnlyCollection<Stage> Stages { get; }
|
||||
|
||||
static StageDatabase() {
|
||||
Stages = Array.AsReadOnly(stages);
|
||||
JSON = JsonConvert.SerializeObject(stages);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
using System.Web;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using WebSocketSharp.Server;
|
||||
|
||||
namespace TableturfBattleServer;
|
||||
|
||||
internal class TableturfWebSocketBehaviour : WebSocketBehavior {
|
||||
public Guid GameID { get; set; }
|
||||
public Guid ClientToken { get; set; }
|
||||
|
|
@ -13,7 +15,21 @@ internal class TableturfWebSocketBehaviour : WebSocketBehavior {
|
|||
this.GameID = gameID;
|
||||
if (args.TryGetValue("clientToken", out var clientTokenString) && Guid.TryParse(clientTokenString, out var clientToken))
|
||||
this.ClientToken = clientToken;
|
||||
|
||||
// Send an initial state payload.
|
||||
if (Program.games.TryGetValue(this.GameID, out var game)) {
|
||||
DTO.PlayerData? playerData = null;
|
||||
for (int i = 0; i < game.Players.Count; i++) {
|
||||
var player = game.Players[i];
|
||||
if (player.Token == this.ClientToken) {
|
||||
playerData = new(i, player);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.Send(JsonConvert.SerializeObject(new DTO.WebSocketPayloadWithPlayerData<Game?>("sync", game, playerData)));
|
||||
} else
|
||||
this.Send(JsonConvert.SerializeObject(new DTO.WebSocketPayloadWithPlayerData<Game?>("sync", null, null)));
|
||||
}
|
||||
|
||||
internal void SendInternal(string data) => this.Send(data);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user