feat: Implement initial multiplayer lobby and game room functionality with server-side room management.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import express, { Request, Response } from 'express';
|
||||
import { createServer } from 'http';
|
||||
import { Server } from 'socket.io';
|
||||
import { RoomManager } from './managers/RoomManager';
|
||||
|
||||
const app = express();
|
||||
const httpServer = createServer(app);
|
||||
@@ -11,6 +12,7 @@ const io = new Server(httpServer, {
|
||||
}
|
||||
});
|
||||
|
||||
const roomManager = new RoomManager();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
app.use(express.json());
|
||||
@@ -20,15 +22,68 @@ app.get('/api/health', (_req: Request, res: Response) => {
|
||||
res.json({ status: 'ok', message: 'Server is running' });
|
||||
});
|
||||
|
||||
// Socket.IO connection
|
||||
// Socket.IO logic
|
||||
io.on('connection', (socket) => {
|
||||
console.log('A user connected', socket.id);
|
||||
|
||||
socket.on('create_room', ({ hostId, hostName, packs }, callback) => {
|
||||
const room = roomManager.createRoom(hostId, hostName, packs);
|
||||
socket.join(room.id);
|
||||
console.log(`Room created: ${room.id} by ${hostName}`);
|
||||
callback({ success: true, room });
|
||||
});
|
||||
|
||||
socket.on('join_room', ({ roomId, playerId, playerName }, callback) => {
|
||||
const room = roomManager.joinRoom(roomId, playerId, playerName);
|
||||
if (room) {
|
||||
socket.join(room.id);
|
||||
console.log(`Player ${playerName} joined room ${roomId}`);
|
||||
io.to(room.id).emit('room_update', room); // Broadcast update
|
||||
callback({ success: true, room });
|
||||
} else {
|
||||
callback({ success: false, message: 'Room not found or full' });
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('rejoin_room', ({ roomId }) => {
|
||||
// Just rejoin the socket channel if validation passes (not fully secure yet)
|
||||
socket.join(roomId);
|
||||
const room = roomManager.getRoom(roomId);
|
||||
if (room) socket.emit('room_update', room);
|
||||
});
|
||||
|
||||
socket.on('send_message', ({ roomId, sender, text }) => {
|
||||
const message = roomManager.addMessage(roomId, sender, text);
|
||||
if (message) {
|
||||
io.to(roomId).emit('new_message', message);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('start_game', ({ roomId }) => {
|
||||
const room = roomManager.startGame(roomId);
|
||||
if (room) {
|
||||
io.to(roomId).emit('room_update', room);
|
||||
// Here we would also emit 'draft_state' with initial packs
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log('User disconnected', socket.id);
|
||||
// TODO: Handle player disconnect (mark as offline but don't kick immediately)
|
||||
});
|
||||
});
|
||||
|
||||
httpServer.listen(PORT, () => {
|
||||
console.log(`Server running on http://localhost:${PORT}`);
|
||||
import os from 'os';
|
||||
|
||||
httpServer.listen(Number(PORT), '0.0.0.0', () => {
|
||||
console.log(`Server running on http://0.0.0.0:${PORT}`);
|
||||
|
||||
const interfaces = os.networkInterfaces();
|
||||
for (const name of Object.keys(interfaces)) {
|
||||
for (const iface of interfaces[name]!) {
|
||||
if (iface.family === 'IPv4' && !iface.internal) {
|
||||
console.log(` - Network IP: http://${iface.address}:${PORT}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
107
src/server/managers/RoomManager.ts
Normal file
107
src/server/managers/RoomManager.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
interface Player {
|
||||
id: string;
|
||||
name: string;
|
||||
isHost: boolean;
|
||||
role: 'player' | 'spectator';
|
||||
}
|
||||
|
||||
interface ChatMessage {
|
||||
id: string;
|
||||
sender: string;
|
||||
text: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
interface Room {
|
||||
id: string;
|
||||
hostId: string;
|
||||
players: Player[];
|
||||
packs: any[]; // Store generated packs (JSON)
|
||||
status: 'waiting' | 'drafting' | 'finished';
|
||||
messages: ChatMessage[];
|
||||
maxPlayers: number;
|
||||
}
|
||||
|
||||
export class RoomManager {
|
||||
private rooms: Map<string, Room> = new Map();
|
||||
|
||||
createRoom(hostId: string, hostName: string, packs: any[]): Room {
|
||||
const roomId = Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||
const room: Room = {
|
||||
id: roomId,
|
||||
hostId,
|
||||
players: [{ id: hostId, name: hostName, isHost: true, role: 'player' }],
|
||||
packs,
|
||||
status: 'waiting',
|
||||
messages: [],
|
||||
maxPlayers: 8
|
||||
};
|
||||
this.rooms.set(roomId, room);
|
||||
return room;
|
||||
}
|
||||
|
||||
joinRoom(roomId: string, playerId: string, playerName: string): Room | null {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) return null;
|
||||
|
||||
// Rejoin if already exists
|
||||
const existingPlayer = room.players.find(p => p.id === playerId);
|
||||
if (existingPlayer) {
|
||||
return room;
|
||||
}
|
||||
|
||||
// Determine role
|
||||
let role: 'player' | 'spectator' = 'player';
|
||||
if (room.players.filter(p => p.role === 'player').length >= room.maxPlayers || room.status !== 'waiting') {
|
||||
role = 'spectator';
|
||||
}
|
||||
|
||||
room.players.push({ id: playerId, name: playerName, isHost: false, role });
|
||||
return room;
|
||||
}
|
||||
|
||||
leaveRoom(roomId: string, playerId: string): Room | null {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) return null;
|
||||
|
||||
room.players = room.players.filter(p => p.id !== playerId);
|
||||
|
||||
// If host leaves, assign new host from remaining players
|
||||
if (room.players.length === 0) {
|
||||
this.rooms.delete(roomId);
|
||||
return null;
|
||||
} else if (room.hostId === playerId) {
|
||||
const nextPlayer = room.players.find(p => p.role === 'player') || room.players[0];
|
||||
if (nextPlayer) {
|
||||
room.hostId = nextPlayer.id;
|
||||
nextPlayer.isHost = true;
|
||||
}
|
||||
}
|
||||
return room;
|
||||
}
|
||||
|
||||
startGame(roomId: string): Room | null {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) return null;
|
||||
room.status = 'drafting';
|
||||
return room;
|
||||
}
|
||||
|
||||
getRoom(roomId: string): Room | undefined {
|
||||
return this.rooms.get(roomId);
|
||||
}
|
||||
|
||||
addMessage(roomId: string, sender: string, text: string): ChatMessage | null {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) return null;
|
||||
|
||||
const message: ChatMessage = {
|
||||
id: Math.random().toString(36).substring(7),
|
||||
sender,
|
||||
text,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
room.messages.push(message);
|
||||
return message;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user