From 4511d33c8aa9b7f1ca70682f16513bfb4b189257 Mon Sep 17 00:00:00 2001 From: Svornost <11434-svornost@users.noreply.gitgud.io> Date: Fri, 31 Jul 2020 16:35:32 -0700 Subject: [PATCH] Use tabs for indentation in ibcJS.js --- src/js/ibcJS.js | 999 ++++++++++++++++++++++++------------------------ 1 file changed, 499 insertions(+), 500 deletions(-) diff --git a/src/js/ibcJS.js b/src/js/ibcJS.js index c0330d7f151..66a22c0c815 100644 --- a/src/js/ibcJS.js +++ b/src/js/ibcJS.js @@ -1,502 +1,501 @@ globalThis.ibc = (() => { - // These IDs are considered to be unknown parents - let or_null = (s) => specificCharacterID(s) ? s : null; - - // Find some sort of state for a slave. Checks first the gene pool, then V.slaves, then the - // missing table - let find_gp = (id) => (slaveStateById(id) || V.genePool.find((s) => s.ID == id) || ((id in V.missingTable) ? V.missingTable[id] : null) || null); - - // Create a node for the given ID - let create_node = (id) => ({ - id: id, // Node ID - mother: null, - father: null, - nodecodes: [], // NodeCodes - nodecodes_str: [], // String version of the NodeCodes, for comparison - _coeff: null, // Cached CoI - child_count: 0 // Number of children of the node, for computing NodeCodes - }); - - // Determine the length of the shared prefix between the two NodeCode parameters - let prefix_len = (nca, ncb) => { - let i = 0; - for (i=0; i<Math.min(nca.length, ncb.length); i++) { - if (nca[i] !== ncb[i]) - break; - } - return i; - }; - - // Determine the set of longest common prefixes for a node pair - let prefixes = (a, b) => { - let found = []; - let found_s = []; - a.nodecodes.forEach(nca => { - let match = false; - b.nodecodes.forEach(ncb => { - let l = prefix_len(nca, ncb); - if (l === 0 && match) - return; - - if (l > 0) { - match = true; - let pfx = nca.slice(0,l); - let pfx_s = pfx.join(';'); - - if (!found_s.includes(pfx_s)) { - found_s.push(pfx_s); - found.push(pfx); - } - } - }); - }); - - return found; - }; - - // Search up the tree to find a given NodeCode, starting at `n` - let find_nc = (nc, n) => { - if (n.nodecodes_str.includes(nc.join(';'))) { - return n; - } - - let ret = null; - if (n.mother !== null) - ret = find_nc(nc, n.mother); - if (n.father !== null && ret === null) - ret = find_nc(nc, n.father); - - return ret; - }; - - // Determine the set of common ancestors between a node pair - let common = (a, b) => { - let pfx = prefixes(a, b); - let pfx_s = pfx.map(s => s.join(';')); - let anc = []; - - while (pfx.length > 0) { - let p = pfx.pop(0); - pfx_s.pop(0); - let ret = find_nc(p, a); - - ret.nodecodes.forEach(nc => { - let i = pfx_s.indexOf(nc.join(';')); - if (i === -1) - return; - - pfx.pop(i); - pfx_s.pop(i); - }); - - if (anc.findIndex(s => (s[0] == ret)) === -1) - anc.push([ret, p]); - } - - return anc; - }; - - // Determine the set of NodeCodes on `n` with prefix `x` - let mps = (n, x) => { - let x_s = x.join(';'); - return n.nodecodes.filter(nc => (nc.slice(0, x.length).join(';') === x_s)); - }; - - // Compute the set of all paths to the parents of `n` with prefix `x` - let pp = (mother, father, x) => { - let m = mps(mother, x); - let f = mps(father, x); - - let prod = []; - m.forEach(i => { - f.forEach(j => { - prod.push([i, j]); - }); - }); - - return prod; - }; - - let kinship = (mother, father) => { - let _coeff = 0; - if (mother === null || father === null) - _coeff = 0; - else if (mother == father) - _coeff = 0.5 * (1 + coeff(mother)); - else { - let cf = 0; - let cmn = common(mother, father); - - cmn.forEach(el => { - let c = el[0]; - let p = el[1]; - let p_s = p.join(';'); - - let paths = pp(mother, father, p); - let paths_s = paths.map(p => [p[0].join(';'), p[1].join(';')].join(',')); - - cmn.forEach(el2 => { - let co = el2[0]; - if (co == c) - return; - let found = []; - let m_pp = []; - let f_pp = []; - - co.nodecodes.forEach(nc => { - if (nc.slice(0, p.length).join(';') != p_s) - return; - - m_pp = m_pp.concat(mps(mother, nc)); - f_pp = f_pp.concat(mps(father, nc)); - }); - - m_pp.forEach(mp => { - f_pp.forEach(fp => { - let mf_s = [mp.join(';'), fp.join(';')].join(','); - let i = paths_s.indexOf(mf_s); - if (i === -1) - return; - paths_s.pop(i); - paths.pop(i); - }); - }); - }); - paths.forEach(p => { - let pfx = prefix_len(p[0], p[1]); - - cf += 0.5**(p[0].length + p[1].length+1 - 2*pfx) * (1 + coeff(c)); - }); - }); - - _coeff = cf; - } - - return _coeff; - }; - - // Determine the coefficient of inbreeding of a node `n` - let coeff = n => { - if (n._coeff === null) - n._coeff = kinship(n.mother, n.father); - return n._coeff; - }; - - // Populate the NodeCodes. - // - // Each node has a set of NodeCodes, which represent the set of paths from it to its ancestors. - // NodeCodes here are represented by arrays of integers. - // - // NodeCodes are constructed recursively in this fashion: - // - // - Assign each of the founders (nodes with both parents === null) an unique ID, starting from - // 0 and incrementing each time (the order doesn't matter); a founder's set of NodeCodes has - // exactly one NodeCode, which is [ID] (an array containing only their ID) - // - // - For each other node, let M be its child number w.r.t. its mother and N its child number - // w.r.t. its father, i.e. the number of children that the respective parent has had before - // this one (the order is not important to the algorithm, it's arbitrary here for - // convenience). Its set of NodeCodes is the set of all its mother's NodeCodes with M appended - // and all of its father's NodeCodes with N appended. For example, if its mother has the - // NodeCodes [[2]] and M = 3 and its father has the NodeCodes [[0,1], [3,1]] and N = 1 then - // the set of NodeCodes for this node would be - // - // [[2, 3], [0, 1, 1], [3, 1, 1]] - // - // We do this iteratively here, looping over the set of all nodes until each has been assigned - // a NodeCode. This requires looping through a number of times equal to the number of - // generations, since as soon as both parents have NodeCodes their children's NodeCodes may be - // computed. - let make_nc = nodes => { - // Generate founder NodeCodes - let total = Object.keys(nodes).length; - let seen = []; - let curid = 0; - Object.values(nodes).forEach(n => { - if (n.mother !== null || n.father !== null) - return; - n.nodecodes.push([curid]); - curid += 1; - seen.push(n.id); - }); - - // Generate the rest of the NodeCodes - let oldSeenLength = -1; - while (seen.length !== total) { - oldSeenLength = seen.length; - Object.keys(nodes).forEach(s=> { - let n = nodes[s]; - if (seen.includes(+s)) // We've already done this - return; - else if ((n.mother !== null && n.mother.nodecodes.length === 0) || (n.father !== null && n.father.nodecodes.length === 0)) // Too soon, we haven't done its parents - return; - - seen.push(n.id); - // Compute the NodeCodes from its parents - [n.mother, n.father].forEach((a, i) => { - if (a === null || (n.mother === n.father && i === 1)) // Ignore missing parents/repeated - return; - - a.nodecodes.forEach(nc => { - // Copy the NodeCode, push the child number, then add it - let nnc = nc.slice(); - nnc.push(a.child_count); - n.nodecodes.push(nnc); - }); - a.child_count += 1; - }); - - // NodeCodes must be sorted; this suffices - n.nodecodes.sort(); - }); - // check to make sure we're progressing...if not, there's a cycle in the graph - // dump all the nodes participating in or descended from the cycle and let the player figure it out - if (oldSeenLength === seen.length) { - const badSlaveIDs = Object.keys(nodes).filter(s => !seen.includes(+s)).map(k => nodes[k].id); - throw(`Inbreeding calculation: heritance cycle detected. Check slave IDs: ${badSlaveIDs}`); - } - } - - // Cache the string NodeCodes - Object.values(nodes).forEach(n => { - n.nodecodes_str = n.nodecodes.map(nc => nc.join(';')); - }); - }; - - // Make nodes for an array of slaves - let nodes_slaves = (slaves, ignore_coeffs=false) => { - let nodes = {}; - - // Recursively create the nodes we need, moving upwards from the given slave - let create_node_rec = s => { - // Certain parents (e.g. 0, societal elite) are not considered to be related, despite - // having the same ID; convert them to null - let m = or_null(s.mother); - let f = or_null(s.father); - - // Ensure that parent nodes are created - [m, f].forEach(p => { - if (p !== null && !(p in nodes)) { // Not created, we have to do something - if (p === -1) { - create_node_rec(SugarCube.State.variables.PC); - } else { - // Search for a slave state, genePool entry, or missingTable entry - let gp = find_gp(p); - if (gp !== null) { - // If we find one, we might have ancestry information: recurse - create_node_rec(gp); - } else { - // Otherwise, just create a plain node - nodes[p] = create_node(p); - } - } - } - }); - - if (!(s.ID in nodes)) // Create this node if necessary - nodes[s.ID] = create_node(s.ID); - // We created its parents earlier, so set them to the actual nodes - nodes[s.ID].mother = (m === null) ? m : nodes[m]; - nodes[s.ID].father = (f === null) ? f : nodes[f]; - - // Try to use a cached CoI for performance - let sg = find_gp(s.ID); - if (!ignore_coeffs && sg !== null && "inbreedingCoeff" in sg && sg.inbreedingCoeff !== -1) { - nodes[s.ID]._coeff = sg.inbreedingCoeff; - } - }; - - // Populate the nodes - slaves.forEach(s=> create_node_rec(s)); - - // Populate NodeCodes - make_nc(nodes); - - return nodes; - } - - // Determine the coefficients of inbreeding of an array of slaves. Returns a mapping of their - // ID to their coefficient of inbreeding - let coeff_slaves = (slaves, ignore_coeffs=false) => { - let ret = {}; - // First, pull as many existing CoI off the slaves - slaves.forEach(s => { - let sg = find_gp(s.ID); - if (!ignore_coeffs && sg !== null && "inbreedingCoeff" in sg && sg.inbreedingCoeff !== -1) { - ret[s.ID] = sg.inbreedingCoeff; - } - }); - - // Now do any we haven't done already - slaves = slaves.filter(s => (!(s.ID in ret))); - if (slaves.length > 0) { - let nodes = nodes_slaves(slaves, ignore_coeffs); - - // Compute coefficients - slaves.forEach(s => { - ret[s.ID] = coeff(nodes[s.ID]); - }); - } - - return ret; - }; - - // Determine the kinship between two slaves `a` and `b` - let kinship_slaves = (a, b, ignore_coeffs=false) => { - if (a === 0 || b === 0) - return 0; - - return kinship_one_many(a, [b], ignore_coeffs)[b.ID]; - }; - - // Determine the coefficient of inbreeding of a single slave - let coeff_slave = (slave, ignore_coeffs=false) => { - if (!ignore_coeffs && "inbreedingCoeff" in slave && slave.inbreedingCoeff !== -1) - return slave.inbreedingCoeff; - - let gp = find_gp(slave.ID); - if (!ignore_coeffs && gp !== null && "inbreedingCoeff" in gp && gp.inbreedingCoeff !== -1) - return gp.inbreedingCoeff; - - return coeff_slaves([slave], ignore_coeffs)[slave.ID]; - }; - - // Determine the kinship between one and many slaves. Returns an mapping from the ID of each of - // the slaves in `others` to its kinship with slave `a` - let kinship_one_many = (a, others, ignore_coeffs=false) => { - if (a === 0) { - let ks = {}; - others.forEach(s => { - if (s === 0) - ks[s] = 0; - else - ks[s.ID] = 0; - }); - return ks; - } - - let nodes = nodes_slaves(others.concat([a]), ignore_coeffs); - - let ks = {}; - others.forEach(s => { - if (s === -3) { - // Fake a slave object for the player's old master - s = {ID: -3, mother: 0, father: 0, inbreedingCoeff: 0}; - } - - if (s === 0) - ks[s] = 0; - else - ks[s.ID] = kinship(nodes[a.ID], nodes[s.ID]); - }); - - return ks; - }; - - // Recalculate the inbreeding coefficient for all slaves dependent on the passed IDs (e.g. the - // slaves themselves and all of their children). This will replace the inbreeding coefficients - // wherever they exist with the computed values, ignoring all cached values. - // - // This should be called if parents are changed. - let recalculate_coeff_ids = (ids) => { - // These are all the slave-like objects, i.e. they have ID, mother, and father. There will - // be multiple elements with the same ID: we want this, since we have to replace all - // occurrences of the COI for the affected slaves - let all_slave_like = V.slaves.concat(V.genePool).concat(V.cribs).concat(V.tanks).concat(Object.values(V.missingTable)); - if (V.boomerangSlave !== 0) - all_slave_like.push(V.boomerangSlave); - if (V.traitor !== 0) - all_slave_like.push(V.traitor); - if (V.activeSlave !== 0) - all_slave_like.push(V.activeSlave); - all_slave_like.push(V.PC); - // Add a fake entry for the PC's old master - all_slave_like.push({ID: -3, mother: 0, father: 0, inbreedingCoeff: 0}); - - // Gather the genetics of all current fetuses - let all_fetuses = V.slaves.filter(s => s.preg > 0).map(s => s.womb.map(i => i.genetics)).reduce((res, cur) => res.concat(cur), []); - - // Recursively find all of the given ID's children, born and unborn - let find_children_rec = (id, cur_slaves, cur_fetuses, cur_fetus_parents) => { - // Add fetuses - all_fetuses.filter(f => (f.father === id || f.mother === id)).forEach(f => { - // We may have to manually add the parents later - let actual_f = or_null(f.father); - let actual_m = or_null(f.mother); - if (actual_f !== null) - cur_fetus_parents.add(actual_f); - if (actual_m !== null) - cur_fetus_parents.add(actual_m); - - cur_fetuses.add(f); - }); - - // Recursively add slaves - all_slave_like.filter(s => (s.father === id || s.mother === id)).forEach(s => { - if (!cur_slaves.has(s.ID)) { - cur_slaves.add(s.ID); - find_children_rec(s.ID, cur_slaves, cur_fetuses, cur_fetus_parents); - } - }); - }; - - // We only need slave IDs, since we have to update all of their entries (including GP) - let needed_slave_ids = new Set(); - // Since each fetus has a unique record, a set still suffices - let needed_fetuses = new Set(); - let needed_parent_ids = new Set(); - - // Find all the children of the IDs we need to do - ids.forEach(id => { - needed_slave_ids.add(id); - find_children_rec(id, needed_slave_ids, needed_fetuses, needed_parent_ids); - }); - - // Now we assemble the tree from the slaves - let needed_slaves = []; - needed_slave_ids.forEach(id => { - if (typeof id !== "undefined") - needed_slaves.push(all_slave_like.find(s => s.ID === id)); - }); - needed_parent_ids.forEach(id => { - if (typeof id !== "undefined" && !needed_slave_ids.has(id)) - needed_slaves.push(all_slave_like.find(s => s.ID == id)); - }); - let nodes = nodes_slaves(needed_slaves, true); - - // Now calculate the inbreeding coefficients (they're cached in the tree once calculated) - all_slave_like.filter(s => needed_slave_ids.has(s.ID)).forEach(s => { - s.inbreedingCoeff = coeff(nodes[s.ID]); - }); - - // Finally, handle all of the kinship for the fetuses - let kinship_cache = new Map(); // Manually cache it - needed_fetuses.forEach(f => { - if (or_null(f.mother) === null || or_null(f.father) === null) { - f.inbreedingCoeff = 0; - return; - } - - // Use a string of the form "parent;parent" to store the cache value; since kinship is - // commutative, the minimum parent ID will be first - let kinship_str = Math.min(f.mother, f.father) + ';' + Math.max(f.mother, f.father); - if (!kinship_cache.has(kinship_str)) - kinship_cache.set(kinship_str, kinship(nodes[f.mother], nodes[f.father])); - - f.inbreedingCoeff = kinship_cache.get(kinship_str); - }); - }; - - let recalculate_coeff_id = (id) => { - return recalculate_coeff_ids([id]); - }; - - return { - coeff: coeff_slave, - coeff_slaves, - kinship: kinship_slaves, - kinship_one_many, - recalculate_coeff_ids, - recalculate_coeff_id - }; + // These IDs are considered to be unknown parents + let or_null = (s) => specificCharacterID(s) ? s : null; + + // Find some sort of state for a slave. Checks first the gene pool, then V.slaves, then the + // missing table + let find_gp = (id) => (slaveStateById(id) || V.genePool.find((s) => s.ID === id) || ((id in V.missingTable) ? V.missingTable[id] : null) || null); + + // Create a node for the given ID + let create_node = (id) => ({ + id: id, // Node ID + mother: null, + father: null, + nodecodes: [], // NodeCodes + nodecodes_str: [], // String version of the NodeCodes, for comparison + _coeff: null, // Cached CoI + child_count: 0 // Number of children of the node, for computing NodeCodes + }); + + // Determine the length of the shared prefix between the two NodeCode parameters + let prefix_len = (nca, ncb) => { + let i = 0; + for (i=0; i<Math.min(nca.length, ncb.length); i++) { + if (nca[i] !== ncb[i]) + break; + } + return i; + }; + + // Determine the set of longest common prefixes for a node pair + let prefixes = (a, b) => { + let found = []; + let found_s = []; + a.nodecodes.forEach(nca => { + let match = false; + b.nodecodes.forEach(ncb => { + let l = prefix_len(nca, ncb); + if (l === 0 && match) + return; + + if (l > 0) { + match = true; + let pfx = nca.slice(0, l); + let pfx_s = pfx.join(';'); + + if (!found_s.includes(pfx_s)) { + found_s.push(pfx_s); + found.push(pfx); + } + } + }); + }); + + return found; + }; + + // Search up the tree to find a given NodeCode, starting at `n` + let find_nc = (nc, n) => { + if (n.nodecodes_str.includes(nc.join(';'))) { + return n; + } + + let ret = null; + if (n.mother !== null) + ret = find_nc(nc, n.mother); + if (n.father !== null && ret === null) + ret = find_nc(nc, n.father); + + return ret; + }; + + // Determine the set of common ancestors between a node pair + let common = (a, b) => { + let pfx = prefixes(a, b); + let pfx_s = pfx.map(s => s.join(';')); + let anc = []; + + while (pfx.length > 0) { + let p = pfx.pop(0); + pfx_s.pop(0); + let ret = find_nc(p, a); + + ret.nodecodes.forEach(nc => { + let i = pfx_s.indexOf(nc.join(';')); + if (i === -1) + return; + + pfx.pop(i); + pfx_s.pop(i); + }); + + if (anc.findIndex(s => (s[0] == ret)) === -1) + anc.push([ret, p]); + } + + return anc; + }; + + // Determine the set of NodeCodes on `n` with prefix `x` + let mps = (n, x) => { + let x_s = x.join(';'); + return n.nodecodes.filter(nc => (nc.slice(0, x.length).join(';') === x_s)); + }; + + // Compute the set of all paths to the parents of `n` with prefix `x` + let pp = (mother, father, x) => { + let m = mps(mother, x); + let f = mps(father, x); + + let prod = []; + m.forEach(i => { + f.forEach(j => { + prod.push([i, j]); + }); + }); + + return prod; + }; + + let kinship = (mother, father) => { + let _coeff = 0; + if (mother === null || father === null) + _coeff = 0; + else if (mother == father) + _coeff = 0.5 * (1 + coeff(mother)); + else { + let cf = 0; + let cmn = common(mother, father); + + cmn.forEach(el => { + let c = el[0]; + let p = el[1]; + let p_s = p.join(';'); + + let paths = pp(mother, father, p); + let paths_s = paths.map(p => [p[0].join(';'), p[1].join(';')].join(',')); + + cmn.forEach(el2 => { + let co = el2[0]; + if (co == c) + return; + let m_pp = []; + let f_pp = []; + + co.nodecodes.forEach(nc => { + if (nc.slice(0, p.length).join(';') != p_s) + return; + + m_pp = m_pp.concat(mps(mother, nc)); + f_pp = f_pp.concat(mps(father, nc)); + }); + + m_pp.forEach(mp => { + f_pp.forEach(fp => { + let mf_s = [mp.join(';'), fp.join(';')].join(','); + let i = paths_s.indexOf(mf_s); + if (i === -1) + return; + paths_s.pop(i); + paths.pop(i); + }); + }); + }); + paths.forEach(p => { + let pfx = prefix_len(p[0], p[1]); + + cf += 0.5**(p[0].length + p[1].length+1 - 2*pfx) * (1 + coeff(c)); + }); + }); + + _coeff = cf; + } + + return _coeff; + }; + + // Determine the coefficient of inbreeding of a node `n` + let coeff = n => { + if (n._coeff === null) + n._coeff = kinship(n.mother, n.father); + return n._coeff; + }; + + // Populate the NodeCodes. + // + // Each node has a set of NodeCodes, which represent the set of paths from it to its ancestors. + // NodeCodes here are represented by arrays of integers. + // + // NodeCodes are constructed recursively in this fashion: + // + // - Assign each of the founders (nodes with both parents === null) an unique ID, starting from + // 0 and incrementing each time (the order doesn't matter); a founder's set of NodeCodes has + // exactly one NodeCode, which is [ID] (an array containing only their ID) + // + // - For each other node, let M be its child number w.r.t. its mother and N its child number + // w.r.t. its father, i.e. the number of children that the respective parent has had before + // this one (the order is not important to the algorithm, it's arbitrary here for + // convenience). Its set of NodeCodes is the set of all its mother's NodeCodes with M appended + // and all of its father's NodeCodes with N appended. For example, if its mother has the + // NodeCodes [[2]] and M = 3 and its father has the NodeCodes [[0,1], [3,1]] and N = 1 then + // the set of NodeCodes for this node would be + // + // [[2, 3], [0, 1, 1], [3, 1, 1]] + // + // We do this iteratively here, looping over the set of all nodes until each has been assigned + // a NodeCode. This requires looping through a number of times equal to the number of + // generations, since as soon as both parents have NodeCodes their children's NodeCodes may be + // computed. + let make_nc = nodes => { + // Generate founder NodeCodes + let total = Object.keys(nodes).length; + let seen = []; + let curid = 0; + Object.values(nodes).forEach(n => { + if (n.mother !== null || n.father !== null) + return; + n.nodecodes.push([curid]); + curid += 1; + seen.push(n.id); + }); + + // Generate the rest of the NodeCodes + let oldSeenLength = -1; + while (seen.length !== total) { + oldSeenLength = seen.length; + Object.keys(nodes).forEach(s=> { + let n = nodes[s]; + if (seen.includes(+s)) // We've already done this + return; + else if ((n.mother !== null && n.mother.nodecodes.length === 0) || (n.father !== null && n.father.nodecodes.length === 0)) // Too soon, we haven't done its parents + return; + + seen.push(n.id); + // Compute the NodeCodes from its parents + [n.mother, n.father].forEach((a, i) => { + if (a === null || (n.mother === n.father && i === 1)) // Ignore missing parents/repeated + return; + + a.nodecodes.forEach(nc => { + // Copy the NodeCode, push the child number, then add it + let nnc = nc.slice(); + nnc.push(a.child_count); + n.nodecodes.push(nnc); + }); + a.child_count += 1; + }); + + // NodeCodes must be sorted; this suffices + n.nodecodes.sort(); + }); + // check to make sure we're progressing...if not, there's a cycle in the graph + // dump all the nodes participating in or descended from the cycle and let the player figure it out + if (oldSeenLength === seen.length) { + const badSlaveIDs = Object.keys(nodes).filter(s => !seen.includes(+s)).map(k => nodes[k].id); + throw(`Inbreeding calculation: heritance cycle detected. Check slave IDs: ${badSlaveIDs}`); + } + } + + // Cache the string NodeCodes + Object.values(nodes).forEach(n => { + n.nodecodes_str = n.nodecodes.map(nc => nc.join(';')); + }); + }; + + // Make nodes for an array of slaves + let nodes_slaves = (slaves, ignore_coeffs=false) => { + let nodes = {}; + + // Recursively create the nodes we need, moving upwards from the given slave + let create_node_rec = s => { + // Certain parents (e.g. 0, societal elite) are not considered to be related, despite + // having the same ID; convert them to null + let m = or_null(s.mother); + let f = or_null(s.father); + + // Ensure that parent nodes are created + [m, f].forEach(p => { + if (p !== null && !(p in nodes)) { // Not created, we have to do something + if (p === -1) { + create_node_rec(SugarCube.State.variables.PC); + } else { + // Search for a slave state, genePool entry, or missingTable entry + let gp = find_gp(p); + if (gp !== null) { + // If we find one, we might have ancestry information: recurse + create_node_rec(gp); + } else { + // Otherwise, just create a plain node + nodes[p] = create_node(p); + } + } + } + }); + + if (!(s.ID in nodes)) // Create this node if necessary + nodes[s.ID] = create_node(s.ID); + // We created its parents earlier, so set them to the actual nodes + nodes[s.ID].mother = (m === null) ? m : nodes[m]; + nodes[s.ID].father = (f === null) ? f : nodes[f]; + + // Try to use a cached CoI for performance + let sg = find_gp(s.ID); + if (!ignore_coeffs && sg !== null && "inbreedingCoeff" in sg && sg.inbreedingCoeff !== -1) { + nodes[s.ID]._coeff = sg.inbreedingCoeff; + } + }; + + // Populate the nodes + slaves.forEach(s=> create_node_rec(s)); + + // Populate NodeCodes + make_nc(nodes); + + return nodes; + }; + + // Determine the coefficients of inbreeding of an array of slaves. Returns a mapping of their + // ID to their coefficient of inbreeding + let coeff_slaves = (slaves, ignore_coeffs=false) => { + let ret = {}; + // First, pull as many existing CoI off the slaves + slaves.forEach(s => { + let sg = find_gp(s.ID); + if (!ignore_coeffs && sg !== null && "inbreedingCoeff" in sg && sg.inbreedingCoeff !== -1) { + ret[s.ID] = sg.inbreedingCoeff; + } + }); + + // Now do any we haven't done already + slaves = slaves.filter(s => (!(s.ID in ret))); + if (slaves.length > 0) { + let nodes = nodes_slaves(slaves, ignore_coeffs); + + // Compute coefficients + slaves.forEach(s => { + ret[s.ID] = coeff(nodes[s.ID]); + }); + } + + return ret; + }; + + // Determine the kinship between two slaves `a` and `b` + let kinship_slaves = (a, b, ignore_coeffs=false) => { + if (a === 0 || b === 0) + return 0; + + return kinship_one_many(a, [b], ignore_coeffs)[b.ID]; + }; + + // Determine the coefficient of inbreeding of a single slave + let coeff_slave = (slave, ignore_coeffs=false) => { + if (!ignore_coeffs && "inbreedingCoeff" in slave && slave.inbreedingCoeff !== -1) + return slave.inbreedingCoeff; + + let gp = find_gp(slave.ID); + if (!ignore_coeffs && gp !== null && "inbreedingCoeff" in gp && gp.inbreedingCoeff !== -1) + return gp.inbreedingCoeff; + + return coeff_slaves([slave], ignore_coeffs)[slave.ID]; + }; + + // Determine the kinship between one and many slaves. Returns an mapping from the ID of each of + // the slaves in `others` to its kinship with slave `a` + let kinship_one_many = (a, others, ignore_coeffs=false) => { + if (a === 0) { + let ks = {}; + others.forEach(s => { + if (s === 0) + ks[s] = 0; + else + ks[s.ID] = 0; + }); + return ks; + } + + let nodes = nodes_slaves(others.concat([a]), ignore_coeffs); + + let ks = {}; + others.forEach(s => { + if (s === -3) { + // Fake a slave object for the player's old master + s = {ID: -3, mother: 0, father: 0, inbreedingCoeff: 0}; + } + + if (s === 0) + ks[s] = 0; + else + ks[s.ID] = kinship(nodes[a.ID], nodes[s.ID]); + }); + + return ks; + }; + + // Recalculate the inbreeding coefficient for all slaves dependent on the passed IDs (e.g. the + // slaves themselves and all of their children). This will replace the inbreeding coefficients + // wherever they exist with the computed values, ignoring all cached values. + // + // This should be called if parents are changed. + let recalculate_coeff_ids = (ids) => { + // These are all the slave-like objects, i.e. they have ID, mother, and father. There will + // be multiple elements with the same ID: we want this, since we have to replace all + // occurrences of the COI for the affected slaves + let all_slave_like = V.slaves.concat(V.genePool).concat(V.cribs).concat(V.tanks).concat(Object.values(V.missingTable)); + if (V.boomerangSlave !== 0) + all_slave_like.push(V.boomerangSlave); + if (V.traitor !== 0) + all_slave_like.push(V.traitor); + if (V.activeSlave !== 0) + all_slave_like.push(V.activeSlave); + all_slave_like.push(V.PC); + // Add a fake entry for the PC's old master + all_slave_like.push({ID: -3, mother: 0, father: 0, inbreedingCoeff: 0}); + + // Gather the genetics of all current fetuses + let all_fetuses = V.slaves.filter(s => s.preg > 0).map(s => s.womb.map(i => i.genetics)).reduce((res, cur) => res.concat(cur), []); + + // Recursively find all of the given ID's children, born and unborn + let find_children_rec = (id, cur_slaves, cur_fetuses, cur_fetus_parents) => { + // Add fetuses + all_fetuses.filter(f => (f.father === id || f.mother === id)).forEach(f => { + // We may have to manually add the parents later + let actual_f = or_null(f.father); + let actual_m = or_null(f.mother); + if (actual_f !== null) + cur_fetus_parents.add(actual_f); + if (actual_m !== null) + cur_fetus_parents.add(actual_m); + + cur_fetuses.add(f); + }); + + // Recursively add slaves + all_slave_like.filter(s => (s.father === id || s.mother === id)).forEach(s => { + if (!cur_slaves.has(s.ID)) { + cur_slaves.add(s.ID); + find_children_rec(s.ID, cur_slaves, cur_fetuses, cur_fetus_parents); + } + }); + }; + + // We only need slave IDs, since we have to update all of their entries (including GP) + let needed_slave_ids = new Set(); + // Since each fetus has a unique record, a set still suffices + let needed_fetuses = new Set(); + let needed_parent_ids = new Set(); + + // Find all the children of the IDs we need to do + ids.forEach(id => { + needed_slave_ids.add(id); + find_children_rec(id, needed_slave_ids, needed_fetuses, needed_parent_ids); + }); + + // Now we assemble the tree from the slaves + let needed_slaves = []; + needed_slave_ids.forEach(id => { + if (typeof id !== "undefined") + needed_slaves.push(all_slave_like.find(s => s.ID === id)); + }); + needed_parent_ids.forEach(id => { + if (typeof id !== "undefined" && !needed_slave_ids.has(id)) + needed_slaves.push(all_slave_like.find(s => s.ID === id)); + }); + let nodes = nodes_slaves(needed_slaves, true); + + // Now calculate the inbreeding coefficients (they're cached in the tree once calculated) + all_slave_like.filter(s => needed_slave_ids.has(s.ID)).forEach(s => { + s.inbreedingCoeff = coeff(nodes[s.ID]); + }); + + // Finally, handle all of the kinship for the fetuses + let kinship_cache = new Map(); // Manually cache it + needed_fetuses.forEach(f => { + if (or_null(f.mother) === null || or_null(f.father) === null) { + f.inbreedingCoeff = 0; + return; + } + + // Use a string of the form "parent;parent" to store the cache value; since kinship is + // commutative, the minimum parent ID will be first + let kinship_str = Math.min(f.mother, f.father) + ';' + Math.max(f.mother, f.father); + if (!kinship_cache.has(kinship_str)) + kinship_cache.set(kinship_str, kinship(nodes[f.mother], nodes[f.father])); + + f.inbreedingCoeff = kinship_cache.get(kinship_str); + }); + }; + + let recalculate_coeff_id = (id) => { + return recalculate_coeff_ids([id]); + }; + + return { + coeff: coeff_slave, + coeff_slaves, + kinship: kinship_slaves, + kinship_one_many, + recalculate_coeff_ids, + recalculate_coeff_id + }; })(); -- GitLab