elus 0.1.0

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