tactical_tic_tac_toe 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,230 @@
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
@@ -0,0 +1,49 @@
1
+ require "spec_helper"
2
+ require "player_factory"
3
+
4
+ require "game"
5
+
6
+ module TicTacToe
7
+ describe PlayerFactory do
8
+ include_context "default_values"
9
+
10
+ describe ".build" do
11
+ let(:player_types) { [AvailablePlayerTypes::HUMAN, AvailablePlayerTypes::COMPUTER] }
12
+ let(:player_classes) { [HumanPlayer, ComputerPlayer] }
13
+ let(:player_config) do
14
+ {
15
+ game: Game.new({}),
16
+ player_mark: @default_player_marks.first
17
+ }
18
+ end
19
+
20
+ def build_player(config)
21
+ PlayerFactory.build(config)
22
+ end
23
+
24
+ it "creates a player of the given type" do
25
+ player_types.zip(player_classes).each do |type, player_class|
26
+ player_config[:type] = type
27
+
28
+ expect(build_player(player_config)).to be_a player_class
29
+ end
30
+ end
31
+
32
+ it "creates players that respond to #move" do
33
+ player_types.each do |type|
34
+ player_config[:type] = type
35
+
36
+ expect(build_player(player_config)).to respond_to :move
37
+ end
38
+ end
39
+
40
+ it "creates players that use the given mark" do
41
+ player_types.each do |type|
42
+ player_config[:type] = type
43
+
44
+ expect(build_player(player_config).player_mark).to eq player_config[:player_mark]
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,107 @@
1
+ require "rspec"
2
+ require "rspec/collection_matchers"
3
+
4
+ RSpec.shared_context "default_values" do
5
+ before :all do
6
+ @default_board_size = 3
7
+ @default_first_player = :x
8
+ @default_second_player = :o
9
+ @default_player_marks = [:x, :o]
10
+ @default_player_types = [:human, :computer]
11
+ end
12
+ end
13
+
14
+ RSpec.shared_context "helper_methods" do
15
+ def blank_board_configuration(board_size)
16
+ (0...board_size**2).map { TicTacToe::Board.blank_mark }
17
+ end
18
+
19
+ def at_least_one_repeated_line?(string)
20
+ lines = string.split("\n")
21
+ lines.uniq.length < lines.length
22
+ end
23
+
24
+ def random_coordinates(board_size)
25
+ [rand(board_size), rand(board_size)]
26
+ end
27
+
28
+ def new_board(parameters)
29
+ TicTacToe::Board.new(parameters)
30
+ end
31
+
32
+ def build_board(config)
33
+ size = Math.sqrt(config.size).to_i
34
+ new_board(size: size, config: config)
35
+ end
36
+
37
+ def blank_board(board_size)
38
+ new_board(size: board_size)
39
+ end
40
+
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
+ def board_with_draw(board_size, player_marks)
56
+ 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|
60
+ if row == 0
61
+ mark = col.odd? ? first_mark : second_mark
62
+ board.mark_cell(mark, row, col)
63
+ else
64
+ mark = col.even? ? first_mark : second_mark
65
+ board.mark_cell(mark, row, col)
66
+ 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
+ end
84
+ 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
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tactical_tic_tac_toe
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Batman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-01 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Command line tic tac toe! Run with command 'ttt'
14
+ email: no@no.no
15
+ executables:
16
+ - ttt
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - bin/ttt
21
+ - lib/available_player_types.rb
22
+ - lib/board.rb
23
+ - lib/command_line_interface.rb
24
+ - lib/computer_player.rb
25
+ - lib/game.rb
26
+ - lib/human_player.rb
27
+ - lib/negamax.rb
28
+ - lib/old_computer_player.rb
29
+ - lib/player_factory.rb
30
+ - lib/tactical_tic_tac_toe.rb
31
+ - spec/board_spec.rb
32
+ - spec/command_line_interface_spec.rb
33
+ - spec/computer_player_spec.rb
34
+ - spec/game_spec.rb
35
+ - spec/human_player_spec.rb
36
+ - spec/negamax_spec.rb
37
+ - spec/old_computer_player_spec.rb
38
+ - spec/player_factory_spec.rb
39
+ - spec/spec_helper.rb
40
+ homepage: http://rubygems.org/gems/tactical_tic_tac_toe
41
+ licenses:
42
+ - MIT
43
+ metadata: {}
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 2.4.6
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Command line tic tac toe.
64
+ test_files: []