record games in gcs

This commit is contained in:
Evan
2024-12-07 18:30:04 -08:00
parent 2d2df14ae3
commit 7669105a1b
17 changed files with 131 additions and 132 deletions
+13
View File
@@ -0,0 +1,13 @@
import { GameConfig, GameID, GameRecord, GameRecordSchema, Turn } from "../core/Schemas";
import { Storage } from '@google-cloud/storage';
const storage = new Storage();
export async function archive(gameRecord: GameRecord) {
console.log(`writing game ${gameRecord.id} to gcs`)
const bucket = storage.bucket("openfront-games");
const file = bucket.file(gameRecord.id);
await file.save(JSON.stringify(GameRecordSchema.parse(gameRecord)), {
contentType: 'application/json'
});
}
+3 -14
View File
@@ -4,6 +4,7 @@ import { v4 as uuidv4 } from 'uuid';
import { Client } from "./Client";
import { GamePhase, GameServer } from "./GameServer";
import { Difficulty, GameMap, GameType } from "../core/game/Game";
import { generateGameID } from "../core/Util";
@@ -38,7 +39,7 @@ export class GameManager {
}
createPrivateGame(): string {
const id = genSmallGameID()
const id = generateGameID()
this.games.push(new GameServer(
id,
Date.now(),
@@ -77,9 +78,8 @@ export class GameManager {
const now = Date.now()
if (now > this.lastNewLobby + this.config.gameCreationRate()) {
this.lastNewLobby = now
const id = uuidv4()
lobbies.push(new GameServer(
id,
generateGameID(),
now,
true,
this.config,
@@ -97,15 +97,4 @@ export class GameManager {
finished.map(g => g.endGame()); // Fire and forget
this.games = [...lobbies, ...active]
}
}
function genSmallGameID(): string {
// Generate a UUID
const uuid: string = uuidv4();
// Convert UUID to base64
const base64: string = btoa(uuid);
// Take the first 4 characters of the base64 string
return base64.slice(0, 4);
}
+11 -18
View File
@@ -4,9 +4,10 @@ import { Client } from "./Client";
import WebSocket from 'ws';
import { slog } from "./StructuredLog";
import { Storage } from '@google-cloud/storage';
import { ProcessGameRecord as ProcessRecord } from "../core/Util";
import { CreateGameRecord, CreateGameRecord as ProcessRecord } from "../core/Util";
import { archive } from "./Archive";
import { arc } from "d3";
const storage = new Storage();
export enum GamePhase {
Lobby = 'LOBBY',
@@ -90,7 +91,12 @@ export class GameServer {
}
public startTime(): number {
return this._startTime
if (this._startTime > 0) {
return this._startTime
} else {
//game hasn't started yet, only works for public games
return this.createdAt + this.config.lobbyLifetime()
}
}
public start() {
@@ -150,21 +156,8 @@ export class GameServer {
console.log(`ending game ${this.id} with ${this.turns.length} turns`)
try {
if (this.turns.length > 100) {
console.log(`writing game ${this.id} to gcs`)
const bucket = storage.bucket(this.config.gameStorageBucketName());
const file = bucket.file(this.id);
const game = {
id: this.id,
gameConfig: this.gameConfig,
startTimestampMS: this._startTime,
endTimestampMS: Date.now(),
date: new Date().toISOString().split('T')[0],
turns: this.turns
}
const processed = ProcessRecord(game)
await file.save(JSON.stringify(GameRecordSchema.parse(processed)), {
contentType: 'application/json'
});
const record = CreateGameRecord(this.id, this.gameConfig, this.turns, this._startTime, Date.now())
archive(record)
}
} catch (error) {
console.log('error writing game to gcs: ' + error)
+8 -54
View File
@@ -9,9 +9,7 @@ import { getConfig } from '../core/configuration/Config';
import { LogSeverity, slog } from './StructuredLog';
import { Client } from './Client';
import { GamePhase, GameServer } from './GameServer';
import { v4 as uuidv4 } from 'uuid';
import { z } from 'zod';
import { ProcessGameRecord } from '../core/Util';
import { archive } from './Archive';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -25,7 +23,6 @@ app.use(express.static(path.join(__dirname, '../../out')));
app.use(express.json())
const gm = new GameManager(getConfig())
const privateGames = new Map<string, GameRecord>()
// New GET endpoint to list lobbies
app.get('/lobbies', (req, res) => {
@@ -46,66 +43,23 @@ app.post('/private_lobby', (req, res) => {
});
});
app.post('/new_private_game_record', (req, res) => {
app.post('/archive_singleplayer_game', (req, res) => {
try {
// Validate the complete game record sent by client
const gameRecord = GameRecordSchema.parse(req.body);
privateGames.set(gameRecord.id, gameRecord);
slog('new_private_game_record', 'Created new private game record', { id: gameRecord.id }, LogSeverity.DEBUG);
res.json({ id: gameRecord.id });
} catch (error) {
slog('new_private_game_record', 'Failed to create new private game record', { error }, LogSeverity.ERROR);
res.status(400).json({ error: 'Invalid game record format' });
}
});
app.put('/add_single_player_game_turn', (req, res) => {
const { gameId, turns } = req.body;
try {
const gameRecord = privateGames.get(gameId);
const gameRecord = req.body
if (!gameRecord) {
console.log('game record not found in request')
res.status(404).json({ error: 'Game record not found' });
return;
}
// Validate the array of turns
const validatedTurns = z.array(TurnSchema).parse(turns);
// Add the turns to the game record's turns
gameRecord.turns.push(...validatedTurns);
privateGames.set(gameId, gameRecord);
res.json({ success: true, numTurns: validatedTurns.length });
} catch (error) {
slog('add_single_player_game_turn', 'Failed to add turns', { error, gameId }, LogSeverity.ERROR);
res.status(400).json({ error: 'Invalid turns format' });
}
});
app.put('/complete_single_player_game_record/:id', (req, res) => {
const gameId = req.params.id;
try {
let gameRecord = privateGames.get(gameId);
if (!gameRecord) {
res.status(404).json({ error: 'Game record not found' });
return;
}
gameRecord.endTimestampMS = Date.now();
gameRecord = ProcessGameRecord(gameRecord)
// TODO: send to gcs
GameRecordSchema.parse(gameRecord);
console.log(`archiving singleplayer game ${gameRecord.id}`)
archive(gameRecord)
res.json({
success: true,
durationSeconds: gameRecord.durationSeconds
});
} catch (error) {
slog('complete_single_player_game_record', 'Failed to complete game record', { error, gameId }, LogSeverity.ERROR);
slog('complete_single_player_game_record', 'Failed to complete game record', { error }, LogSeverity.ERROR);
res.status(400).json({ error: 'Invalid game record format' });
}
})