diff --git a/src/client/ClientGame.ts b/src/client/ClientGame.ts index 0986b6241..1ecac1a58 100644 --- a/src/client/ClientGame.ts +++ b/src/client/ClientGame.ts @@ -10,18 +10,26 @@ import {TerrainMap} from "../core/game/TerrainMapLoader"; import {and, bfs, dist, manhattanDist} from "../core/Util"; import {TerrainLayer} from "./graphics/layers/TerrainLayer"; import {WinCheckExecution} from "../core/execution/WinCheckExecution"; -import {SendAllianceRequestUIEvent, SendBreakAllianceUIEvent} from "./graphics/layers/UILayer"; +import {SendAttackIntentEvent, SendBoatAttackIntentEvent, SendBreakAllianceIntentEvent, SendSpawnIntentEvent, Transport} from "./Transport"; import {createCanvas} from "./graphics/Utils"; -import {DisplayMessageEvent, MessageType, AllianceRequestReplyUIEvent as SendAllianceRequestReplyUIEvent} from "./graphics/layers/EventsDisplay"; +import {DisplayMessageEvent, MessageType} from "./graphics/layers/EventsDisplay"; export function createClientGame(name: string, clientID: ClientID, playerID: PlayerID, ip: string | null, gameID: GameID, config: Config, terrainMap: TerrainMap): ClientGame { let eventBus = new EventBus() + let game = createGame(terrainMap, eventBus, config) const canvas = createCanvas() let gameRenderer = createRenderer(canvas, game, eventBus, clientID) + const wsHost = process.env.WEBSOCKET_URL || window.location.host; + const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const socket = new WebSocket(`${wsProtocol}//${wsHost}`) + + const transport = new Transport(socket, eventBus, gameID, clientID, playerID) + + return new ClientGame( name, clientID, @@ -32,14 +40,14 @@ export function createClientGame(name: string, clientID: ClientID, playerID: Pla game, gameRenderer, new InputHandler(canvas, eventBus), - new Executor(game, gameID) + new Executor(game, gameID), + socket, ) } export class ClientGame { private myPlayer: Player private turns: Turn[] = [] - private socket: WebSocket private isActive = false private currTurn = 0 @@ -59,13 +67,11 @@ export class ClientGame { private gs: Game, private renderer: GameRenderer, private input: InputHandler, - private executor: Executor + private executor: Executor, + private socket: WebSocket, ) { } public join() { - const wsHost = process.env.WEBSOCKET_URL || window.location.host; - const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - this.socket = new WebSocket(`${wsProtocol}//${wsHost}`) this.socket.onopen = () => { console.log('Connected to game server!'); this.socket.send( @@ -93,11 +99,11 @@ export class ClientGame { if (!this.isActive) { this.start() } - this.sendIntent({ - type: "updateName", - name: this.playerName, - clientID: this.id - }) + // this.sendIntent({ + // type: "updateName", + // name: this.playerName, + // clientID: this.id + // }) } if (message.type == "turn") { this.addTurn(message.turn) @@ -125,9 +131,6 @@ export class ClientGame { this.eventBus.on(PlayerEvent, (e) => this.playerEvent(e)) this.eventBus.on(MouseUpEvent, (e) => this.inputEvent(e)) - this.eventBus.on(SendAllianceRequestUIEvent, (e) => this.onSendAllianceRequest(e)) - this.eventBus.on(SendAllianceRequestReplyUIEvent, (e) => this.onAllianceRequestReplyUIEvent(e)) - this.eventBus.on(SendBreakAllianceUIEvent, (e) => this.onBreakAllianceRequestUIEvent(e)) this.renderer.initialize() this.input.initialize() @@ -192,7 +195,7 @@ export class ClientGame { } const tile = this.gs.tile(cell) if (tile.isLand() && !tile.hasOwner() && this.gs.inSpawnPhase()) { - this.sendSpawnIntent(cell) + this.eventBus.emit(new SendSpawnIntentEvent(cell, this.playerName)) return } if (this.gs.inSpawnPhase()) { @@ -260,9 +263,17 @@ export class ClientGame { enemyShoreClosest = enemyShoreDists[0].dist } if (enemyShoreClosest < borderTileClosest / 6) { - this.sendBoatAttackIntent(targetID, enemyShoreDists[0].tile.cell(), this.gs.config().boatAttackAmount(this.myPlayer, owner)) + this.eventBus.emit(new SendBoatAttackIntentEvent( + targetID, + enemyShoreDists[0].tile.cell(), + this.gs.config().boatAttackAmount(this.myPlayer, owner) + )) } else { - this.sendAttackIntent(targetID, cell, this.gs.config().attackAmount(this.myPlayer, owner)) + this.eventBus.emit(new SendAttackIntentEvent( + targetID, + cell, + this.gs.config().attackAmount(this.myPlayer, owner) + )) } } @@ -276,90 +287,12 @@ export class ClientGame { .filter(t => t.owner() != this.myPlayer) .sort((a, b) => manhattanDist(tile.cell(), a.cell()) - manhattanDist(tile.cell(), b.cell())) if (tn.length > 0) { - this.sendBoatAttackIntent(tn[0].owner().id(), tn[0].cell(), this.gs.config().boatAttackAmount(this.myPlayer, owner)) + this.eventBus.emit(new SendBoatAttackIntentEvent( + tn[0].owner().id(), + tn[0].cell(), + this.gs.config().boatAttackAmount(this.myPlayer, owner) + )) } } } - - private onSendAllianceRequest(event: SendAllianceRequestUIEvent) { - this.sendIntent({ - type: "allianceRequest", - clientID: this.id, - requestor: event.requestor.id(), - recipient: event.recipient.id(), - }) - } - - private onAllianceRequestReplyUIEvent(event: SendAllianceRequestReplyUIEvent) { - this.sendIntent({ - type: "allianceRequestReply", - clientID: this.id, - requestor: event.allianceRequest.requestor().id(), - recipient: event.allianceRequest.recipient().id(), - accept: event.accepted, - }) - } - - private onBreakAllianceRequestUIEvent(event: SendBreakAllianceUIEvent) { - this.sendIntent({ - type: "breakAlliance", - clientID: this.id, - requestor: event.requestor.id(), - recipient: event.recipient.id(), - }) - } - - private sendSpawnIntent(cell: Cell) { - this.sendIntent({ - type: "spawn", - clientID: this.id, - playerID: this.playerID, - name: this.playerName, - playerType: PlayerType.Human, - x: cell.x, - y: cell.y - }) - } - - private sendAttackIntent(targetID: PlayerID, cell: Cell, troops: number) { - this.sendIntent({ - type: "attack", - clientID: this.id, - attackerID: this.myPlayer.id(), - targetID: targetID, - troops: troops, - sourceX: null, - sourceY: null, - targetX: cell.x, - targetY: cell.y - }) - } - - private sendBoatAttackIntent(targetID: PlayerID, cell: Cell, troops: number) { - this.sendIntent({ - type: "boat", - clientID: this.id, - attackerID: this.myPlayer.id(), - targetID: targetID, - troops: troops, - x: cell.x, - y: cell.y, - }) - } - - private sendIntent(intent: Intent) { - if (this.socket.readyState === WebSocket.OPEN) { - const msg = ClientIntentMessageSchema.parse({ - type: "intent", - clientID: this.id, - gameID: this.gameID, - intent: intent - }) - this.socket.send(JSON.stringify(msg)) - } else { - console.log('WebSocket is not open. Current state:', this.socket.readyState); - console.log('attempting reconnect') - } - } - } \ No newline at end of file diff --git a/src/client/Transport.ts b/src/client/Transport.ts new file mode 100644 index 000000000..71f8f843f --- /dev/null +++ b/src/client/Transport.ts @@ -0,0 +1,145 @@ +import {EventBus, GameEvent} from "../core/EventBus" +import {AllianceRequest, Cell, Player, PlayerID, PlayerType} from "../core/game/Game" +import {ClientID, ClientIntentMessageSchema, GameID, Intent} from "../core/Schemas" + + +export class SendAllianceRequestIntentEvent implements GameEvent { + constructor( + public readonly requestor: Player, + public readonly recipient: Player + ) { } +} +export class SendBreakAllianceIntentEvent implements GameEvent { + constructor( + public readonly requestor: Player, + public readonly recipient: Player + ) { } +} + +export class SendAllianceReplyIntentEvent implements GameEvent { + constructor( + public readonly allianceRequest: AllianceRequest, + public readonly accepted: boolean + ) { } +} + +export class SendSpawnIntentEvent implements GameEvent { + constructor( + public readonly cell: Cell, + public readonly playerName: string, + ) { } +} + +export class SendAttackIntentEvent implements GameEvent { + constructor(public readonly targetID: PlayerID, + public readonly cell: Cell, + public readonly troops: number + ) { } +} + +export class SendBoatAttackIntentEvent implements GameEvent { + constructor( + public readonly targetID: PlayerID, + public readonly cell: Cell, + public readonly troops: number + ) { } +} + +export class Transport { + + constructor( + private socket: WebSocket, + private eventBus: EventBus, + private gameID: GameID, + private clientID: ClientID, + private playerID: PlayerID, + ) { + this.eventBus.on(SendAllianceRequestIntentEvent, (e) => this.onSendAllianceRequest(e)) + this.eventBus.on(SendAllianceReplyIntentEvent, (e) => this.onAllianceRequestReplyUIEvent(e)) + this.eventBus.on(SendBreakAllianceIntentEvent, (e) => this.onBreakAllianceRequestUIEvent(e)) + this.eventBus.on(SendSpawnIntentEvent, (e) => this.onSendSpawnIntentEvent(e)) + this.eventBus.on(SendAttackIntentEvent, (e) => this.onSendAttackIntent(e)) + this.eventBus.on(SendBoatAttackIntentEvent, (e) => this.onSendBoatAttackIntent(e)) + } + + private onSendAllianceRequest(event: SendAllianceRequestIntentEvent) { + this.sendIntent({ + type: "allianceRequest", + clientID: this.clientID, + requestor: event.requestor.id(), + recipient: event.recipient.id(), + }) + } + + private onAllianceRequestReplyUIEvent(event: SendAllianceReplyIntentEvent) { + this.sendIntent({ + type: "allianceRequestReply", + clientID: this.clientID, + requestor: event.allianceRequest.requestor().id(), + recipient: event.allianceRequest.recipient().id(), + accept: event.accepted, + }) + } + + private onBreakAllianceRequestUIEvent(event: SendBreakAllianceIntentEvent) { + this.sendIntent({ + type: "breakAlliance", + clientID: this.clientID, + requestor: event.requestor.id(), + recipient: event.recipient.id(), + }) + } + + private onSendSpawnIntentEvent(event: SendSpawnIntentEvent) { + this.sendIntent({ + type: "spawn", + clientID: this.clientID, + playerID: this.playerID, + name: event.playerName, + playerType: PlayerType.Human, + x: event.cell.x, + y: event.cell.y + }) + } + + private onSendAttackIntent(event: SendAttackIntentEvent) { + this.sendIntent({ + type: "attack", + clientID: this.clientID, + attackerID: this.playerID, + targetID: event.targetID, + troops: event.troops, + sourceX: null, + sourceY: null, + targetX: event.cell.x, + targetY: event.cell.y + }) + } + + private onSendBoatAttackIntent(event: SendBoatAttackIntentEvent) { + this.sendIntent({ + type: "boat", + clientID: this.clientID, + attackerID: this.playerID, + targetID: event.targetID, + troops: event.troops, + x: event.cell.x, + y: event.cell.y, + }) + } + + private sendIntent(intent: Intent) { + if (this.socket.readyState === WebSocket.OPEN) { + const msg = ClientIntentMessageSchema.parse({ + type: "intent", + clientID: this.clientID, + gameID: this.gameID, + intent: intent + }) + this.socket.send(JSON.stringify(msg)) + } else { + console.log('WebSocket is not open. Current state:', this.socket.readyState); + console.log('attempting reconnect') + } + } +} diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts index a44807149..a10029a70 100644 --- a/src/client/graphics/layers/EventsDisplay.ts +++ b/src/client/graphics/layers/EventsDisplay.ts @@ -1,15 +1,9 @@ import {nullable} from "zod"; import {EventBus, GameEvent} from "../../../core/EventBus"; -import {AllianceExpiredEvent, AllianceRequest, AllianceRequestEvent, AllianceRequestReplyEvent, BrokeAllianceEvent, Game, Player, PlayerID} from "../../../core/game/Game"; +import {AllianceExpiredEvent, AllianceRequestEvent, AllianceRequestReplyEvent, BrokeAllianceEvent, Game, Player, PlayerID} from "../../../core/game/Game"; import {ClientID} from "../../../core/Schemas"; import {Layer} from "./Layer"; - -export class AllianceRequestReplyUIEvent implements GameEvent { - constructor( - public readonly allianceRequest: AllianceRequest, - public readonly accepted: boolean, - ) { } -} +import {SendAllianceReplyIntentEvent} from "../../Transport"; export enum MessageType { SUCCESS, @@ -136,18 +130,18 @@ export class EventsDisplay implements Layer { { text: "Accept", className: "btn", - action: () => this.eventBus.emit(new AllianceRequestReplyUIEvent(event.allianceRequest, true)), + action: () => this.eventBus.emit(new SendAllianceReplyIntentEvent(event.allianceRequest, true)), }, { text: "Reject", className: "btn btn-info", - action: () => this.eventBus.emit(new AllianceRequestReplyUIEvent(event.allianceRequest, false)), + action: () => this.eventBus.emit(new SendAllianceReplyIntentEvent(event.allianceRequest, false)), } ], highlight: true, type: MessageType.INFO, createdAt: this.game.ticks(), - onDelete: () => this.eventBus.emit(new AllianceRequestReplyUIEvent(event.allianceRequest, false)) + onDelete: () => this.eventBus.emit(new SendAllianceReplyIntentEvent(event.allianceRequest, false)) }); } diff --git a/src/client/graphics/layers/UILayer.ts b/src/client/graphics/layers/UILayer.ts index cf2149b6f..44e6afe7a 100644 --- a/src/client/graphics/layers/UILayer.ts +++ b/src/client/graphics/layers/UILayer.ts @@ -1,5 +1,5 @@ import {GameEnv, Theme} from "../../../core/configuration/Config"; -import {EventBus, GameEvent} from "../../../core/EventBus"; +import {EventBus} from "../../../core/EventBus"; import {WinEvent} from "../../../core/execution/WinCheckExecution"; import {AllianceRequest, AllianceRequestReplyEvent, Game, Player} from "../../../core/game/Game"; import {ClientID} from "../../../core/Schemas"; @@ -7,21 +7,7 @@ import {ContextMenuEvent} from "../../InputHandler"; import {Layer} from "./Layer"; import {TransformHandler} from "../TransformHandler"; import {MessageType} from "./EventsDisplay"; - -export class SendAllianceRequestUIEvent implements GameEvent { - constructor( - public readonly requestor: Player, - public readonly recipient: Player - ) { } -} - - -export class SendBreakAllianceUIEvent implements GameEvent { - constructor( - public readonly requestor: Player, - public readonly recipient: Player - ) { } -} +import {SendAllianceRequestIntentEvent, SendBreakAllianceIntentEvent} from "../../Transport"; interface MenuOption { label: string; @@ -265,7 +251,7 @@ export class UILayer implements Layer { label: "Break Alliance", action: (): void => { this.eventBus.emit( - new SendBreakAllianceUIEvent(myPlayer, owner) + new SendBreakAllianceIntentEvent(myPlayer, owner) ) }, }) @@ -274,7 +260,7 @@ export class UILayer implements Layer { label: "Request Alliance", action: (): void => { this.eventBus.emit( - new SendAllianceRequestUIEvent(myPlayer, owner) + new SendAllianceRequestIntentEvent(myPlayer, owner) ) this.game.displayMessage(`sending alliance request to ${owner.name()}`, MessageType.INFO, myPlayer.id()) },