Created
June 25, 2018 17:43
-
-
Save jeremyf/715fb116d2874e1b7218df789bf3099f to your computer and use it in GitHub Desktop.
This file contains 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 '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