-
Notifications
You must be signed in to change notification settings - Fork 210
Description
ソード・ワールド2.0、2.5のレーティング表部分の解析結果(rating_parsed.rb)の冗長な部分を簡潔にする。
現状
rating_parsed.rbでは、各インスタンス変数を nil で初期化しており、またそれらを正規化するメソッド(nil ならば 0 等の既定値に変換したり、範囲内に収めたりするメソッド)を用意している。
BCDice/lib/bcdice/game_system/sword_world/rating_parsed.rb
Lines 34 to 41 in c833464
| def initialize | |
| @critical = nil | |
| @kept_modify = nil | |
| @first_to = nil | |
| @first_modify = nil | |
| @greatest_fortune = false | |
| @rateup = nil | |
| end |
正規化メソッドの例:
BCDice/lib/bcdice/game_system/sword_world/rating_parsed.rb
Lines 55 to 58 in c833464
| # @return [Integer] | |
| def first_modify | |
| return @first_modify || 0 | |
| end |
以下に示すように、これらはうまく使われていない。
rating_parsed.rbの #to_s には、必要なパーツを文字列に追加する処理が書かれているが、必要性の判断は nil との比較ではなく、0 等の既定値との比較で行われている。また、条件判定と式展開で正規化メソッドが2回呼び出されているため、インスタンス変数と既定値の比較が最大で3回行われている。
BCDice/lib/bcdice/game_system/sword_world/rating_parsed.rb
Lines 81 to 95 in c833464
| 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 |
レーティング表の処理はSwordWorld.rbに書かれているが、ここでも必要性の判断は nil との比較ではなく、0 等の既定値との比較で行われている。
例1:
BCDice/lib/bcdice/game_system/SwordWorld.rb
Lines 82 to 95 in c833464
| first_to = command.first_to | |
| first_modify = command.first_modify | |
| loop do | |
| dice_raw, diceText = rollDice(command) | |
| dice = dice_raw | |
| if first_to != 0 | |
| dice = dice_raw = first_to | |
| first_to = 0 | |
| elsif first_modify != 0 | |
| dice += first_modify | |
| first_modify = 0 | |
| end |
例2:
BCDice/lib/bcdice/game_system/SwordWorld.rb
Lines 325 to 341 in c833464
| # rate回数が1回で、修正値がない時には途中式と最終結果が一致するので、途中式を省略する | |
| 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) | |
| end |
以上の例のように、各インスタンス変数の nil は使われておらず、0 等の既定値を直接設定すればよくなっている。
おそらく、構文解析器の #parsed にある、値をまとめて設定する処理との整合性をとるために、このような形になったのではないだろうか。
BCDice/lib/bcdice/game_system/sword_world/rating_parser.y
Lines 215 to 229 in ca56617
| 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.semi_fixed_val = option[:semi_fixed_val] | |
| p.tmp_fixed_val = option[:tmp_fixed_val] | |
| p.modifier = modifier.eval(@round_type) | |
| p.modifier_after_half = option[:modifier_after_half]&.eval(@round_type) | |
| end | |
| end |
変更案
RatingParsedの各インスタンス変数を原則として 0 等の既定値で初期化するようにする。そうすれば、正規化メソッドは不要になり、直接インスタンス変数を参照すればよくなる。nil での初期化のままにし、SwordWorld.rb側も含めて処理を書き換えることも一案だが、範囲内に収める等の処理もあるため、数値で表現しておく方が問題が生じにくいと思われる。
class RatingParsed
def initialize
@critical = nil
@kept_modify = 0
@first_to = 0
@first_modify = 0
@greatest_fortune = false
@rateup = 0
end
# @return [Boolean]
def half
@modifier_after_half != 0
end
# @return [String]
def to_s()
sequence = ["KeyNo.#{@rate}"]
sequence << "c[#{@critical}]" if @critical < 13
sequence << "m[#{Format.modifier(@first_modify)}]" if @first_modify != 0
sequence << "m[#{@first_to}]" if @first_to != 0
sequence << "r[#{@rateup}]" if @rateup != 0
sequence << "gf" if @greatest_fortune
sequence << "a[#{Format.modifier(@kept_modify)}]" if @kept_modify != 0
sequence << Format.modifier(@modifier) if @modifier != 0
return sequence.join
end
endRatingParsedに値を範囲内に収める正規化処理のメソッドを用意する。これは構文解析後に1回行えばよい。
# rating_parsed.rb
class RatingParsed
def normalize!
# クリティカル値を3以上に設定する
@critical = (@critical || (half ? 13 : 10)).clamp(3..)
# ...
self
end
end
# SwordWorld.rb
class SwordWorld < Base
def rating(string) # レーティング表
command = rating_parser.parse(string).normalize!
# ...
end
end構文解析器では、#parsed において、オプションが設定されたかを if で判定するように変更する必要があるが、構文解析中の判定にも重複箇所が多くあるため、専用のBuilderを作るのがよいかもしれない。#515 の変更にも合わせる。