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