From eb8d6897504ed17d9211b5cccef27cef333a9c3f Mon Sep 17 00:00:00 2001 From: ocha Date: Thu, 30 Apr 2020 20:17:07 +0900 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E7=AE=97=E3=83=AD=E3=83=BC=E3=83=AB?= =?UTF-8?q?=EF=BC=9A=E3=83=95=E3=82=A3=E3=83=AB=E3=82=BF=E5=87=A6=E7=90=86?= =?UTF-8?q?=E4=BB=98=E3=81=8D=E3=83=80=E3=82=A4=E3=82=B9=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=AB=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #88 大きな/小さな出目から、複数個取る/除く機能。 D&D 5thなどで使う。 --- src/dice/add_dice/node.rb | 87 ++++++++++++++++++++++++++++++++ src/dice/add_dice/parser.rb | 45 +++++++++++++---- src/dice/add_dice/randomizer.rb | 4 +- src/test/add_dice_parser_test.rb | 64 +++++++++++++++++++++++ src/test/data/None.txt | 48 ++++++++++++++++++ 5 files changed, 236 insertions(+), 12 deletions(-) diff --git a/src/dice/add_dice/node.rb b/src/dice/add_dice/node.rb index 99178e12c..8bef68900 100644 --- a/src/dice/add_dice/node.rb +++ b/src/dice/add_dice/node.rb @@ -329,6 +329,93 @@ def s_exp end end + # フィルタ処理付きダイスロールのノード。 + # + # ダイスロール後、条件に従って出目を選択し、和を求める。 + class DiceRollWithFilter + # フィルタの構造体 + # @!attribute abbr + # @return [Symbol] フィルタの略称 + # @!attribute apply + # @return [Proc] フィルタ処理の内容 + Filter = Struct.new(:abbr, :apply) + + # 大きな出目から複数個取る + KEEP_HIGHEST = Filter.new( + :KH, + lambda { |values, n| values.sort.reverse.take(n) } + ).freeze + + # 小さな出目から複数個取る + KEEP_LOWEST = Filter.new( + :KL, + lambda { |values, n| values.sort.take(n) } + ).freeze + + # 大きな出目から複数個除く + DROP_HIGHEST = Filter.new( + :DH, + lambda { |values, n| values.sort.reverse.drop(n) } + ).freeze + + # 小さな出目から複数個除く + DROP_LOWEST = Filter.new( + :DL, + lambda { |values, n| values.sort.drop(n) } + ).freeze + + # ノードを初期化する + # @param [Number] times ダイスを振る回数のノード + # @param [Number] sides ダイスの面数のノード + # @param [Number] n_filtering ダイスを残す/減らす個数のノード + # @param [Filter] filter フィルタ + def initialize(times, sides, n_filtering, filter) + @times = times.literal + @sides = sides.literal + @n_filtering = n_filtering.literal + @filter = filter + + # ダイスを振った結果の出力 + @text = nil + end + + # ノードを評価する(ダイスを振り、出目を選択して和を求める) + # + # 評価結果は出目の合計値になる。 + # 出目はランダマイザに記録される。 + # + # @param [Randomizer] randomizer ランダマイザ + # @return [Integer] 評価結果(出目の合計値) + def eval(randomizer) + values = randomizer.roll_once(@times, @sides) + total = @filter. + apply[values, @n_filtering]. + reduce(0, &:+) + + @text = "#{total}[#{values.join(',')}]" + + return total + end + + # 文字列に変換する + # @return [String] + def to_s + "#{@times}D#{@sides}#{@filter.abbr}#{@n_filtering}" + end + + # メッセージへの出力を返す + # @return [String] + def output + @text + end + + # ノードのS式を返す + # @return [String] + def s_exp + "(DiceRollWithFilter #{@times} #{@sides} #{@filter.abbr.inspect} #{@n_filtering})" + end + end + # 数値のノード class Number # 値 diff --git a/src/dice/add_dice/parser.rb b/src/dice/add_dice/parser.rb index 4f8218efc..9f1279fe6 100644 --- a/src/dice/add_dice/parser.rb +++ b/src/dice/add_dice/parser.rb @@ -50,7 +50,7 @@ def error? # 構文解析対象の文字列をトークンの配列に変換する # @return [Array] def tokenize(expr) - expr.gsub(%r{[\+\-\*/DURS@]}) { |e| " #{e} " }.split(' ') + expr.gsub(%r{[\+\-\*/DURSKHL@]}) { |e| " #{e} " }.split(' ') end # 式 @@ -148,16 +148,40 @@ def unary # 項:ダイスロール、数値 def term - ret = expect_number() + num = expect_number() if consume("D") - times = ret sides = expect_number() - critical = consume("@") ? expect_number() : nil - ret = AddDice::Node::DiceRoll.new(times, sides, critical) + filter = dice_roll_filter() + if filter + # ダイスロール後のフィルタリングあり + n_filtering = expect_number() + return Node::DiceRollWithFilter.new(num, sides, n_filtering, filter) + end + + # 通常のダイスロール + critical = consume("@") ? expect_number() : nil + return Node::DiceRoll.new(num, sides, critical) end - ret + return num + end + + # ダイスロール:フィルタ処理 + def dice_roll_filter + if consume('K', 'H') + # 大きな出目から複数個取る + Node::DiceRollWithFilter::KEEP_HIGHEST + elsif consume('K', 'L') + # 小さな出目から複数個取る + Node::DiceRollWithFilter::KEEP_LOWEST + elsif consume('D', 'H') + # 大きな出目から複数個除く + Node::DiceRollWithFilter::DROP_HIGHEST + elsif consume('D', 'L') + # 小さな出目から複数個除く + Node::DiceRollWithFilter::DROP_LOWEST + end end # トークンを消費する @@ -165,15 +189,16 @@ def term # トークンと期待した文字列が合致していた場合、次のトークンに進む。 # 合致していなかった場合は、進まない。 # - # @param [String] str 期待する文字列 + # @param [String] expected 期待する文字列 # @return [true] トークンと期待した文字列が合致していた場合 # @return [false] トークンと期待した文字列が合致していなかった場合 - def consume(str) - if @tokens[@idx] != str + def consume(*expected) + target = @tokens.slice(@idx, expected.length) + unless target == expected return false end - @idx += 1 + @idx += expected.length return true end diff --git a/src/dice/add_dice/randomizer.rb b/src/dice/add_dice/randomizer.rb index 28a186e20..26f27b729 100644 --- a/src/dice/add_dice/randomizer.rb +++ b/src/dice/add_dice/randomizer.rb @@ -51,8 +51,6 @@ def roll(times, sides, critical) [total_sum, text] end - private - # ダイスを振る(振り足しなしの1回分) # @param [Integer] times ダイス数 # @param [Integer] sides 面数 @@ -70,6 +68,8 @@ def roll_once(times, sides) return dice_str.split(',').map(&:to_i) end + private + def double_check? if @dicebot.sameDiceRerollCount != 0 # 振り足しありのゲームでダイスが二個以上 if @dicebot.sameDiceRerollType <= 0 # 判定のみ振り足し diff --git a/src/test/add_dice_parser_test.rb b/src/test/add_dice_parser_test.rb index bfcf61a0b..6eb810c9b 100644 --- a/src/test/add_dice_parser_test.rb +++ b/src/test/add_dice_parser_test.rb @@ -104,6 +104,70 @@ def test_parse_target_value_constant_fonding ) end + # 大きな出目から複数個取る + def test_parse_keep_high + test_parse( + '5D10KH3', + '(Command (DiceRollWithFilter 5 10 :KH 3))' + ) + end + + # 小さな出目から複数個取る + def test_parse_keep_low + test_parse( + '5D10KL3', + '(Command (DiceRollWithFilter 5 10 :KL 3))' + ) + end + + # 大きな出目から複数個除く + def test_parse_drop_high + test_parse( + '5D10DH3', + '(Command (DiceRollWithFilter 5 10 :DH 3))' + ) + end + + # 小さな出目から複数個除く + def test_parse_drop_low + test_parse( + '5D10DL3', + '(Command (DiceRollWithFilter 5 10 :DL 3))' + ) + end + + # 大きな値キープ機能、修正値付き + def test_parse_keep_high_with_modifier + test_parse( + '5D10KH3+1', + '(Command (+ (DiceRollWithFilter 5 10 :KH 3) 1))' + ) + end + + # 小さな値キープ機能、修正値付き + def test_parse_keep_low_with_modifier + test_parse( + '5D10KL3+1', + '(Command (+ (DiceRollWithFilter 5 10 :KL 3) 1))' + ) + end + + # 大きな値ドロップ機能、修正値付き + def test_parse_drop_high_with_modifier + test_parse( + '5D10DH3+1', + '(Command (+ (DiceRollWithFilter 5 10 :DH 3) 1))' + ) + end + + # 小さな値ドロップ機能、修正値付き + def test_parse_drop_low_with_modifier + test_parse( + '5D10DL3+1', + '(Command (+ (DiceRollWithFilter 5 10 :DL 3) 1))' + ) + end + private # 構文解析をテストする diff --git a/src/test/data/None.txt b/src/test/data/None.txt index 60bef06c7..b98e2be81 100644 --- a/src/test/data/None.txt +++ b/src/test/data/None.txt @@ -1088,3 +1088,51 @@ S2R6[3] output: DiceBot : (2R6[3]) > 3,2 + 1 > 成功数0###secret dice### rand:3/6,2/6,1/6 +============================ +input: +5D10KH3 大きな出目から3個取った後の和 +output: +DiceBot : (5D10KH3) > 21[8,3,2,5,8] > 21 +rand:8/10,3/10,2/10,5/10,8/10 +============================ +input: +5D10KL3 小さな出目から3個取った後の和 +output: +DiceBot : (5D10KL3) > 10[8,3,2,5,8] > 10 +rand:8/10,3/10,2/10,5/10,8/10 +============================ +input: +5D10DH3 大きな出目から3個除いた後の和 +output: +DiceBot : (5D10DH3) > 5[8,3,2,5,8] > 5 +rand:8/10,3/10,2/10,5/10,8/10 +============================ +input: +5D10DL3 小さな出目から3個除いた後の和 +output: +DiceBot : (5D10DL3) > 16[8,3,2,5,8] > 16 +rand:8/10,3/10,2/10,5/10,8/10 +============================ +input: +5D10KH0 取る数が0の場合でも動く +output: +DiceBot : (5D10KH0) > 0[8,3,2,5,8] > 0 +rand:8/10,3/10,2/10,5/10,8/10 +============================ +input: +5D10KH7 取る数が多すぎても動く +output: +DiceBot : (5D10KH7) > 26[8,3,2,5,8] > 26 +rand:8/10,3/10,2/10,5/10,8/10 +============================ +input: +2D20KH1+1 キープ機能修正値付き +output: +DiceBot : (2D20KH1+1) > 20[1,20]+1 > 21 +rand:1/20,20/20 +============================ +input: +2D20DH1+1 ドロップ機能修正値付き +output: +DiceBot : (2D20DH1+1) > 1[1,20]+1 > 2 +rand:1/20,20/20