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

加算ロール:フィルタ処理付きダイスロールを追加する #181

Merged
merged 4 commits into from
May 8, 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
90 changes: 90 additions & 0 deletions src/dice/add_dice/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,96 @@ 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 { |sorted_values, n| sorted_values.reverse.take(n) }
).freeze

# 小さな出目から複数個取る
KEEP_LOWEST = Filter.new(
:KL,
lambda { |sorted_values, n| sorted_values.take(n) }
).freeze

# 大きな出目から複数個除く
DROP_HIGHEST = Filter.new(
:DH,
lambda { |sorted_values, n| sorted_values.reverse.drop(n) }
).freeze

# 小さな出目から複数個除く
DROP_LOWEST = Filter.new(
:DL,
lambda { |sorted_values, n| sorted_values.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)
sorted_values = randomizer.roll_once(@times, @sides).sort
total = @filter.
apply[sorted_values, @n_filtering].
reduce(0, &:+)

@text = "#{total}[#{sorted_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
# 値
Expand Down
47 changes: 37 additions & 10 deletions src/dice/add_dice/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def error?
# 構文解析対象の文字列をトークンの配列に変換する
# @return [Array<String>]
def tokenize(expr)
expr.gsub(%r{[\+\-\*/DURS@]}) { |e| " #{e} " }.split(' ')
expr.gsub(%r{[\+\-\*/DURSKHL@]}) { |e| " #{e} " }.split(' ')
end

# 式
Expand Down Expand Up @@ -150,33 +150,60 @@ def unary

# 項:ダイスロール、数値
def term
ret = expect_number()
num = expect_number()
if consume("D")
times = ret
times = num
sides = expect_number()
critical = consume("@") ? expect_number() : nil

filter = dice_roll_filter()
if filter
# ダイスロール後のフィルタリングあり
n_filtering = expect_number()
@contain_dice_roll = true
return Node::DiceRollWithFilter.new(times, sides, n_filtering, filter)
end

# 通常のダイスロール
critical = consume("@") ? expect_number() : nil
@contain_dice_roll = true
ret = AddDice::Node::DiceRoll.new(times, sides, critical)
return Node::DiceRoll.new(times, 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

# トークンを消費する
#
# トークンと期待した文字列が合致していた場合、次のトークンに進む。
# 合致していなかった場合は、進まない。
#
# @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

Expand Down
4 changes: 2 additions & 2 deletions src/dice/add_dice/randomizer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ def roll(times, sides, critical)
return dice_groups
end

private

# ダイスを振る(振り足しなしの1回分)
# @param [Integer] times ダイス数
# @param [Integer] sides 面数
Expand All @@ -62,6 +60,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 # 判定のみ振り足し
Expand Down
64 changes: 64 additions & 0 deletions src/test/add_dice_parser_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

# 構文解析をテストする
Expand Down
48 changes: 48 additions & 0 deletions src/test/data/None.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1098,3 +1098,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[2,3,5,8,8] > 21
rand:8/10,3/10,2/10,5/10,8/10
============================
input:
5D10KL3 小さな出目から3個取った後の和
output:
DiceBot : (5D10KL3) > 10[2,3,5,8,8] > 10
rand:8/10,3/10,2/10,5/10,8/10
============================
input:
5D10DH3 大きな出目から3個除いた後の和
output:
DiceBot : (5D10DH3) > 5[2,3,5,8,8] > 5
rand:8/10,3/10,2/10,5/10,8/10
============================
input:
5D10DL3 小さな出目から3個除いた後の和
output:
DiceBot : (5D10DL3) > 16[2,3,5,8,8] > 16
rand:8/10,3/10,2/10,5/10,8/10
============================
input:
5D10KH0 取る数が0の場合でも動く
output:
DiceBot : (5D10KH0) > 0[2,3,5,8,8] > 0
rand:8/10,3/10,2/10,5/10,8/10
============================
input:
5D10KH7 取る数が多すぎても動く
output:
DiceBot : (5D10KH7) > 26[2,3,5,8,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