fifthed_sim 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +0 -1
- data/HelpWanted.md +23 -0
- data/README.md +144 -5
- data/bin/console +2 -8
- data/bin/playground +42 -0
- data/exe/diceroll +9 -0
- data/fifthed_sim.gemspec +4 -0
- data/lib/fifthed_sim/actor.rb +80 -0
- data/lib/fifthed_sim/attack.rb +86 -0
- data/lib/fifthed_sim/calculated_fixnum.rb +57 -0
- data/lib/fifthed_sim/compiler/parser.rb +46 -0
- data/lib/fifthed_sim/compiler/transform.rb +28 -0
- data/lib/fifthed_sim/compiler.rb +51 -0
- data/lib/fifthed_sim/damage.rb +54 -0
- data/lib/fifthed_sim/damage_types.rb +39 -0
- data/lib/fifthed_sim/dice_expression.rb +134 -0
- data/lib/fifthed_sim/distribution.rb +219 -20
- data/lib/fifthed_sim/nodes/addition_node.rb +75 -0
- data/lib/fifthed_sim/nodes/block_node.rb +46 -0
- data/lib/fifthed_sim/nodes/division_node.rb +26 -0
- data/lib/fifthed_sim/nodes/greater_node.rb +41 -0
- data/lib/fifthed_sim/nodes/less_node.rb +46 -0
- data/lib/fifthed_sim/nodes/multi_node.rb +135 -0
- data/lib/fifthed_sim/nodes/multiplication_node.rb +25 -0
- data/lib/fifthed_sim/nodes/number_node.rb +38 -0
- data/lib/fifthed_sim/nodes/roll_node.rb +88 -0
- data/lib/fifthed_sim/nodes/subtraction_node.rb +24 -0
- data/lib/fifthed_sim/roll_repl.rb +117 -0
- data/lib/fifthed_sim/spell.rb +74 -0
- data/lib/fifthed_sim/stat.rb +49 -0
- data/lib/fifthed_sim/stat_block.rb +57 -0
- data/lib/fifthed_sim/version.rb +3 -1
- data/lib/fifthed_sim.rb +28 -4
- metadata +74 -8
- data/lib/fifthed_sim/dice_calculation.rb +0 -88
- data/lib/fifthed_sim/dice_result.rb +0 -108
- data/lib/fifthed_sim/die_roll.rb +0 -66
- data/lib/fifthed_sim/helpers/average_comparison.rb +0 -14
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative '../dice_expression'
|
2
|
+
require_relative '../calculated_fixnum'
|
3
|
+
|
4
|
+
module FifthedSim
|
5
|
+
class LessNode < DiceExpression
|
6
|
+
using CalculatedFixnum
|
7
|
+
|
8
|
+
def initialize(lhs, rhs)
|
9
|
+
@lhs, @rhs = lhs, rhs
|
10
|
+
end
|
11
|
+
|
12
|
+
def value
|
13
|
+
[@lhs.value, @rhs.value].min
|
14
|
+
end
|
15
|
+
|
16
|
+
def reroll
|
17
|
+
self.class.new(@lhs.reroll, @rhs.reroll)
|
18
|
+
end
|
19
|
+
|
20
|
+
def max
|
21
|
+
[@lhs.max, @rhs.max].min
|
22
|
+
end
|
23
|
+
|
24
|
+
def min
|
25
|
+
[@lhs.min, @rhs.min].min
|
26
|
+
end
|
27
|
+
|
28
|
+
def distribution
|
29
|
+
@lhs.distribution.convolve_least(@rhs.distribution)
|
30
|
+
end
|
31
|
+
|
32
|
+
def equation_representation
|
33
|
+
"min(#{@lhs.equation_representation}, #{@rhs.equation_representation})"
|
34
|
+
end
|
35
|
+
|
36
|
+
def value_equation(terminal: false)
|
37
|
+
lhs = @lhs.value_equation(terminal: terminal)
|
38
|
+
rhs = @rhs.value_equation(terminal: terminal)
|
39
|
+
"min(#{lhs}, #{rhs})"
|
40
|
+
end
|
41
|
+
|
42
|
+
def expression_equation
|
43
|
+
"min(#{@lhs.expression_equation}, #{@rhs.expression_equation})"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require_relative '../distribution'
|
2
|
+
require_relative '../dice_expression'
|
3
|
+
|
4
|
+
##
|
5
|
+
# We sneakily monkey-patch Fixnum here, to allow us to use a nice syntax
|
6
|
+
# for factorials.
|
7
|
+
#
|
8
|
+
# We only do this if somebody hasn't done it already, in case ruby adds this to the standard one day.
|
9
|
+
class Fixnum
|
10
|
+
unless self.instance_methods(:false).include?(:factorial)
|
11
|
+
##
|
12
|
+
# Mathematical factorial
|
13
|
+
def factorial
|
14
|
+
(1..self).inject(:*) || 1
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
##
|
21
|
+
# Mathemetical combination
|
22
|
+
def combination(n, r)
|
23
|
+
n.factorial / (r.factorial * (n - r).factorial)
|
24
|
+
end
|
25
|
+
|
26
|
+
module FifthedSim
|
27
|
+
##
|
28
|
+
# This class models the result of a roll of multiple dice.
|
29
|
+
# It is filled with the actual result of randomly-rolled dice, but contains
|
30
|
+
# methods to enable the calculation of average values.
|
31
|
+
class MultiNode < DiceExpression
|
32
|
+
def self.d(num, type)
|
33
|
+
self.new(num.times.map{RollNode.roll(type)})
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Generally, don't calculate this yourself
|
38
|
+
def initialize(array)
|
39
|
+
unless array.is_a?(Array) && ! array.empty?
|
40
|
+
raise ArgumentError, "Not a valid array"
|
41
|
+
end
|
42
|
+
unless array.all?{|elem| elem.is_a?(RollNode) }
|
43
|
+
raise ArgumentError, "Not all die rolls"
|
44
|
+
end
|
45
|
+
@array = array
|
46
|
+
end
|
47
|
+
|
48
|
+
def reroll
|
49
|
+
self.class.new(@array.map(&:reroll))
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Did any of our dice crit?
|
54
|
+
def has_crit?
|
55
|
+
@array.any?(&:crit?)
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Did any of our dice critically fail?
|
60
|
+
def has_critfail?
|
61
|
+
@array.any?(&:critfail?)
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# What is the theoretical average value when we roll this many dice?
|
66
|
+
def average
|
67
|
+
@array.map(&:average).inject(:+)
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Calculate the value of these dice
|
72
|
+
def value
|
73
|
+
@array.map(&:value).inject(:+)
|
74
|
+
end
|
75
|
+
|
76
|
+
##
|
77
|
+
# How many dice did we roll?
|
78
|
+
def roll_count
|
79
|
+
@array.count
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# What kind of dice did we roll?
|
84
|
+
def dice_type
|
85
|
+
@array.first.type
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# The minimum value we could have rolled
|
90
|
+
def min_value
|
91
|
+
roll_count
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# The maximum value we could have rolled
|
96
|
+
def max_value
|
97
|
+
dice_type * roll_count
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# Obtain a probability distribution for when we roll this many dice.
|
102
|
+
# This is an instnace of the Distribution class.
|
103
|
+
def distribution
|
104
|
+
total_possible = (dice_type ** roll_count)
|
105
|
+
mapped = min_value.upto(max_value).map do |k|
|
106
|
+
[k, (occurences(k) / total_possible.to_f)]
|
107
|
+
end
|
108
|
+
Distribution.new(Hash[mapped])
|
109
|
+
end
|
110
|
+
|
111
|
+
def value_equation(terminal: false)
|
112
|
+
"(" + @array.map do |a|
|
113
|
+
a.value_equation(terminal: terminal)
|
114
|
+
end.join(", ") + ")"
|
115
|
+
end
|
116
|
+
|
117
|
+
def expression_equation
|
118
|
+
"(" + @array.map do |a|
|
119
|
+
a.expression_equation
|
120
|
+
end.join(", ") + ")"
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def occurences(num)
|
126
|
+
dice_type = @array.first.type
|
127
|
+
num_dice = @array.size
|
128
|
+
0.upto((num - num_dice) / dice_type).map do |k|
|
129
|
+
((-1) ** k) *
|
130
|
+
combination(num_dice, k) *
|
131
|
+
combination(num - (dice_type*k) - 1, num_dice- 1)
|
132
|
+
end.inject(:+)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative '../dice_expression'
|
2
|
+
|
3
|
+
module FifthedSim
|
4
|
+
class MultiplicationNode < DiceExpression
|
5
|
+
using CalculatedFixnum
|
6
|
+
def initialize(lhs, rhs)
|
7
|
+
@lhs = lhs
|
8
|
+
@rhs = rhs
|
9
|
+
end
|
10
|
+
|
11
|
+
def value
|
12
|
+
@lhs.value * @rhs.value
|
13
|
+
end
|
14
|
+
|
15
|
+
def distribution
|
16
|
+
@lhs.distribution.convolve_multiply(@rhs.distribution)
|
17
|
+
end
|
18
|
+
|
19
|
+
def reroll
|
20
|
+
self.class.new(@lhs.reroll, @rhs.reroll)
|
21
|
+
end
|
22
|
+
|
23
|
+
define_binary_op_equations "*"
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative '../dice_expression'
|
2
|
+
|
3
|
+
module FifthedSim
|
4
|
+
##
|
5
|
+
# Normally we handle numbers by use of a refinement on Fixnum
|
6
|
+
# However, in some cases, we may have the fixnum as the *start* of an expression.
|
7
|
+
# In this case, we have a problem, because Fixnum#+ is not overloaded to return a DiceNode.
|
8
|
+
# In this case, we must use this, a NumberNode.
|
9
|
+
# NumberNodes wrap a number.
|
10
|
+
class NumberNode < DiceExpression
|
11
|
+
def initialize(arg)
|
12
|
+
unless arg.is_a? Fixnum
|
13
|
+
raise ArgumentError, "#{arg.inspect} is not a fixnum"
|
14
|
+
end
|
15
|
+
@value = arg
|
16
|
+
end
|
17
|
+
|
18
|
+
def distribution
|
19
|
+
Distribution.for(@value)
|
20
|
+
end
|
21
|
+
|
22
|
+
def value
|
23
|
+
@value
|
24
|
+
end
|
25
|
+
|
26
|
+
def reroll
|
27
|
+
self.class.new(@value)
|
28
|
+
end
|
29
|
+
|
30
|
+
def value_equation(terminal: false)
|
31
|
+
@value.to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
def expression_equation
|
35
|
+
@value.to_s
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require_relative '../dice_expression.rb'
|
2
|
+
|
3
|
+
module FifthedSim
|
4
|
+
##
|
5
|
+
# Model a single roll of the dice.
|
6
|
+
# Users of the library will rarely interact with this class, and will instead manpiulate values based on the DiceResult type.
|
7
|
+
#
|
8
|
+
class RollNode < DiceExpression
|
9
|
+
|
10
|
+
##
|
11
|
+
# Create a diceresult by rolling a certain type.
|
12
|
+
def self.roll(type)
|
13
|
+
raise ArgumentError, "Must be an Integer" unless type.is_a? Fixnum
|
14
|
+
self.new(SecureRandom.random_number(type) + 1, type)
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Obtain a DieRoll filled with the average result of this die type
|
19
|
+
# This will round down.
|
20
|
+
def self.average(type)
|
21
|
+
self.new((type + 1) / 2, type)
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Obtain an average value for this die type, as a float
|
26
|
+
# We're extremely lazy here.
|
27
|
+
def self.average_value(type)
|
28
|
+
self.new(1, type).average
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(val, type)
|
32
|
+
unless val.is_a?(Fixnum) && type.is_a?(Fixnum)
|
33
|
+
raise ArgumentError, "Type invald"
|
34
|
+
end
|
35
|
+
@value = val
|
36
|
+
@type = type
|
37
|
+
end
|
38
|
+
|
39
|
+
def reroll
|
40
|
+
self.class.roll(@type)
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :value, :type
|
44
|
+
|
45
|
+
##
|
46
|
+
# The average roll for a die of this type
|
47
|
+
def average
|
48
|
+
(@type + 1) / 2.0
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# How far away this roll is from the average roll
|
53
|
+
def difference_from_average
|
54
|
+
@value - average
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Is this roll a critical failure? (AKA, is it a 1?)
|
59
|
+
def critfail?
|
60
|
+
@value == 1
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Is this roll a critical? (AKA, is it the max value of the dice?)
|
65
|
+
def crit?
|
66
|
+
@value == @type
|
67
|
+
end
|
68
|
+
|
69
|
+
def distribution
|
70
|
+
Distribution.for((1..@type))
|
71
|
+
end
|
72
|
+
|
73
|
+
def value_equation(terminal: false)
|
74
|
+
return value.to_s unless terminal
|
75
|
+
if critfail?
|
76
|
+
Rainbow(value.to_s).color(:red).bright.to_s
|
77
|
+
elsif crit?
|
78
|
+
Rainbow(value.to_s).color(:yellow).bright.to_s
|
79
|
+
else
|
80
|
+
value.to_s
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def expression_equation
|
85
|
+
"d#{@type}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative '../dice_expression'
|
2
|
+
module FifthedSim
|
3
|
+
class SubtractionNode < DiceExpression
|
4
|
+
using CalculatedFixnum
|
5
|
+
def initialize(lhs, rhs)
|
6
|
+
@lhs = lhs
|
7
|
+
@rhs = rhs
|
8
|
+
end
|
9
|
+
|
10
|
+
def value
|
11
|
+
@lhs - @rhs
|
12
|
+
end
|
13
|
+
|
14
|
+
def reroll
|
15
|
+
self.class.new(@lhs.reroll, @rhs.reroll)
|
16
|
+
end
|
17
|
+
|
18
|
+
def distribution
|
19
|
+
@lhs.distribution.convolve_subtract(@rhs.distribution)
|
20
|
+
end
|
21
|
+
|
22
|
+
define_binary_op_equations "-"
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module FifthedSim
|
2
|
+
class RollRepl
|
3
|
+
def initialize(inspect = true, errors = false)
|
4
|
+
@inspect = inspect
|
5
|
+
@errors = errors
|
6
|
+
end
|
7
|
+
|
8
|
+
def run(kill_on_interrupt = false)
|
9
|
+
begin
|
10
|
+
while buf = Readline.readline("> ", true)
|
11
|
+
run_cmd(buf)
|
12
|
+
kill_on_interrupt = false
|
13
|
+
end
|
14
|
+
rescue Interrupt
|
15
|
+
self.exit if kill_on_interrupt
|
16
|
+
puts "Control-C again or 'Exit' to quit"
|
17
|
+
run(true)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def info(cmd)
|
22
|
+
s = cmd.gsub(/info/, "").chomp
|
23
|
+
if s.length > 0
|
24
|
+
run_command(s)
|
25
|
+
end
|
26
|
+
return error_msg("Have nothing to get info of") unless @last_roll
|
27
|
+
lb = ->(x){Rainbow(x).color(:yellow).bright.to_s + ": "}
|
28
|
+
puts (%i(max min percentile).map do |p|
|
29
|
+
lb[p] + @last_roll.public_send(p).to_s
|
30
|
+
end.inject{|m, x| m + ", " + x})
|
31
|
+
end
|
32
|
+
|
33
|
+
def reroll
|
34
|
+
return error_msg("Nothing to reroll") unless @last_roll
|
35
|
+
display_roll(@last_roll.reroll)
|
36
|
+
end
|
37
|
+
|
38
|
+
def run_cmd(cmd)
|
39
|
+
case cmd
|
40
|
+
when /(quit|exit|stop)/
|
41
|
+
self.exit
|
42
|
+
when "rr", "reroll"
|
43
|
+
self.reroll
|
44
|
+
when /info/
|
45
|
+
|
46
|
+
self.info(cmd)
|
47
|
+
when "help"
|
48
|
+
self.help
|
49
|
+
when "inspect"
|
50
|
+
toggle_inspect
|
51
|
+
when "errors"
|
52
|
+
toggle_errors
|
53
|
+
else
|
54
|
+
self.roll(cmd)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def roll(cmd)
|
59
|
+
r = DiceExpression(cmd)
|
60
|
+
@last_roll = r
|
61
|
+
display_roll(r)
|
62
|
+
rescue FifthedSim::Compiler::CompileError => e
|
63
|
+
if e.char
|
64
|
+
display_compile_error(e, cmd)
|
65
|
+
else
|
66
|
+
error_msg("Could not parse expression!")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def display_roll(r)
|
71
|
+
if @inspect
|
72
|
+
puts " = " + r.value_equation(terminal: true)
|
73
|
+
puts " => " + Rainbow(r.value.to_s).underline.bright.to_s
|
74
|
+
else
|
75
|
+
puts r.value.to_s
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def error_msg(msg)
|
80
|
+
puts Rainbow(msg).color(:red).bright
|
81
|
+
end
|
82
|
+
|
83
|
+
def display_compile_error(err, cmd)
|
84
|
+
if @errors
|
85
|
+
error_msg("Could not read line: #{err.tree_cause}")
|
86
|
+
else
|
87
|
+
error_msg("Could not read line: #{err.message}")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
[:inspect, :errors].each do |m|
|
92
|
+
send(:define_method, "toggle_#{m}") do
|
93
|
+
t = instance_variable_get("@#{m}")
|
94
|
+
puts "#{m}: #{t.inspect} => #{(!t).inspect}"
|
95
|
+
instance_variable_set("@#{m}", !t)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def exit
|
100
|
+
puts "Goodbye."
|
101
|
+
exit!
|
102
|
+
end
|
103
|
+
|
104
|
+
def help
|
105
|
+
cmd = ->(a){ Rainbow(a.to_s).bright.underline + ":" }
|
106
|
+
puts %Q{COMMANDS:
|
107
|
+
#{cmd[:help]} display this message
|
108
|
+
#{cmd[:inspect]} toggle equation inspection
|
109
|
+
#{cmd[:quit]} exit the roller
|
110
|
+
#{cmd[:errors]} toggle in-depth compile errors for dice expressions
|
111
|
+
#{cmd["arrow keys"]} navigate like GNU readline
|
112
|
+
#{cmd[:info]} get info about the previous roll, or a roll on this line.
|
113
|
+
#{cmd["rr, reroll"]} reroll the previous dice
|
114
|
+
}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module FifthedSim
|
2
|
+
##
|
3
|
+
# Spells model save-or-take-damage stuff.
|
4
|
+
# At some point in the future I hope to modify them so they work as other stu
|
5
|
+
class Spell
|
6
|
+
class DefinitionProxy
|
7
|
+
##
|
8
|
+
def initialize(name, &block)
|
9
|
+
@hash = {
|
10
|
+
name: name
|
11
|
+
}
|
12
|
+
instance_eval(&block)
|
13
|
+
end
|
14
|
+
|
15
|
+
%i(damage save_damage).each do |m|
|
16
|
+
self.send(:define_method, m) do |damage = nil, &block|
|
17
|
+
if block
|
18
|
+
@hash[m] = Damage.define(&block)
|
19
|
+
elsif damage.is_a?(Damage)
|
20
|
+
@hash[m] = damage
|
21
|
+
else
|
22
|
+
raise ArgumentError, "#{damage} is not damage!"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def save_dc(n)
|
28
|
+
@hash[:save_dc] = n
|
29
|
+
end
|
30
|
+
|
31
|
+
def save_type(n)
|
32
|
+
@hash[:save_type] = n
|
33
|
+
end
|
34
|
+
|
35
|
+
def attrs
|
36
|
+
@hash
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.define(name, &block)
|
41
|
+
h = DefinitionProxy.new(name, &block).attrs
|
42
|
+
self.new(h)
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(hash)
|
46
|
+
@name = hash[:name]
|
47
|
+
@damage = hash[:damage]
|
48
|
+
@save_damage = hash[:save_damage]
|
49
|
+
@save_type = hash[:save_type]
|
50
|
+
@save_dc = hash[:save_dc]
|
51
|
+
end
|
52
|
+
|
53
|
+
attr_reader :save_dc,
|
54
|
+
:save_type
|
55
|
+
|
56
|
+
def against(other)
|
57
|
+
other.saving_throw(@save_type).test_then do |res|
|
58
|
+
if res >= @save_dc
|
59
|
+
@save_damage.to(other)
|
60
|
+
else
|
61
|
+
@damage.to(other)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def raw_damage
|
67
|
+
@damage.raw
|
68
|
+
end
|
69
|
+
|
70
|
+
def raw_save_damage
|
71
|
+
@save_damage.raw
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module FifthedSim
|
2
|
+
class Stat
|
3
|
+
class DefinitionProxy
|
4
|
+
def initialize(&block)
|
5
|
+
@hash = {
|
6
|
+
mod_bonus: 0,
|
7
|
+
save_mod_bonus: 0
|
8
|
+
}
|
9
|
+
instance_eval(&block)
|
10
|
+
end
|
11
|
+
|
12
|
+
%i(value mod_bonus save_mod_bonus).each do |e|
|
13
|
+
self.send(:define_method, e) do |x|
|
14
|
+
@hash[e] = x
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :hash
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.define(&block)
|
22
|
+
h = DefinitionProxy.new(&block).hash
|
23
|
+
self.new(h)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.from_value(h)
|
27
|
+
raise ArgumentError, "#{h} not fixnum" unless h.is_a?(Fixnum)
|
28
|
+
self.new({value: h, save_mod: 0, mod_bonus: 0})
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(hash)
|
32
|
+
@value = hash[:value]
|
33
|
+
@mod_bonus = (hash[:mod_bonus] || 0)
|
34
|
+
@save_mod_bonus = (hash[:save_mod_bonus] || 0)
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_reader :value,
|
38
|
+
:save_mod_bonus,
|
39
|
+
:mod_bonus
|
40
|
+
|
41
|
+
def mod
|
42
|
+
((@value - 10) / 2) + @mod_bonus
|
43
|
+
end
|
44
|
+
|
45
|
+
def saving_throw
|
46
|
+
1.d(20) + mod + save_mod_bonus
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require_relative './stat'
|
2
|
+
|
3
|
+
module FifthedSim
|
4
|
+
STAT_TYPES = [:str,
|
5
|
+
:dex,
|
6
|
+
:wis,
|
7
|
+
:cha,
|
8
|
+
:con,
|
9
|
+
:int]
|
10
|
+
class StatBlock
|
11
|
+
|
12
|
+
|
13
|
+
class DefinitionProxy
|
14
|
+
def initialize(&block)
|
15
|
+
@hash = {}
|
16
|
+
instance_eval(&block)
|
17
|
+
end
|
18
|
+
|
19
|
+
STAT_TYPES.each do |type|
|
20
|
+
self.send(:define_method, type) do |x = nil, &block|
|
21
|
+
if block
|
22
|
+
@hash[type] = Stat.define(&block)
|
23
|
+
elsif x.is_a?(Stat)
|
24
|
+
@hash[type] = x
|
25
|
+
elsif x.is_a?(Fixnum)
|
26
|
+
@hash[type] = Stat.from_value(x)
|
27
|
+
else
|
28
|
+
raise ArgumentError, "not a stat"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_accessor :hash
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.define(&block)
|
37
|
+
h = DefinitionProxy.new(&block)
|
38
|
+
self.new(h.hash)
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(hash)
|
42
|
+
@hash = Hash[hash.map do |k, v|
|
43
|
+
if v.is_a?(Stat)
|
44
|
+
[k, v]
|
45
|
+
else
|
46
|
+
[k, Stat.new(v)]
|
47
|
+
end
|
48
|
+
end]
|
49
|
+
end
|
50
|
+
|
51
|
+
STAT_TYPES.each do |st|
|
52
|
+
self.send(:define_method, st) do
|
53
|
+
@hash[st]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/fifthed_sim/version.rb
CHANGED
data/lib/fifthed_sim.rb
CHANGED
@@ -1,18 +1,42 @@
|
|
1
1
|
require "fifthed_sim/version"
|
2
|
-
require "fifthed_sim/
|
3
|
-
require "fifthed_sim/die_roll"
|
4
|
-
require "fifthed_sim/dice_calculation"
|
2
|
+
require "fifthed_sim/dice_expression"
|
5
3
|
require "fifthed_sim/distribution"
|
4
|
+
require "fifthed_sim/attack"
|
5
|
+
require "fifthed_sim/actor"
|
6
|
+
require "fifthed_sim/stat_block"
|
7
|
+
require "fifthed_sim/damage_types"
|
8
|
+
require "fifthed_sim/damage"
|
9
|
+
require "fifthed_sim/spell"
|
10
|
+
require "fifthed_sim/compiler"
|
11
|
+
require "fifthed_sim/roll_repl"
|
6
12
|
require "securerandom"
|
7
13
|
|
8
14
|
module FifthedSim
|
15
|
+
|
16
|
+
##
|
17
|
+
# Roll a dice.
|
18
|
+
# Normally, you access this through the monkey-patch on Fixnum.
|
9
19
|
def self.d(*args)
|
10
|
-
|
20
|
+
MultiNode.d(*args)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.make_roll(val, type)
|
24
|
+
RollNode.new(val, type)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.define_actor(name, &block)
|
28
|
+
Actor.define(name, &block)
|
11
29
|
end
|
12
30
|
end
|
13
31
|
|
14
32
|
class Fixnum
|
33
|
+
##
|
34
|
+
# Enable you to create dice rolls via `3.d(6)` syntax.
|
35
|
+
# This returns a DiceResult, meaning that you can add them together
|
36
|
+
# to form a calculation.
|
15
37
|
def d(o)
|
16
38
|
FifthedSim.d(self, o)
|
17
39
|
end
|
40
|
+
|
41
|
+
|
18
42
|
end
|