From fe4845f86455d15bb5b50242d4a1a0e4334a6343 Mon Sep 17 00:00:00 2001 From: SAKATA Sinji Date: Wed, 1 Apr 2020 17:31:58 +0900 Subject: [PATCH] AddDice::Parser --- src/dice/AddDice.rb | 326 +++---------------------------- src/diceBot/CthulhuTech.rb | 16 +- src/diceBot/Satasupe.rb | 7 +- src/test/data/None.txt | 28 +-- src/test/data/RuneQuest.txt | 3 +- src/test/data/dummyBot.txt | 26 +-- src/utils/add_dice/node.rb | 155 +++++++++++++++ src/utils/add_dice/parser.rb | 216 ++++++++++++++++++++ src/utils/add_dice/randomizer.rb | 101 ++++++++++ 9 files changed, 533 insertions(+), 345 deletions(-) create mode 100644 src/utils/add_dice/node.rb create mode 100644 src/utils/add_dice/parser.rb create mode 100644 src/utils/add_dice/randomizer.rb diff --git a/src/dice/AddDice.rb b/src/dice/AddDice.rb index 6624712b4..7d622cdb0 100644 --- a/src/dice/AddDice.rb +++ b/src/dice/AddDice.rb @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- require "utils/normalizer" +require "utils/add_dice/parser" +require "utils/add_dice/randomizer" class AddDice def initialize(bcdice, diceBot) @@ -14,320 +16,38 @@ def initialize(bcdice, diceBot) #################### 加算ダイス ######################## def rollDice(string) - debug("AddDice.rollDice() begin string", string) + parser = Parser.new(string) - m = %r{(^|\s)S?(([\d\+\*\-]*[\d]+D[\d/UR@]*[\d\+\*\-D/UR]*)(([<>=]+)([?\-\d]+))?)($|\s)}i.match(string) - return "1" unless m - - string = m[2] - judgeText = m[4] # '>=10'といった成否判定文字 - judgeOperator = m[5] # '>=' といった判定の条件演算子 文字 - diffText = m[6] - - signOfInequality = "" - isCheckSuccess = false - - if judgeText - isCheckSuccess = true - string = m[3] - signOfInequality = @bcdice.marshalSignOfInequality(judgeOperator) - end - - dice_cnt = 0 - dice_max = 0 - total_n = 0 - dice_n = 0 - output = "" - n1 = 0 - n_max = 0 - - addUpTextList = string.split("+") - - addUpTextList.each do |addUpText| - subtractTextList = addUpText.split("-") - - subtractTextList.each_with_index do |subtractText, index| - next if subtractText.empty? - - debug("begin rollDiceAddingUp(subtractText, isCheckSuccess)", subtractText, isCheckSuccess) - dice_now, dice_n_wk, dice_str, n1_wk, n_max_wk, cnt_wk, max_wk = rollDiceAddingUp(subtractText, isCheckSuccess) - debug("end rollDiceAddingUp(subtractText, isCheckSuccess) -> dice_now", dice_now) - - rate = (index == 0 ? 1 : -1) - - total_n += dice_now * rate - dice_n += dice_n_wk * rate - n1 += n1_wk - n_max += n_max_wk - dice_cnt += cnt_wk - dice_max = max_wk if max_wk > dice_max - - next if @diceBot.sendMode == 0 - - operatorText = getOperatorText(rate, output) - output += "#{operatorText}#{dice_str}" - end - end - - if signOfInequality != "" - string += "#{signOfInequality}#{diffText}" + command = parser.parse() + if parser.error? + return '1' end - # ダイス目による補正処理(現状ナイトメアハンターディープ専用) - addText, revision = @diceBot.getDiceRevision(n_max, dice_max, total_n) - debug('addText, revision', addText, revision) + randomizer = Randomizer.new(@bcdice, @diceBot, command.cmp_op) + total = command.lhs.eval(randomizer) - debug("@nick_e", @nick_e) - if @diceBot.sendMode > 0 - if output =~ /[^\d\[\]]+/ - output = "#{@nick_e}: (#{string}) > #{output} > #{total_n}#{addText}" + output = + if randomizer.dice_list.size <= 1 && command.lhs.is_a?(Node::DiceRoll) + "#{@nick_e}: (#{command}) > #{total}" else - output = "#{@nick_e}: (#{string}) > #{total_n}#{addText}" - end - else - output = "#{@nick_e}: (#{string}) > #{total_n}#{addText}" - end - - total_n += revision - - if signOfInequality != "" # 成功度判定処理 - cmp_op = Normalizer.comparison_op(signOfInequality) - target = Normalizer.target_number(diffText) - successText = @diceBot.check_result(total_n, dice_n, @dice_list, dice_max, cmp_op, target) - debug("check_suc successText", successText) - output += successText - end - - # ダイスロールによるポイント等の取得処理用(T&T悪意、ナイトメアハンター・ディープ宿命、特命転校生エクストラパワーポイントなど) - output += @diceBot.getDiceRolledAdditionalText(n1, n_max, dice_max) - - if (dice_cnt == 0) || (dice_max == 0) - output = '1' - end - - debug("AddDice.rollDice() end output", output) - return output - end - - def rollDiceAddingUp(string, isCheckSuccess = false) # 加算ダイスロール(個別処理) - debug("rollDiceAddingUp() begin string", string) - - dice_max = 0 - dice_total = 1 - dice_n = 0 - output = "" - n1 = 0 - n_max = 0 - dice_cnt_total = 0 - double_check = false - - if @diceBot.sameDiceRerollCount != 0 # 振り足しありのゲームでダイスが二個以上 - if @diceBot.sameDiceRerollType <= 0 # 判定のみ振り足し - debug('判定のみ振り足し') - double_check = true if isCheckSuccess - elsif @diceBot.sameDiceRerollType <= 1 # ダメージのみ振り足し - debug('ダメージのみ振り足し') - double_check = true unless isCheckSuccess - else # 両方振り足し - double_check = true - end - end - - debug("double_check", double_check) - - while (m = /(^([\d]+\*[\d]+)\*(.+)|(.+)\*([\d]+\*[\d]+)$|(.+)\*([\d]+\*[\d]+)\*(.+))/.match(string)) - if m[2] - string = @bcdice.parren_killer('(' + m[2] + ')') + '*' + m[3] - elsif m[5] - string = m[4] + '*' + @bcdice.parren_killer('(' + m[5] + ')') - elsif m[7] - string = m[6] + '*' + @bcdice.parren_killer('(' + m[7] + ')') + '*' + m[8] - end - end - - debug("string", string) - - emptyResult = [dice_total, dice_n, output, n1, n_max, dice_cnt_total, dice_max] - - mul_cmd = string.split("*") - mul_cmd.each do |mul_line| - if (m = mul_line.match(%r{([\d]+)D([\d]+)(@(\d+))?(/\d+[UR]?)?}i)) - dice_count = m[1].to_i - dice_max = m[2].to_i - critical = m[4].to_i - slashMark = m[5] - - return emptyResult if (critical != 0) && !@diceBot.is2dCritical - return emptyResult if dice_max > $DICE_MAXNUM - - dice_max, dice_now, output_tmp, n1_count, max_number_tmp, result_dice_count = - rollDiceAddingUpCommand(dice_count, dice_max, slashMark, double_check, isCheckSuccess, critical) - - output += "*" if output != "" - output += output_tmp - - dice_total *= dice_now - - dice_n += dice_now - dice_cnt_total += result_dice_count - n1 += n1_count - n_max += max_number_tmp - - else - mul_line = mul_line.to_i - debug('dice_total', dice_total) - debug('mul_line', mul_line) - - dice_total *= mul_line - - unless output.empty? - output += "*" - end - - if mul_line < 0 - output += "(#{mul_line})" - else - output += mul_line.to_s - end - end - end - - debug("rollDiceAddingUp() end output", dice_total, dice_n, output, n1, n_max, dice_cnt_total, dice_max) - return dice_total, dice_n, output, n1, n_max, dice_cnt_total, dice_max - end - - def rollDiceAddingUpCommand(dice_count, dice_max, slashMark, double_check, isCheckSuccess, critical) - result_dice_count = 0 - dice_now = 0 - n1_count = 0 - max_number = 0 - dice_str = "" - dice_arry = [] - dice_arry.push(dice_count) - loop_count = 0 - - debug("before while dice_arry", dice_arry) - - while !dice_arry.empty? - debug("IN while dice_arry", dice_arry) - - dice_wk = dice_arry.shift - result_dice_count += dice_wk - - debug('dice_wk', dice_wk) - debug('dice_max', dice_max) - debug('(sortType & 1)', (@diceBot.sortType & 1)) - - dice_dat = rollLocal(dice_wk, dice_max, (@diceBot.sortType & 1)) - debug('dice_dat', dice_dat) - - dice_new = dice_dat[0] - dice_now += dice_new - - debug('slashMark', slashMark) - dice_now = getSlashedDice(slashMark, dice_now) - - dice_str += "][" if dice_str != "" - debug('dice_str', dice_str) - - dice_str += dice_dat[1] - n1_count += dice_dat[2] - max_number += dice_dat[3] - - # 振り足しありでダイスが二個以上 - if double_check && (dice_wk >= 2) - addDiceArrayByAddDiceCount(dice_dat, dice_max, dice_arry, dice_wk) + "#{@nick_e}: (#{command}) > #{command.lhs.output} > #{total}" end - @diceBot.check2dCritical(critical, dice_new, dice_arry, loop_count) - loop_count += 1 - end + dice_list = randomizer.dice_list + num_one = dice_list.count(1) + num_max = dice_list.count(randomizer.sides) - # ダイス目文字列からダイス値を変更する場合の処理(現状クトゥルフ・テック専用) - dice_now = @diceBot.changeDiceValueByDiceText(dice_now, dice_str, isCheckSuccess, dice_max) + suffix, revision = @diceBot.getDiceRevision(num_max, randomizer.sides, total) + output += suffix + total += revision - output = "" - if @diceBot.sendMode > 1 - output += "#{dice_now}[#{dice_str}]" - elsif @diceBot.sendMode > 0 - output += dice_now.to_s + if command.cmp_op + dice_total = dice_list.inject(&:+) + output += @diceBot.check_result(total, dice_total, dice_list, randomizer.sides, command.cmp_op, command.rhs) end - return dice_max, dice_now, output, n1_count, max_number, result_dice_count - end - - def addDiceArrayByAddDiceCount(dice_dat, _dice_max, dice_queue, roll_times) - values = dice_dat[1].split(",").map(&:to_i) - count_bucket = {} - - values.each do |val| - count_bucket[val] ||= 0 - count_bucket[val] += 1 - end + output += @diceBot.getDiceRolledAdditionalText(num_one, num_max, randomizer.sides) - reroll_threshold = @diceBot.sameDiceRerollCount == 1 ? roll_times : @diceBot.sameDiceRerollCount - count_bucket.each do |_, num| - if num >= reroll_threshold - dice_queue.push(num) - end - end - end - - def getSlashedDice(slashMark, lhs) - m = %r{^/(\d+)(.)?$}i.match(slashMark) - return lhs unless m - - rhs = m[1].to_i - mark = m[2] - - return lhs if rhs == 0 - - value = lhs.to_f / rhs - - if mark == "U" - return value.ceil - elsif mark == "R" - return value.round - else - return value.floor - end - end - - def rollLocal(dice_wk, dice_max, sortType) - if dice_max == 66 - return rollD66(dice_wk) - end - - ret = @bcdice.roll(dice_wk, dice_max, sortType) - @dice_list.concat(ret[1].split(",").map(&:to_i)) - - return ret - end - - def rollD66(count) - d66List = [] - - count.times do - d66List << @bcdice.getD66Value() - end - - total = d66List.inject { |sum, i| sum + i } - text = d66List.join(',') - n1Count = d66List.count(1) - nMaxCount = d66List.count(66) - - @dice_list.concat(d66List) - - return [total, text, n1Count, nMaxCount, 0, 0, 0] - end - - def getOperatorText(rate, output) - if rate < 0 - '-' - elsif output.empty? - '' - else - "+" - end + return output end end diff --git a/src/diceBot/CthulhuTech.rb b/src/diceBot/CthulhuTech.rb index e77c5da39..3791c7542 100644 --- a/src/diceBot/CthulhuTech.rb +++ b/src/diceBot/CthulhuTech.rb @@ -81,23 +81,17 @@ def getDamageDice(total_n, diff) # ダイス目文字列からダイス値を変更する場合の処理 # クトゥルフ・テックの判定用ダイス計算 - def changeDiceValueByDiceText(dice_now, dice_str, isCheckSuccess, dice_max) - debug("changeDiceValueByDiceText dice_now, dice_str, isCheckSuccess, dice_max", dice_now, dice_str, isCheckSuccess, dice_max) - if isCheckSuccess && (dice_max == 10) - debug('cthulhutech_check(dice_str) called') - debug('dice_str, dice_now', dice_str, dice_now) - dice_now = cthulhutech_check(dice_str) + def changeDiceValueByDiceText(dice_total, dice_list, cmp_op, sides) + if cmp_op && (sides == 10) + dice_total = cthulhutech_check(dice_list) end - debug('dice_str, dice_now', dice_str, dice_now) - return dice_now + return dice_total end #################### CthulhuTech ######################## # CthulhuTechの判定用ダイス計算 - def cthulhutech_check(dice_str) - dice_aRR = dice_str.split(/,/).collect { |i| i.to_i } - + def cthulhutech_check(dice_aRR) dice_num = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] max_num = 0 diff --git a/src/diceBot/Satasupe.rb b/src/diceBot/Satasupe.rb index 9d8cc6b25..1f2130042 100644 --- a/src/diceBot/Satasupe.rb +++ b/src/diceBot/Satasupe.rb @@ -532,10 +532,11 @@ def getNpcTableResult(counts) age_type -= 1 agen_text = agen[age_type] - age_num = agen_text.split(/\+/) + age_const, age_dice = agen_text.split("+") - total, = rollDiceAddingUp(age_num[1]) - ysold = total + age_num[0].to_i + times, sides = age_dice.split("D").map(&:to_i) + total, = roll(times, sides) + ysold = total + age_const.to_i lmodValue = lmood[(rand 6)] lageValue = lage[(rand 3)] diff --git a/src/test/data/None.txt b/src/test/data/None.txt index 46e5ba439..e9d6ed6d1 100644 --- a/src/test/data/None.txt +++ b/src/test/data/None.txt @@ -25,7 +25,7 @@ rand:4/6,3/6 input: 2d6+1-1-2-3-4 output: -DiceBot : (2D6+1-1-2-3-4) > 5[4,1]+1-1-2-3-4 > -4 +DiceBot : (2D6-9) > 5[4,1]-9 > -4 rand:4/6,1/6 ============================ input: @@ -43,13 +43,13 @@ rand:2/6,4/6 input: 2d10+3-4 output: -DiceBot : (2D10+3-4) > 8[3,5]+3-4 > 7 +DiceBot : (2D10-1) > 8[3,5]-1 > 7 rand:3/10,5/10 ============================ input: 2d10+3*4 output: -DiceBot : (2D10+3*4) > 8[3,5]+3*4 > 20 +DiceBot : (2D10+12) > 8[3,5]+12 > 20 rand:3/10,5/10 ============================ input: @@ -79,61 +79,61 @@ rand:6/6,5/6,6/6,2/6,1/6,4/6 input: 1D6/2 output: -DiceBot : (1D6/2) > 0 +DiceBot : (1D6/2) > 1[1]/2 > 0 rand:1/6 ============================ input: 3D6/2 output: -DiceBot : (3D6/2) > 3[1,2,4] > 3 +DiceBot : (3D6/2) > 7[1,2,4]/2 > 3 rand:1/6,2/6,4/6 ============================ input: 3D6/2+1D6 output: -DiceBot : (3D6/2+1D6) > 3[1,2,4]+5[5] > 8 +DiceBot : (3D6/2+1D6) > 7[1,2,4]/2+5[5] > 8 rand:1/6,2/6,4/6,5/6 ============================ input: 3D6+1D6/2 output: -DiceBot : (3D6+1D6/2) > 7[1,2,4]+2[5] > 9 +DiceBot : (3D6+1D6/2) > 7[1,2,4]+5[5]/2 > 9 rand:1/6,2/6,4/6,5/6 ============================ input: 3D6+1D6/2U output: -DiceBot : (3D6+1D6/2U) > 7[1,2,4]+3[5] > 10 +DiceBot : (3D6+1D6/2U) > 7[1,2,4]+5[5]/2U > 10 rand:1/6,2/6,4/6,5/6 ============================ input: 5D6/10 output: -DiceBot : (5D6/10) > 2[6,6,6,6,5] > 2 +DiceBot : (5D6/10) > 29[6,6,6,6,5]/10 > 2 rand:6/6,6/6,6/6,6/6,5/6 ============================ input: 3D6/2U output: -DiceBot : (3D6/2U) > 4[1,2,4] > 4 +DiceBot : (3D6/2U) > 7[1,2,4]/2U > 4 rand:1/6,2/6,4/6 ============================ input: 5D6/10u output: -DiceBot : (5D6/10U) > 3[6,6,6,2,1] > 3 +DiceBot : (5D6/10U) > 21[6,6,6,2,1]/10U > 3 rand:6/6,6/6,6/6,2/6,1/6 ============================ input: 1D100/10R output: -DiceBot : (1D100/10R) > 6 +DiceBot : (1D100/10R) > 55[55]/10R > 6 rand:55/100 ============================ input: 1D100/10r output: -DiceBot : (1D100/10R) > 5 +DiceBot : (1D100/10R) > 54[54]/10R > 5 rand:54/100 ============================ input: @@ -169,7 +169,7 @@ rand:2/4,3/3,5/6,5/6,4/6 input: 1D6/0 output: -DiceBot : (1D6/0) > 1 +DiceBot : (1D6/0) > 1[1]/0 > 1 rand:1/6 ============================ input: diff --git a/src/test/data/RuneQuest.txt b/src/test/data/RuneQuest.txt index df3bfed7d..c77b763ca 100644 --- a/src/test/data/RuneQuest.txt +++ b/src/test/data/RuneQuest.txt @@ -181,7 +181,8 @@ rand:11/100 input: 1d100>=68+12 output: -rand: +RuneQuest : (1D100>=80) > 11 > 失敗 +rand:11/100 ============================ input: 1d100>=(68+12) diff --git a/src/test/data/dummyBot.txt b/src/test/data/dummyBot.txt index 121131317..97267a70b 100644 --- a/src/test/data/dummyBot.txt +++ b/src/test/data/dummyBot.txt @@ -96,13 +96,13 @@ rand: input: 2D6*2*3 output: -DiceBot : (2D6*2*3) > 5[2,3]*6 > 30 +DiceBot : (2D6*6) > 5[2,3]*6 > 30 rand:2/6,3/6 ============================ input: 2D6*2*3-1 output: -DiceBot : (2D6*2*3-1) > 5[2,3]*6-1 > 29 +DiceBot : (2D6*6-1) > 5[2,3]*6-1 > 29 rand:2/6,3/6 ============================ input: @@ -120,66 +120,66 @@ rand: input: 2D10/10 output: -DiceBot : (2D10/10) > 0[1,8] > 0 +DiceBot : (2D10/10) > 9[1,8]/10 > 0 rand:1/10,8/10 ============================ input: 2D10/10 output: -DiceBot : (2D10/10) > 1[1,9] > 1 +DiceBot : (2D10/10) > 10[1,9]/10 > 1 rand:1/10,9/10 ============================ input: 2D10/10 output: -DiceBot : (2D10/10) > 1[1,10] > 1 +DiceBot : (2D10/10) > 11[1,10]/10 > 1 rand:1/10,10/10 ============================ input: 2D10/10R output: -DiceBot : (2D10/10R) > 0[1,3] > 0 +DiceBot : (2D10/10R) > 4[1,3]/10R > 0 rand:1/10,3/10 ============================ input: 2D10/10R output: -DiceBot : (2D10/10R) > 1[1,4] > 1 +DiceBot : (2D10/10R) > 5[1,4]/10R > 1 rand:1/10,4/10 ============================ input: 2D10/10R output: -DiceBot : (2D10/10R) > 1[10,4] > 1 +DiceBot : (2D10/10R) > 14[10,4]/10R > 1 rand:10/10,4/10 ============================ input: 2D10/10R output: -DiceBot : (2D10/10R) > 2[10,5] > 2 +DiceBot : (2D10/10R) > 15[10,5]/10R > 2 rand:10/10,5/10 ============================ input: 2D10/10U output: -DiceBot : (2D10/10U) > 1[1,1] > 1 +DiceBot : (2D10/10U) > 2[1,1]/10U > 1 rand:1/10,1/10 ============================ input: 2D10/10U output: -DiceBot : (2D10/10U) > 1[1,8] > 1 +DiceBot : (2D10/10U) > 9[1,8]/10U > 1 rand:1/10,8/10 ============================ input: 2D10/10U output: -DiceBot : (2D10/10U) > 1[1,9] > 1 +DiceBot : (2D10/10U) > 10[1,9]/10U > 1 rand:1/10,9/10 ============================ input: 2D10/10U output: -DiceBot : (2D10/10U) > 2[1,10] > 2 +DiceBot : (2D10/10U) > 11[1,10]/10U > 2 rand:1/10,10/10 ============================ diff --git a/src/utils/add_dice/node.rb b/src/utils/add_dice/node.rb new file mode 100644 index 000000000..10df86934 --- /dev/null +++ b/src/utils/add_dice/node.rb @@ -0,0 +1,155 @@ +class AddDice + module Node + class Command + attr_reader :lhs, :cmp_op, :rhs + + def initialize(lhs, cmp_op, rhs) + @lhs = lhs + @cmp_op = cmp_op + @rhs = rhs + end + + def to_s + @lhs.to_s + cmp_op_text + @rhs.to_s + end + + private + + def cmp_op_text + case @cmp_op + when :'!=' + '<>' + when :== + '=' + else + @cmp_op.to_s + end + end + end + + class BinaryOp + def initialize(lhs, op, rhs, round_type = nil) + @lhs = lhs + @op = op + @rhs = rhs + @round_type = round_type + end + + def eval(randomizer) + lhs = @lhs.eval(randomizer) + rhs = @rhs.eval(randomizer) + + calc(lhs, rhs) + end + + def to_s + @lhs.to_s + @op.to_s + @rhs.to_s + round_type_suffix() + end + + def output + @lhs.output + @op.to_s + @rhs.output + round_type_suffix() + end + + private + + def calc(lhs, rhs) + if @op != :/ + return lhs.send(@op, rhs) + end + + if rhs.zero? + return 1 + end + + case @round_type + when :roundUp + (lhs.to_f / rhs).ceil + when :roundOff + (lhs.to_f / rhs).round + else + lhs / rhs + end + end + + def round_type_suffix + case @round_type + when :roundUp + "U" + when :roundOff + "R" + else + "" + end + end + end + + class Negate + attr_reader :body + + def initialize(body) + @body = body + end + + def eval(randomizer) + -@body.eval(randomizer) + end + + def to_s + "-#{@body}" + end + + def output + "-#{@body.output}" + end + end + + class DiceRoll + def initialize(times, sides, critical) + @times = times.literal + @sides = sides.literal + @critical = critical.nil? ? nil : critical.literal + @text = nil + end + + def eval(randomizer) + total, @text = randomizer.roll(@times, @sides, @critical) + + total + end + + def to_s + if @critical + "#{@times}D#{@sides}@#{@critical}" + else + "#{@times}D#{@sides}" + end + end + + def output + @text + end + end + + class Number + attr_reader :literal + + def initialize(literal) + @literal = literal + end + + def negate + Number.new(-@literal) + end + + def eval(_randomizer) + @literal + end + + def to_s + @literal.to_s + end + + alias output to_s + end + end +end diff --git a/src/utils/add_dice/parser.rb b/src/utils/add_dice/parser.rb new file mode 100644 index 000000000..a6d8421ba --- /dev/null +++ b/src/utils/add_dice/parser.rb @@ -0,0 +1,216 @@ +require "utils/ArithmeticEvaluator" +require "utils/normalizer" +require "utils/add_dice/node" + +class AddDice + class Parser + def initialize(expr) + @expr = expr + @idx = 0 + @error = false + end + + def parse() + lhs, cmp_op, rhs = @expr.partition(/[<>=]+/) + + cmp_op = Normalizer.comparison_op(cmp_op) + if !rhs.empty? && rhs != "?" + rhs = ArithmeticEvaluator.new.eval(rhs) + end + + @tokens = tokenize(lhs) + lhs = expr() + + if @idx != @tokens.size + @error = true + end + + return AddDice::Node::Command.new(lhs, cmp_op, rhs) + end + + def error? + @error + end + + private + + def tokenize(expr) + expr.gsub(%r{[\+\-\*/DURS@]}) { |e| " #{e} " }.split(' ') + end + + def expr + consume("S") + + return add() + end + + def add + sequence = [mul()] + + loop do + if consume("+") + sequence.push(:+, nil, mul()) + elsif consume("-") + sequence.push(:-, nil, mul()) + else + break + end + end + + sequence = fold_sequence(sequence) + return construct_binop(sequence) + end + + def mul + sequence = [unary()] + + loop do + if consume("*") + sequence.push(:*, nil, unary()) + elsif consume("/") + rhs = unary() + round_type = consume_round_type() + sequence.push(:/, round_type, rhs) + else + break + end + end + + sequence = fold_sequence(sequence) + return construct_binop(sequence) + end + + def fold_sequence(sequence) + list = [sequence.shift] + + while !sequence.empty? + lhs = list.pop + op, round_type, rhs = sequence.shift(3) + + if lhs.is_a?(Node::Number) && rhs.is_a?(Node::Number) && (list.empty? || op != :/) + num = calc(lhs.literal, op, rhs.literal, round_type) + list.push(Node::Number.new(num)) + else + list.push(lhs, op, round_type, rhs) + end + end + + list + end + + def construct_binop(sequence) + node = sequence.shift + + while !sequence.empty? + op, round_type, rhs = sequence.shift(3) + if rhs.is_a?(Node::Number) && rhs.literal.negative? + if op == :+ + op = :- + rhs = rhs.negate + elsif op == :- + op = :+ + rhs = rhs.negate + end + end + node = Node::BinaryOp.new(node, op, rhs, round_type) + end + + node + end + + def calc(lhs, op, rhs, round_type) + if op != :/ + return lhs.send(op, rhs) + end + + if rhs.zero? + @error = true + return 1 + end + + case round_type + when :roundUp + (lhs.to_f / rhs).ceil + when :roundOff + (lhs.to_f / rhs).round + else + lhs / rhs + end + end + + def unary + if consume("+") + unary() + elsif consume("-") + node = unary() + + case node + when Node::Negate + node.body + when Node::Number + node.negate() + else + AddDice::Node::Negate.new(node) + end + else + term() + end + end + + def term + ret = expect_number() + if consume("D") + times = ret + sides = expect_number() + critical = consume("@") ? expect_number() : nil + + ret = AddDice::Node::DiceRoll.new(times, sides, critical) + end + + ret + end + + def consume(str) + if @tokens[@idx] != str + return false + end + + @idx += 1 + return true + end + + def consume_round_type() + if consume("U") + :roundUp + elsif consume("R") + :roundOff + end + end + + def expect(str) + if @tokens[@idx] != str + @error = true + end + + @idx += 1 + end + + def expect_number() + unless integer?(@tokens[@idx]) + @error = true + @idx += 1 + return AddDice::Node::Number.new(0) + end + + ret = @tokens[@idx].to_i + @idx += 1 + return AddDice::Node::Number.new(ret) + end + + def integer?(str) + # Ruby 1.9 以降では Kernel.#Integer を使うべき + # Ruby 1.8 にもあるが、基数を指定できない問題がある + !/^\d+$/.match(str).nil? + end + end +end diff --git a/src/utils/add_dice/randomizer.rb b/src/utils/add_dice/randomizer.rb new file mode 100644 index 000000000..d9eb15098 --- /dev/null +++ b/src/utils/add_dice/randomizer.rb @@ -0,0 +1,101 @@ +class AddDice + class Randomizer + attr_reader :dicebot, :cmp_op, :dice_list, :sides + + def initialize(bcdice, dicebot, cmp_op) + @bcdice = bcdice + @dicebot = dicebot + @cmp_op = cmp_op + @sides = 0 + @dice_list = [] + end + + def roll(times, sides, critical) + total = 0 + results_list = [] + + loop_count = 0 + queue = [times] + while !queue.empty? + times = queue.shift + val, dice_list = roll_once(times, sides) + + total += val + results_list.push(dice_list) + + enqueue_reroll(dice_list, queue, times) + @dicebot.check2dCritical(critical, val, queue, loop_count) + loop_count += 1 + end + + total = @dicebot.changeDiceValueByDiceText(total, results_list.flatten, @cmp_op, sides) + + text = total.to_s + results_list.each do |list| + text += '[' + list.join(',') + ']' + end + + [total, text] + end + + private + + def roll_once(times, sides) + @sides = sides if @sides < sides + + if sides == 66 + return rollD66(dice_wk) + end + + _, dice_list, = @bcdice.roll(times, sides, @dicebot.sortType & 1) + dice_list = dice_list.split(",").map(&:to_i) + @dice_list.concat(dice_list) + + total = dice_list.inject(&:+) || 0 + return [total, dice_list] + end + + def roll_d66(times) + dice_list = Array.new(times) { @bcdice.getD66Value() } + @dice_list.concat(dice_list) + + total = dice_list.inject(&:+) + return [total, dice_list] + end + + def double_check? + if @dicebot.sameDiceRerollCount != 0 # 振り足しありのゲームでダイスが二個以上 + if @dicebot.sameDiceRerollType <= 0 # 判定のみ振り足し + return true if @cmp_op + elsif @dicebot.sameDiceRerollType <= 1 # ダメージのみ振り足し + debug('ダメージのみ振り足し') + return true unless @cmp_op + else # 両方振り足し + return true + end + end + + return false + end + + def enqueue_reroll(dice_list, dice_queue, roll_times) + unless double_check? && (roll_times >= 2) + return + end + + count_bucket = {} + + dice_list.each do |val| + count_bucket[val] ||= 0 + count_bucket[val] += 1 + end + + reroll_threshold = @dicebot.sameDiceRerollCount == 1 ? roll_times : @dicebot.sameDiceRerollCount + count_bucket.each do |_, num| + if num >= reroll_threshold + dice_queue.push(num) + end + end + end + end +end