using HSchool.Content; namespace HSchool.Ai.Tests; public class DecisionPlannerTests { [Fact] public void ZeroToilet_BeatsALesson() { var (catalog, map, walks) = World(); var duty = map.Rooms.First(room => room.Def == "Classroom").Id; var decision = DecisionPlanner.Decide( catalog, map, walks, Actor(duty, boundToLesson: true, duty, new Dictionary(StringComparer.Ordinal) { ["Toilet"] = 0f, ["Hunger"] = 1f, ["Social"] = 1f, ["Sleep"] = 1f, }), (_, _) => 0); Assert.Equal(GoalKind.Need, decision.Intent.Kind); Assert.Equal("Toilet", decision.Intent.Id); Assert.Equal("UseToilet", decision.Intent.ActionId); Assert.NotNull(decision.WalkTo); Assert.Equal("Restroom", map.Rooms.First(room => room.Id == decision.WalkTo).Def); } [Fact] public void NeedJustBelowThreshold_DoesNotLeaveClass() { var (catalog, map, walks) = World(); var duty = map.Rooms.First(room => room.Def == "Classroom").Id; var decision = DecisionPlanner.Decide( catalog, map, walks, Actor(duty, boundToLesson: true, duty, new Dictionary(StringComparer.Ordinal) { ["Toilet"] = 0.34f, ["Hunger"] = 1f, ["Social"] = 1f, ["Sleep"] = 1f, }), (_, _) => 0); Assert.Equal(GoalKind.Duty, decision.Intent.Kind); Assert.Null(decision.StartAction); Assert.True(decision.WalkTo is null || decision.WalkTo == duty); } [Fact] public void EqualNeeds_FinishTheCurrentAction() { var (catalog, map, walks) = World(); var restroom = map.Rooms.First(room => room.Def == "Restroom").Id; var intent = new Intent(GoalKind.Need, "Toilet", DecisionPlanner.NeedWeightAtZero, "UseToilet"); var decision = DecisionPlanner.Decide( catalog, map, walks, Actor( restroom, boundToLesson: true, dutyRoom: map.Rooms.First(room => room.Def == "Classroom").Id, needs: new Dictionary(StringComparer.Ordinal) { ["Toilet"] = 0f, ["Hunger"] = 0f, ["Social"] = 1f, ["Sleep"] = 1f, }, intent: intent, activityActive: true), (_, _) => 0); Assert.Equal("Toilet", decision.Intent.Id); Assert.Null(decision.WalkTo); Assert.Null(decision.StartAction); } [Fact] public void BreakAtDutyRoom_PicksLeisureEvenWithAStaleLessonIntent() { var (catalog, map, walks) = World(); var duty = map.Rooms.First(room => room.Def == "Classroom").Id; var decision = DecisionPlanner.Decide( catalog, map, walks, Actor( duty, boundToLesson: false, duty, FullNeeds(), intent: new Intent(GoalKind.Duty, duty, DecisionPlanner.DutyLessonWeight, null)), (_, _) => 0); Assert.Equal(GoalKind.Leisure, decision.Intent.Kind); } [Fact] public void LeisureOnABreak_IsNotYankedBackToTheNextHomeroom() { var (catalog, map, walks) = World(); var classroom = "classroom-101"; var corridor = "corridor-1"; var decision = DecisionPlanner.Decide( catalog, map, walks, Actor( corridor, boundToLesson: false, classroom, FullNeeds(), intent: new Intent(GoalKind.Leisure, "Chat", 3f, "Chat")), (_, _) => 0); Assert.Equal(GoalKind.Leisure, decision.Intent.Kind); Assert.NotEqual(classroom, decision.WalkTo); } [Fact] public void BreakAtDutyRoom_PicksLeisure_LessonDoesNot() { var (catalog, map, walks) = World(); var duty = map.Rooms.First(room => room.Def == "Classroom").Id; var onBreak = DecisionPlanner.Decide( catalog, map, walks, Actor(duty, boundToLesson: false, duty, FullNeeds()), (_, _) => 0); var inLesson = DecisionPlanner.Decide( catalog, map, walks, Actor(duty, boundToLesson: true, duty, FullNeeds()), (_, _) => 0); Assert.Equal(GoalKind.Leisure, onBreak.Intent.Kind); Assert.NotNull(onBreak.Intent.ActionId); Assert.True(catalog.Actions[onBreak.Intent.ActionId!].Weight > 0); Assert.Equal(GoalKind.Duty, inLesson.Intent.Kind); Assert.Null(inLesson.StartAction); } [Fact] public void BreakAwayFromNextRoom_WalksThereInsteadOfChatting() { var (catalog, map, walks) = World(); var classroom = map.Rooms.First(room => room.Id == "classroom-101").Id; var gym = "gym-hall"; var fromIdle = DecisionPlanner.Decide( catalog, map, walks, Actor(classroom, boundToLesson: false, gym, FullNeeds()), (_, _) => 0); var fromStaleLesson = DecisionPlanner.Decide( catalog, map, walks, Actor( classroom, boundToLesson: false, gym, FullNeeds(), intent: new Intent(GoalKind.Duty, classroom, DecisionPlanner.DutyLessonWeight, null)), (_, _) => 0); Assert.Equal(GoalKind.Duty, fromIdle.Intent.Kind); Assert.Equal(gym, fromIdle.WalkTo); Assert.Equal(gym, fromStaleLesson.WalkTo); Assert.Equal(gym, fromStaleLesson.Intent.Id); } [Fact] public void ArrivedAtNextRoomThisBreak_StaysInsteadOfChatting() { var (catalog, map, walks) = World(); var gym = "gym-hall"; var decision = DecisionPlanner.Decide( catalog, map, walks, Actor( gym, boundToLesson: false, gym, FullNeeds(), intent: new Intent(GoalKind.Duty, gym, DecisionPlanner.DutyTravelWeight, null)), (_, _) => 0); Assert.Equal(GoalKind.Duty, decision.Intent.Kind); Assert.Equal(gym, decision.Intent.Id); Assert.Null(decision.WalkTo); Assert.Null(decision.StartAction); } private static ActorState Actor( string node, bool boundToLesson, string dutyRoom, IReadOnlyDictionary needs, Intent? intent = null, bool activityActive = false) => new( node, node, IsWalking: false, activityActive, IsStudent: true, IsStaff: false, IsParent: false, boundToLesson, dutyRoom, needs, intent ?? Intent.None); private static Dictionary FullNeeds() => new(StringComparer.Ordinal) { ["Toilet"] = 1f, ["Hunger"] = 1f, ["Social"] = 1f, ["Sleep"] = 1f, }; private static (DefCatalog Catalog, MapLayout Map, WalkGraph Walks) World() { var (catalog, map) = Fixtures.Vanilla(); return (catalog, map, WalkGraph.Build(catalog, map)); } }