models/bag.js

import Model from "./model.js";
import Item from "./item.js";
import { sentenceCase, pluralize, uncountable } from "../string_utils.js";

function toString(bag, pluralizerFunc) {
  let string = "",
    cash = 0;
  if (bag.entries.length) {
    let items = false;
    let len = bag.entries.filter((entry) => entry.item.not("cash")).length;
    bag.entries.forEach((entry) => {
      if (entry.item.is("cash")) {
        cash += entry.item.value * 100 * entry.count;
      } else {
        items = true;
        string += pluralizerFunc(entry.item, entry.count);
        if (entry.item.title) {
          string += ` (${entry.item.title})`;
        }
        if (len === 1) {
          string += ".";
        } else if (len === 2) {
          string += ", and ";
        } else {
          string += ", ";
        }
        len--;
      }
    });
    if (items && cash) {
      string += " ";
    }
    if (cash) {
      string += "$" + cash.toFixed(0) + " in cash.";
    }
    string = sentenceCase(string);
  } else {
    string += "empty.";
  }
  if (bag.descriptor) {
    string = bag.descriptor + ": " + string;
  }
  return string;
}
function copy(entries) {
  return (entries || []).map((entry) => ({ item: new Item(entry.item), count: entry.count }));
}

class Bag extends Model {
  /**
   * A bag.
   *
   * @param data {Object} The properties to set for this item.
   *
   * @extends Model
   */
  constructor(data = {}) {
    super(data);
    this.entries = copy(data.entries);
    this.type = "Bag";
  }
  addBag(bag) {
    ((bag && bag.entries) || []).forEach((entry) => this.add(entry.item, entry.count));
  }
  removeBag(bag) {
    ((bag && bag.entries) || []).forEach((entry) => this.remove(entry.item, entry.count));
  }
  add(item, count = 1) {
    if (!item) {
      throw new Error("No item passed to bag.add()");
    }
    if (count < 0) {
      throw new Error("Cannot add a negative number of items");
    }
    if (count == 0) {
      return 0;
    }
    let entry = this.visit(null, (e) => e.item.equals(item));
    if (!entry) {
      entry = { item: new Item(item), count: 0 };
      this.entries.push(entry);
    }
    entry.count += count;
    return entry.count;
  }
  remove(item, count = 1) {
    if (count < 0) {
      throw new Error("Cannot remove a negative number of items");
    }
    let entry = this.visit(null, (e) => e.item.equals(item));
    if (!entry) {
      throw new Error("Can’t remove item that’s not in the bag");
    }
    if (count > entry.count) {
      throw new Error(`Can’t remove ${count} items in bag that has only ${entry.count}`);
    }
    if (count == 0) {
      return entry.count;
    }
    entry.count -= count;
    if (entry.count === 0) {
      this.entries.splice(this.entries.indexOf(entry), 1);
    }
    return entry.count;
  }
  visit(startValue, pred, collector = (e) => e) {
    return this.entries.reduce((acc, entry) => {
      if (pred(entry, acc)) acc = collector(entry, acc);
      return acc;
    }, startValue);
  }
  // Find an entry by name or tag
  find(name) {
    return this.visit(null, (e) => e.item.name === name || e.item.tags.has(name));
  }
  value(item) {
    return this.visit(
      0,
      (e) => !item || e.item.equals(item),
      (e, value) => value + e.item.value * e.count
    );
  }
  enc(item) {
    return this.visit(
      0,
      (e) => !item || e.item.equals(item),
      (e, value) => value + e.item.enc * e.count
    );
  }
  totalValue() {
    return this.visit(
      0,
      () => true,
      (e, value) => value + e.item.value * e.count
    );
  }
  totalEnc() {
    return this.visit(
      0,
      () => true,
      (e, value) => value + e.item.enc * e.count
    );
  }
  count(item) {
    return this.visit(
      0,
      (e) => !item || e.item.equals(item),
      (e, value) => value + e.count
    );
  }
  toUncountableString() {
    return toString(this, uncountable);
  }
  toString() {
    return toString(this, pluralize);
  }
}

export default Bag;