dice_parser 1.0.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: baa939eb7c810a0cc7340c117116c1828d5f7512
4
+ data.tar.gz: f4aa186523587a1bcca4b903ed73f215aa30e0cf
5
+ SHA512:
6
+ metadata.gz: 4cd0edbb580d6df4974f50a5d0747212686286abee440a8a8fe954502d6e52b724b06942700b5eb8a4ee4a9c53a8857c7aa5a94dbc9a1dbbe2a44c9611ea5d02
7
+ data.tar.gz: e5f816f66ff554b48b24b3ba026411558e10851f9ca52cf146e094db6375535234dc48ea25ba2754dca14cf62fad21ea727f9dfa6f24061d73126b82628326f6
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in dice.gemspec
4
+ gemspec
5
+
6
+ gem 'minitest'
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012-2014 Stuart Coyle
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Dice
2
+
3
+ A parser for Dice notation
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'dice'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install dice
18
+
19
+ ## Usage
20
+
21
+ The supported dice notation:
22
+
23
+ * NdX - Roll N dice with X sides and add the result. e.g. 3d6, 4d10, 1d100.
24
+ * dX - Roll one die with X sides.
25
+ * d% - Roll one percentile (100 sided) die.
26
+ * hY - Keep the highest Y rolls. e.g. 4d6h3 would give a roll in the range 3..18
27
+ * kY - A synonym for h (keep).
28
+ * lZ - Keep the lowest Z rolls. e.g. 2d6l1 would take the lowest one of two dice.
29
+
30
+ Basic arithmetic is also supported:
31
+ integers, floats, +, -, *, / and () are all allowed.
32
+
33
+ Examples:
34
+ ```
35
+ Dice.roll('3d6+6')
36
+ Dice.roll('2d100/2')
37
+ Dice.roll('4d6 - 3d4')
38
+ Dice.roll('100/4d6h3')
39
+
40
+ ```
41
+
42
+ Currently the gem just uses Random.rand for it's random number generation.
43
+ Other random number generators could easily be added by overriding the Dice.rng method.
44
+
45
+ ## Contributing
46
+
47
+ 1. Fork it
48
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
49
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
50
+ 4. Push to the branch (`git push origin my-new-feature`)
51
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'lib'
7
+ t.libs << "test"
8
+ t.test_files = FileList['test/*_test.rb']
9
+ t.verbose = true
10
+ end
11
+
12
+ desc "Run tests"
13
+ task :default => :test
14
+
data/dice.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/dice/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Stuart Coyle"]
6
+ gem.email = ["stuart.coyle@gmail.com"]
7
+ gem.description = %q{A parser for dice notation. Rolls dice and does various arithmetic.}
8
+ gem.summary = %q{Dice notation parser}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "dice_parser"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Dice::VERSION
17
+ gem.requirements << 'Treetop version 1.4 or later.'
18
+ gem.add_dependency "treetop"
19
+ end
@@ -0,0 +1,62 @@
1
+ module DiceString
2
+ class DiceNode < Treetop::Runtime::SyntaxNode
3
+ def evaluate
4
+ n = num_dice.text_value == "" ? 1 : num_dice.evaluate
5
+ s = sides.text_value == '%' ? 100 : sides.evaluate
6
+ rolls = Array.new(n){ Dice.rng.call(s) }
7
+ sum rolls
8
+ end
9
+
10
+ def maximum
11
+ n = num_dice.text_value == "" ? 1 : num_dice.evaluate
12
+ s = sides.text_value == '%' ? 100 : sides.evaluate
13
+ rolls = Array.new(n){ s }
14
+ sum rolls
15
+ end
16
+
17
+ def minimum
18
+ n = num_dice.text_value == "" ? 1 : num_dice.evaluate
19
+ rolls = Array.new(n){ 1 }
20
+ sum rolls
21
+ end
22
+
23
+ private
24
+ def sum rolls
25
+ rolls = keep.evaluate(rolls) if keep.text_value != ""
26
+ rolls.inject(0){|v,l| v += l}
27
+ end
28
+ end
29
+
30
+ class BinopNode < Treetop::Runtime::SyntaxNode
31
+ def evaluate
32
+ tail.elements.inject(head.evaluate) do |value, element|
33
+ element.operator.apply(value, element.operand.evaluate)
34
+ end
35
+ end
36
+
37
+ def maximum
38
+ tail.elements.inject(head.maximum) do |value, element|
39
+ element.operator.apply(value, element.operand.maximum)
40
+ end
41
+ end
42
+
43
+ def minimum
44
+ tail.elements.inject(head.minimum) do |value, element|
45
+ element.operator.apply(value, element.operand.minimum)
46
+ end
47
+ end
48
+ end
49
+
50
+ class KeepNode < Treetop::Runtime::SyntaxNode
51
+ def evaluate rolls
52
+ case op.text_value
53
+ when 'h'
54
+ rolls.sort.reverse.take n.evaluate
55
+ when 'l'
56
+ rolls.sort.take n.evaluate
57
+ else
58
+ rolls
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,106 @@
1
+ require File.join(File.dirname(__FILE__),'dice_nodes.rb')
2
+
3
+ grammar DiceString
4
+
5
+ rule expression
6
+ additive
7
+ end
8
+
9
+ rule additive
10
+ head:multitive
11
+ tail:(space operator:additive_op space operand:multitive)* <BinopNode>
12
+ end
13
+
14
+ rule multitive
15
+ head:primary
16
+ tail:(space operator:multitive_op space operand:primary)* <BinopNode>
17
+ end
18
+
19
+ rule additive_op
20
+ '+' {
21
+ def apply(a,b)
22
+ a + b
23
+ end
24
+ } / '-' {
25
+ def apply(a,b)
26
+ a - b
27
+ end
28
+ }
29
+ end
30
+
31
+ rule multitive_op
32
+ '*' {
33
+ def apply(a,b)
34
+ a * b
35
+ end
36
+ } / '/' {
37
+ def apply(a,b)
38
+ a / b
39
+ end
40
+ }
41
+ end
42
+
43
+ rule primary
44
+ dice / number / '(' space expression space ')' {
45
+ def evaluate
46
+ expression.evaluate
47
+ end
48
+
49
+ def minimum
50
+ expression.minimum
51
+ end
52
+
53
+ def maximum
54
+ expression.maximum
55
+ end
56
+ }
57
+ end
58
+
59
+ rule number
60
+ float / integer
61
+ end
62
+
63
+ rule float
64
+ integer '.' integer {
65
+ def evaluate
66
+ text_value.to_f
67
+ end
68
+
69
+ def minimum
70
+ evaluate
71
+ end
72
+
73
+ def maximum
74
+ evaluate
75
+ end
76
+ }
77
+ end
78
+
79
+ rule integer
80
+ ([1-9] [0-9]* / '0') {
81
+ def evaluate
82
+ text_value.to_i
83
+ end
84
+
85
+ def minimum
86
+ evaluate
87
+ end
88
+
89
+ def maximum
90
+ evaluate
91
+ end
92
+ }
93
+ end
94
+
95
+ rule space
96
+ ' '*
97
+ end
98
+
99
+ rule dice
100
+ num_dice:integer? 'd' sides:(integer / '%') keep:keep? <DiceNode>
101
+ end
102
+
103
+ rule keep
104
+ op:('h' / 'l') n:integer <KeepNode>
105
+ end
106
+ end
@@ -0,0 +1,3 @@
1
+ module Dice
2
+ VERSION = "1.0.0"
3
+ end
data/lib/dice.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'treetop'
2
+
3
+ require File.join(File.expand_path(File.dirname(__FILE__)), 'dice', 'dice_nodes.rb')
4
+
5
+ # This class encapsulates the dice parser from treetop.
6
+ class Dice
7
+ Treetop.load(File.join(File.expand_path(File.dirname(__FILE__)), 'dice', 'dicestring'))
8
+ @parser = DiceStringParser.new
9
+
10
+ attr_accessor :dice_string
11
+
12
+ def self.parse string
13
+ @parser.parse string
14
+ end
15
+
16
+ def self.rng= generator
17
+ @rng = generator
18
+ end
19
+
20
+ def self.rng
21
+ @rng ||= -> (n) { Random.rand(n) + 1 }
22
+ end
23
+
24
+ def self.roll string
25
+ parse(string).evaluate
26
+ end
27
+
28
+ def initialize dice_string
29
+ @dice_string = dice_string
30
+ end
31
+
32
+ def roll
33
+ dice.evaluate
34
+ end
35
+
36
+ def maximum
37
+ dice.maximum
38
+ end
39
+
40
+ def minimum
41
+ dice.minimum
42
+ end
43
+
44
+ private
45
+
46
+ def dice
47
+ self.class.parse(dice_string)
48
+ end
49
+ end
@@ -0,0 +1,91 @@
1
+ require 'minitest/autorun'
2
+ require 'dice'
3
+
4
+ class DiceParserTest < MiniTest::Test
5
+
6
+ def test_integer
7
+ assert_equal 0, Dice.roll('0')
8
+ assert_equal 32, Dice.roll('32')
9
+ end
10
+
11
+ def test_float
12
+ assert_equal 1.2301, Dice.roll('1.2301')
13
+ assert_equal 23.123, Dice.roll('23.123')
14
+ end
15
+
16
+ def test_addition
17
+ assert_equal 4, Dice.roll('1+3')
18
+ end
19
+
20
+ def test_subtraction
21
+ assert_equal 3, Dice.roll('4-1')
22
+ end
23
+
24
+ def test_multiplication
25
+ assert_equal 6, Dice.roll('2*3')
26
+ end
27
+
28
+ def test_division
29
+ assert_equal 4, Dice.roll('8/2')
30
+ end
31
+
32
+ def test_parens
33
+ assert_equal 7, Dice.roll('1+2*3')
34
+ assert_equal 9, Dice.roll('(1+2)*3')
35
+ assert_equal(-2, Dice.roll('5-(3+4)'))
36
+ end
37
+
38
+ def test_dice
39
+ Random.stub :rand, 4 do
40
+ assert_equal 10, Dice.roll('2d6')
41
+ assert_equal 15, Dice.roll('3d12')
42
+ end
43
+ end
44
+
45
+ def test_percentile
46
+ Random.stub :rand, 40 do
47
+ assert_equal 41, Dice.roll('1d%')
48
+ end
49
+ end
50
+
51
+ def test_keep_highest
52
+ stub_random [5, 2, 3] do
53
+ assert_equal 10, Dice.roll('3d6h2')
54
+ end
55
+ end
56
+
57
+ def test_keep_lowest
58
+ stub_random [5, 2, 3] do
59
+ assert_equal 7, Dice.roll('3d6l2')
60
+ end
61
+ end
62
+
63
+ def test_multiple_dice_sets
64
+ stub_random [4, 8, 1, 3, 2] do
65
+ assert_equal 23, Dice.roll('2d10+3d4')
66
+ end
67
+ end
68
+
69
+ def test_assumed_one
70
+ Random.stub :rand, 50 do
71
+ assert_equal 51, Dice.roll('d100')
72
+ assert_equal 53, Dice.roll('2+d100')
73
+ end
74
+ end
75
+
76
+ def test_adding_an_integer_returns_a_fixnum_not_a_float
77
+ assert_equal Fixnum, Dice.roll("1d6 + 1").class
78
+ end
79
+
80
+ def test_complex_case
81
+ stub_random [4,8,1,3,2,24] do
82
+ assert_equal 10*(5 + 9)*(2 + (2 + 4 + 3)) /(3 - 25)+3.5, Dice.roll('10*2d10*(2+3d4)/(3-d%)+3.5')
83
+ end
84
+ end
85
+
86
+ def stub_random rolls
87
+ Random.stub :rand, -> (_) { rolls.shift } do
88
+ yield
89
+ end
90
+ end
91
+ end
data/test/dice_test.rb ADDED
@@ -0,0 +1,31 @@
1
+ require 'minitest/autorun'
2
+ require 'treetop'
3
+ require 'dice'
4
+
5
+ class DiceTest < MiniTest::Test
6
+ def test_new_assigns_the_dice_string
7
+ assert_equal '3d6', Dice.new('3d6').dice_string
8
+ end
9
+
10
+ def test_roll
11
+ Random.stub :rand, 4 do
12
+ assert_equal 15, Dice.new('3d6').roll
13
+ end
14
+ end
15
+
16
+ def test_maximum
17
+ assert_equal 20, Dice.new('3d6+2').maximum
18
+ end
19
+
20
+ # def test_maximum_complex
21
+ # assert_equal 12 - 3 - 5, Dice.new('3d4 - 3 - 5d10').maximum
22
+ # end
23
+
24
+ def test_minimum
25
+ assert_equal 4, Dice.new('3d6+1').minimum
26
+ end
27
+
28
+ # def test_minimum_complex
29
+ # assert_equal 3-24, Dice.new('3d6 - 4d6').minimum
30
+ # end
31
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dice_parser
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Stuart Coyle
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: treetop
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: A parser for dice notation. Rolls dice and does various arithmetic.
28
+ email:
29
+ - stuart.coyle@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - .gitignore
35
+ - Gemfile
36
+ - LICENSE
37
+ - README.md
38
+ - Rakefile
39
+ - dice.gemspec
40
+ - lib/dice.rb
41
+ - lib/dice/dice_nodes.rb
42
+ - lib/dice/dicestring.treetop
43
+ - lib/dice/version.rb
44
+ - test/dice_parser_test.rb
45
+ - test/dice_test.rb
46
+ homepage: ''
47
+ licenses: []
48
+ metadata: {}
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements:
64
+ - Treetop version 1.4 or later.
65
+ rubyforge_project:
66
+ rubygems_version: 2.4.5
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: Dice notation parser
70
+ test_files:
71
+ - test/dice_parser_test.rb
72
+ - test/dice_test.rb