Skip to content

Commit

Permalink
加算ロール:フィルタ処理付きダイスロールを追加する
Browse files Browse the repository at this point in the history
fixes #88

大きな/小さな出目から、複数個取る/除く機能。
D&D 5thなどで使う。
  • Loading branch information
ochaochaocha3 committed Apr 30, 2020
1 parent f9f3c24 commit eb8d689
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 12 deletions.
87 changes: 87 additions & 0 deletions src/dice/add_dice/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
# 値
Expand Down
45 changes: 35 additions & 10 deletions src/dice/add_dice/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,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 @@ -148,32 +148,57 @@ 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

# トークンを消費する
#
# トークンと期待した文字列が合致していた場合、次のトークンに進む。
# 合致していなかった場合は、進まない。
#
# @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 @@ -51,8 +51,6 @@ def roll(times, sides, critical)
[total_sum, text]
end

private

# ダイスを振る(振り足しなしの1回分)
# @param [Integer] times ダイス数
# @param [Integer] sides 面数
Expand All @@ -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 # 判定のみ振り足し
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 @@ -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

0 comments on commit eb8d689

Please sign in to comment.