games_dice 0.0.5 → 0.0.6
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.
- data/README.md +71 -9
- data/lib/games_dice.rb +7 -1
- data/lib/games_dice/dice.rb +2 -2
- data/lib/games_dice/parser.rb +79 -0
- data/lib/games_dice/version.rb +1 -1
- data/spec/parser_spec.rb +26 -0
- metadata +7 -4
data/README.md
CHANGED
@@ -5,16 +5,31 @@
|
|
5
5
|
A library for simulating dice, intended for constructing a variety of dice systems as used in
|
6
6
|
role-playing and board games.
|
7
7
|
|
8
|
+
## Description
|
9
|
+
|
10
|
+
GamesDice is a gem to automate or simulate a variety of dice rolling systems found in board games and
|
11
|
+
role-playing games.
|
12
|
+
|
13
|
+
GamesDice is designed for systems used to generate integer results, and that do not require the
|
14
|
+
player to make decisions or apply other rules from the game. There are no game mechanics implemented
|
15
|
+
in GamesDice (such as the chance to hit in a combat game).
|
16
|
+
|
17
|
+
The main features of GamesDice are
|
18
|
+
|
19
|
+
* Uses string dice descriptions, the basics of which are familiar to many game players e.g. '2d6 + 3'
|
20
|
+
* Supports common features of dice systems automatically:
|
21
|
+
* Re-rolls that replace or modify the previous roll
|
22
|
+
* Counting number of "successes" from a set of dice
|
23
|
+
* Keeping the best, or worst, results from a set of dice
|
24
|
+
* Can explain how a result was achieved in terms of the individual die rolls
|
25
|
+
* Can calculate probabilities and expected values (experimental feature)
|
26
|
+
|
8
27
|
## Special Note on Versions Prior to 1.0.0
|
9
28
|
|
10
29
|
The author is using this code as an exercise in gem "best practice". As such, the gem
|
11
30
|
will have a deliberately limited set of functionality prior to version 1.0.0, and there should be
|
12
31
|
many small release increments before then.
|
13
32
|
|
14
|
-
The functionality should expand to cover dice systems seen in many role-playing systems, as it
|
15
|
-
progresses through 0.x.y versions (in fact much of this code is already written and working, so if
|
16
|
-
you have a burning need to simulate dice rolls for a specific game, feel free to get in touch).
|
17
|
-
|
18
33
|
## Installation
|
19
34
|
|
20
35
|
Add this line to your application's Gemfile:
|
@@ -33,11 +48,58 @@ Or install it yourself as:
|
|
33
48
|
|
34
49
|
require 'games_dice'
|
35
50
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
51
|
+
dice = GamesDice.create '4d6+3'
|
52
|
+
dice.roll # => 17 (e.g.)
|
53
|
+
|
54
|
+
## Library API
|
55
|
+
|
56
|
+
Although you can refer to the documentation for the contained classes, and use it if needed to
|
57
|
+
build some exotic dice systems, the recommended way to use GamesDice is to create GamesDice::Dice
|
58
|
+
objects via the factory methods from the GamesDice module, and then use those objects to simulate
|
59
|
+
dice rolls, explain the results or calculate probabilties as required.
|
60
|
+
|
61
|
+
### GamesDice factory methods
|
62
|
+
|
63
|
+
#### GamesDice.create dice_description, prng
|
64
|
+
|
65
|
+
Converts a string such as '3d6+6' into a GamesDice::Dice object
|
66
|
+
|
67
|
+
Parameters:
|
68
|
+
|
69
|
+
* dice_description is a string such as '3d6' or '2d4-1'. See String Dice Descriptions below for possibilities.
|
70
|
+
* prng is optional, if provided it should be an object that has a method 'rand( integer )' that works like Ruby's built-in rand method
|
71
|
+
|
72
|
+
Returns a GamesDice::Dice object.
|
73
|
+
|
74
|
+
### GamesDice::Dice instance methods
|
75
|
+
|
76
|
+
#### dice.roll
|
77
|
+
|
78
|
+
Simulates rolling the dice as they were described in the constructor, and keeps a record of how the
|
79
|
+
simulation result was achieved.
|
80
|
+
|
81
|
+
Takes no parameters.
|
82
|
+
|
83
|
+
Returns the integer result of the roll.
|
84
|
+
|
85
|
+
## String Dice Descriptions
|
86
|
+
|
87
|
+
The dice descriptions are a mini-language. A simple six-sided die is described like this:
|
88
|
+
|
89
|
+
1d6
|
90
|
+
|
91
|
+
where the first integer is the number of dice to add together, and the second number is the number
|
92
|
+
of sides on each die. Spaces are allowed before the first number, and after the dice description, but
|
93
|
+
not between either number and the "d".
|
94
|
+
|
95
|
+
The dice mini-language allows for adding and subtracting integers and groups of dice in a list, e.g.
|
96
|
+
|
97
|
+
2d6 + 1d4
|
98
|
+
1d100 + 1d20 - 5
|
99
|
+
|
100
|
+
That is the limit of combining dice and constants though, no multiplications, or bracketed constructs
|
101
|
+
like "(1d8)d8" - you can still use games_dice to help simulate these, but you will need to add your own
|
102
|
+
code to do so.
|
41
103
|
|
42
104
|
## Contributing
|
43
105
|
|
data/lib/games_dice.rb
CHANGED
@@ -8,7 +8,13 @@ require "games_dice/map_rule"
|
|
8
8
|
require "games_dice/complex_die"
|
9
9
|
require "games_dice/bunch"
|
10
10
|
require "games_dice/dice"
|
11
|
+
require "games_dice/parser"
|
11
12
|
|
12
13
|
module GamesDice
|
13
|
-
|
14
|
+
@@parser = GamesDice::Parser.new
|
15
|
+
|
16
|
+
def self.create dice_description, prng = nil
|
17
|
+
parsed = @@parser.parse( dice_description )
|
18
|
+
GamesDice::Dice.new( parsed[:bunches], parsed[:offset], prng )
|
19
|
+
end
|
14
20
|
end
|
data/lib/games_dice/dice.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# models any combination of zero or more Bunches, plus a constant offset, summing them
|
2
2
|
# to create a total result when rolled
|
3
3
|
class GamesDice::Dice
|
4
|
-
# bunches is an Array of Hashes, each of which describes
|
5
|
-
#
|
4
|
+
# bunches is an Array of Hashes, each of which describes a GamesDice::Bunch
|
5
|
+
# and may contain any of the keys that can be used to initialize
|
6
6
|
# the Bunch, plus the following optional key:
|
7
7
|
# :multiplier => any Integer, but typically 1 or -1 to describe whether the Bunch total is to be added or subtracted
|
8
8
|
# offset is an Integer which will be added to the result when rolling all the bunches
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'parslet'
|
2
|
+
|
3
|
+
# converts string dice descriptions to data usable for the GamesDice::Dice constructor
|
4
|
+
class GamesDice::Parser < Parslet::Parser
|
5
|
+
|
6
|
+
# Descriptive language examples (capital letters stand in for integers)
|
7
|
+
# NdX - a roll of N dice, with X sides each, values summed to total
|
8
|
+
# NdX + C - a roll of N dice, with X sides each, values summed to total plus a constant C
|
9
|
+
# NdX - C - a roll of N dice, with X sides each, values summed to total minus a constant C
|
10
|
+
# NdX + MdY + C - any number of +C, -C, +NdX, -NdX etc sub-expressions can be combined
|
11
|
+
# NdXkZ - a roll of N dice, sides X, keep best Z results and sum them
|
12
|
+
# NdXk[Z,worst] - a roll of N dice, sides X, keep worst Z results and sum them
|
13
|
+
# NdXrZ - a roll of N dice, sides X, re-roll and replace Zs
|
14
|
+
# NdXr[Z,add] - a roll of N dice, sides X, re-roll and add on a result of Z
|
15
|
+
# NdXr[Y..Z,add] - a roll of N dice, sides X, re-roll and add on a result of Y..Z
|
16
|
+
# NdXx - exploding dice, synonym for NdXr[X,add]
|
17
|
+
# NdXmZ - mapped dice, values below Z score 0, values above Z score 1
|
18
|
+
# NdXm[>=Z,A] - mapped dice, values greater than or equal to Z score A (unmapped values score 0 by default)
|
19
|
+
|
20
|
+
# These are the Parslet rules that define the dice grammar
|
21
|
+
rule(:integer) { match('[0-9]').repeat(1) }
|
22
|
+
rule(:dlabel) { match('[d]') }
|
23
|
+
rule(:bunch_start) { integer.as(:ndice) >> dlabel >> integer.as(:sides) }
|
24
|
+
rule(:space) { match('\s').repeat(1) }
|
25
|
+
rule(:space?) { space.maybe }
|
26
|
+
rule(:operator) { match('[+-]').as(:op) >> space? }
|
27
|
+
rule(:add_bunch) { operator >> bunch_start >> space? }
|
28
|
+
rule(:add_constant) { operator >> integer.as(:constant) >> space? }
|
29
|
+
rule(:dice_expression) { add_bunch | add_constant }
|
30
|
+
rule(:expressions) { dice_expression.repeat.as(:bunches) }
|
31
|
+
root :expressions
|
32
|
+
|
33
|
+
def parse dice_description, dice_name = nil
|
34
|
+
dice_description = dice_description.to_s.strip
|
35
|
+
dice_name ||= dice_description
|
36
|
+
# Force first item to start '+' for simpler parse rules
|
37
|
+
dice_description = '+' + dice_description unless dice_description =~ /\A[+-]/
|
38
|
+
dice_expressions = super( dice_description )
|
39
|
+
|
40
|
+
{ :bunches => collect_bunches( dice_expressions ), :offset => collect_offset( dice_expressions ) }
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def collect_bunches dice_expressions
|
46
|
+
dice_expressions[:bunches].select {|h| h[:ndice] }.map do |in_hash|
|
47
|
+
out_hash = {}
|
48
|
+
# Convert integers
|
49
|
+
[:ndice, :sides].each do |s|
|
50
|
+
next unless in_hash[s]
|
51
|
+
out_hash[s] = in_hash[s].to_i
|
52
|
+
end
|
53
|
+
|
54
|
+
# Multiplier
|
55
|
+
if in_hash[:op]
|
56
|
+
optype = in_hash[:op].to_s
|
57
|
+
out_hash[:multiplier] = case optype
|
58
|
+
when '+' then 1
|
59
|
+
when '-' then -1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
out_hash
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def collect_offset dice_expressions
|
67
|
+
dice_expressions[:bunches].select {|h| h[:constant] }.inject(0) do |total, in_hash|
|
68
|
+
c = in_hash[:constant].to_i
|
69
|
+
optype = in_hash[:op].to_s
|
70
|
+
if optype == '+'
|
71
|
+
total += c
|
72
|
+
else
|
73
|
+
total -= c
|
74
|
+
end
|
75
|
+
total
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end # class Parser
|
data/lib/games_dice/version.rb
CHANGED
data/spec/parser_spec.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'games_dice'
|
2
|
+
|
3
|
+
describe GamesDice::Parser do
|
4
|
+
|
5
|
+
describe "#parse" do
|
6
|
+
let(:parser) { GamesDice::Parser.new }
|
7
|
+
|
8
|
+
it "should convert descriptive strings to data usable in the GamesDice::Dice constructor" do
|
9
|
+
variations = {
|
10
|
+
'1d6' => { :bunches => [{:ndice=>1, :sides=>6, :multiplier=>1}], :offset => 0 },
|
11
|
+
'2d8-1d4' => { :bunches => [{:ndice=>2, :sides=>8, :multiplier=>1},{:ndice=>1, :sides=>4, :multiplier=>-1}], :offset => 0 },
|
12
|
+
'+ 2d10 - 1d4 ' => { :bunches => [{:ndice=>2, :sides=>10, :multiplier=>1},{:ndice=>1, :sides=>4, :multiplier=>-1}], :offset => 0 },
|
13
|
+
' + 3d6 + 12 ' => { :bunches => [{:ndice=>3, :sides=>6, :multiplier=>1}], :offset => 12 },
|
14
|
+
'-7 + 2d4 + 1 ' => { :bunches => [{:ndice=>2, :sides=>4, :multiplier=>1}], :offset => -6 },
|
15
|
+
'- 3 + 7d20 - 1 ' => { :bunches => [{:ndice=>7, :sides=>20, :multiplier=>1}], :offset => -4 },
|
16
|
+
' - 2d4' => { :bunches => [{:ndice=>2, :sides=>4, :multiplier=>-1}], :offset => 0 },
|
17
|
+
'3d12+5+2d8+1d6' => { :bunches => [{:ndice=>3, :sides=>12, :multiplier=>1},{:ndice=>2, :sides=>8, :multiplier=>1},{:ndice=>1, :sides=>6, :multiplier=>1}], :offset => 5 },
|
18
|
+
}
|
19
|
+
|
20
|
+
variations.each do |input,expected_output|
|
21
|
+
parser.parse( input ).should == expected_output
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end # describe "#parse"
|
26
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: games_dice
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-05-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -82,6 +82,7 @@ files:
|
|
82
82
|
- lib/games_dice/die.rb
|
83
83
|
- lib/games_dice/die_result.rb
|
84
84
|
- lib/games_dice/map_rule.rb
|
85
|
+
- lib/games_dice/parser.rb
|
85
86
|
- lib/games_dice/probabilities.rb
|
86
87
|
- lib/games_dice/reroll_rule.rb
|
87
88
|
- lib/games_dice/version.rb
|
@@ -91,6 +92,7 @@ files:
|
|
91
92
|
- spec/die_result_spec.rb
|
92
93
|
- spec/die_spec.rb
|
93
94
|
- spec/map_rule_spec.rb
|
95
|
+
- spec/parser_spec.rb
|
94
96
|
- spec/probability_spec.rb
|
95
97
|
- spec/reroll_rule_spec.rb
|
96
98
|
- travis.yml
|
@@ -108,7 +110,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
108
110
|
version: '0'
|
109
111
|
segments:
|
110
112
|
- 0
|
111
|
-
hash:
|
113
|
+
hash: 384639309274746311
|
112
114
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
115
|
none: false
|
114
116
|
requirements:
|
@@ -117,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
117
119
|
version: '0'
|
118
120
|
segments:
|
119
121
|
- 0
|
120
|
-
hash:
|
122
|
+
hash: 384639309274746311
|
121
123
|
requirements: []
|
122
124
|
rubyforge_project:
|
123
125
|
rubygems_version: 1.8.24
|
@@ -131,5 +133,6 @@ test_files:
|
|
131
133
|
- spec/die_result_spec.rb
|
132
134
|
- spec/die_spec.rb
|
133
135
|
- spec/map_rule_spec.rb
|
136
|
+
- spec/parser_spec.rb
|
134
137
|
- spec/probability_spec.rb
|
135
138
|
- spec/reroll_rule_spec.rb
|