copycats 0.0.1 → 0.5.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.
@@ -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