import { Room, Player, Role } from '../types'; export class BotManager { // Suspect scores: TargetID -> Score (High = Mafia Suspect) private static suspicionScores: Record> = {}; public static decideAction(room: Room, bot: Player): { action: string, targetId?: string, targetA?: string, targetB?: string } { const alivePlayers = room.players.filter(p => p.isAlive && p.id !== bot.id); const teammates = room.players.filter(p => p.role === 'MAFIA' && p.id !== bot.id); this.initializeScores(room.id, room.players); this.analyzeHistory(room); // PHASE LOGIC if (room.phase === 'NIGHT') { return this.handleNightAction(room, bot, alivePlayers, teammates); } else if (room.phase === 'DAY') { return this.handleDayAction(room, bot, alivePlayers); } else if (room.phase === 'VOTING') { return this.handleVotingAction(room, bot, alivePlayers); } return { action: '' }; } public static cleanup(roomId: string) { delete this.suspicionScores[roomId]; } private static handleNightAction(room: Room, bot: Player, alive: Player[], teammates: Player[]) { // Shuffle to avoid bias when scores are equal const shuffledAlive = [...alive].sort(() => Math.random() - 0.5); switch (bot.role) { case 'MAFIA': const targets = shuffledAlive.filter(p => p.role !== 'MAFIA'); const victim = targets.sort((a, b) => this.getScore(room.id, a.id) - this.getScore(room.id, b.id))[0]; return { action: 'KILL', targetId: victim?.id }; case 'DOCTOR': const toSave = [...shuffledAlive].sort((a, b) => this.getScore(room.id, b.id) - this.getScore(room.id, a.id))[0]; return { action: 'SAVE', targetId: toSave?.id }; case 'DETECTIVE': const suspect = shuffledAlive.sort((a, b) => this.getScore(room.id, b.id) - this.getScore(room.id, a.id))[0]; return { action: 'INVESTIGATE', targetId: suspect?.id }; default: return { action: '' }; } } private static handleDayAction(room: Room, bot: Player, alive: Player[]) { if (bot.role === 'THIEF') { // Avoid stealing from high suspicion (might be mafia and kill the bot) // Steal from low suspicion (likely good roles) const target = alive.sort((a, b) => this.getScore(room.id, a.id) - this.getScore(room.id, b.id))[0]; return { action: 'STEAL', targetId: target?.id }; } if (bot.role === 'PLAYWRIGHT') { // Swap someone high suspicion with someone low suspicion const highSus = alive.sort((a, b) => this.getScore(room.id, b.id) - this.getScore(room.id, a.id))[0]; const lowSus = alive.sort((a, b) => this.getScore(room.id, a.id) - this.getScore(room.id, b.id))[0]; if (highSus && lowSus && highSus.id !== lowSus.id) { return { action: 'SWAP_VOTES', targetA: highSus.id, targetB: lowSus.id }; } } return { action: '' }; } private static handleVotingAction(room: Room, bot: Player, alive: Player[]) { // Standard Analysis: Vote for highest suspicion const shuffledAlive = [...alive].sort(() => Math.random() - 0.5); const target = shuffledAlive.sort((a, b) => this.getScore(room.id, b.id) - this.getScore(room.id, a.id))[0]; return { action: 'VOTE', targetId: target?.id }; } private static initializeScores(roomId: string, players: Player[]) { if (!this.suspicionScores[roomId]) { this.suspicionScores[roomId] = {}; players.forEach(p => this.suspicionScores[roomId][p.id] = 50); } } private static analyzeHistory(room: Room) { const scores = this.suspicionScores[room.id]; room.history.forEach(log => { // ANALYSIS: Thief Action if (log.action === 'STEAL') { if (log.result === 'THIEF_DIED_BY_MAFIA') { scores[log.targetId!] = 100; // Confirmed Mafia } else if (log.result === 'ROLE_STOLEN') { scores[log.targetId!] -= 10; // Probably not Mafia } } // ANALYSIS: Detective Success if (log.action === 'INVESTIGATE' && log.result === 'MAFIA') { scores[log.targetId!] += 40; } // ANALYSIS: Mafia Target (Trust Logic) if (log.action === 'KILL') { // If someone was targeted by Mafia (even if they survived), they are Pro-Town scores[log.targetId!] = 0; } // ANALYSIS: Vote Swaps if (log.action === 'VOTES_SWAPPED') { // Bots become wary of targets involved in swaps const [tA, tB] = log.targetId!.split(','); scores[tA] += 5; scores[tB] += 5; } }); } private static getScore(roomId: string, playerId: string): number { return this.suspicionScores[roomId]?.[playerId] || 50; } }