157 lines
3.4 KiB
Crystal
157 lines
3.4 KiB
Crystal
require "./is_rollable"
|
|
require "./die"
|
|
require "./fixed_value"
|
|
require "./dice"
|
|
|
|
# `Roll` is a list of `Dice`.
|
|
#
|
|
# It is rollable, making the sum of each `Dice` values.
|
|
# It is also possible to get the details of a roll, using the methods
|
|
# `.min_details`, `.max_details`, `.average_details`, `.test_details`
|
|
#
|
|
# Example:
|
|
# ```
|
|
# r = Rollable.parse "1d6+2" # note: it also support "1d6 + 2"
|
|
# r.min # => 3
|
|
# r.max # => 9
|
|
# r.average # => 5.5
|
|
# r.test # => the sum of a random value in 1..6 and 2
|
|
# ```
|
|
class Rollable::Roll < Rollable::IsRollable
|
|
@dice : Array(Dice)
|
|
|
|
getter dice
|
|
|
|
def initialize(@dice)
|
|
end
|
|
|
|
def clone
|
|
Roll.new(@dice.clone)
|
|
end
|
|
|
|
delegate size, to: @dice
|
|
|
|
# Reverse the values of the `Roll`.
|
|
def reverse! : Roll
|
|
@dice.each { |die| die.reverse! }
|
|
self
|
|
end
|
|
|
|
# Return a reversed copy of the `Roll`'s values.
|
|
#
|
|
# Example:
|
|
# ```
|
|
# Roll.parse("1d6").reverse # => -1d6
|
|
# ```
|
|
def reverse : Roll
|
|
Roll.new @dice.map { |die| die.reverse }
|
|
end
|
|
|
|
{% for ft in ["min", "max", "test"] %}
|
|
def {{ ft.id }} : Int32
|
|
@dice.reduce(0) { |r, l| r + l.{{ ft.id }} }
|
|
end
|
|
|
|
def {{ (ft + "_details").id }} : Array(Int32)
|
|
@dice.map {|dice| dice.{{ (ft + "_details").id }} }.flatten
|
|
end
|
|
{% end %}
|
|
|
|
def average : Float64
|
|
@dice.reduce(0.0) { |r, l| r + l.average }
|
|
end
|
|
|
|
def average_details : Array(Float64)
|
|
@dice.map { |dice| dice.average_details }.flatten
|
|
end
|
|
|
|
def ==(right : Roll)
|
|
@dice.size == right.dice.size && @dice.map_with_index { |e, i| right.dice[i] == e }.all? { |e| e == true }
|
|
end
|
|
|
|
{% for op in [">", "<", ">=", "<="] %}
|
|
def {{ op.id }}(right : Roll)
|
|
average != right.average ?
|
|
average {{ op.id }} right.average :
|
|
max != right.max ?
|
|
max {{ op.id }} right.max :
|
|
min {{ op.id }} right.min
|
|
end
|
|
{% end %}
|
|
|
|
def <=>(right : Roll) : Int32
|
|
if average != right.average
|
|
average - right.average > 0 ? 1 : -1
|
|
elsif max != right.max
|
|
max - right.max <=> 0
|
|
else
|
|
min - right.min <=> 0
|
|
end
|
|
end
|
|
|
|
def order!
|
|
@dice.sort! { |a, b| b <=> a }
|
|
self
|
|
end
|
|
|
|
def order
|
|
clone.order!
|
|
end
|
|
|
|
# let a [1d6, 1d4, 1d6, 2, 2d6]
|
|
# first, we copy it
|
|
# for 1d6 we check evey d6 in the copy, fetch and delete them
|
|
def compact!
|
|
i = 0
|
|
until i >= @dice.size
|
|
# fetch the current dice
|
|
dice_current = @dice[i]
|
|
dice_type = dice_current.die
|
|
dice_count = dice_current.count
|
|
# fetch all dice with the same type
|
|
j = @dice.size
|
|
until i >= j - 1
|
|
j = j - 1
|
|
if @dice[j].die == dice_type
|
|
@dice[i].count += @dice[j].count
|
|
elsif @dice[j].die == dice_type.reverse
|
|
@dice[i].count -= @dice[j].count
|
|
else
|
|
next
|
|
end
|
|
@dice.delete_at j
|
|
end
|
|
i = i + 1
|
|
end
|
|
compact_fixed!
|
|
compact_empty!
|
|
self
|
|
end
|
|
|
|
private def compact_fixed!
|
|
fixed = @dice.map_with_index { |d, idx| {d, idx} }
|
|
fixed.select! { |t| t[0].die.fixed? }
|
|
idx = 0
|
|
fixed_dice = fixed.map do |t|
|
|
@dice.delete_at(t[1] - idx)
|
|
idx = idx + 1
|
|
t[0].max
|
|
end.sum
|
|
@dice << FixedValue.new_dice(fixed_dice) if fixed_dice != 0
|
|
end
|
|
|
|
private def compact_empty!
|
|
i = @dice.size - 1
|
|
until i < 0
|
|
@dice.delete_at(i) if @dice[i].count == 0
|
|
i = i - 1
|
|
end
|
|
end
|
|
|
|
def compact
|
|
clone.compact!
|
|
end
|
|
end
|
|
|
|
require "./roll/*"
|