serverplace / src /managers /BotManager.ts
3v324v23's picture
Update Server: Fix Vote Bug, Memory Leak, and Role Reveal
05d8628
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;
}
}