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 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 5x 5x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1x 1x 1x 1x 2x 1x 1x 1x 1x 2x 1x 1x 1x 1x 1x 1x 1x 3x 3x 3x 3x 3x 1x 1x | import {Injectable, computed, inject, signal} from '@angular/core'; import {Game} from "../../models/game"; import {Team} from "../../models/team"; import {JobScheduler} from "../job-scheduler/job-scheduler.service"; import { Role } from '../../models/role'; /** * This service exposes the state of the application in the form of signals. * Some signals are settings for the simulation and can be set. * Others are computed signals and provide statistics about the games already simulated. * A background process continuosly simulates games and update the state. */ @Injectable({ providedIn: 'root' }) export class GameSimulator { scheduler = inject(JobScheduler); constructor() { this.scheduler.addJob(this.backgroundJob); } /** The game setup that is being simulated. Simulations restart when this is modified. */ game = signal(new Game(10, [ {role: Role.WEREWOLF, count: 2}, {role: Role.GUARD, count: 2}, {role: Role.HEALER, count: 1}, ])); /** All the games simulated. They have ended with a winner, number of days, etc. */ results = signal<Game[]>([]); /** @readonly The percent of games won by the Village team */ winRate = computed(() => { const results = this.results(); const wins = results.filter(game => game.winner == Team.VILLAGE).length; return wins / results.length; }) /** * @readonly * More detailed stats for the games simulated. * The map keys are the number of days that the game lasted. * The values are the percentage of games won by each of the teams. */ stats = computed(() => { const results = this.results(); const totalGames = results.length; let longestGame = 0; let shortestGame = Infinity; const winCounts = new Map<number, Map<Team, number>>(); for (const game of results) { const duration = game.days; if (duration > longestGame) longestGame = duration; if (duration < shortestGame) shortestGame = duration; const count = winCounts.get(duration) || new Map<Team, number>(); const team = game.winner; if (team === undefined) continue; const wins = count.get(team) || 0; count.set(team, wins + 1); winCounts.set(duration, count); } const stats = new Map<number, Map<Team, number>>(); for (let days = shortestGame; days <= longestGame; days++) { const villageWinCount = winCounts.get(days)?.get(Team.VILLAGE) || 0; const wolvesWinCount = winCounts.get(days)?.get(Team.WOLVES) || 0; const villageWinRate = villageWinCount / totalGames; const wolvesWinRate = wolvesWinCount / totalGames; stats.set(days, new Map([[Team.VILLAGE,villageWinRate],[Team.WOLVES,wolvesWinRate]])); } return stats; }) /** @readonly The number of players in the settings */ countPlayers = computed(() => { return this.game().players; }); /** @readonly The number of players that are against the village team */ countEvil = computed(() => { return this.game().teamCount(Team.WOLVES); }); /** Restarts the simulation with new game settings, erasing all statistics */ restart(game: Game) { this.game.set(game); this.results.set([]); } /** Job to run using the job scheduler */ backgroundJob = () => { if (this.results().length >= 10000) return; const game = this.game().clone(); while (!game.ended) game.advance(); this.results.update(results => [...results, game]); } } |