feat: Enhance draft system with 4-player 'pick 2' rules, minimum player count, and fix pack duplication by ensuring unique pack instances.
All checks were successful
Build and Deploy / build (push) Successful in 1m25s

This commit is contained in:
2025-12-16 18:41:43 +01:00
parent 58641b34a5
commit 6163869a17
6 changed files with 90 additions and 5 deletions

View File

@@ -85,8 +85,18 @@ export const GameRoom: React.FC<GameRoomProps> = ({ room: initialRoom, currentPl
const handleDraftUpdate = (data: any) => {
setDraftState(data);
};
const handleDraftError = (error: { message: string }) => {
alert(error.message); // Simple alert for now
};
socket.on('draft_update', handleDraftUpdate);
return () => { socket.off('draft_update', handleDraftUpdate); };
socket.on('draft_error', handleDraftError);
return () => {
socket.off('draft_update', handleDraftUpdate);
socket.off('draft_error', handleDraftError);
};
}, []);
const sendMessage = (e: React.FormEvent) => {

View File

@@ -87,7 +87,13 @@ io.on('connection', (socket) => {
// 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);
if (room) {
socket.emit('room_update', room);
if (room.status === 'drafting') {
const draft = draftManager.getDraft(roomId);
if (draft) socket.emit('draft_update', draft);
}
}
});
socket.on('send_message', ({ roomId, sender, text }) => {
@@ -100,6 +106,13 @@ io.on('connection', (socket) => {
socket.on('start_draft', ({ roomId }) => {
const room = roomManager.getRoom(roomId);
if (room && room.status === 'waiting') {
const activePlayers = room.players.filter(p => p.role === 'player');
if (activePlayers.length < 4) {
// Emit error to the host or room
socket.emit('draft_error', { message: 'Draft cannot start. It requires at least 4 players.' });
return;
}
// Create Draft
// All packs in room.packs need to be flat list or handled
// room.packs is currently JSON.

View File

@@ -27,6 +27,7 @@ interface DraftState {
pool: Card[]; // Picked cards
unopenedPacks: Pack[]; // Pack 2 and 3 kept aside
isWaiting: boolean; // True if finished current pack round
pickedInCurrentStep: number; // HOW MANY CARDS PICKED FROM CURRENT ACTIVE PACK
}>;
status: 'drafting' | 'deck_building' | 'complete';
@@ -40,8 +41,16 @@ export class DraftManager extends EventEmitter {
// Distribute 3 packs to each player
// Assume allPacks contains (3 * numPlayers) packs
// Shuffle packs just in case (optional, but good practice)
const shuffledPacks = [...allPacks].sort(() => Math.random() - 0.5);
// DEEP CLONE PACKS to ensure no shared references
// And assign unique internal IDs to avoid collisions
const sanitizedPacks = allPacks.map((p, idx) => ({
...p,
id: `draft-pack-${idx}-${Math.random().toString(36).substr(2, 5)}`,
cards: p.cards.map(c => ({ ...c })) // Shallow clone cards to protect against mutation if needed
}));
// Shuffle packs
const shuffledPacks = sanitizedPacks.sort(() => Math.random() - 0.5);
const draftState: DraftState = {
roomId,
@@ -62,7 +71,8 @@ export class DraftManager extends EventEmitter {
activePack: firstPack || null,
pool: [],
unopenedPacks: playerPacks,
isWaiting: false
isWaiting: false,
pickedInCurrentStep: 0
};
});
@@ -100,8 +110,26 @@ export class DraftManager extends EventEmitter {
// 2. Remove from pack
playerState.activePack.cards = playerState.activePack.cards.filter(c => c !== card);
// Increment pick count for this step
playerState.pickedInCurrentStep = (playerState.pickedInCurrentStep || 0) + 1;
// Determine Picks Required
// Rule: 4 players -> Pick 2. Others -> Pick 1.
const picksRequired = draft.seats.length === 4 ? 2 : 1;
// Check if we should pass the pack
// Pass if: Picked enough cards OR Pack is empty
const shouldPass = playerState.pickedInCurrentStep >= picksRequired || playerState.activePack.cards.length === 0;
if (!shouldPass) {
// Do not pass yet. Returns state so UI updates pool and removes card from view.
return draft;
}
// PASSED
const passedPack = playerState.activePack;
playerState.activePack = null;
playerState.pickedInCurrentStep = 0; // Reset for next pack
// 3. Logic for Passing or Discarding (End of Pack)
if (passedPack.cards.length > 0) {
@@ -137,6 +165,7 @@ export class DraftManager extends EventEmitter {
const p = draft.players[playerId];
if (!p.activePack && p.queue.length > 0) {
p.activePack = p.queue.shift()!;
p.pickedInCurrentStep = 0; // Reset for new pack
}
}
@@ -152,6 +181,7 @@ export class DraftManager extends EventEmitter {
const nextPack = p.unopenedPacks.shift();
if (nextPack) {
p.activePack = nextPack;
p.pickedInCurrentStep = 0; // Reset
}
});
} else {