deepbeige 0.2.1 → 0.2.3
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.
- data/.gitignore +0 -1
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/bin/deepbeige +4 -0
- data/deepbeige.gemspec +20 -18
- data/{arena.rb → lib/arena.rb} +0 -0
- data/lib/deepbeige/main.rb +123 -0
- data/lib/deepbeige.rb +232 -0
- data/{game.rb → lib/game.rb} +0 -0
- data/{human.rb → lib/human.rb} +0 -0
- data/{match.rb → lib/match.rb} +0 -0
- data/{neural_net.rb → lib/neuralnet.rb} +58 -5
- data/{node.rb → lib/node.rb} +0 -0
- data/{noughts_and_crosses.rb → lib/noughts_and_crosses.rb} +0 -0
- data/{pick_a_number.rb → lib/pick_a_number.rb} +0 -0
- data/{player.rb → lib/player.rb} +0 -0
- data/{table.rb → lib/table.rb} +0 -0
- data/lib/tier.rb +0 -0
- data/{tournament.rb → lib/tournament.rb} +0 -0
- metadata +22 -22
- data/deep_beige.rb +0 -223
- data/main.rb +0 -124
- data/population/best.txt +0 -20
data/.gitignore
CHANGED
data/Rakefile
CHANGED
@@ -5,8 +5,8 @@ begin
|
|
5
5
|
require 'jeweler'
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = "deepbeige"
|
8
|
-
gem.summary = "An AI learning
|
9
|
-
gem.description = "An AI learning program that plays
|
8
|
+
gem.summary = "An AI learning algorithm for games"
|
9
|
+
gem.description = "An AI learning program that plays noughts and crosses"
|
10
10
|
gem.email = "david@bochenski.co.uk"
|
11
11
|
gem.homepage = "http://github.com/bochenski/deepbeige"
|
12
12
|
gem.authors = ["David Bochenski"]
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.3
|
data/bin/deepbeige
ADDED
data/deepbeige.gemspec
CHANGED
@@ -5,13 +5,15 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{deepbeige}
|
8
|
-
s.version = "0.2.
|
8
|
+
s.version = "0.2.3"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["David Bochenski"]
|
12
|
-
s.date = %q{2010-09-
|
13
|
-
s.
|
12
|
+
s.date = %q{2010-09-09}
|
13
|
+
s.default_executable = %q{deepbeige}
|
14
|
+
s.description = %q{An AI learning program that plays noughts and crosses}
|
14
15
|
s.email = %q{david@bochenski.co.uk}
|
16
|
+
s.executables = ["deepbeige"]
|
15
17
|
s.extra_rdoc_files = [
|
16
18
|
"LICENSE",
|
17
19
|
"README.rdoc"
|
@@ -23,24 +25,24 @@ Gem::Specification.new do |s|
|
|
23
25
|
"README.rdoc",
|
24
26
|
"Rakefile",
|
25
27
|
"VERSION",
|
26
|
-
"
|
27
|
-
"deep_beige.rb",
|
28
|
+
"bin/deepbeige",
|
28
29
|
"deepbeige.gemspec",
|
29
|
-
"
|
30
|
-
"human.rb",
|
30
|
+
"lib/arena.rb",
|
31
31
|
"lib/deepbeige.rb",
|
32
|
-
"main.rb",
|
33
|
-
"
|
34
|
-
"
|
35
|
-
"
|
36
|
-
"
|
37
|
-
"
|
38
|
-
"
|
39
|
-
"
|
40
|
-
"
|
32
|
+
"lib/deepbeige/main.rb",
|
33
|
+
"lib/game.rb",
|
34
|
+
"lib/human.rb",
|
35
|
+
"lib/match.rb",
|
36
|
+
"lib/neuralnet.rb",
|
37
|
+
"lib/node.rb",
|
38
|
+
"lib/noughts_and_crosses.rb",
|
39
|
+
"lib/pick_a_number.rb",
|
40
|
+
"lib/player.rb",
|
41
|
+
"lib/table.rb",
|
42
|
+
"lib/tier.rb",
|
43
|
+
"lib/tournament.rb",
|
41
44
|
"test/helper.rb",
|
42
45
|
"test/test_deepbeige.rb",
|
43
|
-
"tournament.rb",
|
44
46
|
"ui/Rakefile",
|
45
47
|
"ui/config/build.yml",
|
46
48
|
"ui/lib/application.rb",
|
@@ -51,7 +53,7 @@ Gem::Specification.new do |s|
|
|
51
53
|
s.rdoc_options = ["--charset=UTF-8"]
|
52
54
|
s.require_paths = ["lib"]
|
53
55
|
s.rubygems_version = %q{1.3.7}
|
54
|
-
s.summary = %q{An AI learning
|
56
|
+
s.summary = %q{An AI learning algorithm for games}
|
55
57
|
s.test_files = [
|
56
58
|
"test/helper.rb",
|
57
59
|
"test/test_deepbeige.rb"
|
data/{arena.rb → lib/arena.rb}
RENAMED
File without changes
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'game'
|
3
|
+
require 'pick_a_number'
|
4
|
+
require 'noughts_and_crosses'
|
5
|
+
require 'human'
|
6
|
+
require 'deepbeige'
|
7
|
+
require 'table'
|
8
|
+
|
9
|
+
$application_name = "DeepBeige"
|
10
|
+
|
11
|
+
class DeepBeige
|
12
|
+
class Main
|
13
|
+
class << self
|
14
|
+
def play_game game, p1, p2, options
|
15
|
+
players = [p1,p2]
|
16
|
+
table = Table.new game, players
|
17
|
+
|
18
|
+
options.each do |option|
|
19
|
+
if option == "verbose"
|
20
|
+
game.verbose = true
|
21
|
+
elsif option == "quiet"
|
22
|
+
game.quiet = true
|
23
|
+
table.quiet = true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
table.play_game
|
28
|
+
end
|
29
|
+
|
30
|
+
def player_vs_deepbeige db, game
|
31
|
+
#ok so now I'm interested in playing my best creation
|
32
|
+
db.start_game game.name
|
33
|
+
me = Human.new game
|
34
|
+
if which_player == "1"
|
35
|
+
play_game game, me, db, []
|
36
|
+
else
|
37
|
+
play_game game, db, me, []
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def player_vs_player game
|
42
|
+
p1 = Human.new game
|
43
|
+
p2 = Human.new game
|
44
|
+
play_game game, p1,p2, []
|
45
|
+
end
|
46
|
+
|
47
|
+
def which_player
|
48
|
+
puts
|
49
|
+
puts "Would you like to be player 1 or 2?"
|
50
|
+
gets.chop
|
51
|
+
end
|
52
|
+
|
53
|
+
def options
|
54
|
+
puts
|
55
|
+
puts "What would you like to do?"
|
56
|
+
puts " 1. Player vs DeepBeige"
|
57
|
+
puts " 2. Train DeepBeige"
|
58
|
+
puts " 3. Reset DeepBeige"
|
59
|
+
puts " 4. Player vs Player"
|
60
|
+
puts " 5. Exit Application"
|
61
|
+
end
|
62
|
+
|
63
|
+
def game_choice
|
64
|
+
puts
|
65
|
+
puts "Which Game?"
|
66
|
+
puts " 1. Noughts and Crosses"
|
67
|
+
puts " 2. Pick A Number"
|
68
|
+
game = nil
|
69
|
+
case gets.chop
|
70
|
+
when "1"
|
71
|
+
game = NoughtsAndCrosses.new
|
72
|
+
when "2"
|
73
|
+
game = PickANumber.new
|
74
|
+
else
|
75
|
+
puts "Sorry, didn't understand you there."
|
76
|
+
end
|
77
|
+
game
|
78
|
+
end
|
79
|
+
|
80
|
+
def run!(*arguments)
|
81
|
+
|
82
|
+
# begin
|
83
|
+
db = DeepBeige.new
|
84
|
+
|
85
|
+
version = "0.2.2"
|
86
|
+
puts
|
87
|
+
puts "Welcome to DeepBeige v#{version}"
|
88
|
+
|
89
|
+
exit = false
|
90
|
+
until exit do
|
91
|
+
options
|
92
|
+
case input = gets.chop
|
93
|
+
when "1"
|
94
|
+
game = game_choice
|
95
|
+
player_vs_deepbeige db, game
|
96
|
+
when "2"
|
97
|
+
game = game_choice
|
98
|
+
puts "How Many Generations?"
|
99
|
+
generations = gets.chop.to_i
|
100
|
+
db.train generations, game
|
101
|
+
when "3"
|
102
|
+
game = game_choice
|
103
|
+
db.learn game
|
104
|
+
puts "DeepBeige Reset sucessfully"
|
105
|
+
when "4"
|
106
|
+
game = game_choice
|
107
|
+
player_vs_player game
|
108
|
+
when "5"
|
109
|
+
puts "Bye Bye"
|
110
|
+
exit = true
|
111
|
+
else
|
112
|
+
puts "Sorry I didn't understand you"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
return 0
|
116
|
+
# rescue
|
117
|
+
# $stderr.puts "Something Went Wrong With Deep Beige - Sorry"
|
118
|
+
# return 1
|
119
|
+
# end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/deepbeige.rb
CHANGED
@@ -0,0 +1,232 @@
|
|
1
|
+
require 'neuralnet'
|
2
|
+
|
3
|
+
class DeepBeige
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@neural_net = NeuralNet.new
|
7
|
+
@population =[]
|
8
|
+
end
|
9
|
+
|
10
|
+
def id
|
11
|
+
@neural_net.id
|
12
|
+
end
|
13
|
+
|
14
|
+
def start_game game_name
|
15
|
+
@game_name = game_name
|
16
|
+
case game_name
|
17
|
+
when "NoughtsAndCrosses"
|
18
|
+
@neural_net.generate :inputs => 9, :outputs => 1, :tiers => 3
|
19
|
+
@neural_net.load_from_file (ENV["HOME"] + "/." + $application_name + "/NoughtsAndCrosses/best.txt")
|
20
|
+
when "PickANumber"
|
21
|
+
@neural_net.generate :inputs => 3, :outputs => 1, :tiers => 2
|
22
|
+
@neural_net.load_from_file (ENV["HOME"] + "/." + $application_name + "/PickANumber/best.txt")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_move position, moves
|
27
|
+
game = game_from_name @game_name
|
28
|
+
game.quiet = true
|
29
|
+
game.reload_position moves
|
30
|
+
|
31
|
+
best_move = ""
|
32
|
+
best_score = -2
|
33
|
+
game.legal_moves.each do |move|
|
34
|
+
game2 = game_from_name @game_name
|
35
|
+
game2.quiet = true
|
36
|
+
game2.reload_position moves
|
37
|
+
game2.play_move game.next_player, move
|
38
|
+
input = game2.current_position.values
|
39
|
+
|
40
|
+
if game.next_player == 1
|
41
|
+
input = []
|
42
|
+
game2.current_position.values.each do |value|
|
43
|
+
input << -value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
@neural_net.input = input
|
48
|
+
@neural_net.evaluate
|
49
|
+
score = @neural_net.output_value
|
50
|
+
#p "move #{move} evaluated as #{score}"
|
51
|
+
if score > best_score
|
52
|
+
best_score = score
|
53
|
+
best_move = move
|
54
|
+
end
|
55
|
+
end
|
56
|
+
best_move
|
57
|
+
end
|
58
|
+
|
59
|
+
def learn game
|
60
|
+
generate_population 30, game.name
|
61
|
+
end
|
62
|
+
|
63
|
+
def train generations, game
|
64
|
+
@game_name = game.name
|
65
|
+
@population = load_population game.name
|
66
|
+
|
67
|
+
#reset scores
|
68
|
+
scores = {}
|
69
|
+
@population.each do |neuralnet|
|
70
|
+
scores[neuralnet.id] = 0
|
71
|
+
end
|
72
|
+
|
73
|
+
generation_number = 1
|
74
|
+
generations.times do
|
75
|
+
puts "Evolving Generation #{generation_number}"
|
76
|
+
player_number = 0
|
77
|
+
@population.each do |neuralnet|
|
78
|
+
player = DeepBeige.new
|
79
|
+
player.neural_net = neuralnet
|
80
|
+
player.game_name = game.name
|
81
|
+
|
82
|
+
5.times do
|
83
|
+
play_as_player game.name, player, 1, scores
|
84
|
+
end
|
85
|
+
5.times do
|
86
|
+
play_as_player game.name, player, 2, scores
|
87
|
+
end
|
88
|
+
player_number += 1
|
89
|
+
end
|
90
|
+
leaderboard = scores.sort_by {|id, count| count}
|
91
|
+
position = 1
|
92
|
+
5.times do
|
93
|
+
leader = leaderboard[leaderboard.count - position]
|
94
|
+
puts "#{position}. pts: #{leader[1]}, #{leader[0]}"
|
95
|
+
position +=1
|
96
|
+
end
|
97
|
+
@population.each do |neural_net|
|
98
|
+
if neural_net.id == leaderboard.last[0]
|
99
|
+
neural_net.save_to_file (ENV["HOME"] + "/." + $application_name + "/#{@game_name}/best.txt")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
i = 0
|
103
|
+
while i < 15 do
|
104
|
+
@population.delete_if {|neural_net| neural_net.id == leaderboard[i][0]}
|
105
|
+
i +=1
|
106
|
+
end
|
107
|
+
|
108
|
+
new_nets = []
|
109
|
+
@population.each do |neural_net|
|
110
|
+
new_net = neural_net.clone
|
111
|
+
new_net.mutate
|
112
|
+
new_nets << new_net
|
113
|
+
end
|
114
|
+
@population.concat new_nets
|
115
|
+
|
116
|
+
scores = {}
|
117
|
+
@population.each do |neural_net|
|
118
|
+
scores[neural_net.id] = 0
|
119
|
+
end
|
120
|
+
|
121
|
+
generation_number +=1
|
122
|
+
end
|
123
|
+
|
124
|
+
save_population game.name
|
125
|
+
end
|
126
|
+
|
127
|
+
protected
|
128
|
+
def neural_net= value
|
129
|
+
@neural_net = value
|
130
|
+
end
|
131
|
+
def game_name= value
|
132
|
+
@game_name = value
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
def game_from_name name
|
137
|
+
game = nil
|
138
|
+
case name
|
139
|
+
when "NoughtsAndCrosses"
|
140
|
+
game = NoughtsAndCrosses.new
|
141
|
+
when "PickANumber"
|
142
|
+
game = PickANumber.new
|
143
|
+
end
|
144
|
+
game
|
145
|
+
end
|
146
|
+
|
147
|
+
def load_population name
|
148
|
+
@population = []
|
149
|
+
Dir[ENV["HOME"] + "/." + $application_name + "/#{name}/*[0-9].txt"].each do |filename|
|
150
|
+
candidate = NeuralNet.new
|
151
|
+
candidate.load_from_file filename
|
152
|
+
@population << candidate
|
153
|
+
end
|
154
|
+
@population
|
155
|
+
end
|
156
|
+
|
157
|
+
def save_population name
|
158
|
+
#and at the end of that, we ought to save our population
|
159
|
+
i = 0
|
160
|
+
@population.each do |neural_net|
|
161
|
+
|
162
|
+
neural_net.save_to_file (ENV["HOME"] + "/." + $application_name +"/#{name}/#{i}.txt")
|
163
|
+
i += 1
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def generate_population size, name
|
168
|
+
|
169
|
+
#Check Directories Exist
|
170
|
+
home_dir = ENV["HOME"] + "/." + $application_name
|
171
|
+
unless FileTest::directory?(home_dir)
|
172
|
+
Dir::mkdir(home_dir)
|
173
|
+
end
|
174
|
+
|
175
|
+
dir_name = home_dir +"/#{name}"
|
176
|
+
unless FileTest::directory?(dir_name)
|
177
|
+
Dir::mkdir(dir_name)
|
178
|
+
end
|
179
|
+
|
180
|
+
#Generate Population
|
181
|
+
@population = []
|
182
|
+
scores = {}
|
183
|
+
size.times do
|
184
|
+
neural_net = NeuralNet.new
|
185
|
+
case name
|
186
|
+
when "NoughtsAndCrosses"
|
187
|
+
neural_net.generate :inputs => 9, :outputs => 1, :tiers => 3
|
188
|
+
when "PickANumber"
|
189
|
+
neural_net.generate :inputs => 3, :outputs => 1, :tiers => 2
|
190
|
+
end
|
191
|
+
@population << neural_net
|
192
|
+
end
|
193
|
+
save_population name
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
def play_as_player game_name, player, player_number, scores
|
198
|
+
game = game_from_name game_name
|
199
|
+
game.quiet = true
|
200
|
+
opponent_number = rand(@population.count)
|
201
|
+
#puts "#{player_number} versus opponent #{opponent_number}"
|
202
|
+
opponent_net = @population[opponent_number]
|
203
|
+
opponent = DeepBeige.new
|
204
|
+
opponent.neural_net = opponent_net
|
205
|
+
opponent.game_name = @game_name
|
206
|
+
players = [player, opponent]
|
207
|
+
if player_number == 2
|
208
|
+
player = [opponent,player]
|
209
|
+
end
|
210
|
+
|
211
|
+
table = Table.new game, players
|
212
|
+
table.quiet = true
|
213
|
+
table.play_game
|
214
|
+
if game.drawn?
|
215
|
+
players.each do |player|
|
216
|
+
scores[player.id] +=1
|
217
|
+
end
|
218
|
+
|
219
|
+
elsif game.won?
|
220
|
+
winner = players[game.winner]
|
221
|
+
players.each do |player|
|
222
|
+
if player.id == winner.id
|
223
|
+
scores[player.id] +=2
|
224
|
+
else
|
225
|
+
scores[player.id] -=2
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
|
data/{game.rb → lib/game.rb}
RENAMED
File without changes
|
data/{human.rb → lib/human.rb}
RENAMED
File without changes
|
data/{match.rb → lib/match.rb}
RENAMED
File without changes
|
@@ -43,7 +43,28 @@ class NeuralNet
|
|
43
43
|
value
|
44
44
|
end
|
45
45
|
|
46
|
-
def generate
|
46
|
+
def generate options
|
47
|
+
inputs = 1
|
48
|
+
if options[:inputs]
|
49
|
+
inputs = options[:inputs]
|
50
|
+
end
|
51
|
+
outputs = 1
|
52
|
+
if options[:outputs]
|
53
|
+
outputs = options[:outputs]
|
54
|
+
end
|
55
|
+
tiers = 2
|
56
|
+
if options[:tiers]
|
57
|
+
tiers = options[:tiers]
|
58
|
+
end
|
59
|
+
@width = 0
|
60
|
+
if options[:width]
|
61
|
+
@width = options[:width]
|
62
|
+
end
|
63
|
+
@height = 0
|
64
|
+
if options [:height]
|
65
|
+
@height = options[:height]
|
66
|
+
end
|
67
|
+
|
47
68
|
@network = []
|
48
69
|
input_nodes = []
|
49
70
|
inputs.times do
|
@@ -52,6 +73,30 @@ class NeuralNet
|
|
52
73
|
if input_nodes.count > 0
|
53
74
|
@network << input_nodes
|
54
75
|
end
|
76
|
+
if height > 1 && width > 1
|
77
|
+
|
78
|
+
if width == height
|
79
|
+
#ok, slightly impure but this is our clue
|
80
|
+
#that we're dealing with a 2D board and we're going
|
81
|
+
#to insert a special layer to reflect the board topology
|
82
|
+
tiers = tiers -1
|
83
|
+
tier = []
|
84
|
+
n = 2
|
85
|
+
i = 0
|
86
|
+
j = 0
|
87
|
+
(height - n).times do
|
88
|
+
(width - n).times do
|
89
|
+
node = Node.new(@sigma)
|
90
|
+
end
|
91
|
+
n = n + 1
|
92
|
+
i += 1
|
93
|
+
end
|
94
|
+
@network << tier
|
95
|
+
else
|
96
|
+
#for now we will not bother with rectangles
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
55
100
|
(tiers - 2).times do
|
56
101
|
tier = []
|
57
102
|
10.times do
|
@@ -74,15 +119,13 @@ class NeuralNet
|
|
74
119
|
def fingerprint
|
75
120
|
topline = ""
|
76
121
|
fingerprint = ""
|
77
|
-
sigma = ""
|
78
|
-
tau = ""
|
79
122
|
@network.each do |tier|
|
80
123
|
topline << "#{tier.count},"
|
81
124
|
tier.each do |node|
|
82
125
|
fingerprint << node.fingerprint
|
83
126
|
end
|
84
127
|
end
|
85
|
-
topline.chop + "\n" + @sigma.to_s + "\n" + @tau.to_s + "\n" + fingerprint
|
128
|
+
topline.chop + "\n" + @sigma.to_s + "\n" + @tau.to_s + "\n" + @width.to_s + "\n" + @height.to_s + "\n"+ fingerprint
|
86
129
|
end
|
87
130
|
|
88
131
|
def reload fingerprint
|
@@ -94,6 +137,10 @@ class NeuralNet
|
|
94
137
|
i += 1
|
95
138
|
@tau = fingerprint[i].to_f
|
96
139
|
i += 1
|
140
|
+
@width = fingerprint[i].to_i
|
141
|
+
i += 1
|
142
|
+
@height = fingerprint[i].to_i
|
143
|
+
i += 1
|
97
144
|
|
98
145
|
@network = []
|
99
146
|
tiers.each do |tier|
|
@@ -134,6 +181,9 @@ class NeuralNet
|
|
134
181
|
clone = NeuralNet.new
|
135
182
|
clone.sigma = self.sigma
|
136
183
|
clone.tau = self.tau
|
184
|
+
clone.width = self.width
|
185
|
+
clone.height = self.height
|
186
|
+
|
137
187
|
#iterate in through each tier
|
138
188
|
@network.each do |tier|
|
139
189
|
nodes = []
|
@@ -169,12 +219,15 @@ class NeuralNet
|
|
169
219
|
end
|
170
220
|
|
171
221
|
protected
|
172
|
-
attr_accessor :sigma, :tau
|
222
|
+
attr_accessor :sigma, :tau, :width, :height
|
173
223
|
|
174
224
|
def link_tiers
|
175
225
|
#first cut lets link every node on a tier to each node on the subsequent tier
|
176
226
|
i = 1
|
177
227
|
@network.each do |tier|
|
228
|
+
if i == 1 && @width > 1 && @height > 1
|
229
|
+
|
230
|
+
end
|
178
231
|
if i < @network.count
|
179
232
|
tier.each do |node|
|
180
233
|
j = 0
|
data/{node.rb → lib/node.rb}
RENAMED
File without changes
|
File without changes
|
File without changes
|
data/{player.rb → lib/player.rb}
RENAMED
File without changes
|
data/{table.rb → lib/table.rb}
RENAMED
File without changes
|
data/lib/tier.rb
ADDED
File without changes
|
File without changes
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
version: 0.2.
|
8
|
+
- 3
|
9
|
+
version: 0.2.3
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- David Bochenski
|
@@ -14,8 +14,8 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-09-
|
18
|
-
default_executable:
|
17
|
+
date: 2010-09-09 00:00:00 +01:00
|
18
|
+
default_executable: deepbeige
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: thoughtbot-shoulda
|
@@ -30,10 +30,10 @@ dependencies:
|
|
30
30
|
version: "0"
|
31
31
|
type: :development
|
32
32
|
version_requirements: *id001
|
33
|
-
description: An AI learning program that plays
|
33
|
+
description: An AI learning program that plays noughts and crosses
|
34
34
|
email: david@bochenski.co.uk
|
35
|
-
executables:
|
36
|
-
|
35
|
+
executables:
|
36
|
+
- deepbeige
|
37
37
|
extensions: []
|
38
38
|
|
39
39
|
extra_rdoc_files:
|
@@ -46,24 +46,24 @@ files:
|
|
46
46
|
- README.rdoc
|
47
47
|
- Rakefile
|
48
48
|
- VERSION
|
49
|
-
-
|
50
|
-
- deep_beige.rb
|
49
|
+
- bin/deepbeige
|
51
50
|
- deepbeige.gemspec
|
52
|
-
-
|
53
|
-
- human.rb
|
51
|
+
- lib/arena.rb
|
54
52
|
- lib/deepbeige.rb
|
55
|
-
- main.rb
|
56
|
-
-
|
57
|
-
-
|
58
|
-
-
|
59
|
-
-
|
60
|
-
-
|
61
|
-
-
|
62
|
-
-
|
63
|
-
-
|
53
|
+
- lib/deepbeige/main.rb
|
54
|
+
- lib/game.rb
|
55
|
+
- lib/human.rb
|
56
|
+
- lib/match.rb
|
57
|
+
- lib/neuralnet.rb
|
58
|
+
- lib/node.rb
|
59
|
+
- lib/noughts_and_crosses.rb
|
60
|
+
- lib/pick_a_number.rb
|
61
|
+
- lib/player.rb
|
62
|
+
- lib/table.rb
|
63
|
+
- lib/tier.rb
|
64
|
+
- lib/tournament.rb
|
64
65
|
- test/helper.rb
|
65
66
|
- test/test_deepbeige.rb
|
66
|
-
- tournament.rb
|
67
67
|
- ui/Rakefile
|
68
68
|
- ui/config/build.yml
|
69
69
|
- ui/lib/application.rb
|
@@ -100,7 +100,7 @@ rubyforge_project:
|
|
100
100
|
rubygems_version: 1.3.7
|
101
101
|
signing_key:
|
102
102
|
specification_version: 3
|
103
|
-
summary: An AI learning
|
103
|
+
summary: An AI learning algorithm for games
|
104
104
|
test_files:
|
105
105
|
- test/helper.rb
|
106
106
|
- test/test_deepbeige.rb
|
data/deep_beige.rb
DELETED
@@ -1,223 +0,0 @@
|
|
1
|
-
require 'neural_net'
|
2
|
-
|
3
|
-
class DeepBeige
|
4
|
-
|
5
|
-
def initialize
|
6
|
-
@neural_net = NeuralNet.new
|
7
|
-
@population =[]
|
8
|
-
end
|
9
|
-
|
10
|
-
def id
|
11
|
-
@neural_net.id
|
12
|
-
end
|
13
|
-
|
14
|
-
def start_game game_name
|
15
|
-
@game_name = game_name
|
16
|
-
case game_name
|
17
|
-
when "NoughtsAndCrosses"
|
18
|
-
@neural_net.generate 9, 1, 3
|
19
|
-
@neural_net.load_from_file "DeepBeige/NoughtsAndCrosses/best.txt"
|
20
|
-
when "PickANumber"
|
21
|
-
@neural_net.generate 3,1,2
|
22
|
-
@neural_net.load_from_file "DeepBeige/PickANumber/best.txt"
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def get_move position, moves
|
27
|
-
game = game_from_name @game_name
|
28
|
-
game.quiet = true
|
29
|
-
game.reload_position moves
|
30
|
-
|
31
|
-
best_move = ""
|
32
|
-
best_score = -2
|
33
|
-
game.legal_moves.each do |move|
|
34
|
-
game2 = game_from_name @game_name
|
35
|
-
game2.quiet = true
|
36
|
-
game2.reload_position moves
|
37
|
-
game2.play_move game.next_player, move
|
38
|
-
input = game2.current_position.values
|
39
|
-
|
40
|
-
if game.next_player == 1
|
41
|
-
input = []
|
42
|
-
game2.current_position.values.each do |value|
|
43
|
-
input << -value
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
@neural_net.input = input
|
48
|
-
@neural_net.evaluate
|
49
|
-
score = @neural_net.output_value
|
50
|
-
#p "move #{move} evaluated as #{score}"
|
51
|
-
if score > best_score
|
52
|
-
best_score = score
|
53
|
-
best_move = move
|
54
|
-
end
|
55
|
-
end
|
56
|
-
best_move
|
57
|
-
end
|
58
|
-
|
59
|
-
def learn game
|
60
|
-
generate_population 30, game.name
|
61
|
-
end
|
62
|
-
|
63
|
-
def train generations, game
|
64
|
-
@game_name = game.name
|
65
|
-
@population = load_population game.name
|
66
|
-
|
67
|
-
#reset scores
|
68
|
-
scores = {}
|
69
|
-
@population.each do |neuralnet|
|
70
|
-
scores[neuralnet.id] = 0
|
71
|
-
end
|
72
|
-
|
73
|
-
generation_number = 1
|
74
|
-
generations.times do
|
75
|
-
puts "Evolving Generation #{generation_number}"
|
76
|
-
player_number = 0
|
77
|
-
@population.each do |neuralnet|
|
78
|
-
player = DeepBeige.new
|
79
|
-
player.neural_net = neuralnet
|
80
|
-
player.game_name = game.name
|
81
|
-
|
82
|
-
5.times do
|
83
|
-
play_as_player game.name, player, 1, scores
|
84
|
-
end
|
85
|
-
5.times do
|
86
|
-
play_as_player game.name, player, 2, scores
|
87
|
-
end
|
88
|
-
player_number += 1
|
89
|
-
end
|
90
|
-
leaderboard = scores.sort_by {|id, count| count}
|
91
|
-
position = 1
|
92
|
-
5.times do
|
93
|
-
leader = leaderboard[leaderboard.count - position]
|
94
|
-
puts "#{position}. pts: #{leader[1]}, #{leader[0]}"
|
95
|
-
position +=1
|
96
|
-
end
|
97
|
-
@population.each do |neural_net|
|
98
|
-
if neural_net.id == leaderboard.last[0]
|
99
|
-
neural_net.save_to_file "DeepBeige/#{@game_name}/best.txt"
|
100
|
-
end
|
101
|
-
end
|
102
|
-
i = 0
|
103
|
-
while i < 15 do
|
104
|
-
@population.delete_if {|neural_net| neural_net.id == leaderboard[i][0]}
|
105
|
-
i +=1
|
106
|
-
end
|
107
|
-
|
108
|
-
new_nets = []
|
109
|
-
@population.each do |neural_net|
|
110
|
-
new_net = neural_net.clone
|
111
|
-
new_net.mutate
|
112
|
-
new_nets << new_net
|
113
|
-
end
|
114
|
-
@population.concat new_nets
|
115
|
-
|
116
|
-
scores = {}
|
117
|
-
@population.each do |neural_net|
|
118
|
-
scores[neural_net.id] = 0
|
119
|
-
end
|
120
|
-
|
121
|
-
generation_number +=1
|
122
|
-
end
|
123
|
-
|
124
|
-
save_population game.name
|
125
|
-
end
|
126
|
-
|
127
|
-
protected
|
128
|
-
def neural_net= value
|
129
|
-
@neural_net = value
|
130
|
-
end
|
131
|
-
def game_name= value
|
132
|
-
@game_name = value
|
133
|
-
end
|
134
|
-
|
135
|
-
private
|
136
|
-
def game_from_name name
|
137
|
-
game = nil
|
138
|
-
case name
|
139
|
-
when "NoughtsAndCrosses"
|
140
|
-
game = NoughtsAndCrosses.new
|
141
|
-
when "PickANumber"
|
142
|
-
game = PickANumber.new
|
143
|
-
end
|
144
|
-
game
|
145
|
-
end
|
146
|
-
|
147
|
-
def load_population name
|
148
|
-
@population = []
|
149
|
-
Dir["deepbeige/#{name}/*[0-9].txt"].each do |filename|
|
150
|
-
candidate = NeuralNet.new
|
151
|
-
candidate.load_from_file filename
|
152
|
-
@population << candidate
|
153
|
-
end
|
154
|
-
@population
|
155
|
-
end
|
156
|
-
|
157
|
-
def save_population name
|
158
|
-
#and at the end of that, we ought to save our population
|
159
|
-
i = 0
|
160
|
-
@population.each do |neural_net|
|
161
|
-
neural_net.save_to_file "DeepBeige/#{name}/#{i}.txt"
|
162
|
-
i += 1
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
def generate_population size, name
|
167
|
-
#Generate Population
|
168
|
-
dir_name = "DeepBeige/#{name}"
|
169
|
-
unless FileTest::directory?(dir_name)
|
170
|
-
Dir::mkdir(dir_name)
|
171
|
-
end
|
172
|
-
@population = []
|
173
|
-
scores = {}
|
174
|
-
size.times do
|
175
|
-
neural_net = NeuralNet.new
|
176
|
-
case name
|
177
|
-
when "NoughtsAndCrosses"
|
178
|
-
neural_net.generate 9,1,3
|
179
|
-
when "PickANumber"
|
180
|
-
neural_net.generate 3,1,2
|
181
|
-
end
|
182
|
-
@population << neural_net
|
183
|
-
end
|
184
|
-
save_population name
|
185
|
-
end
|
186
|
-
|
187
|
-
private
|
188
|
-
def play_as_player game_name, player, player_number, scores
|
189
|
-
game = game_from_name game_name
|
190
|
-
game.quiet = true
|
191
|
-
opponent_number = rand(@population.count)
|
192
|
-
#puts "#{player_number} versus opponent #{opponent_number}"
|
193
|
-
opponent_net = @population[opponent_number]
|
194
|
-
opponent = DeepBeige.new
|
195
|
-
opponent.neural_net = opponent_net
|
196
|
-
opponent.game_name = @game_name
|
197
|
-
players = [player, opponent]
|
198
|
-
if player_number == 2
|
199
|
-
player = [opponent,player]
|
200
|
-
end
|
201
|
-
|
202
|
-
table = Table.new game, players
|
203
|
-
table.quiet = true
|
204
|
-
table.play_game
|
205
|
-
if game.drawn?
|
206
|
-
players.each do |player|
|
207
|
-
scores[player.id] +=1
|
208
|
-
end
|
209
|
-
|
210
|
-
elsif game.won?
|
211
|
-
winner = players[game.winner]
|
212
|
-
players.each do |player|
|
213
|
-
if player.id == winner.id
|
214
|
-
scores[player.id] +=2
|
215
|
-
else
|
216
|
-
scores[player.id] -=2
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
|
data/main.rb
DELETED
@@ -1,124 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
require 'rubygems'
|
3
|
-
require 'game'
|
4
|
-
require 'pick_a_number'
|
5
|
-
require 'noughts_and_crosses'
|
6
|
-
require 'human'
|
7
|
-
require 'deep_beige'
|
8
|
-
require 'table'
|
9
|
-
|
10
|
-
def play_game game, p1, p2, options
|
11
|
-
players = [p1,p2]
|
12
|
-
table = Table.new game, players
|
13
|
-
|
14
|
-
options.each do |option|
|
15
|
-
if option == "verbose"
|
16
|
-
game.verbose = true
|
17
|
-
elsif option == "quiet"
|
18
|
-
game.quiet = true
|
19
|
-
table.quiet = true
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
table.play_game
|
24
|
-
end
|
25
|
-
|
26
|
-
def player_vs_deepbeige db, game
|
27
|
-
#ok so now I'm interested in playing my best creation
|
28
|
-
db.start_game game.name
|
29
|
-
me = Human.new game
|
30
|
-
if which_player == "1"
|
31
|
-
play_game game, me, db, []
|
32
|
-
else
|
33
|
-
play_game game, db, me, []
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def player_vs_player game
|
38
|
-
p1 = Human.new game
|
39
|
-
p2 = Human.new game
|
40
|
-
play_game game, p1,p2, []
|
41
|
-
end
|
42
|
-
|
43
|
-
def which_player
|
44
|
-
puts
|
45
|
-
puts "Would you like to be player 1 or 2?"
|
46
|
-
gets.chop
|
47
|
-
end
|
48
|
-
|
49
|
-
def options
|
50
|
-
puts
|
51
|
-
puts "What would you like to do?"
|
52
|
-
puts " 1. Player vs DeepBeige"
|
53
|
-
puts " 2. Train DeepBeige"
|
54
|
-
puts " 3. Reset DeepBeige"
|
55
|
-
puts " 4. Player vs Player"
|
56
|
-
puts " 5. Exit Application"
|
57
|
-
end
|
58
|
-
|
59
|
-
def game_choice
|
60
|
-
puts
|
61
|
-
puts "Which Game?"
|
62
|
-
puts " 1. Noughts and Crosses"
|
63
|
-
puts " 2. Pick A Number"
|
64
|
-
game = nil
|
65
|
-
case gets.chop
|
66
|
-
when "1"
|
67
|
-
game = NoughtsAndCrosses.new
|
68
|
-
when "2"
|
69
|
-
game = PickANumber.new
|
70
|
-
else
|
71
|
-
puts "Sorry, didn't understand you there."
|
72
|
-
end
|
73
|
-
game
|
74
|
-
end
|
75
|
-
|
76
|
-
#This is the main execution loop of our program
|
77
|
-
def main
|
78
|
-
|
79
|
-
db = DeepBeige.new
|
80
|
-
|
81
|
-
version = "0.1.1"
|
82
|
-
puts
|
83
|
-
puts "Welcome to DeepBeige v#{version}"
|
84
|
-
|
85
|
-
exit = false
|
86
|
-
until exit do
|
87
|
-
options
|
88
|
-
case input = gets.chop
|
89
|
-
when "1"
|
90
|
-
game = game_choice
|
91
|
-
player_vs_deepbeige db, game
|
92
|
-
when "2"
|
93
|
-
game = game_choice
|
94
|
-
puts "How Many Generations?"
|
95
|
-
generations = gets.chop.to_i
|
96
|
-
db.train generations, game
|
97
|
-
when "3"
|
98
|
-
game = game_choice
|
99
|
-
db.learn game
|
100
|
-
puts "DeepBeige Reset sucessfully"
|
101
|
-
when "4"
|
102
|
-
game = game_choice
|
103
|
-
player_vs_player game
|
104
|
-
when "5"
|
105
|
-
puts "Bye Bye"
|
106
|
-
exit = true
|
107
|
-
else
|
108
|
-
puts "Sorry I didn't understand you"
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
main unless $Cocoa
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
data/population/best.txt
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
9,9,1
|
2
|
-
1.9888959877114751:1.1713694063264486,0.2131550943633837,-1.4389857774549193,-2.3761064404471623,1.4125388307828852,1.7962657856192086,3.98673652586089,-0.710563267563527,-4.693733927431782
|
3
|
-
-0.5537104997469708:4.353881179541187,-3.7749306975412615,3.9092173844936307,-1.5574066495330294,-1.9867317023123854,2.4016056161982515,0.19093366894771083,2.583574894805702,-0.7814428807197851
|
4
|
-
-3.718402720378594:0.21375589104869108,-0.5952570280952669,3.163222183572866,-2.0666315462205604,1.8466894347641758,2.5737190736563127,0.09083797921525782,-1.7152005515495705,-1.9147906116034963
|
5
|
-
-1.1774113223565559:0.9684176432281265,1.2633322113651906,0.9105205858738161,0.9952880033669778,2.428832023642873,1.1378171183057832,1.2346794709653666,1.614577823722882,-0.5727461748243102
|
6
|
-
-4.240095691598348:5.619686966308787,2.496099436318761,4.236594219999468,-4.089909848411157,-8.583782402447191,7.909784634413837,3.709149525845639,-0.1221209138110586,-1.7739191236891263
|
7
|
-
-0.45644848049775455:0.7525477703501269,0.4416094908434702,0.5151634042799693,0.6494784464984971,-0.21066540646710413,0.9328293326003011,0.4934365894119864,-0.5075989699963754,0.2534248521563641
|
8
|
-
2.000438515839109:-0.18715599814429473,-4.079321241532847,3.0082374216290035,1.6554995963981736,1.2134208468740142,-0.7234607792621472,0.9168360804604427,-0.5284806911574953,3.6316017295618748
|
9
|
-
0.9920544697574241:-0.15133215482382756,0.9977877837645512,-0.06456539820799904,-1.0288035250624143,0.9008138652841244,0.6826185088601288,0.8553527568600676,0.21975379677495055,1.0495630337690707
|
10
|
-
3.8437737235828924:-2.429075885974877,3.0482320934712477,-9.512832982617503,-8.273686259303359,1.5413233764892178,-2.2156195050047747,-7.2849426757667315,5.202236097167509,-0.9365606034293558
|
11
|
-
2.4821899389695874:2.1743832447567346
|
12
|
-
-2.9564446473766672:-3.2541845089551904
|
13
|
-
3.2605627963240384:-0.20795953070592393
|
14
|
-
2.605813320828736:3.154050922464929
|
15
|
-
-0.4579599997104075:2.349414728456047
|
16
|
-
2.653465191369256:4.365018317781122
|
17
|
-
0.6373485836872791:1.7163198231060346
|
18
|
-
0.711515408592374:0.8702577537323323
|
19
|
-
-1.7351499679943323:1.24799151515672
|
20
|
-
2.902919261517124
|