send game hash to server each second

This commit is contained in:
Evan
2025-02-25 11:49:07 -08:00
parent 393ed64ab5
commit 3fa97ed686
11 changed files with 93 additions and 25 deletions
+9 -1
View File
@@ -6,11 +6,16 @@ import { ClientID, GameConfig, GameID, ServerMessage } from "../core/Schemas";
import { loadTerrainMap } from "../core/game/TerrainMapLoader";
import {
SendAttackIntentEvent,
SendHashEvent,
SendSpawnIntentEvent,
Transport,
} from "./Transport";
import { createCanvas } from "./Utils";
import { ErrorUpdate } from "../core/game/GameUpdates";
import {
ErrorUpdate,
GameUpdateType,
HashUpdate,
} from "../core/game/GameUpdates";
import { WorkerClient } from "../core/worker/WorkerClient";
import { consolex, initRemoteSender } from "../core/Consolex";
import { getConfig, getServerConfig } from "../core/configuration/Config";
@@ -171,6 +176,9 @@ export class ClientGameRunner {
showErrorModal(gu.errMsg, gu.stack, this.clientID);
return;
}
gu.updates[GameUpdateType.Hash].forEach((hu: HashUpdate) => {
this.eventBus.emit(new SendHashEvent(hu.tick, hu.hash));
});
this.gameView.update(gu);
this.renderer.tick();
});
+29
View File
@@ -9,6 +9,7 @@ import {
Player,
PlayerID,
PlayerType,
Tick,
UnitType,
} from "../core/game/Game";
import {
@@ -23,6 +24,7 @@ import {
GameConfig,
ClientLogMessageSchema,
ClientSendWinnerSchema,
ClientMessageSchema,
} from "../core/Schemas";
import { LobbyConfig } from "./ClientGameRunner";
import { LocalServer } from "./LocalServer";
@@ -107,6 +109,12 @@ export class SendSetTargetTroopRatioEvent implements GameEvent {
export class SendWinnerEvent implements GameEvent {
constructor(public readonly winner: ClientID) {}
}
export class SendHashEvent implements GameEvent {
constructor(
public readonly tick: Tick,
public readonly hash: number,
) {}
}
export class Transport {
private socket: WebSocket;
@@ -159,6 +167,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));
this.eventBus.on(SendHashEvent, (e) => this.onSendHashEvent(e));
}
private startPing() {
@@ -448,6 +457,26 @@ export class Transport {
}
}
private onSendHashEvent(event: SendHashEvent) {
if (this.isLocal || this.socket.readyState === WebSocket.OPEN) {
const msg = ClientMessageSchema.parse({
type: "hash",
clientID: this.lobbyConfig.clientID,
persistentID: this.lobbyConfig.persistentID,
gameID: this.lobbyConfig.gameID,
tick: event.tick,
hash: event.hash,
});
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({
+1 -1
View File
@@ -63,7 +63,7 @@ export class EventsDisplay extends LitElement implements Layer {
],
[GameUpdateType.BrokeAlliance, (u) => this.onBrokeAllianceEvent(u)],
[GameUpdateType.TargetPlayer, (u) => this.onTargetPlayerEvent(u)],
[GameUpdateType.EmojiUpdate, (u) => this.onEmojiMessageEvent(u)],
[GameUpdateType.Emoji, (u) => this.onEmojiMessageEvent(u)],
]);
constructor() {
+1 -1
View File
@@ -107,7 +107,7 @@ export class OptionsMenu extends LitElement implements Layer {
tick() {
this.hasWinner =
this.hasWinner ||
this.game.updatesSinceLastTick()[GameUpdateType.WinUpdate].length > 0;
this.game.updatesSinceLastTick()[GameUpdateType.Win].length > 0;
if (this.game.inSpawnPhase()) {
this.timer = 0;
} else if (!this.hasWinner && this.game.ticks() % 10 == 0) {
+1 -1
View File
@@ -208,7 +208,7 @@ export class WinModal extends LitElement implements Layer {
}
this.show();
}
this.game.updatesSinceLastTick()[GameUpdateType.WinUpdate].forEach((wu) => {
this.game.updatesSinceLastTick()[GameUpdateType.Win].forEach((wu) => {
const winner = this.game.playerBySmallID(wu.winnerID) as PlayerView;
this.eventBus.emit(new SendWinnerEvent(winner.clientID()));
if (winner == this.game.myPlayer()) {
+11 -2
View File
@@ -48,7 +48,8 @@ export type ClientMessage =
| ClientPingMessage
| ClientIntentMessage
| ClientJoinMessage
| ClientLogMessage;
| ClientLogMessage
| ClientHashMessage;
export type ServerMessage =
| ServerSyncMessage
| ServerStartGameMessage
@@ -65,6 +66,7 @@ export type ClientPingMessage = z.infer<typeof ClientPingMessageSchema>;
export type ClientIntentMessage = z.infer<typeof ClientIntentMessageSchema>;
export type ClientJoinMessage = z.infer<typeof ClientJoinMessageSchema>;
export type ClientLogMessage = z.infer<typeof ClientLogMessageSchema>;
export type ClientHashMessage = z.infer<typeof ClientHashSchema>;
export type PlayerRecord = z.infer<typeof PlayerRecordSchema>;
export type GameRecord = z.infer<typeof GameRecordSchema>;
@@ -268,7 +270,7 @@ export const ServerMessageSchema = z.union([
// Client
const ClientBaseMessageSchema = z.object({
type: z.enum(["winner", "join", "intent", "ping", "log"]),
type: z.enum(["winner", "join", "intent", "ping", "log", "hash"]),
clientID: ID,
persistentID: SafeString.nullable(), // WARNING: persistent id is private.
gameID: ID,
@@ -279,6 +281,12 @@ export const ClientSendWinnerSchema = ClientBaseMessageSchema.extend({
winner: ID.nullable(),
});
export const ClientHashSchema = ClientBaseMessageSchema.extend({
type: z.literal("hash"),
hash: z.number(),
tick: z.number(),
});
export const ClientLogMessageSchema = ClientBaseMessageSchema.extend({
type: z.literal("log"),
severity: z.nativeEnum(LogSeverity),
@@ -308,6 +316,7 @@ export const ClientMessageSchema = z.union([
ClientIntentMessageSchema,
ClientJoinMessageSchema,
ClientLogMessageSchema,
ClientHashSchema,
]);
export const PlayerRecordSchema = z.object({
+18 -10
View File
@@ -241,21 +241,29 @@ export class GameImpl implements Game {
this.execs.push(...inited);
this.unInitExecs = unInited;
this._ticks++;
if (this._ticks % 100 == 0) {
let hash = 1;
this._players.forEach((p) => {
hash += p.hash();
});
consolex.log(`tick ${this._ticks}: hash ${hash}`);
}
for (const player of this._players.values()) {
// Players change each to so always add them
this.addUpdate(player.toUpdate());
}
if (this.ticks() % 10 == 0) {
this.addUpdate({
type: GameUpdateType.Hash,
tick: this.ticks(),
hash: this.hash(),
});
}
this._ticks++;
return this.updates;
}
private hash(): number {
let hash = 1;
this._players.forEach((p) => {
hash += p.hash();
});
return hash;
}
terraNullius(): TerraNullius {
return this._terraNullius;
}
@@ -494,14 +502,14 @@ export class GameImpl implements Game {
sendEmojiUpdate(msg: EmojiMessage): void {
this.addUpdate({
type: GameUpdateType.EmojiUpdate,
type: GameUpdateType.Emoji,
emoji: msg,
});
}
setWinner(winner: Player): void {
this.addUpdate({
type: GameUpdateType.WinUpdate,
type: GameUpdateType.Win,
winnerID: winner.smallID(),
});
}
+13 -5
View File
@@ -34,8 +34,9 @@ export enum GameUpdateType {
BrokeAlliance,
AllianceExpired,
TargetPlayer,
EmojiUpdate,
WinUpdate,
Emoji,
Win,
Hash,
}
export type GameUpdate =
@@ -49,7 +50,8 @@ export type GameUpdate =
| DisplayMessageUpdate
| TargetPlayerUpdate
| EmojiUpdate
| WinUpdate;
| WinUpdate
| HashUpdate;
export interface TileUpdateWrapper {
type: GameUpdateType.Tile;
@@ -133,7 +135,7 @@ export interface TargetPlayerUpdate {
}
export interface EmojiUpdate {
type: GameUpdateType.EmojiUpdate;
type: GameUpdateType.Emoji;
emoji: EmojiMessage;
}
@@ -145,6 +147,12 @@ export interface DisplayMessageUpdate {
}
export interface WinUpdate {
type: GameUpdateType.WinUpdate;
type: GameUpdateType.Win;
winnerID: number;
}
export interface HashUpdate {
type: GameUpdateType.Hash;
tick: Tick;
hash: number;
}
+1 -1
View File
@@ -135,7 +135,7 @@ export class UnitImpl implements Unit {
}
hash(): number {
return this.tile() + simpleHash(this.type());
return this.tile() + simpleHash(this.type()) * this._id;
}
toString(): string {
+3
View File
@@ -1,9 +1,12 @@
import WebSocket from "ws";
import { ClientID } from "../core/Schemas";
import { Tick } from "../core/game/Game";
export class Client {
public lastPing: number;
public hashes: Map<Tick, number> = new Map();
constructor(
public readonly clientID: ClientID,
public readonly persistentID: string,
+6 -3
View File
@@ -3,11 +3,8 @@ import {
ClientMessage,
ClientMessageSchema,
GameConfig,
GameRecordSchema,
Intent,
PlayerRecord,
ServerPingMessageSchema,
ServerStartGameMessage,
ServerStartGameMessageSchema,
ServerTurnMessageSchema,
Turn,
@@ -162,6 +159,12 @@ export class GameServer {
this.lastPingUpdate = Date.now();
client.lastPing = Date.now();
}
if (clientMsg.type == "hash") {
console.log(
`client ${clientMsg.clientID} got hash ${clientMsg.hash} on tick ${clientMsg.tick}`,
);
client.hashes.set(clientMsg.tick, clientMsg.hash);
}
if (clientMsg.type == "winner") {
this.winner = clientMsg.winner;
}