'use strict'; const fs = require('fs'); const path = require('path'); const RandomBot = require('./RandomBot'); const LLMBot = require('./LLMBot'); const NAMES_PATH = path.join(__dirname, 'bot_names.txt'); const FALLBACK_NAMES = ['DefaultBot', 'BackupBot', 'SpareBot', 'ExtraBot']; function readNames() { try { const names = fs .readFileSync(NAMES_PATH, 'utf8') .split('\n') .map((name) => name.trim()) .filter((name) => name.length > 0); return names.length ? names : FALLBACK_NAMES; } catch (err) { console.warn(`[BotFactory] Could not read ${NAMES_PATH}: ${err.message}`); return FALLBACK_NAMES; } } class BotFactory { /** * Create a bot instance. LLM bots fall back to RandomBot when the LLM * client cannot be constructed (missing API key/config) so a game can * always start. */ static createBot(type = 'llm') { switch (String(type).toLowerCase()) { case 'random': return new RandomBot(); case 'llm': try { return new LLMBot(); } catch (err) { console.warn( `[BotFactory] LLM bot unavailable (${err.message}); using RandomBot instead.` ); return new RandomBot(); } default: throw new Error(`Unknown bot type: ${type}`); } } /** * Pick a random bot name, avoiding names already used in the room. */ static generateBotName(exclude = []) { const names = readNames(); const taken = new Set( (Array.isArray(exclude) ? exclude : [exclude]) .filter(Boolean) .map((name) => String(name).toLowerCase()) ); const available = names.filter((name) => !taken.has(name.toLowerCase())); const pool = available.length ? available : names; const pick = pool[Math.floor(Math.random() * pool.length)] || FALLBACK_NAMES[0]; // If every themed name is taken, disambiguate with a number. if (taken.has(pick.toLowerCase())) { let suffix = 2; while (taken.has(`${pick}${suffix}`.toLowerCase())) suffix += 1; return `${pick}${suffix}`; } return pick; } static getAvailableBotTypes() { return ['llm', 'random']; } } module.exports = BotFactory;