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,125 @@
|
||||
using HSchool.Content;
|
||||
using HSchool.People;
|
||||
using HSchool.Schedule;
|
||||
|
||||
namespace HSchool.Ai;
|
||||
|
||||
/// <summary>When this person appears at the yard and when they start walking home today.</summary>
|
||||
public readonly record struct DayPlan(DateOnly Day, DateTime? AppearAt, DateTime? WalkHomeAt, string? FirstRoom)
|
||||
{
|
||||
public bool Comes => AppearAt is not null;
|
||||
}
|
||||
|
||||
public static class DayPlans
|
||||
{
|
||||
private const int ExtraRollMax = 7;
|
||||
|
||||
public static DayPlan Build(
|
||||
DefCatalog catalog,
|
||||
WalkGraph walks,
|
||||
Person person,
|
||||
SchoolClass? schoolClass,
|
||||
Timetable? timetable,
|
||||
DateTime time,
|
||||
int weekDays,
|
||||
int schoolSeed)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(catalog);
|
||||
ArgumentNullException.ThrowIfNull(walks);
|
||||
ArgumentNullException.ThrowIfNull(person);
|
||||
|
||||
var utc = DateTime.SpecifyKind(time, DateTimeKind.Utc);
|
||||
var day = DateOnly.FromDateTime(utc);
|
||||
if (!Duty.ComesToday(person, timetable, catalog, utc, weekDays) || catalog.DayFrame is null)
|
||||
{
|
||||
return new DayPlan(day, null, null, null);
|
||||
}
|
||||
|
||||
var weekday = SchoolDay.WeekdayIndex(utc);
|
||||
var firstRoom = FirstRoom(person, schoolClass, timetable, catalog, weekday);
|
||||
if (firstRoom is null)
|
||||
{
|
||||
return new DayPlan(day, null, null, null);
|
||||
}
|
||||
|
||||
var firstStart = FirstStart(person, schoolClass, timetable, catalog, weekday);
|
||||
var lastEnd = LastEnd(person, schoolClass, timetable, catalog, weekday);
|
||||
var travel = walks.Minutes(walks.TerritoryId, firstRoom);
|
||||
if (float.IsInfinity(travel))
|
||||
{
|
||||
travel = 0f;
|
||||
}
|
||||
|
||||
var slack = SlackMinutes(catalog, person, schoolSeed, day);
|
||||
var appear = DateTime.SpecifyKind(utc.Date.Add(firstStart.ToTimeSpan()).AddMinutes(-(travel + slack)), DateTimeKind.Utc);
|
||||
var walkHome = DateTime.SpecifyKind(utc.Date.Add(lastEnd.ToTimeSpan()), DateTimeKind.Utc);
|
||||
return new DayPlan(day, appear, walkHome, firstRoom);
|
||||
}
|
||||
|
||||
private static string? FirstRoom(
|
||||
Person person,
|
||||
SchoolClass? schoolClass,
|
||||
Timetable? timetable,
|
||||
DefCatalog catalog,
|
||||
int weekday)
|
||||
{
|
||||
if (Duty.IsOtherStaff(person))
|
||||
{
|
||||
return person.WorkplaceRoomId;
|
||||
}
|
||||
|
||||
var lessons = Duty.LessonsToday(person, schoolClass, timetable, weekday);
|
||||
return lessons.Count > 0 ? lessons[0].RoomId : schoolClass?.RoomId;
|
||||
}
|
||||
|
||||
private static TimeOnly FirstStart(
|
||||
Person person,
|
||||
SchoolClass? schoolClass,
|
||||
Timetable? timetable,
|
||||
DefCatalog catalog,
|
||||
int weekday)
|
||||
{
|
||||
var frame = catalog.DayFrame!;
|
||||
if (Duty.IsOtherStaff(person))
|
||||
{
|
||||
return SchoolDay.PeriodStart(frame, 1);
|
||||
}
|
||||
|
||||
var lessons = Duty.LessonsToday(person, schoolClass, timetable, weekday);
|
||||
return lessons.Count > 0 ? SchoolDay.PeriodStart(frame, lessons[0].Period) : SchoolDay.PeriodStart(frame, 1);
|
||||
}
|
||||
|
||||
private static TimeOnly LastEnd(
|
||||
Person person,
|
||||
SchoolClass? schoolClass,
|
||||
Timetable? timetable,
|
||||
DefCatalog catalog,
|
||||
int weekday)
|
||||
{
|
||||
var frame = catalog.DayFrame!;
|
||||
if (Duty.IsOtherStaff(person))
|
||||
{
|
||||
return SchoolDay.PeriodEnd(frame, frame.LessonCount);
|
||||
}
|
||||
|
||||
var lessons = Duty.LessonsToday(person, schoolClass, timetable, weekday);
|
||||
return lessons.Count > 0
|
||||
? SchoolDay.PeriodEnd(frame, lessons[^1].Period)
|
||||
: SchoolDay.PeriodEnd(frame, frame.LessonCount);
|
||||
}
|
||||
|
||||
private static int SlackMinutes(DefCatalog catalog, Person person, int schoolSeed, DateOnly day)
|
||||
{
|
||||
var rng = new Random(Seed.Mix(schoolSeed, person.Id, day.DayNumber, Seed.CommuteSalt));
|
||||
var extra = rng.Next(0, ExtraRollMax);
|
||||
foreach (var name in person.Traits)
|
||||
{
|
||||
if (catalog.Traits.TryGetValue(name, out var trait))
|
||||
{
|
||||
extra += trait.CommuteMinutes;
|
||||
}
|
||||
}
|
||||
|
||||
return extra;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user