tactical_tic_tac_toe 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/bin/ttt +5 -0
- data/lib/available_player_types.rb +8 -0
- data/lib/board.rb +118 -0
- data/lib/command_line_interface.rb +96 -0
- data/lib/computer_player.rb +86 -0
- data/lib/game.rb +88 -0
- data/lib/human_player.rb +14 -0
- data/lib/negamax.rb +39 -0
- data/lib/old_computer_player.rb +89 -0
- data/lib/player_factory.rb +35 -0
- data/lib/tactical_tic_tac_toe.rb +1 -0
- data/spec/board_spec.rb +374 -0
- data/spec/command_line_interface_spec.rb +167 -0
- data/spec/computer_player_spec.rb +222 -0
- data/spec/game_spec.rb +279 -0
- data/spec/human_player_spec.rb +23 -0
- data/spec/negamax_spec.rb +90 -0
- data/spec/old_computer_player_spec.rb +230 -0
- data/spec/player_factory_spec.rb +49 -0
- data/spec/spec_helper.rb +107 -0
- metadata +64 -0
data/spec/board_spec.rb
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "board"
|
|
3
|
+
|
|
4
|
+
module TicTacToe
|
|
5
|
+
describe Board do
|
|
6
|
+
include_context "default_values"
|
|
7
|
+
include_context "helper_methods"
|
|
8
|
+
|
|
9
|
+
let(:board_error) { Board::BoardError }
|
|
10
|
+
let(:_) { Board.blank_mark }
|
|
11
|
+
let(:x) { @default_player_marks.first }
|
|
12
|
+
let(:o) { @default_player_marks.last }
|
|
13
|
+
|
|
14
|
+
describe ".blank_mark" do
|
|
15
|
+
it "returns the mark used by board to indicate a blank cell" do
|
|
16
|
+
board = blank_board(@default_board_size)
|
|
17
|
+
|
|
18
|
+
board.all_coordinates.each do |coordinates|
|
|
19
|
+
expect(board.read_cell(*coordinates)).to eq Board.blank_mark
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe "#initialize" do
|
|
25
|
+
it "raises error if given size is less than 3" do
|
|
26
|
+
error_info = [board_error, "Given size is too small, must be 3 or greater"]
|
|
27
|
+
|
|
28
|
+
expect { new_board(size: 2) }.to raise_error(*error_info)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context "given the configuration of a board with preexisting marks" do
|
|
32
|
+
let(:config) do
|
|
33
|
+
[ x, _, _,
|
|
34
|
+
_, o, _,
|
|
35
|
+
_, _, x ]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "generates a board with the specified marks in each cell" do
|
|
39
|
+
board = new_board(size: Math.sqrt(config.size).to_i, config: config)
|
|
40
|
+
|
|
41
|
+
board.all_coordinates.zip(config).each do |coordinates, mark|
|
|
42
|
+
expect(board.read_cell(*coordinates)).to eq mark
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "raises error if given configuration does not reconcile with given size" do
|
|
47
|
+
error_info = [board_error, "Given size does not reconcile with given configuration"]
|
|
48
|
+
params = {
|
|
49
|
+
size: Math.sqrt(config.size).to_i + 1,
|
|
50
|
+
config: config
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
expect { new_board(params) }.to raise_error(*error_info)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context "when not given a configuration of a board with preexisting marks" do
|
|
58
|
+
it "generates a NxN board of the given size" do
|
|
59
|
+
(3..5).each do |size|
|
|
60
|
+
custom_board = new_board(size: size)
|
|
61
|
+
|
|
62
|
+
expect(custom_board.size).to eq size
|
|
63
|
+
expect(custom_board.all_coordinates.count).to eq size**2
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "leaves the generated board blank" do
|
|
68
|
+
expect(new_board(size: @default_board_size).all_blank?).to be true
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe "#read_cell" do
|
|
74
|
+
it "gets the contents of cell at given row and column" do
|
|
75
|
+
mark = @default_player_marks.sample
|
|
76
|
+
config = blank_board_configuration(@default_board_size)
|
|
77
|
+
config[0] = mark
|
|
78
|
+
board = build_board(config)
|
|
79
|
+
|
|
80
|
+
expect(board.read_cell(0, 0)).to eq mark
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "raises error if cell coordinates are out of bounds" do
|
|
84
|
+
error_info = [board_error, "Cell coordinates are out of bounds"]
|
|
85
|
+
board = blank_board(@default_board_size)
|
|
86
|
+
oob_coordinates = [board.size, board.size]
|
|
87
|
+
|
|
88
|
+
expect { board.read_cell(*oob_coordinates) }.to raise_error(*error_info)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe "#mark_cell" do
|
|
93
|
+
let(:board) { blank_board(@default_board_size) }
|
|
94
|
+
let(:coordinates) { random_coordinates(board.size) }
|
|
95
|
+
let(:mark) { @default_player_marks.sample }
|
|
96
|
+
|
|
97
|
+
it "sets the contents of an empty cell at the given row and column" do
|
|
98
|
+
board.mark_cell(mark, *coordinates)
|
|
99
|
+
|
|
100
|
+
expect(board.read_cell(*coordinates)).to eq mark
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "records the coordinates of the last mark made" do
|
|
104
|
+
board.mark_cell(mark, *coordinates)
|
|
105
|
+
|
|
106
|
+
expect(board.last_move_made).to eq coordinates
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "raises error if cell coordinates are out of bounds" do
|
|
110
|
+
error_info = [board_error, "Cell coordinates are out of bounds"]
|
|
111
|
+
oob_coordinates = [board.size, board.size]
|
|
112
|
+
|
|
113
|
+
expect { board.mark_cell(mark, *oob_coordinates)}.to raise_error(*error_info)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "raises error when attempting to change contents of non-empty cell" do
|
|
117
|
+
error_info = [board_error, "Cannot alter a marked cell"]
|
|
118
|
+
board.mark_cell(mark, *coordinates)
|
|
119
|
+
|
|
120
|
+
expect { board.mark_cell(mark, *coordinates) }.to raise_error(*error_info)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
describe "#lines" do
|
|
125
|
+
let(:board) { blank_board(@default_board_size) }
|
|
126
|
+
|
|
127
|
+
it "returns the contents of every board-length line (rows, cols, and diagonals)" do
|
|
128
|
+
lines = board.lines
|
|
129
|
+
|
|
130
|
+
lines.each do |line|
|
|
131
|
+
expect(line.count).to eq board.size
|
|
132
|
+
end
|
|
133
|
+
expect(lines.count).to eq board.size * 2 + 2
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
describe "#all_coordinates" do
|
|
138
|
+
let(:board) { blank_board(@default_board_size) }
|
|
139
|
+
|
|
140
|
+
it "returns the coordinates of every cell in board" do
|
|
141
|
+
all_coordinates = (0...board.size).to_a.repeated_permutation(2).to_a
|
|
142
|
+
|
|
143
|
+
expect(board.all_coordinates).to match_array all_coordinates
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
describe "#blank_cell_coordinates" do
|
|
148
|
+
it "returns all cell coordinates when board is blank" do
|
|
149
|
+
board = blank_board(@default_board_size)
|
|
150
|
+
|
|
151
|
+
expect(board.blank_cell_coordinates).to match_array board.all_coordinates
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it "returns the coordinates of all blank cells in board" do
|
|
155
|
+
config = [x, o, _, _, _, _, _, _, _].shuffle
|
|
156
|
+
board = build_board(config)
|
|
157
|
+
unmarked_coordinates = board.all_coordinates.reject { |coords| board.marked?(coords) }
|
|
158
|
+
|
|
159
|
+
expect(board.blank_cell_coordinates).to match_array unmarked_coordinates
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it "returns no coordinates when no cells are blank" do
|
|
163
|
+
config = [x, o, x, o, x, o, x, o, x].shuffle
|
|
164
|
+
board = build_board(config)
|
|
165
|
+
|
|
166
|
+
expect(board.blank_cell_coordinates.count).to eq 0
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
describe "#last_mark_made" do
|
|
171
|
+
context "when board is blank" do
|
|
172
|
+
it "returns nil" do
|
|
173
|
+
expect(blank_board(@default_board_size).last_mark_made).to be nil
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
context "when board is not blank" do
|
|
178
|
+
it "returns the last mark made on the board" do
|
|
179
|
+
board = blank_board(@default_board_size)
|
|
180
|
+
mark = @default_player_marks.sample
|
|
181
|
+
board.mark_cell(mark, *random_coordinates(board.size))
|
|
182
|
+
|
|
183
|
+
expect(board.last_mark_made).to eq mark
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
describe "#deep_copy" do
|
|
189
|
+
it "returns a new board" do
|
|
190
|
+
board = blank_board(@default_board_size)
|
|
191
|
+
board_copy = board.deep_copy
|
|
192
|
+
|
|
193
|
+
expect(board).not_to eq board_copy
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it "returns a board that has the same marks as called board" do
|
|
197
|
+
config = [x, o, x, o, x, o, _, _, _].shuffle
|
|
198
|
+
board = build_board(config)
|
|
199
|
+
board_copy = board.deep_copy
|
|
200
|
+
|
|
201
|
+
board.all_coordinates.each do |coordinates|
|
|
202
|
+
expect(board.read_cell(*coordinates)).to eq board_copy.read_cell(*coordinates)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
it "returns a board which references cells that are not those of called board" do
|
|
207
|
+
board = blank_board(@default_board_size)
|
|
208
|
+
board_copy = board.deep_copy
|
|
209
|
+
coordinates = random_coordinates(board.size)
|
|
210
|
+
board.mark_cell(@default_player_marks.first, *coordinates)
|
|
211
|
+
|
|
212
|
+
expect(board.read_cell(*coordinates)).not_to eq board_copy.read_cell(*coordinates)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
describe "#blank?" do
|
|
217
|
+
it "returns true if cell at given coordinates is blank" do
|
|
218
|
+
board = blank_board(@default_board_size)
|
|
219
|
+
|
|
220
|
+
expect(board.blank?(random_coordinates(board.size))).to be true
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
it "returns false if cell at given coordinates is not blank" do
|
|
224
|
+
board = blank_board(@default_board_size)
|
|
225
|
+
coordinates = random_coordinates(board.size)
|
|
226
|
+
board.mark_cell(@default_player_marks.sample, *coordinates)
|
|
227
|
+
|
|
228
|
+
expect(board.blank?(coordinates)).to be false
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
describe "#marked?" do
|
|
233
|
+
it "returns true if cell at given coordinates has any player's mark" do
|
|
234
|
+
board = blank_board(@default_board_size)
|
|
235
|
+
coordinates = random_coordinates(board.size)
|
|
236
|
+
board.mark_cell(@default_player_marks.sample, *coordinates)
|
|
237
|
+
|
|
238
|
+
expect(board.marked?(coordinates)).to be true
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
it "returns false if cell at given coordinates does not have a player's mark" do
|
|
242
|
+
blank_board = blank_board(@default_board_size)
|
|
243
|
+
coordinates = random_coordinates(blank_board.size)
|
|
244
|
+
|
|
245
|
+
expect(blank_board.marked?(coordinates)).to be false
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
describe "#out_of_bounds?" do
|
|
250
|
+
it "returns true if given coordinates are not within bounds of board" do
|
|
251
|
+
board = blank_board(@default_board_size)
|
|
252
|
+
coordinates = [0, board.size]
|
|
253
|
+
|
|
254
|
+
expect(board.out_of_bounds?(coordinates)).to be true
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
it "returns false if given coordinates are within bounds of board" do
|
|
258
|
+
board = blank_board(@default_board_size)
|
|
259
|
+
|
|
260
|
+
board.all_coordinates.each do |coordinates|
|
|
261
|
+
expect(board.out_of_bounds?(coordinates)).to be false
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
describe "#all_blank?" do
|
|
267
|
+
it "returns true if no cell in board is marked" do
|
|
268
|
+
board = blank_board(@default_board_size)
|
|
269
|
+
|
|
270
|
+
expect(board.all_blank?).to be true
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
it "returns false if any cell in board is marked" do
|
|
274
|
+
config = [x, _, _, _, _, _, _, _, _].shuffle
|
|
275
|
+
board = build_board(config)
|
|
276
|
+
|
|
277
|
+
expect(board.all_blank?).to be false
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
describe "#all_marked?" do
|
|
282
|
+
it "returns true if all cells in board are marked" do
|
|
283
|
+
config = [x, x, x, x, x, o, o, o, o].shuffle
|
|
284
|
+
board = build_board(config)
|
|
285
|
+
|
|
286
|
+
expect(board.all_marked?).to be true
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
it "returns false if any cell in board is blank" do
|
|
290
|
+
config = [x, x, x, x, _, o, o, o, o].shuffle
|
|
291
|
+
board = build_board(config)
|
|
292
|
+
|
|
293
|
+
expect(board.all_marked?).to be false
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
describe "#has_winning_line?" do
|
|
298
|
+
it "returns true if board has a horizontal winning line" do
|
|
299
|
+
winning_configs = [
|
|
300
|
+
[ x, x, x,
|
|
301
|
+
_, _, _,
|
|
302
|
+
_, _, _ ],
|
|
303
|
+
[ _, _, _,
|
|
304
|
+
x, x, x,
|
|
305
|
+
_, _, _ ],
|
|
306
|
+
[ _, _, _,
|
|
307
|
+
_, _, _,
|
|
308
|
+
x, x, x ]
|
|
309
|
+
]
|
|
310
|
+
|
|
311
|
+
winning_configs.each do |config|
|
|
312
|
+
expect(build_board(config).has_winning_line?).to be true
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
it "returns true if board has a vertical winning line" do
|
|
317
|
+
winning_configs = [
|
|
318
|
+
[ x, _, _,
|
|
319
|
+
x, _, _,
|
|
320
|
+
x, _, _ ],
|
|
321
|
+
[ _, x, _,
|
|
322
|
+
_, x, _,
|
|
323
|
+
_, x, _ ],
|
|
324
|
+
[ _, _, x,
|
|
325
|
+
_, _, x,
|
|
326
|
+
_, _, x ]
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
winning_configs.each do |config|
|
|
330
|
+
expect(build_board(config).has_winning_line?).to be true
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
it "returns true if board has a diagonal winning line" do
|
|
335
|
+
winning_configs = [
|
|
336
|
+
[ x, _, _,
|
|
337
|
+
_, x, _,
|
|
338
|
+
_, _, x ],
|
|
339
|
+
[ _, _, x,
|
|
340
|
+
_, x, _,
|
|
341
|
+
x, _, _ ]
|
|
342
|
+
]
|
|
343
|
+
|
|
344
|
+
winning_configs.each do |config|
|
|
345
|
+
expect(build_board(config).has_winning_line?).to be true
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
it "returns false if board has no winning line" do
|
|
350
|
+
non_winning_configs = [
|
|
351
|
+
[ _, _, _,
|
|
352
|
+
_, _, _,
|
|
353
|
+
_, _, _ ],
|
|
354
|
+
[ _, _, _,
|
|
355
|
+
x, x, o,
|
|
356
|
+
_, _, _ ],
|
|
357
|
+
[ x, _, _,
|
|
358
|
+
x, _, _,
|
|
359
|
+
o, _, _ ],
|
|
360
|
+
[ x, _, _,
|
|
361
|
+
_, x, _,
|
|
362
|
+
_, _, o ],
|
|
363
|
+
[ x, o, x,
|
|
364
|
+
x, o, x,
|
|
365
|
+
o, x, o ]
|
|
366
|
+
]
|
|
367
|
+
|
|
368
|
+
non_winning_configs.each do |config|
|
|
369
|
+
expect(build_board(config).has_winning_line?).to be false
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "command_line_interface"
|
|
3
|
+
|
|
4
|
+
require "board"
|
|
5
|
+
|
|
6
|
+
module TicTacToe
|
|
7
|
+
describe CommandLineInterface do
|
|
8
|
+
include_context "default_values"
|
|
9
|
+
include_context "helper_methods"
|
|
10
|
+
|
|
11
|
+
let(:input_stream) { StringIO.new }
|
|
12
|
+
let(:output_stream) { StringIO.new }
|
|
13
|
+
let(:interface) do
|
|
14
|
+
parameters = { input: input_stream, output: output_stream }
|
|
15
|
+
CommandLineInterface.new(parameters)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe "#game_setup_interaction" do
|
|
19
|
+
before do
|
|
20
|
+
input_stream.string = %w(human computer).join("\n")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "prints an introductory message to the command line" do
|
|
24
|
+
interface.game_setup_interaction(@default_player_marks)
|
|
25
|
+
|
|
26
|
+
expect(output_stream.string).not_to eq ""
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "returns the player type associated with each of the given player marks" do
|
|
30
|
+
player_types = interface.game_setup_interaction(@default_player_marks)
|
|
31
|
+
|
|
32
|
+
expect(player_types).to have(2).things
|
|
33
|
+
player_types.each do |type|
|
|
34
|
+
expect(@default_player_types).to include type
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe "#solicit_player_type" do
|
|
40
|
+
let(:valid_input) { "human" }
|
|
41
|
+
|
|
42
|
+
it "prints a prompt to the command line" do
|
|
43
|
+
input_stream.string = valid_input
|
|
44
|
+
interface.solicit_player_type(@default_first_player)
|
|
45
|
+
|
|
46
|
+
expect(output_stream.string).not_to eq ""
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
context "when given valid input" do
|
|
50
|
+
it "returns the player type input by user" do
|
|
51
|
+
input_stream.string = valid_input
|
|
52
|
+
|
|
53
|
+
expect(interface.solicit_player_type(@default_first_player)).to eq valid_input.to_sym
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context "when given invalid input" do
|
|
58
|
+
let(:invalid_input) { "foo" }
|
|
59
|
+
|
|
60
|
+
it "prompts the user until given valid input" do
|
|
61
|
+
input_stream.string = invalid_input + "\n" + invalid_input + "\n" + valid_input
|
|
62
|
+
interface.solicit_player_type(@default_first_player)
|
|
63
|
+
|
|
64
|
+
expect(at_least_one_repeated_line?(output_stream.string)).to be true
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe "#show_game_board" do
|
|
70
|
+
it "prints a respresentation of the given board to the command line" do
|
|
71
|
+
board = board_with_draw(@default_board_size, @default_player_marks)
|
|
72
|
+
interface.show_game_board(board)
|
|
73
|
+
|
|
74
|
+
board_characters = output_stream.string.split("")
|
|
75
|
+
cell_count = board.size**2
|
|
76
|
+
expected_first_mark_count = cell_count.odd? ? cell_count / 2 + 1 : cell_count / 2
|
|
77
|
+
expected_second_mark_count = cell_count / 2
|
|
78
|
+
|
|
79
|
+
expect(board_characters.count(@default_first_player.to_s)).to eq expected_first_mark_count
|
|
80
|
+
expect(board_characters.count(@default_second_player.to_s)).to eq expected_second_mark_count
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe "solicit_move" do
|
|
85
|
+
let(:valid_input) { "0, 0" }
|
|
86
|
+
|
|
87
|
+
it "prints a prompt to the command line" do
|
|
88
|
+
input_stream.string = valid_input
|
|
89
|
+
interface.solicit_move(@default_first_player)
|
|
90
|
+
|
|
91
|
+
expect(output_stream.string).not_to eq ""
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context "when given valid input" do
|
|
95
|
+
it "returns the move coordinates input by user" do
|
|
96
|
+
input_stream.string = valid_input
|
|
97
|
+
valid_coordinate = valid_input.split(",").map(&:to_i)
|
|
98
|
+
|
|
99
|
+
expect(interface.solicit_move(@default_first_player)).to eq valid_coordinate
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
context "when given invalid input" do
|
|
104
|
+
let(:invalid_input) { "foo" }
|
|
105
|
+
|
|
106
|
+
it "prompts the user until given valid input" do
|
|
107
|
+
input_stream.string = invalid_input + "\n" + invalid_input + "\n" + valid_input
|
|
108
|
+
interface.solicit_move(@default_first_player)
|
|
109
|
+
|
|
110
|
+
expect(at_least_one_repeated_line?(output_stream.string)).to be true
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
describe "#report_invalid_move" do
|
|
116
|
+
it "prints a notification that a move cannot be made at the given coordinates" do
|
|
117
|
+
interface.report_invalid_move([0, 0])
|
|
118
|
+
|
|
119
|
+
expect(output_stream.string.split("").count("0")).to eq 2
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe "#report_move" do
|
|
124
|
+
it "prints a notification of the move made by the given mark at the given coordinates" do
|
|
125
|
+
interface.report_move(@default_first_player, [0, 0])
|
|
126
|
+
interface_output = output_stream.string
|
|
127
|
+
|
|
128
|
+
expect(interface_output).to include @default_first_player.to_s
|
|
129
|
+
expect(interface_output.split("").count("0")).to eq 2
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
describe "#report_game_over" do
|
|
134
|
+
context "when given the mark of a winning player" do
|
|
135
|
+
it "prints a notification that the player has won" do
|
|
136
|
+
expect(interface).to receive(:report_win).with(@default_first_player)
|
|
137
|
+
interface.report_game_over(@default_first_player)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
context "when winning player is ':none'" do
|
|
142
|
+
it "prints a notification that the game has ended in a draw" do
|
|
143
|
+
expect(interface).to receive(:report_draw)
|
|
144
|
+
interface.report_game_over(:none)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
describe "#report_win" do
|
|
150
|
+
it "prints a notification that the player using the given mark has won" do
|
|
151
|
+
interface.report_win(@default_first_player)
|
|
152
|
+
interface_output = output_stream.string
|
|
153
|
+
|
|
154
|
+
expect(interface_output).to include @default_first_player.to_s
|
|
155
|
+
expect(interface_output).to include "wins"
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
describe "#report_draw" do
|
|
160
|
+
it "prints a notification that the game has ended in a draw" do
|
|
161
|
+
interface.report_draw
|
|
162
|
+
|
|
163
|
+
expect(output_stream.string).to include "draw"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|