-
Notifications
You must be signed in to change notification settings - Fork 0
/
consanguinity.js
163 lines (144 loc) · 5.9 KB
/
consanguinity.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
const lineage = require('./lineage');
const numToOrdinal = (num) => {
if (num === 1) return 'first';
if (num === 2) return 'second';
if (num === 3) return 'third';
return `${num}th`;
};
const numOfTimes = (num) => {
if (num === 1) return 'once';
if (num === 2) return 'twice';
return `${num} times`;
};
const parentStrings = (r, rStr, isReversed) => {
const strings = [
[`${r[0]} is the ${rStr}father of ${r[1]}.`, `${r[0]} is the ${rStr}mother of ${r[1]}.`, `${r[0]} is the ${rStr}parent of ${r[1]}.`],
[`${r[1]} is the ${rStr}son of ${r[0]}.`, `${r[1]} is the ${rStr}daughter of ${r[0]}.`, `${r[1]} is the ${rStr}child of ${r[0]}.`],
];
return {
strings: isReversed ? strings.reverse() : strings,
labelIndices: [10, -7],
};
};
const auntuncleStrings = (r, rStr, isReversed) => {
const strings = [
[`${r[0]} is the ${rStr}uncle of ${r[1]}.`, `${r[0]} is the ${rStr}aunt of ${r[1]}.`, `${r[0]} is the ${rStr}aunt/uncle of ${r[1]}.`],
[`${r[1]} is the ${rStr}nephew of ${r[0]}.`, `${r[1]} is the ${rStr}niece of ${r[0]}.`, `${r[1]} is the ${rStr}nephew/niece of ${r[0]}.`],
];
return {
strings: isReversed ? strings.reverse() : strings,
labelIndices: [10, -7],
};
};
/**
* Returns general "template" strings that describe the consanguineous relationship
* (e.g. mother, granduncle, third cousin) for two characters. Provides strings for
* both directions, with placeheld names, and for different genders.
* Labels agree with this chart:
* https://commons.wikimedia.org/wiki/File:Table_of_Consanguinity_showing_degrees_of_relationship.svg
* @param {number} degree1 Number of generations between the first character and the
* last common ancestor.
* @param {number} degree2 Number of generations between the second character and the
* last common ancestor.
* @returns {Object} An object. strings: a 2D array with 1-2 rows. For 2 rows, the first
* belongs to the first character, and the second belongs to the second. For 1,
* the first describes both characters. The placeholder for the first and second
* characters' names are $1 and $2, respectively. There are always three columns;
* index 0, 1, 2 always correspond with male, female, and unknown labels for the
* row's character. labelIndices: array of slice indices to isolate the label in
* any of the strings.
*/
const generateTemplates = (degree1, degree2) => {
let first = degree1;
let second = degree2;
let r = ['$1', '$2'];
for (let i = 0; i < 2; i += 1) {
if (first === 0) {
switch (second) {
case 0:
return {
strings: [['$1 is himself.', '$1 is herself.', '$1 is themself.']],
labelIndices: [6, -1],
};
case 1:
return parentStrings(r, '', i === 1);
case 2:
return parentStrings(r, 'grand', i === 1);
default:
return parentStrings(r, `${'great-'.repeat(second - 2)}grand`, i === 1);
}
}
if (first === 1) {
switch (second) {
case 0:
return parentStrings(r.reverse(), '', i !== 1);
case 1:
const strings = [
[`${r[0]} is the brother of ${r[1]}.`, `${r[0]} is the sister of ${r[1]}.`, `${r[0]} is the sibling of ${r[1]}.`],
[`${r[1]} is the brother of ${r[0]}.`, `${r[1]} is the sister of ${r[0]}.`, `${r[1]} is the sibling of ${r[0]}.`],
];
return {
strings: i === 1 ? strings.reverse() : strings,
labelIndices: [10, -7],
};
case 2:
return auntuncleStrings(r, '', i === 1);
case 3:
return auntuncleStrings(r, 'great-', i === 1);
default:
return auntuncleStrings(r, `${'great-'.repeat(second - 2)}grand`, i === 1);
}
}
if (first === second) {
return {
strings: [Array(3).fill(`$1 and $2 are ${numToOrdinal(first - 1)} cousins.`)],
labelIndices: [14, -1],
};
}
first = degree2;
second = degree1;
r = r.reverse();
}
const diff = Math.abs(first - second);
return {
strings: [Array(3).fill(`$1 and $2 are ${numToOrdinal(Math.min(first, second) - 1)} cousins ${numOfTimes(diff)} removed.`)],
labelIndices: [14, -1],
};
};
/**
* Returns relevant consanguinity information.
* Gender values: 0: male, 1: female, 2: unknown.
* @param {Line} line1 Line from the first character to the common relative.
* @param {0|1|2} gender1 Gender value of the first character.
* @param {Line} line2 Line from the second character to the common relative.
* @param {0|1|2} gender2 Gender value of the second character.
* @param {boolean} countAdopted Whether to include adoptive relationships.
* @param {boolean} putNames Whether to substitute placeholders for real character names.
* @param {boolean} labelOnly Whether to return the labels rather than descriptive sentences.
* @returns {Object} An object. result: array of 1 or 2 strings. isFull: same as
* lineage.isFullRelationship() return value.
*/
exports.determine = (line1, gender1, line2, gender2, countAdopted, putNames, labelOnly) => {
const result = [];
const last = [line1.size - 1, line2.size - 1];
const csResult = generateTemplates(last[0], last[1]);
const isFull = lineage.isFullRelationship(line1, line2, countAdopted);
const relevantTemplates = [csResult.strings[0][gender1]];
if (csResult.strings.length === 2) relevantTemplates.push(csResult.strings[1][gender2]);
for (const str of relevantTemplates) {
let next;
if (labelOnly) {
const label = str.slice(...csResult.labelIndices);
next = isFull.result ? label : `half-${label}`;
} else {
next = isFull.result ? str
: `${str.slice(0, csResult.labelIndices[0])}half-${str.slice(csResult.labelIndices[0])}`;
}
if (putNames) {
next = next.replace(/\$1/g, line1.youngestMember)
.replace(/\$2/g, line2.youngestMember);
}
result.push(next);
}
return { result, isFull };
};