# frozen_string_literal: true require "dice" class DiceRoller attr_reader :dice attr_reader :result def initialize(roll_command, stat: nil) @roll_command = roll_command @stat = stat&.value @dice = [] end def roll roll_command = parse_roll_command_references(roll_command) Dice.roll(roll_command) 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_delimiters [ "+", "-", "*", "/" ] 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 roll = rand(1..die_type.to_i) result += roll dice << [ die_type, result ] end result end def parse_roll_command_references(roll_command) roll_command = @roll_command.include?("self") ? @roll_command.gsub("self", @stat.to_s) : @roll_command roll_command.split(Regexp.union(roll_delimiters)).each do |part| next if part.match?(/\A\d*\z/) next if part.match?(/\A\d*d\d*\z/) value = CharacterSheetFeature.find_by(slug: part).featurable.value raise "#{part} not found" if value.blank? roll_command.gsub!(part, value.to_s) end roll_command end end