@idealic/poker-engine
Version:
Professional poker game engine and hand evaluator with built-in iterator utilities
137 lines (111 loc) • 4.57 kB
text/typescript
import express from 'express';
import * as Poker from 'poker-engine';
const app = express();
app.use(express.json());
// Database operations (stubbed)
function loadGameHand(gameId: string): Poker.Hand | null {
// TODO: Load the Hand from database by gameId
// The 'log' JSONB field contains the actions array
// const result = await db.query('SELECT * FROM games WHERE id = $1', [gameId]);
// const game = result.rows[0];
// return {
// actions: game.log, // log contains the actions array
// variant: game.variant,
// seed: game.seed,
// // ... construct other Hand properties from game metadata
// };
return null;
}
function saveGameHand(gameId: string, hand: Poker.Hand): void {
// TODO: Save the Hand to database and update timeout
// The 'log' JSONB field stores the actions array
// const game = Poker.Game.create(hand);
// const timeoutAt = new Date(Date.now() + game.timeLimit * 1000);
// await db.query('UPDATE games SET log = $1, timeout_at = $2 WHERE id = $3', [hand.actions, timeoutAt, gameId]);
}
function broadcastToPlayers(gameId: string, game: Poker.Game): void {
// TODO: Send sanitized hands to each player
// const players = getPlayersInGame(gameId);
// for (const playerId of players) {
// const sanitizedHand = Poker.Game.export(game, playerId);
// websocket.send(playerId, { gameId, hand: sanitizedHand });
// }
}
/**
* Single endpoint for poker game state management
* Follows the stateless request-response pattern described in the spec
*/
app.post('/game/poker', async (req, res) => {
try {
// Step 1: Receiving - Request body IS the Hand from the client
const clientHand: Poker.Hand = req.body;
if (!clientHand) {
return res.status(400).json({ error: 'Missing hand data' });
}
// Step 2: Loading - Load current hand from database
const serverHand = loadGameHand(clientHand.id);
if (!serverHand) {
return res.status(404).json({ error: 'Game not found' });
}
// Step 3: Merging - Merge client hand with server hand
const mergedHand = Poker.Hand.merge(serverHand, clientHand);
// Step 4: Applying - Create game (which applies all actions from the merged Hand)
let game = Poker.Game.create(mergedHand);
// Check and execute dealer actions if needed (flop, turn, river, showdown)
game = Poker.Game.advance(game);
// Step 5: Storing - Save updated hand to database
const updatedHand = Poker.Game.export(game);
saveGameHand(clientHand.id, updatedHand);
// Step 6: Sanitizing & Updating - Broadcast sanitized hands to all players
broadcastToPlayers(clientHand.id, game);
// Return the sanitized hand for the requesting player
// not strictly necessary, as typically client will receieve it in the websocket message
const responseHand = Poker.Game.export(game, Poker.Game.getAuthorPlayerIndex(game));
res.json(responseHand);
} catch (error) {
console.error('Error processing poker action:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
/**
* Check for expired game timeouts and apply fold actions
* This function should be called periodically (e.g., every second)
*/
async function checkGameTimeouts(): Promise<void> {
const expiredGames = await db.query(
'SELECT id FROM games WHERE timeout_at < NOW() AND timeout_at IS NOT NULL'
);
for (const row of expiredGames.rows) {
const gameId = row.id;
// Load the current game state
const hand = loadGameHand(gameId);
if (!hand) continue;
const game = Poker.Game.create(hand);
const activePlayerIndex = Poker.Game.getActivePlayer(game);
if (activePlayerIndex !== -1) {
// Apply fold action for the timed-out player
const foldAction = Poker.Game.act(game, 'fold', activePlayerIndex);
const updatedGame = Poker.Game.applyAction(game, foldAction);
// Check and execute dealer actions if needed
const finalGame = Poker.Game.advance(updatedGame);
// Save updated state (which also updates timeout)
const updatedHand = Poker.Game.export(finalGame);
saveGameHand(gameId, updatedHand);
// Broadcast to all players
broadcastToPlayers(gameId, finalGame);
}
}
}
/**
* Start timeout polling - runs every second
* This would typically be a separate process/service
*/
setInterval(() => {
checkGameTimeouts();
}, 1000);
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Poker game server running on port ${PORT}`);
});
export default app;