File size: 4,759 Bytes
05d8628
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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;
  }
}