Spaces:
Sleeping
Sleeping
| import { Room, Player, Role } from '../types'; | |
| export class BotManager { | |
| // Suspect scores: TargetID -> Score (High = Mafia Suspect) | |
| private static suspicionScores: Record<string, Record<string, number>> = {}; | |
| 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; | |
| } | |
| } | |