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 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
- # Simple 6-sided die, more to follow
37
- d = GamesDice::Die.new( 6 )
38
- d.roll # => 4
39
- d.result # => 4
40
- d.explain_roll # => "4"
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
- # TODO: Factory methods for various dice schemes
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
@@ -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 either a GamesDice::Bunch
5
- # a Hash in the Array that describes a Bunch may contain any of the keys that can be used to initialize
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
@@ -1,3 +1,3 @@
1
1
  module GamesDice
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -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.5
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-04-15 00:00:00.000000000 Z
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: -2463166365146025536
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: -2463166365146025536
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