erics_tic_tac_toe 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ pkg/
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2012 Eric Koslow
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,15 @@
1
+ Tic Tac Toe
2
+ ====
3
+
4
+ How to play
5
+ ----
6
+
7
+ irb -Ilib
8
+ require 'game'
9
+ Game.new.run
10
+
11
+ Whats coming
12
+ ----
13
+
14
+ Gem and Bin executable for playing games easily
15
+ Play games on boards bigger than 3x3
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'rake'
2
+ require 'bundler'
3
+ require 'rake/testtask'
4
+
5
+ Bundler::GemHelper.install_tasks
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.libs << 'test'
9
+ t.pattern = 'test/**/*_test.rb'
10
+ end
11
+
12
+ task :default => :test
data/bin/tic_tac_toe ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ libdir = File.expand_path('../../lib', __FILE__).sub(/^#{Dir.pwd}\//, '')
4
+ begin
5
+ require 'tic_tac_toe'
6
+ rescue LoadError
7
+ case $!.to_s
8
+ when /tic_tac_toe/
9
+ if !$:.include?(libdir)
10
+ warn "warn: #$!. trying again with #{libdir} on load path"
11
+ $:.unshift(libdir)
12
+ retry
13
+ end
14
+ end
15
+ raise
16
+ end
17
+
18
+ TicTacToe::Game.new.run
@@ -0,0 +1,148 @@
1
+ module TicTacToe
2
+ class Board
3
+
4
+ SIZE = 3.freeze
5
+
6
+ def initialize(grid=nil)
7
+ #[[ nil, nil, nil],
8
+ # [ nil, nil, nil],
9
+ # [ nil, nil, nil]]
10
+ @grid = grid || [ [nil] * SIZE ] * SIZE
11
+ end
12
+
13
+ def get_cell(x, y)
14
+ @grid[y][x]
15
+ end
16
+
17
+ # Returns the closest middle cell
18
+ def center_cell
19
+ mid = @grid.size/2
20
+ get_cell(mid, mid)
21
+ end
22
+
23
+ # Sets the closet middle cell if its not already set
24
+ def center_cell=(letter)
25
+ mid = @grid.size/2
26
+ play_at(mid, mid, letter)
27
+ end
28
+
29
+ # Returns the corners of the grid
30
+ def corners
31
+ [@grid[0][0],
32
+ @grid[0][SIZE-1],
33
+ @grid[SIZE-1][SIZE-1],
34
+ @grid[SIZE-1][0]]
35
+ end
36
+
37
+ # Returns the corners of the empty cells
38
+ def any_empty_position(&block)
39
+ SIZE.times do |y|
40
+ SIZE.times do |x|
41
+ next if get_cell(x, y)
42
+ yield(x, y)
43
+ end
44
+ end
45
+ end
46
+
47
+ # Plays a letter at a position, unless that position has already been taken
48
+ def play_at(x, y, letter)
49
+ # Weird bug I found in ruby 1.9.3-p0
50
+ # Given a @grid of [ [nil,nil,nil], [nil,nil,nil], [nil,nil,nil] ]
51
+ # If you call @grid[0][0] = 'x'
52
+ # I aspect @grid to be [ ['x',nil,nil], [nil,nil,nil], [nil,nil,nil] ]
53
+ # What happens is that @grid is [ ['x',nil,nil], ['x',nil,nil], ['x',nil,nil] ]
54
+ # This is a workaround for that
55
+ inner = @grid[y].clone
56
+ inner[x] ||= letter
57
+ @grid[y] = inner
58
+ end
59
+
60
+ # Returns true if the grid is empty
61
+ def empty?
62
+ each_cell do |cell|
63
+ return false if cell
64
+ end
65
+ true
66
+ end
67
+
68
+ # Returns true if the grid only has one element
69
+ def only_one?
70
+ counter = 0
71
+ each_cell do |cell|
72
+ counter += 1 if cell
73
+ return false if counter >= 2
74
+ end
75
+ counter == 1
76
+ end
77
+
78
+ # Returns true if every cell is set to a value
79
+ def full?
80
+ each_cell do |cell|
81
+ return false unless cell
82
+ end
83
+ true
84
+ end
85
+
86
+ # Returns true if the board has a wining pattern
87
+ def solved?
88
+ return true if won_across?
89
+ return true if won_up_and_down?
90
+ return true if won_diagonally?
91
+ false
92
+ end
93
+
94
+ def to_s
95
+ output = ["-----------\n"]
96
+ @grid.each_with_index do |row, i|
97
+ row.each_with_index do |cell, j|
98
+ output << "#{cell || ((i*3)+j+1)} | #{"\n" if j==2}"
99
+ end
100
+ end
101
+ output << ["-----------\n\n"]
102
+ output.join
103
+ end
104
+
105
+ # Preform a deep clone of the board
106
+ # FIXME: This only works for SIZE=3
107
+ def clone
108
+ Board.new([[@grid[0][0], @grid[0][1], @grid[0][2] ],
109
+ [@grid[1][0], @grid[1][1], @grid[1][2] ],
110
+ [@grid[2][0], @grid[2][1], @grid[2][2] ]])
111
+ end
112
+
113
+ private
114
+
115
+ def each_cell(&block)
116
+ @grid.each do |row|
117
+ row.each do |cell|
118
+ yield(cell)
119
+ end
120
+ end
121
+ end
122
+
123
+ def won_across?(grid = @grid)
124
+ grid.any? do |row|
125
+ winning_group?(row)
126
+ end
127
+ end
128
+
129
+ def won_up_and_down?
130
+ won_across?(@grid.transpose)
131
+ end
132
+
133
+ def won_diagonally?
134
+ base = (0..@grid.size-1)
135
+ left_to_right = base.collect { |i| @grid[i][i]}
136
+ right_to_left = base.collect { |i| @grid[i][@grid.size-1-i]}
137
+ return true if winning_group?(left_to_right)
138
+ return true if winning_group?(right_to_left)
139
+ false
140
+ end
141
+
142
+ def winning_group?(group)
143
+ # Won when none of the cells are nil and they are all the same value
144
+ !group.any? { |cell| cell.nil? } && group.uniq.size == 1
145
+ end
146
+
147
+ end
148
+ end
@@ -0,0 +1,62 @@
1
+ #This is a computer that will play a perfect game of tic-tac-toe
2
+ #Author: Eric Koslow
3
+
4
+ require_relative 'game_types/terminal_game'
5
+
6
+ module TicTacToe
7
+ class Game
8
+ def initialize(type=TerminalGame)
9
+ @board = Board.new
10
+ @game_type = type.new(@board)
11
+ end
12
+
13
+ def run
14
+ if @game_type.computer_goes_first?
15
+ @computer_letter = O
16
+ get_move_from_computer!
17
+ else
18
+ @computer_letter = X
19
+ end
20
+
21
+ @game_type.update_board
22
+
23
+ loop do
24
+ @game_type.get_move_from_user!
25
+ @game_type.update_board
26
+ break if game_over?
27
+
28
+ get_move_from_computer!
29
+ @game_type.update_board
30
+ break if game_over?
31
+ end
32
+
33
+ if @game_type.play_again?
34
+ new_game
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def new_game
41
+ Game.new.run
42
+ end
43
+
44
+ def game_over?
45
+ if @board.solved?
46
+ @game_type.display_text("You lost!")
47
+ true
48
+ elsif @board.full?
49
+ @game_type.display_text("Cats game!")
50
+ true
51
+ else
52
+ false
53
+ end
54
+ end
55
+
56
+ def get_move_from_computer!
57
+ Solver.new(@board, @computer_letter).next_move!
58
+ @game_type.display_text("Computer's move (#{@computer_letter}):")
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,63 @@
1
+ module TicTacToe
2
+ class TerminalGame
3
+ class IllegalMove < RuntimeError
4
+ end
5
+
6
+ def initialize(board)
7
+ @board = board
8
+ end
9
+
10
+ def computer_goes_first?
11
+ print "Would you like to play first or second? (f/s) "
12
+ input = gets.chomp
13
+ unless input =~ /^(f|first)$/i
14
+ @human_letter = X
15
+ true
16
+ else
17
+ @human_letter = O
18
+ false
19
+ end
20
+ end
21
+
22
+ def get_move_from_user!
23
+ print "Your move (#{@human_letter}) (1-9): "
24
+ input = gets.chomp
25
+
26
+ if input =~ /^\d$/
27
+ cords = cord_from_num(input.to_i)
28
+ if !@board.get_cell(*cords)
29
+ @board.play_at(*(cords+[@human_letter]))
30
+ else
31
+ raise IllegalMove.new("That cell is already taken")
32
+ end
33
+ else
34
+ raise IllegalMove.new("Must be a single number")
35
+ end
36
+ rescue IllegalMove => e
37
+ display_text "Illegal Move: #{e.message}. Please try again"
38
+ get_move_from_user!
39
+ end
40
+
41
+ def play_again?
42
+ print "Play again? (y/n) "
43
+ input = gets.chomp
44
+ input =~ /^(y|yes)$/i
45
+ end
46
+
47
+ def update_board
48
+ puts @board
49
+ end
50
+
51
+ def display_text(text)
52
+ puts text
53
+ end
54
+
55
+ private
56
+
57
+ def cord_from_num(num)
58
+ num -= 1
59
+ [num % Board::SIZE, num/Board::SIZE]
60
+ end
61
+ end
62
+
63
+ end
@@ -0,0 +1,16 @@
1
+ require_relative 'strategies/threebythree_stategy'
2
+
3
+ module TicTacToe
4
+ class Solver
5
+
6
+ def initialize(board, letter, strategy=ThreebythreeStrategy)
7
+ @strategy = strategy.new(board, letter)
8
+ end
9
+
10
+ # Performs the next perfect move and updates the board as a side effect
11
+ def next_move!
12
+ @strategy.solve!
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,197 @@
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
@@ -0,0 +1,30 @@
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
@@ -0,0 +1,3 @@
1
+ module TicTacToe
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,13 @@
1
+ # Third Party Requies
2
+
3
+ # Module wide methods / constants
4
+ module TicTacToe
5
+ X = 'x'.freeze
6
+ O = 'o'.freeze
7
+ end
8
+
9
+ # Internal Project Requires
10
+ require 'tic-tac-toe/board'
11
+ require 'tic-tac-toe/solver'
12
+ require 'tic-tac-toe/game'
13
+
@@ -0,0 +1,141 @@
1
+ require 'test_helper'
2
+
3
+ class BoardTest < MiniTest::Unit::TestCase
4
+ def setup
5
+ TicTacToe::Board.instance_eval { attr_accessor :grid } # For testing purposes
6
+ @board = TicTacToe::Board.new
7
+ end
8
+
9
+ def test_empty
10
+ # Board empty when created
11
+ assert @board.empty?
12
+
13
+ # Board not longer empty after letter placed
14
+ @board.play_at(0,0,'o')
15
+ refute @board.empty?
16
+ end
17
+
18
+ def test_full
19
+ # Board not full when empty
20
+ refute @board.full?
21
+
22
+ # Board full when no cells are nil
23
+ @board.grid = [ %w(x o x), %w(o x o), %w(x o x)]
24
+ assert @board.full?
25
+ end
26
+
27
+ def test_only_one
28
+ # False when empty
29
+ refute @board.only_one?
30
+
31
+ # True when one
32
+ @board.grid = [ ['x',nil,nil], [nil,nil,nil], [nil,nil,nil] ]
33
+ assert @board.only_one?
34
+
35
+ # False when more than one
36
+ @board.grid = [ ['x','x',nil], [nil,nil,nil], [nil,nil,nil] ]
37
+ refute @board.only_one?
38
+ end
39
+
40
+ def test_get_cell
41
+ # Returns nil when nothing in cell
42
+ assert_nil @board.get_cell(0, 0)
43
+
44
+ # Returns the right letter after they've been played
45
+ @board.play_at(0,0,"o")
46
+ assert_equal "o", @board.get_cell(0, 0)
47
+
48
+ @board.play_at(1,2,"o")
49
+ assert_equal "o", @board.get_cell(1, 2)
50
+ end
51
+
52
+ def test_center_cell
53
+ # Center cell is nil in empty grid
54
+ assert_nil @board.center_cell
55
+
56
+ # Returns the correct letter after its been placed
57
+ @board.play_at(1,1,'o')
58
+ assert_equal "o", @board.center_cell
59
+
60
+ # Does not override values after they've been placed
61
+ @board.center_cell = 'x'
62
+ assert_equal "o", @board.center_cell
63
+
64
+ # Can use helper when the center cell is nil
65
+ @board = TicTacToe::Board.new
66
+ @board.center_cell = 'x'
67
+ assert_equal "x", @board.center_cell
68
+ end
69
+
70
+ def test_corners
71
+ # All of the corners are nil in an empty grid
72
+ @board.corners.each { |corner| assert_nil corner }
73
+
74
+ # Will return the corners in the proper order (clockwise)
75
+ # 0 => Top Left
76
+ # 1 => Top Right
77
+ # 2 => Bottom Right
78
+ # 3 => Bottom Left
79
+ @board.play_at(0,0,'a')
80
+ @board.play_at(2,0,'b')
81
+ @board.play_at(2,2,'c')
82
+ @board.play_at(0,2,'d')
83
+ answers = %w( a b c d )
84
+ @board.corners.each_with_index { |corner, i| assert_equal answers[i], corner }
85
+ end
86
+
87
+ def test_play_at
88
+ # Cells are proper set and override nil values
89
+ assert_nil @board.get_cell(1,1)
90
+ @board.play_at(1, 1, 'x')
91
+ assert_equal 'x', @board.get_cell(1,1)
92
+ end
93
+
94
+ def test_can_not_overide_values
95
+ assert_nil @board.get_cell(1,1)
96
+ @board.play_at(1, 1, 'x')
97
+ assert_equal 'x', @board.get_cell(1,1)
98
+ @board.play_at(1, 1, 'o')
99
+ assert_equal 'x', @board.get_cell(1,1)
100
+ end
101
+
102
+ def test_solved_acoss
103
+ refute @board.solved?
104
+
105
+ @board.grid = [ %w(x x x), [nil, nil, nil], [nil, nil, nil]]
106
+ assert @board.solved?
107
+
108
+ @board.grid = [ [nil,nil,'x'], [nil,'o',nil], ['x','x','x'] ]
109
+ assert @board.solved?
110
+
111
+ @board.grid = [ %w(x o x), [nil, nil, nil], [nil, nil, nil]]
112
+ refute @board.solved?
113
+ end
114
+
115
+ def test_solved_up_and_down
116
+ refute @board.solved?
117
+
118
+ @board.grid = [ %w(x o o), ['x', nil, nil], ['x', nil, nil]]
119
+ assert @board.solved?
120
+
121
+ @board.grid = [ [nil, nil, 'x'], [nil, nil, 'x'], [nil, nil, 'x']]
122
+ assert @board.solved?
123
+
124
+ @board.grid = [ %w(x o o), ['x', nil, nil], ['o', nil, nil]]
125
+ refute @board.solved?
126
+ end
127
+
128
+ def test_won_diagonally
129
+ refute @board.solved?
130
+
131
+ @board.grid = [ ['x', 'o', 'x'], ['x', 'x', nil], ['o', nil, 'x']]
132
+ assert @board.solved?
133
+
134
+ @board.grid = [ [nil, nil, 'x'], [nil, 'x', nil], ['x', nil, nil]]
135
+ assert @board.solved?
136
+
137
+ @board.grid = [ [nil, nil, 'x'], [nil, 'x', nil], ['o', nil, nil]]
138
+ refute @board.solved?
139
+ end
140
+
141
+ end
@@ -0,0 +1,25 @@
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
@@ -0,0 +1,149 @@
1
+ require 'test_helper'
2
+
3
+ class SolverTest < MiniTest::Unit::TestCase
4
+ def setup
5
+ TicTacToe::Solver.instance_eval { attr_accessor :strategy } # For testing
6
+ @solver = TicTacToe::Solver.new(TicTacToe::Board.new, 'x')
7
+ @solver.strategy.class.instance_eval { attr_accessor :implementation } # For testing
8
+ @solver.strategy.implementation.class.instance_eval { attr_accessor :board } # For testing
9
+ end
10
+
11
+ def test_win_next_move
12
+ refute board.solved?
13
+
14
+ # Will win, when there is a winning move
15
+ set_grid([[ 'x', 'x', nil], [nil, nil, nil], [nil, nil, nil] ])
16
+ refute board.solved? # Is not solved yet
17
+ @solver.next_move!
18
+ assert board.solved? # Solved it
19
+ assert_equal 'x', board.get_cell(2, 0) # Placed the right letter
20
+
21
+ set_grid([[ 'x', nil, nil], [nil, 'x', nil], [nil, nil, nil] ])
22
+ refute board.solved?
23
+ @solver.next_move!
24
+ assert board.solved?
25
+ assert_equal 'x', board.get_cell(2, 2)
26
+
27
+ set_grid([[ 'x', nil, nil], [nil, nil, nil], ['x', nil, nil] ])
28
+ refute board.solved?
29
+ @solver.next_move!
30
+ assert board.solved?
31
+ assert_equal 'x', board.get_cell(0, 1)
32
+
33
+ # Can not win when there is no winning move
34
+ set_grid([[ 'o', nil, nil], [nil, nil, nil], ['o', nil, nil] ])
35
+ refute board.solved?
36
+ @solver.next_move!
37
+ refute board.solved?
38
+ end
39
+
40
+ def test_block_next_move
41
+ refute board.solved?
42
+
43
+ # Will block when the opponent is about to win
44
+ set_grid([ ['o', 'o', nil], [nil, nil,nil], [nil,nil,nil]])
45
+ refute board.solved?
46
+ @solver.next_move!
47
+ refute board.solved?
48
+ # Placed the right letter at the right place
49
+ assert_equal 'x', board.get_cell(2, 0)
50
+ end
51
+
52
+
53
+ def test_fork_next_move
54
+ refute board.solved?
55
+
56
+ # Place letter in place where next turn is an automatic win
57
+ set_grid([ ['x', nil, nil], [nil, 'o',nil], [nil,nil,'x']])
58
+ refute board.solved?
59
+ @solver.next_move!
60
+ refute board.solved?
61
+ assert_equal 'x', board.get_cell(2, 0)
62
+
63
+ # 1 | 2 | x
64
+ # 4 | o | 6
65
+ # 7 | x | 9 <--
66
+ set_grid([ [nil, nil, 'x'], [nil, 'o',nil], [nil,'x',nil]])
67
+ refute board.solved?
68
+ @solver.next_move!
69
+ refute board.solved?
70
+ assert_equal 'x', board.get_cell(2, 2)
71
+ end
72
+
73
+ def test_block_fork_next_move
74
+ # Will block an opponent if they will have a change to fork next turn
75
+
76
+ # 1 | 2 | o
77
+ # 4 | x | 6
78
+ # 7 | o | 9 <--
79
+ set_grid([ [nil, nil, 'o'], [nil, 'x',nil], [nil,'o',nil]])
80
+ refute board.solved?
81
+ @solver.next_move!
82
+ refute board.solved?
83
+ assert_equal 'x', board.get_cell(2, 2)
84
+
85
+ # 1 | 2 | o
86
+ # 4 | x | 6
87
+ # o | 8 | 9
88
+ set_grid([[nil,nil,'o'],[nil,'x',nil],['o',nil,nil]])
89
+ @solver.next_move!
90
+ assert_nil board.get_cell(2,2)
91
+ assert_nil board.get_cell(0,0)
92
+
93
+ # x | 2 | 3
94
+ # 4 | o | 6
95
+ # 7 | 8 | o
96
+ set_grid([ ['x',nil,nil], [nil,'o',nil], [nil,nil,'o'] ])
97
+ @solver.next_move!
98
+ assert_nil board.get_cell(1, 0)
99
+ assert_nil board.get_cell(0, 1)
100
+ assert_nil board.get_cell(2, 1)
101
+ assert_nil board.get_cell(1, 2)
102
+ end
103
+
104
+ def test_center
105
+ # Will play in the center if its an empty board
106
+ assert board.empty?
107
+ @solver.next_move!
108
+ assert_equal 'x', board.center_cell
109
+ assert board.only_one?
110
+ end
111
+
112
+ def test_oposite_corner
113
+ # 1 | 2 | o
114
+ # 4 | x | 6
115
+ # 7 | 8 | 9
116
+ set_grid([[nil,nil,'o'],[nil,'x',nil],[nil,nil,nil]])
117
+ @solver.next_move!
118
+ assert_equal 'x', board.get_cell(0, 2)
119
+
120
+ set_grid([['o',nil,nil],[nil,'x',nil],[nil,nil,nil]])
121
+ @solver.next_move!
122
+ assert_equal 'x', board.get_cell(2, 2)
123
+ end
124
+
125
+ def test_empty_corner
126
+ set_grid([[nil,nil,nil],[nil,'o',nil],[nil,nil,nil]])
127
+ @solver.next_move!
128
+ assert_equal 'x', board.get_cell(0,0)
129
+ end
130
+
131
+ def test_any_empty_position
132
+ # o | x | o
133
+ # o | x | 6
134
+ # x | o | x
135
+ set_grid([['o', 'x', 'o'],['o','x',nil],['x','o','x']])
136
+ @solver.next_move!
137
+ assert_equal 'x', board.get_cell(2, 1)
138
+ end
139
+
140
+ private
141
+
142
+ def set_grid(grid)
143
+ board.instance_variable_set("@grid", grid)
144
+ end
145
+
146
+ def board
147
+ @solver.strategy.implementation.board
148
+ end
149
+ end
@@ -0,0 +1,3 @@
1
+ require 'tic_tac_toe'
2
+ require 'minitest/unit'
3
+ require 'minitest/autorun'
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "tic-tac-toe/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "erics_tic_tac_toe"
7
+ s.version = TicTacToe::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Eric Koslow"]
10
+ s.email = ["ekoslow@gmail.com"]
11
+ s.homepage = "https://github.com/ekosz/Erics-Tic-Tac-Toe"
12
+ s.summary = %q{A game of Tic Tac Toe}
13
+ s.description = %q{Plays the perfect game of Tic Tac Toe everytime. This computer can not lose.}
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_development_dependency 'minitest'
21
+ s.add_development_dependency "rake"
22
+ end
23
+
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: erics_tic_tac_toe
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Eric Koslow
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2012-04-09 00:00:00 -04:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: minitest
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ type: :development
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ type: :development
37
+ version_requirements: *id002
38
+ description: Plays the perfect game of Tic Tac Toe everytime. This computer can not lose.
39
+ email:
40
+ - ekoslow@gmail.com
41
+ executables:
42
+ - tic_tac_toe
43
+ extensions: []
44
+
45
+ extra_rdoc_files: []
46
+
47
+ files:
48
+ - .gitignore
49
+ - LICENSE
50
+ - README.markdown
51
+ - Rakefile
52
+ - bin/tic_tac_toe
53
+ - lib/tic-tac-toe/board.rb
54
+ - lib/tic-tac-toe/game.rb
55
+ - lib/tic-tac-toe/game_types/terminal_game.rb
56
+ - lib/tic-tac-toe/solver.rb
57
+ - lib/tic-tac-toe/strategies/threebythree_implementations/brute_force_implementation.rb
58
+ - lib/tic-tac-toe/strategies/threebythree_stategy.rb
59
+ - lib/tic-tac-toe/version.rb
60
+ - lib/tic_tac_toe.rb
61
+ - test/board_test.rb
62
+ - test/brute_force_implementation_test.rb
63
+ - test/solver_test.rb
64
+ - test/test_helper.rb
65
+ - tic-tac-toe.gemspec
66
+ has_rdoc: true
67
+ homepage: https://github.com/ekosz/Erics-Tic-Tac-Toe
68
+ licenses: []
69
+
70
+ post_install_message:
71
+ rdoc_options: []
72
+
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: "0"
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: "0"
87
+ requirements: []
88
+
89
+ rubyforge_project:
90
+ rubygems_version: 1.6.2
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: A game of Tic Tac Toe
94
+ test_files:
95
+ - test/board_test.rb
96
+ - test/brute_force_implementation_test.rb
97
+ - test/solver_test.rb
98
+ - test/test_helper.rb