erics_tic_tac_toe 0.1.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|