tic_tac_toe_nhu 0.0.2 → 0.0.3
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 +4 -4
- data/coverage/.resultset.json +43 -311
- data/lib/tic_tac_toe/board.rb +9 -2
- data/lib/tic_tac_toe/game_factory.rb +9 -13
- data/lib/tic_tac_toe/main.rb +10 -3
- data/lib/tic_tac_toe/strategy/minimax.rb +19 -21
- data/lib/tic_tac_toe/ui/console.rb +17 -4
- data/spec/integration/tictactoe/tic_tac_toe.rb +3 -6
- data/spec/integration/tictactoe/unbeatable_computer_spec.rb +58 -0
- data/spec/mocks/game.rb +6 -0
- data/spec/mocks/game_factory.rb +6 -0
- data/spec/mocks/player_factory.rb +6 -0
- data/spec/mocks/rules.rb +6 -0
- data/spec/mocks/ui/console.rb +6 -0
- data/spec/tic_tac_toe/board_spec.rb +25 -1
- data/spec/tic_tac_toe/game_spec.rb +52 -21
- data/spec/tic_tac_toe/main_spec.rb +5 -14
- data/spec/tic_tac_toe/player_spec.rb +3 -3
- data/spec/tic_tac_toe/ui/console_spec.rb +10 -3
- data/tic_tac_toe_nhu.gemspec +1 -1
- metadata +2 -2
- data/spec/tic_tac_toe/strategy/mock.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad5f41218a6c0fbe21ea418aae195ede891e00c8
|
4
|
+
data.tar.gz: 72979a92b1f8ffc400aa6ccd1d587c386ec3f4c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 96b3da857ffaf1e25cb8b76c62f547aebee2f5effbc5cc801649494b9aeb951f97efc83733cff092f6400002ed2b39d0c583fc419741b50a6931559801545251
|
7
|
+
data.tar.gz: fdc55c56a5a3c22da80712bcbb852f24806ffec263e37c8590b44957b87139ef0afd5dce96abf97762f763943eaae64e7dbdd1e3fbeeb421af04a498f795030b
|
data/coverage/.resultset.json
CHANGED
@@ -1,107 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"RSpec": {
|
3
3
|
"coverage": {
|
4
|
-
"/Users/nhunguyen/Documents/Ruby/tictactoe/lib/tic_tac_toe/board.rb": [
|
5
|
-
1,
|
6
|
-
1,
|
7
|
-
null,
|
8
|
-
null,
|
9
|
-
1,
|
10
|
-
1,
|
11
|
-
null,
|
12
|
-
1,
|
13
|
-
119,
|
14
|
-
119,
|
15
|
-
null,
|
16
|
-
null,
|
17
|
-
1,
|
18
|
-
120,
|
19
|
-
120,
|
20
|
-
120,
|
21
|
-
null,
|
22
|
-
null,
|
23
|
-
1,
|
24
|
-
3380,
|
25
|
-
3377,
|
26
|
-
3377,
|
27
|
-
null,
|
28
|
-
3,
|
29
|
-
null,
|
30
|
-
null,
|
31
|
-
null,
|
32
|
-
1,
|
33
|
-
3070,
|
34
|
-
null,
|
35
|
-
null,
|
36
|
-
1,
|
37
|
-
2244,
|
38
|
-
null,
|
39
|
-
null,
|
40
|
-
1,
|
41
|
-
26,
|
42
|
-
null,
|
43
|
-
null,
|
44
|
-
1,
|
45
|
-
8643,
|
46
|
-
25929,
|
47
|
-
null,
|
48
|
-
null,
|
49
|
-
null,
|
50
|
-
1,
|
51
|
-
8640,
|
52
|
-
8640,
|
53
|
-
null,
|
54
|
-
233280,
|
55
|
-
25920,
|
56
|
-
null,
|
57
|
-
8640,
|
58
|
-
null,
|
59
|
-
null,
|
60
|
-
1,
|
61
|
-
8640,
|
62
|
-
8640,
|
63
|
-
8640,
|
64
|
-
null,
|
65
|
-
null,
|
66
|
-
1,
|
67
|
-
3898,
|
68
|
-
3898,
|
69
|
-
35082,
|
70
|
-
null,
|
71
|
-
3898,
|
72
|
-
null,
|
73
|
-
null,
|
74
|
-
1,
|
75
|
-
1,
|
76
|
-
38462,
|
77
|
-
38460,
|
78
|
-
null,
|
79
|
-
9667,
|
80
|
-
null,
|
81
|
-
null,
|
82
|
-
1,
|
83
|
-
38462,
|
84
|
-
38461,
|
85
|
-
38460,
|
86
|
-
null,
|
87
|
-
null,
|
88
|
-
1,
|
89
|
-
38460,
|
90
|
-
null,
|
91
|
-
null,
|
92
|
-
1,
|
93
|
-
8640,
|
94
|
-
77760,
|
95
|
-
null,
|
96
|
-
8640,
|
97
|
-
null,
|
98
|
-
null,
|
99
|
-
1,
|
100
|
-
86400,
|
101
|
-
null,
|
102
|
-
null,
|
103
|
-
null
|
104
|
-
],
|
105
4
|
"/Users/nhunguyen/Documents/Ruby/tictactoe/lib/tic_tac_toe/game_factory.rb": [
|
106
5
|
1,
|
107
6
|
1,
|
@@ -110,11 +9,11 @@
|
|
110
9
|
1,
|
111
10
|
1,
|
112
11
|
1,
|
113
|
-
|
12
|
+
13,
|
114
13
|
null,
|
115
14
|
null,
|
116
15
|
1,
|
117
|
-
|
16
|
+
6,
|
118
17
|
null,
|
119
18
|
null,
|
120
19
|
1,
|
@@ -135,26 +34,22 @@
|
|
135
34
|
1,
|
136
35
|
1,
|
137
36
|
1,
|
138
|
-
1,
|
139
|
-
1,
|
140
37
|
null,
|
141
38
|
null,
|
142
39
|
1,
|
143
40
|
2,
|
144
|
-
2,
|
145
|
-
2,
|
146
41
|
null,
|
147
42
|
null,
|
148
43
|
1,
|
149
44
|
1,
|
150
|
-
1,
|
151
|
-
1,
|
152
45
|
null,
|
153
46
|
null,
|
154
47
|
1,
|
155
48
|
1,
|
49
|
+
null,
|
50
|
+
null,
|
156
51
|
1,
|
157
|
-
|
52
|
+
5,
|
158
53
|
null,
|
159
54
|
null,
|
160
55
|
null
|
@@ -202,216 +97,40 @@
|
|
202
97
|
null,
|
203
98
|
null
|
204
99
|
],
|
205
|
-
"/Users/nhunguyen/Documents/Ruby/tictactoe/lib/tic_tac_toe/
|
206
|
-
1,
|
207
|
-
1,
|
208
|
-
1,
|
209
|
-
73,
|
210
|
-
null,
|
211
|
-
null,
|
212
|
-
1,
|
213
|
-
3080,
|
214
|
-
1922,
|
215
|
-
1605,
|
216
|
-
null,
|
217
|
-
null,
|
218
|
-
1,
|
219
|
-
321,
|
220
|
-
null,
|
221
|
-
null,
|
222
|
-
1,
|
223
|
-
13528,
|
224
|
-
null,
|
225
|
-
null,
|
226
|
-
1,
|
227
|
-
1,
|
228
|
-
8638,
|
229
|
-
164366,
|
230
|
-
null,
|
231
|
-
null,
|
232
|
-
null,
|
233
|
-
1,
|
234
|
-
8638,
|
235
|
-
null,
|
236
|
-
null,
|
237
|
-
null
|
238
|
-
],
|
239
|
-
"/Users/nhunguyen/Documents/Ruby/tictactoe/lib/tic_tac_toe/player_factory.rb": [
|
240
|
-
1,
|
241
|
-
1,
|
242
|
-
1,
|
243
|
-
null,
|
244
|
-
1,
|
245
|
-
1,
|
246
|
-
null,
|
247
|
-
1,
|
248
|
-
4,
|
249
|
-
null,
|
250
|
-
null,
|
251
|
-
1,
|
252
|
-
4,
|
253
|
-
4,
|
254
|
-
null,
|
255
|
-
null,
|
256
|
-
null
|
257
|
-
],
|
258
|
-
"/Users/nhunguyen/Documents/Ruby/tictactoe/lib/tic_tac_toe/strategy/minimax.rb": [
|
259
|
-
1,
|
260
|
-
null,
|
261
|
-
1,
|
262
|
-
1,
|
263
|
-
1,
|
264
|
-
1,
|
265
|
-
27,
|
266
|
-
27,
|
267
|
-
27,
|
268
|
-
27,
|
269
|
-
null,
|
270
|
-
null,
|
271
|
-
1,
|
272
|
-
23,
|
273
|
-
3,
|
274
|
-
null,
|
275
|
-
20,
|
276
|
-
20,
|
277
|
-
null,
|
278
|
-
null,
|
279
|
-
null,
|
280
|
-
1,
|
281
|
-
1,
|
282
|
-
1,
|
283
|
-
1,
|
284
|
-
1,
|
285
|
-
null,
|
286
|
-
1,
|
287
|
-
23,
|
288
|
-
23,
|
289
|
-
null,
|
290
|
-
null,
|
291
|
-
1,
|
292
|
-
3,
|
293
|
-
1,
|
294
|
-
null,
|
295
|
-
null,
|
296
|
-
1,
|
297
|
-
1620,
|
298
|
-
1620,
|
299
|
-
3068,
|
300
|
-
3068,
|
301
|
-
3068,
|
302
|
-
3068,
|
303
|
-
null,
|
304
|
-
1620,
|
305
|
-
null,
|
306
|
-
null,
|
307
|
-
1,
|
308
|
-
3068,
|
309
|
-
1468,
|
310
|
-
null,
|
311
|
-
1600,
|
312
|
-
1600,
|
313
|
-
null,
|
314
|
-
null,
|
315
|
-
null,
|
316
|
-
1,
|
317
|
-
3068,
|
318
|
-
null,
|
319
|
-
null,
|
320
|
-
1,
|
321
|
-
1468,
|
322
|
-
1468,
|
323
|
-
320,
|
324
|
-
317,
|
325
|
-
null,
|
326
|
-
null,
|
327
|
-
null,
|
328
|
-
null,
|
329
|
-
1,
|
330
|
-
1920,
|
331
|
-
null,
|
332
|
-
null,
|
333
|
-
1,
|
334
|
-
3227,
|
335
|
-
4688,
|
336
|
-
null,
|
337
|
-
null,
|
338
|
-
null,
|
339
|
-
1,
|
340
|
-
null,
|
341
|
-
null
|
342
|
-
],
|
343
|
-
"/Users/nhunguyen/Documents/Ruby/tictactoe/lib/tic_tac_toe/strategy/console_user.rb": [
|
344
|
-
1,
|
345
|
-
1,
|
346
|
-
1,
|
347
|
-
null,
|
348
|
-
1,
|
349
|
-
7,
|
350
|
-
7,
|
351
|
-
null,
|
352
|
-
null,
|
100
|
+
"/Users/nhunguyen/Documents/Ruby/tictactoe/lib/tic_tac_toe/main.rb": [
|
353
101
|
1,
|
354
|
-
3,
|
355
|
-
null,
|
356
|
-
3,
|
357
|
-
5,
|
358
|
-
5,
|
359
|
-
null,
|
360
|
-
null,
|
361
|
-
3,
|
362
|
-
null,
|
363
|
-
null,
|
364
102
|
1,
|
365
103
|
1,
|
366
104
|
null,
|
367
|
-
null,
|
368
|
-
null,
|
369
|
-
null
|
370
|
-
],
|
371
|
-
"/Users/nhunguyen/Documents/Ruby/tictactoe/lib/tic_tac_toe/player.rb": [
|
372
105
|
1,
|
373
106
|
1,
|
374
107
|
1,
|
375
108
|
null,
|
376
109
|
1,
|
377
|
-
|
378
|
-
|
379
|
-
|
110
|
+
12,
|
111
|
+
12,
|
112
|
+
12,
|
380
113
|
null,
|
381
114
|
null,
|
382
115
|
1,
|
383
|
-
|
116
|
+
12,
|
117
|
+
12,
|
118
|
+
12,
|
119
|
+
12,
|
120
|
+
12,
|
384
121
|
null,
|
385
122
|
null,
|
386
|
-
null
|
387
|
-
],
|
388
|
-
"/Users/nhunguyen/Documents/Ruby/tictactoe/lib/tic_tac_toe/main.rb": [
|
389
|
-
1,
|
390
|
-
1,
|
391
|
-
1,
|
392
|
-
null,
|
393
123
|
1,
|
394
124
|
1,
|
125
|
+
13,
|
126
|
+
13,
|
127
|
+
13,
|
395
128
|
1,
|
396
|
-
null,
|
397
|
-
1,
|
398
|
-
14,
|
399
|
-
14,
|
400
|
-
14,
|
401
|
-
null,
|
402
|
-
null,
|
403
129
|
1,
|
404
|
-
11,
|
405
|
-
11,
|
406
|
-
11,
|
407
130
|
null,
|
408
|
-
11,
|
409
|
-
11,
|
410
|
-
11,
|
411
131
|
null,
|
412
132
|
null,
|
413
133
|
1,
|
414
|
-
1,
|
415
134
|
7,
|
416
135
|
7,
|
417
136
|
7,
|
@@ -423,9 +142,9 @@
|
|
423
142
|
null,
|
424
143
|
null,
|
425
144
|
1,
|
145
|
+
12,
|
146
|
+
12,
|
426
147
|
11,
|
427
|
-
11,
|
428
|
-
10,
|
429
148
|
null,
|
430
149
|
1,
|
431
150
|
null,
|
@@ -441,8 +160,8 @@
|
|
441
160
|
1,
|
442
161
|
null,
|
443
162
|
1,
|
444
|
-
|
445
|
-
|
163
|
+
14,
|
164
|
+
14,
|
446
165
|
null,
|
447
166
|
null,
|
448
167
|
1,
|
@@ -454,10 +173,14 @@
|
|
454
173
|
null,
|
455
174
|
null,
|
456
175
|
1,
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
176
|
+
5,
|
177
|
+
5,
|
178
|
+
5,
|
179
|
+
5,
|
180
|
+
4,
|
181
|
+
null,
|
182
|
+
1,
|
183
|
+
null,
|
461
184
|
null,
|
462
185
|
null,
|
463
186
|
1,
|
@@ -487,16 +210,25 @@
|
|
487
210
|
null,
|
488
211
|
null,
|
489
212
|
1,
|
490
|
-
|
491
|
-
|
492
|
-
|
213
|
+
5,
|
214
|
+
5,
|
215
|
+
20,
|
493
216
|
null,
|
494
|
-
|
217
|
+
5,
|
218
|
+
null,
|
219
|
+
null,
|
220
|
+
1,
|
221
|
+
20,
|
222
|
+
null,
|
223
|
+
null,
|
224
|
+
1,
|
225
|
+
40,
|
226
|
+
20,
|
495
227
|
null,
|
496
228
|
null,
|
497
229
|
null
|
498
230
|
]
|
499
231
|
},
|
500
|
-
"timestamp":
|
232
|
+
"timestamp": 1374084056
|
501
233
|
}
|
502
234
|
}
|
data/lib/tic_tac_toe/board.rb
CHANGED
@@ -21,7 +21,7 @@ module TicTacToe
|
|
21
21
|
squares[move] = value
|
22
22
|
@unique_marked_values << value if !@unique_marked_values.include?(value)
|
23
23
|
else
|
24
|
-
raise MoveNotAvailableError
|
24
|
+
raise MoveNotAvailableError.new("#{value} is trying to move at #{move}. available_moves: #{available_moves.inspect}")
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
@@ -67,6 +67,14 @@ module TicTacToe
|
|
67
67
|
result
|
68
68
|
end
|
69
69
|
|
70
|
+
def clone
|
71
|
+
board_copy = self.class.new
|
72
|
+
squares.each_with_index do |value, index|
|
73
|
+
board_copy.mark(index, value) if value
|
74
|
+
end
|
75
|
+
board_copy
|
76
|
+
end
|
77
|
+
|
70
78
|
private
|
71
79
|
def move_available?(move)
|
72
80
|
return false if out_of_range?(move)
|
@@ -78,7 +86,6 @@ module TicTacToe
|
|
78
86
|
def out_of_range?(move)
|
79
87
|
return true if move < 0
|
80
88
|
return true if move >= squares.size
|
81
|
-
return false
|
82
89
|
end
|
83
90
|
|
84
91
|
def marked?(move)
|
@@ -9,7 +9,7 @@ module TicTacToe
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def types
|
12
|
-
[
|
12
|
+
[[:human, :computer], [:computer, :human], [:human, :human], [:computer, :computer]]
|
13
13
|
end
|
14
14
|
|
15
15
|
def create(type_index, board)
|
@@ -29,27 +29,23 @@ module TicTacToe
|
|
29
29
|
|
30
30
|
private
|
31
31
|
def computer_human_game(board)
|
32
|
-
|
33
|
-
human = @player_factory.human
|
34
|
-
TicTacToe::Game.new(board, computer, human)
|
32
|
+
create_game(board, @player_factory.computer(board), @player_factory.human)
|
35
33
|
end
|
36
34
|
|
37
35
|
def human_computer_game(board)
|
38
|
-
|
39
|
-
human = @player_factory.human
|
40
|
-
TicTacToe::Game.new(board, human, computer)
|
36
|
+
create_game(board, @player_factory.human, @player_factory.computer(board))
|
41
37
|
end
|
42
38
|
|
43
39
|
def human_human_game(board)
|
44
|
-
|
45
|
-
human2 = @player_factory.human("Friend", "O")
|
46
|
-
TicTacToe::Game.new(board, human1, human2)
|
40
|
+
create_game(board, @player_factory.human, @player_factory.human("Friend", "O"))
|
47
41
|
end
|
48
42
|
|
49
43
|
def computer_computer_game(board)
|
50
|
-
|
51
|
-
|
52
|
-
|
44
|
+
create_game(board, @player_factory.computer(board, "X", "O"), @player_factory.computer(board, "O", "X"))
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_game(board, player1, player2)
|
48
|
+
TicTacToe::Game.new(board, player1, player2)
|
53
49
|
end
|
54
50
|
end
|
55
51
|
end
|
data/lib/tic_tac_toe/main.rb
CHANGED
@@ -14,15 +14,22 @@ module TicTacToe
|
|
14
14
|
|
15
15
|
def start
|
16
16
|
@ui.display_welcome_message
|
17
|
-
|
18
|
-
@game = @game_factory.create(game_type, @board)
|
19
|
-
|
17
|
+
@game = create_game
|
20
18
|
play until @game.over?
|
21
19
|
@ui.display_board(@board)
|
22
20
|
display_result
|
23
21
|
end
|
24
22
|
|
25
23
|
private
|
24
|
+
def create_game
|
25
|
+
game_type = @ui.game_type
|
26
|
+
begin
|
27
|
+
@game_factory.create(game_type, @board)
|
28
|
+
rescue ArgumentError
|
29
|
+
create_game
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
26
33
|
def play
|
27
34
|
@ui.display_board(@board)
|
28
35
|
player = @game.current_player
|
@@ -7,16 +7,12 @@ module TicTacToe
|
|
7
7
|
@board = board
|
8
8
|
@player = player
|
9
9
|
@opponent = opponent
|
10
|
-
@rules = TicTacToe::Rules.new(@board)
|
11
10
|
end
|
12
11
|
|
13
12
|
def move
|
14
|
-
if first_move?
|
15
|
-
|
16
|
-
|
17
|
-
move = minimax(@player)
|
18
|
-
move.move
|
19
|
-
end
|
13
|
+
return first_move if first_move?
|
14
|
+
move = minimax(@player, @board)
|
15
|
+
move.move
|
20
16
|
end
|
21
17
|
|
22
18
|
private
|
@@ -35,22 +31,22 @@ module TicTacToe
|
|
35
31
|
0
|
36
32
|
end
|
37
33
|
|
38
|
-
def minimax(player)
|
34
|
+
def minimax(player, board)
|
39
35
|
moves = []
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
36
|
+
board.available_moves.each do |move|
|
37
|
+
clone_board = board.clone
|
38
|
+
clone_board.mark(move, player)
|
39
|
+
moves << player_move(player, move, clone_board)
|
44
40
|
found_best_move?(moves[-1])
|
45
41
|
end
|
46
42
|
best_move(moves)
|
47
43
|
end
|
48
44
|
|
49
|
-
def player_move(player, move)
|
50
|
-
if
|
51
|
-
PlayerMove.new(move, score(player), 0)
|
45
|
+
def player_move(player, move, board)
|
46
|
+
if rules(board).game_over?
|
47
|
+
PlayerMove.new(move, score(player, board), 0)
|
52
48
|
else
|
53
|
-
child_move = minimax(opponent(player))
|
49
|
+
child_move = minimax(opponent(player), board)
|
54
50
|
PlayerMove.new(move, -child_move.score, child_move.depth += 1)
|
55
51
|
end
|
56
52
|
end
|
@@ -59,13 +55,11 @@ module TicTacToe
|
|
59
55
|
move.score == WINNING_SCORE and move.depth == 0
|
60
56
|
end
|
61
57
|
|
62
|
-
def score(player)
|
63
|
-
winner =
|
58
|
+
def score(player, board)
|
59
|
+
winner = rules(board).winner
|
64
60
|
return WINNING_SCORE if winner == player
|
65
61
|
return LOSING_SCORE if winner == opponent(player)
|
66
|
-
return TIE if
|
67
|
-
|
68
|
-
nil
|
62
|
+
return TIE if rules(board).tied?
|
69
63
|
end
|
70
64
|
|
71
65
|
def opponent(player)
|
@@ -76,6 +70,10 @@ module TicTacToe
|
|
76
70
|
sorted_moves = moves.sort{ |a, b| [a.score, a.depth] <=> [b.score, b.depth]}
|
77
71
|
sorted_moves.max_by {|m| m.score}
|
78
72
|
end
|
73
|
+
|
74
|
+
def rules(board)
|
75
|
+
TicTacToe::Rules.new(board)
|
76
|
+
end
|
79
77
|
end
|
80
78
|
|
81
79
|
PlayerMove = Struct.new(:move, :score, :depth)
|
@@ -17,10 +17,14 @@ module TicTacToe
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def game_type
|
20
|
-
@output.puts("Please
|
20
|
+
@output.puts("Please enter a game type from the list.")
|
21
21
|
@output.puts(game_type_list)
|
22
22
|
type = @input.gets
|
23
|
-
type
|
23
|
+
if type =~ /\d/
|
24
|
+
type.to_i
|
25
|
+
else
|
26
|
+
game_type
|
27
|
+
end
|
24
28
|
end
|
25
29
|
|
26
30
|
def display_winner(winner)
|
@@ -28,7 +32,7 @@ module TicTacToe
|
|
28
32
|
end
|
29
33
|
|
30
34
|
def display_tied_game
|
31
|
-
@output.puts("
|
35
|
+
@output.puts("Tied!")
|
32
36
|
end
|
33
37
|
|
34
38
|
def display_square_not_available
|
@@ -52,9 +56,18 @@ module TicTacToe
|
|
52
56
|
def game_type_list
|
53
57
|
result = ""
|
54
58
|
TicTacToe::GameFactory.new.types.each_with_index do |value, index|
|
55
|
-
result << "#{index + 1} - #{value}\n"
|
59
|
+
result << "#{index + 1} - #{game_type_values(value)}\n"
|
56
60
|
end
|
57
61
|
result
|
58
62
|
end
|
63
|
+
|
64
|
+
def game_type_values(types)
|
65
|
+
"#{player_type(types[0])} vs #{player_type(types[1])}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def player_type(type)
|
69
|
+
return "Human" if type == :human
|
70
|
+
return "Computer" if type == :computer
|
71
|
+
end
|
59
72
|
end
|
60
73
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'tic_tac_toe/board'
|
2
|
+
require 'tic_tac_toe/rules'
|
3
|
+
require 'tic_tac_toe/player_factory'
|
4
|
+
|
5
|
+
class SimpleStrategy
|
6
|
+
attr_accessor :next_move
|
7
|
+
|
8
|
+
def move
|
9
|
+
next_move
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "Unbeatable computer", :slow_test => true do
|
14
|
+
before(:each) do
|
15
|
+
@board = TicTacToe::Board.new
|
16
|
+
@human_value = "X"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "wins all game when computer goes first" do
|
20
|
+
make_computer_move(@board)
|
21
|
+
make_move(@board, [])
|
22
|
+
end
|
23
|
+
|
24
|
+
it "wins all game when human goes first" do
|
25
|
+
make_move(@board, [])
|
26
|
+
end
|
27
|
+
|
28
|
+
def make_computer_move(clone_board)
|
29
|
+
computer = TicTacToe::PlayerFactory.new.computer(clone_board)
|
30
|
+
clone_board.mark(computer.move, computer.value)
|
31
|
+
clone_board
|
32
|
+
end
|
33
|
+
|
34
|
+
def rules(board)
|
35
|
+
TicTacToe::Rules.new(board)
|
36
|
+
end
|
37
|
+
|
38
|
+
def make_move(board, move_history)
|
39
|
+
if rules(board).game_over?
|
40
|
+
print "."
|
41
|
+
if rules(board).winner == @human_value
|
42
|
+
print move_history
|
43
|
+
raise "Human wins."
|
44
|
+
end
|
45
|
+
else
|
46
|
+
board.available_moves.each do |move|
|
47
|
+
move_history << move
|
48
|
+
cloned_board = board.clone
|
49
|
+
cloned_board.mark(move, "X")
|
50
|
+
make_computer_move(cloned_board) if !rules(cloned_board).game_over?
|
51
|
+
make_move(cloned_board, move_history)
|
52
|
+
move_history.delete(move)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
end
|
data/spec/mocks/game.rb
CHANGED
data/spec/mocks/game_factory.rb
CHANGED
@@ -6,3 +6,9 @@ class MockPlayerFactory
|
|
6
6
|
define(:human) {|name = "Sue", value = "X"|}
|
7
7
|
define(:computer) {|board, value = "X", opponent="O"|}
|
8
8
|
end
|
9
|
+
|
10
|
+
describe TicTacToe::PlayerFactory do
|
11
|
+
it "checks mock player" do
|
12
|
+
MockPlayerFactory.should be_substitutable_for(TicTacToe::PlayerFactory)
|
13
|
+
end
|
14
|
+
end
|
data/spec/mocks/rules.rb
CHANGED
data/spec/mocks/ui/console.rb
CHANGED
@@ -165,7 +165,7 @@ describe TicTacToe::Board do
|
|
165
165
|
it "returns two marks when two user marks the board" do
|
166
166
|
@board.mark(4, "X")
|
167
167
|
@board.mark(0, "O")
|
168
|
-
@board.unique_marked_values.should
|
168
|
+
@board.unique_marked_values.should =~ ["X", "O"]
|
169
169
|
end
|
170
170
|
|
171
171
|
it "returns one mark even though user marks the board twice" do
|
@@ -176,6 +176,30 @@ describe TicTacToe::Board do
|
|
176
176
|
|
177
177
|
end
|
178
178
|
|
179
|
+
describe "creating a copy of the board" do
|
180
|
+
it "is marked on the copy when the original is marked" do
|
181
|
+
@board.mark(4, "X")
|
182
|
+
board_copy = @board.clone
|
183
|
+
|
184
|
+
board_copy.available_moves.should_not include(4)
|
185
|
+
end
|
186
|
+
|
187
|
+
it "is unaffected by changes to the original" do
|
188
|
+
@board.mark(4, "X")
|
189
|
+
board_copy = @board.clone
|
190
|
+
@board.mark(5, "O")
|
191
|
+
|
192
|
+
board_copy.available_moves.should include(5)
|
193
|
+
end
|
194
|
+
|
195
|
+
it "marks the actual value" do
|
196
|
+
@board.mark(4, "X")
|
197
|
+
board_copy = @board.clone
|
198
|
+
|
199
|
+
board_copy.unique_marked_values.should == ["X"]
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
179
203
|
def mark_all_squares
|
180
204
|
(0...@size**2).each {|position| @board.mark(position, @player)}
|
181
205
|
end
|
@@ -8,38 +8,38 @@ describe TicTacToe::Game do
|
|
8
8
|
before(:each) do
|
9
9
|
@board = TicTacToe::Board.new
|
10
10
|
|
11
|
-
@
|
12
|
-
@
|
11
|
+
@player1_strategy = MockDynamicStrategy.new
|
12
|
+
@player1 = TicTacToe::Player.new("Todd", "X", @player1_strategy)
|
13
13
|
|
14
|
-
@
|
15
|
-
@
|
14
|
+
@player2_strategy = MockDynamicStrategy.new
|
15
|
+
@player2 = TicTacToe::Player.new("John", "O", @player2_strategy)
|
16
16
|
|
17
|
-
@game = TicTacToe::Game.new(@board, @
|
17
|
+
@game = TicTacToe::Game.new(@board, @player1, @player2)
|
18
18
|
end
|
19
19
|
|
20
20
|
context "marking board" do
|
21
21
|
it "mark the board with user input" do
|
22
|
-
@
|
22
|
+
@player1_strategy.add_move(1)
|
23
23
|
@game.make_move
|
24
|
-
@board.unique_marked_values.should include(@
|
24
|
+
@board.unique_marked_values.should include(@player1.value)
|
25
25
|
end
|
26
26
|
|
27
27
|
it "does not mark the board if user doesn't return an input" do
|
28
|
-
@
|
28
|
+
@player1_strategy.add_move(nil)
|
29
29
|
@game.make_move
|
30
|
-
@board.unique_marked_values.should_not include(@
|
30
|
+
@board.unique_marked_values.should_not include(@player1.value)
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
34
|
describe "return winner player based on the value return from rules" do
|
35
35
|
it "is Todd when value is X" do
|
36
|
-
mark_board([0, 1, 2], @
|
37
|
-
@game.winner.should == @
|
36
|
+
mark_board([0, 1, 2], @player1.value)
|
37
|
+
@game.winner.should == @player1
|
38
38
|
end
|
39
39
|
|
40
40
|
it "is John when it is O" do
|
41
|
-
mark_board([0, 1, 2], @
|
42
|
-
@game.winner.should == @
|
41
|
+
mark_board([0, 1, 2], @player2.value)
|
42
|
+
@game.winner.should == @player2
|
43
43
|
end
|
44
44
|
|
45
45
|
it "is nil when no one wins" do
|
@@ -49,25 +49,25 @@ describe TicTacToe::Game do
|
|
49
49
|
|
50
50
|
describe "changes player" do
|
51
51
|
it "doesn't change player if player 1 doesn't return a move" do
|
52
|
-
@game.current_player.should == @
|
53
|
-
@
|
52
|
+
@game.current_player.should == @player1
|
53
|
+
@player1_strategy.add_move(nil)
|
54
54
|
|
55
55
|
@game.make_move
|
56
|
-
@game.current_player.should == @
|
56
|
+
@game.current_player.should == @player1
|
57
57
|
end
|
58
58
|
|
59
59
|
it "changes to player 2 after player 1 moves" do
|
60
|
-
@game.current_player.should == @
|
61
|
-
@
|
60
|
+
@game.current_player.should == @player1
|
61
|
+
@player1_strategy.add_move(1)
|
62
62
|
|
63
63
|
@game.make_move
|
64
|
-
@game.current_player.should == @
|
64
|
+
@game.current_player.should == @player2
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
68
|
describe "game over" do
|
69
69
|
it "is over when there is a winner" do
|
70
|
-
mark_board([0, 1, 2], @
|
70
|
+
mark_board([0, 1, 2], @player2.value)
|
71
71
|
@game.should be_over
|
72
72
|
end
|
73
73
|
|
@@ -81,11 +81,42 @@ describe TicTacToe::Game do
|
|
81
81
|
end
|
82
82
|
|
83
83
|
it "is not over when there is no winner or a tied" do
|
84
|
-
mark_board([1, 2], @
|
84
|
+
mark_board([1, 2], @player1.value)
|
85
85
|
@game.should_not be_over
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
+
# describe "a copy of the game" do
|
90
|
+
# it "has the same current player" do
|
91
|
+
# @player1_strategy.add_move(1)
|
92
|
+
# @game.make_move
|
93
|
+
# @game.clone.current_player.should == @game.current_player
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# it "has the same moves previously made" do
|
97
|
+
# @player1_strategy.add_move(1)
|
98
|
+
# @game.make_move
|
99
|
+
# @game.clone.board.available_moves.should_not include(1)
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# it "has the current player unaffected by changes to the original game" do
|
103
|
+
# clone_game = @game.clone
|
104
|
+
# @player1_strategy.add_move(1)
|
105
|
+
# @game.make_move
|
106
|
+
#
|
107
|
+
# clone_game.current_player.should == @player1
|
108
|
+
# @game.current_player.should == @player2
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# it "clones the board" do
|
112
|
+
# clone_game = @game.clone
|
113
|
+
# @player1_strategy.add_move(1)
|
114
|
+
# @game.make_move
|
115
|
+
#
|
116
|
+
# @game.board.available_moves.should_not include(1)
|
117
|
+
# clone_game.board.available_moves.should include(1)
|
118
|
+
# end
|
119
|
+
# end
|
89
120
|
def mark_board(moves, value)
|
90
121
|
moves.each do |m|
|
91
122
|
@board.mark(m, value)
|
@@ -19,20 +19,6 @@ describe TicTacToe::Main do
|
|
19
19
|
@controller = TicTacToe::Main.new(@ui, @game_factory)
|
20
20
|
end
|
21
21
|
|
22
|
-
context "ensure mock class has the same interface" do
|
23
|
-
it "checks MockGame" do
|
24
|
-
MockGame.should be_substitutable_for(TicTacToe::Game)
|
25
|
-
end
|
26
|
-
|
27
|
-
it "checks console" do
|
28
|
-
MockConsole.should be_substitutable_for(TicTacToe::Console)
|
29
|
-
end
|
30
|
-
|
31
|
-
it "checks game factory" do
|
32
|
-
MockGameFactory.should be_substitutable_for(TicTacToe::GameFactory)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
22
|
describe "starts game" do
|
37
23
|
it "displays welcome message" do
|
38
24
|
@controller.start
|
@@ -48,6 +34,11 @@ describe TicTacToe::Main do
|
|
48
34
|
@controller.start
|
49
35
|
@game_factory.was told_to(:create)
|
50
36
|
end
|
37
|
+
|
38
|
+
it "asks ui for game type again if the input is incorrect" do
|
39
|
+
@game_factory.will_create ArgumentError.new, @game
|
40
|
+
@controller.start
|
41
|
+
end
|
51
42
|
end
|
52
43
|
|
53
44
|
describe "play game" do
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'tic_tac_toe/spec_helper'
|
2
2
|
require 'tic_tac_toe/player'
|
3
|
-
require '
|
3
|
+
require 'mocks/strategy/dynamic'
|
4
4
|
|
5
5
|
describe TicTacToe::Player do
|
6
6
|
|
@@ -18,13 +18,13 @@ describe TicTacToe::Player do
|
|
18
18
|
|
19
19
|
it "gets player's move" do
|
20
20
|
move = 4
|
21
|
-
strategy =
|
21
|
+
strategy = MockDynamicStrategy.new([4])
|
22
22
|
player = TicTacToe::Player.new(nil, nil, strategy)
|
23
23
|
player.move.should == move
|
24
24
|
end
|
25
25
|
|
26
26
|
it "gets strategy" do
|
27
|
-
strategy =
|
27
|
+
strategy = MockDynamicStrategy.new([4])
|
28
28
|
player = TicTacToe::Player.new(nil, nil, strategy)
|
29
29
|
player.strategy.should == strategy
|
30
30
|
|
@@ -50,14 +50,14 @@ describe TicTacToe::Console do
|
|
50
50
|
|
51
51
|
context "reading user input for game type" do
|
52
52
|
it "displays a list of game type" do
|
53
|
-
expected_display = "1 -
|
53
|
+
expected_display = "1 - Human vs Computer\n2 - Computer vs Human\n3 - Human vs Human\n4 - Computer vs Computer"
|
54
54
|
@input.string = "1"
|
55
55
|
@console.game_type
|
56
56
|
@output.string.should match expected_display
|
57
57
|
end
|
58
58
|
|
59
59
|
it "displays a message asking user to select a game type" do
|
60
|
-
expected_display = "Please
|
60
|
+
expected_display = "Please enter a game type from the list."
|
61
61
|
@input.string = "1"
|
62
62
|
@console.game_type
|
63
63
|
@output.string.should match expected_display
|
@@ -67,6 +67,13 @@ describe TicTacToe::Console do
|
|
67
67
|
@input.string = "1"
|
68
68
|
@console.game_type.should == 1
|
69
69
|
end
|
70
|
+
|
71
|
+
it "asks for user input again when user enter a character" do
|
72
|
+
expected_display = "Please enter a game type from the list."
|
73
|
+
@input.should_receive(:gets).twice.and_return("a", "1")
|
74
|
+
@console.game_type
|
75
|
+
@output.string.should match expected_display
|
76
|
+
end
|
70
77
|
end
|
71
78
|
|
72
79
|
it "displays winner" do
|
@@ -77,7 +84,7 @@ describe TicTacToe::Console do
|
|
77
84
|
|
78
85
|
it "display tied game" do
|
79
86
|
@console.display_tied_game
|
80
|
-
@output.string.should == "
|
87
|
+
@output.string.should == "Tied!\n"
|
81
88
|
end
|
82
89
|
|
83
90
|
it "displays square is not available" do
|
data/tic_tac_toe_nhu.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tic_tac_toe_nhu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nhu Nguyen
|
@@ -40,6 +40,7 @@ files:
|
|
40
40
|
- lib/tic_tac_toe/strategy/minimax.rb
|
41
41
|
- lib/tic_tac_toe/ui/console.rb
|
42
42
|
- spec/integration/tictactoe/tic_tac_toe.rb
|
43
|
+
- spec/integration/tictactoe/unbeatable_computer_spec.rb
|
43
44
|
- spec/mocks/game.rb
|
44
45
|
- spec/mocks/game_factory.rb
|
45
46
|
- spec/mocks/player_factory.rb
|
@@ -57,7 +58,6 @@ files:
|
|
57
58
|
- spec/tic_tac_toe/spec_helper.rb
|
58
59
|
- spec/tic_tac_toe/strategy/console_user_spec.rb
|
59
60
|
- spec/tic_tac_toe/strategy/minimax_spec.rb
|
60
|
-
- spec/tic_tac_toe/strategy/mock.rb
|
61
61
|
- spec/tic_tac_toe/ui/console_spec.rb
|
62
62
|
- tic_tac_toe_nhu.gemspec
|
63
63
|
homepage: http://rubygems.org/gems/tic_tac_toe_nhu
|