crapshoot 0.1.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.
@@ -0,0 +1,62 @@
1
+ %%{
2
+ machine scanner;
3
+
4
+ action _number { @mark_num = p }
5
+ action number { @num_stack.push atos(data[@mark_num..p-1]) }
6
+
7
+ action constant { @tokens << Tokens::Constant.new(@num_stack.pop) }
8
+ action series {
9
+ drop = @drop_current
10
+ @drop_current = nil
11
+ sides = @num_stack.pop
12
+ count = @num_stack.pop
13
+ @tokens << Tokens::Series.new(count, sides, drop)
14
+ }
15
+ action arithmetic { @tokens << Tokens::Arithmetic.new(data[p-1].chr) }
16
+
17
+ action drop { @drop_current = data[p-1].chr }
18
+
19
+ Number = digit+ >_number %number;
20
+
21
+ Constant = Number %constant;
22
+
23
+ Drop = '^' | 'v' %drop;
24
+ Series = Number 'd' Number Drop? %series;
25
+
26
+ Arithmetic = ('+' | '-') %arithmetic;
27
+
28
+ UnaryExpression = Series | Constant;
29
+ BinaryExpression = UnaryExpression (space* Arithmetic space* UnaryExpression)+;
30
+ Expression = UnaryExpression | BinaryExpression;
31
+
32
+ main := Expression;
33
+ }%%
34
+
35
+ module Crapshoot
36
+ module Parser
37
+ class Scan
38
+ def initialize
39
+ @tokens = []
40
+ @num_stack = []
41
+ end
42
+
43
+ def parse(line)
44
+ data = line.codepoints.to_a
45
+ stack = []
46
+ p = 0
47
+ ts = 0
48
+ te = 0
49
+ act = 0
50
+ eof = data.length
51
+ %% write data;
52
+ %% write init;
53
+ %% write exec;
54
+ return @tokens
55
+ end
56
+
57
+ def atos(a)
58
+ a.map(&:chr).join
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,23 @@
1
+ module Crapshoot
2
+ class Postfixer
3
+ def postfixify(infix_tokens)
4
+ @infix_orig = infix_tokens
5
+ @infix = @infix_orig.dup
6
+ @postfix = []
7
+ until @infix.empty?
8
+ step
9
+ end
10
+
11
+ return @postfix
12
+ end
13
+
14
+ def step
15
+ candidate = @infix.shift
16
+ unless candidate.independent
17
+ dependency = @infix.shift
18
+ @postfix.push dependency
19
+ end
20
+ @postfix.push candidate
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,38 @@
1
+ require File.join(File.dirname(__FILE__), 'parser', 'scan.rb')
2
+ %w{ base constant series arithmetic }.each do |f|
3
+ require File.join(File.dirname(__FILE__), 'tokens', f)
4
+ end
5
+
6
+ module Crapshoot
7
+ class Scanner
8
+ def initialize
9
+ @parser = Parser::Scan.new
10
+ end
11
+
12
+ def parse(line)
13
+ @line = line
14
+ begin
15
+ @result = @parser.parse @line
16
+ return @result
17
+ rescue => e
18
+ @exception = e
19
+ return nil
20
+ end
21
+ end
22
+
23
+ def successful?
24
+ @result
25
+ end
26
+
27
+ def inspect_errors
28
+ return 'No error' if successful?
29
+ backtrace = @exception.backtrace
30
+ filtered_backtrace = []
31
+ backtrace.each do |i|
32
+ break if i.include? __FILE__
33
+ filtered_backtrace << i
34
+ end
35
+ return "#{ @exception.message } at #{ filtered_backtrace.join("\n")}"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,24 @@
1
+ module Crapshoot
2
+ module Tokens
3
+ class Arithmetic < Base
4
+ def initialize(operation)
5
+ @operation = operation
6
+ end
7
+
8
+ def independent
9
+ false
10
+ end
11
+
12
+ def eval(stack)
13
+ r = stack.pop
14
+ l = stack.pop
15
+ @result = l.send(@operation.to_sym, r)
16
+ @result
17
+ end
18
+
19
+ def inspect
20
+ "<Crapshoot::Tokens::Arithmetic operation=#{@operation}>"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,9 @@
1
+ module Crapshoot
2
+ module Tokens
3
+ class Base
4
+ def independent
5
+ true
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ module Crapshoot
2
+ module Tokens
3
+ class Constant < Base
4
+ def initialize(number)
5
+ @value = number.to_i
6
+ end
7
+
8
+ def eval(stack)
9
+ return @value
10
+ end
11
+
12
+ def inspect
13
+ "<Crapshoot::Tokens::Constant value=#{@value}>"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,42 @@
1
+ module Crapshoot
2
+ module Tokens
3
+ class Series < Base
4
+ def initialize(count, sides, drop = nil)
5
+ @count = count.to_i
6
+ @sides = sides.to_i
7
+ @drop = drop
8
+ end
9
+
10
+ def eval(stack)
11
+ results_array = (1..@count).to_a.map{ roll_a_die }.sort
12
+ @result = results_array.inject(&:+)
13
+ roll_description = results_array.join '+'
14
+
15
+ case @drop
16
+ when '^'
17
+ max = results_array[0]
18
+ @result -= max
19
+ roll_description += '-#{max}'
20
+ when 'v'
21
+ min = results_array[-1]
22
+ @result -= min
23
+ roll_description += '-#{min}'
24
+ end
25
+ @description = "(#{roll_description})"
26
+
27
+ return @result
28
+ end
29
+
30
+ def inspect
31
+ "<Crapshoot::Tokens::Series dice=#{@count}d#{@sides} drop=#{@drop or 'nothing'}>"
32
+ end
33
+
34
+ private
35
+
36
+ def roll_a_die
37
+ # +1 because we can roll a zero
38
+ ActiveSupport::SecureRandom.random_number(@sides) + 1
39
+ end
40
+ end
41
+ end
42
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'shoulda'
12
+
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+ require 'crapshoot'
16
+
17
+ class Test::Unit::TestCase
18
+ end
@@ -0,0 +1,19 @@
1
+ require 'helper'
2
+
3
+ class TestCrapshoot < Test::Unit::TestCase
4
+ def self.should_roll(from, options)
5
+ keys = options.keys
6
+
7
+ should "successfully evaluate #{from.inspect}" do
8
+ result = Crapshoot.roll from
9
+ keys.each do |k|
10
+ constraint = options[k]
11
+ assert result.send(k.to_sym, constraint), "#{result} was not #{k} #{constraint}"
12
+ end
13
+ end
14
+ end
15
+ context 'The Crapshoot module' do
16
+ should_roll '4d6', '>='=>4, '<='=>24
17
+ should_roll '4d6 + 200', '>='=>204, '<='=>224
18
+ end
19
+ end
@@ -0,0 +1,39 @@
1
+ require 'helper'
2
+
3
+ class TestEvaluator < Test::Unit::TestCase
4
+ include Crapshoot::Tokens
5
+ def self.should_evaluate(from, options)
6
+ keys = options.keys
7
+
8
+ should "successfully evaluate #{from.inspect}" do
9
+ result = @evaluator.evaluate from
10
+ keys.each do |k|
11
+ constraint = options[k]
12
+ assert result.send(k.to_sym, constraint), "#{result} was not #{k} #{constraint}"
13
+ end
14
+ end
15
+ end
16
+
17
+ context 'an evaluator' do
18
+ setup do
19
+ @evaluator = Crapshoot::Evaluator.new
20
+ end
21
+
22
+ five = Constant.new '5'
23
+ plus = Arithmetic.new '+'
24
+ minus = Arithmetic.new '-'
25
+ six = Constant.new '6'
26
+ series1 = Series.new '5', '6', nil
27
+ series2 = Series.new '4', '20', 'v'
28
+ series3 = Series.new '20', '2'
29
+ series4 = Series.new '20', 1, 'v'
30
+ series5 = Series.new '20', 1, '^'
31
+
32
+ should_evaluate [five, six, plus], '=='=> 11
33
+ should_evaluate [series1, series1, plus], '>=' => 10, '<=' => 60
34
+ should_evaluate [five, series2, plus], '>=' => 8, '<=' => 65
35
+ should_evaluate [series3], '>=' => 20, '<=' => 40
36
+ should_evaluate [series4], '=='=>19
37
+ should_evaluate [series5], '=='=>19
38
+ end
39
+ end
@@ -0,0 +1,28 @@
1
+ require 'helper'
2
+
3
+ class TestPostfixer < Test::Unit::TestCase
4
+ include Crapshoot::Tokens
5
+ def self.should_postfixify(from, to)
6
+ should "postfixify #{from.inspect} to #{to.inspect}" do
7
+ candidate = @postfixer.postfixify from
8
+ assert_equal to, candidate
9
+ end
10
+ end
11
+ context 'a postfixer' do
12
+ setup do
13
+ @postfixer = Crapshoot::Postfixer.new
14
+ end
15
+
16
+ five = Constant.new '5'
17
+ plus = Arithmetic.new '+'
18
+ minus = Arithmetic.new '-'
19
+ six = Constant.new '6'
20
+ series1 = Series.new '5', '6', nil
21
+ series2 = Series.new '4', '20', 'v'
22
+
23
+ should_postfixify([five, plus, six],
24
+ [five, six, plus])
25
+ should_postfixify([series1, plus, six, minus, series2],
26
+ [series1, six, plus, series2, minus])
27
+ end
28
+ end
@@ -0,0 +1,44 @@
1
+ require 'helper'
2
+
3
+ class TestScanner < Test::Unit::TestCase
4
+ include Crapshoot::Tokens
5
+ def self.should_parse(line, *sequence)
6
+ should "parse #{line.inspect} without error" do
7
+ @scanner.parse(line)
8
+
9
+ assert @scanner.successful?,
10
+ "Failed to parse #{line.inspect}: #{@scanner.inspect_errors}"
11
+ end
12
+
13
+ unless sequence.empty?
14
+ should "parse #{line.inspect} into #{sequence.inspect}" do
15
+ result = @scanner.parse(line)
16
+ result.zip sequence do |r, c|
17
+ assert_kind_of c, r
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ context 'a scanner' do
24
+ setup do
25
+ @scanner = Crapshoot::Scanner.new
26
+ end
27
+
28
+ should_parse '4', Constant
29
+ should_parse '4d6', Series
30
+ should_parse '1 + 2', Constant, Arithmetic, Constant
31
+ should_parse '1+2', Constant, Arithmetic, Constant
32
+ should_parse '4d6 + 4d6', Series, Arithmetic, Series
33
+ should_parse '4d6v', Series
34
+ should_parse '4d6 + 2', Series, Arithmetic, Constant
35
+ should_parse '15', Constant
36
+ should_parse '14d100v + 8d8 + 3', Series, Arithmetic, Series, Arithmetic, Constant
37
+
38
+ should 'have an error string' do
39
+ @scanner.parse 'fffff'
40
+ assert @scanner.inspect_errors
41
+ end
42
+ end
43
+
44
+ end
metadata ADDED
@@ -0,0 +1,305 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: crapshoot
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Bryce Kerley
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-12-28 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: crapshoot
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ prerelease: false
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: activesupport
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ segments:
41
+ - 3
42
+ - 0
43
+ version: "3.0"
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *id002
47
+ - !ruby/object:Gem::Dependency
48
+ name: shoulda
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: *id003
60
+ - !ruby/object:Gem::Dependency
61
+ name: bundler
62
+ requirement: &id004 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ~>
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 1
69
+ - 0
70
+ - 0
71
+ version: 1.0.0
72
+ type: :development
73
+ prerelease: false
74
+ version_requirements: *id004
75
+ - !ruby/object:Gem::Dependency
76
+ name: jeweler
77
+ requirement: &id005 !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ segments:
83
+ - 1
84
+ - 5
85
+ - 2
86
+ version: 1.5.2
87
+ type: :development
88
+ prerelease: false
89
+ version_requirements: *id005
90
+ - !ruby/object:Gem::Dependency
91
+ name: rcov
92
+ requirement: &id006 !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ type: :development
101
+ prerelease: false
102
+ version_requirements: *id006
103
+ - !ruby/object:Gem::Dependency
104
+ name: guard-test
105
+ requirement: &id007 !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ segments:
111
+ - 0
112
+ - 1
113
+ - 4
114
+ version: 0.1.4
115
+ type: :development
116
+ prerelease: false
117
+ version_requirements: *id007
118
+ - !ruby/object:Gem::Dependency
119
+ name: treetop
120
+ requirement: &id008 !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ segments:
126
+ - 1
127
+ - 4
128
+ - 9
129
+ version: 1.4.9
130
+ type: :development
131
+ prerelease: false
132
+ version_requirements: *id008
133
+ - !ruby/object:Gem::Dependency
134
+ name: shoulda
135
+ requirement: &id009 !ruby/object:Gem::Requirement
136
+ none: false
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ segments:
141
+ - 0
142
+ version: "0"
143
+ type: :development
144
+ prerelease: false
145
+ version_requirements: *id009
146
+ - !ruby/object:Gem::Dependency
147
+ name: bundler
148
+ requirement: &id010 !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ~>
152
+ - !ruby/object:Gem::Version
153
+ segments:
154
+ - 1
155
+ - 0
156
+ - 0
157
+ version: 1.0.0
158
+ type: :development
159
+ prerelease: false
160
+ version_requirements: *id010
161
+ - !ruby/object:Gem::Dependency
162
+ name: jeweler
163
+ requirement: &id011 !ruby/object:Gem::Requirement
164
+ none: false
165
+ requirements:
166
+ - - ~>
167
+ - !ruby/object:Gem::Version
168
+ segments:
169
+ - 1
170
+ - 5
171
+ - 2
172
+ version: 1.5.2
173
+ type: :development
174
+ prerelease: false
175
+ version_requirements: *id011
176
+ - !ruby/object:Gem::Dependency
177
+ name: rcov
178
+ requirement: &id012 !ruby/object:Gem::Requirement
179
+ none: false
180
+ requirements:
181
+ - - ">="
182
+ - !ruby/object:Gem::Version
183
+ segments:
184
+ - 0
185
+ version: "0"
186
+ type: :development
187
+ prerelease: false
188
+ version_requirements: *id012
189
+ - !ruby/object:Gem::Dependency
190
+ name: guard-test
191
+ requirement: &id013 !ruby/object:Gem::Requirement
192
+ none: false
193
+ requirements:
194
+ - - ~>
195
+ - !ruby/object:Gem::Version
196
+ segments:
197
+ - 0
198
+ - 1
199
+ - 4
200
+ version: 0.1.4
201
+ type: :development
202
+ prerelease: false
203
+ version_requirements: *id013
204
+ - !ruby/object:Gem::Dependency
205
+ name: treetop
206
+ requirement: &id014 !ruby/object:Gem::Requirement
207
+ none: false
208
+ requirements:
209
+ - - ~>
210
+ - !ruby/object:Gem::Version
211
+ segments:
212
+ - 1
213
+ - 4
214
+ - 9
215
+ version: 1.4.9
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: *id014
219
+ - !ruby/object:Gem::Dependency
220
+ name: activesupport
221
+ requirement: &id015 !ruby/object:Gem::Requirement
222
+ none: false
223
+ requirements:
224
+ - - ~>
225
+ - !ruby/object:Gem::Version
226
+ segments:
227
+ - 3
228
+ - 0
229
+ version: "3.0"
230
+ type: :runtime
231
+ prerelease: false
232
+ version_requirements: *id015
233
+ description: Crapshoot is a dice-rolling gem that parses complicated notation for most of your pen-and-paper gaming needs.
234
+ email: bkerley@brycekerley.net
235
+ executables: []
236
+
237
+ extensions: []
238
+
239
+ extra_rdoc_files:
240
+ - LICENSE.txt
241
+ - README.rdoc
242
+ files:
243
+ - .document
244
+ - Gemfile
245
+ - Gemfile.lock
246
+ - Guardfile
247
+ - LICENSE.txt
248
+ - README.rdoc
249
+ - Rakefile
250
+ - VERSION
251
+ - crapshoot.gemspec
252
+ - lib/crapshoot.rb
253
+ - lib/crapshoot/evaluator.rb
254
+ - lib/crapshoot/parser/scan.rb
255
+ - lib/crapshoot/parser/scan.rl
256
+ - lib/crapshoot/postfixer.rb
257
+ - lib/crapshoot/scanner.rb
258
+ - lib/crapshoot/tokens/arithmetic.rb
259
+ - lib/crapshoot/tokens/base.rb
260
+ - lib/crapshoot/tokens/constant.rb
261
+ - lib/crapshoot/tokens/series.rb
262
+ - test/helper.rb
263
+ - test/test_crapshoot.rb
264
+ - test/test_evaluator.rb
265
+ - test/test_postfixer.rb
266
+ - test/test_scanner.rb
267
+ has_rdoc: true
268
+ homepage: http://github.com/bkerley/crapshoot
269
+ licenses:
270
+ - MIT
271
+ post_install_message:
272
+ rdoc_options: []
273
+
274
+ require_paths:
275
+ - lib
276
+ required_ruby_version: !ruby/object:Gem::Requirement
277
+ none: false
278
+ requirements:
279
+ - - ">="
280
+ - !ruby/object:Gem::Version
281
+ hash: -352312256047208438
282
+ segments:
283
+ - 0
284
+ version: "0"
285
+ required_rubygems_version: !ruby/object:Gem::Requirement
286
+ none: false
287
+ requirements:
288
+ - - ">="
289
+ - !ruby/object:Gem::Version
290
+ segments:
291
+ - 0
292
+ version: "0"
293
+ requirements: []
294
+
295
+ rubyforge_project:
296
+ rubygems_version: 1.3.7
297
+ signing_key:
298
+ specification_version: 3
299
+ summary: The alpha and omega of rolling dice
300
+ test_files:
301
+ - test/helper.rb
302
+ - test/test_crapshoot.rb
303
+ - test/test_evaluator.rb
304
+ - test/test_postfixer.rb
305
+ - test/test_scanner.rb