import Engine, { Building, Command, Faction, LogEntry, Planet, Player } from "@gaia-project/engine"; import { sum } from "lodash"; import { CommandObject, parseCommands } from "../recent"; import { ChartKind } from "./chart-factory"; import { ChartGroup, ChartSource, ChartType } from "./charts"; export type ExtractChange> = ( player: Player, source: Source ) => (entry: LogEntry, logIndex: number, endScoring: boolean) => number; export enum ChartSummary { total, weightedTotal, balance, } export type SimpleSourceFactory> = { name: ChartType; group?: ChartGroup; playerSummaryLineChartTitle: string; sources: Source[]; summary: ChartSummary; initialValue?: (player: Player, source: Source) => number; extractChange?: ExtractChange; extractLog?: ExtractLog; }; export function processLogEntry( log: LogEntry, commands: CommandObject[] | null, processor: (cmd: CommandObject | null, log: LogEntry, allCommands: CommandObject[], cmdIndex: number) => number ): number { if (commands != null) { let res = 0; commands.forEach((cmd, index) => { res += processor(cmd, log, commands, index); }); return res; } else { return processor(null, log, [], 0); } } export function logEntryProcessor( processor: (cmd: CommandObject | null, log: LogEntry, allCommands: CommandObject[], cmdIndex: number) => number ): (moveHistory: string[], log: LogEntry) => number { return (moveHistory: string[], log: LogEntry): number => { if (log.move != null) { const move = moveHistory[log.move]; // current move isn't added yet if (move != null) { return processLogEntry(log, parseCommands(move), processor); } return 0; } else { return processLogEntry(log, null, processor); } }; } type ExtractLogArgProcessor = (a: ExtractLogArg) => number; type ExtractLogFunction = (p: Player, s: Source, engine: Engine) => ExtractLogArgProcessor; export type ExtractLogEntry = { extractLog: ExtractLog>; commandFilter?: Command[]; sourceTypeFilter?: any[]; factionFilter?: Faction[]; }; export class ExtractLog { private readonly fn: ExtractLogFunction; private constructor(fn: ExtractLogFunction) { this.fn = fn; } static new(fn: ExtractLogFunction): ExtractLog { return new ExtractLog(fn); } static wrapper(supplier: (p: Player, s: Source, engine: Engine) => ExtractLog): ExtractLog { return ExtractLog.new((p, s, engine) => supplier(p, s, engine).processor(p, s, engine)); } static filterPlayer(e: (a: ExtractLogArg) => number): ExtractLog { return ExtractLog.new((p) => (a) => (a.cmd && a.cmd.faction == p.faction ? e(a) : 0)); } static filterPlayerChanges(e: (a: ExtractLogArg) => number): ExtractLog { return ExtractLog.filterPlayer((a) => (a.cmdIndex == 0 ? e(a) : 0)); } static mux(entries: ExtractLogEntry[]): ExtractLog> { return ExtractLog.wrapper((p, s, engine) => { const logs = entries .filter( (e) => (!e.sourceTypeFilter || e.sourceTypeFilter.includes(s.type)) && this.matchesFaction(e.factionFilter, p) ) .map((e) => ({ extractLog: e.extractLog.processor(p, s, engine), commandFilter: e.commandFilter, factionFilter: e.factionFilter, })); return ExtractLog.new>(() => (a) => { return sum( logs .filter( (e) => (!e.commandFilter || (a.cmd && e.commandFilter.includes(a.cmd.command))) && (!a.log.player || this.matchesFaction(e.factionFilter, a.data.player(a.log.player))) ) .map((e) => e.extractLog(a)) ); }); }); } private static matchesFaction(filter: Faction[] | null, p: Player) { return !filter || (p && filter.includes(p.faction)); } processor(p: Player, s: Source, engine: Engine): ExtractLogArgProcessor { return this.fn(p, s, engine); } } export type ExtractLogArg = { cmd: CommandObject; allCommands: CommandObject[]; cmdIndex: number; source: Source; data: Engine; log: LogEntry; }; export function commandCounter( command: Command, arg0: string, scorer: (a: ExtractLogArg) => number = () => 1 ): ExtractLog> { return ExtractLog.filterPlayer((a) => (command == a.cmd.command && a.cmd.args[0] == arg0 ? scorer(a) : 0)); } export function commandCounterArg0EqualsSource(...want: Command[]): ExtractLog> { return ExtractLog.filterPlayer((a) => want.includes(a.cmd.command) && (a.cmd.args[0] as any) == a.source.type ? 1 : 0 ); } export function planetCounter( includeLantidsGuestMine: (s: ChartSource) => boolean, includeLostPlanet: (s: ChartSource) => boolean, includeRegularPlanet: (planet: Planet, type: T, player: Player) => boolean, countTransdim = true, value: (cmd: CommandObject, log: LogEntry, planet: Planet, location: string) => number = () => 1 ): ExtractLog> { const transdim = new Set(); const owners: { [key: string]: Faction } = {}; return ExtractLog.new((want) => { return (e) => { const cmd = e.cmd; if (!cmd) { return 0; } const data = e.data; const source = e.source; switch (cmd.command) { case Command.PlaceLostPlanet: return cmd.faction == want.faction && includeLostPlanet(source) ? value(cmd, e.log, Planet.Lost, cmd.args[0]) : 0; case Command.Build: const building = cmd.args[0] as Building; const location = cmd.args[1]; const { q, r } = data.map.parse(location); const hex = data.map.grid.get({ q, r }); const planet = hex.data.planet; const owner = owners[location]; if (owner == null) { owners[location] = cmd.faction; } if (cmd.faction != want.faction) { return 0; } if (owner != want.faction && want.faction == Faction.Lantids) { return includeLantidsGuestMine(source) ? value(cmd, e.log, planet, location) : 0; } if (building == Building.GaiaFormer && countTransdim) { transdim.add(location); if (includeRegularPlanet(Planet.Transdim, source.type, want)) { return value(cmd, e.log, Planet.Transdim, location); } } if ( includeRegularPlanet(planet, source.type, want) && (building == Building.Mine || (building == Building.PlanetaryInstitute && want.faction == Faction.Ivits)) && !transdim.has(location) ) { return value(cmd, e.log, planet, location); } } return 0; }; }); }