tic_tac_toes 0.0.9 → 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/Gemfile.lock +1 -1
- data/bin/tic_tac_toes +2 -6
- data/lib/tic_tac_toes/command_line/menu.rb +3 -3
- data/lib/tic_tac_toes/command_line/runner.rb +6 -4
- data/lib/tic_tac_toes/core/game_state.rb +8 -0
- data/lib/tic_tac_toes/core/history.rb +1 -0
- data/lib/tic_tac_toes/core/move_strategies/easy_ai.rb +2 -4
- data/lib/tic_tac_toes/core/move_strategies/hard_ai.rb +27 -27
- data/lib/tic_tac_toes/core/move_strategies/human.rb +2 -2
- data/lib/tic_tac_toes/core/move_strategies/medium_ai.rb +2 -2
- data/lib/tic_tac_toes/core/player.rb +3 -3
- data/lib/tic_tac_toes/core/rules.rb +1 -1
- data/lib/tic_tac_toes/ui/adapter.rb +5 -5
- data/lib/tic_tac_toes/version.rb +1 -1
- data/spec/tic_tac_toes/command_line/menu_spec.rb +1 -3
- data/spec/tic_tac_toes/command_line/runner_spec.rb +0 -34
- data/spec/tic_tac_toes/core/game_state_spec.rb +54 -7
- data/spec/tic_tac_toes/core/history_spec.rb +4 -4
- data/spec/tic_tac_toes/core/move_strategies/easy_ai_spec.rb +14 -7
- data/spec/tic_tac_toes/core/move_strategies/hard_ai_spec.rb +43 -28
- data/spec/tic_tac_toes/core/move_strategies/human_spec.rb +9 -9
- data/spec/tic_tac_toes/core/move_strategies/medium_ai_spec.rb +14 -9
- data/spec/tic_tac_toes/core/player_spec.rb +17 -14
- data/spec/tic_tac_toes/core/rules_spec.rb +3 -2
- data/spec/tic_tac_toes/ui/adapter_spec.rb +9 -9
- metadata +2 -8
- data/lib/tic_tac_toes/core/board_factory.rb +0 -11
- data/lib/tic_tac_toes/core/game_state_factory.rb +0 -15
- data/spec/tic_tac_toes/core/board_factory_spec.rb +0 -13
- data/spec/tic_tac_toes/core/game_state_factory_spec.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b7b828672ca3ca2908fc62b6f7cf25622a16322
|
4
|
+
data.tar.gz: 704c24b5374e84549277e659401621ee7044ee75
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a9a5b96876e221e2b43c7e5996be97ea4e14af904017d6f7e5f64e3ae978ab08b71ef3f194ad25ffe690a785ca8a8168943c290b396c6c414a957dd042be566d
|
7
|
+
data.tar.gz: 14ff8a5d98dd83cadd2821a3321a758a716eb81783d617539777093c4eae479a5b7057fb5bb364a169169d089b58bd902a03352feb6f96c8bd5ef62e7133009f
|
data/Gemfile.lock
CHANGED
data/bin/tic_tac_toes
CHANGED
@@ -5,13 +5,11 @@ $LOAD_PATH << File.expand_path(File.dirname(__FILE__) + '/../lib')
|
|
5
5
|
require 'tic_tac_toes/command_line/prompt'
|
6
6
|
require 'tic_tac_toes/core/io'
|
7
7
|
|
8
|
-
require 'tic_tac_toes/core/board_factory'
|
9
8
|
require 'tic_tac_toes/core/player_factory'
|
10
9
|
require 'tic_tac_toes/command_line/menu'
|
11
10
|
|
12
11
|
require 'tic_tac_toes/database/pg_wrapper'
|
13
12
|
require 'tic_tac_toes/core/history'
|
14
|
-
require 'tic_tac_toes/core/game_state_factory'
|
15
13
|
|
16
14
|
require 'tic_tac_toes/command_line/runner'
|
17
15
|
|
@@ -20,12 +18,10 @@ database = "tic_tac_toes"
|
|
20
18
|
prompt = TicTacToes::CommandLine::Prompt
|
21
19
|
io = TicTacToes::Core::IO.new(prompt)
|
22
20
|
|
23
|
-
board_factory = TicTacToes::Core::BoardFactory.new
|
24
21
|
player_factory = TicTacToes::Core::PlayerFactory.new(io)
|
25
|
-
menu = TicTacToes::CommandLine::Menu.new(io,
|
22
|
+
menu = TicTacToes::CommandLine::Menu.new(io, player_factory)
|
26
23
|
|
27
24
|
database_wrapper = TicTacToes::Database::PGWrapper.new(database)
|
28
25
|
history = TicTacToes::Core::History.new(database_wrapper)
|
29
|
-
game_state_factory = TicTacToes::Core::GameStateFactory.new(history)
|
30
26
|
|
31
|
-
TicTacToes::CommandLine::Runner.new(io, menu,
|
27
|
+
TicTacToes::CommandLine::Runner.new(io, menu, history).run
|
@@ -1,16 +1,16 @@
|
|
1
|
+
require 'tic_tac_toes/core/board'
|
1
2
|
require 'tic_tac_toes/core/rules'
|
2
3
|
|
3
4
|
module TicTacToes
|
4
5
|
module CommandLine
|
5
6
|
class Menu
|
6
|
-
def initialize(io,
|
7
|
+
def initialize(io, player_factory)
|
7
8
|
@io = io
|
8
|
-
@board_factory = board_factory
|
9
9
|
@player_factory = player_factory
|
10
10
|
end
|
11
11
|
|
12
12
|
def get_board
|
13
|
-
|
13
|
+
TicTacToes::Core::Board.new(row_size: get_row_size)
|
14
14
|
end
|
15
15
|
|
16
16
|
def get_players
|
@@ -1,15 +1,17 @@
|
|
1
|
+
require 'tic_tac_toes/core/game_state'
|
2
|
+
|
1
3
|
module TicTacToes
|
2
4
|
module CommandLine
|
3
5
|
class Runner
|
4
|
-
def initialize(io, menu,
|
6
|
+
def initialize(io, menu, history)
|
5
7
|
@io = io
|
6
8
|
@menu = menu
|
7
|
-
@
|
9
|
+
@history = history
|
8
10
|
end
|
9
11
|
|
10
12
|
def run
|
11
13
|
board, players = @menu.get_board, @menu.get_players
|
12
|
-
game_state =
|
14
|
+
game_state = TicTacToes::Core::GameState.new(board, players, @history)
|
13
15
|
|
14
16
|
take_turn(game_state) until game_state.game_over?
|
15
17
|
end_game(game_state)
|
@@ -21,7 +23,7 @@ module TicTacToes
|
|
21
23
|
@io.draw_board(game_state.board)
|
22
24
|
@io.thinking_notification if game_state.current_player.needs_to_think
|
23
25
|
|
24
|
-
move = game_state.current_player.place_and_return_move(game_state
|
26
|
+
move = game_state.current_player.place_and_return_move(game_state)
|
25
27
|
game_state.turn_over(move)
|
26
28
|
end
|
27
29
|
|
@@ -14,10 +14,18 @@ module TicTacToes
|
|
14
14
|
@history.record_board_size(@board.size)
|
15
15
|
end
|
16
16
|
|
17
|
+
def place_move(space)
|
18
|
+
@board.place(current_player, space)
|
19
|
+
end
|
20
|
+
|
17
21
|
def current_player
|
18
22
|
@players.first
|
19
23
|
end
|
20
24
|
|
25
|
+
def next_player
|
26
|
+
@players[1]
|
27
|
+
end
|
28
|
+
|
21
29
|
def turn_over(move)
|
22
30
|
@history.record_move(move)
|
23
31
|
@players.rotate!
|
@@ -4,14 +4,18 @@ module TicTacToes
|
|
4
4
|
module Core
|
5
5
|
module MoveStrategies
|
6
6
|
module HardAI
|
7
|
-
|
7
|
+
ALPHA, BETA = -1, 1
|
8
|
+
|
9
|
+
def self.move(game_state)
|
10
|
+
board = game_state.board
|
11
|
+
|
8
12
|
return 0 if nine_board_first_move?(board)
|
9
13
|
return second_move(board) if nine_board_second_move?(board)
|
10
14
|
|
11
15
|
open_spaces = Hash[board.open_spaces.map { |space| [space, nil] }]
|
12
16
|
|
13
17
|
open_spaces.each do |space, score|
|
14
|
-
score = minimax(
|
18
|
+
score = minimax(generate_game_state(space, game_state), :min, ALPHA, BETA)
|
15
19
|
open_spaces[space] = score
|
16
20
|
end
|
17
21
|
|
@@ -21,43 +25,39 @@ module TicTacToes
|
|
21
25
|
|
22
26
|
private
|
23
27
|
|
24
|
-
def self.minimax(
|
25
|
-
return score(
|
28
|
+
def self.minimax(game_state, current_player, alpha, beta)
|
29
|
+
return score(game_state) if game_state.game_over?
|
26
30
|
|
27
31
|
if current_player == :max
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
best_score = [best_score, score].max
|
32
|
+
game_state.board.open_spaces.each do |space|
|
33
|
+
alpha = [alpha, minimax(generate_game_state(space, game_state), :min, alpha, beta)].max
|
34
|
+
break if beta <= alpha
|
32
35
|
end
|
33
|
-
|
34
|
-
|
36
|
+
alpha
|
35
37
|
elsif current_player == :min
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
best_score = [best_score, score].min
|
38
|
+
game_state.board.open_spaces.each do |space|
|
39
|
+
beta = [beta, minimax(generate_game_state(space, game_state), :max, alpha, beta)].min
|
40
|
+
break if beta <= alpha
|
40
41
|
end
|
41
|
-
|
42
|
+
beta
|
42
43
|
end
|
43
44
|
end
|
44
45
|
|
45
|
-
def self.
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
def self.generate_game_state(space, game_state)
|
47
|
+
new_game_state = Marshal.load(Marshal.dump(game_state))
|
48
|
+
new_game_state.place_move(space)
|
49
|
+
new_game_state.turn_over([])
|
50
|
+
new_game_state
|
49
51
|
end
|
50
52
|
|
51
|
-
def self.score(
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
def self.score(game_state)
|
54
|
+
winner = game_state.determine_winner
|
55
|
+
if winner.nil?
|
56
|
+
0
|
57
|
+
elsif winner.move_strategy == self
|
56
58
|
1
|
57
|
-
when opponent_token
|
58
|
-
-1
|
59
59
|
else
|
60
|
-
|
60
|
+
-1
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
@@ -6,13 +6,13 @@ module TicTacToes
|
|
6
6
|
@io = io
|
7
7
|
end
|
8
8
|
|
9
|
-
def move(
|
9
|
+
def move(game_state)
|
10
10
|
@io.move_solicitation
|
11
11
|
|
12
12
|
Integer(@io.solicit_input)
|
13
13
|
rescue ArgumentError
|
14
14
|
@io.not_an_integer_error
|
15
|
-
move(
|
15
|
+
move(game_state)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -5,9 +5,9 @@ module TicTacToes
|
|
5
5
|
module Core
|
6
6
|
module MoveStrategies
|
7
7
|
module MediumAI
|
8
|
-
def self.move(
|
8
|
+
def self.move(game_state)
|
9
9
|
move_strategy = [EasyAI, HardAI, HardAI].sample
|
10
|
-
move_strategy.move(
|
10
|
+
move_strategy.move(game_state)
|
11
11
|
end
|
12
12
|
end
|
13
13
|
end
|
@@ -10,10 +10,10 @@ module TicTacToes
|
|
10
10
|
@io = io
|
11
11
|
end
|
12
12
|
|
13
|
-
def place_and_return_move(
|
13
|
+
def place_and_return_move(game_state)
|
14
14
|
loop do
|
15
|
-
space = @move_strategy.move(
|
16
|
-
break [@token, space] if
|
15
|
+
space = @move_strategy.move(game_state)
|
16
|
+
break [@token, space] if game_state.place_move(space)
|
17
17
|
@io.invalid_move_error
|
18
18
|
end
|
19
19
|
end
|
@@ -4,7 +4,7 @@ module TicTacToes
|
|
4
4
|
def self.make_move(game_state, move, listener)
|
5
5
|
unless move.nil?
|
6
6
|
move = move.to_i
|
7
|
-
game_state.
|
7
|
+
game_state.place_move(move)
|
8
8
|
end
|
9
9
|
|
10
10
|
if game_state.game_over?
|
@@ -13,7 +13,7 @@ module TicTacToes
|
|
13
13
|
end
|
14
14
|
|
15
15
|
game_state.turn_over(move)
|
16
|
-
game_state.current_player.place_and_return_move(game_state
|
16
|
+
game_state.current_player.place_and_return_move(game_state)
|
17
17
|
|
18
18
|
if game_state.game_over?
|
19
19
|
tell_listener_game_is_over(game_state, listener)
|
@@ -26,13 +26,13 @@ module TicTacToes
|
|
26
26
|
private
|
27
27
|
|
28
28
|
def self.tell_listener_game_is_over(game_state, listener)
|
29
|
-
|
29
|
+
winning_player = game_state.determine_winner
|
30
30
|
|
31
|
-
case
|
31
|
+
case winning_player
|
32
32
|
when nil
|
33
33
|
listener.game_ended_in_draw(game_state)
|
34
34
|
else
|
35
|
-
listener.game_ended_in_winner(game_state,
|
35
|
+
listener.game_ended_in_winner(game_state, winning_player.token)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
data/lib/tic_tac_toes/version.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'tic_tac_toes/command_line/menu'
|
2
|
-
require 'tic_tac_toes/core/board_factory'
|
3
2
|
require 'tic_tac_toes/core/player_factory'
|
4
3
|
require 'tic_tac_toes/core/io'
|
5
4
|
require 'tic_tac_toes/core/move_strategies/human'
|
@@ -7,9 +6,8 @@ require 'tic_tac_toes/core/move_strategies/medium_ai'
|
|
7
6
|
|
8
7
|
describe TicTacToes::CommandLine::Menu do
|
9
8
|
let(:io) { double }
|
10
|
-
let(:board_factory) { TicTacToes::Core::BoardFactory.new }
|
11
9
|
let(:player_factory) { TicTacToes::Core::PlayerFactory.new(io) }
|
12
|
-
let(:menu) { TicTacToes::CommandLine::Menu.new(io,
|
10
|
+
let(:menu) { TicTacToes::CommandLine::Menu.new(io, player_factory) }
|
13
11
|
|
14
12
|
describe '#get_board' do
|
15
13
|
let(:invalid_row_size) { 11 }
|
@@ -1,38 +1,4 @@
|
|
1
1
|
require 'tic_tac_toes/command_line/runner'
|
2
2
|
|
3
3
|
describe TicTacToes::CommandLine::Runner do
|
4
|
-
describe '#run' do
|
5
|
-
let(:game_state) { double(board: 'board', players: 'players', game_over: true) }
|
6
|
-
|
7
|
-
let(:io) { double(draw_board: true,
|
8
|
-
thinking_notification: true,
|
9
|
-
game_over_notification: true) }
|
10
|
-
let(:menu) { double(get_board: true,
|
11
|
-
get_players: true) }
|
12
|
-
let(:game_state) { double(board: 'board',
|
13
|
-
players: 'players',
|
14
|
-
game_over: true,
|
15
|
-
game_over?: true,
|
16
|
-
determine_winner: true) }
|
17
|
-
let(:game_state_factory) { double(generate_game_state: game_state) }
|
18
|
-
|
19
|
-
let(:runner) { TicTacToes::CommandLine::Runner.new(io, menu, game_state_factory) }
|
20
|
-
|
21
|
-
it 'gets an initial game state' do
|
22
|
-
expect(game_state_factory).to receive(:generate_game_state)
|
23
|
-
runner.run
|
24
|
-
end
|
25
|
-
|
26
|
-
it 'takes turns until the game is over' do
|
27
|
-
allow(game_state).to receive(:game_over?).and_return(false, true)
|
28
|
-
|
29
|
-
expect(runner).to receive(:take_turn).once
|
30
|
-
runner.run
|
31
|
-
end
|
32
|
-
|
33
|
-
it 'ends the game when the game is over' do
|
34
|
-
expect(runner).to receive(:end_game)
|
35
|
-
runner.run
|
36
|
-
end
|
37
|
-
end
|
38
4
|
end
|
@@ -13,6 +13,41 @@ describe TicTacToes::Core::GameState do
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
+
describe '#place_move' do
|
17
|
+
let(:x) { double("human player", token: "x") }
|
18
|
+
let(:o) { double("computer player", token: "o") }
|
19
|
+
let(:players) { [x, o] }
|
20
|
+
|
21
|
+
let(:history) { double(record_board_size: true) }
|
22
|
+
|
23
|
+
it 'returns nil if the board’s space is not nil' do
|
24
|
+
board = TicTacToes::TestBoardGenerator.generate([ x, nil, nil,
|
25
|
+
nil, nil, nil,
|
26
|
+
nil, nil, nil])
|
27
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
28
|
+
|
29
|
+
expect(game_state.place_move(0)).to eq(nil)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'returns nil if it isn’t in its board’s spaces range' do
|
33
|
+
board = TicTacToes::TestBoardGenerator.generate([nil, nil, nil,
|
34
|
+
nil, nil, nil,
|
35
|
+
nil, nil, nil])
|
36
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
37
|
+
expect(game_state.place_move(9)).to eq(nil)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'places the current player at the space if the space is nil and in the board’s spaces range' do
|
41
|
+
board = TicTacToes::TestBoardGenerator.generate([nil, nil, nil,
|
42
|
+
nil, nil, nil,
|
43
|
+
nil, nil, nil])
|
44
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
45
|
+
|
46
|
+
game_state.place_move(0)
|
47
|
+
expect(game_state.board.space(0)).to eq(game_state.current_player)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
16
51
|
describe '#current_player' do
|
17
52
|
it 'returns the first item of its players array' do
|
18
53
|
history = double(record_board_size: true)
|
@@ -24,6 +59,17 @@ describe TicTacToes::Core::GameState do
|
|
24
59
|
end
|
25
60
|
end
|
26
61
|
|
62
|
+
describe '#next_player' do
|
63
|
+
it 'returns the second item of its players array' do
|
64
|
+
history = double(record_board_size: true)
|
65
|
+
players = ['first_player', 'second_player']
|
66
|
+
game_state = TicTacToes::Core::GameState.new('board', players, history)
|
67
|
+
|
68
|
+
next_player = game_state.next_player
|
69
|
+
expect(next_player).to eq('second_player')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
27
73
|
describe '#turn_over' do
|
28
74
|
it 'records the last move' do
|
29
75
|
move = double
|
@@ -47,21 +93,21 @@ describe TicTacToes::Core::GameState do
|
|
47
93
|
|
48
94
|
describe '#game_over' do
|
49
95
|
it 'records the winner' do
|
50
|
-
|
96
|
+
winning_player = double
|
51
97
|
history = double(record_board_size: true, persist: true)
|
52
98
|
game_state = TicTacToes::Core::GameState.new('board', 'players', history)
|
53
99
|
|
54
|
-
expect(history).to receive(:record_winner).with(
|
55
|
-
game_state.game_over(
|
100
|
+
expect(history).to receive(:record_winner).with(winning_player)
|
101
|
+
game_state.game_over(winning_player)
|
56
102
|
end
|
57
103
|
|
58
104
|
it 'persists its history' do
|
59
|
-
|
105
|
+
winning_player = double(token: 'x')
|
60
106
|
history = double(record_board_size: true, record_winner: true)
|
61
107
|
game_state = TicTacToes::Core::GameState.new('board', 'players', history)
|
62
108
|
|
63
109
|
expect(history).to receive(:persist)
|
64
|
-
game_state.game_over(
|
110
|
+
game_state.game_over(winning_player)
|
65
111
|
end
|
66
112
|
end
|
67
113
|
|
@@ -76,6 +122,7 @@ describe TicTacToes::Core::GameState do
|
|
76
122
|
board = TicTacToes::TestBoardGenerator.generate([x, x, o,
|
77
123
|
o, o, nil,
|
78
124
|
x, o, x])
|
125
|
+
|
79
126
|
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
80
127
|
expect(game_state.game_over?).to be false
|
81
128
|
end
|
@@ -113,7 +160,8 @@ describe TicTacToes::Core::GameState do
|
|
113
160
|
winning_token = "o"
|
114
161
|
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
115
162
|
|
116
|
-
|
163
|
+
winning_player = game_state.determine_winner
|
164
|
+
expect(winning_player.token).to eq(winning_token)
|
117
165
|
end
|
118
166
|
|
119
167
|
it "returns nil if there is not a winner" do
|
@@ -125,5 +173,4 @@ describe TicTacToes::Core::GameState do
|
|
125
173
|
expect(game_state.determine_winner).to be_nil
|
126
174
|
end
|
127
175
|
end
|
128
|
-
|
129
176
|
end
|
@@ -23,11 +23,11 @@ describe TicTacToes::Core::History do
|
|
23
23
|
end
|
24
24
|
|
25
25
|
describe '#record_winner' do
|
26
|
-
it "records the passed winner" do
|
27
|
-
|
26
|
+
it "records the token of the passed winner" do
|
27
|
+
winner = double(token: 'O')
|
28
28
|
|
29
|
-
history.record_winner(
|
30
|
-
expect(history.winner).to eq(token)
|
29
|
+
history.record_winner(winner)
|
30
|
+
expect(history.winner).to eq(winner.token)
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
@@ -1,18 +1,25 @@
|
|
1
1
|
require 'tic_tac_toes/test_board_generator'
|
2
|
+
require 'tic_tac_toes/core/game_state'
|
2
3
|
require 'tic_tac_toes/core/move_strategies/easy_ai'
|
4
|
+
require 'tic_tac_toes/core/player_factory'
|
3
5
|
|
4
6
|
describe TicTacToes::Core::MoveStrategies::EasyAI do
|
5
7
|
describe '#move' do
|
6
|
-
let(:players) { double("players") }
|
7
|
-
let(:easy_ai) { TicTacToes::Core::MoveStrategies::EasyAI }
|
8
|
-
|
9
8
|
it "returns a randomly-selected valid move" do
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
player_factory = TicTacToes::Core::PlayerFactory.new('unused_io')
|
10
|
+
x = player_factory.generate_human_player('x')
|
11
|
+
o = player_factory.generate_computer_player('o', :hard)
|
12
|
+
players = [x, o]
|
13
|
+
|
14
|
+
board = TicTacToes::TestBoardGenerator.generate([ o, nil, nil,
|
15
|
+
nil, x, nil,
|
16
|
+
nil, x, nil])
|
17
|
+
|
18
|
+
history = double(record_board_size: true)
|
19
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
13
20
|
valid_moves = [1, 2, 3, 5, 6, 8]
|
14
21
|
|
15
|
-
move =
|
22
|
+
move = TicTacToes::Core::MoveStrategies::EasyAI.move(game_state)
|
16
23
|
expect(valid_moves).to include(move)
|
17
24
|
end
|
18
25
|
end
|
@@ -1,129 +1,144 @@
|
|
1
1
|
require 'tic_tac_toes/test_board_generator'
|
2
2
|
require 'tic_tac_toes/core/move_strategies/hard_ai'
|
3
|
+
require 'tic_tac_toes/core/game_state'
|
3
4
|
require 'tic_tac_toes/core/player'
|
4
5
|
|
5
6
|
describe TicTacToes::Core::MoveStrategies::HardAI do
|
7
|
+
let(:alpha) { TicTacToes::Core::MoveStrategies::HardAI::ALPHA }
|
8
|
+
let(:beta) { TicTacToes::Core::MoveStrategies::HardAI::BETA }
|
6
9
|
let(:hard_ai) { TicTacToes::Core::MoveStrategies::HardAI }
|
7
10
|
let(:x) { TicTacToes::Core::Player.new("human", "x", false, "io") }
|
8
11
|
let(:o) { TicTacToes::Core::Player.new(hard_ai, "o", true, "io") }
|
9
12
|
let(:players) { [o, x] }
|
10
|
-
|
13
|
+
let(:history) { TicTacToes::UI::NullHistory.new }
|
11
14
|
|
12
15
|
describe '#move' do
|
13
|
-
it
|
16
|
+
it 'returns the best move' do
|
14
17
|
board = TicTacToes::TestBoardGenerator.generate([x, nil, nil,
|
15
18
|
o, o, nil,
|
16
19
|
x, nil, x])
|
20
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
17
21
|
best_move = 5
|
18
22
|
|
19
|
-
expect(hard_ai.move(
|
23
|
+
expect(hard_ai.move(game_state)).to eql(best_move)
|
20
24
|
end
|
21
25
|
|
22
|
-
context
|
26
|
+
context 'when playing on a 3x3 board' do
|
23
27
|
it 'returns 0 when making the first move' do
|
24
28
|
board = TicTacToes::TestBoardGenerator.generate([nil, nil, nil,
|
25
29
|
nil, nil, nil,
|
26
30
|
nil, nil, nil])
|
31
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
27
32
|
|
28
|
-
expect(hard_ai.move(
|
33
|
+
expect(hard_ai.move(game_state)).to eq(0)
|
29
34
|
end
|
30
35
|
|
31
|
-
it
|
36
|
+
it 'returns 4 when the opponent’s first move was a corner' do
|
32
37
|
board = TicTacToes::TestBoardGenerator.generate([nil, nil, nil,
|
33
38
|
nil, nil, nil,
|
34
39
|
nil, nil, x])
|
40
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
35
41
|
|
36
|
-
expect(hard_ai.move(
|
42
|
+
expect(hard_ai.move(game_state)).to eq(4)
|
37
43
|
end
|
38
44
|
|
39
|
-
it
|
45
|
+
it 'returns 4 when the opponent’s first move was an edge' do
|
40
46
|
board = TicTacToes::TestBoardGenerator.generate([nil, nil, nil,
|
41
47
|
nil, nil, x,
|
42
48
|
nil, nil, nil])
|
49
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
43
50
|
|
44
|
-
expect(hard_ai.move(
|
51
|
+
expect(hard_ai.move(game_state)).to eq(4)
|
45
52
|
end
|
46
53
|
|
47
|
-
it
|
54
|
+
it 'returns 0 when the opponent’s first move was the center' do
|
48
55
|
board = TicTacToes::TestBoardGenerator.generate([nil, nil, nil,
|
49
56
|
nil, x, nil,
|
50
57
|
nil, nil, nil])
|
58
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
51
59
|
|
52
|
-
expect(hard_ai.move(
|
60
|
+
expect(hard_ai.move(game_state)).to eq(0)
|
53
61
|
end
|
54
62
|
end
|
55
63
|
end
|
56
64
|
|
57
65
|
|
58
66
|
describe '#minimax' do
|
59
|
-
it
|
67
|
+
it 'returns the correct score for a pre-win board' do
|
60
68
|
board = TicTacToes::TestBoardGenerator.generate([x, nil, nil,
|
61
69
|
o, o, nil,
|
62
70
|
x, nil, x])
|
71
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
63
72
|
win_score = 1
|
64
73
|
|
65
|
-
expect(hard_ai.minimax(
|
74
|
+
expect(hard_ai.minimax(game_state, :max, alpha, beta)).to eql(win_score)
|
66
75
|
end
|
67
76
|
|
68
|
-
it
|
77
|
+
it 'returns the correct score for a pre-loss board' do
|
69
78
|
board = TicTacToes::TestBoardGenerator.generate([ o, o, x,
|
70
79
|
nil, nil, nil,
|
71
80
|
x, nil, x])
|
81
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
72
82
|
loss_score = -1
|
73
83
|
|
74
|
-
expect(hard_ai.minimax(
|
84
|
+
expect(hard_ai.minimax(game_state, :max, alpha, beta)).to eql(loss_score)
|
75
85
|
end
|
76
86
|
|
77
|
-
it
|
87
|
+
it 'returns the correct score for a pre-draw board' do
|
78
88
|
board = TicTacToes::TestBoardGenerator.generate([x, x, o,
|
79
89
|
o, nil, x,
|
80
90
|
x, o, x])
|
91
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
81
92
|
draw_score = 0
|
82
93
|
|
83
|
-
expect(hard_ai.minimax(
|
94
|
+
expect(hard_ai.minimax(game_state, :max, alpha, beta)).to eql(draw_score)
|
84
95
|
end
|
85
96
|
end
|
86
97
|
|
87
98
|
|
88
|
-
describe '#
|
89
|
-
it
|
90
|
-
|
99
|
+
describe '#generate_game_state' do
|
100
|
+
it 'returns a game state based on a space and an existing game state' do
|
101
|
+
space, token = 3, 'o'
|
91
102
|
board = TicTacToes::TestBoardGenerator.generate([ x, nil, nil,
|
92
103
|
nil, o, nil,
|
93
104
|
x, nil, nil])
|
105
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
94
106
|
|
95
|
-
|
96
|
-
expect(
|
107
|
+
new_game_state = hard_ai.generate_game_state(space, game_state)
|
108
|
+
expect(new_game_state.board.space(space).token).to eq(token)
|
97
109
|
end
|
98
110
|
end
|
99
111
|
|
100
112
|
|
101
113
|
describe '#score' do
|
102
|
-
it
|
114
|
+
it 'returns the correct score when HardAI has won' do
|
103
115
|
board = TicTacToes::TestBoardGenerator.generate([ o, nil, nil,
|
104
116
|
nil, o, nil,
|
105
117
|
nil, nil, o])
|
118
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
106
119
|
win_score = 1
|
107
120
|
|
108
|
-
expect(hard_ai.score(
|
121
|
+
expect(hard_ai.score(game_state)).to eq(win_score)
|
109
122
|
end
|
110
123
|
|
111
|
-
it
|
124
|
+
it 'returns the correct score when no one has won' do
|
112
125
|
board = TicTacToes::TestBoardGenerator.generate([o, o, x,
|
113
126
|
x, x, o,
|
114
127
|
o, x, o])
|
128
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
115
129
|
draw_score = 0
|
116
130
|
|
117
|
-
expect(hard_ai.score(
|
131
|
+
expect(hard_ai.score(game_state)).to eql(draw_score)
|
118
132
|
end
|
119
133
|
|
120
|
-
it
|
134
|
+
it 'returns the correct score when the opponent has won' do
|
121
135
|
board = TicTacToes::TestBoardGenerator.generate([ x, nil, nil,
|
122
136
|
nil, x, nil,
|
123
137
|
nil, nil, x])
|
138
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
124
139
|
loss_score = -1
|
125
140
|
|
126
|
-
expect(hard_ai.score(
|
141
|
+
expect(hard_ai.score(game_state)).to eql(loss_score)
|
127
142
|
end
|
128
143
|
end
|
129
144
|
end
|
@@ -2,29 +2,29 @@ require 'tic_tac_toes/core/move_strategies/human'
|
|
2
2
|
|
3
3
|
describe TicTacToes::Core::MoveStrategies::Human do
|
4
4
|
describe '#move' do
|
5
|
-
let(:io) { double(
|
5
|
+
let(:io) { double('io', solicit_input: 0, move_solicitation: true, not_an_integer_error: true) }
|
6
6
|
let(:human) { TicTacToes::Core::MoveStrategies::Human.new(io) }
|
7
7
|
|
8
|
-
it
|
8
|
+
it 'displays a move solicitation' do
|
9
9
|
expect(io).to receive(:move_solicitation)
|
10
|
-
human.move(
|
10
|
+
human.move('game_state')
|
11
11
|
end
|
12
12
|
|
13
13
|
context 'when given not-integer-like input' do
|
14
|
-
let(:not_integer_like) {
|
15
|
-
let(:integer_like) {
|
14
|
+
let(:not_integer_like) { 'string' }
|
15
|
+
let(:integer_like) { '100' }
|
16
16
|
|
17
|
-
it
|
17
|
+
it 'displays a not an integer error' do
|
18
18
|
allow(io).to receive(:solicit_input).and_return(not_integer_like, integer_like)
|
19
19
|
|
20
20
|
expect(io).to receive(:not_an_integer_error).once
|
21
|
-
human.move(
|
21
|
+
human.move('game_state')
|
22
22
|
end
|
23
23
|
|
24
|
-
it
|
24
|
+
it 'only returns a move (converted to integer) once it gets integer-like input' do
|
25
25
|
allow(io).to receive(:solicit_input).and_return(not_integer_like, integer_like)
|
26
26
|
|
27
|
-
expect(human.move(
|
27
|
+
expect(human.move('game_state')).to eq(100)
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
@@ -1,20 +1,25 @@
|
|
1
1
|
require 'tic_tac_toes/test_board_generator'
|
2
|
+
require 'tic_tac_toes/core/game_state'
|
2
3
|
require 'tic_tac_toes/core/move_strategies/medium_ai'
|
4
|
+
require 'tic_tac_toes/core/player_factory'
|
3
5
|
|
4
6
|
describe TicTacToes::Core::MoveStrategies::MediumAI do
|
5
|
-
let(:medium_ai) { TicTacToes::Core::MoveStrategies::MediumAI }
|
6
|
-
let(:x) { TicTacToes::Core::Player.new("human", "x", false, "io") }
|
7
|
-
let(:o) { TicTacToes::Core::Player.new(medium_ai, "o", true, "io") }
|
8
|
-
let(:players) { [x, o] }
|
9
|
-
|
10
7
|
describe '#move' do
|
11
8
|
it "returns a valid move (based on either EasyAI or HardAI)" do
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
player_factory = TicTacToes::Core::PlayerFactory.new('unused_io')
|
10
|
+
x = player_factory.generate_human_player('x')
|
11
|
+
o = player_factory.generate_computer_player('o', :hard)
|
12
|
+
players = [x, o]
|
13
|
+
|
14
|
+
board = TicTacToes::TestBoardGenerator.generate([ o, o, x,
|
15
|
+
nil, x, nil,
|
16
|
+
nil, x, nil])
|
17
|
+
|
18
|
+
history = TicTacToes::UI::NullHistory.new
|
19
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
15
20
|
valid_moves = [3, 5, 6, 8]
|
16
21
|
|
17
|
-
move =
|
22
|
+
move = TicTacToes::Core::MoveStrategies::MediumAI.move(game_state)
|
18
23
|
expect(valid_moves).to include(move)
|
19
24
|
end
|
20
25
|
end
|
@@ -2,31 +2,34 @@ require 'tic_tac_toes/core/board'
|
|
2
2
|
require 'tic_tac_toes/core/player'
|
3
3
|
|
4
4
|
describe TicTacToes::Core::Player do
|
5
|
-
let(:board)
|
6
|
-
let(:io) { double("io", invalid_move_error: true) }
|
7
|
-
let(:players) { double("players") }
|
5
|
+
let(:board) { TicTacToes::Core::Board.new(row_size: 3) }
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
let(:io) { double(invalid_move_error: true) }
|
8
|
+
|
9
|
+
let(:move_strategy) { double }
|
10
|
+
let(:x) { TicTacToes::Core::Player.new(move_strategy, 'x', false, io) }
|
11
|
+
let(:o) { double }
|
12
|
+
let(:players) { [x, o] }
|
13
|
+
|
14
|
+
let(:history) { double(record_board_size: true) }
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
16
|
+
let(:game_state) { TicTacToes::Core::GameState.new(board, players, history) }
|
17
|
+
|
18
|
+
describe '#place_and_return_move' do
|
19
|
+
it 'only places/returns a move once it receives a valid move' do
|
17
20
|
invalid_space, valid_space = 9, 0
|
18
21
|
allow(move_strategy).to receive(:move).and_return(invalid_space, valid_space)
|
19
22
|
|
20
|
-
|
21
|
-
expect(board.space(valid_space)).to eq(
|
23
|
+
x.place_and_return_move(game_state)
|
24
|
+
expect(board.space(valid_space)).to eq(x)
|
22
25
|
end
|
23
26
|
|
24
|
-
it
|
27
|
+
it 'displays an invalid move error when given an invalid move' do
|
25
28
|
invalid_space, valid_space = 9, 0
|
26
29
|
allow(move_strategy).to receive(:move).and_return(invalid_space, valid_space)
|
27
30
|
|
28
31
|
expect(io).to receive(:invalid_move_error)
|
29
|
-
|
32
|
+
x.place_and_return_move(game_state)
|
30
33
|
end
|
31
34
|
end
|
32
35
|
end
|
@@ -86,13 +86,14 @@ describe TicTacToes::Core::Rules do
|
|
86
86
|
|
87
87
|
|
88
88
|
describe '#determine_winner' do
|
89
|
-
it "returns the winning
|
89
|
+
it "returns the winning player when there is a winner" do
|
90
90
|
board = TicTacToes::TestBoardGenerator.generate([ o, nil, nil,
|
91
91
|
nil, o, nil,
|
92
92
|
nil, nil, o])
|
93
93
|
winning_token = "o"
|
94
94
|
|
95
|
-
|
95
|
+
winning_player = rules.determine_winner(board, players)
|
96
|
+
expect(winning_player.token).to eql(winning_token)
|
96
97
|
end
|
97
98
|
|
98
99
|
it "returns nil if there is not a winner" do
|
@@ -5,12 +5,12 @@ require 'tic_tac_toes/ui/adapter'
|
|
5
5
|
|
6
6
|
describe TicTacToes::UI::Adapter do
|
7
7
|
describe '#make_move' do
|
8
|
-
let(:history)
|
9
|
-
|
8
|
+
let(:history) { TicTacToes::UI::NullHistory.new }
|
9
|
+
|
10
10
|
player_factory = TicTacToes::Core::PlayerFactory.new('unused_io')
|
11
|
-
let(:x)
|
12
|
-
let(:o)
|
13
|
-
let(:players)
|
11
|
+
let(:x) { player_factory.generate_human_player('x') }
|
12
|
+
let(:o) { player_factory.generate_computer_player('o', :hard) }
|
13
|
+
let(:players) { [x, o] }
|
14
14
|
|
15
15
|
context 'when the game is still in progress' do
|
16
16
|
it 'sends its listener #moves_were_made with the updated game state' do
|
@@ -25,8 +25,8 @@ describe TicTacToes::UI::Adapter do
|
|
25
25
|
TicTacToes::UI::Adapter.make_move(game_state, move, listener)
|
26
26
|
|
27
27
|
first_move, second_move = 2, 1
|
28
|
-
|
29
|
-
|
28
|
+
board.place(x, first_move)
|
29
|
+
board.place(o, second_move)
|
30
30
|
expect(listener).to have_received(:moves_were_made).with(game_state)
|
31
31
|
end
|
32
32
|
end
|
@@ -44,7 +44,7 @@ describe TicTacToes::UI::Adapter do
|
|
44
44
|
allow(listener).to receive(:game_ended_in_winner)
|
45
45
|
TicTacToes::UI::Adapter.make_move(game_state, move, listener)
|
46
46
|
|
47
|
-
|
47
|
+
board.place(x, 2)
|
48
48
|
expect(listener).to have_received(:game_ended_in_winner).with(game_state, winning_token)
|
49
49
|
end
|
50
50
|
end
|
@@ -61,7 +61,7 @@ describe TicTacToes::UI::Adapter do
|
|
61
61
|
allow(listener).to receive(:game_ended_in_draw)
|
62
62
|
TicTacToes::UI::Adapter.make_move(game_state, move, listener)
|
63
63
|
|
64
|
-
|
64
|
+
board.place(x, 8)
|
65
65
|
expect(listener).to have_received(:game_ended_in_draw).with(game_state)
|
66
66
|
end
|
67
67
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tic_tac_toes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Spatafora
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-07-
|
11
|
+
date: 2014-07-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -90,9 +90,7 @@ files:
|
|
90
90
|
- lib/tic_tac_toes/command_line/prompt.rb
|
91
91
|
- lib/tic_tac_toes/command_line/runner.rb
|
92
92
|
- lib/tic_tac_toes/core/board.rb
|
93
|
-
- lib/tic_tac_toes/core/board_factory.rb
|
94
93
|
- lib/tic_tac_toes/core/game_state.rb
|
95
|
-
- lib/tic_tac_toes/core/game_state_factory.rb
|
96
94
|
- lib/tic_tac_toes/core/history.rb
|
97
95
|
- lib/tic_tac_toes/core/io.rb
|
98
96
|
- lib/tic_tac_toes/core/move_strategies/easy_ai.rb
|
@@ -111,9 +109,7 @@ files:
|
|
111
109
|
- lib/tic_tac_toes/version.rb
|
112
110
|
- spec/tic_tac_toes/command_line/menu_spec.rb
|
113
111
|
- spec/tic_tac_toes/command_line/runner_spec.rb
|
114
|
-
- spec/tic_tac_toes/core/board_factory_spec.rb
|
115
112
|
- spec/tic_tac_toes/core/board_spec.rb
|
116
|
-
- spec/tic_tac_toes/core/game_state_factory_spec.rb
|
117
113
|
- spec/tic_tac_toes/core/game_state_spec.rb
|
118
114
|
- spec/tic_tac_toes/core/history_spec.rb
|
119
115
|
- spec/tic_tac_toes/core/io_spec.rb
|
@@ -157,9 +153,7 @@ summary: The game Tic-tac-toe, featuring an unbeatable AI
|
|
157
153
|
test_files:
|
158
154
|
- spec/tic_tac_toes/command_line/menu_spec.rb
|
159
155
|
- spec/tic_tac_toes/command_line/runner_spec.rb
|
160
|
-
- spec/tic_tac_toes/core/board_factory_spec.rb
|
161
156
|
- spec/tic_tac_toes/core/board_spec.rb
|
162
|
-
- spec/tic_tac_toes/core/game_state_factory_spec.rb
|
163
157
|
- spec/tic_tac_toes/core/game_state_spec.rb
|
164
158
|
- spec/tic_tac_toes/core/history_spec.rb
|
165
159
|
- spec/tic_tac_toes/core/io_spec.rb
|
@@ -1,15 +0,0 @@
|
|
1
|
-
require 'tic_tac_toes/core/game_state'
|
2
|
-
|
3
|
-
module TicTacToes
|
4
|
-
module Core
|
5
|
-
class GameStateFactory
|
6
|
-
def initialize(history)
|
7
|
-
@history = history
|
8
|
-
end
|
9
|
-
|
10
|
-
def generate_game_state(board, players)
|
11
|
-
GameState.new(board, players, @history)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
require 'tic_tac_toes/core/board_factory'
|
2
|
-
|
3
|
-
describe TicTacToes::Core::BoardFactory do
|
4
|
-
describe '#generate_board' do
|
5
|
-
it 'returns a board object generated from a row size' do
|
6
|
-
board_factory = TicTacToes::Core::BoardFactory.new
|
7
|
-
row_size = 5
|
8
|
-
|
9
|
-
board = board_factory.generate_board(row_size)
|
10
|
-
expect(board.row_size).to eq(row_size)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
require 'tic_tac_toes/core/game_state_factory'
|
2
|
-
|
3
|
-
describe TicTacToes::Core::GameStateFactory do
|
4
|
-
describe '#generate_game_state' do
|
5
|
-
it 'returns a game state object generated from a board object and player array' do
|
6
|
-
history = double(record_board_size: true)
|
7
|
-
game_state_factory = TicTacToes::Core::GameStateFactory.new(history)
|
8
|
-
board, players = double(size: 3), double
|
9
|
-
|
10
|
-
game_state = game_state_factory.generate_game_state(board, players)
|
11
|
-
expect(game_state.board).to eq(board)
|
12
|
-
expect(game_state.players).to eq(players)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|