Enhance school creation functionality by introducing support for name sets in the API and UI. Update the catalog to include skills, traits, body attributes, needs, and name sets, improving character generation capabilities. Revise localization strings for better user guidance and update tests to validate the new name set functionality and ensure robustness in school creation processes.
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
namespace HSchool.People;
|
||||
|
||||
/// <summary>
|
||||
/// Builds a roster from a catalog, a map and a school seed. Deterministic: same inputs, same people.
|
||||
/// </summary>
|
||||
public static class RosterGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Matches <c>SimulationOptions.DefaultStartDate</c> so tests without a clock still sit in
|
||||
/// the default school year. Callers with a live clock must pass <paramref name="asOf"/>.
|
||||
/// </summary>
|
||||
public static readonly DateTime DefaultAsOf = new(2012, 4, 3, 6, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
public static Roster Generate(
|
||||
DefCatalog catalog,
|
||||
MapLayout map,
|
||||
int schoolSeed,
|
||||
string nameSetId,
|
||||
DateTime? asOf = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(catalog);
|
||||
ArgumentNullException.ThrowIfNull(map);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(nameSetId);
|
||||
|
||||
if (!catalog.NameSets.TryGetValue(nameSetId, out var names))
|
||||
{
|
||||
throw new ArgumentException($"Unknown name set '{nameSetId}'.", nameof(nameSetId));
|
||||
}
|
||||
|
||||
var when = DateTime.SpecifyKind(asOf ?? DefaultAsOf, DateTimeKind.Utc);
|
||||
var yearStart = SchoolYears.StartOn(when);
|
||||
var demand = SchoolDemand.From(catalog, map);
|
||||
var plans = FamilyPlanner.Plan(schoolSeed, demand.Seats);
|
||||
|
||||
var people = new List<Person>();
|
||||
var families = new List<Family>(plans.Count);
|
||||
foreach (var plan in plans)
|
||||
{
|
||||
var (family, members) = FamilyFactory.Create(catalog, names, schoolSeed, plan, yearStart, when);
|
||||
families.Add(family);
|
||||
people.AddRange(members);
|
||||
}
|
||||
|
||||
var nextFamily = plans.Count;
|
||||
while (people.Count(person => !person.IsStudent) < demand.Staff.Count)
|
||||
{
|
||||
var (family, members) = FamilyFactory.CreateStaffOnly(catalog, names, schoolSeed, nextFamily, when);
|
||||
families.Add(family);
|
||||
people.AddRange(members);
|
||||
nextFamily++;
|
||||
}
|
||||
|
||||
var staffed = AssignStaff(people, demand.Staff);
|
||||
var classes = FillClasses(demand.Classes, staffed);
|
||||
return new Roster(staffed, families, classes);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Person> AssignStaff(List<Person> people, IReadOnlyList<StaffOpening> openings)
|
||||
{
|
||||
if (openings.Count == 0)
|
||||
{
|
||||
return people;
|
||||
}
|
||||
|
||||
var jobs = new Dictionary<string, StaffOpening>(StringComparer.Ordinal);
|
||||
var index = 0;
|
||||
foreach (var person in people)
|
||||
{
|
||||
if (person.IsStudent || index >= openings.Count)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
jobs[person.Id] = openings[index];
|
||||
index++;
|
||||
}
|
||||
|
||||
if (jobs.Count == 0)
|
||||
{
|
||||
return people;
|
||||
}
|
||||
|
||||
var result = new Person[people.Count];
|
||||
for (var i = 0; i < people.Count; i++)
|
||||
{
|
||||
var person = people[i];
|
||||
result[i] = jobs.TryGetValue(person.Id, out var job)
|
||||
? person with
|
||||
{
|
||||
IsStaff = true,
|
||||
Position = job.Position,
|
||||
WorkplaceRoomId = job.RoomId,
|
||||
}
|
||||
: person;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<SchoolClass> FillClasses(
|
||||
IReadOnlyList<SchoolClass> classes,
|
||||
IReadOnlyList<Person> people)
|
||||
{
|
||||
var pupils = new Dictionary<string, List<string>>(StringComparer.Ordinal);
|
||||
foreach (var schoolClass in classes)
|
||||
{
|
||||
pupils[schoolClass.Id] = new List<string>(schoolClass.Capacity);
|
||||
}
|
||||
|
||||
foreach (var person in people)
|
||||
{
|
||||
if (person.ClassId is { } classId && pupils.TryGetValue(classId, out var list))
|
||||
{
|
||||
list.Add(person.Id);
|
||||
}
|
||||
}
|
||||
|
||||
var filled = new SchoolClass[classes.Count];
|
||||
for (var i = 0; i < classes.Count; i++)
|
||||
{
|
||||
var schoolClass = classes[i];
|
||||
filled[i] = schoolClass with { PupilIds = pupils[schoolClass.Id] };
|
||||
}
|
||||
|
||||
return filled;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user