const express = require('express'); const db = require('../database'); const router = express.Router(); /** * Select a game using weighted random selection based on favor bias * * Bias system: * - favor_bias = 1 (favored): 3x weight * - favor_bias = 0 (neutral): 1x weight * - favor_bias = -1 (disfavored): 0.2x weight (still possible but less likely) * * Also considers pack-level bias */ function selectGameWithBias(games) { if (games.length === 0) return null; if (games.length === 1) return games[0]; // Get pack biases const packs = db.prepare('SELECT name, favor_bias FROM packs').all(); const packBiasMap = {}; packs.forEach(pack => { packBiasMap[pack.name] = pack.favor_bias || 0; }); // Calculate weights for each game const weights = games.map(game => { let weight = 1.0; // Base weight // Apply game-level bias const gameBias = game.favor_bias || 0; if (gameBias === 1) { weight *= 3.0; // Favored games are 3x more likely } else if (gameBias === -1) { weight *= 0.2; // Disfavored games are 5x less likely } // Apply pack-level bias const packBias = packBiasMap[game.pack_name] || 0; if (packBias === 1) { weight *= 2.0; // Favored packs are 2x more likely } else if (packBias === -1) { weight *= 0.3; // Disfavored packs are ~3x less likely } return weight; }); // Calculate total weight const totalWeight = weights.reduce((sum, w) => sum + w, 0); // Pick a random number between 0 and totalWeight let random = Math.random() * totalWeight; // Select game based on weighted random for (let i = 0; i < games.length; i++) { random -= weights[i]; if (random <= 0) { return games[i]; } } // Fallback (shouldn't reach here) return games[games.length - 1]; } // Pick a random game with filters and repeat avoidance router.post('/pick', (req, res) => { try { const { playerCount, drawing, length, familyFriendly, sessionId, excludePlayed } = req.body; // Build query for eligible games let query = 'SELECT * FROM games WHERE enabled = 1'; const params = []; if (playerCount) { const count = parseInt(playerCount); query += ' AND min_players <= ? AND max_players >= ?'; params.push(count, count); } if (drawing === 'only') { query += ' AND game_type = ?'; params.push('Drawing'); } else if (drawing === 'exclude') { query += ' AND (game_type != ? OR game_type IS NULL)'; params.push('Drawing'); } if (length) { if (length === 'short') { query += ' AND (length_minutes <= 15 OR length_minutes IS NULL)'; } else if (length === 'medium') { query += ' AND length_minutes > 15 AND length_minutes <= 25'; } else if (length === 'long') { query += ' AND length_minutes > 25'; } } if (familyFriendly !== undefined) { query += ' AND family_friendly = ?'; params.push(familyFriendly ? 1 : 0); } // Get eligible games const stmt = db.prepare(query); let eligibleGames = stmt.all(...params); if (eligibleGames.length === 0) { return res.status(404).json({ error: 'No games match the current filters', suggestion: 'Try adjusting your filters or enabling more games' }); } // Apply repeat avoidance if session provided if (sessionId) { let lastGamesQuery; if (excludePlayed) { // Exclude ALL previously played games in this session lastGamesQuery = db.prepare(` SELECT DISTINCT game_id FROM session_games WHERE session_id = ? `).all(sessionId); } else { // Default: only exclude last 2 games lastGamesQuery = db.prepare(` SELECT game_id FROM session_games WHERE session_id = ? ORDER BY played_at DESC LIMIT 2 `).all(sessionId); } const excludeIds = lastGamesQuery.map(g => g.game_id); if (excludeIds.length > 0) { eligibleGames = eligibleGames.filter(game => !excludeIds.includes(game.id)); } if (eligibleGames.length === 0) { return res.status(404).json({ error: excludePlayed ? 'All eligible games have been played in this session' : 'All eligible games have been played recently', suggestion: 'Enable more games or adjust your filters', recentlyPlayed: excludeIds }); } } // Apply favor biasing to selection const selectedGame = selectGameWithBias(eligibleGames); res.json({ game: selectedGame, poolSize: eligibleGames.length, totalEnabled: eligibleGames.length + (sessionId ? 2 : 0) // Approximate }); } catch (error) { res.status(500).json({ error: error.message }); } }); module.exports = router;