tictactoe-core 0.1.1
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 +7 -0
- data/.travis.yml +9 -0
- data/Gemfile +3 -0
- data/README.md +2 -0
- data/Rakefile +34 -0
- data/lib/tictactoe.rb +1 -0
- data/lib/tictactoe/ai/ab_minimax.rb +78 -0
- data/lib/tictactoe/ai/ab_negamax.rb +45 -0
- data/lib/tictactoe/ai/perfect_intelligence.rb +35 -0
- data/lib/tictactoe/ai/random_chooser.rb +21 -0
- data/lib/tictactoe/ai/tree.rb +44 -0
- data/lib/tictactoe/boards/board_type_factory.rb +15 -0
- data/lib/tictactoe/boards/four_by_four_board.rb +28 -0
- data/lib/tictactoe/boards/three_by_three_board.rb +27 -0
- data/lib/tictactoe/game.rb +102 -0
- data/lib/tictactoe/players/computer.rb +21 -0
- data/lib/tictactoe/players/factory.rb +21 -0
- data/lib/tictactoe/sequence.rb +24 -0
- data/lib/tictactoe/state.rb +64 -0
- data/runtests.sh +1 -0
- data/spec/performance_spec.rb +16 -0
- data/spec/properties_spec.rb +27 -0
- data/spec/rake_rspec.rb +42 -0
- data/spec/regression_spec.rb +29 -0
- data/spec/reproducible_random.rb +16 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/test_run.rb +19 -0
- data/spec/tictactoe/ai/ab_minimax_spec.rb +511 -0
- data/spec/tictactoe/ai/ab_negamax_spec.rb +199 -0
- data/spec/tictactoe/ai/perfect_intelligence_spec.rb +122 -0
- data/spec/tictactoe/ai/random_choser_spec.rb +50 -0
- data/spec/tictactoe/boards/board_type_factory_spec.rb +16 -0
- data/spec/tictactoe/boards/four_by_four_board_spec.rb +30 -0
- data/spec/tictactoe/boards/three_by_three_board_spec.rb +30 -0
- data/spec/tictactoe/game_spec.rb +288 -0
- data/spec/tictactoe/players/computer_spec.rb +42 -0
- data/spec/tictactoe/players/factory_spec.rb +48 -0
- data/spec/tictactoe/sequence_spec.rb +20 -0
- data/spec/tictactoe/state_spec.rb +136 -0
- data/tictactoe-core-0.1.0.gem +0 -0
- data/tictactoe-core.gemspec +15 -0
- metadata +84 -0
@@ -0,0 +1,288 @@
|
|
1
|
+
require 'tictactoe/game'
|
2
|
+
|
3
|
+
RSpec.describe Tictactoe::Game do
|
4
|
+
class Human
|
5
|
+
attr_reader :mark
|
6
|
+
|
7
|
+
def initialize(mark, moves)
|
8
|
+
@mark = mark
|
9
|
+
@moves = moves
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_move(state)
|
13
|
+
@moves.pop
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:moves){[]}
|
18
|
+
|
19
|
+
def create(board_size, x_type, o_type)
|
20
|
+
game = described_class.new(board_size, x_type, o_type)
|
21
|
+
game.register_human_factory(lambda{|mark| Human.new(mark, moves)})
|
22
|
+
game
|
23
|
+
end
|
24
|
+
|
25
|
+
def human_tick_playing_to(game, loc)
|
26
|
+
moves << loc
|
27
|
+
game.tick()
|
28
|
+
end
|
29
|
+
|
30
|
+
def computer_tick(game)
|
31
|
+
game.tick()
|
32
|
+
end
|
33
|
+
|
34
|
+
def expect_amount_of_marks(game, mark, expected_count)
|
35
|
+
actual_count = game.marks.select{|m| m == mark}.count
|
36
|
+
expect(actual_count).to eq(expected_count)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'is not finished' do
|
40
|
+
game = create(3, :human, :human)
|
41
|
+
expect(game.is_finished?).to eq(false)
|
42
|
+
end
|
43
|
+
|
44
|
+
describe 'can be observed' do
|
45
|
+
it 'initial game size 3' do
|
46
|
+
game = create(3, :human, :human)
|
47
|
+
expect(game.marks).to eq([
|
48
|
+
nil, nil, nil,
|
49
|
+
nil, nil, nil,
|
50
|
+
nil, nil, nil
|
51
|
+
])
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'initial game size 4' do
|
55
|
+
game = create(4, :human, :human)
|
56
|
+
expect(game.marks).to eq([
|
57
|
+
nil, nil, nil, nil,
|
58
|
+
nil, nil, nil, nil,
|
59
|
+
nil, nil, nil, nil,
|
60
|
+
nil, nil, nil, nil
|
61
|
+
])
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'first play of x on size 3' do
|
65
|
+
game = create(3, :human, :human)
|
66
|
+
human_tick_playing_to(game, 0)
|
67
|
+
expect(game.marks).to eq([
|
68
|
+
:x, nil, nil,
|
69
|
+
nil, nil, nil,
|
70
|
+
nil, nil, nil
|
71
|
+
])
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'first play of o on size 3' do
|
75
|
+
game = create(3, :human, :human)
|
76
|
+
human_tick_playing_to(game, 0)
|
77
|
+
human_tick_playing_to(game, 1)
|
78
|
+
expect(game.marks).to eq([
|
79
|
+
:x, :o, nil,
|
80
|
+
nil, nil, nil,
|
81
|
+
nil, nil, nil
|
82
|
+
])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe 'available' do
|
87
|
+
it 'returns all the moves when no move has been made' do
|
88
|
+
game = create(3, :human, :human)
|
89
|
+
expect(game.available).to eq([
|
90
|
+
0, 1, 2,
|
91
|
+
3, 4, 5,
|
92
|
+
6, 7, 8
|
93
|
+
])
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'returns all the available moves for when there is one move made on 3 by 3' do
|
97
|
+
game = create(3, :human, :human)
|
98
|
+
human_tick_playing_to(game, 0)
|
99
|
+
expect(game.available).to eq([
|
100
|
+
1, 2,
|
101
|
+
3, 4, 5,
|
102
|
+
6, 7, 8
|
103
|
+
])
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'returns all the available moves for when there is one move made on 4 by 4' do
|
107
|
+
game = create(4, :human, :human)
|
108
|
+
human_tick_playing_to(game, 15)
|
109
|
+
expect(game.available).to eq([
|
110
|
+
0, 1, 2, 3,
|
111
|
+
4, 5, 6, 7,
|
112
|
+
8, 9,10,11,
|
113
|
+
12,13,14
|
114
|
+
])
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe 'is not finished if no player has a line' do
|
119
|
+
it do
|
120
|
+
game = create(4, :human, :human)
|
121
|
+
human_tick_playing_to(game, 0)
|
122
|
+
human_tick_playing_to(game, 4)
|
123
|
+
human_tick_playing_to(game, 1)
|
124
|
+
human_tick_playing_to(game, 5)
|
125
|
+
human_tick_playing_to(game, 2)
|
126
|
+
human_tick_playing_to(game, 6)
|
127
|
+
expect(game.is_finished?).to eq(false)
|
128
|
+
end
|
129
|
+
|
130
|
+
it do
|
131
|
+
game = create(3, :human, :human)
|
132
|
+
human_tick_playing_to(game, 0)
|
133
|
+
human_tick_playing_to(game, 3)
|
134
|
+
human_tick_playing_to(game, 1)
|
135
|
+
human_tick_playing_to(game, 4)
|
136
|
+
expect(game.is_finished?).to eq(false)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe 'is finished when a player has a line' do
|
141
|
+
it do
|
142
|
+
game = create(3, :human, :human)
|
143
|
+
human_tick_playing_to(game, 0)
|
144
|
+
human_tick_playing_to(game, 3)
|
145
|
+
human_tick_playing_to(game, 1)
|
146
|
+
human_tick_playing_to(game, 4)
|
147
|
+
human_tick_playing_to(game, 2)
|
148
|
+
expect(game.is_finished?).to eq(true)
|
149
|
+
expect(game.winner).to eq(:x)
|
150
|
+
end
|
151
|
+
|
152
|
+
it do
|
153
|
+
game = create(3, :human, :human)
|
154
|
+
human_tick_playing_to(game, 3)
|
155
|
+
human_tick_playing_to(game, 0)
|
156
|
+
human_tick_playing_to(game, 4)
|
157
|
+
human_tick_playing_to(game, 1)
|
158
|
+
human_tick_playing_to(game, 8)
|
159
|
+
human_tick_playing_to(game, 2)
|
160
|
+
expect(game.is_finished?).to eq(true)
|
161
|
+
expect(game.winner).to eq(:o)
|
162
|
+
end
|
163
|
+
|
164
|
+
it do
|
165
|
+
game = create(4, :human, :human)
|
166
|
+
human_tick_playing_to(game, 0)
|
167
|
+
human_tick_playing_to(game, 4)
|
168
|
+
human_tick_playing_to(game, 1)
|
169
|
+
human_tick_playing_to(game, 5)
|
170
|
+
human_tick_playing_to(game, 2)
|
171
|
+
human_tick_playing_to(game, 6)
|
172
|
+
human_tick_playing_to(game, 3)
|
173
|
+
expect(game.is_finished?).to eq(true)
|
174
|
+
expect(game.winner).to eq(:x)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe 'is finished when the board is full' do
|
179
|
+
it do
|
180
|
+
game = create(3, :human, :human)
|
181
|
+
human_tick_playing_to(game, 0)
|
182
|
+
human_tick_playing_to(game, 1)
|
183
|
+
human_tick_playing_to(game, 2)
|
184
|
+
human_tick_playing_to(game, 5)
|
185
|
+
human_tick_playing_to(game, 3)
|
186
|
+
human_tick_playing_to(game, 6)
|
187
|
+
human_tick_playing_to(game, 4)
|
188
|
+
human_tick_playing_to(game, 8)
|
189
|
+
human_tick_playing_to(game, 7)
|
190
|
+
expect(game.marks).to eq([
|
191
|
+
:x, :o, :x,
|
192
|
+
:x, :x, :o,
|
193
|
+
:o, :x, :o
|
194
|
+
])
|
195
|
+
expect(game.is_finished?).to eq(true)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
describe 'cannot play twice to the same move' do
|
200
|
+
it 'second play' do
|
201
|
+
game = create(3, :human, :human)
|
202
|
+
human_tick_playing_to(game, 0)
|
203
|
+
human_tick_playing_to(game, 0)
|
204
|
+
expect(game.marks).to eq([
|
205
|
+
:x, nil, nil,
|
206
|
+
nil, nil, nil,
|
207
|
+
nil, nil, nil
|
208
|
+
])
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
describe 'cannot be played when finished' do
|
213
|
+
it do
|
214
|
+
game = create(3, :human, :human)
|
215
|
+
|
216
|
+
human_tick_playing_to(game, 0)
|
217
|
+
human_tick_playing_to(game, 3)
|
218
|
+
human_tick_playing_to(game, 1)
|
219
|
+
human_tick_playing_to(game, 4)
|
220
|
+
human_tick_playing_to(game, 2)
|
221
|
+
|
222
|
+
human_tick_playing_to(game, 5)
|
223
|
+
|
224
|
+
expect(game.marks).to eq([
|
225
|
+
:x, :x, :x,
|
226
|
+
:o, :o, nil,
|
227
|
+
nil, nil, nil
|
228
|
+
])
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
describe 'if the human has no move ignores the ticks' do
|
233
|
+
it do
|
234
|
+
game = create(3, :human, :human)
|
235
|
+
human_tick_playing_to(game, nil)
|
236
|
+
human_tick_playing_to(game, nil)
|
237
|
+
human_tick_playing_to(game, nil)
|
238
|
+
expect_amount_of_marks(game, :x, 0)
|
239
|
+
end
|
240
|
+
|
241
|
+
it do
|
242
|
+
game = create(3, :human, :human)
|
243
|
+
human_tick_playing_to(game, nil)
|
244
|
+
human_tick_playing_to(game, nil)
|
245
|
+
human_tick_playing_to(game, nil)
|
246
|
+
human_tick_playing_to(game, 0)
|
247
|
+
expect_amount_of_marks(game, :x, 1)
|
248
|
+
expect_amount_of_marks(game, :o, 0)
|
249
|
+
end
|
250
|
+
|
251
|
+
it do
|
252
|
+
game = create(3, :human, :human)
|
253
|
+
human_tick_playing_to(game, 0)
|
254
|
+
human_tick_playing_to(game, nil)
|
255
|
+
human_tick_playing_to(game, nil)
|
256
|
+
human_tick_playing_to(game, nil)
|
257
|
+
expect_amount_of_marks(game, :x, 1)
|
258
|
+
expect_amount_of_marks(game, :o, 0)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
describe 'the computer plays' do
|
263
|
+
it 'the first turn' do
|
264
|
+
game = create(3, :computer, :human)
|
265
|
+
computer_tick(game)
|
266
|
+
expect_amount_of_marks(game, :x, 1)
|
267
|
+
expect_amount_of_marks(game, :o, 0)
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'the second turn' do
|
271
|
+
game = create(3, :human, :computer)
|
272
|
+
human_tick_playing_to(game, 0)
|
273
|
+
computer_tick(game)
|
274
|
+
expect_amount_of_marks(game, :x, 1)
|
275
|
+
expect_amount_of_marks(game, :o, 1)
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'four consecutive turns' do
|
279
|
+
game = create(3, :computer, :computer)
|
280
|
+
computer_tick(game)
|
281
|
+
computer_tick(game)
|
282
|
+
computer_tick(game)
|
283
|
+
computer_tick(game)
|
284
|
+
expect_amount_of_marks(game, :x, 2)
|
285
|
+
expect_amount_of_marks(game, :o, 2)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'tictactoe/players/computer'
|
2
|
+
|
3
|
+
RSpec.describe Tictactoe::Players::Computer do
|
4
|
+
class IntelligenceSpy
|
5
|
+
attr_reader :received_state, :received_mark
|
6
|
+
|
7
|
+
def initialize(moves)
|
8
|
+
@moves = moves
|
9
|
+
end
|
10
|
+
|
11
|
+
def desired_moves(state, mark)
|
12
|
+
@received_state = state
|
13
|
+
@received_mark = mark
|
14
|
+
@moves
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class ChooseFirst
|
19
|
+
def choose_one(list)
|
20
|
+
list.first
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def create(mark, intelligence = IntelligenceSpy.new([1, 2, 3]), chooser = ChooseFirst.new)
|
25
|
+
described_class.new(mark, intelligence, chooser)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'has a mark' do
|
29
|
+
computer = create(:x)
|
30
|
+
expect(computer.mark).to eq(:x)
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'can make a move' do
|
35
|
+
intelligence = IntelligenceSpy.new([1, 2, 3])
|
36
|
+
computer = create(:x, intelligence, ChooseFirst.new)
|
37
|
+
|
38
|
+
expect(computer.get_move(:state)).to eq (1)
|
39
|
+
expect(intelligence.received_state).to eq (:state)
|
40
|
+
expect(intelligence.received_mark).to eq (:x)
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'tictactoe/players/factory'
|
2
|
+
|
3
|
+
RSpec.describe Tictactoe::Players::Factory do
|
4
|
+
class ComputerFake
|
5
|
+
attr_reader :mark
|
6
|
+
|
7
|
+
def initialize(mark)
|
8
|
+
@mark = mark
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:computer_factory) do
|
13
|
+
lambda{|mark| ComputerFake.new(mark)}
|
14
|
+
end
|
15
|
+
let(:factory) do
|
16
|
+
factory = described_class.new()
|
17
|
+
factory.register(:computer, computer_factory)
|
18
|
+
factory
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'creates a computer player' do
|
22
|
+
computer_player = factory.create(:computer, :x)
|
23
|
+
|
24
|
+
expect(computer_player).to be_a(ComputerFake)
|
25
|
+
expect(computer_player.mark).to eq(:x)
|
26
|
+
end
|
27
|
+
|
28
|
+
class HumanFake
|
29
|
+
attr_reader :mark
|
30
|
+
|
31
|
+
def initialize(mark)
|
32
|
+
@mark = mark
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'creates a human computer after being registered' do
|
37
|
+
human_factory = lambda{|mark| HumanFake.new(mark)}
|
38
|
+
factory.register(:human, human_factory)
|
39
|
+
human_player = factory.create(:human, :o)
|
40
|
+
|
41
|
+
expect(human_player).to be_a(HumanFake)
|
42
|
+
expect(human_player.mark).to eq(:o)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'raises a message when no factory is available for that type' do
|
46
|
+
expect{factory.create(:unknown_type, :o)}.to raise_error("No factory has been defined for type: unknown_type")
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'tictactoe/sequence'
|
2
|
+
|
3
|
+
RSpec.describe Tictactoe::Sequence do
|
4
|
+
it 'has the first value' do
|
5
|
+
expect(described_class.new([:x, :o]).first.value).to eq(:x)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'has the second value' do
|
9
|
+
expect(described_class.new([:x, :o]).first.next.value).to eq(:o)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'cycles when reaches the end' do
|
13
|
+
expect(described_class.new([:x, :o]).first.next.next.value).to eq(:x)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'can handle 3 nodes' do
|
17
|
+
expect(described_class.new([0, 1, 2]).first.next.next.value).to eq(2)
|
18
|
+
expect(described_class.new([0, 1, 2]).first.next.next.next.value).to eq(0)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tictactoe/state'
|
3
|
+
require 'tictactoe/boards/three_by_three_board'
|
4
|
+
|
5
|
+
RSpec.describe Tictactoe::State do
|
6
|
+
def look_at(state, location)
|
7
|
+
state.marks[location]
|
8
|
+
end
|
9
|
+
|
10
|
+
def expect_state(marks)
|
11
|
+
expect(@state.marks).to eq(marks)
|
12
|
+
end
|
13
|
+
|
14
|
+
def expect_finished(expected)
|
15
|
+
finished = @state.when_finished{true} || false
|
16
|
+
expect(finished).to eq(expected)
|
17
|
+
end
|
18
|
+
|
19
|
+
def expect_winner(expected)
|
20
|
+
actual_winner = @state.when_finished{|winner| winner}
|
21
|
+
expect(actual_winner).to eq(expected)
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "given a 3x3 board" do
|
25
|
+
before(:each) do
|
26
|
+
@board = Tictactoe::Boards::ThreeByThreeBoard.new
|
27
|
+
@state = described_class.new(@board)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "can make a move" do
|
31
|
+
next_state = @state.make_move(3, :mark)
|
32
|
+
expect(look_at(next_state, 3)).to eq(:mark)
|
33
|
+
expect(next_state.available_moves).not_to include(3)
|
34
|
+
expect(next_state.played_moves).to eq(1)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "is immutable" do
|
38
|
+
@state.make_move(2, :mark)
|
39
|
+
expect(look_at(@state, 2)).to eq(nil)
|
40
|
+
expect(@state.available_moves).to include(2)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'can access to the board marks' do
|
44
|
+
expect(@state.marks).to eq([
|
45
|
+
nil, nil, nil,
|
46
|
+
nil, nil, nil,
|
47
|
+
nil, nil, nil,
|
48
|
+
])
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "with no moves" do
|
52
|
+
it "should have an empty state" do
|
53
|
+
expect_state([nil, nil, nil, nil, nil, nil, nil, nil, nil])
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should not be finished" do
|
57
|
+
expect_finished(false)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should have no winner" do
|
61
|
+
expect_winner(nil)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "with the first move" do
|
66
|
+
before(:each) do
|
67
|
+
@loc = 0
|
68
|
+
@state = @state.make_move(@loc, :X)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should contain that move" do
|
72
|
+
expect(look_at(@state, @loc)).to eq(:X)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
Tictactoe::Boards::ThreeByThreeBoard.new.lines.each do |line|
|
77
|
+
describe "with a line for player a" do
|
78
|
+
before(:each) do
|
79
|
+
line.each do |l|
|
80
|
+
@state = @state.make_move(l, :X)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should be finished" do
|
85
|
+
expect_finished(true)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should have won" do
|
89
|
+
expect_winner(:X)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def set_state(*marks)
|
95
|
+
marks.each_with_index do |mark, location|
|
96
|
+
@state = @state.make_move(location, mark)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "with three moves not in line for player a" do
|
101
|
+
before(:each) do
|
102
|
+
set_state(
|
103
|
+
:X, :X, nil,
|
104
|
+
:X, nil, nil,
|
105
|
+
nil, nil, nil
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should not be finished" do
|
110
|
+
expect_finished(false)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should have no winner" do
|
114
|
+
expect_winner(nil)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "with a full board but no winner" do
|
119
|
+
before(:each) do
|
120
|
+
set_state(
|
121
|
+
:O, :X, :X,
|
122
|
+
:X, :X, :O,
|
123
|
+
:O, :O, :X
|
124
|
+
)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should be finished" do
|
128
|
+
expect_finished(true)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should have no winner" do
|
132
|
+
expect_winner(nil)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|