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 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