Day plans take a precomputed extra; Ai does not know precipitation. Co-authored-by: Cursor <cursoragent@cursor.com>
137 lines
4.4 KiB
C#
137 lines
4.4 KiB
C#
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Day plans. Rain and snow arrive as already-counted extra minutes — this project does not
|
|
/// know precipitation.
|
|
/// </summary>
|
|
public static class DayPlans
|
|
{
|
|
public static DayPlan Build(
|
|
DefCatalog catalog,
|
|
WalkGraph walks,
|
|
Person person,
|
|
SchoolClass? schoolClass,
|
|
Timetable? timetable,
|
|
DateTime time,
|
|
int weekDays,
|
|
int schoolSeed,
|
|
int extraCommuteMinutes = 0)
|
|
{
|
|
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) + Math.Max(0, extraCommuteMinutes);
|
|
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 min = 0;
|
|
var max = 6;
|
|
if (catalog.BehaviorRules is { } rules)
|
|
{
|
|
min = rules.CommuteSlackMin;
|
|
max = rules.CommuteSlackMax;
|
|
}
|
|
|
|
var extra = rng.Next(min, max + 1);
|
|
foreach (var name in person.Traits)
|
|
{
|
|
if (catalog.Traits.TryGetValue(name, out var trait))
|
|
{
|
|
extra += trait.CommuteMinutes;
|
|
}
|
|
}
|
|
|
|
return extra;
|
|
}
|
|
}
|