DiceRoller service

This commit is contained in:
Trevor Vallender 2024-06-11 14:55:39 +01:00
parent bd21b53b06
commit c01ab549fd
5 changed files with 121 additions and 1 deletions

View File

@ -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?

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,5 +1,4 @@
- fix inconsistencies in nested create: do we need to submit the id or use in url?
- add stat stimulus controller to update stats
- default avatars
- add uuid/slug to characters and any other url-visible ids
- shared/private notes