From a5a2f46099f7b735f1d5d7e491780e7038398c5d Mon Sep 17 00:00:00 2001 From: Evan Date: Fri, 24 Jan 2025 20:07:12 -0800 Subject: [PATCH] have client send winner to server --- src/client/LocalServer.ts | 8 +++++++- src/client/Transport.ts | 24 +++++++++++++++++++++++- src/client/graphics/GameRenderer.ts | 1 + src/client/graphics/layers/WinModal.ts | 4 ++++ src/core/Schemas.ts | 14 +++++++++++--- src/core/Util.ts | 6 ++++-- src/server/Archive.ts | 2 +- src/server/GameServer.ts | 9 +++++++-- 8 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/client/LocalServer.ts b/src/client/LocalServer.ts index 8538d71ac..1a99bdfb4 100644 --- a/src/client/LocalServer.ts +++ b/src/client/LocalServer.ts @@ -16,6 +16,8 @@ export class LocalServer { private paused = false + private winner: ClientID | null = null + constructor( private serverConfig: ServerConfig, @@ -58,6 +60,9 @@ export class LocalServer { } this.intents.push(clientMsg.intent) } + if (clientMsg.type == "winner") { + this.winner = clientMsg.winner + } } private endTurn() { @@ -92,7 +97,8 @@ export class LocalServer { players, this.turns, this.startedAt, - Date.now() + Date.now(), + this.winner ) // Clear turns because beacon only supports up to 64kb record.turns = [] diff --git a/src/client/Transport.ts b/src/client/Transport.ts index a242607fb..bca5567f4 100644 --- a/src/client/Transport.ts +++ b/src/client/Transport.ts @@ -2,7 +2,7 @@ import { Config, ServerConfig } from "../core/configuration/Config" import { SendLogEvent } from "../core/Consolex" import { EventBus, GameEvent } from "../core/EventBus" import { AllianceRequest, AllPlayers, Cell, GameType, Player, PlayerID, PlayerType, UnitType } from "../core/game/Game" -import { ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, GameID, Intent, ServerMessage, ServerMessageSchema, ClientPingMessageSchema, GameConfig, ClientLogMessageSchema } from "../core/Schemas" +import { ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, GameID, Intent, ServerMessage, ServerMessageSchema, ClientPingMessageSchema, GameConfig, ClientLogMessageSchema, ClientSendWinnerSchema } from "../core/Schemas" import { LobbyConfig } from "./ClientGameRunner" import { LocalServer } from "./LocalServer" import { UsernameInput } from "./UsernameInput"; @@ -93,6 +93,12 @@ export class SendSetTargetTroopRatioEvent implements GameEvent { ) { } } +export class SendWinnerEvent implements GameEvent { + constructor( + public readonly winner: ClientID + ) { } +} + export class Transport { private socket: WebSocket @@ -132,6 +138,7 @@ export class Transport { this.eventBus.on(SendLogEvent, (e) => this.onSendLogEvent(e)) this.eventBus.on(PauseGameEvent, (e) => this.onPauseGameEvent(e)) + this.eventBus.on(SendWinnerEvent, (e) => this.onSendWinnerEvent(e)) } private startPing() { @@ -375,6 +382,21 @@ export class Transport { } } + private onSendWinnerEvent(event: SendWinnerEvent) { + if (this.isLocal || this.socket.readyState === WebSocket.OPEN) { + const msg = ClientSendWinnerSchema.parse({ + type: "winner", + clientID: this.lobbyConfig.clientID, + gameID: this.lobbyConfig.gameID, + winner: event.winner, + }) + this.sendMsg(JSON.stringify(msg)) + } else { + console.log('WebSocket is not open. Current state:', this.socket.readyState); + console.log('attempting reconnect') + } + } + private sendIntent(intent: Intent) { if (this.isLocal || this.socket.readyState === WebSocket.OPEN) { const msg = ClientIntentMessageSchema.parse({ diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 4ac998a28..2e5c23267 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -81,6 +81,7 @@ export function createRenderer(canvas: HTMLCanvasElement, game: GameView, eventB if (!(winModel instanceof WinModal)) { console.error('win modal not found') } + winModel.eventBus = eventBus winModel.game = game const optionsMenu = document.querySelector('options-menu') as OptionsMenu diff --git a/src/client/graphics/layers/WinModal.ts b/src/client/graphics/layers/WinModal.ts index 0d8726050..288f57852 100644 --- a/src/client/graphics/layers/WinModal.ts +++ b/src/client/graphics/layers/WinModal.ts @@ -7,6 +7,8 @@ import { Layer } from './Layer'; import { GameUpdateType } from '../../../core/game/GameUpdates'; import { PseudoRandom } from '../../../core/PseudoRandom'; import { simpleHash } from '../../../core/Util'; +import { EventBus } from '../../../core/EventBus'; +import { SendWinnerEvent } from '../../Transport'; const lowRadiationVictoryQuotes = [ @@ -147,6 +149,7 @@ export const defeatQuotes = [ @customElement('win-modal') export class WinModal extends LitElement implements Layer { public game: GameView + public eventBus: EventBus private rand: PseudoRandom; @@ -300,6 +303,7 @@ export class WinModal extends LitElement implements Layer { this.game.updatesSinceLastTick()[GameUpdateType.WinUpdate] .forEach(wu => { const winner = this.game.playerBySmallID(wu.winnerID) as PlayerView + this.eventBus.emit(new SendWinnerEvent(winner.clientID())) if (winner == this.game.myPlayer()) { this._title = 'You Won!' if (this.game.numTilesWithFallout() / this.game.numLandTiles() > .6) { diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts index 8b8827e1f..bea67b70e 100644 --- a/src/core/Schemas.ts +++ b/src/core/Schemas.ts @@ -31,13 +31,14 @@ export type BuildUnitIntent = z.infer export type Turn = z.infer export type GameConfig = z.infer -export type ClientMessage = ClientPingMessage | ClientIntentMessage | ClientJoinMessage | ClientLogMessage +export type ClientMessage = ClientSendWinnerMessage | ClientPingMessage | ClientIntentMessage | ClientJoinMessage | ClientLogMessage export type ServerMessage = ServerSyncMessage | ServerStartGameMessage | ServerPingMessage export type ServerSyncMessage = z.infer export type ServerStartGameMessage = z.infer export type ServerPingMessage = z.infer +export type ClientSendWinnerMessage = z.infer export type ClientPingMessage = z.infer export type ClientIntentMessage = z.infer export type ClientJoinMessage = z.infer @@ -224,11 +225,16 @@ export const ServerMessageSchema = z.union([ // Client const ClientBaseMessageSchema = z.object({ - type: z.enum(['join', 'intent', 'ping', 'log']), + type: z.enum(['winner', 'join', 'intent', 'ping', 'log']), clientID: ID, gameID: ID, }) +export const ClientSendWinnerSchema = ClientBaseMessageSchema.extend({ + type: z.literal('winner'), + winner: ID, +}) + export const ClientLogMessageSchema = ClientBaseMessageSchema.extend({ type: z.literal('log'), severity: z.nativeEnum(LogSeverity), @@ -254,6 +260,7 @@ export const ClientJoinMessageSchema = ClientBaseMessageSchema.extend({ }) export const ClientMessageSchema = z.union([ + ClientSendWinnerSchema, ClientPingMessageSchema, ClientIntentMessageSchema, ClientJoinMessageSchema, @@ -276,5 +283,6 @@ export const GameRecordSchema = z.object({ durationSeconds: z.number(), date: SafeString, num_turns: z.number(), - turns: z.array(TurnSchema) + turns: z.array(TurnSchema), + winner: ID.nullable() }) diff --git a/src/core/Util.ts b/src/core/Util.ts index 7bd608739..f9c798dca 100644 --- a/src/core/Util.ts +++ b/src/core/Util.ts @@ -2,7 +2,7 @@ import { v4 as uuidv4 } from 'uuid'; import twemoji from 'twemoji'; import DOMPurify from 'dompurify'; import { Cell, Game, Player, Unit } from "./game/Game"; -import { GameConfig, GameID, GameRecord, PlayerRecord, Turn } from './Schemas'; +import { ClientID, GameConfig, GameID, GameRecord, PlayerRecord, Turn } from './Schemas'; import { customAlphabet, nanoid } from 'nanoid'; import { andFN, GameMap, manhattanDistFN, TileRef } from './game/GameMap'; @@ -207,7 +207,8 @@ export function CreateGameRecord( players: PlayerRecord[], turns: Turn[], start: number, - end: number + end: number, + winner: ClientID | null ): GameRecord { const record: GameRecord = { id: id, @@ -235,6 +236,7 @@ export function CreateGameRecord( record.players = players record.durationSeconds = Math.floor((record.endTimestampMS - record.startTimestampMS) / 1000) record.num_turns = turns.length + record.winner = winner return record; } diff --git a/src/server/Archive.ts b/src/server/Archive.ts index 094b033c6..5803a6d0d 100644 --- a/src/server/Archive.ts +++ b/src/server/Archive.ts @@ -79,7 +79,7 @@ async function archiveToBigQuery(gameRecord: GameRecord) { duration_seconds: gameRecord.durationSeconds, number_turns: gameRecord.num_turns, game_mode: gameRecord.gameConfig.gameType, - winner: null, + winner: gameRecord.winner, difficulty: gameRecord.gameConfig.difficulty, map: gameRecord.gameConfig.gameMap, players: gameRecord.players.map(p => ({ diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts index 45fd2c2e5..7a9c34f6a 100644 --- a/src/server/GameServer.ts +++ b/src/server/GameServer.ts @@ -5,7 +5,6 @@ import WebSocket from 'ws'; import { slog } from "./StructuredLog"; import { CreateGameRecord } from "../core/Util"; import { archive } from "./Archive"; -import { arc } from "d3"; export enum GamePhase { @@ -31,6 +30,8 @@ export class GameServer { private lastPingUpdate = 0 + private winner: ClientID | null = null + constructor( public readonly id: string, public readonly createdAt: number, @@ -89,6 +90,9 @@ export class GameServer { this.lastPingUpdate = Date.now() client.lastPing = Date.now() } + if (clientMsg.type == "winner") { + this.winner = clientMsg.winner + } } catch (error) { console.log(`error handline websocket request in game server: ${error}`) } @@ -190,7 +194,8 @@ export class GameServer { playerRecords, this.turns, this._startTime, - Date.now() + Date.now(), + this.winner ) ) } else {