elus 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,124 @@
1
+ require File.join(File.dirname(__FILE__), ".." ,"spec_helper" )
2
+
3
+ module Elus
4
+ include ElusTest
5
+
6
+ shared_examples_for 'all generators' do
7
+ it 'should generate rules array' do
8
+ generator = described_class.new
9
+ rules = generator.generate_rules
10
+ rules.class.should == Array
11
+ rules.each {|rule| rule.class.should == Rule} unless rules.empty?
12
+ end
13
+ end
14
+
15
+ describe Generator do
16
+ it_should_behave_like 'all generators'
17
+ end
18
+
19
+ describe EmptyGenerator do
20
+ it_should_behave_like 'all generators'
21
+
22
+ it 'should generate empty rules set' do
23
+ generator = EmptyGenerator.new
24
+ generator.generate_rules.should == []
25
+ end
26
+ end
27
+
28
+ describe Turn1Generator do
29
+ before :each do
30
+ @generator = Turn1Generator.new
31
+ @rules = @generator.generate_rules
32
+ end
33
+
34
+ it_should_behave_like 'all generators'
35
+
36
+ it 'should generate non-empty rules set' do
37
+ @rules.should_not be_empty
38
+ end
39
+
40
+ it 'should generate rules with Any condition and without "no" branch' do
41
+ @rules.each do |rule|
42
+ rule.name.should =~ /If last Piece is Any Piece/
43
+ rule.name.should_not =~ /otherwise/
44
+ end
45
+ end
46
+
47
+ it 'should generate only rules with valid(.X.) Pieces in "yes" branch' do
48
+ @rules.count.should == 12
49
+ permutate do |code|
50
+ piece = Piece.create(code)
51
+ re = Regexp.new(', ' + piece.name + ' Piece is next')
52
+ @rules.count {|rule| rule.name =~ re}.should == 1
53
+ end
54
+ end
55
+ end
56
+
57
+ describe Turn2Generator do
58
+ before :each do
59
+ @generator = Turn2Generator.new
60
+ @rules = @generator.generate_rules
61
+ end
62
+
63
+ it_should_behave_like 'all generators'
64
+
65
+ it 'should generate non-empty rules set' do
66
+ @rules.should_not be_empty
67
+ end
68
+
69
+ it 'should generate rules without Any condition and with obligatory "no" branch' do
70
+ @rules.each do |rule|
71
+ rule.name.should_not =~ /If last Piece is Any Piece/
72
+ rule.name.should =~ /otherwise/
73
+ end
74
+ end
75
+
76
+ it 'should generate only rules with opposite yes-no branches' do
77
+ @rules.count.should == 36
78
+ permutate('1') do |cond_code|
79
+ cond = Piece.create(cond_code)
80
+ permutate do |yes_code|
81
+ yes = Piece.create(yes_code)
82
+ no = Piece.create(Piece.different(yes_code))
83
+ re = Regexp.new("If last Piece is #{cond.name} Piece, #{yes.name} Piece is next, otherwise #{no.name} Piece is next")
84
+ @rules.count {|rule| rule.name =~ re}.should == 1
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ describe Turn3Generator do
91
+ before :each do
92
+ @generator = Turn3Generator.new
93
+ @rules = @generator.generate_rules
94
+ end
95
+
96
+ it_should_behave_like 'all generators'
97
+
98
+ it 'should generate non-empty rules set' do
99
+ @rules.should_not be_empty
100
+ end
101
+
102
+ it 'should generate rules without Any condition and with obligatory "no" branch' do
103
+ @rules.each do |rule|
104
+ rule.name.should_not =~ /If last Piece is Any Piece/
105
+ rule.name.should =~ /otherwise/
106
+ end
107
+ end
108
+
109
+ it 'should generate rules with all possible yes-no branches' do
110
+ @rules.count.should == 432
111
+ permutate('1') do |cond_code|
112
+ cond = Piece.create(cond_code)
113
+ permutate do |yes_code|
114
+ yes = Piece.create(yes_code)
115
+ permutate do |no_code|
116
+ no = Piece.create(no_code)
117
+ re = Regexp.new("If last Piece is #{cond.name} Piece, #{yes.name} Piece is next, otherwise #{no.name} Piece is next")
118
+ @rules.count {|rule| rule.name =~ re}.should == 1
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,190 @@
1
+ require File.join(File.dirname(__FILE__), ".." ,"spec_helper" )
2
+
3
+ module Elus
4
+ include ElusTest
5
+
6
+ describe Piece do
7
+ context "creating" do
8
+ it "should raise error if new method is called" do
9
+ CODES.each_key do |code|
10
+ lambda{Piece.new(code)}.should raise_error(NoMethodError)
11
+ end
12
+ WRONG_CODES.each do |code|
13
+ lambda{Piece.new(code)}.should raise_error(NoMethodError)
14
+ end
15
+ end
16
+
17
+ it "should return nil for a wrong code" do
18
+ WRONG_CODES.each do |code|
19
+ Piece.create(code).should == nil
20
+ end
21
+ Piece.create(nil).should == nil
22
+ Piece.create('').should == nil
23
+ Piece.create(1).should == nil
24
+ Piece.create(3.14).should == nil
25
+ Piece.create(:byd).should == nil
26
+ Piece.create([]).should == nil
27
+ Piece.create([1,2,3]).should == nil
28
+ Piece.create({:symb=>'something'}).should == nil
29
+ end
30
+
31
+ it "should have code and name consistent with an input code" do
32
+ CODES.each do |code, name|
33
+ Piece.create(code).name.should == name
34
+ Piece.create(code).code.should == Piece.convert_code(code)
35
+ end
36
+ end
37
+
38
+ it "should drop irrelevant characters from code" do
39
+ Piece.create('b y d ').name.should == BYD
40
+ Piece.create('yfdzb').name.should == BYD
41
+ Piece.create('AEFHIJKLMNOPQTUVWXZaefhijklmnopqtuvwxz byd').name.should == BYD
42
+ Piece.create('byd23456789~@#$%^&*()_+-?><{}[],|/`').name.should == BYD
43
+ end
44
+
45
+ it "should drop duplicate characters from code" do
46
+ Piece.create('byyd').name.should == BYD
47
+ Piece.create('DDyb').name.should == BYD
48
+ Piece.create('AEFHIJbbbbbbbbbbbbbbbbbbbbyd').name.should == BYD
49
+ Piece.create('byddd').name.should == BYD
50
+ Piece.create('byrd').name.should == BYD
51
+ Piece.create('bydrrdrrrrdr').name.should == BYD
52
+ end
53
+
54
+ it "should sort code letter if they are given in wrong order" do
55
+ Piece.create('ydb').name.should == BYD
56
+ Piece.create('ybd').name.should == BYD
57
+ Piece.create('dBy').name.should == BYD
58
+ Piece.create('Dby').name.should == BYD
59
+ Piece.create('dyb').name.should == BYD
60
+ Piece.create('Bdy').name.should == BYD
61
+ end
62
+
63
+ it "should not sort code letter mixed with numbers or specials but return nil" do
64
+ Piece.create('1dy').should == nil
65
+ Piece.create('d1b').should == nil
66
+ Piece.create('yb0').should == nil
67
+ Piece.create('!dy').should == nil
68
+ Piece.create('d=b').should == nil
69
+ Piece.create('yb.').should == nil
70
+ end
71
+
72
+ it "should create Pieces from codes with special meaning" do
73
+ SPECIAL_CODES.each do |code, name|
74
+ Piece.create(code).name.should == name
75
+ end
76
+ end
77
+ end
78
+
79
+ context 'comparing' do
80
+ it 'should match its code, self and Piece with the same code' do
81
+ (CODES.merge SPECIAL_CODES).each do |code, name|
82
+ piece1 = Piece.create(code)
83
+ piece2 = Piece.create(code)
84
+ should_be_equal(piece1, code)
85
+ should_be_equal(piece1, piece1)
86
+ should_be_equal(piece1, piece2)
87
+ end
88
+ end
89
+
90
+ it 'should be reciprocal' do
91
+ all_chars_twice do |code1, code2|
92
+ piece1 = Piece.create(code1)
93
+ piece2 = Piece.create(code2)
94
+ should_be_equal(piece2, piece1) if piece1 == piece2
95
+ should_be_equal(piece1, piece2) if piece2 == piece1
96
+ end
97
+ end
98
+
99
+ it 'should match dots in all positions' do
100
+ all_chars do |code|
101
+ c1,c2,c3 = code.split(//)
102
+ piece = Piece.create(c1+c2+c3)
103
+ should_be_equal(piece, '.'+c2+c3)
104
+ should_be_equal(piece, Piece.create('.'+c2+c3))
105
+ should_be_equal(Piece.create('.'+c2+c3), piece)
106
+ should_be_equal(piece, c1+'.'+c3)
107
+ should_be_equal(piece, Piece.create(c1+'.'+c3))
108
+ should_be_equal(Piece.create(c1+'.'+c3), piece)
109
+ should_be_equal(piece, c1+c2+'.')
110
+ should_be_equal(piece, Piece.create(c1+c2+'.'))
111
+ should_be_equal(Piece.create(c1+c2+'.'), piece)
112
+ end
113
+ end
114
+ it 'should support eql? comparison (based on code)' do
115
+ all_chars_twice do |code1, code2|
116
+ piece1 = Piece.create(code1)
117
+ piece2 = Piece.create(code2)
118
+ (piece1.eql? piece2).should be_true if piece1.code == piece2.code
119
+ (piece2.eql? piece1).should be_true if piece1.code == piece2.code
120
+ end
121
+ end
122
+
123
+ it "should be bigger than nil" do
124
+ all_chars('01=!.') do |code|
125
+ (Piece.create(code) > nil).should be_true
126
+ end
127
+ end
128
+ end
129
+
130
+ context 'multiplying Piece by mask (finding next Piece)' do
131
+ it 'should copy mask if the mask contains only numbers OR dots' do
132
+ all_chars_twice('01.=!', '01.') do |code1, code2|
133
+ piece = Piece.create(code1)
134
+ mask = Piece.create(code2)
135
+ should_be_equal(piece * mask, mask)
136
+ end
137
+ end
138
+
139
+ it 'should convert mask if mask contains special chars' do
140
+ all_chars_twice do |orig_code, mask_code|
141
+ expected = []
142
+ mask_code.split(//).each_with_index do |char,i|
143
+ expected[i] = case char
144
+ when '0' then '0'
145
+ when '1' then '1'
146
+ when '.' then '.'
147
+ when '=' then orig_code[i]
148
+ when '!' then Piece.different(orig_code[i])
149
+ end
150
+ end
151
+ expected_code = expected.join('')
152
+ piece = Piece.create(orig_code)
153
+ mask = Piece.create(mask_code)
154
+ should_be_equal(piece * mask, expected_code)
155
+ should_be_equal(piece * mask, Piece.create(expected_code))
156
+ end
157
+ end
158
+ end
159
+
160
+ context 'assorted class methods' do
161
+ it 'should be able to return universal (Any) Piece' do
162
+ any = Piece.any
163
+ any.name.should == 'Any'
164
+ all_chars do |code|
165
+ piece = Piece.create(code)
166
+ should_be_equal(piece, any)
167
+ (piece * any).name.should == 'Any'
168
+ end
169
+ end
170
+
171
+ it 'should be able to convert code to different (opposite)' do
172
+ all_chars do |code|
173
+ expected = []
174
+ code.split(//).each_with_index do |char,i|
175
+ expected[i] = case char
176
+ when '0' then '1'
177
+ when '1' then '0'
178
+ when '.' then '.'
179
+ when '=' then '!'
180
+ when '!' then '='
181
+ end
182
+ end
183
+ expected_code = expected.join('')
184
+ Piece.different(code).should == expected_code
185
+ end
186
+ end
187
+ end
188
+
189
+ end
190
+ end
@@ -0,0 +1,39 @@
1
+ require File.join(File.dirname(__FILE__), ".." ,"spec_helper" )
2
+
3
+ module Elus
4
+ include ElusTest
5
+
6
+ describe Rule do
7
+ context 'creating' do
8
+ it 'should create appropriate Rules given correct arguments' do
9
+ RULES.each do |args, name|
10
+ rule = Rule.new(*args)
11
+ rule.name.should == name
12
+ end
13
+ end
14
+ it 'should fail if given wrong arguments' do
15
+ WRONG_RULES.each do |args|
16
+ lambda{Rule.new(*args)}.should raise_error(Invalid)
17
+ end
18
+ end
19
+ end
20
+ context 'applying Rule to Pieces' do
21
+ it 'should produce expected outcome when applied to any Piece' do
22
+ all_chars_twice('01.') do |piece, condition|
23
+ p = Piece.create(piece)
24
+ c = Piece.create(condition)
25
+ random_chars_twice 100 do |yes, no|
26
+ y = Piece.create(yes)
27
+ n = Piece.create(no)
28
+ rule = Rule.new(c, y, n)
29
+ if p == c
30
+ should_be_equal(rule.apply(p), p * y)
31
+ else
32
+ should_be_equal(rule.apply(p), p * n)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,203 @@
1
+ require File.join(File.dirname(__FILE__), ".." ,"spec_helper" )
2
+
3
+ module Elus
4
+ include ElusTest
5
+
6
+ def create_solver(options={})
7
+ @stdout = options[:stdout] || mock('stdout').as_null_object
8
+ @stdin = options[:stdin] || mock('stdin')
9
+ @stdin.stub(:gets).and_return(*options[:input]) if options[:input]
10
+ @solver = Solver.new(@stdin, @stdout)
11
+ end
12
+
13
+ def start_solver(options={})
14
+ create_solver(options)
15
+ @solver.start(options[:generator] || stub('generator', :generate_rules => []))
16
+ end
17
+
18
+ # sets expectation for stdout to receive strictly ordered sequence of exact messages
19
+ def stdout_should_receive(*messages)
20
+ messages.each do |message|
21
+ @stdout.should_receive(:puts).with(message).once.ordered
22
+ end
23
+ end
24
+
25
+ # sets expectation for stdout to receive message(s) containing all of the patterns (unordered)
26
+ def stdout_should_include(*patterns)
27
+ patterns.each do |pattern|
28
+ re = Regexp === pattern ? pattern : Regexp.new(Regexp.escape(pattern))
29
+ @stdout.should_receive(:puts).with(re).at_least(:once)
30
+ end
31
+ end
32
+
33
+ describe Solver do
34
+ context 'starting up' do
35
+ it 'should send a welcome message' do
36
+ create_solver
37
+ stdout_should_receive("Welcome to Elus Solver!")
38
+ @solver.start(stub('generator', :generate_rules => []))
39
+ end
40
+
41
+ it 'should prompt for Game state' do
42
+ create_solver
43
+ stdout_should_receive("Enter Game state:")
44
+ @solver.start(stub('generator', :generate_rules => []))
45
+ end
46
+ end
47
+
48
+ context 'inputing single Piece' do
49
+ it 'should return corresponding Piece for each correct code entered' do
50
+ CODES.each do |code, name|
51
+ start_solver
52
+ @stdin.stub(:gets).and_return(code)
53
+ piece = @solver.input_piece
54
+ piece.name.should == name
55
+ end
56
+ end
57
+ it 'should inform about invalid codes and return nil if interrupted' do
58
+ WRONG_CODES.each do |code|
59
+ start_solver
60
+ @stdin.stub(:gets).and_return(code, "\n")
61
+ stdout_should_receive("Invalid code: #{code}")
62
+ @solver.input_piece.should == nil
63
+ end
64
+ end
65
+ end
66
+
67
+ # context 'inputing Game state as a codestring' do
68
+ # it 'should ignore wrong piece codes' do
69
+ # WRONG_CODES.each do |code|
70
+ # start_solver
71
+ # stdout_should_receive("Free:\n#{BYD}\n#{BYD}\n#{BYD}\nBoard:\n#{BYD}\n#{BYD}\n#{BYD}\n")
72
+ # @solver.input_state(code+"\n"+"BYD\n"*6)
73
+ # end
74
+ # end
75
+ # it 'should output matching Piece name' do
76
+ # CODES.each do |code, name|
77
+ # start_solver
78
+ # stdout_should_include(name)
79
+ # @solver.input_state("#{code}\n"+"BYD\n"*5)
80
+ # end
81
+ # end
82
+ # it 'should raise exception if not enough Pieces' do
83
+ # (0..5).each do |n|
84
+ # start_solver
85
+ # lambda{@solver.input_state("BYD\n"*n)}.should raise_error(Invalid)
86
+ # end
87
+ # end
88
+ # it 'should output correct Game state' do
89
+ # CODES.each do |code, name|
90
+ # start_solver
91
+ # stdout_should_receive("Free:\n#{name}\n#{BYD}\n#{BYD}\nBoard:\n#{BYD}\n#{BYD}\n#{BYD}\n")
92
+ # @solver.input_state("#{code}\n#"+"BYD\n"*5)
93
+ # end
94
+ # end
95
+ # end
96
+
97
+ context 'inputing Game state from stdin' do
98
+ it 'should report wrong piece codes' do
99
+ WRONG_CODES.each do |code|
100
+ start_solver(:input => ["BYD\n", "BYD\n", "BYD\n", "BYD\n", "BYD\n", "BYD\n", code+"\n", "\n"])
101
+ stdout_should_receive("Invalid code: #{code}\n")
102
+ @solver.input_state
103
+ end
104
+ end
105
+ it 'should output matching Piece name' do
106
+ CODES.each do |code, name|
107
+ start_solver(:input => [code+"\n", "BYD\n", "BYD\n", "BYD\n", "BYD\n", "BYD\n", "\n"])
108
+ stdout_should_include(name)
109
+ @solver.input_state
110
+ end
111
+ end
112
+ it 'should raise exception if not enough Pieces' do
113
+ (0..5).each do |n|
114
+ start_solver(:input => [* Array.new(n,"BYD\n") << "\n"])
115
+ lambda{@solver.input_state}.should raise_error(Invalid)
116
+ end
117
+ end
118
+ it 'should output prompts, separate piece feedback and then correct Game state' do
119
+ CODES.each do |code, name|
120
+ start_solver(:input => [code+"\n", "BYD\n", "BYD\n", "BYD\n", "BYD\n", "BYD\n", "\n"])
121
+ stdout_should_receive("Enter Free Piece code (1):", "You entered Free Piece (1): #{name}",
122
+ "Enter Free Piece code (2):", "You entered Free Piece (2): #{BYD}",
123
+ "Enter Free Piece code (3):", "You entered Free Piece (3): #{BYD}",
124
+ "Enter Board Piece code (1):", "You entered Board Piece (1): #{BYD}",
125
+ "Enter Board Piece code (2):", "You entered Board Piece (2): #{BYD}",
126
+ "Enter Board Piece code (3):", "You entered Board Piece (3): #{BYD}")
127
+ @solver.input_state
128
+ end
129
+ end
130
+ end
131
+
132
+ context 'showing state/hints' do
133
+ it 'should correctly output Game state before making move' do
134
+ CODES.each do |code, name|
135
+ start_solver(:input => [code+"\n", "BYD\n", "BYD\n", "BYD\n", "BYD\n", "BYD\n", "\n"])
136
+ @solver.input_state
137
+ stdout_should_receive("Free:\n#{name}\n#{BYD}\n#{BYD}\nBoard:\n#{BYD}\n#{BYD}\n#{BYD}\n")
138
+ @solver.make_move
139
+ end
140
+ end
141
+
142
+ it 'should output zero Rules when making moves in inconsistent (wrong) Game state' do
143
+ start_solver(:generator => Turn1Generator.new, :input => ["BYD\n", "BYD\n", "BYD\n", "BYD\n", "BYD\n", "BYD\n", "BYD\n", "\n"])
144
+ stdout_should_include(/Rules\(0\):\s*Moves\(0\):/)
145
+ @solver.input_state
146
+ @solver.make_move
147
+ end
148
+
149
+ it 'should output possible game Rules wheh given correct Game state' do
150
+ start_solver(:generator => Turn1Generator.new, :input => ["BGC\n", "sgd\n", "syc\n", "BYD\n", "SYD\n", "BGD\n", "\n"])
151
+ stdout_should_include \
152
+ "Rules(2):
153
+ If last Piece is Any Piece, Diamond Piece is next
154
+ If last Piece is Any Piece, Same shape Piece is next
155
+ Moves(1):
156
+ Small Green Diamond(2)"
157
+ @solver.input_state
158
+ @solver.make_move
159
+ end
160
+ end
161
+
162
+ context 'making move' do
163
+ it 'should prompt to make move' do
164
+ start_solver(:generator => Turn1Generator.new, :input => ["BGC\n", "sgd\n", "syc\n", "BYD\n", "SYD\n", "BGD\n", "\n"])
165
+ stdout_should_include("Make your move:")
166
+ @solver.input_state
167
+ @solver.make_move
168
+ end
169
+
170
+ it 'should output move feedback for incorrect move (invalid code, interrupt)' do
171
+ start_solver(:generator => Turn1Generator.new, :input => ["BGC\n", "sgd\n", "syc\n", "BYD\n", "SYD\n", "BGD\n", "\n"])
172
+ @solver.input_state
173
+ @stdin.stub(:gets).and_return("Saa","\n","SYC","SYD","BYC")
174
+ stdout_should_include("Make your move:", "Wrong move (no piece given)")
175
+ @solver.make_move
176
+ end
177
+
178
+ it 'should output move feedback for incorrect move (valid code, but not in free set)' do
179
+ start_solver(:generator => Turn1Generator.new, :input => ["BGC\n", "sgd\n", "syc\n", "BYD\n", "SYD\n", "BGD\n", "\n"])
180
+ @solver.input_state
181
+ @stdin.stub(:gets).and_return("BGD","N","SYC","SYD","BYC")
182
+ stdout_should_include("Make your move:", "Wrong move (not in free set): Big Green Diamond")
183
+ @solver.make_move
184
+ end
185
+
186
+ it 'should output move feedback for right move' do
187
+ start_solver(:generator => Turn1Generator.new, :input => ["BGC\n", "sgd\n", "syc\n", "BYD\n", "SYD\n", "BGD\n", "\n"])
188
+ @solver.input_state
189
+ @stdin.stub(:gets).and_return("SGD","Y","SYC","SYD","BYC")
190
+ stdout_should_include("Make your move:", "You moved: Small Green Diamond", "Was the move right(Y/N)?:", "Great, now enter new Free set:")
191
+ @solver.make_move
192
+ end
193
+
194
+ it 'should output move feedback for wrong move' do
195
+ start_solver(:generator => Turn1Generator.new, :input => ["BGC\n", "sgd\n", "syc\n", "BYD\n", "SYD\n", "BGD\n", "\n"])
196
+ @solver.input_state
197
+ @stdin.stub(:gets).and_return("SYC","N")
198
+ stdout_should_include("Make your move:", "You moved: Small Yellow Circle", "Was the move right(Y/N)?:", "Too bad")
199
+ @solver.make_move
200
+ end
201
+ end
202
+ end
203
+ end