nick_tac_toe 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/nick_tac_toe +23 -0
- data/lib/nick_tac_toe/board.rb +140 -0
- data/lib/nick_tac_toe/game.rb +61 -0
- data/lib/nick_tac_toe/human.rb +12 -0
- data/lib/nick_tac_toe/minimax.rb +58 -0
- data/lib/nick_tac_toe/move.rb +7 -0
- data/lib/nick_tac_toe/player.rb +11 -0
- data/lib/nick_tac_toe/setup.rb +12 -0
- data/lib/nick_tac_toe.rb +7 -0
- data/spec/board_spec.rb +255 -0
- data/spec/game_spec.rb +181 -0
- data/spec/minimax_spec.rb +76 -0
- data/spec/move_spec.rb +13 -0
- data/spec/player_spec.rb +11 -0
- metadata +69 -0
data/bin/nick_tac_toe
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
4
|
+
|
5
|
+
require 'nick_tac_toe'
|
6
|
+
|
7
|
+
players = [Human.new("x"), Minimax.new("o")]
|
8
|
+
players.shuffle!
|
9
|
+
game = Game.new(players.first, players.last)
|
10
|
+
|
11
|
+
# the main game loop!
|
12
|
+
until game.over?
|
13
|
+
game.board.print
|
14
|
+
game.take_next_turn
|
15
|
+
end
|
16
|
+
|
17
|
+
# displays the winner
|
18
|
+
game.board.print
|
19
|
+
if game.won? == true
|
20
|
+
puts "We have a winner! o__o"
|
21
|
+
else
|
22
|
+
puts "It is a draw. u__u"
|
23
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
class Board
|
2
|
+
|
3
|
+
attr_accessor :player_one, :player_two, :cells
|
4
|
+
|
5
|
+
def initialize(player_one, player_two)
|
6
|
+
@cells = [empty_spot]*9
|
7
|
+
@player_one = player_one
|
8
|
+
@player_two = player_two
|
9
|
+
end
|
10
|
+
|
11
|
+
def print
|
12
|
+
list = []
|
13
|
+
cells.each_with_index do |marker, index|
|
14
|
+
if marker == empty_spot
|
15
|
+
list << index+1
|
16
|
+
else
|
17
|
+
list << marker
|
18
|
+
end
|
19
|
+
end
|
20
|
+
list.map! do |m|
|
21
|
+
case m
|
22
|
+
when 'x'
|
23
|
+
"\e[1;36mx\e[0m"
|
24
|
+
when 'o'
|
25
|
+
"\e[1;33mo\e[0m"
|
26
|
+
else
|
27
|
+
"\e[1;30m#{m}\e[0m"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
puts "\n#{list[0]} | #{list[1]} | #{list[2]}\n----------\n#{list[3]} | #{list[4]} | #{list[5]}\n----------\n#{list[6]} | #{list[7]} | #{list[8]}\n "
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_cell position
|
34
|
+
@cells[position]
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_cell position, team
|
38
|
+
@cells[position] = team
|
39
|
+
end
|
40
|
+
|
41
|
+
def rows
|
42
|
+
[@cells[0..2], @cells[3..5], @cells[6..8]]
|
43
|
+
end
|
44
|
+
|
45
|
+
def columns
|
46
|
+
[
|
47
|
+
[@cells[0], @cells[3], @cells[6]],
|
48
|
+
[@cells[1], @cells[4], @cells[7]],
|
49
|
+
[@cells[2], @cells[5], @cells[8]]
|
50
|
+
]
|
51
|
+
end
|
52
|
+
|
53
|
+
def diagonals
|
54
|
+
[
|
55
|
+
[@cells[0], @cells[4], @cells[8]],
|
56
|
+
[@cells[2], @cells[4], @cells[6]],
|
57
|
+
]
|
58
|
+
end
|
59
|
+
|
60
|
+
def valid_move?(position)
|
61
|
+
if [0,1,2,3,4,5,6,7,8].include?(position.to_i-1)
|
62
|
+
if get_cell(position-1) == empty_spot
|
63
|
+
return true
|
64
|
+
end
|
65
|
+
else
|
66
|
+
return false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def blocking_group_for(team)
|
71
|
+
3.times do |i| # rows
|
72
|
+
return [3*i, 3*i + 1, 3*i + 2] if rows[i].sort == [empty_spot, opponent_for(team), opponent_for(team)]
|
73
|
+
end
|
74
|
+
|
75
|
+
3.times do |i| # columns
|
76
|
+
return [i, i + 3, i + 6] if columns[i].sort == [empty_spot, opponent_for(team), opponent_for(team)]
|
77
|
+
end
|
78
|
+
|
79
|
+
return [0, 4, 8] if diagonals[0].sort == [empty_spot, opponent_for(team), opponent_for(team)]
|
80
|
+
return [2, 4, 6] if diagonals[1].sort == [empty_spot, opponent_for(team), opponent_for(team)]
|
81
|
+
|
82
|
+
return nil
|
83
|
+
end
|
84
|
+
|
85
|
+
def empty_spot_in_group(group)
|
86
|
+
empty_spots = group.select do |position|
|
87
|
+
get_cell(position) == empty_spot
|
88
|
+
end
|
89
|
+
return empty_spots.first
|
90
|
+
end
|
91
|
+
|
92
|
+
def winning_group_for(team)
|
93
|
+
blocking_group_for(opponent_for(team))
|
94
|
+
end
|
95
|
+
|
96
|
+
def center
|
97
|
+
4
|
98
|
+
end
|
99
|
+
|
100
|
+
def empty_at_center?
|
101
|
+
get_cell(4) == empty_spot
|
102
|
+
end
|
103
|
+
|
104
|
+
def first_empty_cell
|
105
|
+
@cells.each_with_index do |cell, index|
|
106
|
+
return index if cell == empty_spot
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def opponent_for(team)
|
111
|
+
if team == @player_one
|
112
|
+
return @player_two
|
113
|
+
else
|
114
|
+
return @player_one
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def remaining_moves
|
119
|
+
count = []
|
120
|
+
9.times do |index|
|
121
|
+
count << index if @cells[index] == empty_spot
|
122
|
+
end
|
123
|
+
return count
|
124
|
+
end
|
125
|
+
|
126
|
+
def copy
|
127
|
+
new_board = self.dup
|
128
|
+
new_board.cells = @cells.dup
|
129
|
+
# new_board.player_one = @player_one.dup
|
130
|
+
# new_board.player_one = @player_two.dup
|
131
|
+
return new_board
|
132
|
+
end
|
133
|
+
|
134
|
+
private ################################
|
135
|
+
|
136
|
+
def empty_spot
|
137
|
+
""
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'nick_tac_toe/board'
|
2
|
+
|
3
|
+
class Game
|
4
|
+
attr_reader :board, :current_player, :player_one, :player_two
|
5
|
+
|
6
|
+
def initialize(player_one, player_two)
|
7
|
+
@board = Board.new(player_one.team, player_two.team)
|
8
|
+
@player_one = player_one
|
9
|
+
@player_two = player_two
|
10
|
+
@current_player = player_one
|
11
|
+
end
|
12
|
+
|
13
|
+
def take_next_turn
|
14
|
+
make_move(current_player.move_for(board))
|
15
|
+
end
|
16
|
+
|
17
|
+
def make_move(position)
|
18
|
+
@board.set_cell(position, current_player.team)
|
19
|
+
toggle_current_player
|
20
|
+
end
|
21
|
+
|
22
|
+
def over?
|
23
|
+
return true if draw? || won?
|
24
|
+
end
|
25
|
+
|
26
|
+
def draw?
|
27
|
+
return false if won?
|
28
|
+
return board.cells.all? {|cell| cell == @player_one.team || cell == @player_two.team }
|
29
|
+
end
|
30
|
+
|
31
|
+
def won?
|
32
|
+
return true if board.rows.any? { |row| winner_in_group(row) }
|
33
|
+
return true if board.columns.any? { |column| winner_in_group(column) }
|
34
|
+
return true if board.diagonals.any? { |diagonal| winner_in_group(diagonal) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def winner_in_group(group)
|
38
|
+
group == [@player_two.team]*3 || group == [@player_one.team]*3
|
39
|
+
end
|
40
|
+
|
41
|
+
def player_won?(player)
|
42
|
+
return true if board.rows.any? { |row| row == [player.team]*3 }
|
43
|
+
return true if board.columns.any? { |column| column == [player.team]*3 }
|
44
|
+
return true if board.diagonals.any? { |diagonal| diagonal == [player.team]*3 }
|
45
|
+
end
|
46
|
+
|
47
|
+
def winner
|
48
|
+
return player_one if player_won?(player_one)
|
49
|
+
return player_two if player_won?(player_two)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def toggle_current_player
|
55
|
+
if @current_player == @player_one
|
56
|
+
@current_player = @player_two
|
57
|
+
else
|
58
|
+
@current_player = @player_one
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Human < Player
|
2
|
+
def move_for(board)
|
3
|
+
puts "Where will you move? Pick an empty space, 1-9."
|
4
|
+
move_choice = gets.chomp.to_i
|
5
|
+
while !board.valid_move?(move_choice)
|
6
|
+
puts "That is not a valid move!"
|
7
|
+
move_choice = gets.chomp.to_i
|
8
|
+
end
|
9
|
+
return move_choice - 1
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class Minimax < Player
|
2
|
+
def move_for(board)
|
3
|
+
get_max_move(board).position
|
4
|
+
end
|
5
|
+
|
6
|
+
|
7
|
+
def get_max_move(board)
|
8
|
+
current_max_move = Move.new(0, -1)
|
9
|
+
board.remaining_moves.each do |remaining_move|
|
10
|
+
new_board = board.copy
|
11
|
+
new_board.set_cell(remaining_move, team)
|
12
|
+
return Move.new(remaining_move, 1) if player_won?(team, new_board)
|
13
|
+
return Move.new(remaining_move, 0) if new_board.cells.all? { |cell| cell == new_board.player_one || cell == new_board.player_two }
|
14
|
+
return Move.new(remaining_move, -1) if player_won?(new_board.opponent_for(team), new_board)
|
15
|
+
new_rating = get_min_move(new_board).rating
|
16
|
+
if new_rating > current_max_move.rating
|
17
|
+
current_max_move = Move.new(remaining_move, new_rating)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
return current_max_move
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_min_move(board)
|
24
|
+
current_min_move = Move.new(0, 1)
|
25
|
+
board.remaining_moves.each do |remaining_move|
|
26
|
+
new_board = board.copy
|
27
|
+
new_board.set_cell(remaining_move, new_board.opponent_for(team));
|
28
|
+
return Move.new(current_min_move.position, 1) if player_won?(team, new_board)
|
29
|
+
return Move.new(current_min_move.position, 0) if new_board.cells.all? { |cell| cell == new_board.player_one || cell == new_board.player_two }
|
30
|
+
return Move.new(current_min_move.position, -1) if player_won?(new_board.opponent_for(team), new_board)
|
31
|
+
new_rating = get_max_move(new_board).rating
|
32
|
+
if new_rating < current_min_move.rating
|
33
|
+
current_min_move = Move.new(remaining_move, new_rating)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
return current_min_move
|
37
|
+
end
|
38
|
+
|
39
|
+
def player_won?(team, board)
|
40
|
+
return true if board.rows.any? { |row| row == [team]*3 }
|
41
|
+
return true if board.columns.any? { |column| column == [team]*3 }
|
42
|
+
return true if board.diagonals.any? { |diagonal| diagonal == [team]*3 }
|
43
|
+
end
|
44
|
+
|
45
|
+
def computer?
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
private #################
|
50
|
+
|
51
|
+
def winning_group(board)
|
52
|
+
board.winning_group_for(@team)
|
53
|
+
end
|
54
|
+
|
55
|
+
def blocking_group(board)
|
56
|
+
board.blocking_group_for(@team)
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
def get_player_team
|
2
|
+
puts "Choose a team (x or o)"
|
3
|
+
input = gets.chomp
|
4
|
+
unless input == "x" || input == "X" || input == "o" || input == "O"
|
5
|
+
puts "That is neither x nor o!"
|
6
|
+
get_player_team
|
7
|
+
else
|
8
|
+
input = "x" if input == "X"
|
9
|
+
input = "o" if input == "O"
|
10
|
+
return input
|
11
|
+
end
|
12
|
+
end
|
data/lib/nick_tac_toe.rb
ADDED
data/spec/board_spec.rb
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
require "nick_tac_toe"
|
2
|
+
|
3
|
+
describe Board do
|
4
|
+
it "has a team for the computer player" do
|
5
|
+
board = Board.new("o", "x")
|
6
|
+
board.player_one.should == "o"
|
7
|
+
end
|
8
|
+
|
9
|
+
it "has a team for the opponent" do
|
10
|
+
board = Board.new("o", "x")
|
11
|
+
board.player_two.should == "x"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "gets a cell" do
|
15
|
+
board = Board.new("o", "x")
|
16
|
+
board.get_cell(0).should == ""
|
17
|
+
end
|
18
|
+
|
19
|
+
it "sets a cell" do
|
20
|
+
board = Board.new("o", "x")
|
21
|
+
board.set_cell(0, "x")
|
22
|
+
board.get_cell(0).should == "x"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns the board as rows" do
|
26
|
+
board = Board.new("o", "x")
|
27
|
+
board.set_cell(0, "x")
|
28
|
+
board.set_cell(3, "x")
|
29
|
+
board.set_cell(6, "x")
|
30
|
+
board.rows.should == [["x", "", ""],["x", "", ""],["x", "", ""]]
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns the board as coulmns" do
|
34
|
+
board = Board.new("o", "x")
|
35
|
+
board.set_cell(0, "x")
|
36
|
+
board.set_cell(3, "x")
|
37
|
+
board.set_cell(6, "x")
|
38
|
+
board.columns.should == [["x", "x", "x"],["", "", ""],["", "", ""]]
|
39
|
+
end
|
40
|
+
|
41
|
+
it "returns the board as diagonals" do
|
42
|
+
board = Board.new("o", "x")
|
43
|
+
board.set_cell(0, "x")
|
44
|
+
board.set_cell(3, "x")
|
45
|
+
board.set_cell(6, "x")
|
46
|
+
board.diagonals.should == [["x", "", ""],["", "", "x"]]
|
47
|
+
end
|
48
|
+
|
49
|
+
it "is valid for an in range move on an empty board" do
|
50
|
+
board = Board.new("o", "x")
|
51
|
+
board.valid_move?(3).should be_true
|
52
|
+
end
|
53
|
+
|
54
|
+
it "is not valid for a move out of range on an empty board" do
|
55
|
+
board = Board.new("o", "x")
|
56
|
+
board.valid_move?(-1).should be_false
|
57
|
+
end
|
58
|
+
|
59
|
+
it "is not valid for a move to a space that is not empty" do
|
60
|
+
board = Board.new("o", "x")
|
61
|
+
board.set_cell(4, "x")
|
62
|
+
board.valid_move?(5).should be_false
|
63
|
+
end
|
64
|
+
|
65
|
+
it "is not valid for a string to be a move" do
|
66
|
+
board = Board.new("o", "x")
|
67
|
+
board.valid_move?("s").should be_false
|
68
|
+
end
|
69
|
+
|
70
|
+
context "blocking_group_for" do
|
71
|
+
before(:each) do
|
72
|
+
@board = Board.new("o", "x")
|
73
|
+
end
|
74
|
+
|
75
|
+
it "finds the first row as a blocking group" do
|
76
|
+
@board.set_cell(0, "x")
|
77
|
+
@board.set_cell(1, "x")
|
78
|
+
@board.blocking_group_for("o").should == [0,1,2]
|
79
|
+
end
|
80
|
+
|
81
|
+
it "returns nothing if there is nowhere to block" do
|
82
|
+
@board.blocking_group_for("o").should == nil
|
83
|
+
end
|
84
|
+
|
85
|
+
it "returns the second row" do
|
86
|
+
@board.set_cell(3, "x")
|
87
|
+
@board.set_cell(4, "x")
|
88
|
+
@board.blocking_group_for("o").should == [3,4,5]
|
89
|
+
end
|
90
|
+
|
91
|
+
it "returns the second row if you can block in the center" do
|
92
|
+
@board.set_cell(3, "x")
|
93
|
+
@board.set_cell(5, "x")
|
94
|
+
@board.blocking_group_for("o").should == [3,4,5]
|
95
|
+
end
|
96
|
+
|
97
|
+
it "returns a blocking row for the other team" do
|
98
|
+
@board.set_cell(3, "o")
|
99
|
+
@board.set_cell(5, "o")
|
100
|
+
@board.blocking_group_for("x").should == [3,4,5]
|
101
|
+
end
|
102
|
+
|
103
|
+
it "does not return a row that is full" do
|
104
|
+
@board.set_cell(3, "o")
|
105
|
+
@board.set_cell(4, "x")
|
106
|
+
@board.set_cell(5, "o")
|
107
|
+
@board.blocking_group_for("x").should == nil
|
108
|
+
end
|
109
|
+
|
110
|
+
it "finds the first column as a blocking group" do
|
111
|
+
@board.set_cell(0, "x")
|
112
|
+
@board.set_cell(3, "x")
|
113
|
+
@board.blocking_group_for("o").should == [0,3,6]
|
114
|
+
end
|
115
|
+
|
116
|
+
it "finds the second column as a blocking group" do
|
117
|
+
@board.set_cell(1, "x")
|
118
|
+
@board.set_cell(4, "x")
|
119
|
+
@board.blocking_group_for("o").should == [1,4,7]
|
120
|
+
end
|
121
|
+
|
122
|
+
it "finds the third column as a blocking group" do
|
123
|
+
@board.set_cell(2, "x")
|
124
|
+
@board.set_cell(5, "x")
|
125
|
+
@board.blocking_group_for("o").should == [2,5,8]
|
126
|
+
end
|
127
|
+
|
128
|
+
it "finds the first diagonal as a blocking group" do
|
129
|
+
@board.set_cell(0, "x")
|
130
|
+
@board.set_cell(4, "x")
|
131
|
+
@board.blocking_group_for("o").should == [0,4,8]
|
132
|
+
end
|
133
|
+
|
134
|
+
it "finds the other diagonal as a blocking group" do
|
135
|
+
@board.set_cell(2, "x")
|
136
|
+
@board.set_cell(4, "x")
|
137
|
+
@board.blocking_group_for("o").should == [2,4,6]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context "empty_spot_in_group" do
|
142
|
+
before(:each) do
|
143
|
+
@board = Board.new("o", "x")
|
144
|
+
end
|
145
|
+
|
146
|
+
it "returns the first spot" do
|
147
|
+
@board.set_cell(1, "x")
|
148
|
+
@board.set_cell(2, "o")
|
149
|
+
@board.empty_spot_in_group([0,1,2]).should == 0
|
150
|
+
end
|
151
|
+
|
152
|
+
it "returns the first spot" do
|
153
|
+
@board.set_cell(0, "x")
|
154
|
+
@board.set_cell(2, "o")
|
155
|
+
@board.empty_spot_in_group([0,1,2]).should == 1
|
156
|
+
end
|
157
|
+
|
158
|
+
it "returns the third spot" do
|
159
|
+
@board.set_cell(2, "x")
|
160
|
+
@board.set_cell(4, "o")
|
161
|
+
@board.empty_spot_in_group([2,4,6]).should == 6
|
162
|
+
end
|
163
|
+
|
164
|
+
it "returns nil if the row is full" do
|
165
|
+
@board.set_cell(0, "x")
|
166
|
+
@board.set_cell(1, "x")
|
167
|
+
@board.set_cell(2, "o")
|
168
|
+
@board.empty_spot_in_group([0,1,2]).should == nil
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context "winning_group_for" do
|
173
|
+
before(:each) do
|
174
|
+
@board = Board.new("o", "x")
|
175
|
+
end
|
176
|
+
|
177
|
+
it "finds the first row as a winning group" do
|
178
|
+
@board.set_cell(0, "x")
|
179
|
+
@board.set_cell(1, "x")
|
180
|
+
@board.winning_group_for("x").should == [0,1,2]
|
181
|
+
end
|
182
|
+
|
183
|
+
it "finds the first column as a winning group" do
|
184
|
+
@board.set_cell(0, "x")
|
185
|
+
@board.set_cell(3, "x")
|
186
|
+
@board.winning_group_for("x").should == [0,3,6]
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context "remaining_moves" do
|
191
|
+
before(:each) do
|
192
|
+
@board = Board.new("o", "x")
|
193
|
+
end
|
194
|
+
it "calculates the number of remaining moves when there is one spot left" do
|
195
|
+
@board.set_cell(0, "x")
|
196
|
+
@board.set_cell(1, "x")
|
197
|
+
@board.set_cell(2, "o")
|
198
|
+
@board.set_cell(3, "x")
|
199
|
+
@board.set_cell(4, "x")
|
200
|
+
@board.set_cell(5, "o")
|
201
|
+
@board.set_cell(6, "x")
|
202
|
+
@board.set_cell(7, "x")
|
203
|
+
@board.remaining_moves.should == [8]
|
204
|
+
end
|
205
|
+
|
206
|
+
it "calculates the number of remaining moves when there are two spots left" do
|
207
|
+
@board.set_cell(0, "x")
|
208
|
+
@board.set_cell(1, "x")
|
209
|
+
@board.set_cell(2, "o")
|
210
|
+
@board.set_cell(3, "x")
|
211
|
+
@board.set_cell(4, "x")
|
212
|
+
@board.set_cell(5, "o")
|
213
|
+
@board.set_cell(6, "x")
|
214
|
+
@board.remaining_moves.should == [7,8]
|
215
|
+
end
|
216
|
+
|
217
|
+
it "calculates the number of remaining moves when there are no spots left" do
|
218
|
+
@board.set_cell(0, "x")
|
219
|
+
@board.set_cell(1, "x")
|
220
|
+
@board.set_cell(2, "o")
|
221
|
+
@board.set_cell(3, "x")
|
222
|
+
@board.set_cell(4, "x")
|
223
|
+
@board.set_cell(5, "o")
|
224
|
+
@board.set_cell(6, "x")
|
225
|
+
@board.set_cell(7, "x")
|
226
|
+
@board.set_cell(8, "x")
|
227
|
+
@board.remaining_moves.should == []
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
context "dup" do
|
232
|
+
before(:each) do
|
233
|
+
@board = Board.new("o", "x")
|
234
|
+
end
|
235
|
+
it "returns a copy of the board" do
|
236
|
+
@board.set_cell(1,"x")
|
237
|
+
new_board = @board.copy
|
238
|
+
new_board.get_cell(1).should == "x"
|
239
|
+
end
|
240
|
+
|
241
|
+
it "does not affect the original board when changing the new board" do
|
242
|
+
@board.set_cell(1,"x")
|
243
|
+
new_board = @board.copy
|
244
|
+
new_board.set_cell(0, "o")
|
245
|
+
@board.get_cell(0).should_not == "o"
|
246
|
+
end
|
247
|
+
|
248
|
+
it "dups players!" do
|
249
|
+
new_board = @board.copy
|
250
|
+
new_board.player_one = "f"
|
251
|
+
@board.player_one.should_not == "f"
|
252
|
+
end
|
253
|
+
|
254
|
+
end
|
255
|
+
end
|
data/spec/game_spec.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
require "nick_tac_toe"
|
2
|
+
|
3
|
+
describe "Game" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@player_one = Human.new("x")
|
7
|
+
@player_two = Human.new("o")
|
8
|
+
@game = Game.new(@player_one, @player_two)
|
9
|
+
end
|
10
|
+
|
11
|
+
context "Game over" do
|
12
|
+
it "starts with player one as the current player" do
|
13
|
+
@game.current_player.should == @player_one
|
14
|
+
end
|
15
|
+
|
16
|
+
it "makes a move on the board" do
|
17
|
+
@game.make_move(1)
|
18
|
+
@game.board.get_cell(1).should == @player_one.team
|
19
|
+
end
|
20
|
+
|
21
|
+
it "makes a move using player twos team" do
|
22
|
+
@game.make_move(1)
|
23
|
+
@game.make_move(2)
|
24
|
+
@game.board.get_cell(2).should == @player_two.team
|
25
|
+
end
|
26
|
+
|
27
|
+
it "switches to player two's turn" do
|
28
|
+
@game.make_move(2)
|
29
|
+
@game.current_player.should == @player_two
|
30
|
+
end
|
31
|
+
|
32
|
+
it "switches back to player one" do
|
33
|
+
@game.make_move(2)
|
34
|
+
@game.make_move(2)
|
35
|
+
@game.current_player.should == @player_one
|
36
|
+
end
|
37
|
+
|
38
|
+
it "does not call the game a draw if the board is not full" do
|
39
|
+
@game.board.set_cell(0, "x")
|
40
|
+
@game.draw?.should be_false
|
41
|
+
end
|
42
|
+
|
43
|
+
it "is not a draw if the game is full with a winner" do
|
44
|
+
@game.board.set_cell(0, "o")
|
45
|
+
@game.board.set_cell(1, "o")
|
46
|
+
@game.board.set_cell(2, "o")
|
47
|
+
@game.board.set_cell(3, "o")
|
48
|
+
@game.board.set_cell(4, "x")
|
49
|
+
@game.board.set_cell(5, "x")
|
50
|
+
@game.board.set_cell(6, "o")
|
51
|
+
@game.board.set_cell(7, "x")
|
52
|
+
@game.board.set_cell(8, "x")
|
53
|
+
@game.draw?.should be_false
|
54
|
+
end
|
55
|
+
|
56
|
+
it "has no winner for new game" do
|
57
|
+
@game.winner.should be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it "has player one as the winner" do
|
61
|
+
@game.board.set_cell(0, "x")
|
62
|
+
@game.board.set_cell(3, "o")
|
63
|
+
@game.board.set_cell(1, "x")
|
64
|
+
@game.board.set_cell(4, "o")
|
65
|
+
@game.board.set_cell(2, "x")
|
66
|
+
@game.winner.should == @game.player_one
|
67
|
+
end
|
68
|
+
|
69
|
+
it "has player two as the winner" do
|
70
|
+
@game.board.set_cell(0, "x")
|
71
|
+
@game.board.set_cell(3, "o")
|
72
|
+
@game.board.set_cell(1, "x")
|
73
|
+
@game.board.set_cell(4, "o")
|
74
|
+
@game.board.set_cell(8, "x")
|
75
|
+
@game.board.set_cell(5, "o")
|
76
|
+
@game.winner.should == @game.player_two
|
77
|
+
end
|
78
|
+
|
79
|
+
it "has no winner for draw game" do
|
80
|
+
@game.board.set_cell(0, "x")
|
81
|
+
@game.board.set_cell(1, "o")
|
82
|
+
@game.board.set_cell(2, "x")
|
83
|
+
@game.board.set_cell(3, "x")
|
84
|
+
@game.board.set_cell(4, "o")
|
85
|
+
@game.board.set_cell(5, "o")
|
86
|
+
@game.board.set_cell(6, "o")
|
87
|
+
@game.board.set_cell(7, "x")
|
88
|
+
@game.board.set_cell(8, "x")
|
89
|
+
@game.winner.should be_nil
|
90
|
+
end
|
91
|
+
# new tests end
|
92
|
+
|
93
|
+
|
94
|
+
it "knows that the game a draw if the board is full" do
|
95
|
+
# x o x
|
96
|
+
# x o o
|
97
|
+
# o x x
|
98
|
+
@game.board.set_cell(0, "x")
|
99
|
+
@game.board.set_cell(1, "o")
|
100
|
+
@game.board.set_cell(2, "x")
|
101
|
+
@game.board.set_cell(3, "x")
|
102
|
+
@game.board.set_cell(4, "o")
|
103
|
+
@game.board.set_cell(5, "o")
|
104
|
+
@game.board.set_cell(6, "o")
|
105
|
+
@game.board.set_cell(7, "x")
|
106
|
+
@game.board.set_cell(8, "x")
|
107
|
+
@game.draw?.should be_true
|
108
|
+
end
|
109
|
+
|
110
|
+
it "knows that three horizonal marks is a win" do
|
111
|
+
@game.board.set_cell(0, "o")
|
112
|
+
@game.board.set_cell(1, "o")
|
113
|
+
@game.board.set_cell(2, "o")
|
114
|
+
@game.won?.should be_true
|
115
|
+
end
|
116
|
+
|
117
|
+
it "knows that three vertical marks is a win" do
|
118
|
+
@game.board.set_cell(0, "o")
|
119
|
+
@game.board.set_cell(3, "o")
|
120
|
+
@game.board.set_cell(6, "o")
|
121
|
+
@game.won?.should be_true
|
122
|
+
end
|
123
|
+
|
124
|
+
it "knows that three diagonal marks is a win" do
|
125
|
+
@game.board.set_cell(0, "o")
|
126
|
+
@game.board.set_cell(4, "o")
|
127
|
+
@game.board.set_cell(8, "o")
|
128
|
+
@game.won?.should be_true
|
129
|
+
end
|
130
|
+
|
131
|
+
it "knows if there is not a winner in a group" do
|
132
|
+
@game.winner_in_group(["x","x","o"]).should be_false
|
133
|
+
end
|
134
|
+
|
135
|
+
it "knows if there is a winner in a group" do
|
136
|
+
@game.winner_in_group(["x","x","x"]).should be_true
|
137
|
+
end
|
138
|
+
|
139
|
+
it "knows if a specific player won in a row" do
|
140
|
+
@game.board.set_cell(0, @player_one.team)
|
141
|
+
@game.board.set_cell(1, @player_one.team)
|
142
|
+
@game.board.set_cell(2, @player_one.team)
|
143
|
+
@game.player_won?(@player_one).should be_true
|
144
|
+
end
|
145
|
+
|
146
|
+
it "returns false if the other player won in a row" do
|
147
|
+
@game.board.set_cell(0, @player_one.team)
|
148
|
+
@game.board.set_cell(1, @player_one.team)
|
149
|
+
@game.board.set_cell(2, @player_one.team)
|
150
|
+
@game.player_won?(@player_two).should be_false
|
151
|
+
end
|
152
|
+
|
153
|
+
it "knows if a specific player won in a column" do
|
154
|
+
@game.board.set_cell(0, @player_one.team)
|
155
|
+
@game.board.set_cell(3, @player_one.team)
|
156
|
+
@game.board.set_cell(6, @player_one.team)
|
157
|
+
@game.player_won?(@player_one).should be_true
|
158
|
+
end
|
159
|
+
|
160
|
+
it "returns false if the other player won in a column" do
|
161
|
+
@game.board.set_cell(0, @player_one.team)
|
162
|
+
@game.board.set_cell(3, @player_one.team)
|
163
|
+
@game.board.set_cell(6, @player_one.team)
|
164
|
+
@game.player_won?(@player_two).should be_false
|
165
|
+
end
|
166
|
+
|
167
|
+
it "knows if a specific player won in a diagonal" do
|
168
|
+
@game.board.set_cell(0, @player_one.team)
|
169
|
+
@game.board.set_cell(4, @player_one.team)
|
170
|
+
@game.board.set_cell(8, @player_one.team)
|
171
|
+
@game.player_won?(@player_one).should be_true
|
172
|
+
end
|
173
|
+
|
174
|
+
it "returns false if the other player won in a diagonal" do
|
175
|
+
@game.board.set_cell(0, @player_one.team)
|
176
|
+
@game.board.set_cell(4, @player_one.team)
|
177
|
+
@game.board.set_cell(8, @player_one.team)
|
178
|
+
@game.player_won?(@player_two).should be_false
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "nick_tac_toe"
|
2
|
+
|
3
|
+
describe "Minimax" do
|
4
|
+
before(:each) do
|
5
|
+
@board = Board.new("o", "x")
|
6
|
+
@minimax = Minimax.new("o")
|
7
|
+
end
|
8
|
+
|
9
|
+
it "blocks the opponent" do
|
10
|
+
# o - -
|
11
|
+
# x x -
|
12
|
+
# - - -
|
13
|
+
@board.set_cell(0, "o")
|
14
|
+
@board.set_cell(3, "x")
|
15
|
+
@board.set_cell(4, "x")
|
16
|
+
@minimax.move_for(@board).should == 5
|
17
|
+
end
|
18
|
+
|
19
|
+
xit "should win" do
|
20
|
+
@board.set_cell(0, "o")
|
21
|
+
@board.set_cell(1, "o")
|
22
|
+
@minimax.move_for(@board).should == 2
|
23
|
+
end
|
24
|
+
|
25
|
+
it "places a mark in the last available spot on the board" do
|
26
|
+
# 0 1 2 x x o
|
27
|
+
# 3 4 5 o o x
|
28
|
+
# 6 7 8 x o x
|
29
|
+
@board.set_cell(0, "x")
|
30
|
+
@board.set_cell(1, "x")
|
31
|
+
@board.set_cell(2, "o")
|
32
|
+
@board.set_cell(3, "o")
|
33
|
+
@board.set_cell(4, "o")
|
34
|
+
@board.set_cell(5, "x")
|
35
|
+
@board.set_cell(6, "x")
|
36
|
+
@board.set_cell(8, "x")
|
37
|
+
@minimax.move_for(@board).should == 7
|
38
|
+
end
|
39
|
+
|
40
|
+
xit "places in the top left corner if it goes first" do
|
41
|
+
@minimax.move_for(@board).should == 0
|
42
|
+
end
|
43
|
+
|
44
|
+
it "avoids this trap I made!" do
|
45
|
+
# x 1 2
|
46
|
+
# 3 o 5
|
47
|
+
# 6 x 8
|
48
|
+
@board.set_cell(0, "x")
|
49
|
+
@board.set_cell(4, "o")
|
50
|
+
@board.set_cell(7, "x")
|
51
|
+
@minimax.move_for(@board).should == 3
|
52
|
+
end
|
53
|
+
|
54
|
+
it "avoids the classic trap!" do
|
55
|
+
@board.set_cell(0, "x")
|
56
|
+
@minimax.move_for(@board).should == 4
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should avoid stage 2 of the classic trap!" do
|
60
|
+
@board.set_cell(0, "x")
|
61
|
+
@board.set_cell(4, "o")
|
62
|
+
@board.set_cell(8, "x")
|
63
|
+
@minimax.move_for(@board).should_not == 2
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should avoid stage 2 of the classic trap part 2!" do
|
67
|
+
@board.set_cell(0, "x")
|
68
|
+
@board.set_cell(4, "o")
|
69
|
+
@board.set_cell(8, "x")
|
70
|
+
@minimax.move_for(@board).should_not == 6
|
71
|
+
end
|
72
|
+
|
73
|
+
it "is a computer" do
|
74
|
+
Minimax.new("o").should be_computer
|
75
|
+
end
|
76
|
+
end
|
data/spec/move_spec.rb
ADDED
data/spec/player_spec.rb
ADDED
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nick_tac_toe
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.4
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nick Meccia
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-10-25 00:00:00 -05:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: Your mum.
|
18
|
+
email: nickmeccia@gmail.com
|
19
|
+
executables:
|
20
|
+
- nick_tac_toe
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files: []
|
24
|
+
|
25
|
+
files:
|
26
|
+
- bin/nick_tac_toe
|
27
|
+
- spec/board_spec.rb
|
28
|
+
- spec/game_spec.rb
|
29
|
+
- spec/minimax_spec.rb
|
30
|
+
- spec/move_spec.rb
|
31
|
+
- spec/player_spec.rb
|
32
|
+
- lib/nick_tac_toe/board.rb
|
33
|
+
- lib/nick_tac_toe/game.rb
|
34
|
+
- lib/nick_tac_toe/human.rb
|
35
|
+
- lib/nick_tac_toe/minimax.rb
|
36
|
+
- lib/nick_tac_toe/move.rb
|
37
|
+
- lib/nick_tac_toe/player.rb
|
38
|
+
- lib/nick_tac_toe/setup.rb
|
39
|
+
- lib/nick_tac_toe.rb
|
40
|
+
has_rdoc: true
|
41
|
+
homepage:
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
requirements: []
|
62
|
+
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 1.6.2
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: Yes, sir.
|
68
|
+
test_files: []
|
69
|
+
|