mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-26 09:54:36 +00:00
First Commit
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
import {ClientID} from "../core/Game";
|
||||
import WebSocket from 'ws';
|
||||
|
||||
|
||||
export class Client {
|
||||
constructor(public readonly id: ClientID, public readonly ws: WebSocket) { }
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import {GameID, LobbyID} from "../core/Game";
|
||||
import {Client} from "./Client";
|
||||
import {Lobby} from "./Lobby";
|
||||
import {GameServer} from "./GameServer";
|
||||
import {defaultSettings, Settings} from "../core/Settings";
|
||||
import {generateUniqueID} from "../core/Util";
|
||||
|
||||
export class GameManager {
|
||||
|
||||
private lastNewLobby: number = 0
|
||||
|
||||
private _lobbies: Map<LobbyID, Lobby> = new Map()
|
||||
|
||||
private games: Map<GameID, GameServer> = new Map()
|
||||
|
||||
constructor(private settings: Settings) { }
|
||||
|
||||
|
||||
public hasLobby(lobbyID: LobbyID): boolean {
|
||||
return this._lobbies.has(lobbyID)
|
||||
}
|
||||
|
||||
public addClientToLobby(client: Client, lobbyID: LobbyID) {
|
||||
this._lobbies.get(lobbyID).addClient(client)
|
||||
}
|
||||
|
||||
addLobby(lobby: Lobby) {
|
||||
this._lobbies.set(lobby.id, lobby)
|
||||
}
|
||||
|
||||
lobby(id: LobbyID): Lobby {
|
||||
return this._lobbies.get(id)
|
||||
}
|
||||
|
||||
lobbies(): Lobby[] {
|
||||
return Array.from(this._lobbies.values())
|
||||
}
|
||||
|
||||
addGame(game: GameServer) {
|
||||
this.games.set(game.id, game)
|
||||
}
|
||||
|
||||
startGame(lobby: Lobby) {
|
||||
const gs = new GameServer(generateUniqueID(), lobby.clients, defaultSettings)
|
||||
this.games.set(gs.id, gs)
|
||||
gs.start()
|
||||
}
|
||||
|
||||
tick() {
|
||||
const now = Date.now()
|
||||
|
||||
const active = this.lobbies().filter(l => !l.isExpired(now))
|
||||
const expired = this.lobbies().filter(l => l.isExpired(now))
|
||||
this._lobbies = new Map(active.map(lobby => [lobby.id, lobby]));
|
||||
expired.forEach(lobby => {
|
||||
const game = new GameServer(generateUniqueID(), lobby.clients, this.settings)
|
||||
this.games.set(game.id, game)
|
||||
game.start()
|
||||
})
|
||||
|
||||
if (now > this.lastNewLobby + this.settings.lobbyCreationRate()) {
|
||||
this.lastNewLobby = now
|
||||
this.addLobby(new Lobby(generateUniqueID(), this.settings.lobbyLifetime()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import {EventBus} from "../core/EventBus";
|
||||
import {ClientID, GameID} from "../core/Game";
|
||||
import {ClientMessage, ClientMessageSchema, Intent, ServerStartGameMessage, ServerStartGameMessageSchema, ServerTurnMessageSchema, Turn} from "../core/Schemas";
|
||||
import {Settings} from "../core/Settings";
|
||||
import {Ticker, TickEvent} from "../core/Ticker";
|
||||
import {Client} from "./Client";
|
||||
|
||||
export class GameServer {
|
||||
|
||||
private turns: Turn[] = []
|
||||
private intents: Intent[] = []
|
||||
|
||||
constructor(
|
||||
public readonly id: GameID,
|
||||
private clients: Map<ClientID, Client>,
|
||||
private settings: Settings,
|
||||
) {
|
||||
}
|
||||
|
||||
public start() {
|
||||
this.clients.forEach(c => {
|
||||
c.ws.on('message', (message: string) => {
|
||||
const clientMsg: ClientMessage = ClientMessageSchema.parse(JSON.parse(message))
|
||||
if (clientMsg.type == "intent") {
|
||||
this.addIntent(clientMsg.intent)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
const startGame = JSON.stringify(ServerStartGameMessageSchema.parse(
|
||||
{
|
||||
type: "start"
|
||||
}
|
||||
))
|
||||
this.clients.forEach(c => {
|
||||
c.ws.send(startGame)
|
||||
})
|
||||
setInterval(() => this.endTurn(), this.settings.turnIntervalMs());
|
||||
}
|
||||
|
||||
private addIntent(intent: Intent) {
|
||||
this.intents.push(intent)
|
||||
}
|
||||
|
||||
private endTurn() {
|
||||
const pastTurn: Turn = {
|
||||
turnNumber: this.turns.length,
|
||||
intents: this.intents
|
||||
}
|
||||
this.turns.push(pastTurn)
|
||||
this.intents = []
|
||||
|
||||
const msg = JSON.stringify(ServerTurnMessageSchema.parse(
|
||||
{
|
||||
type: "turn",
|
||||
turn: pastTurn
|
||||
}
|
||||
))
|
||||
this.clients.forEach(c => {
|
||||
c.ws.send(msg)
|
||||
})
|
||||
}
|
||||
|
||||
private tick(event: TickEvent) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import {ClientID} from "../core/Game";
|
||||
import {Client} from "./Client";
|
||||
|
||||
export class Lobby {
|
||||
|
||||
public clients: Map<ClientID, Client> = new Map()
|
||||
private startGameTs: number
|
||||
|
||||
|
||||
constructor(public readonly id: string, durationMs: number) {
|
||||
this.startGameTs = Date.now() + durationMs
|
||||
}
|
||||
|
||||
public addClient(client: Client) {
|
||||
this.clients.set(client.id, client)
|
||||
}
|
||||
|
||||
public isExpired(now: number): boolean {
|
||||
return now > this.startGameTs
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import express, {json} from 'express';
|
||||
import http from 'http';
|
||||
import {WebSocketServer} from 'ws';
|
||||
import path from 'path';
|
||||
import {fileURLToPath} from 'url';
|
||||
import {GameManager} from './GameManager';
|
||||
import {Client} from './Client';
|
||||
import {ClientMessage, ClientMessageSchema} from '../core/Schemas';
|
||||
import {Lobby} from './Lobby';
|
||||
import {defaultSettings} from '../core/Settings';
|
||||
|
||||
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const app = express();
|
||||
const server = http.createServer(app);
|
||||
const wss = new WebSocketServer({server});
|
||||
|
||||
|
||||
// Serve static files from the 'out' directory
|
||||
app.use(express.static(path.join(__dirname, '../../out')));
|
||||
app.use(express.json())
|
||||
|
||||
const gm = new GameManager(defaultSettings)
|
||||
|
||||
// New GET endpoint to list lobbies
|
||||
app.get('/lobbies', (req, res) => {
|
||||
const lobbyList = Array.from(gm.lobbies()).map(lobby => ({
|
||||
id: lobby.id,
|
||||
}));
|
||||
|
||||
res.json({
|
||||
lobbies: lobbyList,
|
||||
});
|
||||
});
|
||||
|
||||
wss.on('connection', (ws) => {
|
||||
|
||||
ws.on('message', (message: string) => {
|
||||
console.log(`got message ${message}`)
|
||||
const clientMsg: ClientMessage = ClientMessageSchema.parse(JSON.parse(message))
|
||||
if (clientMsg.type == "join") {
|
||||
if (gm.hasLobby(clientMsg.lobbyID)) {
|
||||
gm.addClientToLobby(new Client(clientMsg.clientID, ws), clientMsg.lobbyID)
|
||||
}
|
||||
}
|
||||
// TODO: send error message
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
function runGame() {
|
||||
setInterval(() => tick(), 1000);
|
||||
}
|
||||
|
||||
function tick() {
|
||||
gm.tick()
|
||||
}
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
console.log(`Server will try to run on http://localhost:${PORT}`);
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(`Server is running on http://localhost:${PORT}`);
|
||||
});
|
||||
|
||||
runGame()
|
||||
|
||||
Reference in New Issue
Block a user