tables/table.js

import { roll } from "../random_utils.js";

const RANGE_ERROR = "Dice ranges must be from 1 to 100";

/**
 * A table of stuff you select by rolling 1d100.
 *
 * @example
 * let table = new Table();
 * table.add(50, 'heads')
 * table.add(50, 'tails');
 * table.get();
 * => 'heads'
 *
 */
class Table {
  /**
   * @param outFunction {Function} A function to run on values supplied to the add method. For
   *  example, you might create a function to convert item names to true Item objects, as a
   *  convenience when building the table.
   */
  constructor({ outFunction = (a) => a } = {}) {
    this.outFunction = outFunction;
    this.rows = [];
    this.sum = 0;
  }
  /**
   * Add something to this table. You can either specify the specific percentage that this
   * element should be selected, or you can provide the range of die roll numbers (this
   * latter approach is easier when you're adapting an existing pen-and-paper table).
   *
   * The percentages must add up to 100% (or 100 on 1d100).
   *
   * @example
   * let table = new Table();
   * table.add(50, object); // will occur 50% of the time
   * table.add(1, 70, object); // Will occur 70% of the time, on a roll of 1 to 70
   *
   * @param percentOrStartRoll {Number} % chance occurrence of 100%, or the start number on 1d100
   * @param [endRoll] {Number}
   * @param object  {Object} Element to add to the table, can be any type
   */
  add(...args) {
    let start, end, object, chance;
    if (args.length === 3) {
      start = args[0];
      end = args[1];
      object = args[2];
      chance = end - start + 1;
      if (start < 1 || start > 100 || end < 1 || end > 100) {
        throw new Error(RANGE_ERROR);
      }
    } else {
      chance = args[0];
      object = args[1];
      if (chance < 1 || chance > 100) {
        throw new Error(RANGE_ERROR);
      }
    }
    if (typeof object === "undefined") {
      throw new Error("Object is undefined");
    }
    this.rows.push({ chance, object });
    this.sum += chance;
    return this;
  }
  /**
   * Get an item from the table, based on percentages.
   *
   * @return {Object} An item from the table
   */
  get() {
    if (Math.round(this.sum) !== 100) {
      throw new Error(`Table elements add up to ${Math.round(this.sum)} (not 100%)`);
    }
    let result = roll(100);
    for (let i = 0, len = this.rows.length; i < len; i++) {
      if (result <= this.rows[i].chance) {
        return this.outFunction(this.rows[i].object);
      }
      result -= this.rows[i].chance;
    }
  }
  /**
   * @return {Number} the number of items in the table.
   */
  size() {
    return this.rows.length;
  }
}

export default Table;