Created
May 7, 2014 16:03
-
-
Save zph/f08ce66e7b44183bb3b2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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