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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 90a9a9a08a763d1a45802b801ab7209932b4f87f
4
- data.tar.gz: d0fe04ab969feb057556cd9b272093338f451519
3
+ metadata.gz: 5422ceb23dd17ef9f678a8862c6421a4375635fe
4
+ data.tar.gz: 7dc9aed92efc0aa7479fc717ad616b258c51a13c
5
5
  SHA512:
6
- metadata.gz: 8a2a6e285c1c8339f862dd05650ff8a857a6c315f0eb88489c6231430ffe2ddbb9043161b2f1ee024977af4c8ec2e103fe30256c426b361556b4f14f02b9fabf
7
- data.tar.gz: cbbbca7956907d03c60202d60ea6fc193d7b44c66c9f9bd7f464870746cac035230dab4e2b920833fa5e0ab47e80aef2deebe7ae4d0e9ad0a5069cd88601df99
6
+ metadata.gz: 001ca4624857309974c523bc7c1a012b6886b3e1c44c022cf6dc849a271dec18d85f602c1c80739c5d5555b1bbe6abcdd58c08089702db32af750ee87c085db8
7
+ data.tar.gz: 860b4661253c1048ec48debddadc8ad87d6507c069fc6458d952e629626814594018f3dbb87db80e8c5c251c74f846ff97d42fbfe23e7af3005f1e3b6d85dd8f
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Tic Tac Toe Gem
2
+
3
+
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new(:spec) do |t|
4
+ t.fail_on_error = false
5
+ end
6
+
7
+ task :default => :spec
8
+
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,12 @@
1
+ module TicTacToe
2
+ module AIRules
3
+ class Factory
4
+ def self.get(move)
5
+ case move[:difficulty]
6
+ when :unbeatable_ai
7
+ UnbeatableAI.make_move(move[:board], move[:mark])
8
+ end
9
+ end
10
+ end
11
+ end
12
+ 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
@@ -0,0 +1,13 @@
1
+ module TicTacToe
2
+ class Human
3
+ attr_reader :mark
4
+
5
+ def initialize(mark)
6
+ @mark = mark
7
+ end
8
+
9
+ def make_move(space, board)
10
+ board.place_move(@mark, space)
11
+ end
12
+ end
13
+ end
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
@@ -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
@@ -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.3
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
- - lib/ttt.rb
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
-