using HSchool.Content; using HSchool.People; using HSchool.Schedule; namespace HSchool.Ai.Tests; public class WalkingTests { private static readonly DateTime TuesdayLesson = new(2012, 4, 3, 8, 45, 0, DateTimeKind.Utc); private static readonly DateTime Sunday = new(2012, 4, 8, 10, 0, 0, DateTimeKind.Utc); private static readonly DateTime SpringBreak = new(2012, 3, 31, 10, 0, 0, DateTimeKind.Utc); [Fact] public void PathFrom201ToFirstFloorRestroom_GoesThroughStairsAndCorridors() { var (catalog, map) = Fixtures.Vanilla(); var walks = WalkGraph.Build(catalog, map); var hops = walks.Path("classroom-201", "restroom-1"); Assert.Equal(["corridor-2", "stairs-2", "stairs-1", "corridor-1", "restroom-1"], hops); Assert.DoesNotContain("yard", hops); Assert.Equal(6.5f, walks.Minutes("classroom-201", "restroom-1")); } [Fact] public void PresenceStepper_TakesTheSameMinutesAsTheGraph() { var (catalog, map) = Fixtures.Vanilla(); var walks = WalkGraph.Build(catalog, map); var hops = walks.Path("classroom-201", "restroom-1"); var cost = walks.Minutes("classroom-201", "restroom-1"); var presence = new Presence("classroom-201", 0f, "restroom-1", HeadingHome: false, [.. hops]); var walked = PresenceStepper.Advance(presence, walks, cost); Assert.Equal("restroom-1", walked.NodeId); Assert.Empty(walked.Path); Assert.Equal(0f, walked.RemainingMinutes); var stepwise = presence; var elapsed = 0f; const float step = 0.25f; while (stepwise.NodeId != "restroom-1" || stepwise.Path.Length > 0 || stepwise.RemainingMinutes > 0) { stepwise = PresenceStepper.Advance(stepwise, walks, step); elapsed += step; Assert.True(elapsed <= cost + step); } Assert.Equal(cost, elapsed); } [Fact] public void GymAfterClassroom101_CostsSixMinutesAgainstATenMinuteBreak() { var (catalog, map) = Fixtures.Vanilla(); var walks = WalkGraph.Build(catalog, map); var hops = walks.Path("classroom-101", "gym-hall"); Assert.Equal(["corridor-1", "porch", "yard", "gym-hall"], hops); Assert.Equal(6f, walks.Minutes("classroom-101", "gym-hall")); Assert.Equal(10, catalog.DayFrame!.BreakMinutes); Assert.True(walks.Minutes("classroom-101", "gym-hall") < catalog.DayFrame.BreakMinutes); } [Fact] public void ComesToday_IsFalseOnSundayAndHolidays() { var (catalog, map) = Fixtures.Vanilla(); var roster = RosterGenerator.Generate(catalog, map, schoolSeed: 1, "Russia", TuesdayLesson); var pupil = roster.People.First(person => person.IsStudent); var schoolClass = roster.Classes.First(row => row.Id == pupil.ClassId); var table = new Timetable( [new LessonPlacement(schoolClass.Id, "Mathematics", "t1", schoolClass.RoomId, Day: 1, Period: 1)], []); Assert.True(Duty.ComesToday(pupil, table, catalog, TuesdayLesson, weekDays: 5)); Assert.False(Duty.ComesToday(pupil, table, catalog, Sunday, weekDays: 5)); Assert.False(Duty.ComesToday(pupil, table, catalog, SpringBreak, weekDays: 5)); Assert.False(Duty.ComesToday( roster.People.First(person => person.IsParent && !person.IsStaff && !person.IsStudent), table, catalog, TuesdayLesson, weekDays: 5)); } [Fact] public void ClassWithoutLessons_StaysAway_TeacherWithAnotherClassComes() { var (catalog, map) = Fixtures.Vanilla(); var roster = RosterGenerator.Generate(catalog, map, schoolSeed: 1, "Russia", TuesdayLesson); var idle = roster.Classes[0]; var busy = roster.Classes[1]; var idlePupil = roster.People.First(person => person.Id == idle.PupilIds[0]); var busyPupil = roster.People.First(person => person.Id == busy.PupilIds[0]); var teacher = roster.People.First(person => person.IsParent && !person.IsStudent && !person.IsStaff) with { IsStaff = true, Position = Staffing.TeacherPosition, }; var table = new Timetable( [new LessonPlacement(busy.Id, "Mathematics", teacher.Id, busy.RoomId, Day: 1, Period: 1)], []); Assert.False(Duty.ComesToday(idlePupil, table, catalog, TuesdayLesson, weekDays: 5)); Assert.True(Duty.ComesToday(busyPupil, table, catalog, TuesdayLesson, weekDays: 5)); Assert.True(Duty.ComesToday(teacher, table, catalog, TuesdayLesson, weekDays: 5)); Assert.Null(Duty.RoomAt(idlePupil, idle, table, catalog, TuesdayLesson, weekDays: 5)); Assert.Equal(busy.RoomId, Duty.RoomAt(busyPupil, busy, table, catalog, TuesdayLesson, weekDays: 5)); Assert.Equal(busy.RoomId, Duty.RoomAt(teacher, null, table, catalog, TuesdayLesson, weekDays: 5)); } [Fact] public void SameSeedAndMap_YieldTheSameDayPlan() { var (catalog, map) = Fixtures.Vanilla(); var walks = WalkGraph.Build(catalog, map); var roster = RosterGenerator.Generate(catalog, map, schoolSeed: 9, "Russia", TuesdayLesson); var pupil = roster.People.First(person => person.IsStudent); var schoolClass = roster.Classes.First(row => row.Id == pupil.ClassId); var table = new Timetable( [new LessonPlacement(schoolClass.Id, "Mathematics", "t1", schoolClass.RoomId, Day: 1, Period: 1)], []); var first = DayPlans.Build(catalog, walks, pupil, schoolClass, table, TuesdayLesson, weekDays: 5, schoolSeed: 9); var second = DayPlans.Build(catalog, walks, pupil, schoolClass, table, TuesdayLesson, weekDays: 5, schoolSeed: 9); Assert.Equal(first, second); Assert.True(first.Comes); Assert.Equal(schoolClass.RoomId, first.FirstRoom); Assert.True(first.AppearAt < DateTime.SpecifyKind(TuesdayLesson.Date.Add(new TimeSpan(8, 30, 0)), DateTimeKind.Utc)); } [Fact] public void SnowMinutes_AppearEarlierThanClear() { var (clear, snow, extra) = PlansWithExtra(catalog => catalog.BehaviorRules!.CommuteSnowMinutes); Assert.Equal(6, extra); Assert.Equal(clear.AppearAt!.Value.AddMinutes(-extra), snow.AppearAt); } [Fact] public void RainMinutes_AppearEarlierThanClear() { var (clear, rain, extra) = PlansWithExtra(catalog => catalog.BehaviorRules!.CommuteRainMinutes); Assert.Equal(3, extra); Assert.Equal(clear.AppearAt!.Value.AddMinutes(-extra), rain.AppearAt); } [Fact] public void Clear_MatchesOmittedExtraArgument() { var (catalog, map) = Fixtures.Vanilla(); var (walks, pupil, schoolClass, table) = ComingPupil(catalog, map); var implied = DayPlans.Build(catalog, walks, pupil, schoolClass, table, TuesdayLesson, weekDays: 5, schoolSeed: 9); var explicitZero = DayPlans.Build( catalog, walks, pupil, schoolClass, table, TuesdayLesson, weekDays: 5, schoolSeed: 9, extraCommuteMinutes: 0); Assert.Equal(implied, explicitZero); Assert.True(implied.Comes); } [Fact] public void Lazy_AppearsCloserToTheBell_OnSnow() { var (catalog, map) = Fixtures.Vanilla(); var walks = WalkGraph.Build(catalog, map); var roster = RosterGenerator.Generate(catalog, map, schoolSeed: 9, "Russia", TuesdayLesson); var lazy = roster.People.First(person => person.IsStudent && person.Traits.Contains("Lazy")); var schoolClass = roster.Classes.First(row => row.Id == lazy.ClassId); var keen = roster.People.First(person => person.IsStudent && person.ClassId == lazy.ClassId && !person.Traits.Contains("Lazy")); var table = new Timetable( [new LessonPlacement(schoolClass.Id, "Mathematics", "t1", schoolClass.RoomId, Day: 1, Period: 1)], []); var extra = catalog.BehaviorRules!.CommuteSnowMinutes; var lazyPlan = DayPlans.Build( catalog, walks, lazy, schoolClass, table, TuesdayLesson, weekDays: 5, schoolSeed: 9, extra); var keenPlan = DayPlans.Build( catalog, walks, keen, schoolClass, table, TuesdayLesson, weekDays: 5, schoolSeed: 9, extra); Assert.True(lazyPlan.Comes); Assert.True(keenPlan.Comes); Assert.True(lazyPlan.AppearAt > keenPlan.AppearAt); } [Fact] public void SameSeedAndSnowMinutes_YieldTheSameAppearAt() { var (catalog, map) = Fixtures.Vanilla(); var (walks, pupil, schoolClass, table) = ComingPupil(catalog, map); var extra = catalog.BehaviorRules!.CommuteSnowMinutes; var first = DayPlans.Build( catalog, walks, pupil, schoolClass, table, TuesdayLesson, weekDays: 5, schoolSeed: 9, extra); var second = DayPlans.Build( catalog, walks, pupil, schoolClass, table, TuesdayLesson, weekDays: 5, schoolSeed: 9, extra); Assert.Equal(first.AppearAt, second.AppearAt); Assert.True(first.Comes); } [Fact] public void Sources_DoNotMentionPrecipitation() { var root = Path.Combine(Fixtures.RepoRoot(), "src", "HSchool.Ai"); foreach (var path in Directory.EnumerateFiles(root, "*.cs")) { var text = File.ReadAllText(path); Assert.DoesNotContain("Precipitation", text, StringComparison.Ordinal); } } private static (DayPlan Clear, DayPlan Weather, int Extra) PlansWithExtra(Func extraOf) { var (catalog, map) = Fixtures.Vanilla(); var (walks, pupil, schoolClass, table) = ComingPupil(catalog, map); var extra = extraOf(catalog); var clear = DayPlans.Build(catalog, walks, pupil, schoolClass, table, TuesdayLesson, weekDays: 5, schoolSeed: 9); var weather = DayPlans.Build( catalog, walks, pupil, schoolClass, table, TuesdayLesson, weekDays: 5, schoolSeed: 9, extra); return (clear, weather, extra); } private static (WalkGraph Walks, Person Pupil, SchoolClass SchoolClass, Timetable Table) ComingPupil( DefCatalog catalog, MapLayout map) { var walks = WalkGraph.Build(catalog, map); var roster = RosterGenerator.Generate(catalog, map, schoolSeed: 9, "Russia", TuesdayLesson); var pupil = roster.People.First(person => person.IsStudent); var schoolClass = roster.Classes.First(row => row.Id == pupil.ClassId); var table = new Timetable( [new LessonPlacement(schoolClass.Id, "Mathematics", "t1", schoolClass.RoomId, Day: 1, Period: 1)], []); return (walks, pupil, schoolClass, table); } }