Files
h-school/tests/HSchool.Simulation.Tests/AppropriatenessSimulationTests.cs
T

310 lines
12 KiB
C#

using HSchool.Ai;
using HSchool.Content;
using HSchool.People;
using HSchool.Schedule;
using HSchool.Simulation;
namespace HSchool.Simulation.Tests;
public class AppropriatenessSimulationTests
{
private static readonly DateTime TuesdayMorning = new(2012, 4, 3, 6, 0, 0, DateTimeKind.Utc);
private static readonly DateTime JanuaryMorning = new(2012, 1, 10, 6, 0, 0, DateTimeKind.Utc);
private static readonly DateTime JulyMorning = new(2012, 7, 10, 6, 0, 0, DateTimeKind.Utc);
[Fact]
public void Morning_NoBrightPolicy_WornColorsAreNotBright()
{
using var school = Open(TuesdayMorning, seed: 31);
school.DressRules = school.DressRules with
{
Students = new DressRulePair(FormPolicies.Regular, ColorPolicies.NoBright),
};
MorningDress.Apply(school);
foreach (var pupil in school.Roster!.People.Where(person => person.IsStudent))
{
foreach (var item in pupil.Items.Where(row => row.Location == ItemLocations.Worn))
{
if (item.Color is { } color
&& school.Catalog!.Colors.TryGetValue(color, out var colorDef))
{
Assert.DoesNotContain(ColorTags.Bright, colorDef.Tags, StringComparer.Ordinal);
}
}
}
}
[Fact]
public void FrostMorning_WornOuterExists_JulyHasNoFurCoat()
{
using var january = Open(JanuaryMorning, seed: 31);
MorningDress.Apply(january);
Assert.Contains(
january.Roster!.People.Where(person => person.IsStudent),
person => person.Items.Any(item =>
item.Location == ItemLocations.Worn
&& january.Catalog!.Things[item.Def].Layers.Any(layer =>
layer.Equals(ApparelLayers.Outer, StringComparison.Ordinal))));
using var july = Open(JulyMorning, seed: 31);
july.SyncWeather(force: true);
MorningDress.Apply(july);
Assert.DoesNotContain(
july.Roster!.People.SelectMany(person => person.Items),
item => item.Location == ItemLocations.Worn && item.Def == "FurCoat");
}
[Fact]
public void PeBreak_PupilReachesOwnChangingRoom()
{
var (school, pupilId) = StaffedPeClass();
using (school)
{
MorningDress.Apply(school);
AdvanceTo(school, new DateTime(2012, 4, 3, 9, 24, 0, DateTimeKind.Utc));
var row = school.CapturePresence().Single(item => item.PersonId == pupilId);
var person = school.Roster!.People.Single(item => item.Id == pupilId);
var room = person.Female ? "girls-changing-room" : "boys-changing-room";
Assert.Equal(room, row.NodeId);
Assert.Empty(row.Path);
}
}
[Fact]
public void Boy_DoesNotEnterGirlsChangingRoom()
{
var (school, pupilId) = StaffedPeClass(forceBoy: true);
using (school)
{
MorningDress.Apply(school);
AdvanceTo(school, new DateTime(2012, 4, 3, 9, 18, 0, DateTimeKind.Utc));
var row = school.CapturePresence().Single(item => item.PersonId == pupilId);
Assert.NotEqual("girls-changing-room", row.NodeId);
}
}
[Fact]
public void ChangeWithoutLocker_StashedItemGoesToBag()
{
using var school = Open(TuesdayMorning, seed: 31);
school.DressRules = school.DressRules with
{
Students = new DressRulePair(FormPolicies.Regular, ColorPolicies.NoBright),
};
var pupil = school.Roster!.People.First(person => person.IsStudent && person.LockerRoomId is null);
var items = pupil.Items.ToList();
items.Add(new InventoryItem("TShirt", "Red", 1f, ItemLocations.Worn));
ReplaceItems(pupil, items);
ApparelDresser.RedressPerson(school, pupil, ApparelMode.Everyday, includeHome: false);
pupil = school.Roster.People.Single(person => person.Id == pupil.Id);
Assert.Contains(pupil.Items, item => item.Def == "TShirt" && item.Color == "Red" && item.Location == ItemLocations.Bag);
}
[Fact]
public void RedressPerson_WritesApparelChangedRows()
{
using var school = Open(TuesdayMorning, seed: 31);
school.DressRules = school.DressRules with
{
Students = new DressRulePair(FormPolicies.Regular, ColorPolicies.NoBright),
};
var pupil = school.Roster!.People.First(person => person.IsStudent && person.LockerRoomId is null);
var items = pupil.Items.ToList();
items.Add(new InventoryItem("TShirt", "Red", 1f, ItemLocations.Worn));
ReplaceItems(pupil, items);
ApparelDresser.RedressPerson(school, pupil, ApparelMode.Everyday, includeHome: false);
Assert.Contains(
school.DayLog,
row => row.PersonId == pupil.Id
&& row.Type == PersonLogTypes.ApparelChanged
&& row.ThingDef == "TShirt");
var page = PersonLogBrowser.Apply(
school.DayLog,
pupil.Id,
new PersonLogQuery(null, Descending: true, Page: 1, PageSize: 20),
school.Catalog,
"ru");
Assert.Contains(page.Entries, entry => entry.Type == PersonLogTypes.ApparelChanged);
Assert.Contains(
page.Entries,
entry => entry.Label.StartsWith("переоделся:", StringComparison.Ordinal));
}
[Fact]
public void PendingStrictRule_DoesNotApplyUntilNextMorning()
{
using var school = Open(TuesdayMorning, seed: 31);
MorningDress.Apply(school);
var before = BrightWornCount(school);
school.DressRules = school.DressRules with
{
PendingStudents = new DressRulePair(FormPolicies.Strict, ColorPolicies.NoBright),
};
MorningDress.Apply(school);
Assert.Equal(before, BrightWornCount(school));
school.Clock.JumpTo(new DateTime(2012, 4, 4, 6, 0, 0, DateTimeKind.Utc));
ApparelWear.Apply(school, gameMinutes: 0, before: new DateTime(2012, 4, 3, 22, 0, 0, DateTimeKind.Utc));
Assert.Equal(0, BrightWornCount(school));
}
[Fact]
public void PendingNoBright_AfterMorning_RemovesBrightWornColours()
{
using var school = Open(TuesdayMorning, seed: 31);
MorningDress.Apply(school);
school.DressRules = school.DressRules with
{
PendingStudents = new DressRulePair(FormPolicies.Regular, ColorPolicies.Free),
};
AdvanceMorning(school);
Assert.True(BrightWornCount(school) > 0, "Free colours should allow at least one bright item.");
school.DressRules = school.DressRules with
{
PendingStudents = new DressRulePair(FormPolicies.Regular, ColorPolicies.NoBright),
};
AdvanceMorning(school);
Assert.Equal(0, BrightWornCount(school));
}
[Fact]
public void WhiteTopBlackBottom_AfterMorning_WhiteShirtsOnStudents()
{
using var school = Open(TuesdayMorning, seed: 31);
MorningDress.Apply(school);
school.DressRules = school.DressRules with
{
PendingStudents = new DressRulePair(FormPolicies.Regular, ColorPolicies.WhiteTopBlackBottom),
};
AdvanceMorning(school);
var checkedAny = false;
foreach (var pupil in school.Roster!.People.Where(person => person.IsStudent))
{
foreach (var item in pupil.Items.Where(row => row.Location == ItemLocations.Worn))
{
if (!school.Catalog!.Things[item.Def].Layers.Any(layer =>
layer.Equals(ApparelLayers.Top, StringComparison.Ordinal)))
{
continue;
}
checkedAny = true;
Assert.Equal("White", item.Color);
}
}
Assert.True(checkedAny, "Expected at least one pupil with a top-layer shirt.");
}
private static void AdvanceMorning(School school)
{
var before = school.Clock.Time;
school.Clock.JumpTo(before.Date.AddDays(1).AddHours(6));
ApparelWear.Apply(school, gameMinutes: 0, before);
}
private static int BrightWornCount(School school)
{
var count = 0;
foreach (var pupil in school.Roster!.People.Where(person => person.IsStudent))
{
foreach (var item in pupil.Items.Where(row => row.Location == ItemLocations.Worn))
{
if (item.Color is { } color
&& school.Catalog!.Colors.TryGetValue(color, out var colorDef)
&& colorDef.Tags.Contains(ColorTags.Bright, StringComparer.Ordinal))
{
count++;
}
}
}
return count;
}
private static void ReplaceItems(Person person, List<InventoryItem> items)
{
if (person.Items is IList<InventoryItem> list && !list.IsReadOnly)
{
list.Clear();
foreach (var item in items)
{
list.Add(item);
}
}
}
private static (School School, string PupilId) StaffedPeClass(bool forceBoy = false)
{
var (catalog, map) = Vanilla();
var roster = RosterGenerator.Generate(catalog, map, schoolSeed: 1, "Russia", TuesdayMorning);
roster = LockerAssigner.Apply(catalog, map, roster);
roster = DressGenerator.EnsureRoster(catalog, roster, schoolSeed: 1, TuesdayMorning);
var pool = ApplicantPool.Create(catalog, roster, schoolSeed: 1, "Russia", TuesdayMorning);
var schoolClass = roster.Classes.First(row =>
row.RoomId is "classroom-101" or "classroom-102" or "classroom-103" or "classroom-104");
var pupil = roster.People
.Where(person => schoolClass.PupilIds.Contains(person.Id))
.First(person => forceBoy ? !person.Female : true);
var school = School.Create(1, "PE", TuesdayMorning, catalog, map);
school.InstallPeople(roster, seed: 1, "Russia", pool, climatePresetId: "TemperateContinental");
school.SetTimetable(new Timetable(
[
new LessonPlacement(schoolClass.Id, "Mathematics", "t1", schoolClass.RoomId, Day: 1, Period: 1),
new LessonPlacement(schoolClass.Id, "PhysicalEducation", "t2", "gym-hall", Day: 1, Period: 2),
],
[]));
school.ConfigurePresence(weekDays: 5, maxDecisionsPerTick: 10_000);
return (school, pupil.Id);
}
private static School Open(DateTime start, int seed)
{
var (catalog, map) = Vanilla();
var roster = RosterGenerator.Generate(catalog, map, seed, "Russia", start);
roster = LockerAssigner.Apply(catalog, map, roster);
roster = DressGenerator.EnsureRoster(catalog, roster, seed, start);
var pool = ApplicantPool.Create(catalog, roster, seed, "Russia", start);
var school = School.Create(1, "Одежда", start, catalog, map);
school.InstallPeople(roster, seed, "Russia", pool, climatePresetId: "TemperateContinental");
school.ConfigurePresence(weekDays: 5, maxDecisionsPerTick: 10_000);
return school;
}
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 = new List<ContentDocument>();
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('\\', '/');
documents.Add(new ContentDocument(CatalogLoader.CorePackId, relative, File.ReadAllText(path)));
}
var catalog = new CatalogLoader().Load([CatalogLoader.CorePackId], documents);
var map = CatalogLoader.LastDefaultMap([CatalogLoader.CorePackId], documents);
Assert.NotNull(map);
return (catalog, map);
}
}