| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- const diceRegex = () => /\b(\d*)[dD](\d+)\b(?:\s?([+-])\s?(\d+))?/g
- const rerollRegex = () => /\b(reroll|re-roll)\s+((?:(?:\S*|\d+s|\d+'s|\d+)\s*)+)/
- const numberRegex = () => /\b(one|two|three|four|five|six|seven|eight|nine|ten|ones|twos|threes|fours|fives|sixes|sevens|eights|nines|tens|one's|two's|three's|four's|five's|seven's|eight's|nine's|ten's|\d+)\b/gi
- const advRegex = () => /(disadvantage|advantage)/i
- class Roll {
- constructor(roll) {
- if (roll) Object.assign(this, roll)
- }
- value() {
- return this.values.reduce((a, b) => a + b, this.bonus || 0)
- }
- toString() {
- let str = this.values.map(val => `[${val}]`).join(' + ')
- if (this.bonus < 0) {
- str += ` - ${Math.abs(this.bonus)}`
- } else if (this.bonus > 0) {
- str += ` + ${this.bonus}`
- }
- if (this.values.length > 1 || this.bonus) {
- str += ` = ${this.value()}`
- }
- if (this.rerolls === 1) {
- str += ` with 1 reroll`
- } else if (this.rerolls > 1) {
- str += ` with ${this.rerolls} rerolls`
- }
- return str
- }
- }
- class BadRoll extends Roll {
- constructor(text) {
- super({text})
- }
- toString() {
- return this.text
- }
- }
- const random = n => Math.floor(Math.random() * n) + 1
- class Dice {
- constructor(dice) {
- if (dice) Object.assign(this, dice)
- }
- roll() {
- if (this.count > 100) return new BadRoll(`I lost count rolling ${this}`)
- if (this.sides > 256) return new BadRoll(`The faces are too small to read on ${this}`)
- if (this.count === 0) return new BadRoll(`I finished before I started rolling ${this}`)
- if (this.sides === 0) return new BadRoll(`I lost my grip on ${this}`)
- if (this.sides === 1) return new BadRoll(`The mobius ${this} never stopped rolling`)
- if (this.bonusSign === '-' && this.bonus > this.sides) return new BadRoll(`It doesn't seem fair to roll ${this}`)
- let values = []
- for (let i = 0; i < this.count; i++) {
- if (this.modifier === 'advantage') {
- values.push(Math.max(random(this.sides), random(this.sides)))
- } else if (this.modifier === 'disadvantage') {
- values.push(Math.min(random(this.sides), random(this.sides)))
- } else {
- values.push(random(this.sides))
- }
- }
- let bonus = 0
- switch (this.bonusSign) {
- case '-': bonus = -this.bonus; break
- case '+': bonus = this.bonus; break
- }
- let rerolls = 0
- for (let i = 0; i < 10; i++) {
- let foundAny = false
- for (let d = 0; d < values.length; d++) {
- const v = values[d]
- if (this.rerollNumbers && this.rerollNumbers.includes(v)) {
- values[d] = random(this.sides)
- foundAny = true
- rerolls++
- }
- }
- if (!foundAny) break
- }
- return new Roll({
- values,
- bonus,
- rerolls
- })
- }
- toString() {
- let str = `${this.count}d${this.sides}`
- if (this.bonusSign) {
- str += this.bonusSign + this.bonus
- }
- if (this.modifier) {
- str += ` with ${this.modifier}`
- }
- if (this.rerollNumbers && this.rerollNumbers.length) {
- if (this.rerollNumbers.length === 1) {
- str += ` and reroll ${this.rerollNumbers[0]}'s`
- } else {
- str += ` and reroll ${this.rerollNumbers.slice(0, this.rerollNumbers.length - 1).join(`'s, `)}'s, and ${this.rerollNumbers[this.rerollNumbers.length - 1]}'s`
- }
- }
- return str
- }
- }
- Dice.parse = str => {
- const rrRegex = rerollRegex()
- const rerolls = []
- const reroll = rrRegex.exec(str)
- const rerollNumbers = []
- if (reroll) {
- const nRegex = numberRegex()
- while (number = nRegex.exec(str)) {
- switch (number[1]) {
- case 'ones':
- case 'one':
- rerollNumbers.push(1)
- break
- case 'twos':
- case 'two':
- rerollNumbers.push(2)
- break
- case 'threes':
- case 'three':
- rerollNumbers.push(3)
- break
- case 'fours':
- case 'four':
- rerollNumbers.push(4)
- break
- case 'fives':
- case 'five':
- rerollNumbers.push(5)
- break
- case 'sixes':
- case 'six':
- rerollNumbers.push(6)
- break
- case 'sevens':
- case 'seven':
- rerollNumbers.push(7)
- break
- case 'eights':
- case 'eight':
- rerollNumbers.push(8)
- break
- case 'nines':
- case 'nine':
- rerollNumbers.push(9)
- break
- case 'tens':
- case 'ten':
- rerollNumbers.push(10)
- break
- default:
- const n = parseFloat(number)
- if (!isNaN(n)) {
- rerollNumbers.push(n)
- }
- }
- }
- }
- const aRegex = advRegex()
- const ad = aRegex.exec(str)
-
- const modifier = ad ? (
- ad[1].toLowerCase()[0] === 'a' ? 'advantage' : 'disadvantage'
- ) : null
- const regex = diceRegex()
- const ret = []
- let dice
- while (dice = regex.exec(str)) {
- ret.push(new Dice({
- match: dice,
- count: dice[1] === '' ? 1 : parseFloat(dice[1]),
- sides: parseFloat(dice[2]),
- bonusSign: dice[3] || null,
- bonus: dice[4] === undefined ? null : parseFloat(dice[4]),
- rerollNumbers,
- modifier
- }))
- }
- return ret
- }
- Dice.chat = chat => {
- const dice = Dice.parse(chat)
- if (dice.length) {
- const diceStrings = dice.map(x => x.toString())
- if (diceStrings.length > 1) {
- diceStrings[diceStrings.length - 1] = `and ${diceStrings[diceStrings.length - 1]}`
- }
- const rollingString = `Rolling ${diceStrings.join(', ')}...`
- const rollsStrings = dice.map(die => die.roll().toString())
- return `${rollingString} ${rollsStrings.join(', ')}.`
- }
- }
- module.exports = {
- Dice,
- Roll,
- diceRegex,
- rerollRegex,
- advRegex
- }
|