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

choiceの複数選択と連続要素略記 #490

Merged
merged 4 commits into from
Jul 30, 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
85 changes: 78 additions & 7 deletions lib/bcdice/common_command/choice.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ module CommonCommand
# choice A,B X,Y -> "A,B" と "X,Y" から選ぶ
# choice(A[], B[], C[]) -> "A[]", "B[]", "C[]" から選ぶ
# choice[A(), B(), C()] -> "A()", "B()", "C()" から選ぶ
#
# "choise"の後に数を指定することで、列挙した要素から重複なしで複数個を選ぶ
# choice2[A,B,C] -> "A", "B", "C" から重複なしで2個選ぶ
#
# 指定したい要素が「AからD」のように連続する項目の場合に「A-D」と省略して記述できる
# 略記の展開はアルファベット1文字もしくは数字の範囲に限り、略記1つを指定したときのみ展開される
# choice[A-D] -> choice[A,B,C,D] と等価
# choice[b-g] -> choice[b,c,d,e,f,g] と等価
# choice[3-7] -> choice[3,4,5,6,7] と等価
# choice[A-D,Z] -> 展開されない。 "A-D", "Z" から選ぶ
# choice[D-A] -> 展開されない。
class Choice
PREFIX_PATTERN = /choice/.freeze

Expand All @@ -44,6 +55,12 @@ module BlockDelimiter
space: /\s+/,
}.freeze

DELIMITER_CHAR = {
bracket: ", ",
paren: ", ",
space: " ",
}.freeze

TERMINATION = {
bracket: /\]/,
paren: /\)/,
Expand Down Expand Up @@ -77,6 +94,11 @@ def parse(command)
return nil
end

takes = scanner.scan(/\d+/)&.to_i || 1
if takes == 0
return nil
end

type =
case scanner.scan(/\(|\[|\s+/)
when "["
Expand Down Expand Up @@ -105,47 +127,96 @@ def parse(command)
items.push(last_item.delete_suffix(SUFFIX[type]))

items = items.map(&:strip).reject(&:empty?)
if items.empty?
if items.size == 1
items = parse_multi_item_shorthand(items.first)
end

if items.empty? || items.size < takes
return nil
end

new(
secret: secret,
block_delimiter: type,
takes: takes,
items: items
)
end

def parse_multi_item_shorthand(str)
parse_multi_nums_shorthand(str) || parse_multi_chars_shorthand(str) || []
end

def parse_multi_nums_shorthand(str)
m = /^(\d+)-(\d+)$/.match(str)
unless m
return nil
end

first = m[1].to_i
last = m[2].to_i
if first > last
return nil
end

return first.upto(last).to_a
end

def parse_multi_chars_shorthand(str)
m = /^([a-z])-([a-z])$/.match(str) || /^([A-Z])-([A-Z])$/.match(str)
unless m
return nil
end

first = m[1]
last = m[2]
if first > last
return nil
end

return first.upto(last).to_a
end
end

# @param secret [Boolean]
# @param block_delimiter [BlockDelimiter::BRACKET, BlockDelimiter::PAREN, BlockDelimiter::SPACE]
# @param takes [Integer] 何個チョイスするか
# @param items [Array<String>]
def initialize(secret:, block_delimiter:, items:)
def initialize(secret:, block_delimiter:, takes:, items:)
@secret = secret
@block_delimiter = block_delimiter
@takes = takes
@items = items
end

# @param randomizer [Randomizer]
# @return [Result]
def roll(randomizer)
index = randomizer.roll_index(@items.size)
chosen = @items[index]
items = @items.dup
chosens = []
@takes.times do
index = randomizer.roll_index(items.size)
chosens << items.delete_at(index)
end

Result.new.tap do |r|
chosen = chosens.join(DELIMITER_CHAR[@block_delimiter])

r.secret = @secret
r.text = "(#{expr()}) > #{chosen}"
end
end

def expr
takes = @takes == 1 ? nil : @takes

case @block_delimiter
when BlockDelimiter::SPACE
"choice #{@items.join(' ')}"
"choice#{takes} #{@items.join(' ')}"
when BlockDelimiter::BRACKET
"choice[#{@items.join(',')}]"
"choice#{takes}[#{@items.join(',')}]"
when BlockDelimiter::PAREN
"choice(#{@items.join(',')})"
"choice#{takes}(#{@items.join(',')})"
end
end
end
Expand Down
133 changes: 133 additions & 0 deletions test/data/choice.toml
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,136 @@ game_system = "DiceBot"
input = "choice[, ,, ,, ,] 要素数ゼロ"
output = ""
rands = []

[[ test ]]
game_system = "DiceBot"
input = "choice2[The Call of Cthulhu, The Shadow Over Innsmouth, The Shadow Out of Time] 複数個取得"
output = "(choice2[The Call of Cthulhu,The Shadow Over Innsmouth,The Shadow Out of Time]) > The Shadow Out of Time, The Call of Cthulhu"
rands = [
{ sides = 3, value = 3 },
{ sides = 2, value = 1 },
]

[[ test ]]
game_system = "DiceBot"
input = "choice2(a,b,c) かっこ区切り"
output = "(choice2(a,b,c)) > a, b"
rands = [
{ sides = 3, value = 1 },
{ sides = 2, value = 1 },
]

# 空白区切り
[[ test ]]
game_system = "DiceBot"
input = "choice2 a b c"
output = "(choice2 a b c) > a b"
rands = [
{ sides = 3, value = 1 },
{ sides = 2, value = 1 },
]

[[ test ]]
game_system = "DiceBot"
input = "choice3[A(), B(), C()] 全部取る"
output = "(choice3[A(),B(),C()]) > A(), C(), B()"
rands = [
{ sides = 3, value = 1 },
{ sides = 2, value = 2 },
{ sides = 1, value = 1 },
]

[[ test ]]
game_system = "DiceBot"
input = "choice0[abc,def] 0個とる"
output = ""
rands = []

[[ test ]]
game_system = "DiceBot"
input = "choice3[abc,def] とる数が多い"
output = ""
rands = []

[[ test ]]
game_system = "DiceBot"
input = "choice[A-F] 複数要素の省略形"
output = "(choice[A,B,C,D,E,F]) > C"
rands = [
{ sides = 6, value = 3 },
]

[[ test ]]
game_system = "DiceBot"
input = "choice[c-g] 複数要素の省略形"
output = "(choice[c,d,e,f,g]) > g"
rands = [
{ sides = 5, value = 5 },
]

[[ test ]]
game_system = "DiceBot"
input = "choice[3-10] 複数要素の省略形"
output = "(choice[3,4,5,6,7,8,9,10]) > 10"
rands = [
{ sides = 8, value = 8 },
]

# 複数要素の省略形 空白区切り
[[ test ]]
game_system = "DiceBot"
input = "choice A-F"
output = "(choice A B C D E F) > C"
rands = [
{ sides = 6, value = 3 },
]

[[ test ]]
game_system = "DiceBot"
input = "choice(A-F) 複数要素の省略形 カッコ区切り"
output = "(choice(A,B,C,D,E,F)) > C"
rands = [
{ sides = 6, value = 3 },
]

[[ test ]]
game_system = "DiceBot"
input = "choice[F-A] 大小関係が逆"
output = ""
rands = []

[[ test ]]
game_system = "DiceBot"
input = "choice[g-c] 大小関係が逆"
output = ""
rands = []

[[ test ]]
game_system = "DiceBot"
input = "choice[10-3] 大小関係が逆"
output = ""
rands = []

[[ test ]]
game_system = "DiceBot"
input = "choice[a-zz] 複数文字では省略にならない"
output = ""
rands = []

[[ test ]]
game_system = "DiceBot"
input = "choice[A-F, Z] こういうケースでは展開しない"
output = "(choice[A-F,Z]) > A-F"
rands = [
{ sides = 2, value = 1 },
]

[[ test ]]
game_system = "DiceBot"
input = "choice3[A-F] 複数選択との混合"
output = "(choice3[A,B,C,D,E,F]) > C, F, A"
rands = [
{ sides = 6, value = 3 },
{ sides = 5, value = 5 },
{ sides = 4, value = 1 },
]