class BCDice::DiceTable::RangeTable
各項目について、Rangeを用いて出目の合計の範囲を指定する、表のクラス。
このクラスを使うと、表の定義を短く書ける。 このクラスを使って表を定義するときは、各項目を以下の形で書く。
[出目の合計の範囲, 内容]
「出目の合計の範囲」には、Integerを要素とするRangeか、Integerを置ける。
roll メソッドで表を振ると、出目の合計値と対応する項目が選ばれる。
@example 表の定義(バトルテックの致命的命中表)
CRITICAL_TABLE = RangeTable.new( '致命的命中表', '2D6', [ [2..7, '致命的命中はなかった'], [8..9, '1箇所の致命的命中'], [10..11, '2箇所の致命的命中'], [12, 'その部位が吹き飛ぶ(腕、脚、頭)または3箇所の致命的命中(胴)'] ] )
@example 表を振った結果
CRITICAL_TABLE.roll(bcdice).formatted # 出目の合計が7の場合 :"致命的命中表(7) > 致命的命中はなかった" # 出目の合計が8の場合 :"致命的命中表(8) > 1箇所の致命的命中" # 出目の合計が9の場合 :"致命的命中表(9) > 1箇所の致命的命中" # 出目の合計が10の場合:"致命的命中表(10) > 2箇所の致命的命中"
Constants
- DEFAULT_FORMATTER
表を振った結果の整形処理(既定の処理)
- DICE_ROLL_METHOD_RE
項目を選ぶときのダイスロールの方法を表す正規表現
- Item
表の項目を表す構造体 @!attribute [rw] range
@return [Range] 出目の合計の範囲
@!attribute [rw] content
@return [Object] 内容
- RollResult
表を振った結果を表す構造体 @!attribute [rw] sum
@return [Integer] 出目の合計
@!attribute [rw] values
@return [Array<Integer>] 出目の配列
@!attribute [rw] content
@return [Object] 選ばれた項目の内容
@!attribute [rw] formatted
@return [String] 整形された結果
Attributes
@return [String] 表の名前
@return [Integer] 振るダイスの個数
@return [Integer] 振るダイスの面数
Public Class Methods
表を初期化する
ブロックを与えると、独自の結果整形処理を指定できる。 ブロックは振った表(table
)と振った結果(result
)を引数として受け取る。
@param [String] name 表の名前 @param [String] dice_roll_method
項目を選ぶときのダイスロールの方法(+'1D6'+ など)
@param [Array<(Range, Object)>, Array<(Integer, Object)>] items
表の項目の配列。[出目の合計の範囲, 内容]
@yieldparam [RangeTable] table 振った表 @yieldparam [RollResult] result 表を振った結果 @raise [ArgumentError] ダイスロール方法が正しい書式で指定されていなかった場合 @raise [TypeError] 範囲の型が正しくなかった場合 @raise [RangeError] 出目の合計の最小値がカバーされていなかった場合 @raise [RangeError] 出目の合計の最大値がカバーされていなかった場合 @raise [RangeError] 出目の合計の範囲にずれや重なりがあった場合
@example 表の定義(バトルテックの致命的命中表)
CRITICAL_TABLE = RangeTable.new( '致命的命中表', '2D6', [ [2..7, '致命的命中はなかった'], [8..9, '1箇所の致命的命中'], [10..11, '2箇所の致命的命中'], [12, 'その部位が吹き飛ぶ(腕、脚、頭)または3箇所の致命的命中(胴)'] ] )
@example 独自の結果整形処理を指定する場合
CRITICAL_TABLE_WITH_FORMATTER = RangeTable.new( '致命的命中表', '2D6', [ [2..7, '致命的命中はなかった'], [8..9, '1箇所の致命的命中'], [10..11, '2箇所の致命的命中'], [12, 'その部位が吹き飛ぶ(腕、脚、頭)または3箇所の致命的命中(胴)'] ] ) do |table, result| "致命的命中発生? > #{result.sum}[#{result.values}] > #{result.content}" end CRITICAL_TABLE_WITH_FORMATTER.roll(bcdice).formatted #=> "致命的命中発生? > 11[5,6] > 2箇所の致命的命中"
# File lib/bcdice/dice_table/range_table.rb, line 116 def initialize(name, dice_roll_method, items, &formatter) @name = name.freeze @formatter = formatter || DEFAULT_FORMATTER m = DICE_ROLL_METHOD_RE.match(dice_roll_method) unless m raise( ArgumentError, "#{@name}: invalid dice roll method: #{dice_roll_method}" ) end @num_of_dice = m[1].to_i @num_of_sides = m[2].to_i store(items) end
Public Instance Methods
指定された値に対応する項目を返す @param [Integer] value 値(出目の合計) @return [Item] 指定された値に対応する項目 @raise [RangeError] 範囲外の値が指定された場合
# File lib/bcdice/dice_table/range_table.rb, line 138 def fetch(value) item = @items.find { |i| i.range.include?(value) } unless item raise RangeError, "#{@name}: value is out of range: #{value}" end return item end
表を振る @param randomizer [#roll_barabara] ランダマイザ @return [RollResult] 表を振った結果
# File lib/bcdice/dice_table/range_table.rb, line 150 def roll(randomizer) values = randomizer.roll_barabara(@num_of_dice, @num_of_sides) sum = values.sum() result = RollResult.new(sum, values, fetch(sum).content) result.formatted = @formatter[self, result] return result end
Private Instance Methods
出目の合計の最大値がカバーされていることを確認する @param [Array<(Range, Object)>] sorted_items
ソートされた、項目の配列
@return [self] @raise [RangeError] 出目の合計の最大値がカバーされていなかった場合
# File lib/bcdice/dice_table/range_table.rb, line 228 def assert_max_sum_is_covered(sorted_items) max_sum = @num_of_dice * @num_of_sides range = sorted_items.last[0] unless range.include?(max_sum) raise( RangeError, "#{@name}: max value (#{max_sum}) is not covered: #{range}" ) end self end
出目の合計の最小値がカバーされていることを確認する @param [Array<(Range, Object)>] sorted_items
ソートされた、項目の配列
@return [self] @raise [RangeError] 出目の合計の最小値がカバーされていなかった場合
# File lib/bcdice/dice_table/range_table.rb, line 210 def assert_min_sum_is_covered(sorted_items) min_sum = @num_of_dice range = sorted_items.first[0] unless range.include?(min_sum) raise( RangeError, "#{@name}: min value (#{min_sum}) is not covered: #{range}" ) end self end
出目の合計の範囲にずれや重なりがないことを確認する @param [Array<(Range, Object)>] sorted_items
ソートされた、項目の配列
@return [self] @raise [RangeError] 出目の合計の範囲にずれや重なりがあった場合
# File lib/bcdice/dice_table/range_table.rb, line 246 def assert_no_gap_or_overlap_in_ranges(sorted_items) sorted_items.each_cons(2) do |i1, i2| r1 = i1[0] r2 = i2[0] max1 = r1.max next_of_max1 = max1 + 1 if r2.include?(max1) raise RangeError, "#{@name}: Range overlap: #{r1} and #{r2}" end unless r2.include?(next_of_max1) raise RangeError, "#{@name}: Range gap: #{r1} and #{r2}" end end self end
引数を強制的に整数を要素とするRangeに変換する @param [Range, Integer] x 変換対象 @return [Range] 整数を要素とするRange @raise [TypeError] xの型に対応していなかった場合
# File lib/bcdice/dice_table/range_table.rb, line 189 def coerce_to_int_range(x) case x when Integer return Range.new(x, x) when Range if x.begin.is_a?(Integer) && x.end.is_a?(Integer) return x end end raise( TypeError, "#{@name}: #{x} (#{x.class}) must be an Integer or a Range with Integers " ) end
表の項目を格納する @param [Array<(Range, Object)>, Array<(Integer, Object)>] items
表の項目の配列。[出目の合計の範囲, 内容]
@return [self] @raise [TypeError] 範囲の型が正しくなかった場合 @raise [RangeError] 出目の合計の最小値がカバーされていなかった場合 @raise [RangeError] 出目の合計の最大値がカバーされていなかった場合 @raise [RangeError] 出目の合計の範囲にずれや重なりがあった場合
# File lib/bcdice/dice_table/range_table.rb, line 170 def store(items) items_with_range = items.map { |r, c| [coerce_to_int_range(r), c] } sorted_items = items_with_range.sort_by { |r, _| r.min } assert_min_sum_is_covered(sorted_items) assert_max_sum_is_covered(sorted_items) assert_no_gap_or_overlap_in_ranges(sorted_items) @items = sorted_items .map { |range, content| Item.new(range, content.freeze).freeze } .freeze self end