roller.js

const { randomRoll } = require( './random' );
const { normalizeRollResult, isDefined } = require( './normalizer' );
const { convertToRoll, convertToWodRoll, convertToAnyRoll } = require( './converter' );
const Result = require( './object/Result' );
const Roll = require( './object/Roll' );
const WodRoll = require( './object/WodRoll' );


/**
 * Rolls the dice from `Roll` object.
 *
 * @func
 * @since v2.0.0
 * @param {Roll} roll - `Roll` object or similar
 * @return {Result}
 * @see roll
 * @see rollWod
 * @example
 * rollClassic(new Roll(10, 2, -1)); //=> { notation: '2d10-1', value: 14, rolls: [ 7, 8 ] }
 * rollClassic({ dice: 6 }); //=> { notation: 'd6', value: 4, rolls: [ 4 ] }
 */
function rollClassic( roll ) {
  const data = roll instanceof Roll ? roll : convertToRoll( roll );
  const { dice, count, modifier } = data;

  const rolls = [ ...new Array( count ) ].map(() => randomRoll( dice ));
  const summ = rolls.reduce(( prev, curr ) => prev + curr, 0 );
  const result = normalizeRollResult( summ + modifier );

  return new Result( data.toString(), result, rolls );
}

/**
 * Rolls the dice from `WodRoll` object.
 *
 * @func
 * @since v2.0.0
 * @param {WodRoll} roll - `WodRoll` object or similar
 * @return {Result}
 * @see roll
 * @see rollClassic
 * @example
 * rollWod(new WodRoll(10, 4, true, 8)); //=> { notation: '4d10!>8', value: 2, rolls: [3,10,7,9,5] }
 * rollWod({ dice: 8, count: 3 }); //=> { notation: '3d8>6', value: 2, rolls: [ 7, 3, 9 ] }
 */
function rollWod( roll ) {
  const data = roll instanceof WodRoll ? roll : convertToWodRoll( roll );
  const { dice, count, again, success, fail } = data;

  const rolls = [];

  let i = count;
  while ( i > 0 ) {
    const value = randomRoll( dice );
    rolls.push( value );
    // Check for "10 Again" flag
    // `repeatLimit` will prevent infinite loop, for cases like `d1!>1`
    const repeatLimit = 100;
    if ( value !== dice || !again || rolls.length > repeatLimit ) {
      i -= 1;
    }
  }

  const result = rolls.reduce(( suc, val ) => {
    if ( val >= success ) {
      return suc + 1;
    } else if ( val <= fail ) {
      return suc - 1;
    }
    return suc;
  }, 0 );

  return new Result( data.toString(), Math.max( result, 0 ), rolls );
}

/**
 * Rolls the dice from `Roll` or `WodRoll` objects.
 *
 * @func
 * @alias roll
 * @since v2.0.0
 * @param {Roll|WodRoll|Object} roll - `Roll`, `WodRoll` or similar object.
 * @return {Result} Returns `Result` for defined parameters, otherwise returns `null`.
 * @see rollClassic
 * @see rollWod
 * @example
 * roll(new Roll(10, 2, -1)); //=> { notation: '2d10-1', value: 14, rolls: [ 7, 8 ] }
 * roll({ dice: 6 }); //=> { notation: 'd6', value: 4, rolls: [ 4 ] }
 * roll(new WodRoll(10, 4, true, 8)); //=> { notation: '4d10!>8', value: 2, rolls: [3,10,7,9,5] }
 * roll({ dice: 8, count: 3, again: true }); //=> { notation: '3d8!>6', value: 2, rolls: [7,3,9 ] }
 * roll( null ); //=> null
 */
function rollAny( roll ) {
  if ( roll instanceof Roll ) {
    return rollClassic( roll );
  } else if ( roll instanceof WodRoll ) {
    return rollWod( roll );
  }
  return isDefined( roll ) ? rollAny( convertToAnyRoll( roll )) : null;
}

module.exports = {
  rollClassic,
  rollWod,
  rollAny,
};