Skip to content

Instantly share code, notes, and snippets.

@zph
Created May 7, 2014 16:03
Show Gist options
  • Select an option

  • Save zph/f08ce66e7b44183bb3b2 to your computer and use it in GitHub Desktop.

Select an option

Save zph/f08ce66e7b44183bb3b2 to your computer and use it in GitHub Desktop.
require 'singleton' # => true
# module Kernel
# def d
# DiceBag.instance
# end
# end
module Dice
class String
attr_reader :string, :array # => nil
def initialize(str)
@string = str
end
def self.transform(str)
s = new(str)
s.remove_spaces
s.remove_brackets
s.split
s.format_each_die
s.reassemble_die_format
end
def remove_spaces
string.gsub!(/\s+/, '')
end
def remove_brackets
string.gsub!(/[\[\]]/, '')
end
def split(c = '+')
@array = string.split(c)
end
def format_each_die
@array.map! do |i|
how_many, die_value = i.split('d')
how_many = how_many.to_i
[how_many, die_value]
end
end
def reassemble_die_format
@array.map do |count, value|
([ "d^#{value}" ] * count).join(',')
end.join(',')
end
end
end
class DiceDSL
DIE_REGEX = %r{^
(?<top_or_bottom>t|b)? # Flag for finding the top or bottom dice
(?<tb_number>\d)? # Number of top or bottom dice
\[(?<dice_string>.*)\] # The main dice string, to be parsed later
(?<explode>\*)? # Reroll (explode) die rolls? (i.e. 6 on 1d6)
(?<plus>(\+|\-)\d+)? # Additional points to add to roll (or subtract)
\=? # = Target Number or range follows...
(?<target>.*)?$ # The target number (single 4, range 4..6, above 4+, below 4-)
}x # => /^\n (?<top_or_bottom>t|b)? # Flag for finding the top or bottom dice\n (?<tb_number>\d)? # Number of top or bottom dice\n \[(?<dice_string>.*)\] # The main dice string, to be parsed later\n (?<explode>\*)? # Reroll (explode) die rolls? (i.e. 6 on 1d6)\n (?<plus>(\+|\-)\d+)? # Additional points to add to roll (or subtract)\n \=? # = Target Number or range follows...\n (?<target>.*)?$ # The target number (single 4, range 4..6, above 4+, below 4-)\n /x
DIE_EXTRUDER = %r{\[(.*)\]} # => /\[(.*)\]/
class << self
def get_dies(dices)
dices.match(DIE_EXTRUDER)[1].split('+') # => ["5d8", "1d4"]
end
def parse(dices)
dices.match(DIE_REGEX)
die_set = "[" + string_to_dice_array(dice) + "]"
dice_pool({set: die_set, top: tb_number.to_i, plus: plus.to_i})
end
def dice_pool(*args)
DicePool.new(*args)
end
def string_to_dice_array(dice)
Dice::String.transform(dice)
end
end
end
DiceDSL.parse("3[5d8+1d4]") # => ["5d8", "1d4"]
# "3[5d8]".match(/\[(.*)\]/) # => #<MatchData "[5d8]" 1:"5d8">
class DiceBag
include Singleton # => DiceBag
def ^(max)
@max = max
Die.new(sides: max)
end
def roll(what: self, plus: 0, keep: nil, how_many: nil)
puts "Rolling Away..."
if what.is_a? Array
keep = what.length if keep.nil?
DiceSet.new set: what, top: keep, plus: plus
elsif what.is_a? Die
raise "Must include :how_many if you pass an individual Die" if how_many.nil?
keep = how_many if keep.nil?
DicePool.new set: how_many.times.collect{ what.clone.reroll }, top: keep, plus: plus
else
raise what.class.inspect
end
end
end
class Die
def initialize(sides: 6, plus: 0)
@plus = plus
@sides = sides
roll
end
def +(x)
@plus = x
reroll
end
def inspect; "d#{@sides}#{"+#{@plus}" unless @plus.zero?}=>#{value}"; end
def value; @value; end
def roll; @value = rand(1..@sides)+@plus; self end
def reroll; roll; end
def to_s; value; end
def to_i; value; end
end
class DicePool
attr_reader :how_many, :top_number_of_dice, :results # => nil
attr_writer :how_many, :top_number_of_dice, :results # => nil
def initialize(set: nil, plus: 0, top: 2)
@set = set.is_a?(Die) ? [set] : set
raise 'Dice passed to DicePool must be instances of Die.' unless @set.any?{|s| s.is_a?(Die) }
@plus = plus
@how_many = 100_000
@top_number_of_dice = top
generate_results
end
def highest(top: @top_number_of_dice, re_roll: false)
reroll if re_roll
highest_results = @set.sort_by{|d| d.value }.reverse[0..top-1]
return highest_results.map(&:to_i).inject(0){|sum,i| sum += i } + @plus
end
def reroll
@set.map(&:reroll)
self
end
def roll; reroll; end
def generate_results
@results = Hash.new(0)
@how_many.times { @results[highest(top: @top_number_of_dice, re_roll: true)] += 1 }
self
end
def graph(r=@results)
raise "There are no statistical results to graph. Run :generate_results" if r.empty?
data = ""
data << "Running #{@how_many} times, taking the top #{@top_number_of_dice}, the breakdown of results by value rolled are:"
data << "\n" + ("…" * 100) + "\n"
r.sort.each do |total, count|
percentage = (count.to_f/@how_many.to_f)
data << sprintf("%3d ", total)
data << '('
data << sprintf("%5.2f", percentage * 100)
data << "%) "
dots = (percentage*500).to_i
if dots > 100
data << '°' * 80
data << "*(#{dots})"
else
data << '°' * (percentage*500).to_i
end
data << "\n"
end
data
end
def results
"results: #{(@results.sort_by {|k,v| k }).to_h.inspect}"
end
def total
# @set.map(&:value).inject(0){|sum, i| sum += i} + @plus
highest(top: @top_number_of_dice)
end
def inspect
puts "<DicePool:\n @set: #{@set.inspect}, @plus: #{@plus}, @total: #{total}, @top: #{@top_number_of_dice}>"
end
def self.inspect
puts "<DicePool:\n @set: #{@set.inspect}, @plus: #{@plus}, @total: #{total}, @top: #{@top_number_of_dice}>"
end
end
# Dice = DiceBag.instance # => #<DiceBag:0x007fd2e1905e50>
# str = "5d6" # => "5d6"
# str = 'b3[4d6]+2=18+' # => "b3[4d6]+2=18+"
# DiceDSL.string_to_dice_array(str) # => ",d^,d^"
# ds=DiceDSL.parse("3[4d6]"); [ds.reroll.total, ds.reroll.total, ds.reroll.total, ds.reroll.total, ds.reroll.total, ds.reroll.total]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment