copycats 0.0.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,99 @@
1
+ # encoding: utf-8
2
+
3
+
4
+ ## load all *.file in data folder
5
+
6
+
7
+
8
+ def find_datafiles( root='.' )
9
+ files = []
10
+ ## todo/check: include all subfolders - why? why not?
11
+ Dir.glob( root + '/**/*.csv' ).each do |file|
12
+ files << file
13
+ end
14
+ files
15
+ end
16
+
17
+
18
+ def connect( config={} )
19
+ if config.empty?
20
+ puts "ENV['DATBASE_URL'] - >#{ENV['DATABASE_URL']}<"
21
+
22
+ ### change default to ./copycats.db ?? why? why not?
23
+ db = URI.parse( ENV['DATABASE_URL'] || 'sqlite3:///kitties.db' )
24
+
25
+ if db.scheme == 'postgres'
26
+ config = {
27
+ adapter: 'postgresql',
28
+ host: db.host,
29
+ port: db.port,
30
+ username: db.user,
31
+ password: db.password,
32
+ database: db.path[1..-1],
33
+ encoding: 'utf8'
34
+ }
35
+ else # assume sqlite3
36
+ config = {
37
+ adapter: db.scheme, # sqlite3
38
+ database: db.path[1..-1] # world.db (NB: cut off leading /, thus 1..-1)
39
+ }
40
+ end
41
+ end
42
+
43
+ puts "Connecting to db using settings: "
44
+ pp config
45
+ ActiveRecord::Base.establish_connection( config )
46
+ # ActiveRecord::Base.logger = Logger.new( STDOUT )
47
+ end
48
+
49
+
50
+ def setup_in_memory_db
51
+ # Database Setup & Config
52
+
53
+ ## ActiveRecord::Base.logger = Logger.new( STDOUT )
54
+ ## ActiveRecord::Base.colorize_logging = false - no longer exists - check new api/config setting?
55
+
56
+ connect( adapter: 'sqlite3',
57
+ database: ':memory:' )
58
+
59
+ ## build schema
60
+ CreateDb.new.up
61
+ end # setup_in_memory_db (using SQLite :memory:)
62
+
63
+
64
+
65
+ def setup( data_dir: './data' )
66
+ files = find_datafiles( data_dir )
67
+ pp files
68
+
69
+ ## check if files found
70
+
71
+ ## setup sqlite in-memory db
72
+ setup_in_memory_db()
73
+
74
+
75
+ ## add / read / load all datafiles
76
+ files.each_with_index do |file,i|
77
+
78
+ puts "== #{i+1}/#{files.size} reading datafile '#{file}'..."
79
+
80
+ kitties = CSV.read( file, headers:true )
81
+ pp kitties.headers
82
+
83
+ ## note: for now use first 5 rows for testing
84
+ ## kitties[0..4].each do |row|
85
+ kitties.each do |row|
86
+ ## puts row['id'] + '|' + row['gen'] + '|' + row['genes_kai']
87
+ k = Copycats::Model::Kitty.new
88
+ k.id = row['id'].to_i
89
+ k.gen = row['gen'].to_i
90
+ k.genes = row['genes_kai']
91
+ ## pp k
92
+
93
+ ## print ids for progress report - why? why not?
94
+ print "#{k.id}."
95
+ k.save!
96
+ end
97
+ print "\n"
98
+ end
99
+ end
@@ -0,0 +1,87 @@
1
+ # encoding: utf-8
2
+
3
+ class Gene
4
+
5
+ attr_reader :d, :r1, :r2, :r3
6
+ # d (dominant gene) -- todo/check: rename to just d instead of d0 - why? why not?
7
+ # r1 (1st order recessive gene)
8
+ # r2 (2nd order recessive gene)
9
+ # r3 (3rd order recessive gene)
10
+
11
+ def initialize( arg )
12
+ ## (always) assume String in base32/kai for now
13
+ kai = arg
14
+ ## puts "Gene.initialize #{kai}"
15
+ kai = kai.reverse
16
+ @d = kai[0]
17
+ @r1 = kai[1]
18
+ @r2 = kai[2]
19
+ @r3 = kai[3]
20
+ end
21
+
22
+ def to_kai() @r3 + @r2 + @r1 + @d; end ## return a string in kai/base32 notation
23
+
24
+ def swap
25
+ puts "Gene#swap"
26
+ kai = to_kai.reverse # note: use reverse kai string (kai[0] is first char/digit/letter)
27
+
28
+ 3.downto(1) do |i|
29
+ if Lottery.rand(100) < 25
30
+ puts " bingo! swap #{i}<>#{i-1}"
31
+ kai[i-1], kai[i] = kai[i], kai[i-1]
32
+ end
33
+ end
34
+ Gene.new( kai.reverse ) ## note: do NOT forget to pass in kai (unreversed)
35
+ end
36
+
37
+
38
+ def mutate( other )
39
+ puts "Gene#mutate"
40
+
41
+ gene1 = KAI_TO_INT[d] ## dominant gene1
42
+ gene2 = KAI_TO_INT[other.d] ## dominant gene2
43
+ if gene1 > gene2
44
+ gene1, gene2 = gene2, gene1 ## make sure gene2 is always bigger
45
+ end
46
+ if (gene2 - gene1) == 1 && gene1.even?
47
+ probability = 25
48
+ probability /= 2 if gene1 > 23
49
+ if Lottery.rand(100) < probability
50
+ genex = (gene1 / 2) + 16 ## 16=2^4
51
+ puts " bingo! mutation #{gene2}+#{gene1} => #{genex}"
52
+ puts " #{Kai::ALPHABET[gene2]}+#{Kai::ALPHABET[gene1]} => #{Kai::ALPHABET[genex]}"
53
+ return Kai::ALPHABET[genex]
54
+ end
55
+ end
56
+ nil # no mutation
57
+ end
58
+
59
+ def mix_inner( other )
60
+ puts "Gene#mix_inner"
61
+
62
+ new_d = mutate( other )
63
+ if new_d.nil? ## no mutation of gene.d - use "regular" formula
64
+ new_d = Lottery.rand(100) < 50 ? d : other.d
65
+ end
66
+
67
+ new_r1 = Lottery.rand(100) < 50 ? r1 : other.r1
68
+ new_r2 = Lottery.rand(100) < 50 ? r2 : other.r2
69
+ new_r3 = Lottery.rand(100) < 50 ? r3 : other.r3
70
+
71
+ gene = Gene.new( new_r3 + new_r2 + new_r1 + new_d )
72
+ pp gene
73
+ gene
74
+ end
75
+
76
+
77
+ def mix( other )
78
+ puts "Gene#mix"
79
+ self_swapped = swap
80
+ other_swapped = other.swap
81
+
82
+ gene = self_swapped.mix_inner( other_swapped )
83
+ pp gene
84
+ gene
85
+ end
86
+
87
+ end # class Gene
@@ -0,0 +1,177 @@
1
+ # encoding: utf-8
2
+
3
+
4
+ class Genome
5
+ attr_reader :genes ## hash of genes (key is gene type)
6
+
7
+ def initialize( arg )
8
+ if arg.is_a? Hash
9
+ hash = arg ## assumes (pre-built) hash with genes
10
+ @genes = hash
11
+ else
12
+ if arg.is_a? Integer ## use Integer (Fixnum+Bignum??) - why? why not?
13
+ num = arg
14
+ kai = Kai.encode( num )
15
+ else # else assume string in kai/base32 format
16
+ kai = arg.dup # just in case; make a clean (fresh) copy
17
+ kai = kai.gsub( ' ', '' ) ## allow spaces (strip/remove)
18
+ end
19
+ ## puts "Genome.initialize #{kai}"
20
+ build_genes( kai )
21
+ end
22
+ end
23
+
24
+ def build_genes( kai )
25
+ kai = kai.reverse ## note: reserve for easy left-to-right access
26
+ @genes = {} ## hash of genes (key is gene type)
27
+ ## fix/todo: use as_json for "official" api order
28
+ ## note: use insert order from "official" api
29
+ @genes[:body] = Gene.new( kai[0,4].reverse )
30
+ @genes[:pattern] = Gene.new( kai[4,4].reverse )
31
+ @genes[:coloreyes] = Gene.new( kai[8,4].reverse )
32
+ @genes[:eyes] = Gene.new( kai[12,4].reverse )
33
+ @genes[:color1] = Gene.new( kai[16,4].reverse ) ## colorprimary / body color / base color
34
+ @genes[:color2] = Gene.new( kai[20,4].reverse ) ## colorsecondary / sec color / pattern color / hi(light) color
35
+ @genes[:color3] = Gene.new( kai[24,4].reverse ) ## colortertiary / acc(ent) color
36
+ @genes[:wild] = Gene.new( kai[28,4].reverse )
37
+ @genes[:mouth] = Gene.new( kai[32,4].reverse )
38
+ end
39
+
40
+ def body() TRAITS[:body][:kai][ @genes[:body].d ]; end
41
+ def coloreyes() TRAITS[:coloreyes][:kai][ @genes[:coloreyes].d ]; end
42
+ def eyes() TRAITS[:eyes][:kai][ @genes[:eyes].d ]; end
43
+ def pattern() TRAITS[:pattern][:kai][ @genes[:pattern].d ]; end
44
+ def mouth() TRAITS[:mouth][:kai][ @genes[:mouth].d ]; end
45
+ def color1() TRAITS[:color1][:kai][ @genes[:color1].d ]; end
46
+ def color2() TRAITS[:color2][:kai][ @genes[:color2].d ]; end
47
+ def color3() TRAITS[:color3][:kai][ @genes[:color3].d ]; end
48
+
49
+
50
+
51
+ def genes_color1() @genes[:color1]; end ## rename to color1_genes instead - why? why not?
52
+ def genes_eyes() @genes[:eyes]; end
53
+ ## ....
54
+
55
+ ## add cattributes ?? why? why not?
56
+
57
+
58
+ def mix( other )
59
+ mgenes = genes ## matron genes
60
+ sgenes = other.genes ## sire genes
61
+ new_genes = {}
62
+
63
+ ## todo/fix: use insertion order from "official" api - why? why not?
64
+ ## -- preinitialize with empty hash and than use byte order ??
65
+ TRAIT_KEYS.each do |key|
66
+ mgene = mgenes[key]
67
+ sgene = sgenes[key]
68
+
69
+ new_gene = mgene.mix( sgene )
70
+ new_genes[key] = new_gene
71
+ end
72
+
73
+ Genome.new( new_genes ) ## return new genome from (pre-built) hash (with genes)
74
+ end
75
+
76
+
77
+ def build_tables() GenomeTables.new( self ).build; end
78
+
79
+ def build_mix_tables( other ) GenomeMixTables.new( self, other ).build; end
80
+
81
+ end # class Genome
82
+
83
+
84
+
85
+ class GenomeTables
86
+ def initialize( genome )
87
+ @genome = genome
88
+ end
89
+
90
+ def build
91
+ pos = 0
92
+ buf = ""
93
+
94
+ genes = @genome.genes
95
+
96
+ TRAITS.each do |key, trait|
97
+ gene = genes[key]
98
+ next if gene.nil? ## skip future_1, future_2, etc.
99
+
100
+ buf << "#{trait[:name]} (Genes #{trait[:genes]})\n\n"
101
+
102
+ ###
103
+ ## fix/todo: add stars for purity?
104
+ ## **** - all traits the same
105
+ ## *** - two same pairs of traits
106
+ ## ** - one pair of same traits
107
+
108
+ buf << "|Gene |Binary |Kai |Trait | |\n"
109
+ buf << "|------|---------|-----|---------|---|\n"
110
+ buf << "| #{pos} | #{Kai::BINARY[gene.d]} | #{gene.d} | **#{fmt_trait(trait[:kai][gene.d])}** | d |\n"; pos+=1
111
+ buf << "| #{pos} | #{Kai::BINARY[gene.r1]} | #{gene.r1} | #{fmt_trait(trait[:kai][gene.r1])} | r1 |\n"; pos+=1
112
+ buf << "| #{pos} | #{Kai::BINARY[gene.r2]} | #{gene.r2} | #{fmt_trait(trait[:kai][gene.r2])} | r2 |\n"; pos+=1
113
+ buf << "| #{pos} | #{Kai::BINARY[gene.r3]} | #{gene.r3} | #{fmt_trait(trait[:kai][gene.r3])} | r3 |\n"; pos+=1
114
+ buf << "\n"
115
+
116
+ if key == :body ## add legend for first entry
117
+ buf << "d = dominant, r1 = 1st order recessive, r2 = 2nd order recessive, r3 = 3rd order recessive\n\n"
118
+ end
119
+ end
120
+
121
+ buf
122
+ end
123
+
124
+ ####################
125
+ ## helpers
126
+
127
+ def fmt_trait( trait )
128
+ (trait.nil? || trait.empty?) ? '?' : trait
129
+ end
130
+ end # class GenomeTables
131
+
132
+
133
+
134
+
135
+ class GenomeMixTables
136
+ def initialize( matron, sire )
137
+ @matron = matron
138
+ @sire = sire
139
+ end
140
+
141
+ def build
142
+ pos = 0
143
+ buf = ""
144
+
145
+ mgenes = @matron.genes
146
+ sgenes = @sire.genes
147
+
148
+ TRAITS.each do |key, trait|
149
+ mgene = mgenes[key]
150
+ sgene = sgenes[key]
151
+ next if mgene.nil? ## skip future_1, future_2, etc.
152
+
153
+ buf << "#{trait[:name]} (Genes #{trait[:genes]})\n\n"
154
+
155
+ buf << "|Gene |Kai |Trait (Matron)|Kai|Trait (Sire)| |\n"
156
+ buf << "|------|-----|---------|-----|---------|---|\n"
157
+ buf << "| #{pos} | #{mgene.d} | **#{fmt_trait(trait[:kai][mgene.d])}** | #{sgene.d} | **#{fmt_trait(trait[:kai][sgene.d])}** | d |\n"; pos+=1
158
+ buf << "| #{pos} | #{mgene.r1} | #{fmt_trait(trait[:kai][mgene.r1])} | #{sgene.r1} | #{fmt_trait(trait[:kai][sgene.r1])} | r1 |\n"; pos+=1
159
+ buf << "| #{pos} | #{mgene.r2} | #{fmt_trait(trait[:kai][mgene.r2])} | #{sgene.r2} | #{fmt_trait(trait[:kai][sgene.r2])} | r2 |\n"; pos+=1
160
+ buf << "| #{pos} | #{mgene.r3} | #{fmt_trait(trait[:kai][mgene.r3])} | #{sgene.r3} | #{fmt_trait(trait[:kai][sgene.r3])} | r3 |\n"; pos+=1
161
+ buf << "\n"
162
+
163
+ if key == :body ## add legend for first entry
164
+ buf << "d = dominant, r1 = 1st order recessive, r2 = 2nd order recessive, r3 = 3rd order recessive\n\n"
165
+ end
166
+ end
167
+
168
+ buf
169
+ end
170
+
171
+ ####################
172
+ ## helpers
173
+
174
+ def fmt_trait( trait )
175
+ (trait.nil? || trait.empty?) ? '?' : trait
176
+ end
177
+ end # class GenomeMixTables
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+
3
+ module Copycats
4
+ module Model
5
+
6
+ class Kitty < ActiveRecord::Base
7
+ self.table_name = 'kitties'
8
+
9
+ end # class Kitty
10
+
11
+
12
+ end # module Model
13
+ end # module Copycats
@@ -0,0 +1,82 @@
1
+ # encoding: utf-8
2
+
3
+
4
+ class GenesReport
5
+
6
+ def build
7
+ buf = ""
8
+ buf << "# Genes (#{TRAITS.keys.size} x 4)\n\n"
9
+
10
+ headings = []
11
+ TRAITS.values.each do |trait|
12
+ headings << "#{trait[:name]} (#{trait[:genes]})"
13
+ end
14
+
15
+ buf << headings.join( " • " )
16
+ buf << "\n\n"
17
+
18
+
19
+ ## pp TRAITS
20
+ TRAITS.values.each do |trait|
21
+
22
+ puts "Kai Cattribute"
23
+ items = []
24
+ Kai::ALPHABET.each_char do |kai|
25
+ value = trait[:kai][kai]
26
+ value = '?' if value.nil? || value.empty?
27
+ items << [kai, value]
28
+ end
29
+
30
+ items.each do |item|
31
+ puts "#{item[0]} #{item[1]}"
32
+ end
33
+
34
+ buf << "## #{trait[:name]} (Genes #{trait[:genes]})\n\n"
35
+ buf << make_table( items )
36
+ buf << "\n\n"
37
+ end
38
+
39
+ puts buf
40
+
41
+ buf
42
+ end ## method build
43
+
44
+ def make_table( items )
45
+ rows = make_rows( items, columns: 4 )
46
+ pp rows
47
+
48
+ buf = ""
49
+ buf << "|Kai|Cattribute |Kai|Cattribute |Kai|Cattribute |Kai|Cattribute |\n"
50
+ buf << "|---|-------------|---|------------|---|------------|---|------------|\n"
51
+
52
+ rows.each do |row|
53
+ buf << "| "
54
+ buf << row.map {|item| "#{item[0]} | #{item[1]}" }.join( " | " )
55
+ buf << " |\n"
56
+ end
57
+
58
+ buf
59
+ end
60
+
61
+
62
+ ## helpers
63
+ def make_rows( items, columns: 4 )
64
+ offset = items.size / columns
65
+ pp offset
66
+
67
+ rows = []
68
+ offset.times.with_index do |row|
69
+ ## note: construct [items[row],items[offset+row],items[offset*2+row], ...]
70
+ rows << columns.times.with_index.map { |col| items[offset*col+row] }
71
+ end
72
+ rows
73
+ end
74
+
75
+
76
+ def save( path )
77
+ File.open( path, "w" ) do |f|
78
+ f.write build
79
+ end
80
+ end
81
+
82
+ end # class GenesReport