erics_tic_tac_toe 0.1.0 → 0.5.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.
- data/.gitignore +1 -0
- data/README.markdown +6 -7
- data/Rakefile +2 -1
- data/bin/tic_tac_toe +41 -2
- data/lib/tic_tac_toe.rb +5 -4
- data/lib/tic_tac_toe/board.rb +153 -0
- data/lib/tic_tac_toe/game.rb +70 -0
- data/lib/tic_tac_toe/game_types/terminal_game.rb +86 -0
- data/lib/tic_tac_toe/player.rb +13 -0
- data/lib/tic_tac_toe/players/computer_player.rb +26 -0
- data/lib/tic_tac_toe/players/human_player.rb +28 -0
- data/lib/tic_tac_toe/presentors/game_presenter.rb +16 -0
- data/lib/tic_tac_toe/presentors/player_presenter.rb +13 -0
- data/lib/tic_tac_toe/strategies/minimax_strategy.rb +91 -0
- data/lib/tic_tac_toe/strategies/three_by_three_strategy.rb +260 -0
- data/lib/{tic-tac-toe → tic_tac_toe}/version.rb +1 -1
- data/test/board_test.rb +40 -52
- data/test/game_presentor_test.rb +19 -0
- data/test/game_test.rb +79 -0
- data/test/player_presenter_test.rb +12 -0
- data/test/player_test.rb +57 -0
- data/test/potential_state_test.rb +53 -0
- data/test/solver_test.rb +169 -93
- data/test/terminal_game_test.rb +78 -0
- data/test/test_helper.rb +4 -0
- data/tic-tac-toe.gemspec +1 -1
- metadata +25 -11
- data/lib/tic-tac-toe/board.rb +0 -148
- data/lib/tic-tac-toe/game.rb +0 -62
- data/lib/tic-tac-toe/game_types/terminal_game.rb +0 -63
- data/lib/tic-tac-toe/solver.rb +0 -16
- data/lib/tic-tac-toe/strategies/threebythree_implementations/brute_force_implementation.rb +0 -197
- data/lib/tic-tac-toe/strategies/threebythree_stategy.rb +0 -30
- data/test/brute_force_implementation_test.rb +0 -25
@@ -1,197 +0,0 @@
|
|
1
|
-
# Brute Force Implementation for the Three by Three Strategy
|
2
|
-
# This implementation uses loops to try a change all of the board values and
|
3
|
-
# checking the result
|
4
|
-
#
|
5
|
-
# For example, win! is implemented by trying every cell, and checking if
|
6
|
-
# it was a winning solution
|
7
|
-
module TicTacToe
|
8
|
-
class BruteForceImplementation
|
9
|
-
|
10
|
-
def initialize(board, letter)
|
11
|
-
@board, @letter = board, letter
|
12
|
-
end
|
13
|
-
|
14
|
-
# Try placing letter at every available position
|
15
|
-
# If the board is solved, do that
|
16
|
-
def win!
|
17
|
-
each_position do |x, y|
|
18
|
-
temp_board = @board.clone
|
19
|
-
temp_board.play_at(x, y, @letter)
|
20
|
-
if temp_board.solved?
|
21
|
-
@board.play_at(x, y, @letter)
|
22
|
-
return true
|
23
|
-
end
|
24
|
-
end
|
25
|
-
false
|
26
|
-
end
|
27
|
-
|
28
|
-
# Try placing the opponent's letter at every available position
|
29
|
-
# If the board is solved, block them at that position
|
30
|
-
def block!(board = @board, letter = @letter)
|
31
|
-
each_position do |x, y|
|
32
|
-
temp_board = board.clone
|
33
|
-
temp_board.play_at(x, y, other_player(letter))
|
34
|
-
if temp_board.solved?
|
35
|
-
board.play_at(x, y, letter)
|
36
|
-
return true
|
37
|
-
end
|
38
|
-
end
|
39
|
-
false
|
40
|
-
end
|
41
|
-
|
42
|
-
# Try placing the letter at every position.
|
43
|
-
# If there are now two winning solutions for next turn, go there
|
44
|
-
def fork!
|
45
|
-
each_forking_position do |x, y|
|
46
|
-
@board.play_at(x, y, @letter)
|
47
|
-
return true
|
48
|
-
end
|
49
|
-
false
|
50
|
-
end
|
51
|
-
|
52
|
-
# Try placing the opponent's letter at every position.
|
53
|
-
# If there are now two winning solutions for next turn, block them there
|
54
|
-
def block_fork!(board = @board)
|
55
|
-
each_forking_position(other_player) do |x, y|
|
56
|
-
temp_board = board.clone
|
57
|
-
temp_board.play_at(x,y,@letter)
|
58
|
-
# Search for the elusive double fork
|
59
|
-
each_forking_position(other_player, temp_board) do |x, y|
|
60
|
-
return force_a_block
|
61
|
-
end
|
62
|
-
board.play_at(x, y, @letter)
|
63
|
-
return true
|
64
|
-
end
|
65
|
-
false
|
66
|
-
end
|
67
|
-
|
68
|
-
def center!
|
69
|
-
return false if @board.center_cell
|
70
|
-
|
71
|
-
@board.center_cell = @letter
|
72
|
-
true
|
73
|
-
end
|
74
|
-
|
75
|
-
# Cycle through all of the corners looking for the opponent's letter
|
76
|
-
# If one is found, place letter at the opposite corner
|
77
|
-
def oposite_corner!
|
78
|
-
@board.corners.each_with_index do |corner, index|
|
79
|
-
if corner == other_player
|
80
|
-
case index
|
81
|
-
when 0 # Top Left
|
82
|
-
next if @board.get_cell(Board::SIZE-1, Board::SIZE-1)
|
83
|
-
@board.play_at(Board::SIZE-1, Board::SIZE-1, @letter)
|
84
|
-
return true
|
85
|
-
when 1 # Top Right
|
86
|
-
next if @board.get_cell(0, Board::SIZE-1)
|
87
|
-
@board.play_at(0, Board::SIZE-1, @letter)
|
88
|
-
return true
|
89
|
-
when 2 # Bottom Right
|
90
|
-
next if @board.get_cell(0, 0)
|
91
|
-
@board.play_at(0, 0, @letter)
|
92
|
-
return true
|
93
|
-
when 3 # Bottom Left
|
94
|
-
next if @board.get_cell(Board::SIZE-1, 0)
|
95
|
-
@board.play_at(Board::SIZE-1, 0, @letter)
|
96
|
-
return true
|
97
|
-
else
|
98
|
-
raise Exception.new("Board#corners returned more than 4")
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
false
|
103
|
-
end
|
104
|
-
|
105
|
-
# Cycle though all of the corners, until one is found that is empty
|
106
|
-
def empty_corner!
|
107
|
-
@board.corners.each_with_index do |corner, index|
|
108
|
-
unless corner
|
109
|
-
case index
|
110
|
-
when 0 # Top Left
|
111
|
-
@board.play_at(0, 0, @letter)
|
112
|
-
return true
|
113
|
-
when 1 # Top Right
|
114
|
-
@board.play_at(Board::SIZE-1, 0, @letter)
|
115
|
-
return true
|
116
|
-
when 2 # Bottom Right
|
117
|
-
@board.play_at(Board::SIZE-1, Board::SIZE-1, @letter)
|
118
|
-
return true
|
119
|
-
when 3 # Bottom Left
|
120
|
-
@board.play_at(0, Board::SIZE-1, @letter)
|
121
|
-
return true
|
122
|
-
else
|
123
|
-
raise Exception.new("Board#corners returned more than 4")
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
false
|
128
|
-
end
|
129
|
-
|
130
|
-
# Place letter at a random empty cell, at this point it should only be sides left
|
131
|
-
def empty_side!
|
132
|
-
@board.any_empty_position do |x, y|
|
133
|
-
@board.play_at(x, y, @letter)
|
134
|
-
return true
|
135
|
-
end
|
136
|
-
false
|
137
|
-
end
|
138
|
-
|
139
|
-
private
|
140
|
-
|
141
|
-
def other_player(letter = @letter)
|
142
|
-
letter == X ? O : X
|
143
|
-
end
|
144
|
-
|
145
|
-
def fork_exsits?(x, y, letter = @letter, board = @board)
|
146
|
-
(count = can_win_next_turn?(x, y, letter, board)) && count >= 2
|
147
|
-
end
|
148
|
-
|
149
|
-
def each_position(&block)
|
150
|
-
Board::SIZE.times do |y|
|
151
|
-
Board::SIZE.times do |x|
|
152
|
-
yield(x, y)
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
def each_forking_position(letter = @letter, board = @board, &block)
|
158
|
-
each_position do |x, y|
|
159
|
-
if fork_exsits?(x, y, letter, board)
|
160
|
-
yield(x, y)
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
def can_win_next_turn?(x, y, letter = @letter, board = @board)
|
166
|
-
count = 0
|
167
|
-
temp_board = board.clone
|
168
|
-
temp_board.play_at(x,y,letter)
|
169
|
-
each_position do |x, y|
|
170
|
-
inner_loop_board = temp_board.clone
|
171
|
-
inner_loop_board.play_at(x, y, letter)
|
172
|
-
count += 1 if inner_loop_board.solved?
|
173
|
-
end
|
174
|
-
return count == 0 ? false : count
|
175
|
-
end
|
176
|
-
|
177
|
-
def force_a_block
|
178
|
-
# Force them to block without creating another fork
|
179
|
-
each_position do |x, y|
|
180
|
-
if can_win_next_turn?(x, y)
|
181
|
-
temp_board = @board.clone
|
182
|
-
temp_board.play_at(x, y, @letter)
|
183
|
-
raise Exception.new("Couldn't force a block") unless block!(temp_board, other_player)
|
184
|
-
# Did I just create another fork with that block?
|
185
|
-
if fork_exsits?(x, y, other_player, temp_board)
|
186
|
-
next
|
187
|
-
else
|
188
|
-
@board.play_at(x, y, @letter)
|
189
|
-
return true
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
raise Exception.new("No position found to block the fork")
|
194
|
-
end
|
195
|
-
|
196
|
-
end
|
197
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
require_relative 'threebythree_implementations/brute_force_implementation'
|
2
|
-
|
3
|
-
module TicTacToe
|
4
|
-
class ThreebythreeStrategy
|
5
|
-
def initialize(board, letter, implementation=BruteForceImplementation)
|
6
|
-
@implementation = implementation.new(board, letter)
|
7
|
-
end
|
8
|
-
|
9
|
-
# The strategy is from the Wikipedia article on Tic-Tac-Toe
|
10
|
-
# 1) Try to win
|
11
|
-
# 2) Try to block if they're about to win
|
12
|
-
# 3) Try to fork so you'll win next turn
|
13
|
-
# 4) Try to block their fork so they will not win next turn
|
14
|
-
# 5) Take the center if its not already taken
|
15
|
-
# 6) Play the opposite corner of your opponent
|
16
|
-
# 7) Play in an empty corner
|
17
|
-
# 8) Play in an empty side
|
18
|
-
def solve!
|
19
|
-
return if @implementation.win!
|
20
|
-
return if @implementation.block!
|
21
|
-
return if @implementation.fork!
|
22
|
-
return if @implementation.block_fork!
|
23
|
-
return if @implementation.center!
|
24
|
-
return if @implementation.oposite_corner!
|
25
|
-
return if @implementation.empty_corner!
|
26
|
-
return if @implementation.empty_side!
|
27
|
-
raise Exception.new("No possible moves to play!")
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class BruteForceImplementationTest < MiniTest::Unit::TestCase
|
4
|
-
def setup
|
5
|
-
TicTacToe::BruteForceImplementation.instance_eval { attr_accessor :board }
|
6
|
-
@implementation = TicTacToe::BruteForceImplementation.new(TicTacToe::Board.new, 'x')
|
7
|
-
end
|
8
|
-
|
9
|
-
def test_can_win_next_turn
|
10
|
-
# Test private method that it returns the correct amount of winning moves
|
11
|
-
|
12
|
-
set_grid([[ 'x', nil, nil], [nil, nil, nil], [nil, nil, nil] ])
|
13
|
-
assert_equal 1, @implementation.send(:can_win_next_turn?, 0, 1)
|
14
|
-
|
15
|
-
# 1 | 2 | x
|
16
|
-
# 4 | o | 6
|
17
|
-
# 7 | x | 9
|
18
|
-
set_grid([[ nil, nil, 'x'], [nil, 'o', nil], [nil, 'x', nil] ])
|
19
|
-
assert_equal 2, @implementation.send(:can_win_next_turn?, 2, 2)
|
20
|
-
end
|
21
|
-
|
22
|
-
def set_grid(grid)
|
23
|
-
@implementation.board.instance_variable_set("@grid", grid)
|
24
|
-
end
|
25
|
-
end
|