rubyneat 0.3.5.alpha.6 → 0.4.0.alpha.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.irbrc +2 -0
- data/.ruby-version +1 -0
- data/.semver +3 -3
- data/Gemfile +7 -8
- data/Gemfile.lock +43 -42
- data/Gemfile.lock.orig +161 -0
- data/Gemfile.orig +54 -0
- data/README.md +35 -4
- data/Rakefile +4 -3
- data/lib/rubyneat.rb +4 -3
- data/lib/rubyneat/cli.rb +3 -1
- data/lib/rubyneat/cli/console.rb +18 -0
- data/lib/rubyneat/cli/main.rb +3 -5
- data/lib/rubyneat/cli/templates/generate/.irbrc.tt +3 -0
- data/lib/rubyneat/critter.rb +2 -2
- data/lib/rubyneat/default_neat.rb +1 -1
- data/lib/rubyneat/dsl.rb +13 -12
- data/lib/rubyneat/evaluator.rb +10 -6
- data/lib/rubyneat/evolver.rb +2 -1
- data/lib/rubyneat/expressor.rb +1 -1
- data/lib/rubyneat/neuron.rb +1 -1
- data/lib/rubyneat/population.rb +12 -45
- data/lib/rubyneat/reporting.rb +113 -0
- data/lib/rubyneat/rubyneat.rb +189 -31
- data/rubyneat.gemspec +21 -4
- metadata +53 -4
data/lib/rubyneat/rubyneat.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
require 'semver'
|
1
2
|
require 'distribution'
|
2
3
|
require 'yaml'
|
3
4
|
require 'logger'
|
4
5
|
require 'awesome_print'
|
5
6
|
require 'deep_dive'
|
7
|
+
require 'queue_ding'
|
6
8
|
|
7
9
|
=begin rdoc
|
8
10
|
= RubyNEAT -- a Ruby Implementation of the Neural Evolution of Augmenting Topologies.
|
@@ -90,9 +92,13 @@ module NEAT
|
|
90
92
|
#$log.ap ob
|
91
93
|
end
|
92
94
|
|
93
|
-
# Basis of all NEAT objects
|
95
|
+
# Basis of all NEAT objects.
|
96
|
+
# NeatOb has support for NEAT attributes with
|
97
|
+
# special support for hooks and queues.
|
94
98
|
class NeatOb
|
95
99
|
include DeepDive
|
100
|
+
extend QueueDing
|
101
|
+
|
96
102
|
exclude :controller, :name
|
97
103
|
|
98
104
|
# Designation of this particular object instance
|
@@ -125,6 +131,134 @@ module NEAT
|
|
125
131
|
def to_s
|
126
132
|
"%s<%s>" % [self.class, self.name]
|
127
133
|
end
|
134
|
+
|
135
|
+
|
136
|
+
class << self
|
137
|
+
# Defaultable attributes of neat attributes.
|
138
|
+
#
|
139
|
+
# If hooks: true is given, two hook functions are
|
140
|
+
# created:
|
141
|
+
## <sym>_add() -- add a hook
|
142
|
+
## <sym>_set() -- set a hook, overwriting all other hooks set or added.
|
143
|
+
## <sym>_clear -- clear all hooks
|
144
|
+
## <sym>_none? -- return true if no hooks are defined.
|
145
|
+
## <sym>_one? -- return true if exactly hook is defined.
|
146
|
+
## <sym>_hook() -- for passing unnamed parameters to a singular hook.
|
147
|
+
## <sym>_np_hook() -- for passing unnamed parameters to a singular hook.
|
148
|
+
## <sym>_hook_itself() -- for getting the proc reference to the hook.
|
149
|
+
## <sym>_hooks() -- for passing unnamed parameters.
|
150
|
+
## <sym>_np_hooks() -- for passing a named parameter list.
|
151
|
+
#
|
152
|
+
# For *_hook(), the function returns the single result.
|
153
|
+
# For *_hooks(), the hook function return an array of results
|
154
|
+
# from all the actual registered hooks called.
|
155
|
+
def attr_neat(sym,
|
156
|
+
default: nil,
|
157
|
+
cloneable: nil,
|
158
|
+
hooks: false,
|
159
|
+
queue: false)
|
160
|
+
svar = "@#{sym}"
|
161
|
+
|
162
|
+
# Guess what clonable should be.
|
163
|
+
# This is meant to cover "90%" of the cases.
|
164
|
+
cloneable = case
|
165
|
+
when default.nil?
|
166
|
+
false
|
167
|
+
when default.kind_of?(Numeric)
|
168
|
+
false
|
169
|
+
else
|
170
|
+
true
|
171
|
+
end if cloneable.nil?
|
172
|
+
|
173
|
+
# Sanity checks
|
174
|
+
raise NeatException("Both hooks and queue cannot both be set for #{sym}.") if hooks and queue
|
175
|
+
raise NeatException("Defaults cannot be defined for hooks and queues for #{sym}.") if (hooks or queue) and not default.nil?
|
176
|
+
|
177
|
+
if hooks
|
178
|
+
default = []
|
179
|
+
cloneable = true
|
180
|
+
hook_setup sym
|
181
|
+
end
|
182
|
+
|
183
|
+
if queue
|
184
|
+
default = QDing.new
|
185
|
+
cloneable = true
|
186
|
+
queue_setup sym
|
187
|
+
end
|
188
|
+
|
189
|
+
define_method("#{sym}=") do |v|
|
190
|
+
instance_variable_set(svar, v)
|
191
|
+
end unless hooks or queue
|
192
|
+
|
193
|
+
# TODO: Enhance this getter method for performance.
|
194
|
+
define_method(sym) do
|
195
|
+
instance_variable_set(svar,
|
196
|
+
instance_variable_get(svar) ||
|
197
|
+
((cloneable) ? default.clone
|
198
|
+
: default))
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
def hook_setup(sym)
|
204
|
+
define_method("#{sym}_add") do |&hook|
|
205
|
+
send(sym) << hook
|
206
|
+
end
|
207
|
+
|
208
|
+
define_method("#{sym}_set") do |&hook|
|
209
|
+
send(sym).clear
|
210
|
+
send(sym) << hook
|
211
|
+
end
|
212
|
+
|
213
|
+
define_method("#{sym}_clear") do
|
214
|
+
send(sym).clear
|
215
|
+
end
|
216
|
+
|
217
|
+
define_method("#{sym}_none?") do
|
218
|
+
send(sym).empty?
|
219
|
+
end
|
220
|
+
|
221
|
+
define_method("#{sym}_one?") do
|
222
|
+
send(sym).size == 1
|
223
|
+
end
|
224
|
+
|
225
|
+
# hooks with named parameters
|
226
|
+
define_method("#{sym}_np_hooks") do |**hparams|
|
227
|
+
send(sym).map{|funct| funct.(**hparams)}
|
228
|
+
end
|
229
|
+
|
230
|
+
# hooks with traditional parameters
|
231
|
+
define_method("#{sym}_hooks") do |*params|
|
232
|
+
send(sym).map{|funct| funct.(*params)}
|
233
|
+
end
|
234
|
+
|
235
|
+
# TODO: DRY up the following functions, which does size checking in exacly the same way.
|
236
|
+
# Single hook with named parameters
|
237
|
+
define_method("#{sym}_np_hook") do |**hparams|
|
238
|
+
sz = send(sym).size
|
239
|
+
raise NeatException.new("#{sym}_np_hook must have exactly one hook (#{sz})") unless sz == 1
|
240
|
+
send(sym).map{|funct| funct.(**hparams)}.first
|
241
|
+
end
|
242
|
+
|
243
|
+
# Single hook with traditional parameters
|
244
|
+
define_method("#{sym}_hook") do |*params|
|
245
|
+
sz = send(sym).size
|
246
|
+
raise NeatException.new("#{sym}_hook must have exactly one hook (#{sz})") unless sz == 1
|
247
|
+
send(sym).map{|funct| funct.(*params)}.first
|
248
|
+
end
|
249
|
+
|
250
|
+
# Get the singular hook function
|
251
|
+
define_method("#{sym}_hook_itself") do
|
252
|
+
sz = send(sym).size
|
253
|
+
raise NeatException.new("#{sym}_hook_itself must have exactly one hook (#{sz})") unless sz == 1
|
254
|
+
send(sym).first
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def queue_setup(sym)
|
259
|
+
# Add boilerplate code for queues here.
|
260
|
+
end
|
261
|
+
end
|
128
262
|
end
|
129
263
|
|
130
264
|
class NeatException < Exception
|
@@ -161,9 +295,24 @@ module NEAT
|
|
161
295
|
# a type of "World", if you will, for the entire enterprise.
|
162
296
|
#
|
163
297
|
# Your application shall only have one Controller.
|
298
|
+
#
|
299
|
+
# FIXME: The function hooks really should be able to take more
|
300
|
+
# FIXME: than one hook! we don't need that functionality right
|
301
|
+
# FIXME: now. Also, the Controller 'god' object itself will need
|
302
|
+
# FIXME: to undergo some refactorization so that we can have many
|
303
|
+
# FIXME: of them for HyperNEAT, co-evolution, etc.
|
304
|
+
#
|
305
|
+
# FIXME: An alternative approach would be to have demigod objects
|
306
|
+
# FIXME: where the controller would lord it over them all. Attention
|
307
|
+
# FIXME: must also be given to Rubinius and JRuby so that we can
|
308
|
+
# FIXME: run under multiple cores.
|
164
309
|
class Controller < NeatOb
|
310
|
+
# Version of RubyNEAT runing
|
311
|
+
attr_neat :version, default: SemVer.find.format("%M.%m.%p%s")
|
312
|
+
attr_neat :neater, default: '--unspecified--'
|
313
|
+
|
165
314
|
# global innovation number
|
166
|
-
|
315
|
+
attr_neat :glob_innov_num, default: 0, cloneable: false
|
167
316
|
|
168
317
|
# current sequence number being evaluated
|
169
318
|
attr_reader :seq_num
|
@@ -193,30 +342,36 @@ module NEAT
|
|
193
342
|
## 2 - really verbose
|
194
343
|
## 3 - maximally verbose
|
195
344
|
# Use in conjunction with log.debug
|
196
|
-
|
345
|
+
attr_neat :verbosity, default: 1
|
197
346
|
|
198
347
|
# Query function that Critters shall call.
|
199
|
-
|
348
|
+
attr_neat :query_func, hooks: true
|
200
349
|
|
201
350
|
# Fitness function that Critters shall be rated on.
|
202
|
-
|
351
|
+
attr_neat :fitness_func, hooks: true
|
203
352
|
|
204
353
|
# Recurrence function that Critters will yield to.
|
205
|
-
|
354
|
+
attr_neat :recurrence_func, hooks: true
|
206
355
|
|
207
356
|
# Compare function for fitness
|
208
357
|
# Cost function for integrating in the cost to the fitness scalar.
|
209
|
-
|
358
|
+
attr_neat :compare_func, hooks: true
|
359
|
+
attr_neat :cost_func, hooks: true
|
360
|
+
attr_neat :stop_on_fit_func, hooks: true
|
210
361
|
|
211
362
|
# End run function to call at the end of each generational run
|
212
363
|
# Also report_hook to dump reports for the user, etc.
|
213
|
-
|
364
|
+
attr_neat :end_run, hooks: true
|
365
|
+
attr_neat :report, hooks: true
|
366
|
+
|
367
|
+
# Hook to handle pre_exit functionality
|
368
|
+
attr_neat :pre_exit, hooks: true
|
214
369
|
|
215
370
|
# Logger object for all of RubyNEAT
|
216
371
|
attr_reader :log
|
217
372
|
|
218
373
|
# Various parameters affecting evolution.
|
219
|
-
# Based somewhat on the C version of NEAT.
|
374
|
+
# Based somewhat on the Ken Stanley C version of NEAT.
|
220
375
|
# TODO not all of these parameters are implemented yet!!!
|
221
376
|
class NeatSettings < NeatOb
|
222
377
|
## RubyNEAT specific
|
@@ -255,10 +410,10 @@ module NEAT
|
|
255
410
|
attr_accessor :mate_singlepoint_prob
|
256
411
|
|
257
412
|
# Maximum number of generations to run, if given.
|
258
|
-
|
413
|
+
attr_neat :max_generations, default: 1000
|
259
414
|
|
260
|
-
#
|
261
|
-
|
415
|
+
# Maximum number of populations to maintain in the history buffer.
|
416
|
+
attr_neat :max_population_history, default: 10
|
262
417
|
|
263
418
|
attr_accessor :mutate_add_gene_prob
|
264
419
|
attr_accessor :mutate_add_neuron_prob
|
@@ -270,9 +425,9 @@ module NEAT
|
|
270
425
|
|
271
426
|
# For gene weights perturbations and changes (complete overwrites)
|
272
427
|
attr_accessor :mutate_perturb_gene_weights_prob,
|
273
|
-
|
274
|
-
|
275
|
-
|
428
|
+
:mutate_perturb_gene_weights_sd,
|
429
|
+
:mutate_change_gene_weights_prob,
|
430
|
+
:mutate_change_gene_weights_sd
|
276
431
|
|
277
432
|
attr_accessor :mutate_neuron_trait_prob
|
278
433
|
attr_accessor :mutate_only_prob
|
@@ -284,7 +439,7 @@ module NEAT
|
|
284
439
|
|
285
440
|
# fitness costs, if given, use in the computation of fitness
|
286
441
|
# AFTER the overall fitness for the applied stimuli have been
|
287
|
-
#
|
442
|
+
# calculated.
|
288
443
|
attr_accessor :fitness_cost_per_neuron
|
289
444
|
attr_accessor :fitness_cost_per_gene
|
290
445
|
|
@@ -292,7 +447,8 @@ module NEAT
|
|
292
447
|
# grow to the bigger population size
|
293
448
|
attr_accessor :start_population_size, :population_size
|
294
449
|
|
295
|
-
|
450
|
+
attr_neat :start_sequence_at, default: 0
|
451
|
+
attr_neat :end_sequence_at, default: 100
|
296
452
|
|
297
453
|
attr_accessor :print_every
|
298
454
|
attr_accessor :recur_only_prob
|
@@ -329,10 +485,6 @@ module NEAT
|
|
329
485
|
# Set up defaults for mandatory entries.
|
330
486
|
def initialize
|
331
487
|
super
|
332
|
-
@start_sequence_at = 0
|
333
|
-
@end_sequence_at = 100
|
334
|
-
@max_generations = 1000
|
335
|
-
|
336
488
|
# Default operators
|
337
489
|
@evaluator = Evaluator.new self
|
338
490
|
@expressor = Expressor.new self
|
@@ -349,8 +501,6 @@ module NEAT
|
|
349
501
|
parameters: NeatSettings.new,
|
350
502
|
&block)
|
351
503
|
super(self)
|
352
|
-
@verbosity = 1
|
353
|
-
@glob_innov_num = 0
|
354
504
|
@gaussian = Distribution::Normal.rng
|
355
505
|
@population_history = []
|
356
506
|
@evolver = Evolver.new self
|
@@ -376,19 +526,20 @@ module NEAT
|
|
376
526
|
block.(self) unless block.nil?
|
377
527
|
end
|
378
528
|
|
379
|
-
def new_innovation ;
|
529
|
+
def new_innovation ; self.glob_innov_num += 1 ; end
|
380
530
|
def gaussian ; @gaussian.() ; end
|
381
531
|
|
382
532
|
# Run this evolution.
|
383
533
|
def run
|
384
534
|
pre_run_initialize
|
385
535
|
(1..@parms.max_generations).each do |gen_number|
|
386
|
-
@generation_num = gen_number
|
536
|
+
@generation_num = gen_number # must be set first
|
387
537
|
@population_history << unless @population.nil?
|
388
538
|
@population
|
389
539
|
else
|
390
540
|
@population = @population_class.new(self)
|
391
541
|
end
|
542
|
+
@population.generation = gen_number
|
392
543
|
@population_history.shift unless @population_history.size <= @parms.max_population_history
|
393
544
|
@population.mutate!
|
394
545
|
@population.express!
|
@@ -403,22 +554,22 @@ module NEAT
|
|
403
554
|
@population.analyze!
|
404
555
|
@population.speciate!
|
405
556
|
|
406
|
-
$log.debug @population.dump_s unless
|
557
|
+
$log.debug @population.dump_s unless self.verbosity < 3
|
407
558
|
|
408
559
|
new_pop = @population.evolve
|
409
560
|
|
410
561
|
## Report hook for evaluation
|
411
|
-
|
562
|
+
report_hooks(@population.report)
|
412
563
|
|
413
564
|
## Exit if fitness criteria is reached
|
414
565
|
#FIXME handle this exit condition better!!!!!
|
415
|
-
|
566
|
+
exit_neat if stop_on_fit_func_hook(@population.report.last[:fitness], self) unless stop_on_fit_func_none?
|
416
567
|
|
417
568
|
## Evolve population
|
418
569
|
@population = new_pop
|
419
570
|
|
420
571
|
## Finish up this run
|
421
|
-
|
572
|
+
end_run_hooks(self)
|
422
573
|
end
|
423
574
|
end
|
424
575
|
|
@@ -428,6 +579,13 @@ module NEAT
|
|
428
579
|
@evaluator = @evaluator_class.new(self) if @evaluator.nil?
|
429
580
|
@evolver = @evolver_class.new(self) if @evolver.nil?
|
430
581
|
end
|
582
|
+
|
583
|
+
# Allow us to hook in pre-exit functionality here
|
584
|
+
# This function shall never return.
|
585
|
+
def exit_neat
|
586
|
+
pre_exit_hook(self) unless pre_exit_none?
|
587
|
+
exit
|
588
|
+
end
|
431
589
|
end
|
432
590
|
|
433
591
|
@controller = Controller.new
|
@@ -437,5 +595,5 @@ module NEAT
|
|
437
595
|
end
|
438
596
|
|
439
597
|
# We put all the internal requires at the end to avoid conflicts.
|
440
|
-
|
441
|
-
|
598
|
+
require_relative 'neuron'
|
599
|
+
require_relative 'population'
|
data/rubyneat.gemspec
CHANGED
@@ -2,35 +2,41 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: rubyneat 0.
|
5
|
+
# stub: rubyneat 0.4.0.alpha.3 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "rubyneat"
|
9
|
-
s.version = "0.
|
9
|
+
s.version = "0.4.0.alpha.3"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["Fred Mitchell"]
|
14
|
-
s.date = "2014-
|
14
|
+
s.date = "2014-08-03"
|
15
15
|
s.description = "\n RubyNEAT -- Neural Evolution of Augmenting Topologies for Ruby.\n By way of an enhanced form of Genetic Algorithms -- the NEAT algorithm,\n populations of neural nets are evolved to handle pre-defined goals.\n\n RubyNEAT is the first implementation of the NEAT algorithm for Ruby, and\n it leverages Ruby's power to implement the NEAT algorithm in a way that would\n be difficult to do in other languages. The 'activation function' is largely\n standalone. Basically, activation is achieved by functional programming.\n\n Meaning, once your network is evolved, you can extract it as source code you\n can then utilize without the RubyNEAT engine.\n\n RubyNEAT can be used for nearly any Machine Learning task you can dream of,\n because it's also extensible and modular. See http://rubyneat.com for the\n details.\n "
|
16
|
-
s.email = "
|
16
|
+
s.email = "lordalveric@yahoo.com"
|
17
17
|
s.executables = ["neat"]
|
18
18
|
s.extra_rdoc_files = [
|
19
19
|
"README.md"
|
20
20
|
]
|
21
21
|
s.files = [
|
22
22
|
".directory",
|
23
|
+
".irbrc",
|
24
|
+
".ruby-version",
|
23
25
|
".semver",
|
24
26
|
"Gemfile",
|
25
27
|
"Gemfile.lock",
|
28
|
+
"Gemfile.lock.orig",
|
29
|
+
"Gemfile.orig",
|
26
30
|
"README.md",
|
27
31
|
"Rakefile",
|
28
32
|
"bin/neat",
|
29
33
|
"config/application.rb",
|
30
34
|
"lib/rubyneat.rb",
|
31
35
|
"lib/rubyneat/cli.rb",
|
36
|
+
"lib/rubyneat/cli/console.rb",
|
32
37
|
"lib/rubyneat/cli/generate.rb",
|
33
38
|
"lib/rubyneat/cli/main.rb",
|
39
|
+
"lib/rubyneat/cli/templates/generate/.irbrc.tt",
|
34
40
|
"lib/rubyneat/cli/templates/generate/Gemfile.tt",
|
35
41
|
"lib/rubyneat/cli/templates/generate/README.md.tt",
|
36
42
|
"lib/rubyneat/cli/templates/generate/bin/neat.tt",
|
@@ -45,12 +51,14 @@ Gem::Specification.new do |s|
|
|
45
51
|
"lib/rubyneat/graph.rb",
|
46
52
|
"lib/rubyneat/neuron.rb",
|
47
53
|
"lib/rubyneat/population.rb",
|
54
|
+
"lib/rubyneat/reporting.rb",
|
48
55
|
"lib/rubyneat/rubyneat.rb",
|
49
56
|
"rubyneat.gemspec",
|
50
57
|
"spec/lib/rubyneat/rubyneat_spec.rb"
|
51
58
|
]
|
52
59
|
s.homepage = "http://rubyneat.com"
|
53
60
|
s.licenses = ["MIT"]
|
61
|
+
s.required_ruby_version = Gem::Requirement.new(">= 2.0")
|
54
62
|
s.rubygems_version = "2.2.2"
|
55
63
|
s.summary = "RubyNEAT NeuralEvolution of Augmenting Topologies"
|
56
64
|
|
@@ -65,6 +73,9 @@ Gem::Specification.new do |s|
|
|
65
73
|
s.add_runtime_dependency(%q<thor>, ["~> 0"])
|
66
74
|
s.add_runtime_dependency(%q<awesome_print>, ["~> 1"])
|
67
75
|
s.add_runtime_dependency(%q<deep_dive>, ["~> 0"])
|
76
|
+
s.add_runtime_dependency(%q<bond>, ["~> 0.5"])
|
77
|
+
s.add_runtime_dependency(%q<rb-readline>, ["~> 0.5"])
|
78
|
+
s.add_runtime_dependency(%q<queue_ding>, [">= 0"])
|
68
79
|
s.add_runtime_dependency(%q<gosu>, ["~> 0"])
|
69
80
|
s.add_runtime_dependency(%q<rubyvis>, ["~> 0"])
|
70
81
|
s.add_development_dependency(%q<rspec>, ["~> 2"])
|
@@ -82,6 +93,9 @@ Gem::Specification.new do |s|
|
|
82
93
|
s.add_dependency(%q<thor>, ["~> 0"])
|
83
94
|
s.add_dependency(%q<awesome_print>, ["~> 1"])
|
84
95
|
s.add_dependency(%q<deep_dive>, ["~> 0"])
|
96
|
+
s.add_dependency(%q<bond>, ["~> 0.5"])
|
97
|
+
s.add_dependency(%q<rb-readline>, ["~> 0.5"])
|
98
|
+
s.add_dependency(%q<queue_ding>, [">= 0"])
|
85
99
|
s.add_dependency(%q<gosu>, ["~> 0"])
|
86
100
|
s.add_dependency(%q<rubyvis>, ["~> 0"])
|
87
101
|
s.add_dependency(%q<rspec>, ["~> 2"])
|
@@ -100,6 +114,9 @@ Gem::Specification.new do |s|
|
|
100
114
|
s.add_dependency(%q<thor>, ["~> 0"])
|
101
115
|
s.add_dependency(%q<awesome_print>, ["~> 1"])
|
102
116
|
s.add_dependency(%q<deep_dive>, ["~> 0"])
|
117
|
+
s.add_dependency(%q<bond>, ["~> 0.5"])
|
118
|
+
s.add_dependency(%q<rb-readline>, ["~> 0.5"])
|
119
|
+
s.add_dependency(%q<queue_ding>, [">= 0"])
|
103
120
|
s.add_dependency(%q<gosu>, ["~> 0"])
|
104
121
|
s.add_dependency(%q<rubyvis>, ["~> 0"])
|
105
122
|
s.add_dependency(%q<rspec>, ["~> 2"])
|