models/family.js

import { Builder } from "../string_utils.js";
import { pluralize } from "../string_utils.js";
import Model from "./model.js";

const REL_LABELS = new Map(
  Object.entries({
    deceased: "deceased",
    absent: "absent",
    married: "married to",
    divorced: "divorced from",
    separated: "separated from",
    couple: "lives with",
  })
);

// It is surprisingly hard to get this right.
function relatedNames(ch1, ch2, relationship) {
  let relLabel = REL_LABELS.get(relationship);
  let arr = [];
  if (ch1.name.family === ch2.name.family) {
    arr.push(ch1.name.given);
    arr.push(relLabel);
    arr.push(ch2.name.given);
    arr.push(ch2.name.toLastNameString());
  } else {
    arr.push(ch1.name.toString());
    arr.push(relLabel);
    arr.push(ch2.name.toString());
  }
  let detail = `(Ages ${ch1.age} and ${ch2.age}`;
  if (ch1.status && ch1.status === ch2.status) {
    detail += `; both are ${ch1.status}`;
  } else {
    if (ch1.status) {
      detail += `; ${ch1.name.given} is ${ch1.status}`;
    }
    if (ch2.status) {
      detail += `; ${ch2.name.given} is ${ch2.status}`;
    }
  }
  detail += `).`;
  arr.push(detail);

  var b = new Builder();
  b.append(arr.join(" "));
  return b.toString() + " ";
}

/**
 * A family. This means (in the context of this game), a set of parents with some kids,
 * some of whom may themselves be in family objects with kids, etc. One of the building
 * blocks of encounters and homesteads, at the least.
 */
class Family extends Model {
  constructor(params) {
    super(params);
    this.parent = params.parent;
    this.partner = params.partner;
    this.relationship = params.relationship;
    // Unmarried children
    this.children = []; // Character
    // Children in families of their own
    this.couples = []; // Family
  }
  /**
   * The number of all children (including grown children who are
   * now part of a descendant couple).
   */
  get childCount() {
    return this.children.length + this.couples.length;
  }
  /**
   * The male parent
   */
  get male() {
    return this.parent.male ? this.parent : this.partner;
  }
  /**
   * The female parent
   */
  get female() {
    return this.parent.male ? this.partner : this.parent;
  }
  /**
   * The single parent (if one of the parents has died or the parents are separated).
   */
  get single() {
    if (this.parent.status && !this.partner.status) {
      return this.partner;
    } else if (this.partner.status && !this.parent.status) {
      return this.parent;
    }
    return null;
  }
  isParent(person) {
    return person === this.parent || person === this.partner;
  }
  toString() {
    var b = new Builder();
    b.append(relatedNames(this.parent, this.partner, this.relationship));
    b.if(this.childCount > 0, pluralize(`{|}{1 |}child{ren}: `, this.childCount));
    this.children.map((c) => b.append(`${c.name} (${c.age}). `));
    this.couples.map((c) => b.append(`${c.parent.name} (${c.parent.age}). `));
    b.append(this.couples.map((c) => `\n\n${c.toString()}`).join(" "));
    return b.toString().replaceAll(") (", "; ");
  }
}

export default Family;