tactical_tic_tac_toe 0.1.1 → 0.2.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 +4 -4
- data/lib/board.rb +7 -34
- data/lib/computer_player.rb +3 -4
- data/lib/game.rb +2 -2
- data/lib/human_player.rb +3 -4
- data/spec/board_spec.rb +17 -86
- data/spec/computer_player_spec.rb +22 -12
- data/spec/game_spec.rb +17 -5
- data/spec/human_player_spec.rb +3 -2
- data/spec/spec_helper.rb +6 -57
- metadata +2 -4
- data/lib/old_computer_player.rb +0 -89
- data/spec/old_computer_player_spec.rb +0 -230
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 91f9d2072259b9710988edde50d53c39cc3962e7
|
|
4
|
+
data.tar.gz: fb68237c3ad7abf7c4259fef3bffc81d45701629
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0232937a96a30ad91f9d27fbb38cd67edfd6a7f5e327557edfb2859faa4dd60fe4fa396bc6732c10fee0fe1bcd2c8f9a823a1beacfe6bfa4758b9fd322cd8a10
|
|
7
|
+
data.tar.gz: 2303ffecb1799bfda197289f85d7e418b34edb71efa5f5a83a87d16853d86f2c77df5dfcd34dd5ebdb7fd4e0cb08c252ebdd0305d464e34fb3f3a86b845847f8
|
data/lib/board.rb
CHANGED
|
@@ -1,34 +1,23 @@
|
|
|
1
1
|
module TicTacToe
|
|
2
2
|
class Board
|
|
3
|
-
BoardError = Class.new(StandardError)
|
|
4
3
|
BLANK_MARK = nil
|
|
5
4
|
|
|
6
5
|
attr_reader :size, :last_move_made
|
|
7
6
|
|
|
8
|
-
def self.blank_mark
|
|
9
|
-
BLANK_MARK
|
|
10
|
-
end
|
|
11
|
-
|
|
12
7
|
def initialize(parameters)
|
|
13
|
-
fail BoardError, "Given size is too small, must be 3 or greater" if parameters[:size] < 3
|
|
14
|
-
|
|
15
8
|
@size = parameters[:size]
|
|
16
|
-
|
|
17
|
-
@
|
|
9
|
+
@cells = parameters[:config] || blank_board_configuration
|
|
10
|
+
@last_move_made = parameters[:last_move_made]
|
|
18
11
|
end
|
|
19
12
|
|
|
20
13
|
def read_cell(row, col)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@cells[row][col]
|
|
14
|
+
@cells[row * @size + col]
|
|
24
15
|
end
|
|
25
16
|
|
|
26
17
|
def mark_cell(mark, row, col)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@last_move_made = [row, col]
|
|
31
|
-
@cells[row][col] = mark
|
|
18
|
+
config = @cells.dup
|
|
19
|
+
config[row * @size + col] = mark
|
|
20
|
+
Board.new(size: @size, config: config, last_move_made: [row, col])
|
|
32
21
|
end
|
|
33
22
|
|
|
34
23
|
def lines
|
|
@@ -50,10 +39,6 @@ module TicTacToe
|
|
|
50
39
|
self.read_cell(*last_move_made)
|
|
51
40
|
end
|
|
52
41
|
|
|
53
|
-
def deep_copy
|
|
54
|
-
Board.new(size: @size, config: map_cells_to_configuration(@cells))
|
|
55
|
-
end
|
|
56
|
-
|
|
57
42
|
def blank?(coordinates)
|
|
58
43
|
self.read_cell(*coordinates) == BLANK_MARK
|
|
59
44
|
end
|
|
@@ -83,20 +68,8 @@ module TicTacToe
|
|
|
83
68
|
|
|
84
69
|
private
|
|
85
70
|
|
|
86
|
-
def map_configuration_to_cells(config)
|
|
87
|
-
if Math.sqrt(config.size).to_i != @size
|
|
88
|
-
fail BoardError, "Given size does not reconcile with given configuration"
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
config.each_slice(@size).to_a
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def map_cells_to_configuration(cells)
|
|
95
|
-
cells.flatten
|
|
96
|
-
end
|
|
97
|
-
|
|
98
71
|
def blank_board_configuration
|
|
99
|
-
(
|
|
72
|
+
Array.new(@size**2) { BLANK_MARK }
|
|
100
73
|
end
|
|
101
74
|
|
|
102
75
|
def row_at(row)
|
data/lib/computer_player.rb
CHANGED
|
@@ -5,12 +5,12 @@ module TicTacToe
|
|
|
5
5
|
attr_reader :player_mark
|
|
6
6
|
|
|
7
7
|
def initialize(parameters)
|
|
8
|
-
@board = parameters[:board]
|
|
9
8
|
@player_mark = parameters[:player_mark]
|
|
10
9
|
@opponent_mark = parameters[:opponent_mark]
|
|
11
10
|
end
|
|
12
11
|
|
|
13
|
-
def move
|
|
12
|
+
def move(game)
|
|
13
|
+
@board = game.board
|
|
14
14
|
if @board.all_blank? && @board.size.odd?
|
|
15
15
|
[:row, :col].map { @board.size / 2 }
|
|
16
16
|
else
|
|
@@ -49,11 +49,10 @@ module TicTacToe
|
|
|
49
49
|
|
|
50
50
|
board.blank_cell_coordinates.map do |coordinates|
|
|
51
51
|
child_node = {
|
|
52
|
-
board: board.
|
|
52
|
+
board: board.mark_cell(current_player_mark, *coordinates),
|
|
53
53
|
current_player_mark: toggle_mark(current_player_mark),
|
|
54
54
|
last_move_made: coordinates
|
|
55
55
|
}
|
|
56
|
-
child_node[:board].mark_cell(current_player_mark, *coordinates)
|
|
57
56
|
child_node
|
|
58
57
|
end
|
|
59
58
|
end
|
data/lib/game.rb
CHANGED
|
@@ -37,13 +37,13 @@ module TicTacToe
|
|
|
37
37
|
def handle_one_turn(current_player)
|
|
38
38
|
@interface.show_game_board(@board)
|
|
39
39
|
coordinates = get_valid_move(current_player)
|
|
40
|
-
@board.mark_cell(current_player.player_mark, *coordinates)
|
|
40
|
+
@board = @board.mark_cell(current_player.player_mark, *coordinates)
|
|
41
41
|
@interface.report_move(current_player.player_mark, coordinates)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def get_valid_move(player)
|
|
45
45
|
loop do
|
|
46
|
-
coordinates = player.move
|
|
46
|
+
coordinates = player.move(self)
|
|
47
47
|
if @board.out_of_bounds?(coordinates) || @board.marked?(coordinates)
|
|
48
48
|
@interface.report_invalid_move(coordinates)
|
|
49
49
|
else
|
data/lib/human_player.rb
CHANGED
|
@@ -3,12 +3,11 @@ module TicTacToe
|
|
|
3
3
|
attr_reader :player_mark
|
|
4
4
|
|
|
5
5
|
def initialize(parameters)
|
|
6
|
-
@player_mark = parameters
|
|
7
|
-
@interface = parameters[:interface]
|
|
6
|
+
@player_mark = parameters.fetch(:player_mark)
|
|
8
7
|
end
|
|
9
8
|
|
|
10
|
-
def move
|
|
11
|
-
|
|
9
|
+
def move(game)
|
|
10
|
+
game.interface.solicit_move(@player_mark)
|
|
12
11
|
end
|
|
13
12
|
end
|
|
14
13
|
end
|
data/spec/board_spec.rb
CHANGED
|
@@ -6,28 +6,11 @@ module TicTacToe
|
|
|
6
6
|
include_context "default_values"
|
|
7
7
|
include_context "helper_methods"
|
|
8
8
|
|
|
9
|
-
let(:
|
|
10
|
-
let(:_) { Board.blank_mark }
|
|
9
|
+
let(:_) { Board::BLANK_MARK }
|
|
11
10
|
let(:x) { @default_player_marks.first }
|
|
12
11
|
let(:o) { @default_player_marks.last }
|
|
13
12
|
|
|
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
13
|
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
14
|
context "given the configuration of a board with preexisting marks" do
|
|
32
15
|
let(:config) do
|
|
33
16
|
[ x, _, _,
|
|
@@ -42,16 +25,6 @@ module TicTacToe
|
|
|
42
25
|
expect(board.read_cell(*coordinates)).to eq mark
|
|
43
26
|
end
|
|
44
27
|
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
28
|
end
|
|
56
29
|
|
|
57
30
|
context "when not given a configuration of a board with preexisting marks" do
|
|
@@ -79,14 +52,6 @@ module TicTacToe
|
|
|
79
52
|
|
|
80
53
|
expect(board.read_cell(0, 0)).to eq mark
|
|
81
54
|
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
55
|
end
|
|
91
56
|
|
|
92
57
|
describe "#mark_cell" do
|
|
@@ -94,30 +59,24 @@ module TicTacToe
|
|
|
94
59
|
let(:coordinates) { random_coordinates(board.size) }
|
|
95
60
|
let(:mark) { @default_player_marks.sample }
|
|
96
61
|
|
|
97
|
-
it "
|
|
98
|
-
board
|
|
62
|
+
it "returns a new board with the given coordinates played" do
|
|
63
|
+
board = blank_board(@default_board_size)
|
|
99
64
|
|
|
100
|
-
expect(board.read_cell(*coordinates)).to eq
|
|
65
|
+
expect(board.mark_cell(mark, *coordinates).read_cell(*coordinates)).to eq(mark)
|
|
101
66
|
end
|
|
102
67
|
|
|
103
|
-
it "
|
|
104
|
-
board
|
|
105
|
-
|
|
106
|
-
expect(board.last_move_made).to eq coordinates
|
|
107
|
-
end
|
|
68
|
+
it "does not mutate the board" do
|
|
69
|
+
board = blank_board(@default_board_size)
|
|
108
70
|
|
|
109
|
-
|
|
110
|
-
error_info = [board_error, "Cell coordinates are out of bounds"]
|
|
111
|
-
oob_coordinates = [board.size, board.size]
|
|
71
|
+
board.mark_cell(mark, *coordinates)
|
|
112
72
|
|
|
113
|
-
expect
|
|
73
|
+
expect(board).to be_all_blank
|
|
114
74
|
end
|
|
115
75
|
|
|
116
|
-
it "
|
|
117
|
-
|
|
118
|
-
board.mark_cell(mark, *coordinates)
|
|
76
|
+
it "records the coordinates of the last mark made" do
|
|
77
|
+
returned_board = board.mark_cell(mark, *coordinates)
|
|
119
78
|
|
|
120
|
-
expect
|
|
79
|
+
expect(returned_board.last_move_made).to eq coordinates
|
|
121
80
|
end
|
|
122
81
|
end
|
|
123
82
|
|
|
@@ -178,41 +137,13 @@ module TicTacToe
|
|
|
178
137
|
it "returns the last mark made on the board" do
|
|
179
138
|
board = blank_board(@default_board_size)
|
|
180
139
|
mark = @default_player_marks.sample
|
|
181
|
-
board.mark_cell(mark, *random_coordinates(board.size))
|
|
140
|
+
returned_board = board.mark_cell(mark, *random_coordinates(board.size))
|
|
182
141
|
|
|
183
|
-
expect(
|
|
142
|
+
expect(returned_board.last_mark_made).to eq mark
|
|
184
143
|
end
|
|
185
144
|
end
|
|
186
145
|
end
|
|
187
146
|
|
|
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
147
|
describe "#blank?" do
|
|
217
148
|
it "returns true if cell at given coordinates is blank" do
|
|
218
149
|
board = blank_board(@default_board_size)
|
|
@@ -223,9 +154,9 @@ module TicTacToe
|
|
|
223
154
|
it "returns false if cell at given coordinates is not blank" do
|
|
224
155
|
board = blank_board(@default_board_size)
|
|
225
156
|
coordinates = random_coordinates(board.size)
|
|
226
|
-
board.mark_cell(@default_player_marks.sample, *coordinates)
|
|
157
|
+
returned_board = board.mark_cell(@default_player_marks.sample, *coordinates)
|
|
227
158
|
|
|
228
|
-
expect(
|
|
159
|
+
expect(returned_board.blank?(coordinates)).to be false
|
|
229
160
|
end
|
|
230
161
|
end
|
|
231
162
|
|
|
@@ -233,9 +164,9 @@ module TicTacToe
|
|
|
233
164
|
it "returns true if cell at given coordinates has any player's mark" do
|
|
234
165
|
board = blank_board(@default_board_size)
|
|
235
166
|
coordinates = random_coordinates(board.size)
|
|
236
|
-
board.mark_cell(@default_player_marks.sample, *coordinates)
|
|
167
|
+
returned_board = board.mark_cell(@default_player_marks.sample, *coordinates)
|
|
237
168
|
|
|
238
|
-
expect(
|
|
169
|
+
expect(returned_board.marked?(coordinates)).to be true
|
|
239
170
|
end
|
|
240
171
|
|
|
241
172
|
it "returns false if cell at given coordinates does not have a player's mark" do
|
|
@@ -2,13 +2,14 @@ require "spec_helper"
|
|
|
2
2
|
require "computer_player"
|
|
3
3
|
|
|
4
4
|
require "board"
|
|
5
|
+
require "game"
|
|
5
6
|
|
|
6
7
|
module TicTacToe
|
|
7
8
|
describe ComputerPlayer do
|
|
8
9
|
include_context "default_values"
|
|
9
10
|
include_context "helper_methods"
|
|
10
11
|
|
|
11
|
-
let(:_) { Board
|
|
12
|
+
let(:_) { Board::BLANK_MARK }
|
|
12
13
|
let(:x) { @default_first_player }
|
|
13
14
|
let(:o) { @default_second_player }
|
|
14
15
|
|
|
@@ -41,10 +42,10 @@ module TicTacToe
|
|
|
41
42
|
x, x, o,
|
|
42
43
|
o, _, _
|
|
43
44
|
]
|
|
44
|
-
board = build_board(board_config)
|
|
45
|
-
|
|
45
|
+
board = build_board(board_config).mark_cell(o, *[2, 1])
|
|
46
|
+
game = Game.new(board: board)
|
|
46
47
|
computer_player = x_player(board)
|
|
47
|
-
coordinates = computer_player.move
|
|
48
|
+
coordinates = computer_player.move(game)
|
|
48
49
|
|
|
49
50
|
expect(board.out_of_bounds?(coordinates)).to be false
|
|
50
51
|
expect(board.blank?(coordinates)).to be true
|
|
@@ -53,9 +54,11 @@ module TicTacToe
|
|
|
53
54
|
context "when game board has a center space and it is blank" do
|
|
54
55
|
it "returns the center coordinates" do
|
|
55
56
|
board_size = @default_board_size.odd? ? @default_board_size : 3
|
|
57
|
+
game = Game.new(board: blank_board(board_size))
|
|
58
|
+
|
|
56
59
|
computer_player = x_player(blank_board(board_size))
|
|
57
60
|
|
|
58
|
-
expect(computer_player.move).to eq [board_size / 2, board_size / 2]
|
|
61
|
+
expect(computer_player.move(game)).to eq [board_size / 2, board_size / 2]
|
|
59
62
|
end
|
|
60
63
|
end
|
|
61
64
|
|
|
@@ -75,9 +78,10 @@ module TicTacToe
|
|
|
75
78
|
winning_moves = [[0, 1], [1, 2], [2, 0]]
|
|
76
79
|
board_configs.zip(winning_moves).each do |board_config, winning_move|
|
|
77
80
|
board = build_board(board_config)
|
|
81
|
+
game = Game.new(board: board)
|
|
78
82
|
computer_player = x_player(board)
|
|
79
83
|
|
|
80
|
-
expect(computer_player.move).to eq winning_move
|
|
84
|
+
expect(computer_player.move(game)).to eq winning_move
|
|
81
85
|
end
|
|
82
86
|
end
|
|
83
87
|
end
|
|
@@ -98,9 +102,10 @@ module TicTacToe
|
|
|
98
102
|
winning_moves = [[1, 0], [2, 1], [0, 2]]
|
|
99
103
|
board_configs.zip(winning_moves).each do |board_config, winning_move|
|
|
100
104
|
board = build_board(board_config)
|
|
105
|
+
game = Game.new(board: board)
|
|
101
106
|
computer_player = x_player(board)
|
|
102
107
|
|
|
103
|
-
expect(computer_player.move).to eq winning_move
|
|
108
|
+
expect(computer_player.move(game)).to eq winning_move
|
|
104
109
|
end
|
|
105
110
|
end
|
|
106
111
|
end
|
|
@@ -119,8 +124,9 @@ module TicTacToe
|
|
|
119
124
|
board_configs.zip(winning_moves).each do |board_config, winning_move|
|
|
120
125
|
board = build_board(board_config)
|
|
121
126
|
computer_player = x_player(board)
|
|
127
|
+
game = Game.new(board: board)
|
|
122
128
|
|
|
123
|
-
expect(computer_player.move).to eq winning_move
|
|
129
|
+
expect(computer_player.move(game)).to eq winning_move
|
|
124
130
|
end
|
|
125
131
|
end
|
|
126
132
|
end
|
|
@@ -142,8 +148,9 @@ module TicTacToe
|
|
|
142
148
|
board_configs.zip(winning_moves).each do |board_config, winning_move|
|
|
143
149
|
board = build_board(board_config)
|
|
144
150
|
computer_player = o_player(board)
|
|
151
|
+
game = Game.new(board: board)
|
|
145
152
|
|
|
146
|
-
expect(computer_player.move).to eq winning_move
|
|
153
|
+
expect(computer_player.move(game)).to eq winning_move
|
|
147
154
|
end
|
|
148
155
|
end
|
|
149
156
|
end
|
|
@@ -165,8 +172,9 @@ module TicTacToe
|
|
|
165
172
|
board_configs.zip(winning_moves).each do |board_config, winning_move|
|
|
166
173
|
board = build_board(board_config)
|
|
167
174
|
computer_player = o_player(board)
|
|
175
|
+
game = Game.new(board: board)
|
|
168
176
|
|
|
169
|
-
expect(computer_player.move).to eq winning_move
|
|
177
|
+
expect(computer_player.move(game)).to eq winning_move
|
|
170
178
|
end
|
|
171
179
|
end
|
|
172
180
|
end
|
|
@@ -185,8 +193,9 @@ module TicTacToe
|
|
|
185
193
|
board_configs.zip(winning_moves).each do |board_config, winning_move|
|
|
186
194
|
board = build_board(board_config)
|
|
187
195
|
computer_player = o_player(board)
|
|
196
|
+
game = Game.new(board: board)
|
|
188
197
|
|
|
189
|
-
expect(computer_player.move).to eq winning_move
|
|
198
|
+
expect(computer_player.move(game)).to eq winning_move
|
|
190
199
|
end
|
|
191
200
|
end
|
|
192
201
|
end
|
|
@@ -212,8 +221,9 @@ module TicTacToe
|
|
|
212
221
|
board_configs.zip(good_move_sets).each do |board_config, good_moves|
|
|
213
222
|
board = build_board(board_config)
|
|
214
223
|
computer_player = o_player(board)
|
|
224
|
+
game = Game.new(board: board)
|
|
215
225
|
|
|
216
|
-
expect(good_moves).to include computer_player.move
|
|
226
|
+
expect(good_moves).to include computer_player.move(game)
|
|
217
227
|
end
|
|
218
228
|
end
|
|
219
229
|
end
|
data/spec/game_spec.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "game"
|
|
|
4
4
|
module TicTacToe
|
|
5
5
|
describe Game do
|
|
6
6
|
include_context "default_values"
|
|
7
|
+
include_context "helper_methods"
|
|
7
8
|
|
|
8
9
|
def new_game(parameters)
|
|
9
10
|
Game.new(parameters)
|
|
@@ -83,7 +84,9 @@ module TicTacToe
|
|
|
83
84
|
|
|
84
85
|
describe "#set_up" do
|
|
85
86
|
it "uses information from the interface to create the players" do
|
|
86
|
-
allow(game.interface).to receive(:game_setup_interaction)
|
|
87
|
+
allow(game.interface).to receive(:game_setup_interaction) do
|
|
88
|
+
[AvailablePlayerTypes::HUMAN, AvailablePlayerTypes::COMPUTER]
|
|
89
|
+
end
|
|
87
90
|
game.set_up
|
|
88
91
|
|
|
89
92
|
game.players.each_with_index do |player, index|
|
|
@@ -95,7 +98,9 @@ module TicTacToe
|
|
|
95
98
|
|
|
96
99
|
describe "#handle_turns" do
|
|
97
100
|
it "executes turns until the game is over" do
|
|
98
|
-
allow(game.interface).to receive(:game_setup_interaction)
|
|
101
|
+
allow(game.interface).to receive(:game_setup_interaction) do
|
|
102
|
+
[AvailablePlayerTypes::HUMAN, AvailablePlayerTypes::COMPUTER]
|
|
103
|
+
end
|
|
99
104
|
game.set_up
|
|
100
105
|
allow(game).to receive(:over?).and_return(false, false, false, false, true)
|
|
101
106
|
allow(game).to receive(:handle_one_turn)
|
|
@@ -165,7 +170,6 @@ module TicTacToe
|
|
|
165
170
|
let(:valid_coordinates) { [0, game.board.size - 1] }
|
|
166
171
|
|
|
167
172
|
before do
|
|
168
|
-
game.board.mark_cell(@default_first_player, 0, 0)
|
|
169
173
|
allow(player_stub).to receive(:move).and_return(
|
|
170
174
|
occupied_coordinates,
|
|
171
175
|
out_of_bounds_coordinates,
|
|
@@ -173,12 +177,16 @@ module TicTacToe
|
|
|
173
177
|
end
|
|
174
178
|
|
|
175
179
|
it "complains through the interface" do
|
|
180
|
+
_ = Board::BLANK_MARK
|
|
181
|
+
game = Game.new(board: build_board([0, _, _, _, _, _, _, _, _]))
|
|
176
182
|
expect(game.interface).to receive(:report_invalid_move).exactly(2).times
|
|
177
183
|
|
|
178
184
|
game.get_valid_move(player_stub)
|
|
179
185
|
end
|
|
180
186
|
|
|
181
187
|
it "gets another move from player until it receives valid coordinates" do
|
|
188
|
+
_ = Board::BLANK_MARK
|
|
189
|
+
game = Game.new(board: build_board([0, _, _, _, _, _, _, _, _]))
|
|
182
190
|
allow(game.interface).to receive(:report_invalid_move)
|
|
183
191
|
|
|
184
192
|
expect(game.get_valid_move(player_stub)).to eq valid_coordinates
|
|
@@ -247,7 +255,9 @@ module TicTacToe
|
|
|
247
255
|
|
|
248
256
|
context "in a game with two computer players" do
|
|
249
257
|
before do
|
|
250
|
-
allow(game.interface).to receive(:game_setup_interaction)
|
|
258
|
+
allow(game.interface).to receive(:game_setup_interaction) do
|
|
259
|
+
[AvailablePlayerTypes::COMPUTER, AvailablePlayerTypes::COMPUTER]
|
|
260
|
+
end
|
|
251
261
|
end
|
|
252
262
|
|
|
253
263
|
it "should end the game in a draw" do
|
|
@@ -260,7 +270,9 @@ module TicTacToe
|
|
|
260
270
|
|
|
261
271
|
context "in a game with a human player and a computer player" do
|
|
262
272
|
before do
|
|
263
|
-
allow(game.interface).to receive(:game_setup_interaction)
|
|
273
|
+
allow(game.interface).to receive(:game_setup_interaction) do
|
|
274
|
+
[AvailablePlayerTypes::HUMAN, AvailablePlayerTypes::COMPUTER]
|
|
275
|
+
end
|
|
264
276
|
end
|
|
265
277
|
|
|
266
278
|
it "should end in a win for the computer player against an unskilled human player" do
|
data/spec/human_player_spec.rb
CHANGED
|
@@ -8,15 +8,16 @@ module TicTacToe
|
|
|
8
8
|
let(:interface) { double("CommandLineInterface") }
|
|
9
9
|
let(:human_player) do
|
|
10
10
|
HumanPlayer.new(
|
|
11
|
-
interface: interface,
|
|
11
|
+
# interface: interface,
|
|
12
12
|
player_mark: @default_first_player)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
describe "#move" do
|
|
16
16
|
it "returns the coordinates of the move selected by the player" do
|
|
17
|
+
game = Game.new(interface: interface)
|
|
17
18
|
allow(interface).to receive(:solicit_move).and_return([0, 0])
|
|
18
19
|
|
|
19
|
-
expect(human_player.move).to eq [0, 0]
|
|
20
|
+
expect(human_player.move(game)).to eq [0, 0]
|
|
20
21
|
end
|
|
21
22
|
end
|
|
22
23
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -7,13 +7,16 @@ RSpec.shared_context "default_values" do
|
|
|
7
7
|
@default_first_player = :x
|
|
8
8
|
@default_second_player = :o
|
|
9
9
|
@default_player_marks = [:x, :o]
|
|
10
|
-
@default_player_types = [
|
|
10
|
+
@default_player_types = [
|
|
11
|
+
TicTacToe::AvailablePlayerTypes::HUMAN,
|
|
12
|
+
TicTacToe::AvailablePlayerTypes::COMPUTER
|
|
13
|
+
]
|
|
11
14
|
end
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
RSpec.shared_context "helper_methods" do
|
|
15
18
|
def blank_board_configuration(board_size)
|
|
16
|
-
(0...board_size**2).map { TicTacToe::Board
|
|
19
|
+
(0...board_size**2).map { TicTacToe::Board::BLANK_MARK }
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
def at_least_one_repeated_line?(string)
|
|
@@ -38,25 +41,9 @@ RSpec.shared_context "helper_methods" do
|
|
|
38
41
|
new_board(size: board_size)
|
|
39
42
|
end
|
|
40
43
|
|
|
41
|
-
def board_with_potential_win_loss_or_draw(board_size, player_marks)
|
|
42
|
-
first_mark, second_mark = player_marks
|
|
43
|
-
board = blank_board(board_size)
|
|
44
|
-
(0...board_size - 1).each { |col| board.mark_cell(first_mark, 0, col) }
|
|
45
|
-
(0...board_size - 1).each { |col| board.mark_cell(second_mark, 1, col) }
|
|
46
|
-
(2...board_size).each do |row|
|
|
47
|
-
(0...board_size - 1).each do |col|
|
|
48
|
-
mark = col.even? ? first_mark : second_mark
|
|
49
|
-
board.mark_cell(mark, row, col)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
board
|
|
53
|
-
end
|
|
54
|
-
|
|
55
44
|
def board_with_draw(board_size, player_marks)
|
|
56
45
|
first_mark, second_mark = player_marks
|
|
57
|
-
|
|
58
|
-
(0...board_size).each do |row|
|
|
59
|
-
(0...board_size).each do |col|
|
|
46
|
+
blank_board(board_size).blank_cell_coordinates.reduce(blank_board(board_size)) do |board, (row, col)|
|
|
60
47
|
if row == 0
|
|
61
48
|
mark = col.odd? ? first_mark : second_mark
|
|
62
49
|
board.mark_cell(mark, row, col)
|
|
@@ -64,44 +51,6 @@ RSpec.shared_context "helper_methods" do
|
|
|
64
51
|
mark = col.even? ? first_mark : second_mark
|
|
65
52
|
board.mark_cell(mark, row, col)
|
|
66
53
|
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
board
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def all_wins(board_size, winning_mark)
|
|
73
|
-
winning_boards = horizontal_wins(board_size, winning_mark)
|
|
74
|
-
winning_boards.concat vertical_wins(board_size, winning_mark)
|
|
75
|
-
winning_boards.concat diagonal_wins(board_size, winning_mark)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def horizontal_wins(board_size, winning_mark)
|
|
79
|
-
(0...board_size).each_with_object([]) do |row, winning_boards|
|
|
80
|
-
board = blank_board(board_size)
|
|
81
|
-
(0...board_size).each { |col| board.mark_cell(winning_mark, row, col) }
|
|
82
|
-
winning_boards << board
|
|
83
54
|
end
|
|
84
55
|
end
|
|
85
|
-
|
|
86
|
-
def vertical_wins(board_size, winning_mark)
|
|
87
|
-
(0...board_size).each_with_object([]) do |col, winning_boards|
|
|
88
|
-
board = blank_board(board_size)
|
|
89
|
-
(0...board_size).each { |row| board.mark_cell(winning_mark, row, col) }
|
|
90
|
-
winning_boards << board
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def diagonal_wins(board_size, winning_mark)
|
|
95
|
-
ldiag_board = blank_board(board_size)
|
|
96
|
-
(0...board_size).each do |index|
|
|
97
|
-
ldiag_board.mark_cell(winning_mark, index, index)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
rdiag_board = blank_board(board_size)
|
|
101
|
-
(0...board_size).each do |index|
|
|
102
|
-
rdiag_board.mark_cell(winning_mark, index, rdiag_board.size - index - 1)
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
[ldiag_board, rdiag_board]
|
|
106
|
-
end
|
|
107
56
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tactical_tic_tac_toe
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Batman
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2015-10-
|
|
11
|
+
date: 2015-10-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Command line tic tac toe! Run with command 'ttt'
|
|
14
14
|
email: no@no.no
|
|
@@ -25,7 +25,6 @@ files:
|
|
|
25
25
|
- lib/game.rb
|
|
26
26
|
- lib/human_player.rb
|
|
27
27
|
- lib/negamax.rb
|
|
28
|
-
- lib/old_computer_player.rb
|
|
29
28
|
- lib/player_factory.rb
|
|
30
29
|
- lib/tactical_tic_tac_toe.rb
|
|
31
30
|
- spec/board_spec.rb
|
|
@@ -34,7 +33,6 @@ files:
|
|
|
34
33
|
- spec/game_spec.rb
|
|
35
34
|
- spec/human_player_spec.rb
|
|
36
35
|
- spec/negamax_spec.rb
|
|
37
|
-
- spec/old_computer_player_spec.rb
|
|
38
36
|
- spec/player_factory_spec.rb
|
|
39
37
|
- spec/spec_helper.rb
|
|
40
38
|
homepage: http://rubygems.org/gems/tactical_tic_tac_toe
|
data/lib/old_computer_player.rb
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
module TicTacToe
|
|
2
|
-
class OldComputerPlayer
|
|
3
|
-
attr_reader :player_mark
|
|
4
|
-
|
|
5
|
-
def initialize(parameters)
|
|
6
|
-
@player_mark = parameters[:player_mark]
|
|
7
|
-
@opponent_mark = parameters[:opponent_mark]
|
|
8
|
-
@board = parameters[:board]
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def move
|
|
12
|
-
center_coordinate = [@board.size / 2, @board.size / 2]
|
|
13
|
-
if !@board.marked?(center_coordinate) && @board.size.odd?
|
|
14
|
-
center_coordinate
|
|
15
|
-
else
|
|
16
|
-
select_best_move
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
def select_best_move
|
|
23
|
-
best_score_so_far = {
|
|
24
|
-
player: -Float::INFINITY, # highest score is best for player
|
|
25
|
-
opponent: Float::INFINITY # lowest score is best for opponent
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
successor_boards = generate_possible_successor_boards(@board, @player_mark)
|
|
29
|
-
best_board = successor_boards.max_by { |board| minimax(board, false, best_score_so_far) }
|
|
30
|
-
best_board.last_move_made
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def generate_possible_successor_boards(board, mark)
|
|
34
|
-
board.blank_cell_coordinates.map do |coordinates|
|
|
35
|
-
successor_board = board.deep_copy
|
|
36
|
-
successor_board.mark_cell(mark, *coordinates)
|
|
37
|
-
successor_board
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def minimax(board, my_turn, best_score_so_far)
|
|
42
|
-
if board.has_winning_line? || board.all_marked?
|
|
43
|
-
evaluate(board)
|
|
44
|
-
else
|
|
45
|
-
select_score_of_best_successor_board(board, my_turn, best_score_so_far.dup)
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def evaluate(board)
|
|
50
|
-
board.lines.each do |line|
|
|
51
|
-
return Float::INFINITY if line.all? { |cell| cell == @player_mark }
|
|
52
|
-
return -Float::INFINITY if line.all? { |cell| cell == @opponent_mark }
|
|
53
|
-
end
|
|
54
|
-
0
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def select_score_of_best_successor_board(board, my_turn, best_score_so_far)
|
|
58
|
-
current_player_mark = my_turn ? @player_mark : @opponent_mark
|
|
59
|
-
|
|
60
|
-
successor_boards = generate_possible_successor_boards(board, current_player_mark)
|
|
61
|
-
scores = score_successor_boards(successor_boards, my_turn, best_score_so_far)
|
|
62
|
-
|
|
63
|
-
my_turn ? scores.max : scores.min
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def score_successor_boards(successor_boards, my_turn, best_score_so_far)
|
|
67
|
-
successor_boards.each_with_object([]) do |board, scores|
|
|
68
|
-
score = minimax(board, !my_turn, best_score_so_far.dup)
|
|
69
|
-
scores << score
|
|
70
|
-
|
|
71
|
-
update_best_score_so_far(my_turn, best_score_so_far, score)
|
|
72
|
-
return scores if best_score_guaranteed_elsewhere?(best_score_so_far)
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def update_best_score_so_far(my_turn, best_score_so_far, score)
|
|
77
|
-
if score > best_score_so_far[:player] && my_turn
|
|
78
|
-
best_score_so_far[:player] = score
|
|
79
|
-
end
|
|
80
|
-
if score < best_score_so_far[:opponent] && !my_turn
|
|
81
|
-
best_score_so_far[:opponent] = score
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def best_score_guaranteed_elsewhere?(best_score_so_far)
|
|
86
|
-
best_score_so_far[:player] >= best_score_so_far[:opponent]
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
require "spec_helper"
|
|
2
|
-
require "old_computer_player"
|
|
3
|
-
|
|
4
|
-
require "board"
|
|
5
|
-
|
|
6
|
-
module TicTacToe
|
|
7
|
-
describe OldComputerPlayer do
|
|
8
|
-
include_context "default_values"
|
|
9
|
-
include_context "helper_methods"
|
|
10
|
-
|
|
11
|
-
let(:ai) do
|
|
12
|
-
described_class.new(
|
|
13
|
-
board: blank_board(@default_board_size),
|
|
14
|
-
player_mark: @default_first_player,
|
|
15
|
-
opponent_mark: @default_second_player)
|
|
16
|
-
end
|
|
17
|
-
let(:infinity) { Float::INFINITY }
|
|
18
|
-
let(:neg_infinity) { -Float::INFINITY }
|
|
19
|
-
|
|
20
|
-
describe "#initialize" do
|
|
21
|
-
it "takes a parameters hash with the player's mark, opponent's mark, and the game board" do
|
|
22
|
-
params = {
|
|
23
|
-
board: blank_board(@default_board_size),
|
|
24
|
-
player_mark: @default_first_player,
|
|
25
|
-
opponent_mark: @default_second_player
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
expect { described_class.new(params) }.not_to raise_error
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
describe "#move" do
|
|
33
|
-
context "when a center space is available" do
|
|
34
|
-
it "returns the move coordinate for centermost space" do
|
|
35
|
-
[3, 5, 7].each do |odd_size|
|
|
36
|
-
custom_ai = described_class.new(
|
|
37
|
-
board: blank_board(odd_size),
|
|
38
|
-
player_mark: @default_first_player,
|
|
39
|
-
opponent_mark: @default_second_player)
|
|
40
|
-
|
|
41
|
-
expect(custom_ai.move).to eq [odd_size / 2, odd_size / 2]
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
context "when a center space is not available" do
|
|
47
|
-
let(:_) { Board.blank_mark }
|
|
48
|
-
let(:x) { @default_first_player }
|
|
49
|
-
let(:o) { @default_second_player }
|
|
50
|
-
|
|
51
|
-
def x_player(board)
|
|
52
|
-
parameters = {
|
|
53
|
-
board: board,
|
|
54
|
-
player_mark: x,
|
|
55
|
-
opponent_mark: o
|
|
56
|
-
}
|
|
57
|
-
OldComputerPlayer.new(parameters)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def o_player(board)
|
|
61
|
-
parameters = {
|
|
62
|
-
board: board,
|
|
63
|
-
player_mark: o,
|
|
64
|
-
opponent_mark: x
|
|
65
|
-
}
|
|
66
|
-
OldComputerPlayer.new(parameters)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
context "when computer player can make a horizontal winning move" do
|
|
70
|
-
it "returns the coordinates of the winning move" do
|
|
71
|
-
board_configs = [
|
|
72
|
-
[ x, _, x,
|
|
73
|
-
_, o, _,
|
|
74
|
-
o, x, o ],
|
|
75
|
-
[ o, _, _,
|
|
76
|
-
x, x, _,
|
|
77
|
-
o, o, x ],
|
|
78
|
-
[ _, o, _,
|
|
79
|
-
x, o, o,
|
|
80
|
-
_, x, x ]
|
|
81
|
-
]
|
|
82
|
-
winning_moves = [[0, 1], [1, 2], [2, 0]]
|
|
83
|
-
board_configs.zip(winning_moves).each do |board_config, winning_move|
|
|
84
|
-
board = build_board(board_config)
|
|
85
|
-
computer_player = x_player(board)
|
|
86
|
-
|
|
87
|
-
expect(computer_player.move).to eq winning_move
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
context "when computer player can make a vertical winning move" do
|
|
93
|
-
it "returns the coordinates of the winning move" do
|
|
94
|
-
board_configs = [
|
|
95
|
-
[ x, _, o,
|
|
96
|
-
_, o, x,
|
|
97
|
-
x, _, o ],
|
|
98
|
-
[ o, x, o,
|
|
99
|
-
o, x, _,
|
|
100
|
-
x, _, _ ],
|
|
101
|
-
[ _, o, _,
|
|
102
|
-
_, o, x,
|
|
103
|
-
o, x, x ]
|
|
104
|
-
]
|
|
105
|
-
winning_moves = [[1, 0], [2, 1], [0, 2]]
|
|
106
|
-
board_configs.zip(winning_moves).each do |board_config, winning_move|
|
|
107
|
-
board = build_board(board_config)
|
|
108
|
-
computer_player = x_player(board)
|
|
109
|
-
|
|
110
|
-
expect(computer_player.move).to eq winning_move
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
context "when computer player can make a diagonal winning move" do
|
|
116
|
-
it "returns the coordinates of the winning move" do
|
|
117
|
-
board_configs = [
|
|
118
|
-
[ x, _, o,
|
|
119
|
-
o, x, _,
|
|
120
|
-
x, o, _ ],
|
|
121
|
-
[ o, x, _,
|
|
122
|
-
o, x, _,
|
|
123
|
-
x, o, _ ]
|
|
124
|
-
]
|
|
125
|
-
winning_moves = [[2, 2], [0, 2]]
|
|
126
|
-
board_configs.zip(winning_moves).each do |board_config, winning_move|
|
|
127
|
-
board = build_board(board_config)
|
|
128
|
-
computer_player = x_player(board)
|
|
129
|
-
|
|
130
|
-
expect(computer_player.move).to eq winning_move
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
context "when opponent can make a horizontal winning move next turn" do
|
|
136
|
-
it "returns the coordinates of the move that blocks the opponent from winning" do
|
|
137
|
-
board_configs = [
|
|
138
|
-
[ x, _, x,
|
|
139
|
-
_, o, _,
|
|
140
|
-
o, x, o ],
|
|
141
|
-
[ o, _, _,
|
|
142
|
-
x, x, _,
|
|
143
|
-
o, o, x ],
|
|
144
|
-
[ _, o, _,
|
|
145
|
-
x, o, o,
|
|
146
|
-
_, x, x ]
|
|
147
|
-
]
|
|
148
|
-
winning_moves = [[0, 1], [1, 2], [2, 0]]
|
|
149
|
-
board_configs.zip(winning_moves).each do |board_config, winning_move|
|
|
150
|
-
board = build_board(board_config)
|
|
151
|
-
computer_player = o_player(board)
|
|
152
|
-
|
|
153
|
-
expect(computer_player.move).to eq winning_move
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
context "when opponent can make a vertical winning move next turn" do
|
|
159
|
-
it "returns the coordinates of the move that blocks the opponent from winning" do
|
|
160
|
-
board_configs = [
|
|
161
|
-
[ x, _, o,
|
|
162
|
-
_, o, x,
|
|
163
|
-
x, _, o ],
|
|
164
|
-
[ o, x, o,
|
|
165
|
-
o, x, _,
|
|
166
|
-
x, _, _ ],
|
|
167
|
-
[ _, o, _,
|
|
168
|
-
_, o, x,
|
|
169
|
-
o, x, x ]
|
|
170
|
-
]
|
|
171
|
-
winning_moves = [[1, 0], [2, 1], [0, 2]]
|
|
172
|
-
board_configs.zip(winning_moves).each do |board_config, winning_move|
|
|
173
|
-
board = build_board(board_config)
|
|
174
|
-
computer_player = o_player(board)
|
|
175
|
-
|
|
176
|
-
expect(computer_player.move).to eq winning_move
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
context "when opponent can make a diagonal winning move next turn" do
|
|
182
|
-
it "returns the coordinates of the move that blocks the opponent from winning" do
|
|
183
|
-
board_configs = [
|
|
184
|
-
[ x, _, o,
|
|
185
|
-
o, x, _,
|
|
186
|
-
x, o, _ ],
|
|
187
|
-
[ o, x, _,
|
|
188
|
-
o, x, _,
|
|
189
|
-
x, o, _ ]
|
|
190
|
-
]
|
|
191
|
-
winning_moves = [[2, 2], [0, 2]]
|
|
192
|
-
board_configs.zip(winning_moves).each do |board_config, winning_move|
|
|
193
|
-
board = build_board(board_config)
|
|
194
|
-
computer_player = o_player(board)
|
|
195
|
-
|
|
196
|
-
expect(computer_player.move).to eq winning_move
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
context "when opponent can make a fork next turn" do
|
|
202
|
-
it "returns the coordinates of a move that prevents that fork" do
|
|
203
|
-
board_configs = [
|
|
204
|
-
[ x, _, _,
|
|
205
|
-
_, x, _,
|
|
206
|
-
_, _, o ],
|
|
207
|
-
[ x, _, _,
|
|
208
|
-
_, o, _,
|
|
209
|
-
_, _, x ],
|
|
210
|
-
[ _, x, _,
|
|
211
|
-
_, x, _,
|
|
212
|
-
_, o, _ ]
|
|
213
|
-
]
|
|
214
|
-
good_move_sets = [
|
|
215
|
-
[[2, 0], [0, 2]],
|
|
216
|
-
[[0, 1], [1, 0], [1, 2], [2, 1]],
|
|
217
|
-
[[0, 0], [0, 2], [2, 0], [2, 2]]
|
|
218
|
-
]
|
|
219
|
-
board_configs.zip(good_move_sets).each do |board_config, good_moves|
|
|
220
|
-
board = build_board(board_config)
|
|
221
|
-
computer_player = o_player(board)
|
|
222
|
-
|
|
223
|
-
expect(good_moves).to include computer_player.move
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
|
-
end
|
|
228
|
-
end
|
|
229
|
-
end
|
|
230
|
-
end
|