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

個数カウントダイスを追加する #509

Merged
merged 1 commit into from
Nov 14, 2021
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ lib/bcdice/arithmetic/parser.rb
lib/bcdice/command/parser.rb
lib/bcdice/common_command/add_dice/parser.rb
lib/bcdice/common_command/barabara_dice/parser.rb
lib/bcdice/common_command/tally_dice/parser.rb
lib/bcdice/common_command/calc/parser.rb
lib/bcdice/common_command/reroll_dice/parser.rb
lib/bcdice/common_command/upper_dice/parser.rb
Expand Down
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ AllCops:
- lib/bcdice/command/parser.rb
- lib/bcdice/common_command/add_dice/parser.rb
- lib/bcdice/common_command/barabara_dice/parser.rb
- lib/bcdice/common_command/tally_dice/parser.rb
- lib/bcdice/common_command/calc/parser.rb
- lib/bcdice/common_command/reroll_dice/parser.rb
- lib/bcdice/common_command/upper_dice/parser.rb
Expand Down
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ RACC_TARGETS = [
"lib/bcdice/command/parser.rb",
"lib/bcdice/common_command/add_dice/parser.rb",
"lib/bcdice/common_command/barabara_dice/parser.rb",
"lib/bcdice/common_command/tally_dice/parser.rb",
"lib/bcdice/common_command/calc/parser.rb",
"lib/bcdice/common_command/reroll_dice/parser.rb",
"lib/bcdice/common_command/upper_dice/parser.rb",
Expand Down
2 changes: 2 additions & 0 deletions lib/bcdice/common_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require "bcdice/common_command/add_dice"
require "bcdice/common_command/barabara_dice"
require "bcdice/common_command/tally_dice"
require "bcdice/common_command/calc"
require "bcdice/common_command/choice"
require "bcdice/common_command/d66_dice"
Expand All @@ -15,6 +16,7 @@ module CommonCommand
COMMANDS = [
AddDice,
BarabaraDice,
TallyDice,
Calc,
Choice,
D66Dice,
Expand Down
19 changes: 19 additions & 0 deletions lib/bcdice/common_command/tally_dice.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

require "bcdice/common_command/tally_dice/parser"

module BCDice
module CommonCommand
# 個数カウントダイスのモジュール
module TallyDice
PREFIX_PATTERN = /\d+T[YZ]\d+/.freeze

class << self
def eval(command, game_system, randomizer)
cmd = Parser.parse(command)
cmd&.eval(game_system, randomizer)
end
end
end
end
end
135 changes: 135 additions & 0 deletions lib/bcdice/common_command/tally_dice/node.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# frozen_string_literal: true

require "bcdice/result"

module BCDice
module CommonCommand
module TallyDice
# 個数カウントダイスを表すノードをまとめるモジュール
module Node
# 個数カウントダイス:コマンドのノード
class Command
# 最大面数
MAX_SIDES = 20

# @param secret [Boolean] シークレットダイスか
# @param notation [Notation] ダイス表記
def initialize(secret:, notation:)
@secret = secret
@notation = notation
end

# @param game_system [Base] ゲームシステム
# @param randomizer [Randomizer] ランダマイザ
# @return [Result, nil]
def eval(game_system, randomizer)
dice = @notation.to_dice(game_system.round_type)
unless dice.valid?
return nil
end

if dice.sides > MAX_SIDES
return Result.new("(#{dice}) > 面数は1以上、#{MAX_SIDES}以下としてください")
end

values = dice.roll(randomizer)

values_str = (game_system.sort_barabara_dice? ? values.sort : values)
.join(",")

# TODO: Ruby 2.7以降のみサポートするようになった場合
# Enumerable#tally で書く
values_count = values
.group_by(&:itself)
.transform_values(&:length)

values_count_strs = (1..dice.sides).map do |v|
count = values_count.fetch(v, 0)

next nil if count == 0 && !dice.show_zeros?

"[#{v}]×#{values_count.fetch(v, 0)}"
end

sequence = [
"(#{dice})",
values_str,
values_count_strs.compact.join(", "),
].compact

Result.new.tap do |r|
r.secret = @secret
r.text = sequence.join(" > ")
end
end
end

# 個数カウントダイス:ダイス表記のノード
class Notation
# @return [Integer] 振る回数
attr_reader :times
# @return [Integer] 面数
attr_reader :sides
# @return [Boolean] 個数0を表示するか
attr_reader :show_zeros

# @param times [#eval] 振る回数
# @param sides [#eval] 面数
# @param show_zeros [Boolean] 個数0を表示するか
def initialize(times:, sides:, show_zeros:)
@times = times
@sides = sides
@show_zeros = show_zeros
end

# @param round_type [Symbol] 除算の端数処理方法
# @return [Dice]
def to_dice(round_type)
times = @times.eval(round_type)
sides = @sides.eval(round_type)

Dice.new(times: times, sides: sides, show_zeros: @show_zeros)
end
end

# 個数カウントダイス:ダイスのノード
class Dice
# @return [Integer] 振る回数
attr_reader :times
# @return [Integer] 面数
attr_reader :sides
# @return [Boolean] 個数0を表示するか
attr_reader :show_zeros

alias show_zeros? show_zeros

# @param times [Integer] 振る回数
# @param sides [Integer] 面数
def initialize(times:, sides:, show_zeros:)
@times = times
@sides = sides
@show_zeros = show_zeros
end

# @return [Boolean] ダイスとして有効(振る回数、面数ともに正の数)か
def valid?
@times > 0 && @sides > 0
end

# ダイスを振る
# @param randomizer [BCDice::Randomizer] ランダマイザ
# @return [Array<Integer>] 出目の配列
def roll(randomizer)
randomizer.roll_barabara(@times, @sides)
end

# @return [String]
def to_s
show_zeros_symbol = @show_zeros ? "Z" : "Y"
"#{@times}T#{show_zeros_symbol}#{@sides}"
end
end
end
end
end
end
93 changes: 93 additions & 0 deletions lib/bcdice/common_command/tally_dice/parser.y
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
class BCDice::CommonCommand::TallyDice::Parser
token NUMBER T Y Z R U C F S PLUS MINUS ASTERISK SLASH PARENL PARENR

rule
expr: secret notation
{
result = Node::Command.new(
secret: val[0],
notation: val[1]
)
}

secret: /* none */
{ result = false }
| S
{ result = true }

notation: term T show_zeros term
{
result = Node::Notation.new(
times: val[0],
sides: val[3],
show_zeros: val[2]
)
}

show_zeros: Y
{ result = false }
| Z
{ result = true }

add: add PLUS mul
{ result = Arithmetic::Node::BinaryOp.new(val[0], :+, val[2]) }
| add MINUS mul
{ result = Arithmetic::Node::BinaryOp.new(val[0], :-, val[2]) }
| mul

mul: mul ASTERISK unary
{ result = Arithmetic::Node::BinaryOp.new(val[0], :*, val[2]) }
| mul SLASH unary round_type
{
divied_class = val[3]
result = divied_class.new(val[0], val[2])
}
| unary

round_type: /* none */
{ result = Arithmetic::Node::DivideWithGameSystemDefault }
| U
{ result = Arithmetic::Node::DivideWithCeil }
| C
{ result = Arithmetic::Node::DivideWithCeil }
| R
{ result = Arithmetic::Node::DivideWithRound }
| F
{ result = Arithmetic::Node::DivideWithFloor }

unary: PLUS unary
{ result = val[1] }
| MINUS unary
{ result = Arithmetic::Node::Negative.new(val[1]) }
| term

term: PARENL add PARENR
{ result = val[1] }
| NUMBER
{ result = Arithmetic::Node::Number.new(val[0]) }
end

---- header

require "bcdice/common_command/lexer"
require "bcdice/common_command/tally_dice/node"
require "bcdice/arithmetic/node"

---- inner

def self.parse(source)
new.parse(source)
end

def parse(source)
@lexer = Lexer.new(source)
do_parse()
rescue ParseError
nil
end

private

def next_token
@lexer.next_token
end
53 changes: 53 additions & 0 deletions test/data/tally_sort.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
[[ test ]]
game_system = "StellarKnights"
input = "20TY6 銀剣のステラナイツ ソートあり(sort_barabara_dice? => true)"
output = "(20TY6) > 1,1,2,2,3,3,4,4,4,4,4,4,5,5,5,5,6,6,6,6 > [1]×2, [2]×2, [3]×2, [4]×6, [5]×4, [6]×4"
rands = [
{ sides = 6, value = 6 },
{ sides = 6, value = 2 },
{ sides = 6, value = 6 },
{ sides = 6, value = 4 },
{ sides = 6, value = 6 },
{ sides = 6, value = 5 },
{ sides = 6, value = 4 },
{ sides = 6, value = 3 },
{ sides = 6, value = 2 },
{ sides = 6, value = 1 },
{ sides = 6, value = 4 },
{ sides = 6, value = 4 },
{ sides = 6, value = 5 },
{ sides = 6, value = 1 },
{ sides = 6, value = 6 },
{ sides = 6, value = 5 },
{ sides = 6, value = 4 },
{ sides = 6, value = 3 },
{ sides = 6, value = 4 },
{ sides = 6, value = 5 },
]

[[ test ]]
game_system = "StellarKnights"
input = "20TZ6 銀剣のステラナイツ ソートあり(sort_barabara_dice? => true)"
output = "(20TZ6) > 1,1,2,2,3,3,4,4,4,4,4,4,5,5,5,5,6,6,6,6 > [1]×2, [2]×2, [3]×2, [4]×6, [5]×4, [6]×4"
rands = [
{ sides = 6, value = 6 },
{ sides = 6, value = 2 },
{ sides = 6, value = 6 },
{ sides = 6, value = 4 },
{ sides = 6, value = 6 },
{ sides = 6, value = 5 },
{ sides = 6, value = 4 },
{ sides = 6, value = 3 },
{ sides = 6, value = 2 },
{ sides = 6, value = 1 },
{ sides = 6, value = 4 },
{ sides = 6, value = 4 },
{ sides = 6, value = 5 },
{ sides = 6, value = 1 },
{ sides = 6, value = 6 },
{ sides = 6, value = 5 },
{ sides = 6, value = 4 },
{ sides = 6, value = 3 },
{ sides = 6, value = 4 },
{ sides = 6, value = 5 },
]
Loading