xo 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,10 +4,10 @@ module XO
4
4
 
5
5
  describe Evaluator do
6
6
 
7
- describe ".analyze" do
7
+ let (:evaluator) { Evaluator.instance }
8
+ let (:grid) { Grid.new }
8
9
 
9
- let (:evaluator) { Evaluator.instance }
10
- let (:grid) { Grid.new }
10
+ describe "#analyze" do
11
11
 
12
12
  describe "standard play" do
13
13
 
@@ -210,6 +210,11 @@ module XO
210
210
  positions: [[1, 3], [2, 2], [3, 1]]
211
211
  }]
212
212
  }
213
+
214
+ evaluator.analyze(grid, Grid::X).must_equal result
215
+
216
+ result[:type] = :loser
217
+ evaluator.analyze(grid, Grid::O).must_equal result
213
218
  end
214
219
  end
215
220
 
@@ -227,13 +232,6 @@ module XO
227
232
  end
228
233
  end
229
234
 
230
- describe "invalid token input" do
231
-
232
- it "raises ArgumentError" do
233
- proc { evaluator.analyze(Grid.new, :not_a_token) }.must_raise ArgumentError
234
- end
235
- end
236
-
237
235
  describe "invalid grid input" do
238
236
 
239
237
  it "returns too many moves ahead" do
@@ -256,6 +254,23 @@ module XO
256
254
  evaluator.analyze(grid, Grid::O).must_equal result
257
255
  end
258
256
  end
257
+
258
+ describe "invalid token input" do
259
+
260
+ it "raises ArgumentError" do
261
+ proc { evaluator.analyze(Grid.new, :not_a_token) }.must_raise ArgumentError
262
+ end
263
+ end
264
+ end
265
+
266
+ describe ".xos" do
267
+
268
+ it "returns the number of x's and o's" do
269
+ xs, os = Evaluator.xos(Grid.new('xoxxo'))
270
+
271
+ xs.must_equal 3
272
+ os.must_equal 2
273
+ end
259
274
  end
260
275
  end
261
276
  end
@@ -4,8 +4,6 @@ module XO::AI
4
4
 
5
5
  describe GeometricGrid do
6
6
 
7
- let(:grid) { GeometricGrid.new }
8
-
9
7
  describe "#rotate" do
10
8
 
11
9
  let (:grid) { GeometricGrid.new("xoooxxoxo") }
@@ -81,7 +79,7 @@ module XO::AI
81
79
  end
82
80
  end
83
81
 
84
- describe "equality" do
82
+ describe "#==" do
85
83
 
86
84
  it "is reflexive, symmetric and transitive" do
87
85
  a = GeometricGrid.new('x')
@@ -4,6 +4,8 @@ module XO
4
4
 
5
5
  describe Grid do
6
6
 
7
+ let(:grid) { Grid.new }
8
+
7
9
  it "defines X" do
8
10
  Grid::X.must_equal :x
9
11
  end
@@ -12,6 +14,10 @@ module XO
12
14
  Grid::O.must_equal :o
13
15
  end
14
16
 
17
+ it "defines EMPTY" do
18
+ Grid::EMPTY.must_equal :e
19
+ end
20
+
15
21
  it "has 3 rows" do
16
22
  Grid::ROWS.must_equal 3
17
23
  end
@@ -56,8 +62,8 @@ module XO
56
62
  end
57
63
 
58
64
  it "returns false otherwise" do
59
- [nil, :e, '', ' '].each do
60
- Grid.is_token?(:e).must_equal false
65
+ [nil, :e, '', ' '].each do |val|
66
+ Grid.is_token?(val).must_equal false
61
67
  end
62
68
  end
63
69
  end
@@ -77,16 +83,11 @@ module XO
77
83
  end
78
84
  end
79
85
 
80
- let(:grid) { Grid.new }
81
-
82
- describe "an initial grid" do
86
+ describe ".new" do
83
87
 
84
- it "is empty" do
88
+ it "is returns an empty grid when given no arguments" do
85
89
  grid.empty?.must_equal true
86
90
  end
87
- end
88
-
89
- describe "#new" do
90
91
 
91
92
  it "can create a grid from a string" do
92
93
  grid = Grid.new('xox o')
@@ -154,14 +155,14 @@ module XO
154
155
  end
155
156
 
156
157
  it "returns false when at least one position doesn't have a token" do
157
- grid[1, 3] = :e
158
+ grid[1, 3] = Grid::EMPTY
158
159
  grid.full?.must_equal false
159
160
  end
160
161
  end
161
162
 
162
163
  describe "#[]=" do
163
164
 
164
- it "sets a position to X, O or :e" do
165
+ it "sets a position to X, O or EMPTY" do
165
166
  grid[2, 1] = Grid::X
166
167
  grid[2, 1].must_equal Grid::X
167
168
 
@@ -169,7 +170,7 @@ module XO
169
170
  grid[2, 2].must_equal Grid::O
170
171
 
171
172
  grid[2, 3] = :anything_else
172
- grid[2, 3].must_equal :e
173
+ grid[2, 3].must_equal Grid::EMPTY
173
174
  end
174
175
 
175
176
  it "raises IndexError when the position is off the grid" do
@@ -211,10 +212,6 @@ module XO
211
212
 
212
213
  grid.empty?.must_equal true
213
214
  end
214
-
215
- it "returns self" do
216
- grid.clear.must_be_same_as grid
217
- end
218
215
  end
219
216
 
220
217
  describe "#each" do
data/xo.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.author = 'Dwayne R. Crooks'
10
10
  spec.email = ['me@dwaynecrooks.com']
11
11
  spec.summary = %q{A Ruby library for Tic-tac-toe.}
12
- spec.description = %q{A Ruby library that can be used to develop Tic-tac-toe game clients.}
12
+ spec.description = %q{A Ruby library for Tic-tac-toe that can be used to develop Tic-tac-toe game clients and servers.}
13
13
  spec.homepage = 'https://github.com/dwayne/xo'
14
14
  spec.license = 'MIT'
15
15
 
@@ -20,6 +20,8 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.required_ruby_version = '>= 1.9.3'
22
22
 
23
+ spec.add_runtime_dependency 'state_design_pattern', '~> 0.0.2'
24
+
23
25
  spec.add_development_dependency 'rake', '~> 10.3'
24
26
  spec.add_development_dependency 'minitest', '~> 5.3'
25
27
  spec.add_development_dependency 'coveralls', '~> 0.7'
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dwayne R. Crooks
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-08 00:00:00.000000000 Z
11
+ date: 2014-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: state_design_pattern
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 0.0.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 0.0.2
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rake
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -80,7 +94,8 @@ dependencies:
80
94
  - - ~>
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0.8'
83
- description: A Ruby library that can be used to develop Tic-tac-toe game clients.
97
+ description: A Ruby library for Tic-tac-toe that can be used to develop Tic-tac-toe
98
+ game clients and servers.
84
99
  email:
85
100
  - me@dwaynecrooks.com
86
101
  executables:
@@ -97,19 +112,26 @@ files:
97
112
  - Rakefile
98
113
  - bin/xo
99
114
  - lib/xo.rb
100
- - lib/xo/ai.rb
101
115
  - lib/xo/ai/geometric_grid.rb
116
+ - lib/xo/ai/max_player.rb
117
+ - lib/xo/ai/min_player.rb
102
118
  - lib/xo/ai/minimax.rb
119
+ - lib/xo/ai/player.rb
103
120
  - lib/xo/engine.rb
121
+ - lib/xo/engine/game_context.rb
122
+ - lib/xo/engine/game_over.rb
123
+ - lib/xo/engine/game_state.rb
124
+ - lib/xo/engine/init.rb
125
+ - lib/xo/engine/playing.rb
104
126
  - lib/xo/evaluator.rb
105
127
  - lib/xo/grid.rb
106
128
  - lib/xo/version.rb
129
+ - spec/engine_spec.rb
130
+ - spec/evaluator_spec.rb
131
+ - spec/geometric_grid_spec.rb
132
+ - spec/grid_spec.rb
133
+ - spec/minimax_spec.rb
107
134
  - spec/spec_helper.rb
108
- - spec/xo/ai/geometric_grid_spec.rb
109
- - spec/xo/ai/minimax_spec.rb
110
- - spec/xo/engine_spec.rb
111
- - spec/xo/evaluator_spec.rb
112
- - spec/xo/grid_spec.rb
113
135
  - xo.gemspec
114
136
  homepage: https://github.com/dwayne/xo
115
137
  licenses:
@@ -136,10 +158,10 @@ signing_key:
136
158
  specification_version: 4
137
159
  summary: A Ruby library for Tic-tac-toe.
138
160
  test_files:
161
+ - spec/engine_spec.rb
162
+ - spec/evaluator_spec.rb
163
+ - spec/geometric_grid_spec.rb
164
+ - spec/grid_spec.rb
165
+ - spec/minimax_spec.rb
139
166
  - spec/spec_helper.rb
140
- - spec/xo/ai/geometric_grid_spec.rb
141
- - spec/xo/ai/minimax_spec.rb
142
- - spec/xo/engine_spec.rb
143
- - spec/xo/evaluator_spec.rb
144
- - spec/xo/grid_spec.rb
145
167
  has_rdoc:
@@ -1,2 +0,0 @@
1
- require 'xo/ai/geometric_grid'
2
- require 'xo/ai/minimax'
@@ -1,331 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module XO
4
-
5
- describe Engine do
6
-
7
- let (:engine) { Engine.new }
8
-
9
- describe "its initial state" do
10
-
11
- it "is in the :init state" do
12
- engine.state.must_equal :init
13
- end
14
-
15
- it "is nobody's turn" do
16
- engine.turn.must_equal :nobody
17
- end
18
-
19
- it "has an empty grid" do
20
- engine.grid.empty?.must_equal true
21
- end
22
-
23
- it "has last event set to :new" do
24
- event = engine.last_event
25
-
26
- event[:name].must_equal :new
27
- end
28
-
29
- describe "#next_turn" do
30
-
31
- it "returns :nobody" do
32
- engine.next_turn.must_equal :nobody
33
- end
34
- end
35
- end
36
-
37
- describe "#grid" do
38
-
39
- it "returns a copy of the underlying grid" do
40
- grid = engine.grid
41
-
42
- grid[1, 1] = Grid::X
43
-
44
- engine.grid.open?(1, 1).must_equal true
45
- end
46
- end
47
-
48
- describe "how the state machine works" do
49
-
50
- describe "state :init" do
51
-
52
- it "is in state :init" do
53
- engine.state.must_equal :init
54
- end
55
-
56
- describe "#start" do
57
-
58
- before { engine.start(Grid::X) }
59
-
60
- it "changes state to :playing" do
61
- engine.state.must_equal :playing
62
- end
63
-
64
- it "sets turn to the value passed in" do
65
- engine.turn.must_equal Grid::X
66
- end
67
-
68
- it "starts with an empty grid" do
69
- engine.grid.empty?.must_equal true
70
- end
71
-
72
- it "sets last event" do
73
- event = engine.last_event
74
-
75
- event[:name].must_equal :game_started
76
- end
77
-
78
- describe "#next_turn" do
79
-
80
- it "returns O" do
81
- engine.next_turn.must_equal Grid::O
82
- end
83
- end
84
- end
85
-
86
- it "only allows #start to be called" do
87
- proc { engine.stop }.must_raise Engine::IllegalStateError
88
- proc { engine.play(1, 1) }.must_raise Engine::IllegalStateError
89
- proc { engine.continue_playing(Grid::X) }.must_raise Engine::IllegalStateError
90
- end
91
- end
92
-
93
- describe "state :playing" do
94
-
95
- before do
96
- engine.start(Grid::X)
97
- end
98
-
99
- it "is in state :playing" do
100
- engine.state.must_equal :playing
101
- end
102
-
103
- describe "#play" do
104
-
105
- describe "valid moves" do
106
-
107
- describe "when given (1, 1)" do
108
-
109
- before { engine.play(1, 1) }
110
-
111
- it "remains in state :playing" do
112
- engine.state.must_equal :playing
113
- end
114
-
115
- it "sets turn to O" do
116
- engine.turn.must_equal Grid::O
117
- end
118
-
119
- it "updates the grid at that position" do
120
- engine.grid[1, 1].must_equal Grid::X
121
- end
122
-
123
- it "sets last event" do
124
- event = engine.last_event
125
-
126
- event[:name].must_equal :next_turn
127
- event[:last_move][:turn].must_equal Grid::X
128
- event[:last_move][:r].must_equal 1
129
- event[:last_move][:c].must_equal 1
130
- end
131
- end
132
-
133
- describe "when the next move results in the game being over" do
134
-
135
- describe "winning" do
136
-
137
- before do
138
- engine.play(1, 1).play(2, 1).play(1, 2).play(2, 2).play(1, 3)
139
- end
140
-
141
- it "changes state to :game_over" do
142
- engine.state.must_equal :game_over
143
- end
144
-
145
- it "sets turn to the winner" do
146
- engine.turn.must_equal Grid::X
147
- end
148
-
149
- it "leaves the grid unchanged" do
150
- engine.grid.inspect.must_equal 'xxxoo '
151
- end
152
-
153
- it "sets last event" do
154
- event = engine.last_event
155
-
156
- event[:name].must_equal :game_over
157
- event[:type].must_equal :winner
158
- event[:last_move][:turn].must_equal Grid::X
159
- event[:last_move][:r].must_equal 1
160
- event[:last_move][:c].must_equal 3
161
- event[:details].must_equal [
162
- { where: :row, index: 1, positions: [[1, 1], [1, 2], [1, 3]] }
163
- ]
164
- end
165
- end
166
-
167
- describe "squashed" do
168
-
169
- before do
170
- engine.play(1, 1).play(1, 2).play(1, 3).play(2, 2).play(3, 2).play(2, 1).play(2, 3).play(3, 3).play(3, 1)
171
- end
172
-
173
- it "changes state to :game_over" do
174
- engine.state.must_equal :game_over
175
- end
176
-
177
- it "leaves turn set to the last one played" do
178
- engine.turn.must_equal Grid::X
179
- end
180
-
181
- it "leaves the grid unchanged" do
182
- engine.grid.inspect.must_equal 'xoxooxxxo'
183
- end
184
-
185
- it "sets last event" do
186
- event = engine.last_event
187
-
188
- event[:name].must_equal :game_over
189
- event[:type].must_equal :squashed
190
- event[:last_move][:turn].must_equal Grid::X
191
- event[:last_move][:r].must_equal 3
192
- event[:last_move][:c].must_equal 1
193
- end
194
- end
195
- end
196
- end
197
-
198
- describe "invalid moves" do
199
-
200
- describe "when given (0, 0)" do
201
-
202
- it "sets last event to out of bounds" do
203
- event = engine.play(0, 0).last_event
204
-
205
- event[:name].must_equal :invalid_move
206
- event[:type].must_equal :out_of_bounds
207
- end
208
- end
209
-
210
- describe "when given (1, 1) and it already has a token there" do
211
-
212
- it "sets last event to occupied" do
213
- event = engine.play(1, 1).play(1, 1).last_event
214
-
215
- event[:name].must_equal :invalid_move
216
- event[:type].must_equal :occupied
217
- end
218
- end
219
- end
220
- end
221
-
222
- describe "#stop" do
223
-
224
- before { engine.stop }
225
-
226
- it "changes state to :init" do
227
- engine.state.must_equal :init
228
- end
229
-
230
- it "sets turn to :nobody" do
231
- engine.turn.must_equal :nobody
232
- end
233
-
234
- it "clears the grid" do
235
- engine.grid.empty?.must_equal true
236
- end
237
-
238
- it "sets last event" do
239
- event = engine.last_event
240
-
241
- event[:name].must_equal :game_stopped
242
- end
243
- end
244
-
245
- it "only allows #play and #stop to be called" do
246
- proc { engine.start(Grid::O) }.must_raise Engine::IllegalStateError
247
- proc { engine.continue_playing(Grid::O) }.must_raise Engine::IllegalStateError
248
- end
249
- end
250
-
251
- describe "state :game_over" do
252
-
253
- before do
254
- engine
255
- .start(Grid::O)
256
- .play(1, 1).play(2, 1).play(1, 2).play(2, 2).play(1, 3)
257
- end
258
-
259
- it "is in state :game_over" do
260
- engine.state.must_equal :game_over
261
- end
262
-
263
- describe "#continue_playing" do
264
-
265
- before { engine.continue_playing(Grid::O) }
266
-
267
- it "changes state to :playing" do
268
- engine.state.must_equal :playing
269
- end
270
-
271
- it "sets turn to O" do
272
- engine.turn.must_equal Grid::O
273
- end
274
-
275
- it "clears the grid" do
276
- engine.grid.empty?.must_equal true
277
- end
278
-
279
- it "sets last event" do
280
- event = engine.last_event
281
-
282
- event[:name].must_equal :game_started
283
- event[:type].must_equal :continue_playing
284
- end
285
- end
286
-
287
- describe "#stop" do
288
-
289
- before { engine.stop }
290
-
291
- it "changes state to :init" do
292
- engine.state.must_equal :init
293
- end
294
-
295
- it "sets turn to :nobody" do
296
- engine.turn.must_equal :nobody
297
- end
298
-
299
- it "clears the grid" do
300
- engine.grid.empty?.must_equal true
301
- end
302
-
303
- it "sets last event" do
304
- event = engine.last_event
305
-
306
- event[:name].must_equal :game_stopped
307
- end
308
- end
309
-
310
- it "only allows #continue_playing and #stop to be called" do
311
- proc { engine.start(Grid::X) }.must_raise Engine::IllegalStateError
312
- proc { engine.play(1, 1) }.must_raise Engine::IllegalStateError
313
- end
314
- end
315
- end
316
-
317
- describe "#start with invalid input" do
318
-
319
- it "raises an ArgumentError" do
320
- proc { engine.start(:invalid_input) }.must_raise ArgumentError
321
- end
322
- end
323
-
324
- describe "#continue_playing with invalid input" do
325
-
326
- it "raises an ArgumentError" do
327
- proc { engine.continue_playing(:invalid_input) }.must_raise ArgumentError
328
- end
329
- end
330
- end
331
- end