Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rerollダイスのリファクタリングと振り足し条件指定の仕様変更 #217

Merged
merged 6 commits into from
Jul 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,6 @@ Style/GlobalVars:
- src/test/others/testCard.rb
- src/irc/ircBot.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