Press n or j to go to the next uncovered block, b, p or k for the previous block.
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 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 104x 104x 104x 104x 19x 19x 104x 16x 16x 104x 8x 8x 104x 1x 1x 1x 420x 420x 1x 1x 1x 433x 433x 95x 433x 338x 433x 433x 1x 1x 1x 51x 51x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 145x 145x 145x 119x 119x 13x 119x 8x 106x 98x 98x 98x 119x 119x 145x 145x 1x 1x 1x 23x 23x 1x 1x 1x 98x 98x 98x 98x 54x 54x 48x 33x 33x 48x 21x 21x 65x 65x 1x 1x 1x 13x 13x 13x 13x 13x 1x 1x 1x 8x 8x 8x 8x 8x 1x 1x 1x 35x 35x 1x 1x 1x 1x 1x 1x 1x | import {Role} from "./role"; import {Team} from "./team"; import {Place} from "./place"; import {Phase} from "./phase"; import {Action} from "./action"; import {Chat} from "./chat"; /** * Represents the state of a player and the information they know. * Their initial role is assigned in the constructor, and it determines * their behavior in each phase of the game. */ export class Player { public place = Place.VILLAGE; private _actions = new Map<Phase, Action[]>; private _friends: Player[] = []; private _scouted: Player[] = []; private _chats: Chat[] = []; constructor( public role: Role ) { this._actions.set(Phase.DAY, [Action.VOTE]); if (this.team == Team.WOLVES) { this._actions.set(Phase.NIGHT, [Action.ATTACK]); } if (this.role == Role.GUARD) { this._actions.set(Phase.NIGHT, [Action.GUARD]); } if (this.role == Role.HEALER) { this._actions.set(Phase.NIGHT, [Action.HEAL]); } } /** @readonly Is alive or not */ get alive(): boolean { return this.place != Place.GRAVEYARD; } /** @readonly What side do they belong to */ get team(): Team { switch (this.role) { case Role.WEREWOLF: return Team.WOLVES; default: return Team.VILLAGE; } } /** Let this player know that other players are friends (in the same team, basically) */ friends(newFriends: Player[]) { this._friends = [...this._friends, ...newFriends]; } /** * Returns the actions that this player tries to do in each phase of the game. * For each action, they choose a target player (friend or foe, depending on the action). * - For a vote, this is the voted player. * - For group actions (like guarding) these are the player's choice, and the * group then targets the target that is mentioned more often. * - For indivisual actions like healing a player, they will target that player */ act(phase: Phase, playersAlive: Player[]): Map<Action,Player> { const actions = new Map<Action,Player>(); const legalActions = this._actions.get(phase); if (legalActions) { for (const action of legalActions) { if (action == Action.GUARD) { actions.set(action, this.pickFriendOrSelf(playersAlive)); } else if (action == Action.HEAL) { actions.set(action, this.pickFriendOrAny(playersAlive)); } else if (action == Action.SCOUT) { actions.set(action, this.pickNotScouted(playersAlive)); } else { actions.set(action, this.pickEnemy(playersAlive)); } } } return actions; } /** Sends this player to the graveyard, where they can't act or communicate */ die() { this.place = Place.GRAVEYARD; } /** Select a foe to harm from a given list of targets */ private pickEnemy(targets: Player[]): Player { const enemies = targets.filter(target => !this._friends.includes(target)); if (enemies.length === 0) return this; const randomIndex = Math.floor(Math.random() * enemies.length); if (this._chats.length > 0) { const chat = this._chats[0]; if (chat.vote) { if (enemies.includes(chat.vote)) { return chat.vote; } } chat.vote = enemies[randomIndex]; } return enemies[randomIndex]; } /** Pick a friendly player (including themselves) to help them */ private pickFriendOrSelf(targets: Player[]): Player { const friends = targets.filter(target => this._friends.includes(target)); if (friends.length === 0) return this; const randomIndex = Math.floor(Math.random() * friends.length); return friends[randomIndex]; } /** Pick a player (except themselves) to help them. Priorizes friends. */ private pickFriendOrAny(targets: Player[]): Player { const friends = targets.filter(target => this._friends.includes(target)); if (friends.length === 0) { const randomIndex = Math.floor(Math.random() * targets.length); return targets[randomIndex]; } const randomIndex = Math.floor(Math.random() * friends.length); return friends[randomIndex]; } /** Adds this player to a group chat to coordinate with other players */ chat(chat: Chat) { this._chats.push(chat); } /** Chooses next target to be scouted */ private pickNotScouted(targets: Player[]): Player { const notScouted = targets .filter(target => this != target) .filter(target => !this._scouted.includes(target)) .filter(target => !this._friends.includes(target)); if (notScouted.length == 0) return this.pickEnemy(this._scouted); const randomIndex = Math.floor(Math.random() * notScouted.length); return notScouted[randomIndex]; } /** Receives result from scouting */ scoutResult(player: Player, nightActivity: boolean) { this._scouted.push(player); if (!nightActivity) { this._friends.push(player); } } } |