Adds outfit evaluation, morning home dressing, locker-room ChangeClothes with day-log events, PE/weather AI goals, dress-rules API, and tests.
221 lines
8.8 KiB
C#
221 lines
8.8 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 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));
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|