ttt_gem_8thlight 0.0.3 → 0.0.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.
- checksums.yaml +4 -4
- data/README.md +3 -0
- data/Rakefile +8 -0
- data/lib/ai.rb +18 -0
- data/lib/ai/ai_factory.rb +12 -0
- data/lib/ai/unbeatable_ai.rb +60 -0
- data/lib/board.rb +117 -0
- data/lib/game.rb +143 -0
- data/lib/human.rb +13 -0
- data/lib/mock_ui.rb +33 -0
- data/lib/player_factory.rb +18 -0
- data/spec/ai/ai_factory_spec.rb +12 -0
- data/spec/ai/unbeatable_ai_spec.rb +76 -0
- data/spec/ai_spec.rb +18 -0
- data/spec/board_spec.rb +166 -0
- data/spec/game_spec.rb +271 -0
- data/spec/human_spec.rb +16 -0
- data/spec/player_factory_spec.rb +29 -0
- metadata +47 -3
- data/lib/ttt.rb +0 -80
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5422ceb23dd17ef9f678a8862c6421a4375635fe
|
4
|
+
data.tar.gz: 7dc9aed92efc0aa7479fc717ad616b258c51a13c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 001ca4624857309974c523bc7c1a012b6886b3e1c44c022cf6dc849a271dec18d85f602c1c80739c5d5555b1bbe6abcdd58c08089702db32af750ee87c085db8
|
7
|
+
data.tar.gz: 860b4661253c1048ec48debddadc8ad87d6507c069fc6458d952e629626814594018f3dbb87db80e8c5c251c74f846ff97d42fbfe23e7af3005f1e3b6d85dd8f
|
data/README.md
ADDED
data/Rakefile
ADDED
data/lib/ai.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module TicTacToe
|
2
|
+
class AI
|
3
|
+
attr_reader :mark
|
4
|
+
attr_accessor :difficulty
|
5
|
+
|
6
|
+
def initialize(mark, difficulty = :unbeatable_ai)
|
7
|
+
@mark = mark
|
8
|
+
@difficulty = difficulty
|
9
|
+
end
|
10
|
+
|
11
|
+
def make_move(board)
|
12
|
+
move = { :difficulty => @difficulty,
|
13
|
+
:board => board,
|
14
|
+
:mark => @mark }
|
15
|
+
AIRules::Factory.get(move)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module TicTacToe
|
2
|
+
module AIRules
|
3
|
+
class UnbeatableAI
|
4
|
+
Infinity = 100
|
5
|
+
Negative_Infinity = -100
|
6
|
+
|
7
|
+
def self.make_move(board, mark)
|
8
|
+
best_score = Negative_Infinity
|
9
|
+
best_space = 0
|
10
|
+
opponent_mark = mark == "X" ? "O" : "X"
|
11
|
+
|
12
|
+
board.empty_spaces.each do |space|
|
13
|
+
board.place_move(mark, space)
|
14
|
+
score = minimax(board, opponent_mark, 0, Negative_Infinity, Infinity, false, -1)
|
15
|
+
board.undo_move(space)
|
16
|
+
|
17
|
+
if score > best_score
|
18
|
+
best_score = score
|
19
|
+
best_space = space
|
20
|
+
end
|
21
|
+
end
|
22
|
+
return best_space
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.minimax(board, mark, depth, score_min, score_max, max_player, color)
|
26
|
+
opponent_mark = mark == "X" ? "O" : "X"
|
27
|
+
|
28
|
+
return check_game_state(board, mark, depth) * color if game_done?(board, depth)
|
29
|
+
|
30
|
+
if max_player
|
31
|
+
board.empty_spaces.each do |spaces|
|
32
|
+
board.place_move(mark, spaces)
|
33
|
+
score_min = [score_min, minimax(board, opponent_mark, depth + 1, score_max, score_min, !(max_player), -color)].max
|
34
|
+
board.undo_move(spaces)
|
35
|
+
break if score_min >= score_max
|
36
|
+
end
|
37
|
+
return score_min
|
38
|
+
else
|
39
|
+
board.empty_spaces.each do |spaces|
|
40
|
+
board.place_move(mark, spaces)
|
41
|
+
score_max = [score_max, minimax(board, opponent_mark, depth + 1, score_max, score_min, !(max_player), -color)].min
|
42
|
+
board.undo_move(spaces)
|
43
|
+
break if score_min >= score_max
|
44
|
+
end
|
45
|
+
return score_max
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.check_game_state(board, mark, depth)
|
50
|
+
return 0 if board.tied_game?
|
51
|
+
return 100 + depth if board.winner == mark
|
52
|
+
-100 + depth
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.game_done?(board, depth)
|
56
|
+
board.has_winner? || board.tied_game? || depth == 6
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/board.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
module TicTacToe
|
2
|
+
class Board
|
3
|
+
attr_accessor :spaces, :solutions
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@spaces = %w[1 2 3 4 5 6 7 8 9]
|
7
|
+
winning_solutions
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.parse(board)
|
11
|
+
parsed_board = self.new
|
12
|
+
parse_board = []
|
13
|
+
new_board = board.split('')
|
14
|
+
new_board.each_index do |space|
|
15
|
+
if new_board[space] == '_' || new_board[space].to_i != 0
|
16
|
+
parse_board << (space + 1).to_s
|
17
|
+
else
|
18
|
+
parse_board << new_board[space]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
parsed_board.spaces = parse_board
|
22
|
+
parsed_board
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
@spaces.join
|
27
|
+
end
|
28
|
+
|
29
|
+
def get(space)
|
30
|
+
@spaces[space-1]
|
31
|
+
end
|
32
|
+
|
33
|
+
def place_move(piece, *space)
|
34
|
+
space.each do |space|
|
35
|
+
@spaces[(space.to_i)-1] = piece
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def undo_move(space)
|
40
|
+
@spaces[(space.to_i)-1] = space.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def is_taken?(space)
|
44
|
+
@spaces[(space.to_i) - 1].to_i == 0
|
45
|
+
end
|
46
|
+
|
47
|
+
def tied_game?
|
48
|
+
full_board? && !has_winner?
|
49
|
+
end
|
50
|
+
|
51
|
+
def full_board?
|
52
|
+
(@spaces.count { |x| x == 'X' } + @spaces.count { |x| x == 'O' }) == 9
|
53
|
+
end
|
54
|
+
|
55
|
+
def is_board_empty?
|
56
|
+
!@spaces.include?("X") && !@spaces.include?("O")
|
57
|
+
end
|
58
|
+
|
59
|
+
def has_winner?
|
60
|
+
has_winner = false
|
61
|
+
@solutions.each { |sol| has_winner = true if unique?(sol)}
|
62
|
+
has_winner
|
63
|
+
end
|
64
|
+
|
65
|
+
def unique?(spaces)
|
66
|
+
spaces.map { |s| @spaces[s-1]}.uniq.length == 1
|
67
|
+
end
|
68
|
+
|
69
|
+
def make_blank
|
70
|
+
@spaces.reduce("") do |blank_spaces, spaces|
|
71
|
+
if !spaces.to_i.zero?
|
72
|
+
blank_spaces << '_'
|
73
|
+
else
|
74
|
+
blank_spaces << spaces
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def empty_spaces
|
80
|
+
@spaces.select { |x| x.to_i != 0}
|
81
|
+
end
|
82
|
+
|
83
|
+
def row_win
|
84
|
+
(@spaces.map { |space| space.to_i }).each_slice(3) { |nums| @solutions.push(nums)}
|
85
|
+
end
|
86
|
+
|
87
|
+
def column_win
|
88
|
+
3.times do |check|
|
89
|
+
((check+1)..(9)).step(3).each_slice(3) do |nums|
|
90
|
+
@solutions.push(nums)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def diagonal_win
|
96
|
+
(1..(9)).step(4).each_slice(3) do |nums|
|
97
|
+
@solutions.push(nums)
|
98
|
+
end
|
99
|
+
((3)..(9)-1).step(2).each_slice(3) do |nums|
|
100
|
+
@solutions.push(nums)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def winning_solutions
|
105
|
+
@solutions = []
|
106
|
+
row_win
|
107
|
+
column_win
|
108
|
+
diagonal_win
|
109
|
+
end
|
110
|
+
|
111
|
+
def winner
|
112
|
+
winner = ""
|
113
|
+
@solutions.each { |sol| winner = @spaces[sol[0]-1] if unique?(sol)}
|
114
|
+
winner
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/lib/game.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
module TicTacToe
|
2
|
+
class Game
|
3
|
+
attr_reader :ui, :board, :player_one, :player_two
|
4
|
+
attr_accessor :over
|
5
|
+
|
6
|
+
def initialize(ui, config={}, over = false)
|
7
|
+
@ui = ui
|
8
|
+
game_config(config) if !config.empty?
|
9
|
+
@over = over
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.play_game(ui, config, move="")
|
13
|
+
game = self.new(ui, config)
|
14
|
+
game.set_spaces(config[:game_board])
|
15
|
+
player = game.current_player(config[:game_board])
|
16
|
+
|
17
|
+
if game.board.is_board_empty? && player.class != AI
|
18
|
+
game.ui.display_message
|
19
|
+
game.ui.print_board(game.board)
|
20
|
+
game.place_move(player.mark, move)
|
21
|
+
elsif game.is_over?
|
22
|
+
game.ui.display_result(game.result)
|
23
|
+
game.over = true
|
24
|
+
else
|
25
|
+
game.ui.ask_move(player)
|
26
|
+
move = game.get_move(player) if player.class == AI
|
27
|
+
game.place_move(player.mark, move)
|
28
|
+
game.ui.print_board(game.board)
|
29
|
+
|
30
|
+
if game.is_over?
|
31
|
+
game.ui.display_result(game.result)
|
32
|
+
game.over = true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
game
|
36
|
+
end
|
37
|
+
|
38
|
+
def game_config(config)
|
39
|
+
@player_one = PlayerFactory.create(:type => config[:player_one].to_sym, :mark => "X")
|
40
|
+
@player_two = PlayerFactory.create(:type => config[:player_two].to_sym, :mark => "O")
|
41
|
+
@board = Board.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def start_game
|
45
|
+
@ui.print_board(@board)
|
46
|
+
end
|
47
|
+
|
48
|
+
def set_spaces(board)
|
49
|
+
@board = Board.parse(board)
|
50
|
+
end
|
51
|
+
|
52
|
+
def is_over?
|
53
|
+
tied_game? || has_winner?
|
54
|
+
end
|
55
|
+
|
56
|
+
def tied_game?
|
57
|
+
@board.tied_game?
|
58
|
+
end
|
59
|
+
|
60
|
+
def has_winner?
|
61
|
+
@board.has_winner?
|
62
|
+
end
|
63
|
+
|
64
|
+
def winner
|
65
|
+
@board.winner
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_move(player)
|
69
|
+
return @ui.ask_move(player) if player.class == Human
|
70
|
+
return player.make_move(@board)
|
71
|
+
end
|
72
|
+
|
73
|
+
def valid_move?(space)
|
74
|
+
!@board.is_taken?(space)
|
75
|
+
end
|
76
|
+
|
77
|
+
def ask_move(player)
|
78
|
+
user_input = @ui.ask_move(player)
|
79
|
+
while 0 > user_input || user_input > 9
|
80
|
+
@ui.output.puts("Invalid Move, Try Again")
|
81
|
+
user_input = @ui.ask_move(player)
|
82
|
+
end
|
83
|
+
user_input
|
84
|
+
end
|
85
|
+
|
86
|
+
def make_moves(moves={})
|
87
|
+
moves[:player_one] = get_move(@player_one)
|
88
|
+
if valid_move?(moves[:player_one])
|
89
|
+
place_move(@player_one.mark, moves[:player_one])
|
90
|
+
|
91
|
+
if !is_over?
|
92
|
+
@ui.print_board(@board) if @player_two.class == Human
|
93
|
+
moves[:player_two] = get_move(@player_two)
|
94
|
+
place_move(@player_two.mark, moves[:player_two])
|
95
|
+
@ui.print_board(@obard)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def place_move(mark, move)
|
101
|
+
@board.place_move(mark, move) if !move.empty? && valid_move?(move)
|
102
|
+
end
|
103
|
+
|
104
|
+
def start
|
105
|
+
start_game
|
106
|
+
make_moves until is_over?
|
107
|
+
end_game
|
108
|
+
end
|
109
|
+
|
110
|
+
def end_game
|
111
|
+
@ui.print_board(@board)
|
112
|
+
@ui.display_result(result)
|
113
|
+
again?
|
114
|
+
end
|
115
|
+
|
116
|
+
def current_player(board)
|
117
|
+
count = 0
|
118
|
+
(0..8).each do |space|
|
119
|
+
count += 1 if board[space] != "_"
|
120
|
+
end
|
121
|
+
|
122
|
+
if count.even? || count.zero?
|
123
|
+
return @player_one
|
124
|
+
else
|
125
|
+
return @player_two
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def again?
|
130
|
+
start if @ui.again?
|
131
|
+
@over = true
|
132
|
+
end
|
133
|
+
|
134
|
+
def result
|
135
|
+
return "tie" if tied_game?
|
136
|
+
winner
|
137
|
+
end
|
138
|
+
|
139
|
+
def unique?(spaces)
|
140
|
+
spaces.map { |space| @board.spaces[space-1]}.uniq.length == 1
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
data/lib/human.rb
ADDED
data/lib/mock_ui.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
class MockUI
|
2
|
+
attr_reader :asked_play_again, :board_printed, :displayed_result, :ask_again, :asked_move, :move, :displayed_message
|
3
|
+
|
4
|
+
def output
|
5
|
+
$stdout
|
6
|
+
end
|
7
|
+
|
8
|
+
def print_board(board)
|
9
|
+
@board_printed = true
|
10
|
+
end
|
11
|
+
|
12
|
+
def display_result(result)
|
13
|
+
@displayed_result = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def display_message
|
17
|
+
@displayed_message = true
|
18
|
+
end
|
19
|
+
|
20
|
+
def ask_play_again?
|
21
|
+
@asked_play_again = true
|
22
|
+
end
|
23
|
+
|
24
|
+
def again?
|
25
|
+
@ask_again = true
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
def ask_move(player)
|
30
|
+
@asked_move = true
|
31
|
+
@move = "3"
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'human'
|
2
|
+
|
3
|
+
module TicTacToe
|
4
|
+
class PlayerFactory
|
5
|
+
def self.create(input)
|
6
|
+
case input[:type]
|
7
|
+
when :ai
|
8
|
+
AI.new(input[:mark])
|
9
|
+
when :human
|
10
|
+
Human.new(input[:mark])
|
11
|
+
else
|
12
|
+
raise "Invalid Player Type"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'ai/ai_factory'
|
2
|
+
|
3
|
+
describe TicTacToe::AIRules::Factory do
|
4
|
+
it "makes an unbeatable AI move" do
|
5
|
+
move = { :difficulty => :unbeatable_ai,
|
6
|
+
:board => TicTacToe::Board.new,
|
7
|
+
:mark => "O" }
|
8
|
+
TicTacToe::AIRules::UnbeatableAI.should_receive(:make_move).and_return("5")
|
9
|
+
ai = described_class.get(move)
|
10
|
+
ai.should == "5"
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'ai/unbeatable_ai'
|
2
|
+
require 'board'
|
3
|
+
|
4
|
+
describe TicTacToe::AIRules::UnbeatableAI do
|
5
|
+
let(:board) { TicTacToe::Board.new }
|
6
|
+
let(:mark) { 'O' }
|
7
|
+
|
8
|
+
def test_board(board)
|
9
|
+
TicTacToe::Board.parse(board)
|
10
|
+
end
|
11
|
+
|
12
|
+
context "#minimax" do
|
13
|
+
let(:depth) { 0 }
|
14
|
+
let(:score_min) { -100 }
|
15
|
+
let(:score_max) { 100 }
|
16
|
+
|
17
|
+
it "returns 100 on a winning board" do
|
18
|
+
board = test_board('OOO456789')
|
19
|
+
described_class.minimax(board, mark, depth, score_min, score_max, false, 1).should == 100
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns -100 on a losing board" do
|
23
|
+
board = test_board('XXX456789')
|
24
|
+
described_class.minimax(board, mark, depth, score_min, score_max, false, 1).should == -100
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns 0 on a tied game" do
|
28
|
+
board = test_board('XOXOXOOXO')
|
29
|
+
described_class.minimax(board, mark, depth, score_min, score_max, false, 1).should == 0
|
30
|
+
end
|
31
|
+
|
32
|
+
it "returns -99 one move away from winning" do
|
33
|
+
board = test_board('123OO6789')
|
34
|
+
described_class.minimax(board, mark, depth, score_min, score_max, false, -1).should == -99
|
35
|
+
end
|
36
|
+
|
37
|
+
it "returns 0 a move before a tie" do
|
38
|
+
board = test_board('XOXOXOOX9')
|
39
|
+
described_class.minimax(board, mark, depth, score_min, score_max, false, -1).should == 0
|
40
|
+
end
|
41
|
+
|
42
|
+
it "returns -100 for win" do
|
43
|
+
board = test_board("OOOOOOOO9")
|
44
|
+
described_class.minimax(board, mark, depth, score_min, score_max, true, -1).should == -100
|
45
|
+
end
|
46
|
+
|
47
|
+
it "returns -99 when 2 moves away" do
|
48
|
+
board = test_board("XX3XO6OXO")
|
49
|
+
described_class.minimax(board, mark, depth, score_min, score_max, false, -1).should == -99
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "making winning moves" do
|
54
|
+
it "returns the winning space" do
|
55
|
+
board = test_board("XX3XO6OXO")
|
56
|
+
described_class.make_move(board, mark).should == "3"
|
57
|
+
end
|
58
|
+
|
59
|
+
it "wins instead of tie" do
|
60
|
+
board = test_board("XOX45O7OX")
|
61
|
+
described_class.make_move(board, mark).should == "5"
|
62
|
+
end
|
63
|
+
|
64
|
+
it "wins asap" do
|
65
|
+
board = test_board("OX34O6XX9")
|
66
|
+
described_class.make_move(board, mark).should == "9"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "unbeatable" do
|
71
|
+
it "block" do
|
72
|
+
board = test_board('OXXXXO7O9')
|
73
|
+
described_class.make_move(board, mark).should == "7"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/spec/ai_spec.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'ai'
|
2
|
+
require 'board'
|
3
|
+
|
4
|
+
describe TicTacToe::AI do
|
5
|
+
let(:ai) { described_class.new("O") }
|
6
|
+
|
7
|
+
it "sets to unbeatable AI" do
|
8
|
+
ai.difficulty = :unbeatable_ai
|
9
|
+
ai.difficulty.should == :unbeatable_ai
|
10
|
+
end
|
11
|
+
|
12
|
+
it "makes a move on the board" do
|
13
|
+
board = TicTacToe::Board.new
|
14
|
+
ai.difficulty = :unbeatable_ai
|
15
|
+
ai.make_move(board).should == "1"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/spec/board_spec.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'board'
|
2
|
+
|
3
|
+
describe TicTacToe::Board do
|
4
|
+
let(:board) { described_class.new }
|
5
|
+
|
6
|
+
describe "#new" do
|
7
|
+
it "creates blank board with numbers" do
|
8
|
+
(1..9).each do |num|
|
9
|
+
board.get(num).should == ("#{num}")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#make_blank" do
|
15
|
+
it "makes the board blank" do
|
16
|
+
board.make_blank.should == '_________'
|
17
|
+
end
|
18
|
+
|
19
|
+
it "doesn't replace moves with spaces" do
|
20
|
+
board.spaces[3] = "X"
|
21
|
+
board.make_blank.should == '___X_____'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "making move" do
|
26
|
+
describe "#place_move" do
|
27
|
+
it "places a move" do
|
28
|
+
board.place_move('X', 0)
|
29
|
+
board.get(0).should == "X"
|
30
|
+
end
|
31
|
+
|
32
|
+
it "allows multiple inputs" do
|
33
|
+
board.place_move('X', 1, 2, 3)
|
34
|
+
board.get(1).should == "X"
|
35
|
+
board.get(2).should == "X"
|
36
|
+
board.get(3).should == "X"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#undo_move" do
|
41
|
+
it "undoes a move" do
|
42
|
+
board.place_move('X', 2)
|
43
|
+
board.undo_move(2)
|
44
|
+
board.get(2).should == "2"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#is_taken?" do
|
50
|
+
it "is false when the space isn't taken" do
|
51
|
+
board.is_taken?(5).should be_false
|
52
|
+
end
|
53
|
+
|
54
|
+
it "is true when the space is taken" do
|
55
|
+
board.place_move('X', 6)
|
56
|
+
board.is_taken?(6).should be_true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "checking for winner" do
|
61
|
+
before :each do
|
62
|
+
board.place_move('X', 1, 3, 5, 8)
|
63
|
+
board.place_move('O', 2, 4, 6, 7, 9)
|
64
|
+
end
|
65
|
+
describe "#tied_game?" do
|
66
|
+
it "is true when the game is tied" do
|
67
|
+
board.tied_game?.should be_true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "#full_board?" do
|
73
|
+
it "returns true if board is full" do
|
74
|
+
board.place_move('X', 1, 3, 5, 8)
|
75
|
+
board.place_move('O', 2, 4, 6, 7, 9)
|
76
|
+
board.full_board?.should be_true
|
77
|
+
end
|
78
|
+
|
79
|
+
it "returns false if board not full" do
|
80
|
+
board.full_board?.should be_false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "#has_winner?" do
|
85
|
+
it "returns false with no winner" do
|
86
|
+
board.has_winner?.should be_false
|
87
|
+
end
|
88
|
+
|
89
|
+
it "returns true with a winner" do
|
90
|
+
board.place_move('X', 1, 2, 3)
|
91
|
+
board.has_winner?.should be_true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "#empty_spaces" do
|
96
|
+
it "returns all the empty spaces in the board" do
|
97
|
+
board.empty_spaces.should == %w(1 2 3 4 5 6 7 8 9)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "returns all empty spaces with moves" do
|
101
|
+
board.place_move('O', 1,2,3)
|
102
|
+
board.empty_spaces.should == %w(4 5 6 7 8 9)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "finding winner" do
|
107
|
+
it "knows when X is the winner" do
|
108
|
+
board.place_move('X', 1, 2, 3)
|
109
|
+
board.winner.should == "X"
|
110
|
+
end
|
111
|
+
|
112
|
+
it "knows when O is the winenr" do
|
113
|
+
board.place_move('O', 1, 2, 3)
|
114
|
+
board.winner.should == "O"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "#is_board_empty?" do
|
119
|
+
it "knows when the board is empty" do
|
120
|
+
board.is_board_empty?.should be_true
|
121
|
+
end
|
122
|
+
|
123
|
+
it "isn't empty with moves" do
|
124
|
+
board.place_move('X', 1)
|
125
|
+
board.is_board_empty?.should be_false
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "getting and parsing a board" do
|
130
|
+
it "parses the board" do
|
131
|
+
board_parsed = 'X23456O89'
|
132
|
+
board = described_class.parse(board_parsed)
|
133
|
+
board_parsed.split('').each_with_index do |mark, space|
|
134
|
+
board.get(space+1).should == mark
|
135
|
+
end
|
136
|
+
board.spaces.should be_a(Array)
|
137
|
+
end
|
138
|
+
|
139
|
+
it "gets a board" do
|
140
|
+
board_parsed = '12345XX8O'
|
141
|
+
board = described_class.parse(board_parsed)
|
142
|
+
board.to_s.should == board_parsed
|
143
|
+
end
|
144
|
+
|
145
|
+
it "gets a blank board" do
|
146
|
+
board_parsed = '_________'
|
147
|
+
board = described_class.parse(board_parsed)
|
148
|
+
board.to_s.should == "123456789"
|
149
|
+
end
|
150
|
+
|
151
|
+
it "gets a board with underscores and some marks" do
|
152
|
+
board_parsed = '_X_______'
|
153
|
+
board = described_class.parse(board_parsed)
|
154
|
+
board.to_s.should == "1X3456789"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe "#winning_solutions" do
|
159
|
+
it "gets all winning solutions for board" do
|
160
|
+
board.winning_solutions
|
161
|
+
board.solutions.should == [[1,2,3], [4,5,6], [7,8,9],
|
162
|
+
[1,4,7], [2,5,8], [3,6,9],
|
163
|
+
[1,5,9], [3,5,7]]
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
data/spec/game_spec.rb
ADDED
@@ -0,0 +1,271 @@
|
|
1
|
+
require 'game'
|
2
|
+
require 'mock_ui'
|
3
|
+
|
4
|
+
describe TicTacToe::Game do
|
5
|
+
let(:ui) { MockUI.new }
|
6
|
+
let(:settings) {{:player_one => :human, :player_two => :ai }}
|
7
|
+
let(:game) {described_class.new(ui, settings)}
|
8
|
+
|
9
|
+
describe "#game_config" do
|
10
|
+
before(:each) do
|
11
|
+
game.game_config({:player_one => :human, :player_two => :ai})
|
12
|
+
end
|
13
|
+
|
14
|
+
it "creates the first player" do
|
15
|
+
game.player_one.class.should == TicTacToe::Human
|
16
|
+
end
|
17
|
+
|
18
|
+
it "creates the second player" do
|
19
|
+
game.player_two.class.should == TicTacToe::AI
|
20
|
+
end
|
21
|
+
|
22
|
+
it "creates a board" do
|
23
|
+
game.board.spaces.count.should == 9
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#is_over?" do
|
28
|
+
it "is true when over" do
|
29
|
+
game.set_spaces("OXOXOXXOX")
|
30
|
+
game.is_over?.should be_true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#again?" do
|
35
|
+
it "asks user to play again" do
|
36
|
+
game.set_spaces("OXOXOXXOX")
|
37
|
+
game.start
|
38
|
+
ui.ask_play_again?.should be_true
|
39
|
+
game.over.should be_true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#result" do
|
44
|
+
it "returns X as winner" do
|
45
|
+
game.set_spaces('X234X678X')
|
46
|
+
game.result.should == "X"
|
47
|
+
end
|
48
|
+
|
49
|
+
it "returns O as winner" do
|
50
|
+
game.set_spaces('O234O678O')
|
51
|
+
game.result.should == "O"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "returns a tie" do
|
55
|
+
game.set_spaces('OXOXOXXOX')
|
56
|
+
game.result.should == "tie"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#start" do
|
61
|
+
before(:each) do
|
62
|
+
game.stub(:has_winner).and_return(true)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "prints board after game over" do
|
66
|
+
game.set_spaces("OOO456789")
|
67
|
+
game.start
|
68
|
+
game.ui.board_printed.should be_true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
describe "#new" do
|
74
|
+
it "gets the ui" do
|
75
|
+
game.ui.class.should == MockUI
|
76
|
+
end
|
77
|
+
|
78
|
+
it "has players" do
|
79
|
+
game.player_one.should_not be_nil
|
80
|
+
game.player_two.should_not be_nil
|
81
|
+
end
|
82
|
+
|
83
|
+
it "can set a board" do
|
84
|
+
game.set_spaces("X234567O9")
|
85
|
+
game.board.spaces.should == ["X", "2", "3", "4", "5", "6", "7", "O", "9"]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "#has_winner" do
|
90
|
+
it "has a winner" do
|
91
|
+
game.set_spaces('123OOO789')
|
92
|
+
game.has_winner?.should be_true
|
93
|
+
end
|
94
|
+
|
95
|
+
it "doesnt have a winner" do
|
96
|
+
game.set_spaces('1234O6789')
|
97
|
+
game.has_winner?.should be_false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "#matches?" do
|
102
|
+
it "doesnt match without marks" do
|
103
|
+
game.unique?([1,2, 5]).should be_false
|
104
|
+
end
|
105
|
+
|
106
|
+
it "matches with moves" do
|
107
|
+
game.set_spaces('XXX456789')
|
108
|
+
game.unique?([1,2,3]).should be_true
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context "#tied_game?" do
|
113
|
+
it "is true for a tie" do
|
114
|
+
game.set_spaces('XOXOXOOXO')
|
115
|
+
game.tied_game?.should be_true
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context "#winner" do
|
120
|
+
it "returns X" do
|
121
|
+
game.set_spaces("XXX456789")
|
122
|
+
game.winner.should == "X"
|
123
|
+
end
|
124
|
+
|
125
|
+
it "returns O" do
|
126
|
+
game.set_spaces("OOO456789")
|
127
|
+
game.winner.should == "O"
|
128
|
+
end
|
129
|
+
|
130
|
+
it "doesnt have a winner" do
|
131
|
+
game.set_spaces("XXO456789")
|
132
|
+
game.winner.should == ""
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe "#display_result" do
|
137
|
+
it "displays for a tie" do
|
138
|
+
game.should_receive(:is_over?).and_return(true)
|
139
|
+
game.start
|
140
|
+
game.ui.displayed_result.should be_true
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "valid_move?" do
|
145
|
+
it "is true for a valid move" do
|
146
|
+
game.valid_move?("3").should be_true
|
147
|
+
end
|
148
|
+
|
149
|
+
it "returns false for invalid move" do
|
150
|
+
game.set_spaces("1234X6789")
|
151
|
+
game.valid_move?("5").should be_false
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "#current_player" do
|
156
|
+
it "returns for X" do
|
157
|
+
player = game.current_player('_________')
|
158
|
+
player.should == game.player_one
|
159
|
+
end
|
160
|
+
|
161
|
+
it "returns for O" do
|
162
|
+
player = game.current_player("X_________")
|
163
|
+
player.should == game.player_two
|
164
|
+
end
|
165
|
+
|
166
|
+
it "returns X after moves are made" do
|
167
|
+
player = game.current_player("X__O______")
|
168
|
+
player.should == game.player_one
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe "#play_game" do
|
173
|
+
let(:config) {{:player_one => :ai, :player_two => :human, :game_board => '_________'}}
|
174
|
+
|
175
|
+
it "checks for game over" do
|
176
|
+
config[:player_one] = :human
|
177
|
+
config[:player_two] = :ai
|
178
|
+
config[:game_board] = "O2O45X789"
|
179
|
+
game = described_class.play_game(ui, config)
|
180
|
+
game.board.to_s.should == "OOO45X789"
|
181
|
+
game.over.should be_true
|
182
|
+
end
|
183
|
+
|
184
|
+
it "asks player for their move" do
|
185
|
+
config[:game_board] = "12345X789"
|
186
|
+
described_class.play_game(ui, config)
|
187
|
+
ui.asked_move.should be_true
|
188
|
+
end
|
189
|
+
|
190
|
+
it "doesnt make a move for the human" do
|
191
|
+
config[:player_one] = :human
|
192
|
+
new_game = described_class.play_game(ui, config, "4")
|
193
|
+
new_game.board.to_s.should == "123X56789"
|
194
|
+
end
|
195
|
+
|
196
|
+
it "ends game when game is over" do
|
197
|
+
config[:game_board] = "XXX456789"
|
198
|
+
new_game = described_class.play_game(ui, config)
|
199
|
+
new_game.over.should be_true
|
200
|
+
end
|
201
|
+
|
202
|
+
it "makes a move if computer is first and board is empty" do
|
203
|
+
new_game = described_class.play_game(ui, config)
|
204
|
+
new_game.board.to_s.should == "X23456789"
|
205
|
+
player = game.current_player(new_game.board.to_s)
|
206
|
+
player.should == game.player_two
|
207
|
+
end
|
208
|
+
|
209
|
+
it "doesnt make a move when game is over" do
|
210
|
+
config[:game_board] = "OOO456789"
|
211
|
+
new_game = described_class.play_game(ui, config)
|
212
|
+
new_game.board.to_s.should == "OOO456789"
|
213
|
+
end
|
214
|
+
|
215
|
+
it "lets human make first move" do
|
216
|
+
config[:player_one] = :human
|
217
|
+
new_game = described_class.play_game(ui, config, "5")
|
218
|
+
new_game.board.to_s.should == "1234X6789"
|
219
|
+
end
|
220
|
+
|
221
|
+
it "makes a move when computer is second turn" do
|
222
|
+
config[:player_one] = :human
|
223
|
+
config[:player_two] = :ai
|
224
|
+
config[:game_board] = "12X456789"
|
225
|
+
new_game = described_class.play_game(ui, config)
|
226
|
+
new_game.board.to_s.should == "O2X456789"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
describe "#place_move" do
|
231
|
+
it "places move on the board" do
|
232
|
+
game.place_move("X", "4")
|
233
|
+
game.board.spaces.should == %w(1 2 3 X 5 6 7 8 9)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
describe "#make_moves" do
|
238
|
+
it "receives output" do
|
239
|
+
game.ui.should_receive(:ask_move).and_return(-1, 4)
|
240
|
+
game.ui.output.should_receive(:puts)
|
241
|
+
game.ask_move(game.player_one)
|
242
|
+
end
|
243
|
+
|
244
|
+
it "prints the board when the game is over and its human v human" do
|
245
|
+
game.ui.should_receive(:ask_move).once
|
246
|
+
game.player_two.stub(:class).and_return(TicTacToe::Human)
|
247
|
+
game.ui.stub(:ask_move).and_return("5")
|
248
|
+
game.set_spaces("123X5X789")
|
249
|
+
game.make_moves
|
250
|
+
end
|
251
|
+
|
252
|
+
it "prints the board" do
|
253
|
+
game.ui.should_receive(:print_board)
|
254
|
+
game.make_moves
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
describe "#get_move" do
|
259
|
+
it "makes the human move" do
|
260
|
+
game.player_one.stub(:class).and_return(TicTacToe::Human)
|
261
|
+
game.ui.should_receive(:ask_move).and_return("1")
|
262
|
+
game.get_move(game.player_one).should == "1"
|
263
|
+
end
|
264
|
+
|
265
|
+
it "makes a computer move" do
|
266
|
+
game.player_one.stub(:class).and_return(TicTacToe::AI)
|
267
|
+
game.player_one.should_receive(:make_move).with(game.board).and_return("2")
|
268
|
+
game.get_move(game.player_one).should == "2"
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
data/spec/human_spec.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'human'
|
2
|
+
|
3
|
+
describe TicTacToe::Human do
|
4
|
+
let(:human) { human = described_class.new('X')}
|
5
|
+
|
6
|
+
describe "#new" do
|
7
|
+
it "creates a player with a mark" do
|
8
|
+
human.mark.should == "X"
|
9
|
+
end
|
10
|
+
|
11
|
+
it "makes a move on the board" do
|
12
|
+
board = TicTacToe::Board.new
|
13
|
+
human.make_move(1, board)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'player_factory'
|
2
|
+
require 'human'
|
3
|
+
|
4
|
+
def unbeatable_ai
|
5
|
+
TicTacToe::AIRules::UnbeatableAI
|
6
|
+
end
|
7
|
+
|
8
|
+
describe TicTacToe::PlayerFactory do
|
9
|
+
let(:player_factory) { described_class }
|
10
|
+
|
11
|
+
it "creates a human player" do
|
12
|
+
input = { type: :human, mark: :X }
|
13
|
+
player = player_factory.create(input)
|
14
|
+
player.should be_an_instance_of(TicTacToe::Human)
|
15
|
+
player.mark.should == :X
|
16
|
+
end
|
17
|
+
|
18
|
+
it "creates a computer player" do
|
19
|
+
input = {type: :ai, mark: :O, opponent_mark: :X, difficulty: unbeatable_ai }
|
20
|
+
player = player_factory.create(input)
|
21
|
+
player.should be_an_instance_of(TicTacToe::AI)
|
22
|
+
player.mark.should == :O
|
23
|
+
end
|
24
|
+
|
25
|
+
it "raises expection if input isn't correct" do
|
26
|
+
input = {type: :something, mark: :G}
|
27
|
+
expect { player_factory.create(input)}.to raise_error("Invalid Player Type")
|
28
|
+
end
|
29
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ttt_gem_8thlight
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Meagan Waller
|
@@ -9,14 +9,58 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
date: 2013-10-06 00:00:00.000000000 Z
|
12
|
-
dependencies:
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
13
41
|
description: Tic Tac Toe Gem
|
14
42
|
email: meaganewaller@gmail.com
|
15
43
|
executables: []
|
16
44
|
extensions: []
|
17
45
|
extra_rdoc_files: []
|
18
46
|
files:
|
19
|
-
-
|
47
|
+
- README.md
|
48
|
+
- Rakefile
|
49
|
+
- lib/ai/ai_factory.rb
|
50
|
+
- lib/ai/unbeatable_ai.rb
|
51
|
+
- lib/ai.rb
|
52
|
+
- lib/board.rb
|
53
|
+
- lib/game.rb
|
54
|
+
- lib/human.rb
|
55
|
+
- lib/mock_ui.rb
|
56
|
+
- lib/player_factory.rb
|
57
|
+
- spec/ai/ai_factory_spec.rb
|
58
|
+
- spec/ai/unbeatable_ai_spec.rb
|
59
|
+
- spec/ai_spec.rb
|
60
|
+
- spec/board_spec.rb
|
61
|
+
- spec/game_spec.rb
|
62
|
+
- spec/human_spec.rb
|
63
|
+
- spec/player_factory_spec.rb
|
20
64
|
homepage: http://rubygems.org/gems/ttt_gem_8thlight
|
21
65
|
licenses:
|
22
66
|
- MIT
|
data/lib/ttt.rb
DELETED
@@ -1,80 +0,0 @@
|
|
1
|
-
class TTT
|
2
|
-
PLAYER_ONE = 'x'
|
3
|
-
PLAYER_TWO = 'o'
|
4
|
-
|
5
|
-
attr_accessor :board, :current_player
|
6
|
-
|
7
|
-
def initialize(board = Array.new(9) {"-"}, current_player = PLAYER_ONE)
|
8
|
-
@board = board
|
9
|
-
@current_player = current_player
|
10
|
-
end
|
11
|
-
|
12
|
-
def make_move(space)
|
13
|
-
@board[space] = @current_player
|
14
|
-
@current_player = @current_player == PLAYER_ONE ? PLAYER_TWO : PLAYER_ONE
|
15
|
-
end
|
16
|
-
|
17
|
-
def available_spaces
|
18
|
-
@board.map.with_index { |space, index| index if space == '-' }.compact
|
19
|
-
end
|
20
|
-
|
21
|
-
def new_board_with_move(space)
|
22
|
-
board_with_current_player = deep_copy
|
23
|
-
board_with_current_player.make_move(space)
|
24
|
-
board_with_current_player
|
25
|
-
end
|
26
|
-
|
27
|
-
def deep_copy
|
28
|
-
copy = dup
|
29
|
-
copy.board = board.dup
|
30
|
-
copy.current_player = current_player.dup
|
31
|
-
copy
|
32
|
-
end
|
33
|
-
|
34
|
-
def possible_boards
|
35
|
-
available_spaces.map { |space| new_board_with_move(space) }
|
36
|
-
end
|
37
|
-
|
38
|
-
def possible_values
|
39
|
-
possible_boards.map { |board| board.minimax }
|
40
|
-
end
|
41
|
-
|
42
|
-
def best_move
|
43
|
-
return available_spaces.max_by { |space| new_board_with_move(space).minimax } if @current_player == PLAYER_ONE
|
44
|
-
return available_spaces.min_by { |space| new_board_with_move(space).minimax } if @current_player == PLAYER_TWO
|
45
|
-
end
|
46
|
-
|
47
|
-
def minimax
|
48
|
-
return 100 if x_won
|
49
|
-
return -100 if o_won
|
50
|
-
return 0 if tie
|
51
|
-
return possible_values.max + available_spaces.count if @current_player == PLAYER_ONE
|
52
|
-
return possible_values.min - available_spaces.count if @current_player == PLAYER_TWO
|
53
|
-
end
|
54
|
-
|
55
|
-
def x_won
|
56
|
-
winning_player?(PLAYER_ONE)
|
57
|
-
end
|
58
|
-
|
59
|
-
def o_won
|
60
|
-
winning_player?(PLAYER_TWO)
|
61
|
-
end
|
62
|
-
|
63
|
-
def tie
|
64
|
-
!winning_player?(PLAYER_ONE) &&
|
65
|
-
!winning_player?(PLAYER_TWO) &&
|
66
|
-
available_spaces.count == 0
|
67
|
-
end
|
68
|
-
|
69
|
-
def winning_player?(current_player)
|
70
|
-
return true if @board[0..2] == [current_player] * 3
|
71
|
-
return true if @board[3..5] == [current_player] * 3
|
72
|
-
return true if @board[6..8] == [current_player] * 3
|
73
|
-
return true if [@board[0], @board[3], @board[6]] == [current_player] * 3
|
74
|
-
return true if [@board[1], @board[4], @board[7]] == [current_player] * 3
|
75
|
-
return true if [@board[2], @board[5], @board[8]] == [current_player] * 3
|
76
|
-
return true if [@board[0], @board[4], @board[8]] == [current_player] * 3
|
77
|
-
return true if [@board[2], @board[4], @board[6]] == [current_player] * 3
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|