xo 0.0.1 → 1.0.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.
- checksums.yaml +13 -5
- data/.gitignore +4 -0
- data/.travis.yml +3 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +43 -0
- data/README.md +92 -27
- data/Rakefile +2 -0
- data/bin/xo +314 -0
- data/lib/xo.rb +0 -21
- data/lib/xo/ai.rb +1 -3
- data/lib/xo/ai/geometric_grid.rb +113 -0
- data/lib/xo/ai/minimax.rb +187 -89
- data/lib/xo/engine.rb +187 -68
- data/lib/xo/evaluator.rb +137 -62
- data/lib/xo/grid.rb +153 -24
- data/lib/xo/version.rb +1 -1
- data/spec/spec_helper.rb +3 -0
- data/spec/xo/ai/geometric_grid_spec.rb +137 -0
- data/spec/xo/ai/minimax_spec.rb +56 -36
- data/spec/xo/engine_spec.rb +296 -20
- data/spec/xo/evaluator_spec.rb +210 -39
- data/spec/xo/grid_spec.rb +198 -55
- data/xo.gemspec +9 -2
- metadata +63 -27
- data/lib/xo/ai/advanced_beginner.rb +0 -17
- data/lib/xo/ai/expert.rb +0 -64
- data/lib/xo/ai/novice.rb +0 -11
data/spec/xo/grid_spec.rb
CHANGED
@@ -4,135 +4,231 @@ module XO
|
|
4
4
|
|
5
5
|
describe Grid do
|
6
6
|
|
7
|
-
it
|
7
|
+
it "defines X" do
|
8
|
+
Grid::X.must_equal :x
|
9
|
+
end
|
10
|
+
|
11
|
+
it "defines O" do
|
12
|
+
Grid::O.must_equal :o
|
13
|
+
end
|
14
|
+
|
15
|
+
it "has 3 rows" do
|
8
16
|
Grid::ROWS.must_equal 3
|
9
17
|
end
|
10
18
|
|
11
|
-
it
|
19
|
+
it "has 3 columns" do
|
12
20
|
Grid::COLS.must_equal 3
|
13
21
|
end
|
14
22
|
|
15
|
-
it
|
16
|
-
|
17
|
-
|
18
|
-
|
23
|
+
it "has 9 positions" do
|
24
|
+
Grid::N.must_equal 9
|
25
|
+
end
|
26
|
+
|
27
|
+
describe ".contains?" do
|
28
|
+
|
29
|
+
it "contains all r, c where r is in {1, 2, 3} and c is in {1, 2, 3}" do
|
30
|
+
(1..3).each do |r|
|
31
|
+
(1..3).each do |c|
|
32
|
+
Grid.contains?(r, c).must_equal true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "does not contain any r, c where either r is not in {1, 2, 3} or c is not in {1, 2, 3}" do
|
38
|
+
[[0, 0], [0, 1], [0, 2], [0, 3], [0, 4],
|
39
|
+
[1, 0], [1, 4],
|
40
|
+
[2, 0], [2, 4],
|
41
|
+
[3, 0], [3, 4],
|
42
|
+
[4, 0], [4, 1], [4, 2], [4, 3], [4, 4]].each do |pos|
|
43
|
+
Grid.contains?(*pos).must_equal false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe ".is_token?" do
|
49
|
+
|
50
|
+
it "returns true for an X" do
|
51
|
+
Grid.is_token?(Grid::X).must_equal true
|
52
|
+
end
|
53
|
+
|
54
|
+
it "returns true for an O" do
|
55
|
+
Grid.is_token?(Grid::O).must_equal true
|
56
|
+
end
|
57
|
+
|
58
|
+
it "returns false otherwise" do
|
59
|
+
[nil, :e, '', ' '].each do
|
60
|
+
Grid.is_token?(:e).must_equal false
|
19
61
|
end
|
20
62
|
end
|
21
63
|
end
|
22
64
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
65
|
+
describe ".other_token" do
|
66
|
+
|
67
|
+
it "returns O given X" do
|
68
|
+
Grid.other_token(Grid::X).must_equal Grid::O
|
69
|
+
end
|
70
|
+
|
71
|
+
it "returns X given O" do
|
72
|
+
Grid.other_token(Grid::O).must_equal Grid::X
|
73
|
+
end
|
74
|
+
|
75
|
+
it "returns the same value it was given otherwise" do
|
76
|
+
Grid.other_token(:something_else).must_equal :something_else
|
30
77
|
end
|
31
78
|
end
|
32
79
|
|
33
80
|
let(:grid) { Grid.new }
|
34
81
|
|
35
|
-
describe
|
82
|
+
describe "an initial grid" do
|
36
83
|
|
37
|
-
it
|
84
|
+
it "is empty" do
|
38
85
|
grid.empty?.must_equal true
|
39
86
|
end
|
40
87
|
end
|
41
88
|
|
42
|
-
describe
|
89
|
+
describe "#new" do
|
43
90
|
|
44
|
-
it
|
45
|
-
grid
|
91
|
+
it "can create a grid from a string" do
|
92
|
+
grid = Grid.new('xox o')
|
46
93
|
|
47
|
-
|
48
|
-
|
94
|
+
grid[1, 1].must_equal Grid::X
|
95
|
+
grid[1, 2].must_equal Grid::O
|
96
|
+
grid[1, 3].must_equal Grid::X
|
97
|
+
grid[2, 2].must_equal Grid::O
|
49
98
|
|
50
|
-
|
99
|
+
[[2, 1], [2, 3], [3, 1], [3, 2], [3, 3]].each do |pos|
|
100
|
+
grid.open?(*pos).must_equal true
|
101
|
+
end
|
51
102
|
end
|
52
|
-
end
|
53
103
|
|
54
|
-
|
104
|
+
it "only considers the first 9 characters" do
|
105
|
+
grid = Grid.new(' xoxoxoxox')
|
55
106
|
|
56
|
-
|
57
|
-
|
58
|
-
|
107
|
+
grid.empty?.must_equal true
|
108
|
+
end
|
109
|
+
|
110
|
+
it "can create a grid given exactly 9 characters" do
|
111
|
+
grid = Grid.new('xoxoxoxox')
|
112
|
+
|
113
|
+
grid.full?.must_equal true
|
59
114
|
end
|
60
115
|
end
|
61
116
|
|
62
|
-
describe
|
117
|
+
describe "#dup" do
|
118
|
+
|
119
|
+
it "creates a copy" do
|
120
|
+
grid[1, 1] = Grid::X
|
63
121
|
|
64
|
-
|
65
|
-
|
122
|
+
grid_copy = grid.dup
|
123
|
+
grid_copy[1, 1] = Grid::O
|
124
|
+
|
125
|
+
grid_copy[1, 1].must_equal Grid::O
|
126
|
+
grid[1, 1].must_equal Grid::X
|
66
127
|
end
|
67
128
|
end
|
68
129
|
|
69
|
-
describe
|
130
|
+
describe "#empty?" do
|
70
131
|
|
71
|
-
it
|
72
|
-
|
132
|
+
it "returns false when at least one position has a token" do
|
133
|
+
grid[1, 2] = Grid::X
|
134
|
+
grid.empty?.must_equal false
|
135
|
+
end
|
136
|
+
|
137
|
+
it "returns true otherwise" do
|
138
|
+
grid.empty?.must_equal true
|
73
139
|
end
|
74
140
|
end
|
75
141
|
|
76
|
-
describe
|
142
|
+
describe "#full?" do
|
77
143
|
|
78
144
|
before do
|
79
145
|
(1..3).each do |r|
|
80
146
|
(1..3).each do |c|
|
81
|
-
grid[r, c] = O
|
147
|
+
grid[r, c] = Grid::O
|
82
148
|
end
|
83
149
|
end
|
84
150
|
end
|
85
151
|
|
86
|
-
it
|
152
|
+
it "returns true when every position has a token" do
|
87
153
|
grid.full?.must_equal true
|
88
154
|
end
|
89
155
|
|
90
|
-
it
|
91
|
-
grid[1,
|
156
|
+
it "returns false when at least one position doesn't have a token" do
|
157
|
+
grid[1, 3] = :e
|
92
158
|
grid.full?.must_equal false
|
93
159
|
end
|
94
160
|
end
|
95
161
|
|
96
|
-
describe
|
162
|
+
describe "#[]=" do
|
163
|
+
|
164
|
+
it "sets a position to X, O or :e" do
|
165
|
+
grid[2, 1] = Grid::X
|
166
|
+
grid[2, 1].must_equal Grid::X
|
167
|
+
|
168
|
+
grid[2, 2] = Grid::O
|
169
|
+
grid[2, 2].must_equal Grid::O
|
170
|
+
|
171
|
+
grid[2, 3] = :anything_else
|
172
|
+
grid[2, 3].must_equal :e
|
173
|
+
end
|
174
|
+
|
175
|
+
it "raises IndexError when the position is off the grid" do
|
176
|
+
proc { grid[0, 0] = Grid::X }.must_raise IndexError
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
describe "#[]" do
|
181
|
+
|
182
|
+
it "raises IndexError when the position is off the grid" do
|
183
|
+
proc { grid[4, 4] }.must_raise IndexError
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "#open?" do
|
97
188
|
|
98
|
-
it
|
99
|
-
grid.
|
189
|
+
it "returns true when no token is at the given position" do
|
190
|
+
grid.open?(2, 2).must_equal true
|
100
191
|
end
|
101
192
|
|
102
|
-
it
|
103
|
-
grid[
|
104
|
-
grid.
|
193
|
+
it "returns false when a token is at the given position" do
|
194
|
+
grid[2, 3] = Grid::O
|
195
|
+
grid.open?(2, 3).must_equal false
|
105
196
|
end
|
106
197
|
|
107
|
-
it
|
108
|
-
proc { grid.
|
198
|
+
it "raises IndexError when the position is off the grid" do
|
199
|
+
proc { grid.open?(0, 4) }.must_raise IndexError
|
109
200
|
end
|
110
201
|
end
|
111
202
|
|
112
|
-
describe
|
203
|
+
describe "#clear" do
|
113
204
|
|
114
|
-
it
|
115
|
-
grid[
|
116
|
-
grid[
|
117
|
-
grid[3,
|
205
|
+
it "removes all tokens from the grid" do
|
206
|
+
grid[3, 1] = Grid::X
|
207
|
+
grid[3, 2] = Grid::O
|
208
|
+
grid[3, 3] = Grid::X
|
118
209
|
|
119
210
|
grid.clear
|
120
211
|
|
121
212
|
grid.empty?.must_equal true
|
122
213
|
end
|
214
|
+
|
215
|
+
it "returns self" do
|
216
|
+
grid.clear.must_be_same_as grid
|
217
|
+
end
|
123
218
|
end
|
124
219
|
|
125
|
-
describe
|
220
|
+
describe "#each" do
|
126
221
|
|
127
222
|
it "visits every position and yields a block that takes the position's row, column and value" do
|
128
|
-
grid[1, 1] = O
|
129
|
-
grid[2, 2] = X
|
130
|
-
grid[3, 3] = O
|
223
|
+
grid[1, 1] = Grid::O
|
224
|
+
grid[2, 2] = Grid::X
|
225
|
+
grid[3, 3] = Grid::O
|
131
226
|
|
132
227
|
visited = {}
|
133
228
|
|
134
229
|
grid.each do |r, c, val|
|
135
|
-
# ensure that the
|
230
|
+
# ensure that the position (r, c) is contained within the grid and
|
231
|
+
# ensure that the value at the position is correct
|
136
232
|
grid[r, c].must_equal val
|
137
233
|
|
138
234
|
# keep track of every position we visit
|
@@ -142,5 +238,52 @@ module XO
|
|
142
238
|
visited.keys.size.must_equal(Grid::ROWS * Grid::COLS)
|
143
239
|
end
|
144
240
|
end
|
241
|
+
|
242
|
+
describe "#each_open" do
|
243
|
+
|
244
|
+
it "visits every open position and yields a block that takes the position's row and column" do
|
245
|
+
grid[1, 3] = Grid::X
|
246
|
+
grid[2, 2] = Grid::O
|
247
|
+
grid[3, 1] = Grid::X
|
248
|
+
|
249
|
+
visited = {}
|
250
|
+
|
251
|
+
grid.each_open do |r, c|
|
252
|
+
visited[[r, c]] = true
|
253
|
+
end
|
254
|
+
|
255
|
+
visited.keys.size.must_equal(Grid::ROWS * Grid::COLS - 3)
|
256
|
+
|
257
|
+
[[1, 1], [1, 2], [2, 1], [2, 3], [3, 2], [3, 3]].each do |pos|
|
258
|
+
visited.key?(pos)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
describe "#inspect" do
|
264
|
+
|
265
|
+
it "returns a single line string representation of the grid" do
|
266
|
+
grid[1, 1] = grid[1, 3] = Grid::X
|
267
|
+
grid[1, 2] = grid[3, 3] = Grid::O
|
268
|
+
|
269
|
+
grid.inspect.must_equal "xox o"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
describe "#to_s" do
|
274
|
+
|
275
|
+
it "returns a multiline string representation of the grid" do
|
276
|
+
grid[2, 1] = grid[2, 3] = Grid::X
|
277
|
+
grid[1, 2] = grid[2, 2] = Grid::O
|
278
|
+
|
279
|
+
grid.to_s.must_equal [
|
280
|
+
" | o | ",
|
281
|
+
"---+---+---",
|
282
|
+
" x | o | x ",
|
283
|
+
"---+---+---",
|
284
|
+
" | | "
|
285
|
+
].join("\n")
|
286
|
+
end
|
287
|
+
end
|
145
288
|
end
|
146
289
|
end
|
data/xo.gemspec
CHANGED
@@ -18,6 +18,13 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.
|
22
|
-
|
21
|
+
spec.required_ruby_version = '>= 1.9.3'
|
22
|
+
|
23
|
+
spec.add_development_dependency 'rake', '~> 10.3'
|
24
|
+
spec.add_development_dependency 'minitest', '~> 5.3'
|
25
|
+
spec.add_development_dependency 'coveralls', '~> 0.7'
|
26
|
+
|
27
|
+
# For documentation
|
28
|
+
spec.add_development_dependency 'redcarpet', '~> 3.1'
|
29
|
+
spec.add_development_dependency 'yard', '~> 0.8'
|
23
30
|
end
|
metadata
CHANGED
@@ -1,77 +1,111 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.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-
|
11
|
+
date: 2014-05-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ~>
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '10.
|
20
|
-
- - ">="
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: 10.1.0
|
19
|
+
version: '10.3'
|
23
20
|
type: :development
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
|
-
- -
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '10.1'
|
30
|
-
- - ">="
|
24
|
+
- - ~>
|
31
25
|
- !ruby/object:Gem::Version
|
32
|
-
version: 10.
|
26
|
+
version: '10.3'
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
28
|
name: minitest
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
36
30
|
requirements:
|
37
|
-
- -
|
31
|
+
- - ~>
|
38
32
|
- !ruby/object:Gem::Version
|
39
33
|
version: '5.3'
|
40
|
-
- - ">="
|
41
|
-
- !ruby/object:Gem::Version
|
42
|
-
version: 5.3.2
|
43
34
|
type: :development
|
44
35
|
prerelease: false
|
45
36
|
version_requirements: !ruby/object:Gem::Requirement
|
46
37
|
requirements:
|
47
|
-
- -
|
38
|
+
- - ~>
|
48
39
|
- !ruby/object:Gem::Version
|
49
40
|
version: '5.3'
|
50
|
-
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: coveralls
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.7'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.7'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: redcarpet
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
51
60
|
- !ruby/object:Gem::Version
|
52
|
-
version:
|
61
|
+
version: '3.1'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.1'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.8'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.8'
|
53
83
|
description: A Ruby library that can be used to develop Tic-tac-toe game clients.
|
54
84
|
email:
|
55
85
|
- me@dwaynecrooks.com
|
56
|
-
executables:
|
86
|
+
executables:
|
87
|
+
- xo
|
57
88
|
extensions: []
|
58
89
|
extra_rdoc_files: []
|
59
90
|
files:
|
60
|
-
-
|
91
|
+
- .gitignore
|
92
|
+
- .travis.yml
|
93
|
+
- Gemfile
|
94
|
+
- Gemfile.lock
|
61
95
|
- LICENSE.txt
|
62
96
|
- README.md
|
63
97
|
- Rakefile
|
98
|
+
- bin/xo
|
64
99
|
- lib/xo.rb
|
65
100
|
- lib/xo/ai.rb
|
66
|
-
- lib/xo/ai/
|
67
|
-
- lib/xo/ai/expert.rb
|
101
|
+
- lib/xo/ai/geometric_grid.rb
|
68
102
|
- lib/xo/ai/minimax.rb
|
69
|
-
- lib/xo/ai/novice.rb
|
70
103
|
- lib/xo/engine.rb
|
71
104
|
- lib/xo/evaluator.rb
|
72
105
|
- lib/xo/grid.rb
|
73
106
|
- lib/xo/version.rb
|
74
107
|
- spec/spec_helper.rb
|
108
|
+
- spec/xo/ai/geometric_grid_spec.rb
|
75
109
|
- spec/xo/ai/minimax_spec.rb
|
76
110
|
- spec/xo/engine_spec.rb
|
77
111
|
- spec/xo/evaluator_spec.rb
|
@@ -87,12 +121,12 @@ require_paths:
|
|
87
121
|
- lib
|
88
122
|
required_ruby_version: !ruby/object:Gem::Requirement
|
89
123
|
requirements:
|
90
|
-
- -
|
124
|
+
- - ! '>='
|
91
125
|
- !ruby/object:Gem::Version
|
92
|
-
version:
|
126
|
+
version: 1.9.3
|
93
127
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
128
|
requirements:
|
95
|
-
- -
|
129
|
+
- - ! '>='
|
96
130
|
- !ruby/object:Gem::Version
|
97
131
|
version: '0'
|
98
132
|
requirements: []
|
@@ -103,7 +137,9 @@ specification_version: 4
|
|
103
137
|
summary: A Ruby library for Tic-tac-toe.
|
104
138
|
test_files:
|
105
139
|
- spec/spec_helper.rb
|
140
|
+
- spec/xo/ai/geometric_grid_spec.rb
|
106
141
|
- spec/xo/ai/minimax_spec.rb
|
107
142
|
- spec/xo/engine_spec.rb
|
108
143
|
- spec/xo/evaluator_spec.rb
|
109
144
|
- spec/xo/grid_spec.rb
|
145
|
+
has_rdoc:
|