diff --git a/.gitignore b/.gitignore index 23fad00c6..7d7be8be3 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ lib/bcdice/common_command/barabara_dice/parser.rb lib/bcdice/common_command/calc/parser.rb lib/bcdice/common_command/reroll_dice/parser.rb lib/bcdice/common_command/upper_dice/parser.rb +lib/bcdice/game_system/sword_world/rating_parser.rb diff --git a/.rubocop.yml b/.rubocop.yml index b57ea1f7a..d085fb8ff 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -15,6 +15,7 @@ AllCops: - lib/bcdice/common_command/calc/parser.rb - lib/bcdice/common_command/reroll_dice/parser.rb - lib/bcdice/common_command/upper_dice/parser.rb + - lib/bcdice/game_system/sword_world/rating_parser.rb # Due to old Ruby 1.8.x Style/Lambda: diff --git a/Rakefile b/Rakefile index d44c804e5..687430f11 100644 --- a/Rakefile +++ b/Rakefile @@ -28,6 +28,7 @@ RACC_TARGETS = [ "lib/bcdice/common_command/calc/parser.rb", "lib/bcdice/common_command/reroll_dice/parser.rb", "lib/bcdice/common_command/upper_dice/parser.rb", + "lib/bcdice/game_system/sword_world/rating_parser.rb", ].freeze task racc: RACC_TARGETS diff --git a/lib/bcdice/game_system/SwordWorld.rb b/lib/bcdice/game_system/SwordWorld.rb index 4806c90a6..5792226a6 100644 --- a/lib/bcdice/game_system/SwordWorld.rb +++ b/lib/bcdice/game_system/SwordWorld.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "bcdice/game_system/sword_world/rating_parser" + module BCDice module GameSystem class SwordWorld < Base @@ -22,28 +24,6 @@ def initialize(command) @rating_table = 0 end - # change_textで使うレーティング表コマンドの正規表現 - # - # SW 2.5のダイスボットでも必要なため、共通化のために定数として定義する - RATING_TABLE_RE_FOR_CHANGE_TEXT = /\AS?H?K\d+/i.freeze - - # コマンド実行前にメッセージを置換する - # @param [String] string 受信したメッセージ - # @return [String] - def replace_text(string) - return string unless RATING_TABLE_RE_FOR_CHANGE_TEXT.match?(string) - - string - .gsub(/\[(\d+)\]/) { "c[#{Regexp.last_match(1)}]" } - .gsub(/@(\d+)/) { "c[#{Regexp.last_match(1)}]" } - .gsub(/\$([-+]?\d+)/) { "m[#{Regexp.last_match(1)}]" } - .gsub(/r([-+]?\d+)/i) { "r[#{Regexp.last_match(1)}]" } - end - - def getRatingCommandStrings - "cmCM" - end - def result_2d6(total, dice_total, _dice_list, cmp_op, target) if dice_total >= 12 Result.critical("自動的成功") @@ -64,60 +44,32 @@ def eval_game_system_specific_command(command) private + def rating_parser + return RatingParser.new + end + #################### SWレーティング表 ######################## def rating(string) # レーティング表 - string = replace_text(string) debug("rating string", string) + command = rating_parser.parse(string) - commands = getRatingCommandStrings - - m = /^S?(H?K[\d+\-]+([#{commands}]\[([\d+\-]+)\])*([\d+\-]*)([CMR]\[([\d+\-]+)\]|GF|H)*)/i.match(string) - unless m + unless command debug("not matched") return '1' end - string = m[1] - half = string.include?("H") - - rateUp, string = getRateUpFromString(string) - crit, string = getCriticalFromString(string, half) - firstDiceChanteTo, firstDiceChangeModify, string = getDiceChangesFromString(string) - - key, addValue = getKeyAndAddValueFromString(string) - - return '1' unless key =~ /(\d+)/ - - key = Regexp.last_match(1).to_i - # 2.0対応 rate_sw2_0 = getSW2_0_RatingTable keyMax = rate_sw2_0.length - 1 debug("keyMax", keyMax) - if key > keyMax + if command.rate > keyMax return "キーナンバーは#{keyMax}までです" end newRates = getNewRates(rate_sw2_0) - output = "KeyNo.#{key}" - - output += "c[#{crit}]" if crit < 13 - output += "m[#{firstDiceChangeModify}]" if firstDiceChangeModify != 0 - output += "m[#{firstDiceChanteTo}]" if firstDiceChanteTo != 0 - output += "r[#{rateUp}]" if rateUp != 0 - - output, values = getAdditionalString(string, output) - - debug('output', output) - - if addValue != 0 - output += "+#{addValue}" if addValue > 0 - output += addValue.to_s if addValue < 0 - end - - output += " > " + output = "#{command} > " diceResultTotals = [] diceResults = [] @@ -126,17 +78,19 @@ def rating(string) # レーティング表 diceOnlyTotal = 0 totalValue = 0 round = 0 + first_to = command.first_to + first_modify = command.first_modify loop do - dice_raw, diceText = rollDice(values) + dice_raw, diceText = rollDice(command) dice = dice_raw - if firstDiceChanteTo != 0 - dice = dice_raw = firstDiceChanteTo - firstDiceChanteTo = 0 - elsif firstDiceChangeModify != 0 - dice += firstDiceChangeModify.to_i - firstDiceChangeModify = 0 + if first_to != 0 + dice = dice_raw = first_to + first_to = 0 + elsif first_modify != 0 + dice += first_modify + first_modify = 0 end # 出目がピンゾロの時にはそこで終了 @@ -149,12 +103,12 @@ def rating(string) # レーティング表 break end - dice += getAdditionalDiceValue(dice, values) + dice += command.kept_modify if (command.kept_modify != 0) && (dice != 2) dice = 2 if dice < 2 dice = 12 if dice > 12 - currentKey = [key + round * rateUp, keyMax].min + currentKey = [command.rate + round * command.rateup, keyMax].min debug("currentKey", currentKey) rateValue = newRates[dice][currentKey] debug("rateValue", rateValue) @@ -168,79 +122,15 @@ def rating(string) # レーティング表 round += 1 - break unless dice >= crit + break unless dice >= command.critical end - output += getResultText(totalValue, addValue, diceResults, diceResultTotals, - rateResults, diceOnlyTotal, round, half) + output += getResultText(totalValue, command, diceResults, diceResultTotals, + rateResults, diceOnlyTotal, round) return output end - def getAdditionalString(_string, output) - values = {} - return output, values - end - - def getAdditionalDiceValue(_dice, _values) - 0 - end - - def getCriticalFromString(string, half) - crit = half ? 13 : 10 - - regexp = /c\[(\d+)\]/i - - if regexp =~ string - crit = Regexp.last_match(1).to_i - crit = 3 if crit < 3 # エラートラップ(クリティカル値が3未満なら3とする) - string = string.gsub(regexp, '') - end - - return crit, string - end - - def getDiceChangesFromString(string) - firstDiceChanteTo = 0 - firstDiceChangeModify = 0 - - regexp = /m\[([\d+\-]+)\]/i - - if regexp =~ string - firstDiceChangeModify = Regexp.last_match(1) - - unless /[+\-]/ =~ firstDiceChangeModify - firstDiceChanteTo = firstDiceChangeModify.to_i - firstDiceChangeModify = 0 - end - - string = string.gsub(regexp, '') - end - - return firstDiceChanteTo, firstDiceChangeModify, string - end - - def getRateUpFromString(string) - rateUp = 0 - return rateUp, string - end - - def getKeyAndAddValueFromString(string) - key = nil - addValue = 0 - - if /K(\d+)([\d+\-]*)/i =~ string # ボーナスの抽出 - key = Regexp.last_match(1) - if Regexp.last_match(2) - addValue = ArithmeticEvaluator.eval(Regexp.last_match(2)) - end - else - key = string - end - - return key, addValue - end - def getSW2_0_RatingTable rate_sw2_0 = [ # 0 @@ -397,7 +287,7 @@ def getNewRates(rate_sw2_0) return newRates end - def rollDice(_values) + def rollDice(_command) dice_list = @randomizer.roll_barabara(2, 6) total = dice_list.sum() dice_str = dice_list.join(",") @@ -405,15 +295,14 @@ def rollDice(_values) end # @param rating_total [Integer] - # @param modifier [Integer] + # @param command [SwordWorld::RatingParsed] # @param diceResults [Array] # @param diceResultTotals [Array] # @param rateResults [Array] # @param dice_total [Integer] # @param round [Integer] - # @param half [Boolean] - def getResultText(rating_total, modifier, diceResults, diceResultTotals, - rateResults, dice_total, round, half) + def getResultText(rating_total, command, diceResults, diceResultTotals, + rateResults, dice_total, round) sequence = [] sequence.push("2D:[#{diceResults.join(' ')}]=#{diceResultTotals.join(',')}") @@ -425,14 +314,21 @@ def getResultText(rating_total, modifier, diceResults, diceResultTotals, end # rate回数が1回で、修正値がない時には途中式と最終結果が一致するので、途中式を省略する - if rateResults.size > 1 || modifier != 0 - text = rateResults.join(',') + Format.modifier(modifier) - if half + if rateResults.size > 1 || command.modifier != 0 + text = rateResults.join(',') + Format.modifier(command.modifier) + if command.half text = "(#{text})/2" + if command.modifier_after_half != 0 + text += Format.modifier(command.modifier_after_half) + end + end + sequence.push(text) + elsif command.half + text = "#{rateResults.first}/2" + if command.modifier_after_half != 0 + text += Format.modifier(command.modifier_after_half) end sequence.push(text) - elsif half - sequence.push("#{rateResults.first}/2") end if round > 1 @@ -440,9 +336,12 @@ def getResultText(rating_total, modifier, diceResults, diceResultTotals, sequence.push(round_text) end - total = rating_total + modifier - if half + total = rating_total + command.modifier + if command.half total = (total / 2.0).ceil + if command.modifier_after_half != 0 + total += command.modifier_after_half + end end total_text = total.to_s @@ -450,41 +349,6 @@ def getResultText(rating_total, modifier, diceResults, diceResultTotals, return sequence.join(" > ") end - - def setRatingTable(tnick) - mode_str = "" - pre_mode = @rating_table - - if /(\d+)/ =~ tnick - @rating_table = Regexp.last_match(1).to_i - if @rating_table > 1 - mode_str = "2.0-mode" - @rating_table = 2 - elsif @rating_table > 0 - mode_str = "new-mode" - @rating_table = 1 - else - mode_str = "old-mode" - @rating_table = 0 - end - else - case tnick - when /old/i - @rating_table = 0 - mode_str = "old-mode" - when /new/i - @rating_table = 1 - mode_str = "new-mode" - when /2\.0/i - @rating_table = 2 - mode_str = "2.0-mode" - end - end - - return '1' if @rating_table == pre_mode - - return "RatingTableを#{mode_str}に変更しました" - end end end end diff --git a/lib/bcdice/game_system/SwordWorld2_0.rb b/lib/bcdice/game_system/SwordWorld2_0.rb index fd8c5a5c2..e0eb075a2 100644 --- a/lib/bcdice/game_system/SwordWorld2_0.rb +++ b/lib/bcdice/game_system/SwordWorld2_0.rb @@ -34,10 +34,12 @@ class SwordWorld2_0 < SwordWorld  またタイプの軽減化のために末尾に「@クリティカル値」でも処理するようにしました。  例)K20[10]   K10+5[9]   k30[10]   k10[9]+10   k10-5@9 - ・レーティング表の半減 (HKx) + ・レーティング表の半減 (HKx, KxH+N)  レーティング表の先頭または末尾に"H"をつけると、レーティング表を振って最終結果を半減させます。 +  末尾につけた場合、直後に修正ををつけることで、半減後の加減算を行うことができます。 +  この際、複数の項による修正にはカッコで囲うことが必要です(カッコがないとパースに失敗します)  クリティカル値を指定しない場合、クリティカルなしと扱われます。 -  例)HK20  K20h  HK10-5@9  K10-5@9H  K20gfH +  例)HK20  K20h  HK10-5@9  K10-5@9H  K20gfH  K20+8H+2  K20+8H(1+1) ・ダイス目の修正(運命変転やクリティカルレイ用)  末尾に「$修正値」でダイス目に修正がかかります。 @@ -201,32 +203,13 @@ def eval_game_system_specific_command(command) end end - def getRateUpFromString(string) - rateUp = 0 - - regexp = /r\[(\d+)\]/i - if (m = regexp.match(string)) - rateUp = m[1].to_i - string = string.gsub(regexp, '') - end - - return rateUp, string + def rating_parser + return RatingParser.new(version: :v2_0) end - def getAdditionalString(string, output) - output, values = super(string, output) - - isGratestFortune, = getGratestFortuneFromString(string) - - values['isGratestFortune'] = isGratestFortune - output += "gf" if isGratestFortune - - return output, values - end - - def rollDice(values) - unless values['isGratestFortune'] - return super(values) + def rollDice(command) + unless command.greatest_fortune + return super(command) end dice = @randomizer.roll_once(6) @@ -234,18 +217,6 @@ def rollDice(values) return dice * 2, "#{dice},#{dice}" end - def getGratestFortuneFromString(string) - isGratestFortune = false - - regexp = /gf/i - if regexp.match?(string) - isGratestFortune = true - string = string.gsub(regexp, '') - end - - return isGratestFortune, string - end - def growth(count = 1) ((1..count).map { growth_step }).join " | " end diff --git a/lib/bcdice/game_system/SwordWorld2_5.rb b/lib/bcdice/game_system/SwordWorld2_5.rb index c15d0cb70..4bafa4c07 100644 --- a/lib/bcdice/game_system/SwordWorld2_5.rb +++ b/lib/bcdice/game_system/SwordWorld2_5.rb @@ -32,10 +32,12 @@ class SwordWorld2_5 < SwordWorld2_0  またタイプの軽減化のために末尾に「@クリティカル値」でも処理するようにしました。  例)K20[10]   K10+5[9]   k30[10]   k10[9]+10   k10-5@9 - ・レーティング表の半減 (HKx) + ・レーティング表の半減 (HKx, KxH+N)  レーティング表の先頭または末尾に"H"をつけると、レーティング表を振って最終結果を半減させます。 +  末尾につけた場合、直後に修正ををつけることで、半減後の加減算を行うことができます。 +  この際、複数の項による修正にはカッコで囲うことが必要です(カッコがないとパースに失敗します)  クリティカル値を指定しない場合、クリティカルなしと扱われます。 -  例)HK20  K20h  HK10-5@9  K10-5@9H  K20gfH +  例)HK20  K20h  HK10-5@9  K10-5@9H  K20gfH  K20+8H+2  K20+8H+(1+1) ・ダイス目の修正(運命変転やクリティカルレイ用)  末尾に「$修正値」でダイス目に修正がかかります。 @@ -90,16 +92,8 @@ def eval_game_system_specific_command(command) end end - # コマンド実行前にメッセージを置換する - # @param [String] string 受信したメッセージ - # @return [String] - def replace_text(string) - return string unless RATING_TABLE_RE_FOR_CHANGE_TEXT.match?(string) - - super(string).gsub(/#([-+]?\d+)/) do - modifier = Regexp.last_match(1).to_i - "a[#{Format.modifier(modifier)}]" - end + def rating_parser + return RatingParser.new(version: :v2_5) end def druid_dice(command, power_list) @@ -125,40 +119,6 @@ def druid_dice(command, power_list) return sequence.join(" > ") end - - def getRatingCommandStrings - super + "aA" - end - - def getAdditionalString(string, output) - output, values = super(string, output) - - keptDiceChangeModify, = getKeptDiceChangesFromString(string) - - values['keptDiceChangeModify'] = keptDiceChangeModify - output += "a[#{keptDiceChangeModify}]" if keptDiceChangeModify != 0 - - return output, values - end - - def getAdditionalDiceValue(dice, values) - keptDiceChangeModify = values['keptDiceChangeModify'].to_i - - value = 0 - value += keptDiceChangeModify.to_i if (keptDiceChangeModify != 0) && (dice != 2) - - return value - end - - def getKeptDiceChangesFromString(string) - keptDiceChangeModify = 0 - regexp = /a\[([+\-]\d+)\]/i - if regexp =~ string - keptDiceChangeModify = Regexp.last_match(1) - string = string.gsub(regexp, '') - end - return keptDiceChangeModify, string - end end end end diff --git a/lib/bcdice/game_system/sword_world/rating_lexer.rb b/lib/bcdice/game_system/sword_world/rating_lexer.rb new file mode 100644 index 000000000..d316ca96a --- /dev/null +++ b/lib/bcdice/game_system/sword_world/rating_lexer.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "strscan" + +module BCDice + module GameSystem + class SwordWorld < Base + class RatingLexer + SYMBOLS = { + "+" => :PLUS, + "-" => :MINUS, + "*" => :ASTERISK, + "/" => :SLASH, + "(" => :PARENL, + ")" => :PARENR, + "[" => :BRACKETL, + "]" => :BRACKETR, + "@" => :AT, + "#" => :SHARP, + "$" => :DOLLAR, + }.freeze + + def initialize(source) + # sourceが空文字だとString#splitが空になる + source = source&.split(" ", 2)&.first || "" + @scanner = StringScanner.new(source) + end + + def next_token + return [false, "$"] if @scanner.eos? + + if (number = @scanner.scan(/\d+/)) + [:NUMBER, number.to_i] + else + char = @scanner.getch.upcase + type = SYMBOLS[char] || char.to_sym + [type, char] + end + end + + def source + @scanner.string + end + end + end + end +end diff --git a/lib/bcdice/game_system/sword_world/rating_parsed.rb b/lib/bcdice/game_system/sword_world/rating_parsed.rb new file mode 100644 index 000000000..49740eea5 --- /dev/null +++ b/lib/bcdice/game_system/sword_world/rating_parsed.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +module BCDice + module GameSystem + class SwordWorld < Base + class RatingParsed + # @return [Integer] + attr_accessor :rate + + # @return [Integer, nil] + attr_writer :critical + + # @return [Integer, nil] + attr_writer :kept_modify + + # @return [Integer, nil] + attr_writer :first_to + + # @return [Integer, nil] + attr_writer :first_modify + + # @return [Integer, nil] + attr_writer :rateup + + # @return [Boolean] + attr_accessor :greatest_fortune + + # @return [Integer] + attr_accessor :modifier + + # @return [Integer, nil] + attr_writer :modifier_after_half + + def initialize + @critical = nil + @kept_modify = nil + @first_to = nil + @first_modify = nil + @greatest_fortune = false + @rateup = nil + end + + # @return [Boolean] + def half + return !@modifier_after_half.nil? + end + + # @return [Integer] + def critical + crit = @critical || (half ? 13 : 10) + crit = 3 if crit < 3 + return crit + end + + # @return [Integer] + def first_modify + return @first_modify || 0 + end + + # @return @[Integer] + def first_to + return @first_to || 0 + end + + # @return @[Integer] + def rateup + return @rateup || 0 + end + + # @return @[Integer] + def kept_modify + return @kept_modify || 0 + end + + # @return @[Integer] + def modifier_after_half + return @modifier_after_half || 0 + end + + # @return [String] + def to_s() + output = "KeyNo.#{@rate}" + + output += "c[#{critical}]" if critical < 13 + output += "m[#{Format.modifier(first_modify)}]" if first_modify != 0 + output += "m[#{first_to}]" if first_to != 0 + output += "r[#{rateup}]" if rateup != 0 + output += "gf" if @greatest_fortune + output += "a[#{Format.modifier(kept_modify)}]" if kept_modify != 0 + + if @modifier != 0 + output += Format.modifier(@modifier) + end + return output + end + end + end + end +end diff --git a/lib/bcdice/game_system/sword_world/rating_parser.y b/lib/bcdice/game_system/sword_world/rating_parser.y new file mode 100644 index 000000000..305b1fe88 --- /dev/null +++ b/lib/bcdice/game_system/sword_world/rating_parser.y @@ -0,0 +1,211 @@ +class RatingParser + token NUMBER K R H G F PLUS MINUS ASTERISK SLASH PARENL PARENR BRACKETL BRACKETR AT SHARP DOLLAR + + expect 4 + + rule + expr: rate option + { + rate, option = val + modifier = option[:modifier] || Arithmetic::Node::Number.new(0) + result = parsed(rate, modifier, option) + } + | H rate option + { + _, rate, option = val + option[:modifier_after_half] ||= Arithmetic::Node::Number.new(0) + modifier = option[:modifier] || Arithmetic::Node::Number.new(0) + result = parsed(rate, modifier, option) + } + + + rate: K NUMBER + { result = val[1].to_i } + + option: /* none */ + { + result = {} + } + | option modifier + { + option, term = val + raise ParseError unless option[:modifier].nil? + + option[:modifier] = term + result = option + } + | option BRACKETL unary BRACKETR + { + option, _, term, _ = val + raise ParseError unless option[:critical].nil? + + option[:critical] = term + result = option + } + | option AT unary + { + option, _, term = val + raise ParseError unless option[:critical].nil? + + option[:critical] = term + result = option + } + | option DOLLAR NUMBER + { + option, _, term = val + raise ParseError unless option[:first_to].nil? && option[:first_modify].nil? + + option[:first_to] = term.to_i + result = option + } + | option DOLLAR PLUS NUMBER + { + option, _, _, term = val + raise ParseError unless option[:first_to].nil? && option[:first_modify].nil? + + option[:first_modify] = term.to_i + result = option + } + | option DOLLAR MINUS NUMBER + { + option, _, _, term = val + raise ParseError unless option[:first_to].nil? && option[:first_modify].nil? + + option[:first_modify] = -(term.to_i) + result = option + } + | option H + { + option, _ = val + raise ParseError unless option[:modifier_after_half].nil? + + option[:modifier_after_half] = Arithmetic::Node::Number.new(0) + result = option + } + | option H unary + { + option, _, term = val + raise ParseError unless option[:modifier_after_half].nil? + + option[:modifier_after_half] = term + result = option + } + | option R unary + { + option, _, term = val + raise ParseError unless [:v2_5, :v2_0].include?(@version) && option[:rateup].nil? + + option[:rateup] = term + result = option + } + | option G F + { + option, _, _ = val + raise ParseError unless [:v2_5, :v2_0].include?(@version) && option[:greatest_fortune].nil? + + option[:greatest_fortune] = true + result = option + } + | option SHARP unary + { + option, _, term = val + raise ParseError unless @version == :v2_5 && option[:kept_modify].nil? + + option[:kept_modify] = term + result = option + } + + modifier: PLUS mul + { result = val[1] } + | MINUS mul + { result = Arithmetic::Node::Negative.new(val[1]) } + | modifier PLUS mul + { result = Arithmetic::Node::BinaryOp.new(val[0], :+, val[2]) } + | modifier MINUS mul + { result = Arithmetic::Node::BinaryOp.new(val[0], :-, val[2]) } + + add: add PLUS mul + { result = Arithmetic::Node::BinaryOp.new(val[0], :+, val[2]) } + | add MINUS mul + { result = Arithmetic::Node::BinaryOp.new(val[0], :-, val[2]) } + | mul + + mul: mul ASTERISK unary + { result = Arithmetic::Node::BinaryOp.new(val[0], :*, val[2]) } + | mul SLASH unary + { + result = Arithmetic::Node::DivideWithGameSystemDefault.new(val[0], val[2]) + } + | unary + + unary: PLUS unary + { result = val[1] } + | MINUS unary + { result = Arithmetic::Node::Negative.new(val[1]) } + | term + + term: PARENL add PARENR + { result = val[1] } + | NUMBER + { result = Arithmetic::Node::Number.new(val[0]) } +end + +---- header + +require "bcdice/arithmetic/node" +require "bcdice/enum" +require "bcdice/game_system/sword_world/rating_lexer" +require "bcdice/game_system/sword_world/rating_parsed" + +# SwordWorldの威力表コマンドをパースするクラス +module BCDice + module GameSystem + class SwordWorld < Base + +---- inner + +# デフォルトの丸めを切り上げとしているが、SwordWorldには切り捨てもあるので決め切れない(四捨五入は現状ない) +def initialize(version: :v1_0, round_type: RoundType::CEIL) + super() + @version = version + @round_type = round_type +end + +def set_debug + @yydebug = true + return self +end + +# @param source [String] +# @return [BCDice::GameSystem::SwordWorld::RatingParsed, nil] +def parse(source) + @lexer = RatingLexer.new(source) + do_parse() +rescue ParseError, ZeroDivisionError + nil +end + +private + +def parsed(rate, modifier, option) + RatingParsed.new.tap do |p| + p.rate = rate + p.critical = option[:critical]&.eval(@round_type) + p.kept_modify = option[:kept_modify]&.eval(@round_type) + p.first_to = option[:first_to] + p.first_modify = option[:first_modify] + p.rateup = option[:rateup]&.eval(@round_type) + p.greatest_fortune = option.fetch(:greatest_fortune, false) + p.modifier = modifier.eval(@round_type) + p.modifier_after_half = option[:modifier_after_half]&.eval(@round_type) + end +end + +def next_token + @lexer.next_token +end + +---- footer + end + end +end diff --git a/test/data/SwordWorld.toml b/test/data/SwordWorld.toml index 3f4e143c2..00d5e1a19 100644 --- a/test/data/SwordWorld.toml +++ b/test/data/SwordWorld.toml @@ -4973,6 +4973,15 @@ rands = [ { sides = 6, value = 1 }, ] +[[ test ]] +game_system = "SwordWorld" +input = "k10+10@(8+5) カッコをつけるとクリティカル値が加算される" +output = "KeyNo.10+10 > 2D:[6,5]=11 > 6+10 > 16" +rands = [ + { sides = 6, value = 6 }, + { sides = 6, value = 5 }, +] + [[ test ]] game_system = "SwordWorld" input = "hk10 HKのクリティカル値は13" @@ -4991,6 +5000,33 @@ rands = [ { sides = 6, value = 6 }, ] +[[ test ]] +game_system = "SwordWorld" +input = "k10h+1 半減後加算" +output = "KeyNo.10 > 2D:[6,6]=12 > 7/2+1 > 5" +rands = [ + { sides = 6, value = 6 }, + { sides = 6, value = 6 }, +] + +[[ test ]] +game_system = "SwordWorld" +input = "k10h+1+1 半減後加算に使えるのは1項のみ。それ以外は加減算値となる" +output = "KeyNo.10+1 > 2D:[6,6]=12 > (7+1)/2+1 > 5" +rands = [ + { sides = 6, value = 6 }, + { sides = 6, value = 6 }, +] + +[[ test ]] +game_system = "SwordWorld" +input = "k10h+(1+1) カッコをつければ半減後加算に繰り込まれる" +output = "KeyNo.10 > 2D:[6,6]=12 > 7/2+2 > 6" +rands = [ + { sides = 6, value = 6 }, + { sides = 6, value = 6 }, +] + [[ test ]] game_system = "SwordWorld" input = "hk10h" diff --git a/test/test_sword_world_rating_command_parser.rb b/test/test_sword_world_rating_command_parser.rb new file mode 100644 index 000000000..c31d2cf9d --- /dev/null +++ b/test/test_sword_world_rating_command_parser.rb @@ -0,0 +1,185 @@ +# frozen_string_literal: true + +require "test/unit" +require "bcdice/base" +require "bcdice/game_system/sword_world/rating_parser" + +class TestSwordWorldRatingCommandParser < Test::Unit::TestCase + def test_parse_v1_full_first_modify + parser = BCDice::GameSystem::SwordWorld::RatingParser.new().set_debug() + parsed = parser.parse("K20+5+3@9$+1H+2") + + assert_not_nil(parsed) + assert_equal(20, parsed.rate) + assert_equal(8, parsed.modifier) + assert_equal(9, parsed.critical) + assert_equal(0, parsed.kept_modify) + assert_equal(0, parsed.first_to) + assert_equal(1, parsed.first_modify) + assert_false(parsed.greatest_fortune) + assert_equal(0, parsed.rateup) + assert_true(parsed.half) + assert_equal(2, parsed.modifier_after_half) + end + + def test_parse_v1_full_first_to + parser = BCDice::GameSystem::SwordWorld::RatingParser.new().set_debug() + parsed = parser.parse("K20+5+3@9$8H+2") + + assert_not_nil(parsed) + assert_equal(20, parsed.rate) + assert_equal(8, parsed.modifier) + assert_equal(9, parsed.critical) + assert_equal(0, parsed.kept_modify) + assert_equal(8, parsed.first_to) + assert_equal(0, parsed.first_modify) + assert_false(parsed.greatest_fortune) + assert_equal(0, parsed.rateup) + assert_true(parsed.half) + assert_equal(2, parsed.modifier_after_half) + end + + def test_parse_v1_head_half + parser = BCDice::GameSystem::SwordWorld::RatingParser.new().set_debug() + parsed = parser.parse("HK30+5+3") + + assert_not_nil(parsed) + assert_equal(30, parsed.rate) + assert_equal(8, parsed.modifier) + assert_equal(13, parsed.critical) + assert_equal(0, parsed.kept_modify) + assert_equal(0, parsed.first_to) + assert_equal(0, parsed.first_modify) + assert_false(parsed.greatest_fortune) + assert_equal(0, parsed.rateup) + assert_true(parsed.half) + assert_equal(0, parsed.modifier_after_half) + end + + def test_parse_v1_brace_critical + parser = BCDice::GameSystem::SwordWorld::RatingParser.new().set_debug() + parsed = parser.parse("K50[8]+5+3") + + assert_not_nil(parsed) + assert_equal(50, parsed.rate) + assert_equal(8, parsed.modifier) + assert_equal(8, parsed.critical) + assert_equal(0, parsed.kept_modify) + assert_equal(0, parsed.first_to) + assert_equal(0, parsed.first_modify) + assert_false(parsed.greatest_fortune) + assert_equal(0, parsed.rateup) + assert_false(parsed.half) + assert_equal(0, parsed.modifier_after_half) + end + + def test_parse_v1_brace_critical_only + parser = BCDice::GameSystem::SwordWorld::RatingParser.new().set_debug() + parsed = parser.parse("K50[8]") + + assert_not_nil(parsed) + assert_equal(50, parsed.rate) + assert_equal(0, parsed.modifier) + assert_equal(8, parsed.critical) + assert_equal(0, parsed.kept_modify) + assert_equal(0, parsed.first_to) + assert_equal(0, parsed.first_modify) + assert_false(parsed.greatest_fortune) + assert_equal(0, parsed.rateup) + assert_false(parsed.half) + assert_equal(0, parsed.modifier_after_half) + end + + def test_parse_v1_brace_critical_duplicate + parser = BCDice::GameSystem::SwordWorld::RatingParser.new().set_debug() + parsed = parser.parse("K50[8]+5+3@9") + + assert_nil(parsed) + end + + def test_parse_v1_multiple_modifier + parser = BCDice::GameSystem::SwordWorld::RatingParser.new().set_debug() + parsed = parser.parse("K20+5H+3+2") + + assert_nil(parsed) + end + + def test_parse_v1_gf_unsupported + parser = BCDice::GameSystem::SwordWorld::RatingParser.new().set_debug() + parsed = parser.parse("K50[8]+5+3gf") + + assert_nil(parsed) + end + + def test_parse_v1_kept_modify_unsupported + parser = BCDice::GameSystem::SwordWorld::RatingParser.new().set_debug() + parsed = parser.parse("K50[8]+5+3#+1") + + assert_nil(parsed) + end + + def test_parse_v1_rateup_unsupported + parser = BCDice::GameSystem::SwordWorld::RatingParser.new().set_debug() + parsed = parser.parse("K50[8]+5+3r10") + + assert_nil(parsed) + end + + def test_parse_v1_arithmetic + parser = BCDice::GameSystem::SwordWorld::RatingParser.new().set_debug() + parsed = parser.parse("K50+5*4/2-1+3@10") + + assert_not_nil(parsed) + assert_equal(50, parsed.rate) + assert_equal(12, parsed.modifier) + assert_equal(10, parsed.critical) + assert_equal(0, parsed.kept_modify) + assert_equal(0, parsed.first_to) + assert_equal(0, parsed.first_modify) + assert_false(parsed.greatest_fortune) + assert_equal(0, parsed.rateup) + assert_false(parsed.half) + assert_equal(0, parsed.modifier_after_half) + end + + def test_parse_v20_full + parser = BCDice::GameSystem::SwordWorld::RatingParser.new(version: :v2_0).set_debug() + parsed = parser.parse("K20+5+3@9$+1gfr5H+2") + + assert_not_nil(parsed) + assert_equal(20, parsed.rate) + assert_equal(8, parsed.modifier) + assert_equal(9, parsed.critical) + assert_equal(0, parsed.kept_modify) + assert_equal(0, parsed.first_to) + assert_equal(1, parsed.first_modify) + assert_true(parsed.greatest_fortune) + assert_equal(5, parsed.rateup) + assert_true(parsed.half) + assert_equal(2, parsed.modifier_after_half) + end + + def test_parse_v20_kept_modify_unsupported + parser = BCDice::GameSystem::SwordWorld::RatingParser.new(version: :v2_0).set_debug() + parsed = parser.parse("K20+5+3#1") + + assert_nil(parsed) + end + + def test_parse_v25_full + parser = BCDice::GameSystem::SwordWorld::RatingParser.new(version: :v2_5).set_debug() + parsed = parser.parse("K20+5+3@9#+2$+1gfr5H+2") + + assert_not_nil(parsed) + assert_equal(20, parsed.rate) + assert_equal(8, parsed.modifier) + assert_equal(9, parsed.critical) + assert_equal(2, parsed.kept_modify) + assert_equal(0, parsed.first_to) + assert_equal(1, parsed.first_modify) + assert_true(parsed.greatest_fortune) + assert_equal(5, parsed.rateup) + assert_true(parsed.half) + assert_equal(2, parsed.modifier_after_half) + end +end