fifthed_sim 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|