From d4e8d86571fecbbe3dd54f75d64353e6e1793748 Mon Sep 17 00:00:00 2001 From: Andrio Celos Date: Tue, 25 Oct 2022 12:08:18 +1100 Subject: [PATCH] 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. --- TableturfBattleClient/index.html | 5 +- TableturfBattleClient/src/CardDatabase.ts | 67 ++-- TableturfBattleClient/src/GameVariables.ts | 2 + .../src/Pages/PreGamePage.ts | 48 ++- TableturfBattleClient/src/StageDatabase.ts | 62 ++-- TableturfBattleClient/src/app.ts | 293 +++++++++++------- TableturfBattleClient/tableturf.css | 56 ++++ TableturfBattleServer/DataStructures.cs | 44 +++ TableturfBattleServer/Error.cs | 8 +- TableturfBattleServer/Game.cs | 15 +- TableturfBattleServer/Program.cs | 4 +- TableturfBattleServer/Stage.cs | 48 +-- TableturfBattleServer/StageDatabase.cs | 264 ++++++++-------- .../TableturfWebSocketBehaviour.cs | 20 +- 14 files changed, 552 insertions(+), 384 deletions(-) create mode 100644 TableturfBattleServer/DataStructures.cs diff --git a/TableturfBattleClient/index.html b/TableturfBattleClient/index.html index c916e7e..c148b28 100644 --- a/TableturfBattleClient/index.html +++ b/TableturfBattleClient/index.html @@ -29,7 +29,7 @@

- +

Create or join a different room
+

Edit decks

diff --git a/TableturfBattleClient/src/CardDatabase.ts b/TableturfBattleClient/src/CardDatabase.ts index af2affd..6cfe68d 100644 --- a/TableturfBattleClient/src/CardDatabase.ts +++ b/TableturfBattleClient/src/CardDatabase.ts @@ -1,31 +1,36 @@ -const cardDatabase = { - cards: null as Card[] | null, - loadAsync() { - return new Promise((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((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(); + }); + } +} diff --git a/TableturfBattleClient/src/GameVariables.ts b/TableturfBattleClient/src/GameVariables.ts index c14f65f..f47dfbf 100644 --- a/TableturfBattleClient/src/GameVariables.ts +++ b/TableturfBattleClient/src/GameVariables.ts @@ -13,6 +13,8 @@ let currentGame: { webSocket: WebSocket } | null; +let enterGameTimeout: number | null = null; + const playerList = document.getElementById('playerList')!; const playerListItems: HTMLElement[] = [ ]; diff --git a/TableturfBattleClient/src/Pages/PreGamePage.ts b/TableturfBattleClient/src/Pages/PreGamePage.ts index c0efafe..6b970c4 100644 --- a/TableturfBattleClient/src/Pages/PreGamePage.ts +++ b/TableturfBattleClient/src/Pages/PreGamePage.ts @@ -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) { diff --git a/TableturfBattleClient/src/StageDatabase.ts b/TableturfBattleClient/src/StageDatabase.ts index f322001..8dab187 100644 --- a/TableturfBattleClient/src/StageDatabase.ts +++ b/TableturfBattleClient/src/StageDatabase.ts @@ -1,31 +1,31 @@ -const stageDatabase = { - stages: null as Stage[] | null, - loadAsync() { - return new Promise((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((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(); + }); + } +} diff --git a/TableturfBattleClient/src/app.ts b/TableturfBattleClient/src/app.ts index 1512b16..17d61ca 100644 --- a/TableturfBattleClient/src/app.ts +++ b/TableturfBattleClient/src/app.ts @@ -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; diff --git a/TableturfBattleClient/tableturf.css b/TableturfBattleClient/tableturf.css index c6b6204..b2f16c4 100644 --- a/TableturfBattleClient/tableturf.css +++ b/TableturfBattleClient/tableturf.css @@ -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; + } +} diff --git a/TableturfBattleServer/DataStructures.cs b/TableturfBattleServer/DataStructures.cs new file mode 100644 index 0000000..542d21d --- /dev/null +++ b/TableturfBattleServer/DataStructures.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; + +namespace TableturfBattleServer.DTO; + +internal class WebSocketPayload { + [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 : WebSocketPayload { + [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? CardsUsed; + + public PlayerData(int playerIndex, Card[]? hand, Card[]? deck, Move? move, List? 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) { } +} diff --git a/TableturfBattleServer/Error.cs b/TableturfBattleServer/Error.cs index 9ef7c04..e1ba7e8 100644 --- a/TableturfBattleServer/Error.cs +++ b/TableturfBattleServer/Error.cs @@ -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 { diff --git a/TableturfBattleServer/Game.cs b/TableturfBattleServer/Game.cs index 4fc6635..c5d8beb 100644 --- a/TableturfBattleServer/Game.cs +++ b/TableturfBattleServer/Game.cs @@ -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(); var specialSpacesActivated = new List(); + 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(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(eventType, data, playerData))); } else { - behaviour.SendInternal(JsonConvert.SerializeObject(new { @event = eventType, data })); + behaviour.SendInternal(JsonConvert.SerializeObject(new DTO.WebSocketPayload(eventType, data))); } } } diff --git a/TableturfBattleServer/Program.cs b/TableturfBattleServer/Program.cs index 6c5cc4f..989f0a9 100644 --- a/TableturfBattleServer/Program.cs +++ b/TableturfBattleServer/Program.cs @@ -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; diff --git a/TableturfBattleServer/Stage.cs b/TableturfBattleServer/Stage.cs index e4c234a..af1c919 100644 --- a/TableturfBattleServer/Stage.cs +++ b/TableturfBattleServer/Stage.cs @@ -1,24 +1,24 @@ -using Newtonsoft.Json; - -namespace TableturfBattleServer; -public class Stage { - [JsonProperty("name")] - public string Name { get; } - [JsonProperty("grid")] - internal readonly Space[,] grid; - /// - /// The lists of starting spaces on this stage. - /// - /// - /// 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. - /// - [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; + /// + /// The lists of starting spaces on this stage. + /// + /// + /// 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. + /// + [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)); + } +} diff --git a/TableturfBattleServer/StageDatabase.cs b/TableturfBattleServer/StageDatabase.cs index 630ca69..1702919 100644 --- a/TableturfBattleServer/StageDatabase.cs +++ b/TableturfBattleServer/StageDatabase.cs @@ -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 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 Stages { get; } + + static StageDatabase() { + Stages = Array.AsReadOnly(stages); + JSON = JsonConvert.SerializeObject(stages); + } +} diff --git a/TableturfBattleServer/TableturfWebSocketBehaviour.cs b/TableturfBattleServer/TableturfWebSocketBehaviour.cs index c573ccc..9fb32f1 100644 --- a/TableturfBattleServer/TableturfWebSocketBehaviour.cs +++ b/TableturfBattleServer/TableturfWebSocketBehaviour.cs @@ -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("sync", game, playerData))); + } else + this.Send(JsonConvert.SerializeObject(new DTO.WebSocketPayloadWithPlayerData("sync", null, null))); } internal void SendInternal(string data) => this.Send(data); -} \ No newline at end of file +}