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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 01bb765823d823d69d962bdcf3cd43e8bf7fc48f
4
- data.tar.gz: d2215f338e0b37e2fe3e6fc2d7b17fb4c731e3c1
3
+ metadata.gz: 91f9d2072259b9710988edde50d53c39cc3962e7
4
+ data.tar.gz: fb68237c3ad7abf7c4259fef3bffc81d45701629
5
5
  SHA512:
6
- metadata.gz: f44cc813fba53dec3622cb8051a8551578e7ff941c12d11539e4d48b38eb118641abb19c8b2439aa8e26b6b8b09cd41c30513979363eb06417c8684d32be6772
7
- data.tar.gz: bda0c00122f5e46f4234725dd4045f3541d757b30de32ec6f159c496bf1bb04fe7eb8927153bed5a42fd49204461fdc293d1ebfbd69ab486b879e45fe4a65396
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
- config = parameters[:config] || blank_board_configuration
17
- @cells = map_configuration_to_cells(config)
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
- fail BoardError, "Cell coordinates are out of bounds" if out_of_bounds?([row, col])
22
-
23
- @cells[row][col]
14
+ @cells[row * @size + col]
24
15
  end
25
16
 
26
17
  def mark_cell(mark, row, col)
27
- fail BoardError, "Cell coordinates are out of bounds" if out_of_bounds?([row, col])
28
- fail BoardError, "Cannot alter a marked cell" if marked?([row, col])
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
- (0...@size**2).map { BLANK_MARK }
72
+ Array.new(@size**2) { BLANK_MARK }
100
73
  end
101
74
 
102
75
  def row_at(row)
@@ -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.deep_copy,
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[:player_mark]
7
- @interface = parameters[:interface]
6
+ @player_mark = parameters.fetch(:player_mark)
8
7
  end
9
8
 
10
- def move
11
- @interface.solicit_move(@player_mark)
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(:board_error) { Board::BoardError }
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 "sets the contents of an empty cell at the given row and column" do
98
- board.mark_cell(mark, *coordinates)
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 mark
65
+ expect(board.mark_cell(mark, *coordinates).read_cell(*coordinates)).to eq(mark)
101
66
  end
102
67
 
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
68
+ it "does not mutate the board" do
69
+ board = blank_board(@default_board_size)
108
70
 
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]
71
+ board.mark_cell(mark, *coordinates)
112
72
 
113
- expect { board.mark_cell(mark, *oob_coordinates)}.to raise_error(*error_info)
73
+ expect(board).to be_all_blank
114
74
  end
115
75
 
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)
76
+ it "records the coordinates of the last mark made" do
77
+ returned_board = board.mark_cell(mark, *coordinates)
119
78
 
120
- expect { board.mark_cell(mark, *coordinates) }.to raise_error(*error_info)
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(board.last_mark_made).to eq mark
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(board.blank?(coordinates)).to be false
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(board.marked?(coordinates)).to be true
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.blank_mark }
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
- board.mark_cell(o, *[2, 1])
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).and_return([:human, :computer])
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).and_return([:human, :computer])
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).and_return([:computer, :computer])
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).and_return([:human, :computer])
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
@@ -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 = [:human, :computer]
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.blank_mark }
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
- board = blank_board(board_size)
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.1.1
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-01 00:00:00.000000000 Z
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
@@ -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