Skip to content

Commit

Permalink
Move complex die methods into themed helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
neilslater committed Oct 10, 2021
1 parent bc920f2 commit 747f634
Show file tree
Hide file tree
Showing 2 changed files with 238 additions and 220 deletions.
159 changes: 4 additions & 155 deletions lib/games_dice/complex_die.rb
Expand Up @@ -24,7 +24,9 @@ module GamesDice
# d.explain_result # => "[6+5] 11 Success"
#
class ComplexDie
include GamesDice::ComplexDieHelpers
include RollHelpers
include ProbabilityHelpers
include MinMaxHelpers

# @!visibility private
# arbitrary limit to speed up probability calculations. It should
Expand Down Expand Up @@ -109,17 +111,7 @@ def max
# 1e-9 at worst.
# @return [GamesDice::Probabilities] Probability distribution of die.
def probabilities
return @probabilities if @probabilities

@probabilities = if @rerolls && @maps
GamesDice::Probabilities.from_h(prob_hash_with_rerolls_and_maps)
elsif @rerolls
GamesDice::Probabilities.from_h(recursive_probabilities)
elsif @maps
GamesDice::Probabilities.from_h(prob_hash_with_just_maps)
else
@basic_die.probabilities
end
@probabilities ||= calculate_probabilities
end

# Simulates rolling the die
Expand All @@ -134,84 +126,6 @@ def roll(reason = :basic)

private

def prob_hash_with_rerolls_and_maps
reroll_probs = recursive_probabilities
prob_hash = {}
reroll_probs.each do |v, p|
add_mapped_to_prob_hash(prob_hash, v, p)
end
prob_hash
end

def prob_hash_with_just_maps
prob_hash = {}
@basic_die.probabilities.each do |v, p|
add_mapped_to_prob_hash(prob_hash, v, p)
end
prob_hash
end

def add_mapped_to_prob_hash(prob_hash, orig_val, prob)
mapped_val, = calc_maps(orig_val)
prob_hash[mapped_val] ||= 0.0
prob_hash[mapped_val] += prob
end

def roll_apply_rerolls
return unless @rerolls

subtracting = false
rerolls_remaining = @rerolls.map(&:limit)

rerolls_loop(subtracting, rerolls_remaining)
end

def rerolls_loop(subtracting, rerolls_remaining)
loop do
rule_idx = find_matching_reroll_rule(@basic_die.result, @result.rolls.length, rerolls_remaining)
break unless rule_idx

rule = @rerolls[rule_idx]
rerolls_remaining[rule_idx] -= 1
subtracting = true if rule.type == :reroll_subtract
roll_apply_reroll_rule rule, subtracting
end
end

def roll_apply_reroll_rule(rule, is_subtracting)
# Apply the rule (note reversal for additions, after a subtract)
if is_subtracting && rule.type == :reroll_add
@result.add_roll(@basic_die.roll, :reroll_subtract)
else
@result.add_roll(@basic_die.roll, rule.type)
end
end

# Find which rule, if any, is being triggered
def find_matching_reroll_rule(check_value, num_rolls, rerolls_remaining)
@rerolls.zip(rerolls_remaining).find_index do |rule, remaining|
next if rule.type == :reroll_subtract && num_rolls > 1

remaining.positive? && rule.applies?(check_value)
end
end

def roll_apply_maps
return unless @maps

m, n = calc_maps(@result.value)
@result.apply_map(m, n)
end

def calc_minmax
@min_result = probabilities.min
@max_result = probabilities.max
return if @probabilities_complete

logical_min, logical_max = logical_minmax
@min_result, @max_result = [@min_result, @max_result, logical_min, logical_max].minmax
end

def construct_rerolls(rerolls_input)
check_and_construct rerolls_input, GamesDice::RerollRule, 'rerolls'
end
Expand All @@ -233,70 +147,5 @@ def check_and_construct(input, klass, label)
end
end
end

def calc_maps(original_value)
y = 0
n = ''
@maps.find do |rule|
if (maybe_y = rule.map_from(original_value))
y = maybe_y
n = rule.mapped_name
end
maybe_y
end
[y, n]
end

def minmax_mappings(possible_values)
possible_values.map do |x|
map_val, = calc_maps(x)
map_val
end.minmax
end

# This isn't 100% accurate, but does cover most "normal" scenarios, and we're only falling back to it when we have
# to. The inaccuracy is that min_result..max_result may contain 'holes' which have extreme map values that cannot
# actually occur. In practice it is likely a non-issue unless someone went out of their way to invent a dice scheme
# that broke it.
def logical_minmax
return @basic_die.minmax unless @rerolls || @maps
return minmax_mappings(@basic_die.all_values) unless @rerolls

min_result, max_result = logical_rerolls_minmax
return minmax_mappings((min_result..max_result)) if @maps

[min_result, max_result]
end

def logical_rerolls_minmax
min_result = @basic_die.min
max_result = @basic_die.max
min_subtract = find_minimum_possible_subtract
max_add = find_maximum_possible_adds
min_result = [min_subtract - max_add, min_subtract - max_result].min if min_subtract
[min_result, max_add + max_result]
end

def find_minimum_possible_subtract
min_subtract = nil
@rerolls.select { |r| r.type == :reroll_subtract }.each do |rule|
min_reroll = @basic_die.all_values.select { |v| rule.applies?(v) }.min
next unless min_reroll

min_subtract = [min_reroll, min_subtract].compact.min
end
min_subtract
end

def find_maximum_possible_adds
total_add = 0
@rerolls.select { |r| r.type == :reroll_add }.each do |rule|
max_reroll = @basic_die.all_values.select { |v| rule.applies?(v) }.max
next unless max_reroll

total_add += max_reroll * rule.limit
end
total_add
end
end
end

0 comments on commit 747f634

Please sign in to comment.