Enhance AI and simulation components with presence management and routing capabilities
- Introduced the `HSchool.Ai` project, responsible for routing, day plans, and presence management. - Updated the `HSchool.Simulation` project to integrate with the new AI functionalities, improving decision-making and presence tracking. - Added `travelMinutes` to room and territory definitions, ensuring accurate movement calculations within the simulation. - Enhanced the `School` class to manage presence and implement empty-time skipping functionality. - Updated documentation to reflect the new AI features and their impact on school simulation. - Added tests for presence management and routing to ensure robust functionality and reliability.
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
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, "Slavic", 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, "Slavic", 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, "Slavic", 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 Assembly_DoesNotReferenceArchAspNetOrSockets()
|
||||
{
|
||||
var names = typeof(WalkGraph).Assembly.GetReferencedAssemblies().Select(assembly => assembly.Name!);
|
||||
Assert.DoesNotContain(names, name => name.StartsWith("Arch", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.DoesNotContain(names, name => name.Contains("AspNet", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.DoesNotContain(names, name => name.Contains("Sockets", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Sources_DoNotUseWallClock()
|
||||
{
|
||||
var root = Path.Combine(Fixtures.RepoRoot(), "src", "HSchool.Ai");
|
||||
foreach (var path in Directory.EnumerateFiles(root, "*.cs"))
|
||||
{
|
||||
var text = File.ReadAllText(path);
|
||||
Assert.DoesNotContain("DateTime.Now", text, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("DateTime.UtcNow", text, StringComparison.Ordinal);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user