[refactor] create Transport class

This commit is contained in:
evanpelle
2024-09-26 17:36:54 -07:00
parent 851e12613a
commit 2f9269fa65
4 changed files with 189 additions and 131 deletions
+35 -102
View File
@@ -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')
}
}
}
+145
View File
@@ -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')
}
}
}
+5 -11
View File
@@ -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))
});
}
+4 -18
View File
@@ -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())
},