mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-13 22:38:59 +00:00
record games in gcs
This commit is contained in:
@@ -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'
|
||||
});
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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' });
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user