deepbeige 0.2.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.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/arena.rb +6 -0
- data/deep_beige.rb +213 -0
- data/deepbeige.gemspec +73 -0
- data/game.rb +18 -0
- data/human.rb +14 -0
- data/lib/deepbeige.rb +0 -0
- data/main.rb +114 -0
- data/match.rb +43 -0
- data/neural_net.rb +174 -0
- data/node.rb +106 -0
- data/noughts_and_crosses.rb +141 -0
- data/pick_a_number.rb +97 -0
- data/player.rb +4 -0
- data/population/best.txt +20 -0
- data/table.rb +22 -0
- data/test/helper.rb +10 -0
- data/test/test_deepbeige.rb +7 -0
- data/tournament.rb +1 -0
- data/ui/Rakefile +5 -0
- data/ui/config/build.yml +8 -0
- data/ui/lib/application.rb +47 -0
- data/ui/lib/menu.rb +32 -0
- data/ui/resources/DeepBeige.icns +0 -0
- metadata +106 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 David Bochenski
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= deepbeige
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
13
|
+
* Send me a pull request. Bonus points for topic branches.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2010 David Bochenski. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "deepbeige"
|
8
|
+
gem.summary = "An AI learning program that plays board games"
|
9
|
+
gem.description = "An AI learning program that plays board games"
|
10
|
+
gem.email = "david@bochenski.co.uk"
|
11
|
+
gem.homepage = "http://github.com/bochenski/deepbeige"
|
12
|
+
gem.authors = ["David Bochenski"]
|
13
|
+
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake/testtask'
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'lib' << 'test'
|
24
|
+
test.pattern = 'test/**/test_*.rb'
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
Rcov::RcovTask.new do |test|
|
31
|
+
test.libs << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
task :rcov do
|
37
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
task :test => :check_dependencies
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "deepbeige #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.0
|
data/arena.rb
ADDED
data/deep_beige.rb
ADDED
@@ -0,0 +1,213 @@
|
|
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
|
+
player1 = DeepBeige.new
|
79
|
+
player1.neural_net = neuralnet
|
80
|
+
player1.game_name = @game_name
|
81
|
+
|
82
|
+
5.times do
|
83
|
+
game = game.class.new
|
84
|
+
game.quiet = true
|
85
|
+
opponent_number = rand(@population.count)
|
86
|
+
#puts "#{player_number} versus opponent #{opponent_number}"
|
87
|
+
opponent_net = @population[opponent_number]
|
88
|
+
player2 = DeepBeige.new
|
89
|
+
player2.neural_net = opponent_net
|
90
|
+
player2.game_name = @game_name
|
91
|
+
|
92
|
+
players = [player1,player2]
|
93
|
+
table = Table.new game, players
|
94
|
+
table.quiet = true
|
95
|
+
table.play_game
|
96
|
+
if game.drawn?
|
97
|
+
players.each do |player|
|
98
|
+
scores[player.id] +=1
|
99
|
+
end
|
100
|
+
|
101
|
+
elsif game.won?
|
102
|
+
winner = players[game.winner]
|
103
|
+
players.each do |player|
|
104
|
+
if player.id == winner.id
|
105
|
+
scores[player.id] +=2
|
106
|
+
else
|
107
|
+
scores[player.id] -=2
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
player_number += 1
|
114
|
+
end
|
115
|
+
leaderboard = scores.sort_by {|id, count| count}
|
116
|
+
position = 1
|
117
|
+
5.times do
|
118
|
+
leader = leaderboard[leaderboard.count - position]
|
119
|
+
puts "#{position}. pts: #{leader[1]}, #{leader[0]}"
|
120
|
+
position +=1
|
121
|
+
end
|
122
|
+
@population.each do |neural_net|
|
123
|
+
if neural_net.id == leaderboard.last[0]
|
124
|
+
neural_net.save_to_file "DeepBeige/#{@game_name}/best.txt"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
i = 0
|
128
|
+
while i < 15 do
|
129
|
+
@population.delete_if {|neural_net| neural_net.id == leaderboard[i][0]}
|
130
|
+
i +=1
|
131
|
+
end
|
132
|
+
|
133
|
+
new_nets = []
|
134
|
+
@population.each do |neural_net|
|
135
|
+
new_net = neural_net.clone
|
136
|
+
new_net.mutate
|
137
|
+
new_nets << new_net
|
138
|
+
end
|
139
|
+
@population.concat new_nets
|
140
|
+
|
141
|
+
scores = {}
|
142
|
+
@population.each do |neural_net|
|
143
|
+
scores[neural_net.id] = 0
|
144
|
+
end
|
145
|
+
|
146
|
+
generation_number +=1
|
147
|
+
end
|
148
|
+
|
149
|
+
save_population game.name
|
150
|
+
end
|
151
|
+
|
152
|
+
protected
|
153
|
+
def neural_net= value
|
154
|
+
@neural_net = value
|
155
|
+
end
|
156
|
+
def game_name= value
|
157
|
+
@game_name = value
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
def game_from_name name
|
162
|
+
game = nil
|
163
|
+
case name
|
164
|
+
when "NoughtsAndCrosses"
|
165
|
+
game = NoughtsAndCrosses.new
|
166
|
+
when "PickANumber"
|
167
|
+
game = PickANumber.new
|
168
|
+
end
|
169
|
+
game
|
170
|
+
end
|
171
|
+
|
172
|
+
def load_population name
|
173
|
+
@population = []
|
174
|
+
Dir["deepbeige/#{name}/*[0-9].txt"].each do |filename|
|
175
|
+
candidate = NeuralNet.new
|
176
|
+
candidate.load_from_file filename
|
177
|
+
@population << candidate
|
178
|
+
end
|
179
|
+
@population
|
180
|
+
end
|
181
|
+
|
182
|
+
def save_population name
|
183
|
+
#and at the end of that, we ought to save our population
|
184
|
+
i = 0
|
185
|
+
@population.each do |neural_net|
|
186
|
+
neural_net.save_to_file "DeepBeige/#{name}/#{i}.txt"
|
187
|
+
i += 1
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def generate_population size, name
|
192
|
+
#Generate Population
|
193
|
+
dir_name = "DeepBeige/#{name}"
|
194
|
+
unless FileTest::directory?(dir_name)
|
195
|
+
Dir::mkdir(dir_name)
|
196
|
+
end
|
197
|
+
@population = []
|
198
|
+
scores = {}
|
199
|
+
size.times do
|
200
|
+
neural_net = NeuralNet.new
|
201
|
+
case name
|
202
|
+
when "NoughtsAndCrosses"
|
203
|
+
neural_net.generate 9,1,3
|
204
|
+
when "PickANumber"
|
205
|
+
neural_net.generate 3,1,2
|
206
|
+
end
|
207
|
+
@population << neural_net
|
208
|
+
end
|
209
|
+
save_population name
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
|
data/deepbeige.gemspec
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{deepbeige}
|
8
|
+
s.version = "0.2.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["David Bochenski"]
|
12
|
+
s.date = %q{2010-09-04}
|
13
|
+
s.description = %q{An AI learning program that plays board games}
|
14
|
+
s.email = %q{david@bochenski.co.uk}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"arena.rb",
|
27
|
+
"deep_beige.rb",
|
28
|
+
"deepbeige.gemspec",
|
29
|
+
"game.rb",
|
30
|
+
"human.rb",
|
31
|
+
"lib/deepbeige.rb",
|
32
|
+
"main.rb",
|
33
|
+
"match.rb",
|
34
|
+
"neural_net.rb",
|
35
|
+
"node.rb",
|
36
|
+
"noughts_and_crosses.rb",
|
37
|
+
"pick_a_number.rb",
|
38
|
+
"player.rb",
|
39
|
+
"population/best.txt",
|
40
|
+
"table.rb",
|
41
|
+
"test/helper.rb",
|
42
|
+
"test/test_deepbeige.rb",
|
43
|
+
"tournament.rb",
|
44
|
+
"ui/Rakefile",
|
45
|
+
"ui/config/build.yml",
|
46
|
+
"ui/lib/application.rb",
|
47
|
+
"ui/lib/menu.rb",
|
48
|
+
"ui/resources/DeepBeige.icns"
|
49
|
+
]
|
50
|
+
s.homepage = %q{http://github.com/bochenski/deepbeige}
|
51
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
52
|
+
s.require_paths = ["lib"]
|
53
|
+
s.rubygems_version = %q{1.3.7}
|
54
|
+
s.summary = %q{An AI learning program that plays board games}
|
55
|
+
s.test_files = [
|
56
|
+
"test/helper.rb",
|
57
|
+
"test/test_deepbeige.rb"
|
58
|
+
]
|
59
|
+
|
60
|
+
if s.respond_to? :specification_version then
|
61
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
62
|
+
s.specification_version = 3
|
63
|
+
|
64
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
65
|
+
s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
66
|
+
else
|
67
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
68
|
+
end
|
69
|
+
else
|
70
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
data/game.rb
ADDED
data/human.rb
ADDED
data/lib/deepbeige.rb
ADDED
File without changes
|
data/main.rb
ADDED
@@ -0,0 +1,114 @@
|
|
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
|
+
play_game game, me, db, []
|
31
|
+
end
|
32
|
+
|
33
|
+
def player_vs_player game
|
34
|
+
p1 = Human.new game
|
35
|
+
p2 = Human.new game
|
36
|
+
play_game game, p1,p2, []
|
37
|
+
end
|
38
|
+
|
39
|
+
def options
|
40
|
+
puts
|
41
|
+
puts "What would you like to do?"
|
42
|
+
puts " 1. Player vs DeepBeige"
|
43
|
+
puts " 2. Train DeepBeige"
|
44
|
+
puts " 3. Reset DeepBeige"
|
45
|
+
puts " 4. Player vs Player"
|
46
|
+
puts " 5. Exit Application"
|
47
|
+
end
|
48
|
+
|
49
|
+
def game_choice
|
50
|
+
puts
|
51
|
+
puts "Which Game?"
|
52
|
+
puts " 1. Noughts and Crosses"
|
53
|
+
puts " 2. Pick A Number"
|
54
|
+
game = nil
|
55
|
+
case gets.chop
|
56
|
+
when "1"
|
57
|
+
game = NoughtsAndCrosses.new
|
58
|
+
when "2"
|
59
|
+
game = PickANumber.new
|
60
|
+
else
|
61
|
+
puts "Sorry, didn't understand you there."
|
62
|
+
end
|
63
|
+
game
|
64
|
+
end
|
65
|
+
|
66
|
+
#This is the main execution loop of our program
|
67
|
+
def main
|
68
|
+
|
69
|
+
db = DeepBeige.new
|
70
|
+
|
71
|
+
version = "0.1.1"
|
72
|
+
puts
|
73
|
+
puts "Welcome to DeepBeige v#{version}"
|
74
|
+
|
75
|
+
exit = false
|
76
|
+
until exit do
|
77
|
+
options
|
78
|
+
case input = gets.chop
|
79
|
+
when "1"
|
80
|
+
game = game_choice
|
81
|
+
player_vs_deepbeige db, game
|
82
|
+
when "2"
|
83
|
+
game = game_choice
|
84
|
+
puts "How Many Generations?"
|
85
|
+
generations = gets.chop.to_i
|
86
|
+
db.train generations, game
|
87
|
+
when "3"
|
88
|
+
game = game_choice
|
89
|
+
db.learn game
|
90
|
+
puts "DeepBeige Reset sucessfully"
|
91
|
+
when "4"
|
92
|
+
game = game_choice
|
93
|
+
player_vs_player game
|
94
|
+
when "5"
|
95
|
+
puts "Bye Bye"
|
96
|
+
exit = true
|
97
|
+
else
|
98
|
+
puts "Sorry I didn't understand you"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
main unless $Cocoa
|
104
|
+
|
105
|
+
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
|
112
|
+
|
113
|
+
|
114
|
+
|
data/match.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#A match is a series of games between players
|
2
|
+
require 'rubygems'
|
3
|
+
require 'uuid'
|
4
|
+
require 'table'
|
5
|
+
|
6
|
+
class Match
|
7
|
+
def initialize players, first_to, game
|
8
|
+
@game = game
|
9
|
+
@players = players
|
10
|
+
@first_to = first_to
|
11
|
+
@player_wins = {}
|
12
|
+
players.each do |player|
|
13
|
+
@player_wins[player.id] = 0
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def play
|
18
|
+
while leading_player[1] < @first_to
|
19
|
+
game = @game.class.new
|
20
|
+
table = Table.new game, @players
|
21
|
+
table.play_game
|
22
|
+
if game.winner
|
23
|
+
@player_wins[@players[game.winner].id] += 2
|
24
|
+
p "#{game.winner} won that game and has #{ @player_wins[@players[game.winner].id] } wins"
|
25
|
+
p "leading player is #{leading_player[0]} with #{leading_player[1]} wins"
|
26
|
+
elsif game.drawn?
|
27
|
+
@players.each do |player|
|
28
|
+
@player_wins[player.id] += 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
gets
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def result
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def leading_player
|
40
|
+
leaderboard = @player_wins.sort_by {|id, count| count}
|
41
|
+
leaderboard.last
|
42
|
+
end
|
43
|
+
end
|