Files
h-school/src/HSchool.People/FamilyFactory.cs
T

299 lines
10 KiB
C#

namespace HSchool.People;
internal static class FamilyFactory
{
public static (Family Family, List<Person> Members) Create(
DefCatalog catalog,
NameSetDef names,
int schoolSeed,
FamilyPlan plan,
DateTime yearStart,
DateTime asOf)
{
var rng = new Random(Seed.Mix(schoolSeed, plan.FamilyIndex, Seed.AppearanceSalt));
var familyId = $"f{plan.FamilyIndex}";
var surname = names.Surnames[rng.Next(names.Surnames.Count)];
var childDrafts = new ChildDraft[plan.Seats.Count];
var usedGiven = new HashSet<string>(StringComparer.Ordinal);
for (var i = 0; i < plan.Seats.Count; i++)
{
var seat = plan.Seats[i];
var female = rng.Next(2) == 0;
var given = PickGiven(female ? names.FemaleGiven : names.MaleGiven, rng, usedGiven);
var (first, last) = SchoolYears.BirthWindow(yearStart, seat.Year);
childDrafts[i] = new ChildDraft(seat, female, given, SchoolYears.RandomInRange(rng, first, last));
}
var fatherGiven = PickGiven(names.MaleGiven, rng);
var motherGiven = PickGiven(names.FemaleGiven, rng);
var fatherPatronymicSource = PickGiven(names.MaleGiven, rng);
var motherPatronymicSource = PickGiven(names.MaleGiven, rng);
DateTime motherBirth;
DateTime fatherBirth;
if (childDrafts.Length == 0)
{
motherBirth = asOf.AddYears(-(28 + rng.Next(28))).AddDays(-rng.Next(365));
fatherBirth = motherBirth.AddDays(rng.Next(-5 * 365, (5 * 365) + 1));
}
else
{
var oldestBirth = childDrafts[0].Birth;
foreach (var child in childDrafts)
{
if (child.Birth < oldestBirth)
{
oldestBirth = child.Birth;
}
}
var motherYearsOlder = 22 + rng.Next(17);
motherBirth = oldestBirth.AddYears(-motherYearsOlder).AddDays(-rng.Next(30));
fatherBirth = motherBirth.AddDays(rng.Next(-5 * 365, (5 * 365) + 1));
}
var members = new List<Person>();
var fatherId = $"{familyId}.p0";
var motherId = $"{familyId}.p1";
members.Add(
RollAdult(
catalog,
names,
rng,
fatherId,
familyId,
female: false,
fatherBirth,
asOf,
surname,
fatherGiven,
NameGrammar.Patronymic(fatherPatronymicSource.Form, female: false, names.PatronymicRule),
isParent: childDrafts.Length > 0));
members.Add(
RollAdult(
catalog,
names,
rng,
motherId,
familyId,
female: true,
motherBirth,
asOf,
surname,
motherGiven,
NameGrammar.Patronymic(motherPatronymicSource.Form, female: true, names.PatronymicRule),
isParent: childDrafts.Length > 0));
var childIds = new List<string>(childDrafts.Length);
for (var i = 0; i < childDrafts.Length; i++)
{
var draft = childDrafts[i];
var id = $"{familyId}.c{i}";
childIds.Add(id);
members.Add(
RollChild(
catalog,
names,
rng,
id,
familyId,
draft,
asOf,
surname,
fatherGiven.Form));
}
var family = new Family(familyId, [fatherId, motherId], childIds);
return (family, members);
}
public static (Family Family, List<Person> Members) CreateStaffOnly(
DefCatalog catalog,
NameSetDef names,
int schoolSeed,
int familyIndex,
DateTime asOf)
{
var empty = new FamilyPlan(familyIndex, []);
return Create(catalog, names, schoolSeed, empty, SchoolYears.StartOn(asOf), asOf);
}
private static Person RollAdult(
DefCatalog catalog,
NameSetDef names,
Random rng,
string id,
string familyId,
bool female,
DateTime birth,
DateTime asOf,
SurnameEntry surname,
GivenNameEntry given,
string patronymic,
bool isParent)
{
var age = SchoolYears.AgeYears(birth, asOf);
var roles = isParent ? new[] { PersonRoles.Parent } : new[] { PersonRoles.Staff };
return FinishPerson(
catalog,
names,
rng,
id,
familyId,
female,
birth,
asOf,
age,
surname,
given,
patronymic,
roles,
isStudent: false,
isParent,
classId: null);
}
private static Person RollChild(
DefCatalog catalog,
NameSetDef names,
Random rng,
string id,
string familyId,
ChildDraft draft,
DateTime asOf,
SurnameEntry surname,
string fatherGiven)
{
var age = SchoolYears.AgeYears(draft.Birth, asOf);
var patronymic = NameGrammar.Patronymic(fatherGiven, draft.Female, names.PatronymicRule);
return FinishPerson(
catalog,
names,
rng,
id,
familyId,
draft.Female,
draft.Birth,
asOf,
age,
surname,
draft.Given,
patronymic,
[PersonRoles.Student],
isStudent: true,
isParent: false,
draft.Seat.ClassId);
}
private static Person FinishPerson(
DefCatalog catalog,
NameSetDef names,
Random rng,
string id,
string familyId,
bool female,
DateTime birth,
DateTime asOf,
int age,
SurnameEntry surname,
GivenNameEntry given,
string patronymic,
IReadOnlyList<string> roles,
bool isStudent,
bool isParent,
string? classId)
{
var (numbers, choices) = PersonSampler.Body(catalog, rng, female, age);
var traits = PersonSampler.Traits(catalog, rng, roles, age);
var skills = PersonSampler.Skills(catalog, rng, age, choices, traits);
var needs = PersonSampler.Needs(catalog);
var name = new PersonName(
given.Form,
female ? surname.Female : surname.Male,
patronymic,
GivenTable(given, names.DefaultGivenDeclension),
SurnameTable(surname, female, names.DefaultSurnameDeclension),
PatronymicTable(patronymic, female));
return new Person
{
Id = id,
FamilyId = familyId,
Female = female,
BirthDate = DateTime.SpecifyKind(birth, DateTimeKind.Utc),
Name = name,
IsStudent = isStudent,
IsStaff = false,
IsParent = isParent,
ClassId = classId,
Numbers = numbers,
Choices = choices,
Skills = skills,
Traits = traits,
Needs = needs,
};
}
private static GivenNameEntry PickGiven(
IReadOnlyList<GivenNameEntry> pool,
Random rng,
HashSet<string>? used = null)
{
if (pool.Count == 0)
{
throw new InvalidOperationException("Name set has no given names for this sex.");
}
for (var attempt = 0; attempt < 16; attempt++)
{
var pick = pool[rng.Next(pool.Count)];
if (used is null || used.Add(pick.Form) || used.Count >= pool.Count)
{
return pick;
}
}
return pool[rng.Next(pool.Count)];
}
private static CaseTable GivenTable(GivenNameEntry entry, string defaultModel) =>
new()
{
Nom = NameGrammar.InflectGiven(entry, GrammaticalCase.Nominative, defaultModel),
Gen = NameGrammar.InflectGiven(entry, GrammaticalCase.Genitive, defaultModel),
Dat = NameGrammar.InflectGiven(entry, GrammaticalCase.Dative, defaultModel),
Acc = NameGrammar.InflectGiven(entry, GrammaticalCase.Accusative, defaultModel),
Ins = NameGrammar.InflectGiven(entry, GrammaticalCase.Instrumental, defaultModel),
Pre = NameGrammar.InflectGiven(entry, GrammaticalCase.Prepositional, defaultModel),
};
private static CaseTable SurnameTable(SurnameEntry entry, bool female, string defaultModel) =>
new()
{
Nom = NameGrammar.InflectSurname(entry, female, GrammaticalCase.Nominative, defaultModel),
Gen = NameGrammar.InflectSurname(entry, female, GrammaticalCase.Genitive, defaultModel),
Dat = NameGrammar.InflectSurname(entry, female, GrammaticalCase.Dative, defaultModel),
Acc = NameGrammar.InflectSurname(entry, female, GrammaticalCase.Accusative, defaultModel),
Ins = NameGrammar.InflectSurname(entry, female, GrammaticalCase.Instrumental, defaultModel),
Pre = NameGrammar.InflectSurname(entry, female, GrammaticalCase.Prepositional, defaultModel),
};
private static CaseTable PatronymicTable(string nominative, bool female) =>
new()
{
Nom = nominative,
Gen = NameGrammar.InflectPatronymic(nominative, female, GrammaticalCase.Genitive),
Dat = NameGrammar.InflectPatronymic(nominative, female, GrammaticalCase.Dative),
Acc = NameGrammar.InflectPatronymic(nominative, female, GrammaticalCase.Accusative),
Ins = NameGrammar.InflectPatronymic(nominative, female, GrammaticalCase.Instrumental),
Pre = NameGrammar.InflectPatronymic(nominative, female, GrammaticalCase.Prepositional),
};
private readonly record struct ChildDraft(
PupilSeat Seat,
bool Female,
GivenNameEntry Given,
DateTime Birth);
}