Skip to content

Instantly share code, notes, and snippets.

@jeremyf
Created June 25, 2018 17:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeremyf/715fb116d2874e1b7218df789bf3099f to your computer and use it in GitHub Desktop.
Save jeremyf/715fb116d2874e1b7218df789bf3099f to your computer and use it in GitHub Desktop.
require 'forwardable'
gem 'dice_parser'
require 'dice'
# Responsible for registering random tables and exposing a means of rolling on those tables.
class TableRegistry
# @api public
# @example
# document = [{
# key: '1-a',
# roll: '2d6',
# entries: [
# { range: [2,3,4,5,6], roll_on: '1-b' },
# { range: [7], result: 'Yolo' },
# { range: [8,9,10,11,12], inner_table: {
# roll: '1d4',
# entries: [
# { range: [1,2,3], result: 'Yes' },
# { range: [4], result: 'No' }
# ]
# }
# }
# },{
# key: '1-b',
# roll: '1d6',
# entries: [
# { range: [1,2,3,4,5,6], result: 'sub-table' }
# ]
# }
# ]
# registry = TableRegistry.load(document)
#
# @example
# registry = TableRegistry.load do
# table('1') do
# roll('1d5')
# entry(1, 'Yes!')
# entry(2..5, 'No!')
# end
# end
# @return [TableRegistry, #roll_on]
def self.load(document = nil, context = self, &block)
if document
TableRegistry.new do |registry|
document.each do |data|
context.load_a_table(registry: registry, data: data, context: context)
end
end
else
TableRegistry.new(&block)
end
end
# @api private
def self.load_a_table(registry:, data:, context:, key: nil)
key ||= data.fetch(:key)
label = data.fetch(:label, key)
table = registry.table(key, label: label)
table.roll(data.fetch(:roll))
data.fetch(:entries).each do |table_entry|
range = table_entry.fetch(:range)
if table_entry.key?(:roll_on)
table.entry(range, roll_on: table_entry.fetch(:roll_on))
elsif table_entry.key?(:result)
table.entry(range, table_entry.fetch(:result))
elsif table_entry.key?(:inner_table)
inner_table = table_entry.fetch(:inner_table)
entry = table.entry(range, inner_table: true)
TableRegistry.load_a_table(registry: registry, data: inner_table, context: context, key: entry.key)
end
end
end
attr_reader :table_set
def initialize(&block)
@table_set = TableSet.new(randomizer: self)
instance_exec(self, &block) if block_given?
end
# @api public
# @param key [String] The key of the table you want to roll on
# @see TableRegistry::Table#key for details
# @todo Consider adding a modifier (eg. `roll_on(key, with: -2)`)
def roll_on(key)
@table_set.roll_on(key)
end
def table(*args, &block)
@table_set.add(*args, &block)
end
extend Forwardable
def_delegator :table_set, :render
class TableSet
extend Forwardable
def initialize(randomizer:)
@randomizer = randomizer
@tables = {}
end
def roll_on(table_name)
table = @tables.fetch(table_name)
table.roll!
end
def add(key, label: key, &block)
@tables[key] = Table.new(table_set: self, key: key, label: label, &block)
end
def render(debug: false)
puts "Table Set { object_id: #{object_id} }\n" if debug
@tables.sort { |a,b| a[0] <=> b[0] }.each do |key, table|
table.render
end
end
end
private_constant :TableSet
class Table
attr_reader :key, :table_set, :label
def initialize(table_set:, key:, label:, &block)
@key = key
@table_set = table_set
@label = label
@range_set = RangeSet.new(table: self)
instance_exec(self, &block) if block_given?
end
def render
if label == key
puts "Table: #{key}"
else
puts "Table: #{key} - #{label}"
end
@roller.render
@range_set.render
puts ""
end
def roll!
roll = @roller.roll!
@range_set.resolve(roll: roll)
end
def roll(text)
@roller = Roller.new(text)
end
def entry(range, result = nil, roll_on: nil, inner_table: nil, &inner_table_config)
@range_set.add(table: self, range: range, result: result, roll_on: roll_on, inner_table: inner_table, inner_table_config: inner_table_config)
end
protected
class Roller
def initialize(text)
@text = text
end
def roll!
Dice.roll(@text)
end
def render
puts "#{@text}\tResult"
end
end
private_constant :Roller
class RangeSet
def initialize(table:)
@table = table
@ranges = []
end
def render
@ranges.sort.each do |range|
range.render
end
end
def resolve(roll:)
@ranges.detect { |range| range.include?(roll) }.roll!
end
def add(range:, **kwargs)
range_object = Range.new(range: range, **kwargs)
@ranges << range_object
range_object
end
end
private_constant :RangeSet
module Range
def self.new(table:, range:, result:, roll_on:, inner_table:, inner_table_config:)
if result
Result.new(table: table, range: range, result: result)
elsif roll_on
RollOn.new(table: table, range: range, roll_on: roll_on)
elsif inner_table
InnerRollOn.new(table: table, range: range)
elsif inner_table_config
InnerTable.new(table: table, range: range, inner_table: inner_table_config)
else
raise "Hello"
end
end
class Base
attr_reader :table, :range
def initialize(table:, range:)
@table = table
self.range = range
end
def key
"#{table.key} (Sub #{range})"
end
extend Comparable
def <=>(other)
range.first <=> other.range.first
end
def render
if range.first == range.last
puts "#{range.first}\t#{result}"
else
puts "#{range.first}-#{range.last}\t#{result}"
end
end
def result
raise NotImplementedError
end
extend Forwardable
def_delegator :table, :table_set
def include?(value)
if @range.respond_to?(:include?)
@range.include?(value)
else
@range == value
end
end
def range=(input)
@range = Array(input)
end
end
private_constant :Base
class Result < Base
attr_reader :result
def initialize(result:, **kwargs)
super(**kwargs)
@result = result
end
def roll!
@result
end
end
private_constant :Result
class RollOn < Base
def initialize(roll_on:, **kwargs)
super(**kwargs)
@roll_on = roll_on
end
def roll!
table_set.roll_on(@roll_on)
end
def result
"Roll on #{@roll_on}"
end
end
private_constant :RollOn
class InnerRollOn < Base
def roll!
table_set.roll_on(key)
end
def result
"Roll on #{key}"
end
end
class InnerTable < Base
def initialize(inner_table:, **kwargs)
super(**kwargs)
@table.table_set.add(key, &inner_table)
end
def roll!
table_set.roll_on(key)
end
def result
"Roll on #{key}"
end
end
private_constant :InnerTable
end
private_constant :Range
end
private_constant :Table
end
# Load a JSON document
document = [
{
key: '1-a',
roll: '2d6',
entries: [
{ range: (1..7), roll_on: '1-b' },
{ range: [7], result: 'Yolo' },
{ range: (8..12), inner_table: {
roll: '1d4',
entries: [
{ range: [1,2,3], result: 'Yes' },
{ range: [4], result: 'No' }
]
}
}
]
},{
key: '1-b',
roll: '1d6',
entries: [
{ range: [1,2,3,4,5,6], result: 'sub-table' }
]
}
]
registry = TableRegistry.load(document)
# Use Ruby to load table
registry = TableRegistry.load do
table("1-a") do
roll('2d6')
entry(2..6, roll_on: '1-b')
entry(7) do
roll('1d2')
entry(1, 'Inner Table 1')
entry(2, 'Inner Table 2')
end
entry(8..12, 'Other')
entry(13..24, 'Snork!')
end
table('1-b') do
roll('1d4')
entry(1, '1')
entry(2, '2')
entry(3, '3')
entry(4, '4')
end
end
registry = TableRegistry.load do
table('Random Background') do
roll('1d15')
entry(1, 'Acolyte')
entry(2, 'Charlatan')
entry(3, 'Criminal')
entry(4, 'Entertainer')
entry(5, 'Folk Hero')
entry(6, 'Guild Artisan')
entry(7, 'Hermit')
entry(8, 'Noble')
entry(9, 'Outlander')
entry(10, 'Sage')
entry(11, 'Sailor')
entry(12, 'Soldier')
entry(13, 'Urchin')
entry(14, 'Aarcheologist')
entry(15, 'Anthropologist')
end
end
puts registry.roll_on('Random Background')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment