# frozen_string_literal: true 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 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 roll = rand(1..die_type.to_i) result += roll dice << [ die_type, result ] end result end end