DiceRoller service
This commit is contained in:
parent
bd21b53b06
commit
c01ab549fd
|
@ -11,11 +11,22 @@ class Stat < ApplicationRecord
|
|||
uniqueness: true
|
||||
validates :value, presence: true,
|
||||
numericality: true
|
||||
validate :validate_roll_command
|
||||
|
||||
before_validation :set_slug
|
||||
|
||||
def roll
|
||||
DiceRoller.new(roll_command, stat: self).roll
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_roll_command
|
||||
return if roll_command.blank?
|
||||
|
||||
DiceRoller.new(roll_command).valid?
|
||||
end
|
||||
|
||||
def set_slug
|
||||
return if slug.present? || name.blank?
|
||||
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DiceRoller
|
||||
def initialize(roll_command, stat: nil)
|
||||
@roll_command = roll_command
|
||||
@stat = stat&.value
|
||||
end
|
||||
|
||||
def roll
|
||||
result = 0
|
||||
operator = nil
|
||||
roll_command_parts.each do |part|
|
||||
case part
|
||||
when /\A\d*d\d+\z/
|
||||
operator.nil? ? (result += roll_dice(part)) : (result = result.send(operator, roll_dice(part)))
|
||||
when /\A\d+\z/
|
||||
operator.nil? ? (result += part.to_i) : (result = result.send(operator, part.to_i))
|
||||
when "self"
|
||||
operator.nil? ? (result += @stat) : (result = result.send(operator, @stat))
|
||||
when /\A[+\-*\/]\z/
|
||||
operator = part
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def valid?
|
||||
return if @roll_command.blank?
|
||||
|
||||
# No repeated math operators
|
||||
return false if @roll_command.match?(/[+\-*\/]{2,}/)
|
||||
|
||||
# No leading or trailing math operators
|
||||
return false if @roll_command.match?(/\A[+\-*\/]/) || @roll_command.match?(/[+\-*\/]\z/)
|
||||
|
||||
@roll_command.match?(
|
||||
/
|
||||
\A(
|
||||
(\d*d\d*) |
|
||||
([+\-*\/]) |
|
||||
(\d+) |
|
||||
(self)
|
||||
)*\z/xi,
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def roll_command_parts
|
||||
@roll_command.scan(/([+\-*\/])|(\d*d\d+)|(\d+)|(self)/xi)
|
||||
.flatten
|
||||
.compact_blank
|
||||
end
|
||||
|
||||
def roll_dice(command)
|
||||
parts = command.downcase.split("d").compact_blank
|
||||
die_type = parts.last
|
||||
dice_number = parts.length > 1 ? parts.first.to_i : 1
|
||||
|
||||
result = 0
|
||||
dice_number.times do
|
||||
result += rand(1..die_type.to_i)
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
|
@ -22,4 +22,10 @@ class StatTest < ActiveSupport::TestCase
|
|||
assert_not_equal existing_stat.slug, stat.slug
|
||||
assert_equal "#{existing_stat.slug}-2", stat.slug
|
||||
end
|
||||
|
||||
test "rolls with roll_command" do
|
||||
stat = stats(:strength)
|
||||
stat.roll_command = "1d6"
|
||||
100.times { assert (1..6).cover?(stat.roll) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
class DiceRollerTest < ActiveSupport::TestCase
|
||||
test "correctly validates strings" do
|
||||
valid_strings = [
|
||||
"2d6",
|
||||
"12",
|
||||
"self",
|
||||
"d12",
|
||||
"d20+self",
|
||||
"8+D8-self",
|
||||
]
|
||||
|
||||
invalid_strings = [
|
||||
"+",
|
||||
"sel+10",
|
||||
"d8++13",
|
||||
]
|
||||
|
||||
valid_strings.each do |roll_command|
|
||||
assert DiceRoller.new(roll_command).valid?
|
||||
end
|
||||
|
||||
invalid_strings.each do |roll_command|
|
||||
assert_not DiceRoller.new(roll_command).valid?
|
||||
end
|
||||
end
|
||||
|
||||
test "rolls appropriate results" do
|
||||
100.times do
|
||||
assert (1..6).include? DiceRoller.new("d6").roll
|
||||
assert (2..12).include? DiceRoller.new("2d6").roll
|
||||
assert (11..30).include? DiceRoller.new("d20+self", stat: stats(:strength)).roll
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue