tic_tac_toe_nhu 0.0.11 → 0.1.0
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/bin/tic_tac_toe +2 -2
- data/coverage/.resultset.json +52 -65
- data/lib/tic_tac_toe/board.rb +1 -1
- data/lib/tic_tac_toe/game.rb +21 -19
- data/lib/tic_tac_toe/game_factory.rb +11 -33
- data/lib/tic_tac_toe/player.rb +2 -2
- data/lib/tic_tac_toe/player_factory.rb +19 -4
- data/lib/tic_tac_toe/rules.rb +1 -1
- data/lib/tic_tac_toe/runner.rb +27 -20
- data/lib/tic_tac_toe/strategy/console_user.rb +1 -1
- data/lib/tic_tac_toe/strategy/minimax.rb +18 -16
- data/lib/tic_tac_toe/ui/console.rb +2 -2
- data/lib/tic_tac_toe/values.rb +10 -0
- data/spec/{integration → acceptance}/tictactoe/unbeatable_computer_spec.rb +8 -6
- data/spec/mocks/game.rb +2 -2
- data/spec/mocks/game_factory.rb +1 -1
- data/spec/mocks/player.rb +16 -0
- data/spec/mocks/player_factory.rb +3 -2
- data/spec/mocks/strategy/dynamic.rb +1 -1
- data/spec/tic_tac_toe/game_factory_spec.rb +31 -50
- data/spec/tic_tac_toe/game_spec.rb +53 -62
- data/spec/tic_tac_toe/player_factory_spec.rb +21 -28
- data/spec/tic_tac_toe/player_spec.rb +12 -17
- data/spec/tic_tac_toe/runner_spec.rb +49 -56
- data/spec/tic_tac_toe/strategy/console_user_spec.rb +1 -1
- data/spec/tic_tac_toe/strategy/minimax_spec.rb +65 -64
- data/spec/tic_tac_toe/ui/console_spec.rb +2 -2
- data/spec/tic_tac_toe/values_spec.rb +17 -0
- data/tic_tac_toe_nhu.gemspec +2 -1
- metadata +7 -4
- data/spec/tic_tac_toe/player_factory_mock.rb +0 -10
@@ -1,33 +1,35 @@
|
|
1
1
|
require 'tic_tac_toe/rules'
|
2
|
+
require 'tic_tac_toe/values'
|
2
3
|
|
3
4
|
module TicTacToe
|
4
5
|
module Strategy
|
5
6
|
class Minimax
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
WINNING_SCORE = 1
|
8
|
+
LOSING_SCORE = -1
|
9
|
+
TIE = 0
|
10
|
+
MIDDLE_SQUARE = 4
|
11
|
+
|
12
|
+
def initialize(player_value)
|
13
|
+
@player_value = player_value
|
14
|
+
@opponent_value = TicTacToe::Values.opponent(@player_value)
|
10
15
|
end
|
11
16
|
|
12
|
-
def move
|
13
|
-
return first_move if first_move?
|
14
|
-
move = minimax(
|
17
|
+
def move(board)
|
18
|
+
return first_move(board) if first_move?(board)
|
19
|
+
move = minimax(player_value, board)
|
15
20
|
move.move
|
16
21
|
end
|
17
22
|
|
18
23
|
private
|
19
|
-
|
20
|
-
LOSING_SCORE = -1
|
21
|
-
TIE = 0
|
22
|
-
MIDDLE_SQUARE = 4
|
24
|
+
attr_reader :player_value, :opponent_value
|
23
25
|
|
24
|
-
def first_move?
|
25
|
-
moves_count =
|
26
|
+
def first_move?(board)
|
27
|
+
moves_count = board.number_of_moves
|
26
28
|
moves_count == 0 || moves_count == 1
|
27
29
|
end
|
28
30
|
|
29
|
-
def first_move
|
30
|
-
return MIDDLE_SQUARE if
|
31
|
+
def first_move(board)
|
32
|
+
return MIDDLE_SQUARE if board.available_moves.include?(MIDDLE_SQUARE)
|
31
33
|
0
|
32
34
|
end
|
33
35
|
|
@@ -63,7 +65,7 @@ module TicTacToe
|
|
63
65
|
end
|
64
66
|
|
65
67
|
def opponent(player)
|
66
|
-
(player ==
|
68
|
+
(player == player_value) ? opponent_value : player_value
|
67
69
|
end
|
68
70
|
|
69
71
|
def best_move(moves)
|
@@ -28,7 +28,7 @@ module TicTacToe
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def display_winner(winner)
|
31
|
-
@output.puts("#{winner.name}(#{winner.value})
|
31
|
+
@output.puts("#{winner.name}(#{winner.value}) wins!")
|
32
32
|
end
|
33
33
|
|
34
34
|
def display_tied_game
|
@@ -55,7 +55,7 @@ module TicTacToe
|
|
55
55
|
|
56
56
|
def game_type_list
|
57
57
|
result = ""
|
58
|
-
TicTacToe::GameFactory.new.types.each_with_index do |value, index|
|
58
|
+
TicTacToe::GameFactory.new.types.each_with_index.map do |value, index|
|
59
59
|
result << "#{index + 1} - #{game_type_values(value)}\n"
|
60
60
|
end
|
61
61
|
result
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'tic_tac_toe/board'
|
2
2
|
require 'tic_tac_toe/rules'
|
3
3
|
require 'tic_tac_toe/player_factory'
|
4
|
+
require 'tic_tac_toe/values'
|
4
5
|
|
5
6
|
class SimpleStrategy
|
6
7
|
attr_accessor :next_move
|
@@ -11,9 +12,12 @@ class SimpleStrategy
|
|
11
12
|
end
|
12
13
|
|
13
14
|
describe "Unbeatable computer", :slow_test => true do
|
15
|
+
attr_reader :computer
|
16
|
+
|
14
17
|
before(:each) do
|
15
18
|
@board = TicTacToe::Board.new
|
16
|
-
@human_value =
|
19
|
+
@human_value = TicTacToe::VALUES[0]
|
20
|
+
@computer = TicTacToe::PlayerFactory.new.create(:computer, TicTacToe::VALUES[1])
|
17
21
|
end
|
18
22
|
|
19
23
|
it "wins all game when computer goes first" do
|
@@ -25,10 +29,8 @@ describe "Unbeatable computer", :slow_test => true do
|
|
25
29
|
play_game_for_each_available_move(@board)
|
26
30
|
end
|
27
31
|
|
28
|
-
def make_computer_move(
|
29
|
-
computer
|
30
|
-
clone_board.mark(computer.move, computer.value)
|
31
|
-
clone_board
|
32
|
+
def make_computer_move(board)
|
33
|
+
board.mark(computer.move(board), computer.value)
|
32
34
|
end
|
33
35
|
|
34
36
|
def rules(board)
|
@@ -50,7 +52,7 @@ describe "Unbeatable computer", :slow_test => true do
|
|
50
52
|
|
51
53
|
def make_players_move(board, move_history, move)
|
52
54
|
move_history << move
|
53
|
-
board.mark(move,
|
55
|
+
board.mark(move, @human_value)
|
54
56
|
make_computer_move(board) if !rules(board).game_over?
|
55
57
|
play_game_for_each_available_move(board, move_history)
|
56
58
|
end
|
data/spec/mocks/game.rb
CHANGED
data/spec/mocks/game_factory.rb
CHANGED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'surrogate/rspec'
|
2
|
+
|
3
|
+
class MockPlayer
|
4
|
+
Surrogate.endow(self)
|
5
|
+
define(:initialize) {|name = "Player", value = "X", strategy = "no"|}
|
6
|
+
define_reader :name
|
7
|
+
define_reader :value
|
8
|
+
define_reader :strategy
|
9
|
+
define(:move) {|board|}
|
10
|
+
end
|
11
|
+
|
12
|
+
describe TicTacToe::Player do
|
13
|
+
it "checks if mock is substitutable" do
|
14
|
+
MockPlayer.should be_substitutable_for(TicTacToe::Player)
|
15
|
+
end
|
16
|
+
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'surrogate/rspec'
|
2
|
+
require 'tic_tac_toe/values'
|
2
3
|
|
3
4
|
class MockPlayerFactory
|
4
5
|
Surrogate.endow(self)
|
5
6
|
|
6
|
-
define(:
|
7
|
-
define(:
|
7
|
+
define(:create){|player_type, value|}
|
8
|
+
define(:types){TicTacToe::PlayerFactory.new.types}
|
8
9
|
end
|
9
10
|
|
10
11
|
describe TicTacToe::PlayerFactory do
|
@@ -1,78 +1,59 @@
|
|
1
1
|
require 'tic_tac_toe/spec_helper'
|
2
2
|
require 'tic_tac_toe/game_factory'
|
3
3
|
require 'tic_tac_toe/player_factory'
|
4
|
+
require 'tic_tac_toe/player'
|
4
5
|
require 'mocks/player_factory'
|
5
6
|
|
6
7
|
describe TicTacToe::GameFactory do
|
7
|
-
|
8
8
|
before(:each) do
|
9
|
-
@board = "board"
|
10
9
|
@player_factory = MockPlayerFactory.new
|
11
10
|
@game_factory = TicTacToe::GameFactory.new(@player_factory)
|
12
11
|
end
|
13
12
|
|
14
|
-
it "ensures MockPlayerFactory has the same interface as PlayerFactory" do
|
15
|
-
MockPlayerFactory.should be_substitutable_for(TicTacToe::PlayerFactory)
|
16
|
-
end
|
17
|
-
|
18
13
|
it "returns 4 types of games" do
|
19
14
|
@game_factory.types.size.should == 4
|
20
15
|
end
|
21
16
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
it "returns human vs computer game" do
|
28
|
-
human = "human"
|
29
|
-
computer = "computer"
|
30
|
-
@player_factory.will_have_human human
|
31
|
-
@player_factory.will_have_computer computer
|
32
|
-
|
33
|
-
game = @game_factory.create(1, @board)
|
34
|
-
game.current_player.should == human
|
17
|
+
context "creates game" do
|
18
|
+
before(:each) do
|
19
|
+
@human = TicTacToe::Player.new("Human", TicTacToe::VALUES[0], nil)
|
20
|
+
@computer = TicTacToe::Player.new("Computer", opponent_value(@human.value), nil)
|
21
|
+
end
|
35
22
|
|
36
|
-
|
37
|
-
|
38
|
-
end
|
23
|
+
def test_game_state_creation(game_type, players)
|
24
|
+
@player_factory.will_create players[0], players[1]
|
39
25
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
26
|
+
game = @game_factory.create(game_type)
|
27
|
+
game.current_player.should == players[0]
|
28
|
+
game.make_player_move(1)
|
29
|
+
game.current_player.should == players[1]
|
30
|
+
end
|
45
31
|
|
46
|
-
|
47
|
-
|
32
|
+
it "returns user vs user game" do
|
33
|
+
@human2 = TicTacToe::Player.new("Human 2", opponent_value(@human.value), nil)
|
34
|
+
test_game_state_creation(1, [@human, @human2])
|
35
|
+
end
|
48
36
|
|
49
|
-
|
50
|
-
|
51
|
-
|
37
|
+
it "returns human vs computer game" do
|
38
|
+
test_game_state_creation(2, [@human, @computer])
|
39
|
+
end
|
52
40
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
@player_factory.will_have_human human1, human2
|
41
|
+
it "returns computer vs human game" do
|
42
|
+
test_game_state_creation(3, [@computer, @human])
|
43
|
+
end
|
57
44
|
|
58
|
-
|
59
|
-
|
45
|
+
it "returns computer vs computer game" do
|
46
|
+
@computer2 = TicTacToe::Player.new("Computer 2", opponent_value(@computer.value), nil)
|
47
|
+
test_game_state_creation(4, [@computer, @computer2])
|
48
|
+
end
|
60
49
|
|
61
|
-
|
50
|
+
it "raises an error when the game type doesn't exist" do
|
51
|
+
lambda{@game_factory.create(10)}.should raise_error(ArgumentError)
|
62
52
|
end
|
63
53
|
|
64
|
-
it "returns computer vs computer game" do
|
65
|
-
computer1 = "computer1"
|
66
|
-
computer2 = "computer2"
|
67
|
-
@player_factory.will_have_computer computer2, computer1
|
68
|
-
|
69
|
-
game = @game_factory.create(4, @board)
|
70
|
-
game.current_player.should == computer2
|
71
|
-
|
72
|
-
@player_factory.was asked_for(:computer).times(2)
|
73
54
|
end
|
74
55
|
|
75
|
-
|
76
|
-
|
56
|
+
def opponent_value(value)
|
57
|
+
TicTacToe::Values.opponent(value)
|
77
58
|
end
|
78
59
|
end
|
@@ -1,113 +1,104 @@
|
|
1
1
|
require 'tic_tac_toe/spec_helper'
|
2
|
-
require 'tic_tac_toe/game'
|
3
2
|
require 'tic_tac_toe/board'
|
3
|
+
require 'tic_tac_toe/game'
|
4
4
|
require 'tic_tac_toe/player'
|
5
|
+
require 'tic_tac_toe/values'
|
5
6
|
require 'mocks/strategy/dynamic'
|
6
7
|
|
7
8
|
describe TicTacToe::Game do
|
9
|
+
attr_reader :game_state, :player1, :player2, :board
|
10
|
+
|
8
11
|
before(:each) do
|
9
12
|
@board = TicTacToe::Board.new
|
10
|
-
|
11
13
|
@player1_strategy = MockDynamicStrategy.new
|
12
|
-
@player1 = TicTacToe::Player.new("
|
14
|
+
@player1 = TicTacToe::Player.new("player1", TicTacToe::VALUES[0], @player1_strategy)
|
15
|
+
@player2 = TicTacToe::Player.new("player1", TicTacToe::VALUES[1], nil)
|
16
|
+
@game_state = TicTacToe::Game.new([@player1, @player2], @board)
|
17
|
+
end
|
13
18
|
|
14
|
-
@player2_strategy = MockDynamicStrategy.new
|
15
|
-
@player2 = TicTacToe::Player.new("John", "O", @player2_strategy)
|
16
19
|
|
17
|
-
|
20
|
+
it "can read board" do
|
21
|
+
game_state.board.should == board
|
18
22
|
end
|
19
23
|
|
20
|
-
|
21
|
-
it "
|
24
|
+
describe "game over" do
|
25
|
+
it "is false when there is no mark" do
|
26
|
+
game_state.should_not be_game_over
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should be over when there is a winner" do
|
30
|
+
mark_winning_board(player1.value)
|
31
|
+
game_state.should be_game_over
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "winner" do
|
36
|
+
it "has no winner when there is no mark" do
|
37
|
+
game_state.winner.should be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it "is player 1" do
|
41
|
+
mark_winning_board(player1.value)
|
42
|
+
game_state.winner.should == player1
|
43
|
+
end
|
44
|
+
|
45
|
+
it "is player 2" do
|
46
|
+
mark_winning_board(player2.value)
|
47
|
+
game_state.winner.should == player2
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "make player move" do
|
52
|
+
it "mark the board with player move when caller doesn't pass in a move" do
|
22
53
|
@player1_strategy.add_move(1)
|
23
|
-
@
|
54
|
+
@game_state.make_player_move
|
24
55
|
@board.unique_marked_values.should include(@player1.value)
|
25
56
|
end
|
26
57
|
|
27
58
|
it "does not mark the board if user doesn't return an input and caller didn't pass in a move" do
|
28
59
|
@player1_strategy.add_move(nil)
|
29
|
-
@
|
60
|
+
@game_state.make_player_move
|
30
61
|
@board.unique_marked_values.should_not include(@player1.value)
|
31
62
|
end
|
32
63
|
|
33
64
|
it "marks the board with the move passed in" do
|
34
65
|
@player1_strategy.add_move(1)
|
35
66
|
move = 2
|
36
|
-
@
|
67
|
+
@game_state.make_player_move(move)
|
37
68
|
@board.available_moves.should_not include(move)
|
38
69
|
end
|
39
70
|
|
40
71
|
it "marks the board with player move when move passed in is nil" do
|
41
72
|
@player1_strategy.add_move(1)
|
42
|
-
@
|
73
|
+
@game_state.make_player_move(nil)
|
43
74
|
@board.available_moves.should_not include(1)
|
44
75
|
end
|
45
76
|
|
46
77
|
it "marks the board with current player value when the move passed in" do
|
47
78
|
move = 2
|
48
|
-
@
|
79
|
+
@game_state.make_player_move(move)
|
49
80
|
@board.unique_marked_values.should include(@player1.value)
|
50
81
|
end
|
51
82
|
end
|
52
83
|
|
53
|
-
describe "return winner player based on the value return from rules" do
|
54
|
-
it "is Todd when value is X" do
|
55
|
-
mark_board([0, 1, 2], @player1.value)
|
56
|
-
@game.winner.should == @player1
|
57
|
-
end
|
58
|
-
|
59
|
-
it "is John when it is O" do
|
60
|
-
mark_board([0, 1, 2], @player2.value)
|
61
|
-
@game.winner.should == @player2
|
62
|
-
end
|
63
|
-
|
64
|
-
it "is nil when no one wins" do
|
65
|
-
@game.winner.should be_nil
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
84
|
describe "changes player" do
|
70
85
|
it "doesn't change player if player 1 doesn't return a move" do
|
71
|
-
@
|
72
|
-
@
|
73
|
-
|
74
|
-
@game.make_move
|
75
|
-
@game.current_player.should == @player1
|
86
|
+
@game_state.current_player.should == @player1
|
87
|
+
@game_state.make_player_move
|
88
|
+
@game_state.current_player.should == @player1
|
76
89
|
end
|
77
90
|
|
78
91
|
it "changes to player 2 after player 1 moves" do
|
79
|
-
@
|
80
|
-
@
|
81
|
-
|
82
|
-
@game.make_move
|
83
|
-
@game.current_player.should == @player2
|
92
|
+
@game_state.current_player.should == @player1
|
93
|
+
@game_state.make_player_move(1)
|
94
|
+
@game_state.current_player.should == @player2
|
84
95
|
end
|
85
96
|
end
|
86
97
|
|
87
|
-
describe "game over" do
|
88
|
-
it "is over when there is a winner" do
|
89
|
-
mark_board([0, 1, 2], @player2.value)
|
90
|
-
@game.should be_over
|
91
|
-
end
|
92
|
-
|
93
|
-
it "is over when there is a tied" do
|
94
|
-
mark_board((0...9).to_a, "i")
|
95
|
-
@game.should be_over
|
96
|
-
end
|
97
|
-
|
98
|
-
it "is not over when there is no mark on the board" do
|
99
|
-
@game.should_not be_over
|
100
|
-
end
|
101
|
-
|
102
|
-
it "is not over when there is no winner or a tied" do
|
103
|
-
mark_board([1, 2], @player1.value)
|
104
|
-
@game.should_not be_over
|
105
|
-
end
|
106
|
-
end
|
107
98
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
99
|
+
def mark_winning_board(value)
|
100
|
+
[0, 4, 8].each do |move|
|
101
|
+
game_state.board.mark(move, value)
|
111
102
|
end
|
112
103
|
end
|
113
104
|
end
|