diff options
Diffstat (limited to 'src/LivingVillage.Headless/Program.fs')
| -rw-r--r-- | src/LivingVillage.Headless/Program.fs | 83 |
1 files changed, 54 insertions, 29 deletions
diff --git a/src/LivingVillage.Headless/Program.fs b/src/LivingVillage.Headless/Program.fs index 6f86f45..28fe121 100644 --- a/src/LivingVillage.Headless/Program.fs +++ b/src/LivingVillage.Headless/Program.fs @@ -9,6 +9,7 @@ let secondsPerDay = 86400L let ticksPerDay = secondsPerDay * ticksPerSecond let segmentTicks = 900L +let summaryInterval = 5000L let zeroInput = { MoveX = 0.0f; MoveY = 0.0f } @@ -46,18 +47,34 @@ let actionName (k: NpcActionKind) : string = | Sleep -> "sleep" | Wander -> "wander" | Work -> "work" + | Chat -> "chat" -let runSimulation (days: int64) (seed: uint64) : int = +let npcIndex (id: NpcId) : int = + match id with NpcId i -> i + +let actionDistribution (npcs: Npc[]) : int * int * int * int * int = + let counts = + npcs + |> Array.fold (fun (e, s, w, k, c) npc -> + match npc.Mind.Action with + | Eat -> (e + 1, s, w, k, c) + | Sleep -> (e, s + 1, w, k, c) + | Wander -> (e, s, w + 1, k, c) + | Work -> (e, s, w, k + 1, c) + | Chat -> (e, s, w, k, c + 1)) (0, 0, 0, 0, 0) + counts + +let runSimulation (days: int64) (seed: uint64) (npcCount: int) : int = let totalTicks = days * ticksPerDay - printfn "living-village headless days=%d seed=%d ticks=%d tickrate=%d" days seed totalTicks ticksPerSecond - let mutable world = Sim.initialWorld seed + printfn "living-village headless days=%d seed=%d npcs=%d ticks=%d tickrate=%d" days seed npcCount totalTicks ticksPerSecond + let mutable world = Sim.initialWorldN seed npcCount let mutable driver = { Rng = Rng.ofSeed seed; Until = segmentTicks; Input = zeroInput } let maxX = float32 (Sim.mapWidthTiles * Sim.tilePixels - Sim.tilePixels) let maxY = float32 (Sim.mapHeightTiles * Sim.tilePixels - Sim.tilePixels) let mutable nonFinite = 0L let mutable outOfBounds = 0L let mutable t = 0L - let mutable lastAction = (List.head world.Npcs).Mind.Action + let mutable lastAction = (world.Npcs.[0]).Mind.Action let mutable lastStart = 0L let mutable switchCount = 0L let mutable switchDurSum = 0L @@ -65,6 +82,9 @@ let runSimulation (days: int64) (seed: uint64) : int = let mutable sleepDay = 0L let mutable nightTicks = 0L let mutable dayTicks = 0L + let npcTotal = Array.create npcCount 0L + let lastActions = Array.create npcCount (world.Npcs.[0]).Mind.Action + let mutable chatsWindowStart = 0L while t < totalTicks do let d, input = if t < driver.Until then driver, driver.Input @@ -76,7 +96,12 @@ let runSimulation (days: int64) (seed: uint64) : int = t <- t + 1L let nightNow = Sim.isNightTick t if nightNow then nightTicks <- nightTicks + 1L else dayTicks <- dayTicks + 1L - let head = List.head world.Npcs + for npc in world.Npcs do + let idx = npcIndex npc.Id + if npc.Mind.Action = Chat && lastActions.[idx] <> Chat then + npcTotal.[idx] <- npcTotal.[idx] + 1L + lastActions.[idx] <- npc.Mind.Action + let head = world.Npcs.[0] if head.Mind.Action = Sleep then if nightNow then sleepNight <- sleepNight + 1L else sleepDay <- sleepDay + 1L if head.Mind.Action <> lastAction then @@ -88,27 +113,18 @@ let runSimulation (days: int64) (seed: uint64) : int = if Single.IsNaN p.X || Single.IsNaN p.Y || Single.IsInfinity p.X || Single.IsInfinity p.Y then nonFinite <- nonFinite + 1L if p.X < 0.0f || p.X > maxX || p.Y < 0.0f || p.Y > maxY then outOfBounds <- outOfBounds + 1L - if t % 2000L = 0L then - let day = float t / float ticksPerDay - let memTotal = GC.GetTotalMemory(true) - for npc in world.Npcs do - let n = npc.Mind.Needs - let p' = npc.Pos - let memCount = List.length npc.Mind.Memory - let lastValence = - match List.tryHead npc.Mind.Memory with - | Some e -> e.Valence - | None -> 0.0f - printfn "npc=%A tick=%d day=%.4f pos=(%.1f,%.1f) hunger=%.2f energy=%.2f social=%.2f money=%.2f action=%s mem_count=%d last_valence=%.2f" - npc.Id t day p'.X p'.Y n.Hunger n.Energy n.Social n.Money (actionName npc.Mind.Action) memCount lastValence - if t % 1000L = 0L && t % 2000L <> 0L then + if t % summaryInterval = 0L then + let eat, sleep, wander, work, chat = actionDistribution world.Npcs + let chatsTotal = Array.sum npcTotal let day = float t / float ticksPerDay - printfn "tick=%d time=%.4fs day=%.4f pos=(%.1f,%.1f) rng=%016x" t world.Time day p.X p.Y world.Rng.State + printfn "summary tick=%d day=%.4f eat=%d sleep=%d wander=%d work=%d chat=%d chats_window=%d" + t day eat sleep wander work chat (chatsTotal - chatsWindowStart) + chatsWindowStart <- chatsTotal let segments = switchCount + 1L let avgActionTicks = float (switchDurSum + (totalTicks - lastStart)) / float segments let nightPct = if nightTicks > 0L then 100.0 * float sleepNight / float nightTicks else 0.0 let dayPct = if dayTicks > 0L then 100.0 * float sleepDay / float dayTicks else 0.0 - let npc0 = List.head world.Npcs + let npc0 = world.Npcs.[0] let memCount = List.length npc0.Mind.Memory let lastValence = match List.tryHead npc0.Mind.Memory with @@ -116,31 +132,40 @@ let runSimulation (days: int64) (seed: uint64) : int = | None -> 0.0f printfn "npc=%A action_switches=%d avg_action_ticks=%.1f sleep_night_ticks=%d sleep_day_ticks=%d sleep_night_pct=%.2f sleep_day_pct=%.2f mem_count=%d last_valence=%.2f" npc0.Id switchCount avgActionTicks sleepNight sleepDay nightPct dayPct memCount lastValence + let chatsTotal = Array.sum npcTotal + let minChats = if npcCount > 0 then Array.min npcTotal else 0L + let maxChats = if npcCount > 0 then Array.max npcTotal else 0L + let avgChats = if npcCount > 0 then float chatsTotal / float npcCount else 0.0 + printfn "chats_total=%d chats_min=%d chats_max=%d chats_avg=%.2f" chatsTotal minChats maxChats avgChats printfn "done tick=%d non-finite=%d out-of-bounds=%d" world.Tick nonFinite outOfBounds if nonFinite > 0L || outOfBounds > 0L then 1 else 0 [<EntryPoint>] let main argv = - let rec parse (i: int) (days: int64 option) (seed: uint64 option) : Result<int64 * uint64, string> = + let rec parse (i: int) (days: int64 option) (seed: uint64 option) (npc: int option) : Result<int64 * uint64 * int, string> = if i >= argv.Length then - match days, seed with - | Some d, Some s -> Ok(d, s) + match days, seed, npc with + | Some d, Some s, Some n -> Ok(d, s, n) | _ -> Error "missing --days/--seed" else match argv.[i] with | "--days" when i + 1 < argv.Length -> (match Int64.TryParse argv.[i + 1] with - | true, d when d > 0L -> parse (i + 2) (Some d) seed + | true, d when d > 0L -> parse (i + 2) (Some d) seed npc | _ -> Error $"invalid --days '{argv.[i + 1]}'") | "--seed" when i + 1 < argv.Length -> (match UInt64.TryParse argv.[i + 1] with - | true, s -> parse (i + 2) days (Some s) + | true, s -> parse (i + 2) days (Some s) npc | _ -> Error $"invalid --seed '{argv.[i + 1]}'") + | "--npc" when i + 1 < argv.Length -> + (match Int32.TryParse argv.[i + 1] with + | true, n when n > 0 && n <= 1000 -> parse (i + 2) days seed (Some n) + | _ -> Error $"invalid --npc '{argv.[i + 1]}'") | other -> Error $"unknown argument '{other}'" - match parse 0 None None with - | Ok(days, seed) -> runSimulation days seed + match parse 0 None None (Some 30) with + | Ok(days, seed, npc) -> runSimulation days seed npc | Error msg -> eprintfn $"headless: {msg}" - eprintfn "usage: dotnet run -c Release --project src/LivingVillage.Headless -- --days N --seed S" + eprintfn "usage: dotnet run -c Release --project src/LivingVillage.Headless -- --days N --seed S [--npc N]" 2 |
