Skip to content

Commit 6d56417

Browse files
authored
Merge pull request #490 from bcdice/multi_choice
choiceの複数選択と連続要素略記
2 parents 34d15fe + bc244b0 commit 6d56417

2 files changed

Lines changed: 211 additions & 7 deletions

File tree

lib/bcdice/common_command/choice.rb

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ module CommonCommand
2929
# choice A,B X,Y -> "A,B" と "X,Y" から選ぶ
3030
# choice(A[], B[], C[]) -> "A[]", "B[]", "C[]" から選ぶ
3131
# choice[A(), B(), C()] -> "A()", "B()", "C()" から選ぶ
32+
#
33+
# "choise"の後に数を指定することで、列挙した要素から重複なしで複数個を選ぶ
34+
# choice2[A,B,C] -> "A", "B", "C" から重複なしで2個選ぶ
35+
#
36+
# 指定したい要素が「AからD」のように連続する項目の場合に「A-D」と省略して記述できる
37+
# 略記の展開はアルファベット1文字もしくは数字の範囲に限り、略記1つを指定したときのみ展開される
38+
# choice[A-D] -> choice[A,B,C,D] と等価
39+
# choice[b-g] -> choice[b,c,d,e,f,g] と等価
40+
# choice[3-7] -> choice[3,4,5,6,7] と等価
41+
# choice[A-D,Z] -> 展開されない。 "A-D", "Z" から選ぶ
42+
# choice[D-A] -> 展開されない。
3243
class Choice
3344
PREFIX_PATTERN = /choice/.freeze
3445

@@ -44,6 +55,12 @@ module BlockDelimiter
4455
space: /\s+/,
4556
}.freeze
4657

58+
DELIMITER_CHAR = {
59+
bracket: ", ",
60+
paren: ", ",
61+
space: " ",
62+
}.freeze
63+
4764
TERMINATION = {
4865
bracket: /\]/,
4966
paren: /\)/,
@@ -77,6 +94,11 @@ def parse(command)
7794
return nil
7895
end
7996

97+
takes = scanner.scan(/\d+/)&.to_i || 1
98+
if takes == 0
99+
return nil
100+
end
101+
80102
type =
81103
case scanner.scan(/\(|\[|\s+/)
82104
when "["
@@ -105,47 +127,96 @@ def parse(command)
105127
items.push(last_item.delete_suffix(SUFFIX[type]))
106128

107129
items = items.map(&:strip).reject(&:empty?)
108-
if items.empty?
130+
if items.size == 1
131+
items = parse_multi_item_shorthand(items.first)
132+
end
133+
134+
if items.empty? || items.size < takes
109135
return nil
110136
end
111137

112138
new(
113139
secret: secret,
114140
block_delimiter: type,
141+
takes: takes,
115142
items: items
116143
)
117144
end
145+
146+
def parse_multi_item_shorthand(str)
147+
parse_multi_nums_shorthand(str) || parse_multi_chars_shorthand(str) || []
148+
end
149+
150+
def parse_multi_nums_shorthand(str)
151+
m = /^(\d+)-(\d+)$/.match(str)
152+
unless m
153+
return nil
154+
end
155+
156+
first = m[1].to_i
157+
last = m[2].to_i
158+
if first > last
159+
return nil
160+
end
161+
162+
return first.upto(last).to_a
163+
end
164+
165+
def parse_multi_chars_shorthand(str)
166+
m = /^([a-z])-([a-z])$/.match(str) || /^([A-Z])-([A-Z])$/.match(str)
167+
unless m
168+
return nil
169+
end
170+
171+
first = m[1]
172+
last = m[2]
173+
if first > last
174+
return nil
175+
end
176+
177+
return first.upto(last).to_a
178+
end
118179
end
119180

120181
# @param secret [Boolean]
121182
# @param block_delimiter [BlockDelimiter::BRACKET, BlockDelimiter::PAREN, BlockDelimiter::SPACE]
183+
# @param takes [Integer] 何個チョイスするか
122184
# @param items [Array<String>]
123-
def initialize(secret:, block_delimiter:, items:)
185+
def initialize(secret:, block_delimiter:, takes:, items:)
124186
@secret = secret
125187
@block_delimiter = block_delimiter
188+
@takes = takes
126189
@items = items
127190
end
128191

129192
# @param randomizer [Randomizer]
130193
# @return [Result]
131194
def roll(randomizer)
132-
index = randomizer.roll_index(@items.size)
133-
chosen = @items[index]
195+
items = @items.dup
196+
chosens = []
197+
@takes.times do
198+
index = randomizer.roll_index(items.size)
199+
chosens << items.delete_at(index)
200+
end
134201

135202
Result.new.tap do |r|
203+
chosen = chosens.join(DELIMITER_CHAR[@block_delimiter])
204+
136205
r.secret = @secret
137206
r.text = "(#{expr()}) > #{chosen}"
138207
end
139208
end
140209

141210
def expr
211+
takes = @takes == 1 ? nil : @takes
212+
142213
case @block_delimiter
143214
when BlockDelimiter::SPACE
144-
"choice #{@items.join(' ')}"
215+
"choice#{takes} #{@items.join(' ')}"
145216
when BlockDelimiter::BRACKET
146-
"choice[#{@items.join(',')}]"
217+
"choice#{takes}[#{@items.join(',')}]"
147218
when BlockDelimiter::PAREN
148-
"choice(#{@items.join(',')})"
219+
"choice#{takes}(#{@items.join(',')})"
149220
end
150221
end
151222
end

test/data/choice.toml

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,136 @@ game_system = "DiceBot"
122122
input = "choice[, ,, ,, ,] 要素数ゼロ"
123123
output = ""
124124
rands = []
125+
126+
[[ test ]]
127+
game_system = "DiceBot"
128+
input = "choice2[The Call of Cthulhu, The Shadow Over Innsmouth, The Shadow Out of Time] 複数個取得"
129+
output = "(choice2[The Call of Cthulhu,The Shadow Over Innsmouth,The Shadow Out of Time]) > The Shadow Out of Time, The Call of Cthulhu"
130+
rands = [
131+
{ sides = 3, value = 3 },
132+
{ sides = 2, value = 1 },
133+
]
134+
135+
[[ test ]]
136+
game_system = "DiceBot"
137+
input = "choice2(a,b,c) かっこ区切り"
138+
output = "(choice2(a,b,c)) > a, b"
139+
rands = [
140+
{ sides = 3, value = 1 },
141+
{ sides = 2, value = 1 },
142+
]
143+
144+
# 空白区切り
145+
[[ test ]]
146+
game_system = "DiceBot"
147+
input = "choice2 a b c"
148+
output = "(choice2 a b c) > a b"
149+
rands = [
150+
{ sides = 3, value = 1 },
151+
{ sides = 2, value = 1 },
152+
]
153+
154+
[[ test ]]
155+
game_system = "DiceBot"
156+
input = "choice3[A(), B(), C()] 全部取る"
157+
output = "(choice3[A(),B(),C()]) > A(), C(), B()"
158+
rands = [
159+
{ sides = 3, value = 1 },
160+
{ sides = 2, value = 2 },
161+
{ sides = 1, value = 1 },
162+
]
163+
164+
[[ test ]]
165+
game_system = "DiceBot"
166+
input = "choice0[abc,def] 0個とる"
167+
output = ""
168+
rands = []
169+
170+
[[ test ]]
171+
game_system = "DiceBot"
172+
input = "choice3[abc,def] とる数が多い"
173+
output = ""
174+
rands = []
175+
176+
[[ test ]]
177+
game_system = "DiceBot"
178+
input = "choice[A-F] 複数要素の省略形"
179+
output = "(choice[A,B,C,D,E,F]) > C"
180+
rands = [
181+
{ sides = 6, value = 3 },
182+
]
183+
184+
[[ test ]]
185+
game_system = "DiceBot"
186+
input = "choice[c-g] 複数要素の省略形"
187+
output = "(choice[c,d,e,f,g]) > g"
188+
rands = [
189+
{ sides = 5, value = 5 },
190+
]
191+
192+
[[ test ]]
193+
game_system = "DiceBot"
194+
input = "choice[3-10] 複数要素の省略形"
195+
output = "(choice[3,4,5,6,7,8,9,10]) > 10"
196+
rands = [
197+
{ sides = 8, value = 8 },
198+
]
199+
200+
# 複数要素の省略形 空白区切り
201+
[[ test ]]
202+
game_system = "DiceBot"
203+
input = "choice A-F"
204+
output = "(choice A B C D E F) > C"
205+
rands = [
206+
{ sides = 6, value = 3 },
207+
]
208+
209+
[[ test ]]
210+
game_system = "DiceBot"
211+
input = "choice(A-F) 複数要素の省略形 カッコ区切り"
212+
output = "(choice(A,B,C,D,E,F)) > C"
213+
rands = [
214+
{ sides = 6, value = 3 },
215+
]
216+
217+
[[ test ]]
218+
game_system = "DiceBot"
219+
input = "choice[F-A] 大小関係が逆"
220+
output = ""
221+
rands = []
222+
223+
[[ test ]]
224+
game_system = "DiceBot"
225+
input = "choice[g-c] 大小関係が逆"
226+
output = ""
227+
rands = []
228+
229+
[[ test ]]
230+
game_system = "DiceBot"
231+
input = "choice[10-3] 大小関係が逆"
232+
output = ""
233+
rands = []
234+
235+
[[ test ]]
236+
game_system = "DiceBot"
237+
input = "choice[a-zz] 複数文字では省略にならない"
238+
output = ""
239+
rands = []
240+
241+
[[ test ]]
242+
game_system = "DiceBot"
243+
input = "choice[A-F, Z] こういうケースでは展開しない"
244+
output = "(choice[A-F,Z]) > A-F"
245+
rands = [
246+
{ sides = 2, value = 1 },
247+
]
248+
249+
[[ test ]]
250+
game_system = "DiceBot"
251+
input = "choice3[A-F] 複数選択との混合"
252+
output = "(choice3[A,B,C,D,E,F]) > C, F, A"
253+
rands = [
254+
{ sides = 6, value = 3 },
255+
{ sides = 5, value = 5 },
256+
{ sides = 4, value = 1 },
257+
]

0 commit comments

Comments
 (0)