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

繰り返しコマンドを実装 #297

Merged
merged 3 commits into from
Dec 20, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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