answer-factory 0.0.1
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/LICENSE.txt +21 -0
- data/Rakefile +29 -0
- data/Thorfile +79 -0
- data/VERSION +1 -0
- data/_spikes/old_vs_new_dominated_by?.rb +45 -0
- data/config/database.yml +9 -0
- data/lib/answer-factory.rb +14 -0
- data/lib/answers/answer.rb +126 -0
- data/lib/answers/batch.rb +49 -0
- data/lib/factories/factory.rb +53 -0
- data/lib/factories/workstation.rb +33 -0
- data/lib/operators/basic_operators.rb +240 -0
- data/lib/operators/evaluators.rb +113 -0
- data/lib/operators/samplers_and_selectors.rb +131 -0
- data/pkg/nudgegp-0.0.1.gem +0 -0
- data/readme.md +29 -0
- data/spec/answer_spec.rb +412 -0
- data/spec/batch_spec.rb +98 -0
- data/spec/config_spec.rb +94 -0
- data/spec/factories/factory_spec.rb +86 -0
- data/spec/factories/workstation_spec.rb +139 -0
- data/spec/operators/any_one_sampler_spec.rb +39 -0
- data/spec/operators/dominated_quantile_spec.rb +111 -0
- data/spec/operators/duplicate_genomes_spec.rb +35 -0
- data/spec/operators/evaluators/program_point_evaluator_spec.rb +43 -0
- data/spec/operators/evaluators/test_case_evaluator_spec.rb +129 -0
- data/spec/operators/infrastructure_spec.rb +45 -0
- data/spec/operators/most_dominated_subset_spec.rb +47 -0
- data/spec/operators/nondominated_subset_spec.rb +103 -0
- data/spec/operators/pointCrossover_spec.rb +60 -0
- data/spec/operators/pointDeletion_spec.rb +62 -0
- data/spec/operators/pointMutation_spec.rb +77 -0
- data/spec/operators/random_guess_spec.rb +77 -0
- data/spec/operators/resample_and_clone_spec.rb +60 -0
- data/spec/operators/resample_values_spec.rb +135 -0
- data/spec/operators/uniformBackboneCrossover_spec.rb +67 -0
- data/spec/spec_helper.rb +14 -0
- metadata +201 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2009 William Tozier
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |gemspec|
|
6
|
+
gemspec.name = "answer-factory"
|
7
|
+
gemspec.summary = "Genetic Programming in the Nudge language"
|
8
|
+
gemspec.description = "The pragmaticgp gem provides a simple framework for building, running and managing genetic programming experiments which automatically discover algorithms and equations to solve user-defined problems."
|
9
|
+
gemspec.email = "bill@vagueinnovation.com"
|
10
|
+
gemspec.homepage = "http://github.com/Vaguery/PragGP"
|
11
|
+
gemspec.authors = ["Bill Tozier", "Trek Glowacki", "Jesse Sielaff"]
|
12
|
+
|
13
|
+
gemspec.required_ruby_version = '>= 1.9.1'
|
14
|
+
|
15
|
+
gemspec.add_dependency('nudge', '>= 0.2')
|
16
|
+
gemspec.add_dependency('thor', '>= 0.13')
|
17
|
+
gemspec.add_dependency('couchrest', '>= 0.33')
|
18
|
+
gemspec.add_dependency('fakeweb', '>= 0.33')
|
19
|
+
gemspec.add_dependency('sinatra', '>= 0.9.4')
|
20
|
+
gemspec.add_dependency('activesupport', '>= 2.3.5')
|
21
|
+
|
22
|
+
#files
|
23
|
+
gemspec.files.exclude('_spikes/**')
|
24
|
+
end
|
25
|
+
|
26
|
+
Jeweler::GemcutterTasks.new
|
27
|
+
rescue LoadError
|
28
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
29
|
+
end
|
data/Thorfile
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'lib/answer-factory'
|
3
|
+
|
4
|
+
class Extend_Nudge < Thor::Group
|
5
|
+
include Thor::Actions
|
6
|
+
|
7
|
+
# Define arguments and options
|
8
|
+
argument :project_name
|
9
|
+
class_option :test_framework, :default => :rspec
|
10
|
+
desc "Creates a new project folder structure for Nudge types, instructions and specs"
|
11
|
+
|
12
|
+
|
13
|
+
def self.source_root
|
14
|
+
File.dirname(__FILE__)
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_project_folder
|
18
|
+
dirname = "#{Extend_Nudge.source_root}/#{project_name}"
|
19
|
+
puts dirname
|
20
|
+
if Dir.exist?(dirname) then
|
21
|
+
puts "project directory 'dirname' already exists"
|
22
|
+
else
|
23
|
+
empty_directory(dirname)
|
24
|
+
empty_directory("#{dirname}/lib")
|
25
|
+
empty_directory("#{dirname}/lib/instructions")
|
26
|
+
empty_directory("#{dirname}/lib/interpreter/types")
|
27
|
+
empty_directory("#{dirname}/spec")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
class New_Nudge_Type < Thor::Group
|
34
|
+
include Thor::Actions
|
35
|
+
|
36
|
+
# Define arguments and options
|
37
|
+
argument :typename_root
|
38
|
+
class_option :test_framework, :default => :rspec
|
39
|
+
desc "Creates a new NudgeType class definition file, typical instructions, and rspec files"
|
40
|
+
|
41
|
+
|
42
|
+
def self.source_root
|
43
|
+
File.dirname(__FILE__)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.type_name(string)
|
47
|
+
string.camelize + "Type"
|
48
|
+
end
|
49
|
+
|
50
|
+
def nudge_gem_path
|
51
|
+
Nudge.gem_root
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_lib_file
|
55
|
+
@camelcased_type_name = New_Nudge_Type.type_name(typename_root)
|
56
|
+
filename = "#{@camelcased_type_name}.rb"
|
57
|
+
template("#{nudge_gem_path}/templates/nudge_type_class.erb", "#{New_Nudge_Type.source_root}/lib/types/#{filename}")
|
58
|
+
end
|
59
|
+
|
60
|
+
def create_lib_spec
|
61
|
+
@camelcased_type_name = New_Nudge_Type.type_name(typename_root)
|
62
|
+
filename = "#{@camelcased_type_name}_spec.rb"
|
63
|
+
template("#{nudge_gem_path}/templates/nudge_type_spec.erb", "#{New_Nudge_Type.source_root}/spec/#{filename}")
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_instructions
|
67
|
+
suite = ["define", "equal_q", "duplicate", "flush", "pop",
|
68
|
+
"random", "rotate", "shove", "swap", "yank", "yankdup"]
|
69
|
+
|
70
|
+
suite.each do |inst|
|
71
|
+
@core = "#{typename_root}_#{inst}"
|
72
|
+
filename = "#{@core}.rb"
|
73
|
+
@instname = "#{@core.camelize}Instruction"
|
74
|
+
@type = typename_root
|
75
|
+
@camelized_type = New_Nudge_Type.type_name(typename_root)
|
76
|
+
template("#{nudge_gem_path}/templates/nudge_#{inst}_instruction.erb", "#{New_Nudge_Type.source_root}/lib/instructions/#{filename}")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'nudge'
|
2
|
+
require '../lib/answer-factory'
|
3
|
+
include Nudge
|
4
|
+
include NudgeGP
|
5
|
+
|
6
|
+
|
7
|
+
d1 = Answer.new("")
|
8
|
+
d2 = Answer.new("")
|
9
|
+
|
10
|
+
t1 = Time.now
|
11
|
+
100000.times do
|
12
|
+
d1.scores = {"a" => rand(20)-10, "b" => rand(20)-10,"c" => rand(20)-10,"d" => rand(20)-10,"e" => rand(20)-10,"e1" => rand(20)-10,"e2" => rand(20)-10,"e3" => rand(20)-10,"e4" => rand(20)-10,"e5" => rand(20)-10,"e6" => rand(20)-10,"edfsgdf" => rand(20)-10,"se" => rand(20)-10}
|
13
|
+
d2.scores = {"a" => rand(20)-10, "b" => rand(20)-10,"c" => rand(20)-10,"d" => rand(20)-10,"e" => rand(20)-10,"e1" => rand(20)-10,"e2" => rand(20)-10,"e3" => rand(20)-10,"e4" => rand(20)-10,"e5" => rand(20)-10,"e6" => rand(20)-10,"edfsgdf" => rand(20)-10,"se" => rand(20)-10}
|
14
|
+
d1.dominated_by?(d2)
|
15
|
+
end
|
16
|
+
puts "#{Time.now - t1} sec from the new one"
|
17
|
+
|
18
|
+
|
19
|
+
class Answer
|
20
|
+
def dominated_by?(other, template = self.known_criteria)
|
21
|
+
return false unless (known_criteria == other.known_criteria)
|
22
|
+
|
23
|
+
noWorse = true
|
24
|
+
somewhatBetter = false
|
25
|
+
template.each do |score|
|
26
|
+
if self.scores[score] && other.scores[score]
|
27
|
+
noWorse &&= (self.scores[score] >= other.scores[score])
|
28
|
+
somewhatBetter ||= (self.scores[score] > other.scores[score])
|
29
|
+
else
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
return noWorse && somewhatBetter
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
t1 = Time.now
|
39
|
+
100000.times do
|
40
|
+
d1.scores = {"a" => rand(20)-10, "b" => rand(20)-10,"c" => rand(20)-10,"d" => rand(20)-10,"e" => rand(20)-10,"e1" => rand(20)-10,"e2" => rand(20)-10,"e3" => rand(20)-10,"e4" => rand(20)-10,"e5" => rand(20)-10,"e6" => rand(20)-10,"edfsgdf" => rand(20)-10,"se" => rand(20)-10}
|
41
|
+
d2.scores = {"a" => rand(20)-10, "b" => rand(20)-10,"c" => rand(20)-10,"d" => rand(20)-10,"e" => rand(20)-10,"e1" => rand(20)-10,"e2" => rand(20)-10,"e3" => rand(20)-10,"e4" => rand(20)-10,"e5" => rand(20)-10,"e6" => rand(20)-10,"edfsgdf" => rand(20)-10,"se" => rand(20)-10}
|
42
|
+
d1.dominated_by?(d2)
|
43
|
+
end
|
44
|
+
puts "#{Time.now - t1} sec from the old one"
|
45
|
+
|
data/config/database.yml
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# realy wants to use CouchDB version 0.11 or higher
|
2
|
+
|
3
|
+
# edit this to fit your particular CouchDB configuration
|
4
|
+
main_database:
|
5
|
+
db_root: http://127.0.0.1:5984 # URI of your CouchDB server
|
6
|
+
db_name: my_factory # root database handling your AnswerFactory:
|
7
|
+
tag_filter: _design/routing/_view/by_tag # don't edit this unless you also
|
8
|
+
# change how it's used in the codebase!
|
9
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), "/../lib")
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'nudge'
|
5
|
+
|
6
|
+
require 'answers/answer'
|
7
|
+
require 'answers/batch'
|
8
|
+
|
9
|
+
require 'operators/basic_operators'
|
10
|
+
require 'operators/samplers_and_selectors'
|
11
|
+
require 'operators/evaluators'
|
12
|
+
|
13
|
+
require 'factories/factory'
|
14
|
+
require 'factories/workstation'
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module NudgeGP
|
4
|
+
class Answer
|
5
|
+
attr_accessor :scores, :tags
|
6
|
+
attr_reader :draft_blueprint, :program, :timestamp, :ancestors
|
7
|
+
attr_reader :initialization_options, :progress
|
8
|
+
|
9
|
+
|
10
|
+
def initialize(blueprint, options = {})
|
11
|
+
raise ArgumentError unless
|
12
|
+
blueprint.kind_of?(String) || blueprint.kind_of?(NudgeProgram)
|
13
|
+
build_from_blueprint!(blueprint)
|
14
|
+
|
15
|
+
@scores = Hash.new do |hash, key|
|
16
|
+
raise ArgumentError, "scores must use symbols as keys" unless key.kind_of?(Symbol)
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
@timestamp = Time.now
|
20
|
+
@initialization_options = options
|
21
|
+
@progress = options[:progress] || 0
|
22
|
+
@ancestors = options[:ancestors] || []
|
23
|
+
@tags = Set.new(options[:tags]) || Set.new
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def build_from_blueprint!(blueprint)
|
28
|
+
if blueprint.kind_of?(String)
|
29
|
+
@draft_blueprint = blueprint
|
30
|
+
@program = NudgeProgram.new(blueprint)
|
31
|
+
else
|
32
|
+
@program = blueprint
|
33
|
+
@draft_blueprint = @program.blueprint
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def blueprint
|
39
|
+
@program.blueprint
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def parses?
|
44
|
+
@program.parses?
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def known_criteria
|
49
|
+
@scores.keys.sort
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def score_vector(ordering = known_criteria)
|
54
|
+
ordering.collect {|k| @scores[k]}
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def dominated_by?(other_answer, comparison_criteria = self.known_criteria)
|
59
|
+
|
60
|
+
return false unless (known_criteria & comparison_criteria) ==
|
61
|
+
(other_answer.known_criteria & comparison_criteria)
|
62
|
+
|
63
|
+
could_be_identical = true
|
64
|
+
|
65
|
+
comparison_criteria.each do |score|
|
66
|
+
return false if (my_score = self.scores[score]) < (other_score = other_answer.scores[score])
|
67
|
+
|
68
|
+
if could_be_identical
|
69
|
+
could_be_identical &&= (my_score == other_score)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
return !could_be_identical
|
74
|
+
rescue NoMethodError
|
75
|
+
false
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def points
|
80
|
+
@program.points
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
def delete_point_or_clone(which)
|
85
|
+
((1..self.points).include?(which)) ?
|
86
|
+
self.program.delete_point(which) :
|
87
|
+
self.program.deep_copy
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
def replace_point_or_clone(which, object)
|
92
|
+
if object.kind_of?(String)
|
93
|
+
prog = NudgeProgram.new(object)
|
94
|
+
if !prog.parses?
|
95
|
+
raise(ArgumentError, "Replacement point cannot be parsed")
|
96
|
+
else
|
97
|
+
new_point = prog.linked_code
|
98
|
+
end
|
99
|
+
elsif object.kind_of?(ProgramPoint)
|
100
|
+
new_point = object
|
101
|
+
else
|
102
|
+
raise(ArgumentError, "Program points cannot be replaced by #{object.class} objects")
|
103
|
+
end
|
104
|
+
|
105
|
+
((1..self.points).include?(which)) ?
|
106
|
+
self.program.replace_point(which, new_point) :
|
107
|
+
self.program.deep_copy
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
def add_tag(new_tag)
|
113
|
+
raise ArgumentError, "#{new_tag} is not a Symbol" unless new_tag.kind_of?(Symbol)
|
114
|
+
@tags.add(new_tag)
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
def remove_tag(old_tag)
|
119
|
+
@tags.delete(old_tag)
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
def data
|
124
|
+
{'blueprint' => self.blueprint, 'tags' => self.tags, 'scores' => self.scores, 'timestamp' => @timestamp}
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module NudgeGP
|
2
|
+
# A Batch is simply an Array of Answers, with validation for all assignment methods
|
3
|
+
class Batch < Array
|
4
|
+
|
5
|
+
def self.[](*args)
|
6
|
+
raise ArgumentError unless args.inject(true) {|anded, a| anded & a.kind_of?(Answer)}
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def []=(index, obj)
|
12
|
+
raise ArgumentError unless obj.kind_of?(Answer)
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def <<(obj)
|
18
|
+
raise ArgumentError unless obj.kind_of?(Answer)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def initialize(*args)
|
24
|
+
raise ArgumentError unless args.inject(true) {|anded, a| anded & a.kind_of?(Answer)}
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def bulk_save!(couchdb_uri)
|
30
|
+
raise ArgumentError, "#{couchdb_uri} is not a String" unless couchdb_uri.kind_of?(String)
|
31
|
+
|
32
|
+
db = CouchRest.database!(couchdb_uri)
|
33
|
+
db.bulk_save(self.data)
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def self.load_tagged_answers(couchdb_uri, tag)
|
38
|
+
raise ArgumentError, "#{couchdb_uri} is not a String" unless couchdb_uri.kind_of?(String)
|
39
|
+
raise ArgumentError, "#{tag} is not a String" unless tag.kind_of?(String)
|
40
|
+
db = CouchRest.database(couchdb_uri) # add the view document and key here
|
41
|
+
return Batch.new
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def data
|
46
|
+
self.collect {|i| i.data}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module NudgeGP
|
2
|
+
class Factory
|
3
|
+
require 'open-uri'
|
4
|
+
require 'configatron'
|
5
|
+
|
6
|
+
attr_reader :name
|
7
|
+
attr_reader :instruction_library, :type_library
|
8
|
+
attr_accessor :workstation_names
|
9
|
+
attr_reader :original_options_hash
|
10
|
+
|
11
|
+
|
12
|
+
def initialize(name = "my_factory", options = {})
|
13
|
+
@name = name
|
14
|
+
@original_options_hash = options
|
15
|
+
@instruction_library = options[:instruction_library] || Instruction.all_instructions
|
16
|
+
@type_library = options[:type_library] || NudgeType.all_types
|
17
|
+
@workstation_names = Array.new
|
18
|
+
|
19
|
+
self.configure!
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def couch_available?
|
24
|
+
open(self.configatron.couchdb_uri).status
|
25
|
+
true
|
26
|
+
rescue StandardError
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def configure!
|
32
|
+
self.configure_constants!
|
33
|
+
self.configure_paths!
|
34
|
+
self.configure_databases!
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def configure_constants!
|
39
|
+
self.configatron.factory_name = self.name
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def configure_paths!
|
44
|
+
self.configatron.factory_root = File.expand_path("#{File.dirname(__FILE__)}/../..")
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def configure_databases!
|
49
|
+
self.configatron.configure_from_yaml("#{self.configatron.factory_root}/config/database.yml")
|
50
|
+
self.configatron.couchdb_uri = "#{self.configatron.main_database.db_root}/#{self.name}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module NudgeGP
|
2
|
+
class Workstation
|
3
|
+
attr_reader :name, :capacity, :couchdb_uri, :factory_name
|
4
|
+
attr_accessor :downstream_stations
|
5
|
+
attr_accessor :answers
|
6
|
+
|
7
|
+
|
8
|
+
def initialize(name, options = {})
|
9
|
+
raise ArgumentError, "#{name} is not a Symbol" unless name.kind_of?(Symbol)
|
10
|
+
@name = name
|
11
|
+
@factory_name = options[:factory_name] || 'factory_name'
|
12
|
+
@couchdb_uri = options[:couchdb_uri] || "http://127.0.0.1:5984/#{@factory_name}"
|
13
|
+
@capacity = options[:capacity] || 100
|
14
|
+
@downstream_stations = Array.new
|
15
|
+
@answers = Array.new
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def transfer_answer(which, where)
|
20
|
+
raise ArgumentError, "#{where} is not a Symbol" unless where.kind_of?(Symbol)
|
21
|
+
which.remove_tag(self.name)
|
22
|
+
which.add_tag(where)
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def cycle
|
27
|
+
self.receive!
|
28
|
+
self.build!
|
29
|
+
self.ship!
|
30
|
+
self.scrap!
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|