Преглед изворни кода

Help, multiplier, take/drop

Alan Colon пре 7 година
родитељ
комит
e1ce4ce04b
2 измењених фајлова са 202 додато и 59 уклоњено
  1. 195 59
      dice.js
  2. 7 0
      test.js

+ 195 - 59
dice.js

@@ -1,10 +1,13 @@
+const takeRegex = () => /\b(take|keep|drop|lose)\s+(?:the\s+)?(top|highest|high|best|bottom|lowest|low|worst)\s+(one|two|three|four|five|six|seven|eight|nine|ten|\d+)/i
 const rollRegex = () => /\b(roll|flip)\b/i
 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 numberRegex = () => /\b(take|keep|drop|lose|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 = () => /\b(disadvantage|advantage)\b/i
 const percentRegex = () => /\b(percentile|percentiles|percent)\b/i
 const dicebotRegex = () => /\b(dicebot|dice bot)\b/i
+const multiplierRegex = () => /\bx(-?\d+)/i
+const helpRegex = () => /(dicebot|roll) help/i
 const unicoder = require('./unicoder')
 
 const d6 = {
@@ -77,14 +80,19 @@ class Roll {
     } else if (this.bonus > 0) {
       str += ` + ${this.bonus}`
     }
-    if (this.values.length > 1 || this.bonus) {
-      str += ` = ${this.formatter && this.formatter.total ? this.formatter.total(this.values, this.bonus || 0, this) : this.value()}`
-    }
     if (this.rerolls === 1) {
       str += ` with 1 reroll`
     } else if (this.rerolls > 1) {
       str += ` with ${this.rerolls} rerolls`
     }
+    if (this.dropped && this.dropped.length) {
+      str += ` dropping ${this.dropped.map(v => this.formatter && this.formatter.stringify ? this.formatter.stringify(v) : `[${v}]`).join(', ')}`
+    }
+
+    if (this.values.length > 1 || this.bonus) {
+      str += ` = ${this.formatter && this.formatter.total ? this.formatter.total(this.values, this.bonus || 0, this) : this.value()}`
+    }
+
     return str
   }
 }
@@ -113,47 +121,66 @@ class Dice {
     if (!this.rng) this.rng = () => Math.random()
   }
   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.rng, this.sides), random(this.rng, this.sides)))
-      } else if (this.modifier === 'disadvantage') {
-        values.push(Math.min(random(this.rng, this.sides), random(this.rng, this.sides)))
-      } else {
-        values.push(random(this.rng, this.sides))
+    if (this.multiplier === 0) return [ new BadRoll(`That was easy rolling ${this}`)]
+    if (this.multiplier > 32) return [new BadRoll(`I can't keep track of ${this}`)]
+    if (this.multiplier < 0) return [new BadRoll(`I finished before I started rolling ${this}`)]
+    return Array(this.multiplier).fill().map(() => {
+      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.rng, this.sides), random(this.rng, this.sides)))
+        } else if (this.modifier === 'disadvantage') {
+          values.push(Math.min(random(this.rng, this.sides), random(this.rng, this.sides)))
+        } else {
+          values.push(random(this.rng, 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.rng, this.sides)
-          foundAny = true
-          rerolls++
+      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.rng, this.sides)
+            foundAny = true
+            rerolls++
+          }
         }
+        if (!foundAny) break
       }
-      if (!foundAny) break
-    }
-    return new Roll({
-      values,
-      bonus,
-      rerolls,
-      percent: this.percent,
-      formatter: this.formatter
+      
+      let dropped = []
+      if (this.takeOrDrop) {
+        let c = this.takeOrDrop.count
+        if (this.takeOrDrop.takeDrop === 'drop') {
+          c = Math.max(0, values.length - c)
+        }
+        if (this.takeOrDrop.highLow === 'highest') {
+          values.sort((a,b) => b - a)
+        } else {
+          values.sort((a,b) => a - b)
+        }
+        dropped = values.splice(c)
+      }
+      return new Roll({
+        values,
+        dropped,
+        bonus,
+        rerolls,
+        percent: this.percent,
+        formatter: this.formatter
+      })
     })
   }
   toString() {
@@ -178,21 +205,108 @@ class Dice {
         str += ` and reroll ${this.rerollNumbers.slice(0, this.rerollNumbers.length - 1).join(`'s, `)}'s, and ${this.rerollNumbers[this.rerollNumbers.length - 1]}'s`
       }
     }
+    if (this.takeOrDrop) {
+      str += ` and ${this.takeOrDrop.takeDrop} the ${this.takeOrDrop.highLow} ${this.takeOrDrop.count}`
+    }
+    if (this.multiplier !== 1) {
+      str += ` x${this.multiplier}`
+    }
     return str
   }
 }
 
 Dice.parse = (str, opts) => {
+  str = str.toLowerCase()
   const rRegex = rollRegex()
   const roll = rRegex.exec(str)
   const rrRegex = rerollRegex()
   const rerolls = []
   const reroll = rrRegex.exec(str)
   const rerollNumbers = []
+  const rtake = takeRegex()
+  const take = rtake.exec(str)
+  const mult = multiplierRegex().exec(str)
+  
+  let multiplier = 1
+  if (mult) {
+    multiplier = parseFloat(mult[1])
+  }
+
+  let todTakeDrop = ''
+  let todHighLow = ''
+  let todCount = 0
+  if (take) {
+    switch (take[1]) {
+      case 'take':
+      case 'keep':
+        todTakeDrop = 'take'
+        break
+      case 'drop':
+      case 'lose':
+        todTakeDrop = 'drop'
+        break
+    }
+    switch (take[2]) {
+      case 'top':
+      case 'highest':
+      case 'high':
+      case 'best':
+        todHighLow = 'highest'
+        break
+      case 'bottom':
+      case 'lowest':
+      case 'low':
+      case 'worst':
+        todHighLow = 'lowest'
+        break
+    }
+    switch (take[3]) {
+      case 'one':
+        todCount = 1
+        break
+      case 'two':
+        todCount = 2
+        break
+      case 'three':
+        todCount = 3
+        break
+      case 'four':
+        todCount = 4
+        break
+      case 'five':
+        todCount = 5
+        break
+      case 'six':
+        todCount = 6
+        break
+      case 'seven':
+        todCount = 7
+        break
+      case 'eight':
+        todCount = 8
+        break
+      case 'nine':
+        todCount = 9
+        break
+      case 'ten':
+        todCount = 10
+        break
+      default:
+        todCount = parseInt(take[3])
+    }
+  }
   if (reroll) {
     const nRegex = numberRegex()
-    while (number = nRegex.exec(str)) {
+    const strRerollNumbers = reroll[2]
+    let broken = false
+    while (!broken && (number = nRegex.exec(strRerollNumbers))) {
       switch (number[1]) {
+        case 'drop':
+        case 'keep':
+        case 'take':
+        case 'lose':
+          broken = true
+          break;
         case 'ones':
         case 'one':
           rerollNumbers.push(1)
@@ -261,6 +375,7 @@ Dice.parse = (str, opts) => {
       count: 2,
       sides: 10,
       percent: true,
+      multiplier,
       rng: opts && opts.rng
     }))
   }
@@ -275,6 +390,14 @@ Dice.parse = (str, opts) => {
       rerollNumbers,
       modifier,
       percent,
+      multiplier,
+      takeOrDrop: (todTakeDrop && todHighLow && todCount)
+        ? {
+          takeDrop: todTakeDrop,
+          highLow: todHighLow,
+          count: todCount
+        }
+        : null,
       dicebot,
       rng: opts && opts.rng
     }))
@@ -283,23 +406,36 @@ Dice.parse = (str, opts) => {
 }
 
 Dice.chat = (chat, opts) => {
-  const dice = Dice.parse(chat, opts).filter(x => x.enabled)
-  if (dice.length) {
-    const diceStrings = dice.map(x => x.toString())
-    if (diceStrings.length > 1) {
-      diceStrings[diceStrings.length - 1] = `and ${diceStrings[diceStrings.length - 1]}`
+  if (helpRegex().test(chat)) {
+    return `Tell me to roll dice by saying "Roll" followed by dice notation. For example, \`Roll a D20\`, or \`Roll 4d4+2\`. You can also \`Roll percentile\` to roll two D10's as 1-100, or \`Roll a D100\` (It's just as random). Also try \`Roll a D20 with advantage\`, \`Roll a D20 at disadvantage\`, or \`Roll 4d6 and reroll ones\`. You can roll a nice stat block with \`Roll 4d6, reroll ones, take the top 3 x6\`. If you mess up, you can edit your message without tainting the outcome.`
+  } else {
+    const dice = Dice.parse(chat, opts).filter(x => x.enabled)
+    if (dice.length) {
+      const diceStrings = dice.map(x => x.toString())
+      if (diceStrings.length > 1) {
+        diceStrings[diceStrings.length - 1] = `and ${diceStrings[diceStrings.length - 1]}`
+      }
+      const isCoin = dice.length === 1 && dice[0].sides === 2
+      const rollingString = `${isCoin ? 'Flipping' : 'Rolling'} ${diceStrings.join(', ')}...`
+      const rolls = dice.flatMap(die => die.roll())
+      const rollsSplits = rolls.map(roll => roll.toString().split(' = '))
+      const rollsStrings = rollsSplits.map(x => x[0])
+      const rollsResults = rollsSplits.map(x => x[1]).filter(x => x)
+      const results = rollsResults.length 
+      ? ' = ' + rollsResults.join(', ')
+      : ''
+      
+      const bonusString = rolls
+        .filter(x => x.formatter && x.formatter.bonusText)
+        .map(x => x.formatter.bonusText(x))
+        .filter(x => x)
+        .map(x => '\n' + x)
+        .join('')
+
+      let rollSummary = rollsStrings.join(', ')
+      if (rollSummary.length > 1000) rollSummary = '... '
+      return `${rollingString} ${rollSummary}${results}.${bonusString}`
     }
-    const isCoin = dice.length === 1 && dice[0].sides === 2
-    const rollingString = `${isCoin ? 'Flipping' : 'Rolling'} ${diceStrings.join(', ')}...`
-    const rolls = dice.map(die => die.roll())
-    const rollsStrings = rolls.map(roll => roll.toString())
-    const bonusString = rolls
-      .filter(x => x.formatter && x.formatter.bonusText)
-      .map(x => x.formatter.bonusText(x))
-      .filter(x => x)
-      .map(x => '\n' + x)
-      .join('')
-    return `${rollingString} ${rollsStrings.join(', ')}.${bonusString}`
   }
 }
 

+ 7 - 0
test.js

@@ -14,3 +14,10 @@ Array(n).fill().map(x => console.log(db.Dice.chat('roll 1d2', {rng: sr()})))
 Array(n).fill().map(x => console.log(db.Dice.chat('roll 5d2', {rng: sr()})))
 Array(n).fill().map(x => console.log(db.Dice.chat('roll 5d12', {rng: sr()})))
 Array(n).fill().map(x => console.log(db.Dice.chat('roll percentile', {rng: sr()})))
+
+console.log(db.Dice.chat('roll 4d6, reroll ones, and take the highest three x6'))
+console.log(db.Dice.chat('roll 4d4, reroll ones, and drop the lowest one'.toUpperCase()))
+console.log(db.Dice.chat('roll 1d6 x45'))
+console.log(db.Dice.chat('roll percentile x10'))
+console.log(db.Dice.chat('roll percentile x0'))
+console.log(db.Dice.chat('Dicebot help'))