nick_tac_toe 0.1.4
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/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
|
+
|