feat: Implement draft and game phases with client views, dedicated managers, and server-side card image caching.

This commit is contained in:
2025-12-14 22:23:23 +01:00
parent a2a8b33368
commit 9ff305f1ba
18 changed files with 1289 additions and 18 deletions

View File

@@ -1,27 +1,58 @@
import express, { Request, Response } from 'express';
import { createServer } from 'http';
import { Server } from 'socket.io';
import path from 'path';
import { fileURLToPath } from 'url';
import { RoomManager } from './managers/RoomManager';
import { GameManager } from './managers/GameManager';
import { DraftManager } from './managers/DraftManager';
import { CardService } from './services/CardService';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
origin: "*", // Adjust for production
origin: "*", // Adjust for production,
methods: ["GET", "POST"]
}
});
const roomManager = new RoomManager();
const gameManager = new GameManager();
const draftManager = new DraftManager();
const cardService = new CardService();
const PORT = process.env.PORT || 3000;
app.use(express.json());
app.use(express.json({ limit: '50mb' })); // Increase limit for large card lists
// Serve static images
app.use('/cards', express.static(path.join(__dirname, 'public/cards')));
// API Routes
app.get('/api/health', (_req: Request, res: Response) => {
res.json({ status: 'ok', message: 'Server is running' });
});
app.post('/api/cards/cache', async (req: Request, res: Response) => {
try {
const { cards } = req.body;
if (!cards || !Array.isArray(cards)) {
res.status(400).json({ error: 'Invalid payload' });
return;
}
console.log(`Caching images for ${cards.length} cards...`);
const count = await cardService.cacheImages(cards);
res.json({ success: true, downloaded: count });
} catch (err: any) {
console.error('Error in cache route:', err);
res.status(500).json({ error: err.message });
}
});
// Socket.IO logic
io.on('connection', (socket) => {
console.log('A user connected', socket.id);
@@ -59,11 +90,79 @@ io.on('connection', (socket) => {
}
});
socket.on('start_game', ({ roomId }) => {
socket.on('start_draft', ({ roomId }) => {
const room = roomManager.getRoom(roomId);
if (room && room.status === 'waiting') {
// Create Draft
// All packs in room.packs need to be flat list or handled
// room.packs is currently JSON.
const draft = draftManager.createDraft(roomId, room.players.map(p => p.id), room.packs);
room.status = 'drafting';
io.to(roomId).emit('room_update', room);
io.to(roomId).emit('draft_update', draft);
}
});
socket.on('pick_card', ({ roomId, cardId }) => {
// Find player from socket? Actually we trust clientId sent or inferred (but simpler to trust socket for now if we tracked map, but here just use helper?)
// We didn't store socket->player map here globally. We'll pass playerId in payload for simplicity but validation later.
// Wait, let's look at signature.. pickCard(roomId, playerId, cardId)
// Need playerId. Let's ask client to send it.
// Or we can find it if we know connection...
// Let's assume payload: { roomId, playerId, cardId }
});
// Revised pick_card to actual impl
socket.on('pick_card', ({ roomId, playerId, cardId }) => {
const draft = draftManager.pickCard(roomId, playerId, cardId);
if (draft) {
io.to(roomId).emit('draft_update', draft);
if (draft.status === 'deck_building') {
// Notify room
const room = roomManager.getRoom(roomId);
if (room) {
room.status = 'deck_building';
io.to(roomId).emit('room_update', room);
}
}
}
});
socket.on('start_game', ({ roomId, decks }) => {
const room = roomManager.startGame(roomId);
if (room) {
io.to(roomId).emit('room_update', room);
// Here we would also emit 'draft_state' with initial packs
// Initialize Game
const game = gameManager.createGame(roomId, room.players);
// If decks are provided, load them
if (decks) {
Object.entries(decks).forEach(([playerId, deck]: [string, any]) => {
// @ts-ignore
deck.forEach(card => {
gameManager.addCardToGame(roomId, {
ownerId: playerId,
controllerId: playerId,
oracleId: card.oracle_id || card.id,
name: card.name,
imageUrl: card.image_uris?.normal || card.card_faces?.[0]?.image_uris?.normal || "",
zone: 'library' // Start in library
});
});
});
}
io.to(roomId).emit('game_update', game);
}
});
socket.on('game_action', ({ roomId, action }) => {
const game = gameManager.handleAction(roomId, action);
if (game) {
io.to(roomId).emit('game_update', game);
}
});