load archived game if not found

This commit is contained in:
Evan
2025-02-17 19:30:30 -08:00
parent 836e7db5dc
commit 6a66d0c52d
5 changed files with 91 additions and 52 deletions
+13 -35
View File
@@ -1,46 +1,16 @@
import { Executor } from "../core/execution/ExecutionManager";
import {
Cell,
Game,
PlayerID,
GameMapType,
Difficulty,
GameType,
} from "../core/game/Game";
import { PlayerID, GameMapType, Difficulty, GameType } from "../core/game/Game";
import { EventBus } from "../core/EventBus";
import { createRenderer, GameRenderer } from "./graphics/GameRenderer";
import {
InputHandler,
MouseUpEvent,
ZoomEvent,
DragEvent,
MouseDownEvent,
} from "./InputHandler";
import {
ClientID,
ClientIntentMessageSchema,
ClientJoinMessageSchema,
ClientMessageSchema,
GameConfig,
GameID,
Intent,
ServerMessage,
ServerMessageSchema,
ServerSyncMessage,
Turn,
} from "../core/Schemas";
import {
loadTerrainFromFile,
loadTerrainMap,
} from "../core/game/TerrainMapLoader";
import { InputHandler, MouseUpEvent } from "./InputHandler";
import { ClientID, GameConfig, GameID, ServerMessage } from "../core/Schemas";
import { loadTerrainMap } from "../core/game/TerrainMapLoader";
import {
SendAttackIntentEvent,
SendSpawnIntentEvent,
Transport,
} from "./Transport";
import { createCanvas } from "./Utils";
import { MessageType } from "../core/game/Game";
import { DisplayMessageUpdate, ErrorUpdate } from "../core/game/GameUpdates";
import { ErrorUpdate } from "../core/game/GameUpdates";
import { WorkerClient } from "../core/worker/WorkerClient";
import { consolex, initRemoteSender } from "../core/Consolex";
import { getConfig, getServerConfig } from "../core/configuration/Config";
@@ -219,6 +189,14 @@ export class ClientGameRunner {
if (turn.turnNumber < this.turnsSeen) {
continue;
}
while (turn.turnNumber - 1 > this.turnsSeen) {
this.worker.sendTurn({
turnNumber: this.turnsSeen,
gameID: turn.gameID,
intents: [],
});
this.turnsSeen++;
}
this.worker.sendTurn(turn);
this.turnsSeen++;
}
+4 -4
View File
@@ -121,15 +121,15 @@ export class PlayerExecution implements Execution {
private surroundedBySamePlayer(cluster: Set<TileRef>): false | Player {
const enemies = new Set<number>();
for (const ref of cluster) {
for (const tile of cluster) {
if (
this.mg.isOceanShore(ref) ||
this.mg.neighbors(ref).some((n) => !this.mg.hasOwner(n))
this.mg.isOceanShore(tile) ||
this.mg.neighbors(tile).some((n) => !this.mg.hasOwner(n))
) {
return false;
}
this.mg
.neighbors(ref)
.neighbors(tile)
.filter((n) => this.mg.ownerID(n) != this.player.smallID())
.forEach((p) => enemies.add(this.mg.ownerID(p)));
if (enemies.size != 1) {
+46
View File
@@ -128,6 +128,52 @@ async function archiveToGCS(gameRecord: GameRecord) {
console.log(`${gameRecord.id}: game record successfully written to GCS`);
}
export async function readGameRecord(gameId: GameID): Promise<GameRecord> {
try {
const file = bucket.file(gameId);
// Check if file exists
const [exists] = await file.exists();
if (!exists) {
throw new Error(`Game record ${gameId} not found in GCS`);
}
// Download and parse file content
const [content] = await file.download();
const gameRecord = JSON.parse(content.toString());
// Validate the parsed content against the schema
const validatedRecord = GameRecordSchema.parse(gameRecord);
console.log(`${gameId}: Successfully read game record from GCS`);
return validatedRecord;
} catch (error) {
console.error(`${gameId}: Error reading game record from GCS: ${error}`, {
message: error?.message || error,
stack: error?.stack,
name: error?.name,
...(error && typeof error === "object" ? error : {}),
});
throw error;
}
}
export async function gameRecordExists(gameId: GameID): Promise<boolean> {
try {
const file = bucket.file(gameId);
const [exists] = await file.exists();
return exists;
} catch (error) {
console.error(`${gameId}: Error checking archive existence: ${error}`, {
message: error?.message || error,
stack: error?.stack,
name: error?.name,
...(error && typeof error === "object" ? error : {}),
});
return false;
}
}
function anonymizeIPv4(ipv4: string): string | null {
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
+5 -5
View File
@@ -24,13 +24,13 @@ export class GameManager {
return this.games.filter((g) => g.phase() == phase);
}
addClient(client: Client, gameID: GameID, lastTurn: number) {
addClient(client: Client, gameID: GameID, lastTurn: number): boolean {
const game = this.games.find((g) => g.id == gameID);
if (!game) {
console.log(`game id ${gameID} not found`);
return;
if (game) {
game.addClient(client, lastTurn);
return true;
}
game.addClient(client, lastTurn);
return false;
}
updateGameConfig(gameID: GameID, gameConfig: GameConfig) {
+23 -8
View File
@@ -10,6 +10,7 @@ import {
GameRecord,
GameRecordSchema,
LogSeverity,
ServerStartGameMessageSchema,
} from "../core/Schemas";
import {
GameEnv,
@@ -19,7 +20,7 @@ import {
import { slog } from "./StructuredLog";
import { Client } from "./Client";
import { GamePhase, GameServer } from "./GameServer";
import { archive } from "./Archive";
import { archive, gameRecordExists, readGameRecord } from "./Archive";
import { DiscordBot } from "./DiscordBot";
import {
sanitizeUsername,
@@ -176,13 +177,14 @@ app.put("/private_lobby/:id", (req, res) => {
});
});
app.get("/lobby/:id/exists", (req, res) => {
app.get("/lobby/:id/exists", async (req, res) => {
const lobbyId = req.params.id;
console.log(`checking lobby ${lobbyId} exists`);
const lobbyExists = gm.hasActiveGame(lobbyId);
let gameExists = gm.hasActiveGame(lobbyId);
if (!gameExists) {
gameExists = await gameRecordExists(lobbyId);
}
res.json({
exists: lobbyExists,
exists: gameExists,
});
});
@@ -212,7 +214,7 @@ app.get("*", function (req, res) {
});
wss.on("connection", (ws, req) => {
ws.on("message", (message: string) => {
ws.on("message", async (message: string) => {
try {
const clientMsg: ClientMessage = ClientMessageSchema.parse(
JSON.parse(message),
@@ -233,7 +235,7 @@ wss.on("connection", (ws, req) => {
return;
}
clientMsg.username = sanitizeUsername(clientMsg.username);
gm.addClient(
const wasFound = gm.addClient(
new Client(
clientMsg.clientID,
clientMsg.persistentID,
@@ -244,6 +246,19 @@ wss.on("connection", (ws, req) => {
clientMsg.gameID,
clientMsg.lastTurn,
);
if (!wasFound) {
console.log(`game ${clientMsg.gameID} not found, loading from gcs`);
const record = await readGameRecord(clientMsg.gameID);
ws.send(
JSON.stringify(
ServerStartGameMessageSchema.parse({
type: "start",
turns: record.turns,
config: record.gameConfig,
}),
),
);
}
}
if (clientMsg.type == "log") {
slog({