Skip to content

Commit

Permalink
Merge 4485a47 into b0feed4
Browse files Browse the repository at this point in the history
  • Loading branch information
ysakasin authored Dec 14, 2020
2 parents b0feed4 + 4485a47 commit f58558a
Show file tree
Hide file tree
Showing 4 changed files with 577 additions and 1 deletion.
2 changes: 2 additions & 0 deletions lib/bcdice/common_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require "bcdice/common_command/calc"
require "bcdice/common_command/choice"
require "bcdice/common_command/d66_dice"
require "bcdice/common_command/repeat"
require "bcdice/common_command/reroll_dice"
require "bcdice/common_command/upper_dice"
require "bcdice/common_command/version"
Expand All @@ -15,6 +16,7 @@ module CommonCommand
Calc,
Choice,
D66Dice,
Repeat,
RerollDice,
UpperDice,
Version,
Expand Down
102 changes: 102 additions & 0 deletions lib/bcdice/common_command/repeat.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# frozen_string_literal: true

require "strscan"

module BCDice
module CommonCommand
# リピート
#
# 繰り返し回数を指定して、特定のコマンドを複数回実行する
# 例)
# x5 choice[A,B,C,D] #=> `choice[A,B,C,D]`を5回実行する
# rep2 CC<=75 #=> `CC<=75`を2回実行する
# repeat10 2D6+5 #=> `2D6+6`を10回実行する
#
# このコマンドを入れ子させることは許容しない。繰り返し回数の爆発に繋がるためである。
# 以下は実行時にエラーとなる。
# x10 repeat100 100D100
class Repeat
PREFIX_PATTERN = /(repeat|rep|x)\d+/.freeze

REPEAT_LIMIT = 100

class << self
def eval(command, game_system, randomizer)
cmd = parse(command)
cmd&.roll(game_system, randomizer)
end

private

def parse(command)
scanner = StringScanner.new(command)

secret = !scanner.scan(/s/i).nil?
keyword = scanner.scan(/repeat|rep|x/i)
repeat_times = scanner.scan(/\d+/)&.to_i
whitespace = scanner.scan(/\s+/)
if keyword.nil? || repeat_times.nil? || whitespace.nil?
return nil
end

trailer = scanner.post_match
if trailer.nil? || trailer.empty?
return nil
end

new(
secret: secret,
times: repeat_times,
trailer: trailer
)
end
end

def initialize(secret:, times:, trailer:)
@secret = secret
@times = times
@trailer = trailer
end

def roll(game_system, randomizer)
err = validate()
return err if err

results = Array.new(@times) do
cmd = game_system.class.new(@trailer)
cmd.randomizer = randomizer
cmd.eval()
end

if results.count(nil) == @times
return result_with_text("繰り返し対象のコマンドが実行できませんでした (#{@trailer})")
end

text = results.map.with_index(1) { |r, index| "\##{index}\n#{r.text}" }.join("\n\n")
secret = @secret || results.any?(&:secret?)

Result.new.tap do |r|
r.secret = secret
r.text = text
end
end

private

def validate
if /\A(repeat|rep|x)\d+/.match?(@trailer)
result_with_text("Repeatコマンドの重複はできません")
elsif @times < 1 || REPEAT_LIMIT < @times
result_with_text("繰り返し回数は1以上、#{REPEAT_LIMIT}以下が指定可能です")
end
end

def result_with_text(text)
Result.new.tap do |r|
r.secret = @secret
r.text = text
end
end
end
end
end
Loading

0 comments on commit f58558a

Please sign in to comment.