Skip to content

Commit

Permalink
Merge pull request #217 from bcdice/refactor_reroll_dice
Browse files Browse the repository at this point in the history
Rerollダイスのリファクタリングと振り足し条件指定の仕様変更
  • Loading branch information
ysakasin authored Jul 2, 2020
2 parents cd94ce9 + 289b7a9 commit 1b16d6e
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 93 deletions.
1 change: 0 additions & 1 deletion .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
271 changes: 181 additions & 90 deletions src/dice/RerollDice.rb
Original file line number Diff line number Diff line change
@@ -1,143 +1,234 @@
# -*- 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
@diceBot = 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<Integer>]
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
Loading

0 comments on commit 1b16d6e

Please sign in to comment.