tic_tac_toes 0.1.1 → 0.1.2
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 +4 -4
- data/lib/tic_tac_toes/core/move_strategies/hard_ai.rb +10 -10
- data/lib/tic_tac_toes/ui/adapter.rb +39 -0
- data/lib/tic_tac_toes/version.rb +1 -1
- data/spec/tic_tac_toes/core/move_strategies/hard_ai_spec.rb +14 -11
- data/spec/tic_tac_toes/ui/adapter_spec.rb +53 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e7dac5d68711dc5bafd138607af1f05cb9753c5
|
4
|
+
data.tar.gz: 20480d443fcc914f9f844aa40db5a90b4a52300f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d6b6478a8b3c54643825073c4106336c210f715c119570dbbac80caf89f6cd8a8dbe1b1ad0758557326603e115b4e987aa0792fbe318ebb8680ce30035bc1b7
|
7
|
+
data.tar.gz: b3bbd7b5bc6dc32e8fb175bc2d66160e4f8177be37ab301ac9ef1d492b58a5c095e325ef47f66b4f1d10b1fafd7cb7a7451b2ad9eacddb4c4f52a25f0e23afda
|
data/Gemfile.lock
CHANGED
@@ -14,14 +14,14 @@ GEM
|
|
14
14
|
rspec-core (~> 3.0.0)
|
15
15
|
rspec-expectations (~> 3.0.0)
|
16
16
|
rspec-mocks (~> 3.0.0)
|
17
|
-
rspec-core (3.0.
|
17
|
+
rspec-core (3.0.4)
|
18
18
|
rspec-support (~> 3.0.0)
|
19
|
-
rspec-expectations (3.0.
|
19
|
+
rspec-expectations (3.0.4)
|
20
20
|
diff-lcs (>= 1.2.0, < 2.0)
|
21
21
|
rspec-support (~> 3.0.0)
|
22
|
-
rspec-mocks (3.0.
|
22
|
+
rspec-mocks (3.0.4)
|
23
23
|
rspec-support (~> 3.0.0)
|
24
|
-
rspec-support (3.0.
|
24
|
+
rspec-support (3.0.4)
|
25
25
|
|
26
26
|
PLATFORMS
|
27
27
|
ruby
|
@@ -4,7 +4,7 @@ module TicTacToes
|
|
4
4
|
module Core
|
5
5
|
module MoveStrategies
|
6
6
|
module HardAI
|
7
|
-
ALPHA, BETA = -
|
7
|
+
ALPHA, BETA, DEPTH = -100, 100, 0
|
8
8
|
|
9
9
|
def self.move(game_state)
|
10
10
|
board = game_state.board
|
@@ -15,7 +15,7 @@ module TicTacToes
|
|
15
15
|
open_spaces = Hash[board.open_spaces.map { |space| [space, nil] }]
|
16
16
|
|
17
17
|
open_spaces.each do |space, score|
|
18
|
-
score = minimax(generate_game_state(space, game_state), :min, ALPHA, BETA)
|
18
|
+
score = minimax(generate_game_state(space, game_state), :min, ALPHA, BETA, DEPTH)
|
19
19
|
open_spaces[space] = score
|
20
20
|
end
|
21
21
|
|
@@ -25,18 +25,18 @@ module TicTacToes
|
|
25
25
|
|
26
26
|
private
|
27
27
|
|
28
|
-
def self.minimax(game_state, current_player, alpha, beta)
|
29
|
-
return score(game_state) if game_state.game_over?
|
28
|
+
def self.minimax(game_state, current_player, alpha, beta, depth)
|
29
|
+
return score(game_state, depth) if game_state.game_over?
|
30
30
|
|
31
31
|
if current_player == :max
|
32
32
|
game_state.board.open_spaces.each do |space|
|
33
|
-
alpha = [alpha, minimax(generate_game_state(space, game_state), :min, alpha, beta)].max
|
33
|
+
alpha = [alpha, minimax(generate_game_state(space, game_state), :min, alpha, beta, depth + 1)].max
|
34
34
|
break if beta <= alpha
|
35
35
|
end
|
36
36
|
alpha
|
37
37
|
elsif current_player == :min
|
38
38
|
game_state.board.open_spaces.each do |space|
|
39
|
-
beta = [beta, minimax(generate_game_state(space, game_state), :max, alpha, beta)].min
|
39
|
+
beta = [beta, minimax(generate_game_state(space, game_state), :max, alpha, beta, depth + 1)].min
|
40
40
|
break if beta <= alpha
|
41
41
|
end
|
42
42
|
beta
|
@@ -50,14 +50,14 @@ module TicTacToes
|
|
50
50
|
new_game_state
|
51
51
|
end
|
52
52
|
|
53
|
-
def self.score(game_state)
|
53
|
+
def self.score(game_state, depth)
|
54
54
|
winner = game_state.determine_winner
|
55
55
|
if winner.nil?
|
56
56
|
0
|
57
|
-
elsif winner.move_strategy
|
58
|
-
|
57
|
+
elsif winner.move_strategy.is_a? Human
|
58
|
+
-100
|
59
59
|
else
|
60
|
-
-
|
60
|
+
100 - depth
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'tic_tac_toes/core/player_factory'
|
2
|
+
require 'tic_tac_toes/core/move_strategies/hard_ai'
|
3
|
+
|
1
4
|
module TicTacToes
|
2
5
|
module UI
|
3
6
|
module Adapter
|
@@ -32,6 +35,23 @@ module TicTacToes
|
|
32
35
|
listener.moves_were_made(game_state)
|
33
36
|
end
|
34
37
|
|
38
|
+
def self.predictions(game_state)
|
39
|
+
predictions = []
|
40
|
+
(0..8).each do |space|
|
41
|
+
next predictions << nil unless game_state.board.space(space).nil?
|
42
|
+
|
43
|
+
potential_game_state = generate_game_state(game_state, space)
|
44
|
+
next predictions << predicted_result(potential_game_state) if potential_game_state.game_over?
|
45
|
+
|
46
|
+
potential_game_state.turn_over([])
|
47
|
+
potential_game_state.place_move(Core::MoveStrategies::HardAI.move(potential_game_state))
|
48
|
+
next predictions << predicted_result(potential_game_state) if potential_game_state.game_over?
|
49
|
+
|
50
|
+
predictions << nil
|
51
|
+
end
|
52
|
+
predictions
|
53
|
+
end
|
54
|
+
|
35
55
|
private
|
36
56
|
|
37
57
|
def self.tell_listener_game_is_over(game_state, listener)
|
@@ -44,6 +64,25 @@ module TicTacToes
|
|
44
64
|
listener.game_ended_in_winner(game_state, winning_player.token)
|
45
65
|
end
|
46
66
|
end
|
67
|
+
|
68
|
+
def self.generate_game_state(game_state, space)
|
69
|
+
new_game_state = Marshal.load(Marshal.dump(game_state))
|
70
|
+
new_game_state.turn_over([])
|
71
|
+
new_game_state.place_move(space)
|
72
|
+
new_game_state
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.predicted_result(game_state)
|
76
|
+
winner = game_state.determine_winner
|
77
|
+
|
78
|
+
if winner.nil?
|
79
|
+
"tie"
|
80
|
+
elsif winner.move_strategy.is_a? Core::MoveStrategies::Human
|
81
|
+
"win"
|
82
|
+
else
|
83
|
+
"loss"
|
84
|
+
end
|
85
|
+
end
|
47
86
|
end
|
48
87
|
end
|
49
88
|
end
|
data/lib/tic_tac_toes/version.rb
CHANGED
@@ -1,13 +1,16 @@
|
|
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/move_strategies/human'
|
3
4
|
require 'tic_tac_toes/core/game_state'
|
4
5
|
require 'tic_tac_toes/core/player'
|
5
6
|
|
6
7
|
describe TicTacToes::Core::MoveStrategies::HardAI do
|
7
8
|
let(:alpha) { TicTacToes::Core::MoveStrategies::HardAI::ALPHA }
|
8
9
|
let(:beta) { TicTacToes::Core::MoveStrategies::HardAI::BETA }
|
10
|
+
let(:depth) { TicTacToes::Core::MoveStrategies::HardAI::DEPTH }
|
9
11
|
let(:hard_ai) { TicTacToes::Core::MoveStrategies::HardAI }
|
10
|
-
let(:
|
12
|
+
let(:human) { TicTacToes::Core::MoveStrategies::Human.new('unused_io') }
|
13
|
+
let(:x) { TicTacToes::Core::Player.new(human, "x", false, "io") }
|
11
14
|
let(:o) { TicTacToes::Core::Player.new(hard_ai, "o", true, "io") }
|
12
15
|
let(:players) { [o, x] }
|
13
16
|
let(:history) { TicTacToes::UI::NullHistory.new }
|
@@ -69,9 +72,9 @@ describe TicTacToes::Core::MoveStrategies::HardAI do
|
|
69
72
|
o, o, nil,
|
70
73
|
x, nil, x])
|
71
74
|
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
72
|
-
win_score =
|
75
|
+
win_score = 99
|
73
76
|
|
74
|
-
expect(hard_ai.minimax(game_state, :max, alpha, beta)).to eql(win_score)
|
77
|
+
expect(hard_ai.minimax(game_state, :max, alpha, beta, depth)).to eql(win_score)
|
75
78
|
end
|
76
79
|
|
77
80
|
it 'returns the correct score for a pre-loss board' do
|
@@ -79,9 +82,9 @@ describe TicTacToes::Core::MoveStrategies::HardAI do
|
|
79
82
|
nil, nil, nil,
|
80
83
|
x, nil, x])
|
81
84
|
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
82
|
-
loss_score = -
|
85
|
+
loss_score = -100
|
83
86
|
|
84
|
-
expect(hard_ai.minimax(game_state, :max, alpha, beta)).to eql(loss_score)
|
87
|
+
expect(hard_ai.minimax(game_state, :max, alpha, beta, depth)).to eql(loss_score)
|
85
88
|
end
|
86
89
|
|
87
90
|
it 'returns the correct score for a pre-draw board' do
|
@@ -91,7 +94,7 @@ describe TicTacToes::Core::MoveStrategies::HardAI do
|
|
91
94
|
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
92
95
|
draw_score = 0
|
93
96
|
|
94
|
-
expect(hard_ai.minimax(game_state, :max, alpha, beta)).to eql(draw_score)
|
97
|
+
expect(hard_ai.minimax(game_state, :max, alpha, beta, depth)).to eql(draw_score)
|
95
98
|
end
|
96
99
|
end
|
97
100
|
|
@@ -116,9 +119,9 @@ describe TicTacToes::Core::MoveStrategies::HardAI do
|
|
116
119
|
nil, o, nil,
|
117
120
|
nil, nil, o])
|
118
121
|
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
119
|
-
win_score =
|
122
|
+
win_score = 100
|
120
123
|
|
121
|
-
expect(hard_ai.score(game_state)).to eq(win_score)
|
124
|
+
expect(hard_ai.score(game_state, depth)).to eq(win_score)
|
122
125
|
end
|
123
126
|
|
124
127
|
it 'returns the correct score when no one has won' do
|
@@ -128,7 +131,7 @@ describe TicTacToes::Core::MoveStrategies::HardAI do
|
|
128
131
|
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
129
132
|
draw_score = 0
|
130
133
|
|
131
|
-
expect(hard_ai.score(game_state)).to eql(draw_score)
|
134
|
+
expect(hard_ai.score(game_state, depth)).to eql(draw_score)
|
132
135
|
end
|
133
136
|
|
134
137
|
it 'returns the correct score when the opponent has won' do
|
@@ -136,9 +139,9 @@ describe TicTacToes::Core::MoveStrategies::HardAI do
|
|
136
139
|
nil, x, nil,
|
137
140
|
nil, nil, x])
|
138
141
|
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
139
|
-
loss_score = -
|
142
|
+
loss_score = -100
|
140
143
|
|
141
|
-
expect(hard_ai.score(game_state)).to eql(loss_score)
|
144
|
+
expect(hard_ai.score(game_state, depth)).to eql(loss_score)
|
142
145
|
end
|
143
146
|
end
|
144
147
|
end
|
@@ -91,4 +91,57 @@ describe TicTacToes::UI::Adapter do
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
end
|
94
|
+
|
95
|
+
describe '#predictions' do
|
96
|
+
let(:history) { TicTacToes::UI::NullHistory.new }
|
97
|
+
|
98
|
+
player_factory = TicTacToes::Core::PlayerFactory.new('unused_io')
|
99
|
+
let(:x) { player_factory.generate_player('x', TicTacToes::Core::PlayerFactory::HUMAN) }
|
100
|
+
let(:o) { player_factory.generate_player('o', TicTacToes::Core::PlayerFactory::HARD_AI) }
|
101
|
+
let(:players) { [o, x] }
|
102
|
+
|
103
|
+
it 'returns an array indicating which moves will result in a win' do
|
104
|
+
board = TicTacToes::TestBoardGenerator.generate([ x, x, nil,
|
105
|
+
nil, nil, nil,
|
106
|
+
nil, x, x])
|
107
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
108
|
+
|
109
|
+
expect(TicTacToes::UI::Adapter.predictions(game_state)).to eq([ nil, nil, "win",
|
110
|
+
nil, "win", nil,
|
111
|
+
"win", nil, nil])
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'returns an array indicating which moves will result in an immediate tie' do
|
115
|
+
board = TicTacToes::TestBoardGenerator.generate([x, x, o,
|
116
|
+
o, o, x,
|
117
|
+
x, o, nil])
|
118
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
119
|
+
|
120
|
+
expect(TicTacToes::UI::Adapter.predictions(game_state)).to eq([nil, nil, nil,
|
121
|
+
nil, nil, nil,
|
122
|
+
nil, nil, "tie"])
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'returns an array indicating which moves will result in a tie after the subsequent move' do
|
126
|
+
board = TicTacToes::TestBoardGenerator.generate([o, o, x,
|
127
|
+
x, x, o,
|
128
|
+
o, nil, nil])
|
129
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
130
|
+
|
131
|
+
expect(TicTacToes::UI::Adapter.predictions(game_state)).to eq([nil, nil, nil,
|
132
|
+
nil, nil, nil,
|
133
|
+
nil, "tie", "tie"])
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'returns an array indicating which moves will result in a loss after the subsequent move' do
|
137
|
+
board = TicTacToes::TestBoardGenerator.generate([ o, x, o,
|
138
|
+
x, o, nil,
|
139
|
+
nil, nil, x])
|
140
|
+
game_state = TicTacToes::Core::GameState.new(board, players, history)
|
141
|
+
|
142
|
+
expect(TicTacToes::UI::Adapter.predictions(game_state)).to eq([ nil, nil, nil,
|
143
|
+
nil, nil, "loss",
|
144
|
+
nil, "loss", nil])
|
145
|
+
end
|
146
|
+
end
|
94
147
|
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.1.
|
4
|
+
version: 0.1.2
|
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-08-
|
11
|
+
date: 2014-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|