Skip to content

Commit d33b1a0

Browse files
committed
Exhaustively specify how to handle every part of a FaceConfig object in generateRelative, and have TypeScript enforce that it's exhaustive!
1 parent 0934b74 commit d33b1a0

File tree

1 file changed

+64
-27
lines changed

1 file changed

+64
-27
lines changed

src/generateRelative.ts

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,45 +26,82 @@ export const generateRelative = ({
2626
race,
2727
});
2828

29-
// Regenerate some properties always, and others with some probabilityF
30-
const probRegenerate = 0.25;
31-
const regenerateProperties = {
29+
// Regenerate some properties always, and others with some probability
30+
type RegenerateType =
31+
| "always"
32+
| "never"
33+
| "sometimes"
34+
| "sometimesIfRaceIsKnown";
35+
type ToRegenerateProperties<T, U> = T extends object
36+
? T extends any[]
37+
? U
38+
: {
39+
[K in keyof T]: T[K] extends object
40+
? ToRegenerateProperties<T[K], U> | U
41+
: U;
42+
}
43+
: U;
44+
type RegenerateProperties = ToRegenerateProperties<
45+
FaceConfig,
46+
RegenerateType
47+
>;
48+
49+
const regenerateProperties: RegenerateProperties = {
3250
accessories: "always",
33-
"body.id": "sometimes",
34-
"body.size": "always",
35-
"ear.id": "sometimes",
36-
"ear.size": "sometimes",
37-
"eye.angle": "sometimes",
38-
"eye.id": "sometimes",
39-
"eyebrow.angle": "sometimes",
40-
"eyebrow.id": "sometimes",
51+
body: {
52+
color: "sometimesIfRaceIsKnown",
53+
id: "sometimes",
54+
size: "always",
55+
},
56+
ear: "sometimes",
57+
eye: "sometimes",
58+
eyebrow: "sometimes",
4159
eyeLine: "sometimes",
42-
"face.body.color": "sometimesIfRaceIsKnown",
43-
"face.hair.color": "sometimesIfRaceIsKnown",
4460
facialHair: "always",
4561
fatness: "always",
4662
glasses: "always",
47-
"hair.flip": "always",
48-
"hair.id": "always",
63+
hair: {
64+
color: "sometimesIfRaceIsKnown",
65+
flip: "always",
66+
id: "always",
67+
},
4968
hairBg: "always",
50-
"head.id": "sometimes",
51-
"head.shave": "always",
69+
head: {
70+
id: "sometimes",
71+
shave: "always",
72+
},
73+
jersey: "never",
5274
miscLine: "sometimes",
5375
mouth: "sometimes",
5476
nose: "sometimes",
5577
smileLine: "sometimes",
56-
} as const;
78+
teamColors: "never",
79+
};
5780

58-
for (const [path, regenerateType] of Object.entries(regenerateProperties)) {
59-
if (
60-
regenerateType === "always" ||
61-
((regenerateType === "sometimes" ||
62-
(regenerateType === "sometimesIfRaceIsKnown" && race !== undefined)) &&
63-
Math.random() < probRegenerate)
64-
) {
65-
dset(face, path, delve(randomFace, path));
81+
const probRegenerate = 0.25;
82+
const processRegenerateProperties = (
83+
objOutput: any,
84+
objRandom: any,
85+
regeneratePropertiesLocal:
86+
| RegenerateProperties
87+
| Record<string, RegenerateType>,
88+
) => {
89+
for (const [key, value] of Object.entries(regeneratePropertiesLocal)) {
90+
if (typeof value === "string") {
91+
if (
92+
value === "always" ||
93+
((value === "sometimes" ||
94+
(value === "sometimesIfRaceIsKnown" && race !== undefined)) &&
95+
Math.random() < probRegenerate)
96+
) {
97+
objOutput[key] = objRandom[key];
98+
}
99+
} else {
100+
processRegenerateProperties(objOutput[key], objRandom[key], value);
101+
}
66102
}
67-
}
103+
};
104+
processRegenerateProperties(face, randomFace, regenerateProperties);
68105

69106
// Override any ID properties that are not valid for the specified gender
70107
for (const key of features) {

0 commit comments

Comments
 (0)