using HSchool.Content; using HSchool.People; using HSchool.Schedule; namespace HSchool.Simulation.Tests; /// /// The canteen is fed in two sittings so it is not the whole school at once: juniors eat in the /// break after the third lesson, seniors after the fourth. Before that, everybody crossed the /// hunger threshold at roughly the same minute and the room could not hold them. /// public class LunchTests { private static readonly DateTime Tuesday6 = new(2012, 4, 3, 6, 0, 0, DateTimeKind.Utc); [Fact] public void JuniorsEatAtTheFirstSitting_SeniorsAtTheSecond() { using var school = StaffedSchool(); var (junior, senior) = Parallels(school); // Staff belong to no parallel and may eat at either sitting, so the claim is about pupils: // the two halves of the school never meet in the canteen. AdvanceTo(school, new DateTime(2012, 4, 3, 11, 15, 0, DateTimeKind.Utc)); var first = InCafeteria(school); Assert.Contains(first, junior.Contains); Assert.DoesNotContain(first, senior.Contains); AdvanceTo(school, new DateTime(2012, 4, 3, 12, 20, 0, DateTimeKind.Utc)); var second = InCafeteria(school); Assert.Contains(second, senior.Contains); Assert.DoesNotContain(second, junior.Contains); } /// /// Hunger is restored off campus — people eat at home — so a school week does not leave the /// roster starving. It used to: nothing refilled hunger except eight chairs, and by the end of /// the week a sixth of the school sat at zero. /// [Fact] public void AfterASchoolDay_NobodyIsLeftStarving() { using var school = StaffedSchool(); var pupils = school.Roster!.People.Where(person => person.IsStudent).Select(person => person.Id) .ToHashSet(StringComparer.Ordinal); AdvanceTo(school, new DateTime(2012, 4, 4, 8, 0, 0, DateTimeKind.Utc)); var hunger = Hunger(school, pupils); Assert.NotEmpty(hunger); Assert.All(hunger.Values, value => Assert.True(value > 0.5f, $"somebody came back to school at {value:F2}")); } private static School StaffedSchool() { var (catalog, map) = Vanilla(); var roster = RosterGenerator.Generate(catalog, map, schoolSeed: 1, "Slavic", Tuesday6); var pool = ApplicantPool.Create(catalog, roster, schoolSeed: 1, "Slavic", Tuesday6); foreach (var subject in catalog.Subjects.Values.Where(def => !def.Abstract).Select(def => def.DefName)) { if (pool.Applicants.Count == 0) { break; } var candidate = pool.Applicants[0].Person.Id; var hired = Staffing.Hire(catalog, map, roster, pool, candidate, Staffing.TeacherPosition, 1_000_000f); if (hired.Error != StaffingError.None) { break; } roster = hired.Roster; pool = hired.Pool; var assigned = Staffing.AssignSubject(catalog, roster, pool, candidate, subject, 1_000_000f); if (assigned.Error == StaffingError.None) { roster = assigned.Roster; } } var school = School.Create(1, "Столовая", Tuesday6, catalog, map); school.InstallPeople(roster, seed: 1, "Slavic", pool); school.SetTimetable(SchoolTimetables.Build(catalog, map, roster, null, weekDays: 5)); school.ConfigurePresence(weekDays: 5, maxDecisionsPerTick: 64); return school; } private static (HashSet Junior, HashSet Senior) Parallels(School school) { var junior = new HashSet(StringComparer.Ordinal); var senior = new HashSet(StringComparer.Ordinal); foreach (var row in school.Roster!.Classes) { foreach (var id in row.PupilIds) { (row.Year <= 5 ? junior : senior).Add(id); } } return (junior, senior); } private static string[] InCafeteria(School school) => school.CapturePresence() .Where(row => row.NodeId == "cafeteria") .Select(row => row.PersonId) .ToArray(); private static Dictionary Hunger(School school, HashSet pupils) { var values = new Dictionary(StringComparer.Ordinal); var query = new Arch.Core.QueryDescription().WithAll(); school.World.Query(in query, (ref PersonIdentity identity, ref PersonNeeds needs) => { if (pupils.Contains(identity.Id) && needs.Values.TryGetValue("Hunger", out var value)) { values[identity.Id] = value; } }); return values; } private static void AdvanceTo(School school, DateTime until) { while (school.Clock.Time < until) { school.Tick(0.2d, 5d); } } private static (DefCatalog Catalog, MapLayout Map) Vanilla() { var root = Path.Combine(AppContext.BaseDirectory, "vanilla"); var documents = PackDocumentsFrom(root); var catalog = new CatalogLoader().Load([CatalogLoader.CorePackId], documents); var map = CatalogLoader.LastDefaultMap([CatalogLoader.CorePackId], documents); Assert.NotNull(map); return (catalog, map); } private static IReadOnlyList PackDocumentsFrom(string root) { var documents = new List(); foreach (var path in Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories)) { if (!path.EndsWith(".jsonc", StringComparison.OrdinalIgnoreCase) && !path.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) { continue; } var relative = Path.GetRelativePath(root, path).Replace(Path.DirectorySeparatorChar, (char)47); documents.Add(new ContentDocument(CatalogLoader.CorePackId, relative, File.ReadAllText(path))); } return documents; } }