crapshoot 0.1.0

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