diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index eab5803b4..ef5b8c1a2 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -201,7 +201,6 @@ Style/GlobalVars: - src/test/others/testArgs.rb - src/test/others/testCard.rb - src/dice/AddDice.rb - - src/dice/RerollDice.rb - src/diceBot/RecordOfSteam.rb - src/diceBot/TokumeiTenkousei.rb - src/diceBot/BattleTech.rb diff --git a/src/dice/RerollDice.rb b/src/dice/RerollDice.rb index c18270654..6ab919e9f 100644 --- a/src/dice/RerollDice.rb +++ b/src/dice/RerollDice.rb @@ -1,5 +1,27 @@ # -*- coding: utf-8 -*- +require "utils/normalize" +require "utils/format" + +# 個数振り足しダイス +# +# ダイスを振り、条件を満たした出目の個数だけダイスを振り足す。振り足しがなくなるまでこれを繰り返す。 +# 成功条件を満たす出目の個数を調べ、成功数を表示する。 +# +# 例 +# 2R6+1R10[>3]>=5 +# 2R6+1R10>=5@>3 +# +# 振り足し条件は角カッコかコマンド末尾の @ で指定する。 +# [>3] の場合、3より大きい出目が出たら振り足す。 +# [3] のように数値のみ指定されている場合、成功条件の比較演算子を流用する。 +# 上記の例の時、出目が +# "2R6" -> [5,6] [5,4] [1,3] +# "1R10" -> [9] [1] +# だとすると、 >=5 に該当するダイスは5つなので成功数5となる。 +# +# 成功条件が書かれていない場合、成功数0として扱う。 +# 振り足し条件が数値のみ指定されている場合、比較演算子は >= が指定されたとして振舞う。 class RerollDice def initialize(bcdice, diceBot) @bcdice = bcdice @@ -7,137 +29,206 @@ def initialize(bcdice, diceBot) @nick_e = @bcdice.nick_e end - #################### 個数振り足しダイス ######################## def rollDice(string) - output = '' - - begin - output = rollDiceCatched(string) - rescue StandardError => e - output = "#{string} > " + e.to_s + unless parse(string) + return nil end - return "#{@nick_e}: #{output}" - end - - def rollDiceCatched(string) - debug('RerollDice.rollDice string', string) - string = string.strip - - m = /^S?(\d+R\d+(?:\+\d+R\d+)*)(?:\[(\d+)\])?(?:([<>=]+)(\d+))?(?:@(\d+))?$/.match(string) - unless m - debug("is invaild rdice", string) - return '1' + unless @reroll_threshold + return msg_invalid_reroll_number(string) end - string, braceThreshold, operator, conditionValue, atmarkThreshold = m.captures - - signOfInequality, diff = getCondition(operator, conditionValue) - rerollNumber = getRerollNumber(braceThreshold, atmarkThreshold, diff) - debug('rerollNumber', rerollNumber) - - debug("diff", diff) - - diceQueue = [] - string.split("+").each do |xRn| - x, n = xRn.split("R").map { |s| s.to_i } - checkReRollRule(n, signOfInequality, diff) + dice_queue = @notation.split("+").map do |xRn| + x, n = xRn.split("R").map(&:to_i) + [x, n, 0] + end - diceQueue.push([x, n, 0]) + unless dice_queue.all? { |d| valid_reroll_rule?(d[1], @reroll_cmp_op, @reroll_threshold) } + return msg_invalid_reroll_number(string) end - successCount = 0 - diceStrList = [] + success_count = 0 + dice_str_list = [] dice_cnt_total = 0 - numberSpot1Total = 0 - loopCount = 0 + one_count = 0 + loop_count = 0 - while !diceQueue.empty? && @diceBot.should_reroll?(loopCount) + dice_total_count = 0 + + while !dice_queue.empty? && @diceBot.should_reroll?(loop_count) # xRn - x, n, depth = diceQueue.shift - loopCount += 1 + x, n, depth = dice_queue.shift + loop_count += 1 + dice_total_count += x - total, dice_str, numberSpot1, cnt_max, n_max, success, rerollCount = - @bcdice.roll(x, n, (@diceBot.sortType & 2), 0, signOfInequality, diff, rerollNumber) - debug('bcdice.roll : total, dice_str, numberSpot1, cnt_max, n_max, success, rerollCount', - total, dice_str, numberSpot1, cnt_max, n_max, success, rerollCount) + dice_list = roll_(x, n) + success_count += dice_list.count() { |val| compare(val, @cmp_op, @target_number) } if @cmp_op + reroll_count = dice_list.count() { |val| compare(val, @reroll_cmp_op, @reroll_threshold) } - successCount += success - diceStrList.push(dice_str) - dice_cnt_total += x + dice_str_list.push(dice_list.join(",")) if depth.zero? - numberSpot1Total += numberSpot1 + one_count += dice_list.count(1) end - if rerollCount > 0 - diceQueue.push([rerollCount, n, depth + 1]) + if reroll_count > 0 + dice_queue.push([reroll_count, n, depth + 1]) end end - output = "#{diceStrList.join(' + ')} > 成功数#{successCount}" - string += "[#{rerollNumber}]#{signOfInequality}#{diff}" + grich_text = @diceBot.getGrichText(one_count, dice_total_count, success_count) + + sequence = [ + expr(), + dice_str_list.join(" + "), + "成功数#{success_count}", + trim_prefix(" > ", grich_text), + ].compact - debug("string", string) - output += @diceBot.getGrichText(numberSpot1Total, dice_cnt_total, successCount) + return "#{@nick_e}: #{sequence.join(' > ')}" + end - output = "(#{string}) > #{output}" + private - if output.length > $SEND_STR_MAX # 長すぎたときの救済 - output = "(#{string}) > ... > 回転数#{round} > 成功数#{successCount}" + # @param command [String] + # @return [Boolean] + def parse(command) + m = /^S?(\d+R\d+(?:\+\d+R\d+)*)(?:\[([<>=]+)?(\d+)\])?(?:([<>=]+)(\d+))?(?:@([<>=]+)?(\d+))?$/.match(command) + unless m + return false end - return output + @notation = m[1] + @cmp_op = Normalize.comparison_operator(m[4]) + @target_number = @cmp_op ? m[5].to_i : nil + unless @cmp_op + @cmp_op, @target_number = target_from_default() + end + + @reroll_cmp_op = decide_reroll_cmp_op(m) + @reroll_threshold = decide_reroll_threshold(m[3] || m[7], @target_number) + + return true end - def getCondition(operator, conditionValue) - if operator && conditionValue - operator = @bcdice.marshalSignOfInequality(operator) - conditionValue = conditionValue.to_i - elsif (m = /([<>=]+)(\d+)/.match(@diceBot.defaultSuccessTarget)) - operator = @bcdice.marshalSignOfInequality(m[1]) - conditionValue = m[2].to_i + # @return [Array<(Symbol, Integer)>] + # @return [Array<(nil, nil)>] + def target_from_default + m = /^([<>=]+)(\d+)$/.match(@diceBot.defaultSuccessTarget) + unless m + return nil, nil end - return operator, conditionValue + cmp_op = Normalize.comparison_operator(m[1]) + target_number = cmp_op ? m[2].to_i : nil + return cmp_op, target_number end - def getRerollNumber(braceThreshold, atmarkThreshold, conditionValue) - if braceThreshold - braceThreshold.to_i - elsif atmarkThreshold - atmarkThreshold.to_i + # @param m [MatchData] + # @return [Symbol] + def decide_reroll_cmp_op(m) + bracket_op = m[2] + bracket_number = m[3] + at_op = m[6] + at_number = m[7] + cmp_op = m[4] + + op = + if bracket_op && bracket_number + bracket_op + elsif at_op && at_number + at_op + else + cmp_op + end + + Normalize.comparison_operator(op) || :>= + end + + # @param captured_threshold [String, nil] + # @param target_number [Integer, nil] + # @return [Integer] + # @return [nil] + def decide_reroll_threshold(captured_threshold, target_number) + if captured_threshold + captured_threshold.to_i elsif @diceBot.rerollNumber != 0 @diceBot.rerollNumber - elsif conditionValue - conditionValue.to_i else - raiseErroForJudgeRule() + target_number end end - def raiseErroForJudgeRule() - raise "条件が間違っています。2R6>=5 あるいは 2R6[5] のように振り足し目標値を指定してください。" + # @return [String] + def expr() + reroll_cmp_op_text = @cmp_op != @reroll_cmp_op ? Format.comparison_operator(@reroll_cmp_op) : nil + cmp_op_text = Format.comparison_operator(@cmp_op) + + "(#{@notation}[#{reroll_cmp_op_text}#{@reroll_threshold}]#{cmp_op_text}#{@target_number})" end - def checkReRollRule(dice_max, signOfInequality, diff) # 振り足しロールの条件確認 - valid = true - - case signOfInequality - when '<=' - valid = false if diff >= dice_max - when '>=' - valid = false if diff <= 1 - when '<>' - valid = false if (diff > dice_max) || (diff < 1) - when '<' - valid = false if diff > dice_max - when '>' - valid = false if diff < 1 + # @param command [String] + # @return [String] + def msg_invalid_reroll_number(command) + "#{@nick_e}: #{command} > 条件が間違っています。2R6>=5 あるいは 2R6[5] のように振り足し目標値を指定してください。" + end + + # @param sides [Integer] + # @param cmp_op [Symbol] + # @param reroll_threshold [Integer] + # @return [Boolean] + def valid_reroll_rule?(sides, cmp_op, reroll_threshold) # 振り足しロールの条件確認 + case cmp_op + when :<= + reroll_threshold < sides + when :< + reroll_threshold <= sides + when :>= + reroll_threshold > 1 + when :> + reroll_threshold >= 1 + when :'!=' + (1..sides).include?(reroll_threshold) + else + true end + end - unless valid - raiseErroForJudgeRule() + # @param times [Integer] + # @param sides [Integer] + # @return [Array] + def roll_(times, sides) + _, dice_list, = @bcdice.roll(times, sides, (@diceBot.sortType & 2)) + dice_list.split(",").map(&:to_i) + end + + # @param prefix [String] + # @param string [String] + # @param [String, nil] + def trim_prefix(prefix, string) + if string.start_with?(prefix) + string = string[prefix.size..-1] + end + + if string.empty? + nil + else + string + end + end + + # 整数を比較する + # Ruby 1.8のケア用 + # + # @param lhs [Integer] + # @param op [Symbol] + # @param rhs [Integer] + # @return [Boolean] + def compare(lhs, op, rhs) + if op == :'!=' + lhs != rhs + else + lhs.send(op, rhs) end end end diff --git a/src/test/data/None.txt b/src/test/data/None.txt index a9c69b472..8462d913a 100644 --- a/src/test/data/None.txt +++ b/src/test/data/None.txt @@ -682,6 +682,12 @@ DiceBot : (2R6[3]>=3) > 6,1 + 3 + 1 > 成功数2 rand:6/6,1/6,3/6,1/6 ============================ input: +2R6>3 +output: +DiceBot : (2R6[3]>3) > 6,1 + 3 > 成功数1 +rand:6/6,1/6,3/6 +============================ +input: 12R6>=6 output: DiceBot : (12R6[6]>=6) > 4,1,5,4,3,2,5,1,6,3,2,5 + 5 > 成功数1 @@ -748,6 +754,36 @@ DiceBot : (2R4+2R6[4]>=4) > 4,3 + 3,5 + 1 + 2 > 成功数2 rand:4/4,3/4,3/6,5/6,1/4,2/6 ============================ input: +2R4+2R6[>4]>=4 +output: +DiceBot : (2R4+2R6[>4]>=4) > 4,3 + 3,5 + 2 > 成功数2 +rand:4/4,3/4,3/6,5/6,2/6 +============================ +input: +2R4+2R6>=4@>4 +output: +DiceBot : (2R4+2R6[>4]>=4) > 4,3 + 3,5 + 2 > 成功数2 +rand:4/4,3/4,3/6,5/6,2/6 +============================ +input: +2R4+2R6[<=2]>=4 +output: +DiceBot : (2R4+2R6[<=2]>=4) > 4,2 + 3,5 + 2 + 4 > 成功数3 +rand:4/4,2/4,3/6,5/6,2/4,4/4 +============================ +input: +2R4+2R6>=4@<=2 +output: +DiceBot : (2R4+2R6[<=2]>=4) > 4,2 + 3,5 + 2 + 4 > 成功数3 +rand:4/4,2/4,3/6,5/6,2/4,4/4 +============================ +input: +2R4+2R6[<>4]>=4 +output: +DiceBot : (2R4+2R6[<>4]>=4) > 4,2 + 3,5 + 4 + 4,4 > 成功数5 +rand:4/4,2/4,3/6,5/6,4/4,4/6,4/6 +============================ +input: 134 数値だけには反応しない output: rand: @@ -998,15 +1034,21 @@ DiceBot : 2R6 > 条件が間違っています。2R6>=5 あるいは 2R6[5] rand: ============================ input: +2R6<=7 無限に振り足ししてしまう +output: +DiceBot : 2R6<=7 > 条件が間違っています。2R6>=5 あるいは 2R6[5] のように振り足し目標値を指定してください。 +rand: +============================ +input: 2R6[3] output: -DiceBot : (2R6[3]) > 3,2 + 1 > 成功数0 +DiceBot : (2R6[>=3]) > 3,2 + 1 > 成功数0 rand:3/6,2/6,1/6 ============================ input: S2R6[3] output: -DiceBot : (2R6[3]) > 3,2 + 1 > 成功数0###secret dice### +DiceBot : (2R6[>=3]) > 3,2 + 1 > 成功数0###secret dice### rand:3/6,2/6,1/6 ============================ input: