text:
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

"use strict";

(() => {
	const enosys = () => {
		const err = new Error("not implemented");
		err.code = "ENOSYS";
		return err;
	};

	if (!globalThis.fs) {
		let outputBuf = "";
		globalThis.fs = {
			constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
			writeSync(fd, buf) {
				outputBuf += decoder.decode(buf);
				const nl = outputBuf.lastIndexOf("\n");
				if (nl != -1) {
					console.log(outputBuf.substr(0, nl));
					outputBuf = outputBuf.substr(nl + 1);
				}
				return buf.length;
			},
			write(fd, buf, offset, length, position, callback) {
				if (offset !== 0 || length !== buf.length || position !== null) {
					callback(enosys());
					return;
				}
				const n = this.writeSync(fd, buf);
				callback(null, n);
			},
			chmod(path, mode, callback) { callback(enosys()); },
			chown(path, uid, gid, callback) { callback(enosys()); },
			close(fd, callback) { callback(enosys()); },
			fchmod(fd, mode, callback) { callback(enosys()); },
			fchown(fd, uid, gid, callback) { callback(enosys()); },
			fstat(fd, callback) { callback(enosys()); },
			fsync(fd, callback) { callback(null); },
			ftruncate(fd, length, callback) { callback(enosys()); },
			lchown(path, uid, gid, callback) { callback(enosys()); },
			link(path, link, callback) { callback(enosys()); },
			lstat(path, callback) { callback(enosys()); },
			mkdir(path, perm, callback) { callback(enosys()); },
			open(path, flags, mode, callback) { callback(enosys()); },
			read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
			readdir(path, callback) { callback(enosys()); },
			readlink(path, callback) { callback(enosys()); },
			rename(from, to, callback) { callback(enosys()); },
			rmdir(path, callback) { callback(enosys()); },
			stat(path, callback) { callback(enosys()); },
			symlink(path, link, callback) { callback(enosys()); },
			truncate(path, length, callback) { callback(enosys()); },
			unlink(path, callback) { callback(enosys()); },
			utimes(path, atime, mtime, callback) { callback(enosys()); },
		};
	}

	if (!globalThis.process) {
		globalThis.process = {
			getuid() { return -1; },
			getgid() { return -1; },
			geteuid() { return -1; },
			getegid() { return -1; },
			getgroups() { throw enosys(); },
			pid: -1,
			ppid: -1,
			umask() { throw enosys(); },
			cwd() { throw enosys(); },
			chdir() { throw enosys(); },
		}
	}

	if (!globalThis.crypto) {
		throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
	}

	if (!globalThis.performance) {
		throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
	}

	if (!globalThis.TextEncoder) {
		throw new Error("globalThis.TextEncoder is not available, polyfill required");
	}

	if (!globalThis.TextDecoder) {
		throw new Error("globalThis.TextDecoder is not available, polyfill required");
	}

	const encoder = new TextEncoder("utf-8");
	const decoder = new TextDecoder("utf-8");

	globalThis.Go = class {
		constructor() {
			this.argv = ["js"];
			this.env = {};
			this.exit = (code) => {
				if (code !== 0) {
					console.warn("exit code:", code);
				}
			};
			this._exitPromise = new Promise((resolve) => {
				this._resolveExitPromise = resolve;
			});
			this._pendingEvent = null;
			this._scheduledTimeouts = new Map();
			this._nextCallbackTimeoutID = 1;

			const setInt64 = (addr, v) => {
				this.mem.setUint32(addr + 0, v, true);
				this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
			}

			const getInt64 = (addr) => {
				const low = this.mem.getUint32(addr + 0, true);
				const high = this.mem.getInt32(addr + 4, true);
				return low + high * 4294967296;
			}

			const loadValue = (addr) => {
				const f = this.mem.getFloat64(addr, true);
				if (f === 0) {
					return undefined;
				}
				if (!isNaN(f)) {
					return f;
				}

				const id = this.mem.getUint32(addr, true);
				return this._values[id];
			}

			const storeValue = (addr, v) => {
				const nanHead = 0x7FF80000;

				if (typeof v === "number" && v !== 0) {
					if (isNaN(v)) {
						this.mem.setUint32(addr + 4, nanHead, true);
						this.mem.setUint32(addr, 0, true);
						return;
					}
					this.mem.setFloat64(addr, v, true);
					return;
				}

				if (v === undefined) {
					this.mem.setFloat64(addr, 0, true);
					return;
				}

				let id = this._ids.get(v);
				if (id === undefined) {
					id = this._idPool.pop();
					if (id === undefined) {
						id = this._values.length;
					}
					this._values[id] = v;
					this._goRefCounts[id] = 0;
					this._ids.set(v, id);
				}
				this._goRefCounts[id]++;
				let typeFlag = 0;
				switch (typeof v) {
					case "object":
						if (v !== null) {
							typeFlag = 1;
						}
						break;
					case "string":
						typeFlag = 2;
						break;
					case "symbol":
						typeFlag = 3;
						break;
					case "function":
						typeFlag = 4;
						break;
				}
				this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
				this.mem.setUint32(addr, id, true);
			}

			const loadSlice = (addr) => {
				const array = getInt64(addr + 0);
				const len = getInt64(addr + 8);
				return new Uint8Array(this._inst.exports.mem.buffer, array, len);
			}

			const loadSliceOfValues = (addr) => {
				const array = getInt64(addr + 0);
				const len = getInt64(addr + 8);
				const a = new Array(len);
				for (let i = 0; i < len; i++) {
					a[i] = loadValue(array + i * 8);
				}
				return a;
			}

			const loadString = (addr) => {
				const saddr = getInt64(addr + 0);
				const len = getInt64(addr + 8);
				return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
			}

			const timeOrigin = Date.now() - performance.now();
			this.importObject = {
				go: {
					// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
					// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
					// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
					// This changes the SP, thus we have to update the SP used by the imported function.

					// func wasmExit(code int32)
					"runtime.wasmExit": (sp) => {
						sp >>>= 0;
						const code = this.mem.getInt32(sp + 8, true);
						this.exited = true;
						delete this._inst;
						delete this._values;
						delete this._goRefCounts;
						delete this._ids;
						delete this._idPool;
						this.exit(code);
					},

					// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
					"runtime.wasmWrite": (sp) => {
						sp >>>= 0;
						const fd = getInt64(sp + 8);
						const p = getInt64(sp + 16);
						const n = this.mem.getInt32(sp + 24, true);
						fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
					},

					// func resetMemoryDataView()
					"runtime.resetMemoryDataView": (sp) => {
						sp >>>= 0;
						this.mem = new DataView(this._inst.exports.mem.buffer);
					},

					// func nanotime1() int64
					"runtime.nanotime1": (sp) => {
						sp >>>= 0;
						setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
					},

					// func walltime() (sec int64, nsec int32)
					"runtime.walltime": (sp) => {
						sp >>>= 0;
						const msec = (new Date).getTime();
						setInt64(sp + 8, msec / 1000);
						this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
					},

					// func scheduleTimeoutEvent(delay int64) int32
					"runtime.scheduleTimeoutEvent": (sp) => {
						sp >>>= 0;
						const id = this._nextCallbackTimeoutID;
						this._nextCallbackTimeoutID++;
						this._scheduledTimeouts.set(id, setTimeout(
							() => {
								this._resume();
								while (this._scheduledTimeouts.has(id)) {
									// for some reason Go failed to register the timeout event, log and try again
									// (temporary workaround for https://github.com/golang/go/issues/28975)
									console.warn("scheduleTimeoutEvent: missed timeout event");
									this._resume();
								}
							},
							getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
						));
						this.mem.setInt32(sp + 16, id, true);
					},

					// func clearTimeoutEvent(id int32)
					"runtime.clearTimeoutEvent": (sp) => {
						sp >>>= 0;
						const id = this.mem.getInt32(sp + 8, true);
						clearTimeout(this._scheduledTimeouts.get(id));
						this._scheduledTimeouts.delete(id);
					},

					// func getRandomData(r []byte)
					"runtime.getRandomData": (sp) => {
						sp >>>= 0;
						crypto.getRandomValues(loadSlice(sp + 8));
					},

					// func finalizeRef(v ref)
					"syscall/js.finalizeRef": (sp) => {
						sp >>>= 0;
						const id = this.mem.getUint32(sp + 8, true);
						this._goRefCounts[id]--;
						if (this._goRefCounts[id] === 0) {
							const v = this._values[id];
							this._values[id] = null;
							this._ids.delete(v);
							this._idPool.push(id);
						}
					},

					// func stringVal(value string) ref
					"syscall/js.stringVal": (sp) => {
						sp >>>= 0;
						storeValue(sp + 24, loadString(sp + 8));
					},

					// func valueGet(v ref, p string) ref
					"syscall/js.valueGet": (sp) => {
						sp >>>= 0;
						const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
						sp = this._inst.exports.getsp() >>> 0; // see comment above
						storeValue(sp + 32, result);
					},

					// func valueSet(v ref, p string, x ref)
					"syscall/js.valueSet": (sp) => {
						sp >>>= 0;
						Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
					},

					// func valueDelete(v ref, p string)
					"syscall/js.valueDelete": (sp) => {
						sp >>>= 0;
						Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
					},

					// func valueIndex(v ref, i int) ref
					"syscall/js.valueIndex": (sp) => {
						sp >>>= 0;
						storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
					},

					// valueSetIndex(v ref, i int, x ref)
					"syscall/js.valueSetIndex": (sp) => {
						sp >>>= 0;
						Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
					},

					// func valueCall(v ref, m string, args []ref) (ref, bool)
					"syscall/js.valueCall": (sp) => {
						sp >>>= 0;
						try {
							const v = loadValue(sp + 8);
							const m = Reflect.get(v, loadString(sp + 16));
							const args = loadSliceOfValues(sp + 32);
							const result = Reflect.apply(m, v, args);
							sp = this._inst.exports.getsp() >>> 0; // see comment above
							storeValue(sp + 56, result);
							this.mem.setUint8(sp + 64, 1);
						} catch (err) {
							sp = this._inst.exports.getsp() >>> 0; // see comment above
							storeValue(sp + 56, err);
							this.mem.setUint8(sp + 64, 0);
						}
					},

					// func valueInvoke(v ref, args []ref) (ref, bool)
					"syscall/js.valueInvoke": (sp) => {
						sp >>>= 0;
						try {
							const v = loadValue(sp + 8);
							const args = loadSliceOfValues(sp + 16);
							const result = Reflect.apply(v, undefined, args);
							sp = this._inst.exports.getsp() >>> 0; // see comment above
							storeValue(sp + 40, result);
							this.mem.setUint8(sp + 48, 1);
						} catch (err) {
							sp = this._inst.exports.getsp() >>> 0; // see comment above
							storeValue(sp + 40, err);
							this.mem.setUint8(sp + 48, 0);
						}
					},

					// func valueNew(v ref, args []ref) (ref, bool)
					"syscall/js.valueNew": (sp) => {
						sp >>>= 0;
						try {
							const v = loadValue(sp + 8);
							const args = loadSliceOfValues(sp + 16);
							const result = Reflect.construct(v, args);
							sp = this._inst.exports.getsp() >>> 0; // see comment above
							storeValue(sp + 40, result);
							this.mem.setUint8(sp + 48, 1);
						} catch (err) {
							sp = this._inst.exports.getsp() >>> 0; // see comment above
							storeValue(sp + 40, err);
							this.mem.setUint8(sp + 48, 0);
						}
					},

					// func valueLength(v ref) int
					"syscall/js.valueLength": (sp) => {
						sp >>>= 0;
						setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
					},

					// valuePrepareString(v ref) (ref, int)
					"syscall/js.valuePrepareString": (sp) => {
						sp >>>= 0;
						const str = encoder.encode(String(loadValue(sp + 8)));
						storeValue(sp + 16, str);
						setInt64(sp + 24, str.length);
					},

					// valueLoadString(v ref, b []byte)
					"syscall/js.valueLoadString": (sp) => {
						sp >>>= 0;
						const str = loadValue(sp + 8);
						loadSlice(sp + 16).set(str);
					},

					// func valueInstanceOf(v ref, t ref) bool
					"syscall/js.valueInstanceOf": (sp) => {
						sp >>>= 0;
						this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
					},

					// func copyBytesToGo(dst []byte, src ref) (int, bool)
					"syscall/js.copyBytesToGo": (sp) => {
						sp >>>= 0;
						const dst = loadSlice(sp + 8);
						const src = loadValue(sp + 32);
						if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
							this.mem.setUint8(sp + 48, 0);
							return;
						}
						const toCopy = src.subarray(0, dst.length);
						dst.set(toCopy);
						setInt64(sp + 40, toCopy.length);
						this.mem.setUint8(sp + 48, 1);
					},

					// func copyBytesToJS(dst ref, src []byte) (int, bool)
					"syscall/js.copyBytesToJS": (sp) => {
						sp >>>= 0;
						const dst = loadValue(sp + 8);
						const src = loadSlice(sp + 16);
						if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
							this.mem.setUint8(sp + 48, 0);
							return;
						}
						const toCopy = src.subarray(0, dst.length);
						dst.set(toCopy);
						setInt64(sp + 40, toCopy.length);
						this.mem.setUint8(sp + 48, 1);
					},

					"debug": (value) => {
						console.log(value);
					},
				}
			};
		}

		async run(instance) {
			if (!(instance instanceof WebAssembly.Instance)) {
				throw new Error("Go.run: WebAssembly.Instance expected");
			}
			this._inst = instance;
			this.mem = new DataView(this._inst.exports.mem.buffer);
			this._values = [ // JS values that Go currently has references to, indexed by reference id
				NaN,
				0,
				null,
				true,
				false,
				globalThis,
				this,
			];
			this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
			this._ids = new Map([ // mapping from JS values to reference ids
				[0, 1],
				[null, 2],
				[true, 3],
				[false, 4],
				[globalThis, 5],
				[this, 6],
			]);
			this._idPool = [];   // unused ids that have been garbage collected
			this.exited = false; // whether the Go program has exited

			// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
			let offset = 4096;

			const strPtr = (str) => {
				const ptr = offset;
				const bytes = encoder.encode(str + "\0");
				new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
				offset += bytes.length;
				if (offset % 8 !== 0) {
					offset += 8 - (offset % 8);
				}
				return ptr;
			};

			const argc = this.argv.length;

			const argvPtrs = [];
			this.argv.forEach((arg) => {
				argvPtrs.push(strPtr(arg));
			});
			argvPtrs.push(0);

			const keys = Object.keys(this.env).sort();
			keys.forEach((key) => {
				argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
			});
			argvPtrs.push(0);

			const argv = offset;
			argvPtrs.forEach((ptr) => {
				this.mem.setUint32(offset, ptr, true);
				this.mem.setUint32(offset + 4, 0, true);
				offset += 8;
			});

			// The linker guarantees global data starts from at least wasmMinDataAddr.
			// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
			const wasmMinDataAddr = 4096 + 8192;
			if (offset >= wasmMinDataAddr) {
				throw new Error("total length of command line and environment variables exceeds limit");
			}

			this._inst.exports.run(argc, argv);
			if (this.exited) {
				this._resolveExitPromise();
			}
			await this._exitPromise;
		}

		_resume() {
			if (this.exited) {
				throw new Error("Go program has already exited");
			}
			this._inst.exports.resume();
			if (this.exited) {
				this._resolveExitPromise();
			}
		}

		_makeFuncWrapper(id) {
			const go = this;
			return function () {
				const event = { id: id, this: this, args: arguments };
				go._pendingEvent = event;
				go._resume();
				return event.result;
			};
		}
	}
})();


text:
  <meta charset="utf-8"/>

      Hint count: <input size="3" id="hintcount" value="30"></input>

      <input type="checkbox" id="symmetrical">Symmetrical</input>

      <button id="generate" title="Generate puzzle">Generate</button>

repo_name: eliben/go-sudoku

repo_language: Go

mime_type: text/x-c


text:
// Simple static file server to serve the contents of the `assets` sub-directory
// on localhost.
package main

import (
	"fmt"
	"net/http"
)

func main() {
	port := ":8899"
	fmt.Println("Serving on", port)

	err := http.ListenAndServe(port, http.FileServer(http.Dir("assets")))
	if err != nil {
		fmt.Println("Failed to start server", err)
		return
	}
}


text:
package main

import (
	"flag"
	"fmt"
	"log"
	"math/rand"
	"os"
	"time"

	"github.com/eliben/go-sudoku"
)

// Note: trying to generate difficult boards with low hintcount may take a long
// time.

var symFlag = flag.Bool("sym", false, "generate a symmetrical puzzle")
var diffFlag = flag.Float64("diff", 2.5, "minimal difficulty for generated puzzle")
var hintCountFlag = flag.Int("hintcount", 28, "hint count for generation; higher counts lead to easier puzzles")
var svgOutFlag = flag.String("svgout", "", "file name for SVG output, if needed")

func main() {
	flag.Usage = func() {
		out := flag.CommandLine.Output()
		fmt.Println(out, "usage: generator [options]")
		fmt.Println("Options:")
		flag.PrintDefaults()
	}
	flag.Parse()

	rand.Seed(time.Now().UnixNano())

	count := 0
	maxDifficultySeen := 0.0

	for {
		var board sudoku.Values

		if *symFlag {
			board = sudoku.GenerateSymmetrical(*hintCountFlag)
		} else {
			board = sudoku.Generate(*hintCountFlag)
		}

		d, err := sudoku.EvaluateDifficulty(board)
		if err != nil {
			log.Fatal(err)
		}

		if d >= *diffFlag {
			fmt.Println(sudoku.DisplayAsInput(board))
			fmt.Printf("Difficulty: %.2f\n", d)

			if len(*svgOutFlag) > 0 {
				f, err := os.Create(*svgOutFlag)
				if err != nil {
					log.Fatal(err)
				}
				defer f.Close()
				sudoku.DisplayAsSVG(f, board, d)
				fmt.Println("Wrote SVG output to", *svgOutFlag)
			}

			break
		} else {
			count++
			if d > maxDifficultySeen {
				maxDifficultySeen = d
			}

			if count > 0 && count%10 == 0 {
				fmt.Printf("Tried %v boards; max difficulty seen %.2f\n", count, maxDifficultySeen)
			}
		}
	}
}


text:
# Crypto Copycats Collectibles - Buy! Sell! Hodl! Sire!

## Copycats Database Tools

- [**copycats**](copycats) -  incl. `kitty` command line tool to (auto-)read kitty data records (in comma-separated values (CSV)) into an in-memory SQLite database
and print reports
- [**bitcat**](bitcat) - bit catalog kitty browser; browse your (digital) bit(s) collections

## Database Tables

Table Diagram

![](copycats-tables.png)

SQL Tables (in SQLite Dialect)

``` sql
CREATE TABLE kitties (
    id           INTEGER  PRIMARY KEY AUTOINCREMENT
                          NOT NULL,
    name         VARCHAR,
    genes_kai    VARCHAR  NOT NULL,
    gen          INTEGER  NOT NULL,
    birthdate    DATETIME NOT NULL,
    day_count    INTEGER  NOT NULL,
    matron_id    INTEGER,
    sire_id      INTEGER,
    body_id      INTEGER  NOT NULL,
    pattern_id   INTEGER  NOT NULL,
    coloreyes_id INTEGER  NOT NULL,
    eyes_id      INTEGER  NOT NULL,
    color1_id    INTEGER  NOT NULL,
    color2_id    INTEGER  NOT NULL,
    color3_id    INTEGER  NOT NULL,
    wild_id      INTEGER  NOT NULL,
    mouth_id     INTEGER  NOT NULL
);

CREATE TABLE genes (
    id       INTEGER PRIMARY KEY AUTOINCREMENT
                     NOT NULL,
    kitty_id INTEGER NOT NULL,
    n        INTEGER NOT NULL,
    gene     VARCHAR NOT NULL,
    gene_n   INTEGER NOT NULL,
    trait_id INTEGER NOT NULL
);

CREATE TABLE traits (
    id            INTEGER PRIMARY KEY AUTOINCREMENT
                          NOT NULL,
    trait_type_id INTEGER NOT NULL,
    name          VARCHAR NOT NULL,
    n             INTEGER NOT NULL,
    kai           VARCHAR NOT NULL,
    tier          INTEGER
);

CREATE TABLE trait_types (
    id    INTEGER PRIMARY KEY AUTOINCREMENT
                  NOT NULL,
    name  VARCHAR NOT NULL,
    [key] VARCHAR NOT NULL
);

```

## Database Setup

Use the kitty setup command to setup an SQLite database and (auto-)read
all datafiles. Example:

```
$ kitty setup
```

This will create:

- a single-file SQLite database `kitties.db`
- setup all tables
- add all known traits and trait types (body, pattern, eyes, ...) and
- (auto-)read all datafiles (`**/*.csv`) in the `.` and all subdirectories

Note: Use the `-i/--include` option to change the default data directory (that is, `.`)
and use the `-n/--dbname` option to change the default SQLite database name (that is, `kitties.db`)
and use the `-d/--dbpath` option to change the default SQLite database path (that is, `.`).

Showtime! Use the sqlite3 command line tool
and try some queries. Example:

```
$ sqlite3 kitties.db

sqlite> SELECT * FROM kitties WHERE id = 1;

1||ccac 7787 fa7f afaa 1646 7755 f9ee 4444 6766 7366 cccc eede|0|2017-11-23 06:19:59|...

sqlite> SELECT * FROM genes WHERE trait_id = 14;    -- sphynx (14)

1|1|0|d|0|14
3|1|2|r2|2|14
4|1|3|r3|3|14
38|2|1|r1|1|14
146|5|1|r1|1|14
181|6|0|d|0|14
183|6|2|r2|2|14
...
```

## Database Queries

### SQL

#### Find all kitties with a trait

Let's use the trait savannah (fur) with the id 0:

``` sql
SELECT id FROM kitties WHERE body_id = 0
```

#### Find all kitties with two traits

Let's use the trait savannah (fur) with the id 0
and the trait tiger (pattern) with the id 33:

``` sql
SELECT id FROM  kitties
          WHERE body_id = 0 AND pattern_id = 33
```

#### Find all kitties with a trait (in any gene d/r1/r2/r3)

Note: All traits (12 x 32 = 384) are numbered with ids from 0 to 383 in the traits database table.
Let's use the trait savannah (fur) with the id 0:

``` sql
SELECT kitty_id FROM genes WHERE trait_id = 0
```

#### Find all kitties with a dominant (visible) trait

Note: Use `gene` column (`d`/`r1`/`r2`/`r3`) or the numeric `gene_n`
column (0/1/2/3): Let's use the trait savannah (fur) with the id 0
and a dominant (d) gene:

``` sql
SELECT kitty_id FROM  genes
                WHERE trait_id = 0 AND gene='d'
```

#### Find all kitties with two traits (in any gene d/r1/r2/r3)

Use two query with "intersect" the result. Let's
use the trait savannah (fur) with the id 0
and the trait tiger (pattern) with the id 33:

``` sql
SELECT kitty_id FROM genes WHERE trait_id = 0
INTERSECT
SELECT kitty_id FROM genes WHERE trait_id = 33
```

### Using Models w/ ActiveRecord in Ruby

#### Find all kitties with a trait

Let's use the trait savannah (fur) with the id 0:

``` ruby
Kitty.find_by( body: Trait.find_by( name: 'savannah' ))
# -or -
Kitty.find_by( body_id: 0)
```

#### Find all kitties with two traits

Let's use the trait savannah (fur) with the id 0
and the trait tiger (pattern) with the id 33:

``` ruby
Kitty.find_by( body:    Trait.find_by( name: 'savannah' ),
               pattern: Trait.find_by( name: 'tiger' ))
# -or -
Kitty.find_by( body_id: 0, pattern_id: 33 )
```

#### Find all kitties with a trait (in any gene d/r1/r2/r3)

Let's use the trait savannah (fur) with the id 0:

``` ruby
genes = Gene.find_by( trait: Trait.find_by( name: 'savannah' ))  # query
#-or-
genes = Gene.find_by( trait_id: 0 )
genes.map { |gene| gene.kitty }                                  # get kitties (from gene)
```

#### Find all kitties with a dominant (visible) trait

Let's use the trait savannah (fur) with the id 0 and a dominant (d) gene:

``` ruby
genes = Gene.find_by( trait: Trait.find_by( name: 'savannah' ),
                      d:     'd' )                               #query
#-or-
genes = Gene.find_by( trait_id: 0, d: 'd' )
genes.map { |gene| gene.kitty }                                  # get kitties (from gene)
```

#### Find all kitties with two traits

Use two query with "intersect" the result. Let's
use the trait savannah (fur)
and the trait tiger (pattern):

``` ruby
genes = Gene.select('kitty_id').where( trait: Trait.find_by( name: 'savannah' )).intersect(
        Gene.select('kitty_id').where( trait: Trait.find_by( name: 'pattern' )))
genes.map { |gene| gene.kitty }     # get kitties (from gene)
```

## Datasets

[(Crypto) Kitties on the Blockchain](https://github.com/cryptocopycats/kitties) -
public dataset in comma-separated values (CSV) format in blocks of a thousand kitties each (e.g.
[`000.csv`](https://github.com/cryptocopycats/kitties/blob/master/1-99_999/000.csv) incl. 1-999,
[`001.csv`](https://github.com/cryptocopycats/kitties/blob/master/1-99_999/001.csv) incl. 1000-1999,
[`002.csv`](https://github.com/cryptocopycats/kitties/blob/master/1-99_999/002.csv) incl. 2000-2999,
and so on). The data records for kitties incl. id, gen(eration), matron+sire ids, birthdate, 48 (12x4) genes in kai (base32) notation, and more.

<!--
[(Crypto) Kittydex Kitty Data](https://cryptokittydex.com/resources) - public dataset in comma-separated values (CSV) format in a single file (+250 MiB).
The data records for kitties incl. id, gen(eration), matron+sire ids, birthdate, 48 (12x4) genes in kai (base32) notation, and more.
-->

Add your dataset here!

repo_name: cryptocopycats/copycats

repo_language: Ruby

mime_type: text/html


text:
# Notes

## Todos

- [ ] change formula / recipe to traits in traits.rb

### How to name the group of four genes?

use
- Gene
- Genes / G4 / Gene4x / ?
- Gene Slice / Slice / Gene::Slice / Genome::Slice
- Gene Family / Family
- Gene Cluster / Cluster
- Gene Group / Group
- Gene Region / Region
- Gene Seq / Sequence / ?
- Gene Set / Set / ?
- Multigene (?)
- Zone / Unit / ?

https://en.wikipedia.org/wiki/Gene

> The term gene was introduced by Danish botanist, plant physiologist and geneticist Wilhelm Johannsen in 1905.
> It is inspired by the ancient Greek: γόνος, gonos, that means offspring and procreation.

https://en.wikipedia.org/wiki/Gene_family

### print traits with database id - why? why not?

TRAIT_IDS.md - use / build a new page (report) - why? why not?

### Ruby SQL intersect with ActiveRecord

```
genes = Gene.where( trait: Trait.find_by( name: 'savannah' )).intersect(
        Gene.where( trait: Trait.find_by( name: 'pattern' )))
genes.map { |gene| gene.kitty }     # get kitties (from gene)
```

check if the same as:

```
genes = Gene.find_by( trait: Trait.find_by( name: 'savannah' )) &
        Gene.find_by( trait: Trait.find_by( name: 'pattern' ))
genes.map { |gene| gene.kitty }     # get kitties (from gene)
```

use to_sql  for see generated sql!

check if project on kitty_id required!!
otherwise all records are different (has its own id!!!)

### SQL

``` sql
SELECT kitty_id FROM genes WHERE trait_id = 0
INTERSECT
SELECT kitty_id FROM genes WHERE trait_id = 33
```

check query samples
with article and tags (find article by two tags - more than one - for example)

does it work with inner join?

``` sql
SELECT id      -- ??? to be done (not working)
FROM kitties
INNER JOIN genes
where genes.trait_id in [0,3]
```

or use subselect in where?

``` sql
SELECT id         -- ??? to be done (not working)
FROM kitties
where select kitty_id from genes where trait_id in [0,3]
```

---

## Mutations / Mewtations

5-bit Gene = 2^5 = 32 Values / Attributes

16 Mutation Pairs (16 x 2 = 32)

| Tier                   | Count | Total |
|------------------------|-------|-------|
| Tier 1 (Basic Traits)  |    16 |    16 |
| Tier 2 (Mutations)     |     8 |    24 |
| Tier 3 (Mutations)     |     4 |    28 |
| Tier 4 (Mutations)     |     2 |    30 |
| Tier 5 (Mutations)     |     1 |    31 |

```
Tier 1       Tier 2       Tier 3       Tier 4       Tier  5
 (0-15)       (16-23)      (24-27)      (28,29)      (30)
 0+1  = 16   16+17 = 24   24+25 = 28   28+29 = 30
 2+3  = 17   18+19 = 25   26+27 = 29
 4+5  = 18   20+21 = 26
 6+7  = 19   22+23 = 27
 8+9  = 20
10+11 = 21
12+13 = 22
14+15 = 23
```

Note: It's impossible for a mutation to reach `31` e.g.`30+31 = 31`.

in Kai (base32) notation:

```
Tier 1    Tier 2    Tier 3    Tier 4    Tier 5
 (1-g)     (h-p)     (q-t)     (u,v)      (w)
1+2 = h   h+i = q   q+r = u   u+v = w
3+4 = i   j+k = r   s+t = v
5+6 = j   m+n = s
7+8 = k   o+p = t
9+a = m
b+c = n
d+e = o
f+g = p
```

Note: It's impossible for a mutation to reach `x` e.g. `w+x = x`.

The formula is `n+(n+1) = n/2+16` if n is an even number (0,2,4,6,...).
Running:

``` ruby
(0..31).each do |n|
  if n.even?
    puts "#{n}+#{n+1} = #{n/2+16}"
  end
end
```

results in:

```
 0+1  = 16
 2+3  = 17
 4+5  = 18
 6+7  = 19
 8+9  = 20
10+11 = 21
12+13 = 22
14+15 = 23
16+17 = 24
18+19 = 25
20+21 = 26
22+23 = 27
24+25 = 28
26+27 = 29
28+29 = 30
30+31 = 31
```

and in kai (base32) notation.

``` ruby
ALPHABET = "123456789abcdefghijkmnopqrstuvwx"

(0..31).each do |n|
  if n.even?
    puts "#{ALPHABET[n]}+#{ALPHABET[n+1]} = #{ALPHABET[n/2+16]}"
  end
end
```

results in:

```
1+2 = h
3+4 = i
5+6 = j
7+8 = k
9+a = m
b+c = n
d+e = o
f+g = p
h+i = q
j+k = r
m+n = s
o+p = t
q+r = u
s+t = v
u+v = w
w+x = x
```

## Fancies

Stephen Curry Kitties

Three kitties created in the likeness of Stephen Curry, an NBA player.
These three kittis were ChefFurry, StephFurThree, and #30Furry (Kitty ids: 130, 230, and 330 respectively). However, shortly after releasing these kitties, their pages were removed. Currently the CryptoKitties team states:

We have reason to believe Steph wasn't as involved in the CurryKitties as we thought.
Until we're sure he's an active participant, we're suspending the campaign.

---

- 帝龙喵 (GoldenDragonCat)  888
- 旺财汪 (GoldDogCat) 1802, 1803, 1805, 1806, 1808, 1809, 1812, 1816, 1826, 1827, 1828

Fancies:
- 汪星爷/DogCat 88 tigerpunk, sweetlemoncakes, barkbrown, periwinkle, yokel
- 咚咚锵/LionDanceCat 888 manx, googly, royalblue, starstruck
- 红包喵/FortuneCat 888

http://www.cryptokittieswiki.com/Fancies/

http://www.cryptokittieswiki.com/contracts/

Trait Releases

In early April, 2018, trait releases were changed in gen0 kitties.
Specifically, after the Family Jewels update when new traits were released, it was relatively easy to snipe the diamond. This lead to automation that would quickly purchase the kitty. Therefore traits were changed so they would be introduced at H3 and then after some time go up to H2, till they are at the highest level they will achieve. While this did not eliminated the automation, it made the process require more investment and breeding to get the diamond.1


text:
### 0.0.1 / 2018-01-10

* Everything is new. First release


text:
require 'hoe'
require './lib/copycats/version.rb'

Hoe.spec 'copycats' do

  self.version = Copycats::VERSION

  self.summary = "copycats - command line tool (and core library) crypto cats / kitties collectibles unchained - buy! sell! hodl! sire! - play for free - runs off the blockchain w/ ledger lite - no ether / gas required; run your own peer-to-peer (P2P) network node over HTTP"
  self.description = summary

  self.urls    = ['https://github.com/cryptocopycats/copycats']

  self.author  = 'Gerald Bauer'
  self.email   = 'wwwmake@googlegroups.com'

  # switch extension to .markdown for gihub formatting
  self.readme_file  = 'README.md'
  self.history_file = 'CHANGELOG.md'

  self.extra_deps = [
    ['base32-alphabets', '>= 1.0.0'],
    ['csvreader',        '>= 1.2.3'],
    ['activerecord'],
    ['sqlite3'],
  ]

  self.licenses = ['Public Domain']

  self.spec_extras = {
    required_ruby_version: '>= 2.3'
  }

end


text:
# Kitty #99895

genes (kai): cdad 55r6 7f2f g9bg 1111 3757 d99d 272h 8k78 k884 9da9 fdee

Fur (Genes 0-3)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 0 | 01101 | e | **sphynx** | d |
| 1 | 01101 | e | sphynx | r1 |
| 2 | 01100 | d | munchkin | r2 |
| 3 | 01110 | f | ragamuffin | r3 |

d = dominant, r1 = 1st order recessive, r2 = 2nd order recessive, r3 = 3rd order recessive

Pattern (Genes 4-7)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 4 | 01000 | 9 | **calicool** | d |
| 5 | 01001 | a | luckystripe | r1 |
| 6 | 01100 | d | spock | r2 |
| 7 | 01000 | 9 | calicool | r3 |

Eye Color (Genes 8-11)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 8 | 00011 | 4 | **mintgreen** | d |
| 9 | 00111 | 8 | strawberry | r1 |
| 10 | 00111 | 8 | strawberry | r2 |
| 11 | 10011 | k | bubblegum | r3 |

Eye Shape (Genes 12-15)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 12 | 00111 | 8 | **thicccbrowz** | d |
| 13 | 00110 | 7 | crazy | r1 |
| 14 | 10011 | k | raisedbrow | r2 |
| 15 | 00111 | 8 | thicccbrowz | r3 |

Base Color (Genes 16-19)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 16 | 10000 | h | **cloudwhite** | d |
| 17 | 00001 | 2 | salmon | r1 |
| 18 | 00110 | 7 | aquamarine | r2 |
| 19 | 00001 | 2 | salmon | r3 |

Highlight Color (Genes 20-23)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 20 | 01100 | d | **coffee** | d |
| 21 | 01000 | 9 | swampgreen | r1 |
| 22 | 01000 | 9 | swampgreen | r2 |
| 23 | 01100 | d | coffee | r3 |

Accent Color (Genes 24-27)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 24 | 00110 | 7 | **kittencream** | d |
| 25 | 00100 | 5 | granitegrey | r1 |
| 26 | 00110 | 7 | kittencream | r2 |
| 27 | 00010 | 3 | peach | r3 |

Wild (Genes 28-31)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 28 | 00000 | 1 | **?** | d |
| 29 | 00000 | 1 | ? | r1 |
| 30 | 00000 | 1 | ? | r2 |
| 31 | 00000 | 1 | ? | r3 |

Mouth (Genes 32-35)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 32 | 01111 | g | **soserious** | d |
| 33 | 01010 | b | saycheese | r1 |
| 34 | 01000 | 9 | beard | r2 |
| 35 | 01111 | g | soserious | r3 |


text:
CHANGELOG.md
LICENSE.md
Manifest.txt
README.md
Rakefile
bin/kitty
lib/copycats.rb
lib/copycats/fancies.rb
lib/copycats/gene.rb
lib/copycats/genome.rb
lib/copycats/import/read.rb
lib/copycats/import/setup.rb
lib/copycats/models/kitty.rb
lib/copycats/reports/genes.rb
lib/copycats/reports/kitty.rb
lib/copycats/reports/mix.rb
lib/copycats/reports/traits.rb
lib/copycats/schema.rb
lib/copycats/tool.rb
lib/copycats/traits.rb
lib/copycats/traits_timeline.rb
lib/copycats/version.rb
test/helper.rb
test/test_genome.rb
test/test_mixgenes.rb
test/test_models.rb
test/test_traits.rb
test/test_version.rb


text:
# Kitty #100000

genes (kai): dadc 557j 4aaa gb9g 1161 383k 7dbe 774b 6667 8438 dd9a cbbd

Fur (Genes 0-3)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 0 | 01100 | d | **munchkin** | d |
| 1 | 01010 | b | chartreux | r1 |
| 2 | 01010 | b | chartreux | r2 |
| 3 | 01011 | c | himalayan | r3 |

d = dominant, r1 = 1st order recessive, r2 = 2nd order recessive, r3 = 3rd order recessive

Pattern (Genes 4-7)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 4 | 01001 | a | **luckystripe** | d |
| 5 | 01000 | 9 | calicool | r1 |
| 6 | 01100 | d | spock | r2 |
| 7 | 01100 | d | spock | r3 |

Eye Color (Genes 8-11)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 8 | 00111 | 8 | **strawberry** | d |
| 9 | 00010 | 3 | topaz | r1 |
| 10 | 00011 | 4 | mintgreen | r2 |
| 11 | 00111 | 8 | strawberry | r3 |

Eye Shape (Genes 12-15)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 12 | 00110 | 7 | **crazy** | d |
| 13 | 00101 | 6 | simple | r1 |
| 14 | 00101 | 6 | simple | r2 |
| 15 | 00101 | 6 | simple | r3 |

Base Color (Genes 16-19)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 16 | 01010 | b | **greymatter** | d |
| 17 | 00011 | 4 | orangesoda | r1 |
| 18 | 00110 | 7 | aquamarine | r2 |
| 19 | 00110 | 7 | aquamarine | r3 |

Highlight Color (Genes 20-23)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 20 | 01101 | e | **lemonade** | d |
| 21 | 01010 | b | scarlet | r1 |
| 22 | 01100 | d | coffee | r2 |
| 23 | 00110 | 7 | royalpurple | r3 |

Accent Color (Genes 24-27)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 24 | 10011 | k | **bloodred** | d |
| 25 | 00010 | 3 | peach | r1 |
| 26 | 00111 | 8 | emeraldgreen | r2 |
| 27 | 00010 | 3 | peach | r3 |

Wild (Genes 28-31)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 28 | 00000 | 1 | **?** | d |
| 29 | 00101 | 6 | ? | r1 |
| 30 | 00000 | 1 | ? | r2 |
| 31 | 00000 | 1 | ? | r3 |

Mouth (Genes 32-35)

|Gene  |Binary   |Kai  |Trait    |   |
|------|---------|-----|---------|---|
| 32 | 01111 | g | **soserious** | d |
| 33 | 01000 | 9 | beard | r1 |
| 34 | 01010 | b | saycheese | r2 |
| 35 | 01111 | g | soserious | r3 |


text:
# Crypto Copycats Collectibles - Buy! Sell! Hodl! Sire!

copycats command line tool (and core library) - crypto cats / kitties collectibles unchained - buy! sell! hodl! sire! - play for free - runs off the blockchain w/ ledger lite - no ether / gas required; run your own peer-to-peer (P2P) network node over HTTP

* home  :: [github.com/cryptocopycats/copycats](https://github.com/cryptocopycats/copycats)
* bugs  :: [github.com/cryptocopycats/copycats/issues](https://github.com/cryptocopycats/copycats/issues)
* gem   :: [rubygems.org/gems/copycats](https://rubygems.org/gems/copycats)
* rdoc  :: [rubydoc.info/gems/copycats](http://rubydoc.info/gems/copycats)

<!--
## Timeline / Updates

### New Traits / Catttributes

Mar 21, 2018
- **Springcrocus** - highlight color (2) trait is discovered. #Cattribute

Mar 16, 2018
- **Thunderstruck** - pattern (i) trait is discovered. #Cattribute
- **Rascal** - pattern (3) trait is discovered. #Cattribute

Mar 14, 2018
- **Dragonfruit** - base color (e) trait is discovered. #Cattribute

Mar 9, 2018
- **Belch** - mouth (7) trait is discovered. #Cattribute

Mar 8, 2018
- **Pixiebob** - fur (8) trait is discovered. #Cattribute
- **Poisonberry** - highlight color (4) trait is discovered. #Cattribute
- **Safetyvest** - highlight color (i) trait is discovered. #Cattribute

Mar 5, 2018
- **Cyan** - eye color (g) trait is discovered. #Cattribute

See [Updates - Traits](updates/TRAITS.md) for more.

### New Fancies / Exclusives

Mar 16, 2018
- **Berry** (?) Fancy Cat is discovered. #Fancy Cat

Mar 7, 2018
- **Lion Dance** (888) Fancy Cat is discovered. #Fancy Cat

Mar 2, 2018
- **Dogcat** (88) Fancy Cat is discovered. #Fancy Cat

Mar 1, 2018
- Exclusive **Knight Kitty** (?) is released. #Fancy Cat #Exclusive

See [Updates - Fancies / Exclusives](updates/FANCIES.md) for more.
-->

## kitty Command Line Tool

Use the `kitty` command line tool to (auto-)read kitty data records
(in comma-separated values (CSV)) into an in-memory SQLite database
and print reports. Example - [`kitties/1-99_999/000.csv`](https://github.com/cryptocopycats/kitties/blob/master/1-99_999/000.csv):

```
id,gen,matron_id,sire_id,birthdate,genes,name
1,0,,,2017-11-23 06:19:59,ccac 7787 fa7f afaa 1646 7755 f9ee 4444 6766 7366 cccc eede,
2,0,,,2017-11-23 06:19:59,ca9c 7575 f442 af9g 6664 5557 7777 4444 6686 8667 cccc ffec,
3,0,,,2017-11-23 06:19:59,ac9a 6686 ff7f 99aa 6666 5575 779f 4444 6786 7748 cccc dcfc,
4,0,,,2017-11-23 06:19:59,ccac 5686 22ff f99g 1616 7555 ffed 4444 8668 4687 cccc dcff,
5,0,,,2017-11-23 06:19:59,ca9c 8777 747f g99g 4411 7775 f77d 4444 7788 6377 cccc ffef,
...
```

Note: By default all datafiles (`**/*.csv`) in the `./data` and all subdirectories
get (auto-)read. Use the `-i/--include` option to change the data directory.

### Kitty Genes Reader / Report

Pass in the id (e.g. `1`, `43`, etc.) of the kitty to print a genes report.
Example:

```
$ kitty 1
```

prints:

```
# Kitty #1

genes (kai): ccac 7787 fa7f afaa 1646 7755 f9ee 4444 6766 7366 cccc eede

Fur (Genes 0-3)

|Gene| Binary |Kai (Tier)| Trait      |    |
|----|--------|----------|------------|----|
| 0  | 01101  | e (I)    | **sphynx** | d  |
| 1  | 01100  | d (I)    | munchkin ) | r1 |
| 2  | 01101  | e (I)    | sphynx     | r2 |
| 3  | 01101  | e (I)    | sphynx     | r3 |

d = dominant, r1 = 1st order recessive, r2 = 2nd order recessive, r3 = 3rd order recessive

Pattern (Genes 4-7)

|Gene| Binary |Kai (Tier)| Trait      |    |
|----|--------|----------|------------|----|
| 4  | 01011  | c (I)    | **jaguar** | d  |
| 5  | 01011  | c (I)    | jaguar     | r1 |
| 6  | 01011  | c (I)    | jaguar     | r2 |
| 7  | 01011  | c (I)    | jaguar     | r3 |

Eye Color (Genes 8-11)

|Gene| Binary |Kai (Tier)| Trait       |    |
|----|--------|----------|-------------|----|
| 8  | 00101  | 6 (I)    | **sizzurp** | d  |
| 9  | 00101  | 6 (I)    | sizzurp     | r1 |
| 10 | 00010  | 3 (I)    | topaz       | r2 |
| 11 | 00110  | 7 (I)    | chestnut    | r3 |

Eye Shape (Genes 12-15)

|Gene| Binary  |Kai (Tier)| Trait      |    |
|----|---------|----------|------------|----|
| 12 | 00101   | 6 (I)    | **simple** | d  |
| 13 | 00101   | 6 (I)    | simple     | r1 |
| 14 | 00110   | 7 (I)    | crazy      | r2 |
| 15 | 00101   | 6 (I)    | simple     | r3 |

Base Color (Genes 16-19)

|Gene| Binary |Kai (Tier)| Trait          |    |
|----|--------|----------|----------------|----|
| 16 | 00011  | 4 (I)    | **orangesoda** | d  |
| 17 | 00011  | 4 (I)    | orangesoda     | r1 |
| 18 | 00011  | 4 (I)    | orangesoda     | r2 |
| 19 | 00011  | 4 (I)    | orangesoda     | r3 |

Highlight Color (Genes 20-23)

|Gene| Binary |Kai (Tier)| Trait        |    |
|----|--------|----------|--------------|----|
| 20 | 01101  | e (I)    | **lemonade** | d  |
| 21 | 01101  | e (I)    | lemonade     | r1 |
| 22 | 01000  | 9 (I)    | swampgreen   | r2 |
| 23 | 01110  | f (I)    | chocolate    | r3 |

Accent Color (Genes 24-27)

|Gene| Binary |Kai (Tier)| Trait           |    |
|----|--------|----------|-----------------|----|
| 24 | 00100  | 5 (I)    | **granitegrey** | d  |
| 25 | 00100  | 5 (I)    | granitegrey     | r1 |
| 26 | 00110  | 7 (I)    | kittencream     | r2 |
| 27 | 00110  | 7 (I)    | kittencream     | r3 |

Wild (Genes 28-31)

|Gene| Binary |Kai (Tier)| Trait   |    |
|----|--------|----------|---------|----|
| 28 | 00101  | 6 (I)    | **?**   |  d |
| 29 | 00011  | 4 (I)    | ?       | r1 |
| 30 | 00101  | 6 (I)    | ?       | r2 |
| 31 | 00000  | 1 (I)    | ?       | r3 |

Mouth (Genes 32-35)

|Gene| Binary |Kai (Tier)| Trait        |   |
|----|--------|----------|--------------|---|
| 32 | 01001  | a (I)    | **pouty**    | d |
| 33 | 01001  | a (I)    | pouty        | r1 |
| 34 | 01110  | f (I)    | happygokitty | r2 |
| 35 | 01001  | a (I)    | pouty        | r3 |
```

### Kitty Mix Genes (Matron + Sire) Report

Pass in two ids for the matron and sire kitties to print a mix genes report.
Example:

```
$ kitty 2 43
```

prints:

```
# Kitty #2 + #43

genes (kai) 1: ca9c 7575 f442 af9g 6664 5557 7777 4444 6686 8667 cccc ffec
genes (kai) 2: ca9a 7588 72a7 fa9f 4111 5555 dedf 4444 5888 4666 cccc fded

Fur (Genes 0-3)

|Gene|Kai|Trait (Matron) |Kai|Trait (Sire)  |    |
|----|---|---------------|---|--------------|----|
| 0  | c | **himalayan** | d | **munchkin** | d  |
| 1  | e | sphynx        | e | sphynx       | r1 |
| 2  | f | ragamuffin    | d | munchkin     | r2 |
| 3  | f | ragamuffin    | f | ragamuffin   | r3 |

d = dominant, r1 = 1st order recessive, r2 = 2nd order recessive, r3 = 3rd order recessive

Pattern (Genes 4-7)

|Gene|Kai|Trait (Matron)|Kai|Trait (Sire)|    |
|----|---|--------------|---|------------|----|
| 4  | c | **jaguar**   | c | **jaguar** | d  |
| 5  | c | jaguar       | c | jaguar     | r1 |
| 6  | c | jaguar       | c | jaguar     | r2 |
| 7  | c | jaguar       | c | jaguar     | r3 |

Eye Color (Genes 8-11)

|Gene|Kai|Trait (Matron)|Kai|Trait (Sire) |    |
|----|---|--------------|---|-------------|----|
| 8  | 7 | **chestnut** | 6 | **sizzurp** | d  |
| 9  | 6 | sizzurp      | 6 | sizzurp     | r1 |
| 10 | 6 | sizzurp      | 6 | sizzurp     | r2 |
| 11 | 8 | strawberry   | 4 | mintgreen   | r3 |

Eye Shape (Genes 12-15)

|Gene|Kai|Trait (Matron)|Kai|Trait (Sire)     |    |
|----|---|--------------|---|-----------------|----|
| 12 | 6 | **simple**   | 8 | **thicccbrowz** | d  |
| 13 | 8 | thicccbrowz  | 8 | thicccbrowz     | r1 |
| 14 | 6 | simple       | 8 | thicccbrowz     | r2 |
| 15 | 6 | simple       | 5 | otaku           | r3 |

Base Color (Genes 16-19)

|Gene|Kai|Trait (Matron)  |Kai|Trait (Sire)    |    |
|----|---|----------------|---|----------------|----|
| 16 | 4 | **orangesoda** | 4 | **orangesoda** | d  |
| 17 | 4 | orangesoda     | 4 | orangesoda     | r1 |
| 18 | 4 | orangesoda     | 4 | orangesoda     | r2 |
| 19 | 4 | orangesoda     | 4 | orangesoda     | r3 |

Highlight Color (Genes 20-23)

|Gene|Kai|Trait (Matron)   |Kai|Trait (Sire)   |    |
|----|---|-----------------|---|---------------|----|
| 20 | 7 | **royalpurple** | f | **chocolate** | d  |
| 21 | 7 | royalpurple     | d | coffee        | r1 |
| 22 | 7 | royalpurple     | e | lemonade      | r2 |
| 23 | 7 | royalpurple     | d | coffee        | r3 |

Accent Color (Genes 24-27)

|Gene|Kai|Trait (Matron)   |Kai|Trait (Sire)     |    |
|----|---|-----------------|---|-----------------|----|
| 24 | 7 | **kittencream** | 5 | **granitegrey** | d  |
| 25 | 5 | granitegrey     | 5 | granitegrey     | r1 |
| 26 | 5 | granitegrey     | 5 | granitegrey     | r2 |
| 27 | 5 | granitegrey     | 5 | granitegrey     | r3 |

Wild (Genes 28-31)

|Gene|Kai|Trait (Matron)|Kai|Trait (Sire)|    |
|----|---|--------------|---|------------|----|
| 28 | 4 | **?**        | 1 | **?**      | d  |
| 29 | 6 | ?            | 1 | ?          | r1 |
| 30 | 6 | ?            | 1 | ?          | r2 |
| 31 | 6 | ?            | 4 | ?          | r3 |

Mouth (Genes 32-35)

|Gene|Kai|Trait (Matron) |Kai|Trait (Sire)      |    |
|----|---|---------------|---|------------------|----|
| 32 | g | **soserious** | f | **happygokitty** | d  |
| 33 | 9 | beard         | 9 | beard            | r1 |
| 34 | f | happygokitty  | a | pouty            | r2 |
| 35 | a | pouty         | f | happygokitty     | r3 |
```

## Installation - I Can Has Copycats?

Use:

```
$ gem install copycats
```

## License

![](https://publicdomainworks.github.io/buttons/zero88x31.png)

The `copycats` scripts are dedicated to the public domain.
Use it as you please with no restrictions whatsoever.

## Questions? Comments?

Post them on the [cryptokitties reddit](https://www.reddit.com/r/cryptokitties). Thanks.


text:
# encoding: utf-8

####
# use:
#  $ ruby -I ./lib script/kitty.rb

require 'copycats'

KittyReport.new( id:  100_000,
                 genes: "dadc 557j 4aaa gb9g 1161 383k 7dbe 774b 6667 8438 dd9a cbbd"
               ).save( "./KITTY-100000.md" )

KittyReport.new( id:    99_895,
                 genes: "cdad 55r6 7f2f g9bg 1111 3757 d99d 272h 8k78 k884 9da9 fdee"
               ).save( "./KITTY-99895.md" )


text:
# encoding: utf-8

####################
# bulk converter for csv datafile
#
#  use:
#    $ ruby ./script/convert.rb
#
#  Note: change the input file (at the end of the script)!

require 'csv'
require 'pp'
require 'fileutils'

class KittyConverter

###
# 1-99_999/
#   1.csv      ### 1-999.csv
#   2.csv      ##  1_000-1_999.csv
#   3.csv      ##  2_000-2_999.csv
#   ...
#   99.csv     ### 90_000- 99_999.csv
# 100_000-199_999/
#  100.csv
#  101.csv    ### 101_000-101_999.csv
#  102.csv    ### 102_000-102_999.csv
# 200_000_299_999/
# 300_000-399_999/
# 400_000-499_999/
# 500_000-599_999/
# 600_000-699_999/

DIR={
  0 => '1-99_999',
  1 => '100_000-199_999',
  2 => '200_000_299_999',
  3 => '300_000-399_999',
  4 => '400_000-499_999',
  5 => '500_000-599_999',
  6 => '600_000-699_999',
  7 => '700_000-799_999',
  8 => '800_000-899_999',
  9 => '900_000-999_999',
}

def id_to_nos( id )
  dir_no  = id / 100_000     ## e.g. 0-9 ++ 1 => 100_000-199_999 ...
  file_no = id / 1000        ## e.g. 0-999 ++ 1 => 001 ...

  ## puts "id: #{id} => #{dir_no} / #{file_no}"

  [dir_no, file_no]
end

HEADERS = ['id',
           'gen',
           'matron_id',
           'sire_id',
           'birthdate',
           'genes',
           'name']

def initialize
  @last_no   = [-1,-1]
  @last_out  = nil
end

def out_for( id, out_dir: )
  no = id_to_nos( id )
  print no.inspect

  ## check for new section / batch
  ##  if yes, close old file and open new file
  if @last_no[1] != no[1]
     @last_out.close   if @last_out

     root     = "#{out_dir}/#{DIR[no[0]]}"
     num      = '%03d' % no[1]    ## use 000, 001, 002, 099,
                                  ##     100, 101, 102, ... etc.
     filepath = "#{root}/#{num}.csv"
     puts "\n** >#{filepath}<:"

     FileUtils.mkdir_p( root ) unless Dir.exist?( root )

     out = CSV.open( filepath, 'w' )
     out << HEADERS
     @last_out = out
  end

  @last_no = no
  @last_out
end

def convert( path, out_dir: './o' )
  i       = 0
  last_id = 0
  out     = nil

  CSV.foreach( path, headers: true ) do |row|
    if i==0
      pp row
      pp row.headers
    end

    id = row['id'].to_i
    print id
    print "."

    if id < last_id
      puts "!!! kitty id must be greater than last id"
      exit 1
    end

    last_id = id

    rec = []
    rec << row['id']
    rec << row['gen']
    rec << (row['matron_id'] == '0' ? nil : row['matron_id'])
    rec << (row['sire_id'] == '0' ? nil : row['sire_id'])
    rec << row['birth_date']
    rec << fmt_kai(row['genes_kai'])
    rec << nil  ## name

    out = out_for( id, out_dir: out_dir )
    out << rec

    i+=1
    ## break if i>=2002
  end

  print "\n"
  out.close   ## close last out(put) file/stream
end

#########################
### format helpers

def fmt_kai( kai )
  ## format in groups of four (4) separated by space
  ##  e.g.  ccac7787fa7fafaa16467755f9ee444467667366cccceede
  ##     :  ccac 7787 fa7f afaa 1646 7755 f9ee 4444 6766 7366 cccc eede
  kai.reverse.gsub( /(.{4})/, '\1 ').reverse.strip
end

end # class KittyConverter

conv = KittyConverter.new
conv.convert( 'dl/kittydex-20180320.csv', out_dir: '../kitties' )


text:
#!/usr/bin/env ruby

###################
# DEV TIPS:
#
# For local testing run like:
#
#    ruby -I./lib bin/kitty
#
# Set the executable bit in Linux. Example:
#
#    % chmod a+x bin/kitty
#

require 'copycats'

Copycats.main


text:
# encoding: utf-8

require 'pp'        # for pp => pretty printer

require 'date'
require 'time'
require 'uri'
require 'json'
require 'enumerator'    ## needed for each_slice
require 'optparse'      # note: used for command line tool (see Tool in tool.rb)

require 'logger'    # note: use for ActiveRecord::Base.logger -- remove/replace later w/ LogUtils::Logger ???

## 3rd party gems
require 'active_record'   ## todo: add sqlite3? etc.

require 'base32-alphabets'      ## base32 / kai encoding / decoding for genes
require 'csvreader'

## our own code
require 'copycats/version'    # note: let version always go first
require 'copycats/traits'
require 'copycats/traits_timeline'

require 'copycats/fancies'
require 'copycats/gene'
require 'copycats/genome'

require 'copycats/schema'

require 'copycats/models/kitty'

require 'copycats/links'

require 'copycats/reports/kitty'
require 'copycats/reports/mix'
require 'copycats/reports/genes'
require 'copycats/reports/traits'

require 'copycats/import/setup'
require 'copycats/import/read'

require 'copycats/tool'

## add wrapper for allowing configuration of random number generator
module Lottery

  def self.random
    @random ||= Random.new
  end

  def self.random=(value)
    @random = value
  end

  def self.rand( arg=nil )
    if arg.is_a? Integer
      random.rand( arg )  ## max (number) = arg
    else
      random.rand     ## between 0.0 and 1.0 (as floating point number)
    end
  end
end  ## module Lottery

module Copycats
## add command line binary (tool) e.g. $ try kitty -h
  def self.main
   Tool.new.run( ARGV )
  end
end

# say hello
puts Copycats.banner    if defined?($RUBYLIBS_DEBUG) && $RUBYLIBS_DEBUG


text:
# encoding: utf-8

class Gene

### todo/check:
##    find a better name for Slice(incl.4 genes)
##      e.g. GeneFour, Gene4, GeneGroup, GeneSlice,TraitGenes,... - why? why not?

class Slice   ## Gene::Slice (nested class)

  attr_reader :p, :h1, :h2, :h3   # p(rimary), h(idden) 1, h(idden) 2, h(idden) 3

  ## compat: add alias for ("old/classic") d, r1, r2, r3
  # d  (dominant gene)    -- todo/check: rename to just d instead of d0 - why? why not?
  # r1 (1st order recessive gene)
  # r2 (2nd order recessive gene)
  # r3 (3rd order recessive gene)
  def d()  p;  end   # todo: use alias - why? why not?
  def r1() h1; end
  def r2() h2; end
  def r3() h3; end

  def initialize( arg )
     ## (always) assume String in base32/kai for now
     kai = arg
     ## puts "Gene.initialize #{kai}"
     kai = kai.reverse
     @p   = kai[0]
     @h1  = kai[1]
     @h2  = kai[2]
     @h3  = kai[3]
  end

  def to_kai()  @h3 + @h2 + @h1 + @p; end   ## return a string in kai/base32 notation

  def swap
    puts "Gene#swap"
    kai = to_kai.reverse      # note: use reverse kai string (kai[0] is first char/digit/letter)

    3.downto(1) do |i|
      if Lottery.rand(100) < 25
        puts " bingo! swap #{i}<>#{i-1}"
        kai[i-1], kai[i] = kai[i], kai[i-1]
      end
    end
    Gene.new( kai.reverse )  ## note: do NOT forget to pass in kai (unreversed)
  end

  def mutate( other )
    puts "Gene#mutate"

    gene1 = Kai::NUMBER[p]          ## primary/dominant gene1
    gene2 = Kai::NUMBER[other.p]    ## primary/dominant gene2
    if gene1 > gene2
      gene1, gene2 = gene2, gene1     ## make sure gene2 is always bigger
    end
    if (gene2 - gene1) == 1 && gene1.even?
      probability = 25
      probability /= 2   if gene1 > 23
      if Lottery.rand(100) < probability
        genex = (gene1 / 2) + 16       ## 16=2^4
        puts " bingo! mutation #{gene2}+#{gene1} => #{genex}"
        puts "                 #{Kai::ALPHABET[gene2]}+#{Kai::ALPHABET[gene1]} => #{Kai::ALPHABET[genex]}"
        return Kai::ALPHABET[genex]
      end
    end
    nil   # no mutation
  end

  def mix_inner( other )
    puts "Gene#mix_inner"

    new_p = mutate( other )
    if new_p.nil?    ## no mutation of gene.p - use "regular" formula
      new_p = Lottery.rand(100) < 50 ? p : other.p
    end

    new_h1 = Lottery.rand(100) < 50 ? h1 : other.h1
    new_h2 = Lottery.rand(100) < 50 ? h2 : other.h2
    new_h3 = Lottery.rand(100) < 50 ? h3 : other.h3

    gene = Gene.new( new_h3 + new_h2 + new_h1 + new_p )
    pp gene
    gene
  end

  def mix( other )
    puts "Gene#mix"
    self_swapped  = swap
    other_swapped = other.swap

    gene = self_swapped.mix_inner( other_swapped )
    pp gene
    gene
  end

end  # class Slice
end  # class Gene


text:
# encoding: utf-8

### build "reverse" lookup tables

TRAITS_BY_NAME = {}  # e.g. savannah, selkirk, chantilly, ...
TRAITS_BY_CODE = {}  # e.g. FU00, FU01, FU02, ...

TRAITS.each do |key,h|
  h[:kai].each do |kai, name|

    num = Kai::NUMBER[kai]
    code = "#{h[:code]}#{'%02d' % num}"   ## e.g. FU00, FU01, etc.

    rec = {}
    rec[:name] = name
    rec[:kai]  = kai
    rec[:code] = code
    rec[:type] = key   ## todo - use trait instead of type  (use string not symbol?) - why? why not?

    if TRAITS_BY_NAME[name]
      puts "warn: duplicate trait name!! overwritting old #{name}:"
      pp TRAITS_BY_NAME[name]
      ## add count - why? why not?
      ##  note: totebasic used three (3) times!!!
      ##   how to make name "unique" ? - add kai e.g. totebase_g etc. - why? why not?
    end

    TRAITS_BY_NAME[name] = rec    unless name.empty? || name == '?'
    TRAITS_BY_CODE[code] = rec
  end
end


text:
# encoding: utf-8

module Copycats

class Tool

def run( args )
  opts = {}

  parser = OptionParser.new do |cmd|
    cmd.banner = "Usage: kitty [options]"

    cmd.separator ""
    cmd.separator "  Database options:"

    cmd.on("-n", "--dbname=NAME", "Database name (default: kitties.db)") do |name|
      opts[:db_name] = name
    end

    cmd.on("-d", "--dbpath=PATH", "Database path (default: .)") do |path|
      opts[:db_path] = path
    end

    cmd.separator ""
    cmd.separator "  Data options:"

    cmd.on("-i", "--include=DIR", "Data directory (default: ./data for in-memory queries and . for setup)") do |name|
      opts[:data_dir] = name
    end

    cmd.separator ""
    cmd.separator "  General options:"

    cmd.on("-h", "--help", "Prints this help") do
      puts cmd
      exit
    end
  end

  parser.parse!( args )
  pp opts
  pp args

  db_path = opts[:db_path] || '.'
  db_name = opts[:db_name] || 'kitties.db'
  database = "#{db_path}/#{db_name}"

  if args.size == 1 && args[0] == 'setup'   ## setup database
    connect( adapter: 'sqlite3',
             database: database )

    setup_db
    setup_traits  # note: also builds TRAIT_IDS_CACHE for read_datafiles for now

    data_dir = opts[:data_dir] || '.'    ## for setup assume local dir for data
    read_datafiles( data_dir: data_dir )
  else
    ## check if (local) database exists?
    if File.exist?( database )   ##   if yes, use "cached" local database
      connect( adapter: 'sqlite3',
               database: database )
    else                         ##  otherwise use (new) on-demand in-memory database
      puts "note: database >#{database}<; setting up in-memory database"
      connect( adapter:  'sqlite3',
               database: ':memory:' )

      setup_db
      setup_traits  # note: also builds TRAIT_IDS_CACHE for read_datafiles for now

      data_dir = opts[:data_dir] || './data'
      read_datafiles( data_dir: data_dir )
    end

    if args.size == 1
      id = args[0].to_i
      kitty = Copycats::Model::Kitty.find( id )

      ## pp kitty
      ## pp kitty.genes
      ## pp kitty.body
      ## pp kitty.pattern
      ## pp kitty.genes.to_a

      buf = KittyReport.new( kitty ).build

      puts buf
    elsif args.size == 2
      matron_id = args[0].to_i
      sire_id   = args[1].to_i

      matron = Copycats::Model::Kitty.find( matron_id )
      sire   = Copycats::Model::Kitty.find( sire_id )
      ## pp matron

      buf = MixReport.new( matron, sire ).build

      puts buf
    else
      puts "error: too many arguments (expected setup or one or two kitty ids / names / genes)"
    end
  end
end  ## method run

end   ## class Tool

end   ## module Copycats


text:
# encoding: utf-8

class Genome
  attr_reader :genes   ## hash of (sliced) genes (key is gene trait type)

  def initialize( arg )
    if arg.is_a? Hash
      hash = arg       ## assumes (pre-built) hash with genes
      @genes = hash
    else
      if arg.is_a? Integer   ## use Integer (Fixnum+Bignum??) - why? why not?
        num = arg
        kai = Kai.encode( num )
      else              # else assume string in kai/base32 format
        kai = arg.dup  # just in case; make a clean (fresh) copy
        kai = kai.gsub( ' ', '' )   ## allow spaces (strip/remove)
      end
      ## puts "Genome.initialize #{kai}"
      build_genes( kai )
    end
  end

  def build_genes( kai )
    kai = kai.reverse    ## note: reserve for easy left-to-right access
    @genes = {}   ## hash of genes (key is gene type)
    ## fix/todo: use as_json for "official" api order
    ## note: use insert order from "official" api
    @genes[:body]        = Gene::Slice.new( kai[0,4].reverse )
    @genes[:pattern]     = Gene::Slice.new( kai[4,4].reverse )
    @genes[:coloreyes]   = Gene::Slice.new( kai[8,4].reverse )
    @genes[:eyes]        = Gene::Slice.new( kai[12,4].reverse )
    @genes[:color1]      = Gene::Slice.new( kai[16,4].reverse )  ## colorprimary   / body color / base color
    @genes[:color2]      = Gene::Slice.new( kai[20,4].reverse )  ## colorsecondary / sec color / pattern color /  hi(light) color
    @genes[:color3]      = Gene::Slice.new( kai[24,4].reverse )  ## colortertiary  / acc(ent) color
    @genes[:wild]        = Gene::Slice.new( kai[28,4].reverse )
    @genes[:mouth]       = Gene::Slice.new( kai[32,4].reverse )
    @genes[:environment] = Gene::Slice.new( kai[36,4].reverse )
    @genes[:secret]      = Gene::Slice.new( kai[40,4].reverse )
    @genes[:prestige]    = Gene::Slice.new( kai[44,4].reverse )
  end

  def body()      TRAITS[:body][:kai][ @genes[:body].p ]; end
  def coloreyes() TRAITS[:coloreyes][:kai][ @genes[:coloreyes].p ]; end
  def eyes()      TRAITS[:eyes][:kai][ @genes[:eyes].p ]; end
  def pattern()   TRAITS[:pattern][:kai][ @genes[:pattern].p ]; end
  def mouth()     TRAITS[:mouth][:kai][ @genes[:mouth].p ]; end
  def color1()    TRAITS[:color1][:kai][ @genes[:color1].p ]; end
  def color2()    TRAITS[:color2][:kai][ @genes[:color2].p ]; end
  def color3()    TRAITS[:color3][:kai][ @genes[:color3].p ]; end

  def wild()        TRAITS[:wild][:kai][ @genes[:wild].p ]; end
  def environment() TRAITS[:environment][:kai][ @genes[:environment].p ]; end

  def genes_color1() @genes[:color1]; end   ## rename to color1_genes instead - why? why not?
  def genes_eyes()   @genes[:eyes];   end
  ## ....

  ## add cattributes  ?? why? why not?

  def mix( other )
    mgenes = genes          ## matron genes
    sgenes = other.genes    ## sire genes
    new_genes  = {}

    [:body,      ### todo/fix: use TRAITS.keys or something - why? why not?
     :pattern,
     :coloreyes,
     :eyes,
     :color1,
     :color2,
     :color3,
     :wild,
     :mouth,
     :environment,
     :secret,
     :prestige].each do |key|
      mgene = mgenes[key]
      sgene = sgenes[key]

      new_gene = mgene.mix( sgene )
      new_genes[key] = new_gene
    end

    Genome.new( new_genes )   ## return new genome from (pre-built) hash (with genes)
  end

  def build_tables()   GenomeTables.new( self ).build;  end

  def build_mix_tables( other )  GenomeMixTables.new( self, other ).build; end

end # class Genome

class GenomeTables
  def initialize( genome )
    @genome = genome
  end

  def build
    pos = 0
    buf = ""

    genes = @genome.genes

    TRAITS.each do |key, trait|
      gene       = genes[key]
      next  if gene.nil?   ## skip future_1, future_2, etc.

      buf << "#{trait[:name]} (Genes #{trait[:genes]})\n\n"

###
##   fix/todo: add stars for purity?
##     ****   - all traits the same
##     ***    - two same pairs of traits
##     **     - one pair of same traits

      buf << "|Gene  |Binary   |Kai  |Trait    |   |\n"
      buf << "|------|---------|-----|---------|---|\n"
      buf << "| #{pos} | #{Kai::BINARY[gene.d]} | #{gene.d} | **#{fmt_trait(trait[:kai][gene.d])}** | d |\n"; pos+=1
      buf << "| #{pos} | #{Kai::BINARY[gene.r1]} | #{gene.r1} | #{fmt_trait(trait[:kai][gene.r1])} | r1 |\n"; pos+=1
      buf << "| #{pos} | #{Kai::BINARY[gene.r2]} | #{gene.r2} | #{fmt_trait(trait[:kai][gene.r2])} | r2 |\n"; pos+=1
      buf << "| #{pos} | #{Kai::BINARY[gene.r3]} | #{gene.r3} | #{fmt_trait(trait[:kai][gene.r3])} | r3 |\n"; pos+=1
      buf << "\n"

      if key == :body    ## add legend for first entry
        buf << "d = dominant, r1 = 1st order recessive, r2 = 2nd order recessive, r3 = 3rd order recessive\n\n"
      end
    end

    buf
  end

####################
## helpers

  def fmt_trait( trait )
    (trait.nil? || trait.empty?) ? '?' : trait
  end
end  # class GenomeTables

class GenomeMixTables
  def initialize( matron, sire )
    @matron = matron
    @sire   = sire
  end

  def build
    pos = 0
    buf = ""

    mgenes = @matron.genes
    sgenes = @sire.genes

    TRAITS.each do |key, trait|
      mgene  = mgenes[key]
      sgene  = sgenes[key]
      next  if mgene.nil?   ## skip future_1, future_2, etc.

      buf << "#{trait[:name]} (Genes #{trait[:genes]})\n\n"

      buf << "|Gene  |Kai  |Trait (Matron)|Kai|Trait (Sire)|   |\n"
      buf << "|------|-----|---------|-----|---------|---|\n"
      buf << "| #{pos} | #{mgene.d} | **#{fmt_trait(trait[:kai][mgene.d])}** | #{sgene.d} | **#{fmt_trait(trait[:kai][sgene.d])}** | d |\n"; pos+=1
      buf << "| #{pos} | #{mgene.r1} | #{fmt_trait(trait[:kai][mgene.r1])} | #{sgene.r1} | #{fmt_trait(trait[:kai][sgene.r1])} | r1 |\n"; pos+=1
      buf << "| #{pos} | #{mgene.r2} | #{fmt_trait(trait[:kai][mgene.r2])} | #{sgene.r2} | #{fmt_trait(trait[:kai][sgene.r2])} | r2 |\n"; pos+=1
      buf << "| #{pos} | #{mgene.r3} | #{fmt_trait(trait[:kai][mgene.r3])} | #{sgene.r3} | #{fmt_trait(trait[:kai][sgene.r3])} | r3 |\n"; pos+=1
      buf << "\n"

      if key == :body    ## add legend for first entry
        buf << "d = dominant, r1 = 1st order recessive, r2 = 2nd order recessive, r3 = 3rd order recessive\n\n"
      end
    end

    buf
  end

####################
## helpers

  def fmt_trait( trait )
    (trait.nil? || trait.empty?) ? '?' : trait
  end
end  # class GenomeMixTables


text:
# encoding: utf-8

module Copycats

  MAJOR = 0
  MINOR = 8
  PATCH = 1
  VERSION = [MAJOR,MINOR,PATCH].join('.')

  def self.version
    VERSION
  end

  def self.banner
    "copycats/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
  end

  def self.root
    "#{File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) )}"
  end

end # module Copycats


text:
# encoding: utf-8

#######################
## yuml.me diagram:

=begin
[Kitties|id;gen;birthdate;day_count;genes_kai;name]1-<>48 (12x4)[Genes|n;gene;gene_n],
[Genes]0..*<>-1[Traits|n;kai;tier;name],
[Traits]32<>-1[Trait Types|name;key],
[Kitties]<-parents (matron x sire) [Kitties],
=end

class CreateDb

def up

ActiveRecord::Schema.define do

create_table :kitties do |t|
  ### t.integer :ref,    null: false    ## reference id - todo - find a better name e.g. uid (for unique id?) or gid?
  ## note: use built-in id for id!!!!

  t.string   :name       ## optional name e.g. Genesis, Galaxy Cat, etc.
  t.string   :genes_kai, null: false  ## genes in kai format
  t.integer  :gen,       null: false  ## generation e.g. 0,1,2,etc.
  t.datetime :birthdate, null: false
  t.integer  :day_count, null: false  ## day 1, day 2, running day counter since 2017-11-23

  t.references  :matron   ## optional references kitty (with matron id)
  t.references  :sire     ## optional references kitty (with sire id)

  t.boolean  :is_fancy,      null: false, default: false
  t.boolean  :is_exclusive,  null: false, default: false
  t.boolean  :is_founder,    null: false, default: false   # ids 1 to 100 (in cryptokitties)

  ## for easy queries add convenience gene-d for all traits
  t.references  :body,      null: false    ## gene 0 (d)
  t.references  :pattern,   null: false    ## gene 4 (d)
  t.references  :coloreyes, null: false    ## gene 8 (d)
  t.references  :eyes,      null: false    ## gene 12 (d)
  t.references  :color1,    null: false    ## gene 16 (d)
  t.references  :color2,    null: false    ## gene 20 (d)
  t.references  :color3,    null: false    ## gene 24 (d)
  t.references  :wild,      null: false    ## gene 28 (d)
  t.references  :mouth,     null: false    ## gene 32 (d)
end

create_table :genes do |t|
  t.references  :kitty,   null: false
  t.integer     :n,       null: false   # gene number/pos  0-47  (start w/ 1 why? why not?)

  t.string      :gene,    null: false   # d, r1, r2, r3
  t.integer     :gene_n,  null: false   # 0-3  (0=d,1=r1,2=r2,3=r3 - start w/ 1 why? why not?)

  t.references  :trait,   null: false
end

create_table :traits do |t|
  t.references :trait_type,  null: false
  t.string     :name,        null: false
  t.integer    :n,           null: false  # 0-31  (decimal/base10)
  t.string     :kai,         null: false  # 1-x   (kai/base32)
  t.integer    :tier          # 1,2,3,4 - note: is nil (for x/31) for now (thus, optinal)
end

create_table :trait_types do |t|
  t.string     :name,   null: false  # use "pretty" name e.g. fur, highlight color, etc.
  t.string     :key,    null: false  # use key/ lowercase name from "official json api"
end

end # block Schema.define

end # method up

end # class CreateDb


text:
# encoding: utf-8

##
#  todo: rename file from kitty.rb to models.rb - why? why not?
#     keep all models together in a single file? - why? why not?

module Copycats
  module Model

class Kitty < ActiveRecord::Base
  ## self.table_name = 'kitties'

  has_many :genes

  belongs_to :sire,      class_name: 'Kitty', foreign_key: 'sire_id'
  belongs_to :matron,    class_name: 'Kitty', foreign_key: 'matron_id'

  ## convenience shortcuts for gene (d) traits
  belongs_to :body,      class_name: 'Trait', foreign_key: 'body_id'
  belongs_to :pattern,   class_name: 'Trait', foreign_key: 'pattern_id'
  belongs_to :coloreyes, class_name: 'Trait', foreign_key: 'coloreyes_id'
  belongs_to :eyes,      class_name: 'Trait', foreign_key: 'eyes_id'
  belongs_to :color1,    class_name: 'Trait', foreign_key: 'color1_id'
  belongs_to :color2,    class_name: 'Trait', foreign_key: 'color2_id'
  belongs_to :color3,    class_name: 'Trait', foreign_key: 'color3_id'
  belongs_to :wild,      class_name: 'Trait', foreign_key: 'wild_id'
  belongs_to :mouth,     class_name: 'Trait', foreign_key: 'mouth_id'

  ## todo: add more genes convenience shortcuts
  ## has_many :genes_body
  ## has_many :genes_pattern
end  # class Kitty

class TraitType <  ActiveRecord::Base
  ## self.table_name = 'trait_types'

  has_many :traits
end # class TraitType

class Trait <  ActiveRecord::Base
  ## self.table_name = 'traits'

  belongs_to :trait_types
end # class Trait

class Gene < ActiveRecord::Base
  ## self.table_name = 'genes'

  belongs_to :kitty
end # class Gene

  end # module Model
end # module Copycats


text:
# encoding: utf-8

class TraitsReport

ROMAN = {
  1 => 'I',
  2 => 'II',
  3 => 'III',
  4 => 'IIII',
  5 => 'V'
}

def build
  buf = ""
  buf << "# Traits\n\n"

  buf << "| Tier | Kai |"
  TRAITS.values[0,6].each do |trait|
    buf << " #{trait[:name]} |"
  end
  buf << "\n"
  buf << "|----|----|----|----|----|----|----|----|\n"

  buf << "|    |    |"
  TRAITS.values[0,6].each do |trait|
    buf << " #{trait[:genes]} |"
  end
  buf << "\n"

  Kai::ALPHABET.each_char do |kai|
    tier = ROMAN[ Gene::TIER[kai] ]
    tier = '?'  if tier.nil?
    buf << "| #{tier} | #{kai} |"
    TRAITS.values[0,6].each do |trait|
      value = trait[:kai][kai]
      value = '?'  if value.nil? || value.empty?
      buf << " #{value} |"
    end
    buf << "\n"
  end
  buf << "\n\n"

  ## part ii (split into two tables)
  buf << "| Tier | Kai |"
  TRAITS.values[6,6].each do |trait|
    buf << " #{trait[:name]} |"
  end
  buf << "\n"
  buf << "|----|----|----|----|----|----|----|----|\n"

  buf << "|    |    |"
  TRAITS.values[6,6].each do |trait|
    buf << " #{trait[:genes]} |"
  end
  buf << "\n"

  Kai::ALPHABET.each_char do |kai|
    tier = ROMAN[ Gene::TIER[kai] ]
    tier = '?'  if tier.nil?
    buf << "| #{tier} | #{kai} |"
    TRAITS.values[6,6].each do |trait|
      value = trait[:kai][kai]
      value = '?'  if value.nil? || value.empty?
      buf << " #{value} |"
    end
    buf << "\n"
  end
  buf << "\n\n"

  buf += <<TXT
## Mutations / Mewtations

16 Mutation Pairs (16 x 2 = 32)

```
Tier I    Tier II   Tier III  Tier IIII  Tier V
 (1-g)     (h-p)     (q-t)     (u,v)      (w)
1+2 = h   h+i = q   q+r = u   u+v = w
3+4 = i   j+k = r   s+t = v
5+6 = j   m+n = s
7+8 = k   o+p = t
9+a = m
b+c = n
d+e = o
f+g = p
```

Note: It's impossible for a mutation to reach `x` e.g. `w+x = x`.

## Kai (Base32) Notation

|Kai    |Binary |Num|Kai    |Binary |Num|Kai    |Binary |Num|Kai    |Binary |Num|
|-------|-------|---|-------|-------|---|-------|-------|---|-------|-------|---|
| **1** | 00000 | 0 | **9** | 01000 | 8 | **h** | 10000 |16 | **q** | 11000 |24 |
| **2** | 00001 | 1 | **a** | 01001 | 9 | **i** | 10001 |17 | **r** | 11001 |25 |
| **3** | 00010 | 2 | **b** | 01010 | 10| **j** | 10010 |18 | **s** | 11010 |26 |
| **4** | 00011 | 3 | **c** | 01011 | 11| **k** | 10011 |19 | **t** | 11011 |27 |
| **5** | 00100 | 4 | **d** | 01100 | 12| **m** | 10100 |20 | **u** | 11100 |28 |
| **6** | 00101 | 5 | **e** | 01101 | 13| **n** | 10101 |21 | **v** | 11101 |29 |
| **7** | 00110 | 6 | **f** | 01110 | 14| **o** | 10110 |22 | **w** | 11110 |30 |
| **8** | 00111 | 7 | **g** | 01111 | 15| **p** | 10111 |23 | **x** | 11111 |31 |

Note: The digit-0 and the letter-l are NOT used in kai.

TXT

  puts buf

  buf
end ## method build

  def save( path )
    File.open( path, "w" ) do |f|
      f.write build
    end
  end

end # class TraitsReport


text:
# encoding: utf-8

class MixReport

  def initialize( matron, sire )
    @matron_id   = matron.id
    @sire_id     = sire.id
    @matron_kai  = fmt_kai( matron.genes_kai )    ## pretty print in groups of four (4)
    @sire_kai    = fmt_kai( sire.genes_kai )
  end

  def build
    buf = "# Kitty \##{@matron_id} + \##{@sire_id}\n\n"
    buf << "genes (kai) 1: #{@matron_kai}\n"
    buf << "genes (kai) 2: #{@sire_kai}\n\n"

    buf << Genome.new( @matron_kai ).build_mix_tables( Genome.new( @sire_kai ))

    ## puts buf
    buf
  end

## fix/todo: use "generic" report class (inherits save)!!!!!
  def save( path )
    File.open( path, "w" ) do |f|
      f.write build
    end
  end

#####
#  helpers
#    todo: move to helpers module for (re)use
  def fmt_kai( kai )
    ## format in groups of four (4) separated by space
    ##  e.g.  ccac7787fa7fafaa16467755f9ee444467667366cccceede
    ##     :  ccac 7787 fa7f afaa 1646 7755 f9ee 4444 6766 7366 cccc eede
    kai.reverse.gsub( /(.{4})/, '\1 ').reverse.strip
  end

end  # class KittyReport


text:
# encoding: utf-8

class KittyReport

  def initialize( *args, **kwargs )
    if args.empty? # try keyword args
      @id   = kwargs[:id]
      @kai  = kwargs[:genes]   ## todo/check: also incl. check for kai key - why? why not?
    else
      # try positional args
      ##  for now alsways assume ActiveRecord - Kitty::Model
      kitty = args[0]
      @id   = kitty.id
      @kai  = fmt_kai( kitty.genes_kai )    ## pretty print in groups of four (4)
    end
  end

  def build
    buf = "# Kitty \##{@id}\n\n"
    buf << "genes (kai): #{@kai}\n\n"
    buf << Genome.new( @kai ).build_tables

    ## puts buf

    buf
  end

  def save( path )
    File.open( path, "w" ) do |f|
      f.write build
    end
  end

#####
#  helpers
  def fmt_kai( kai )
    ## format in groups of four (4) separated by space
    ##  e.g.  ccac7787fa7fafaa16467755f9ee444467667366cccceede
    ##     :  ccac 7787 fa7f afaa 1646 7755 f9ee 4444 6766 7366 cccc eede
    kai.reverse.gsub( /(.{4})/, '\1 ').reverse.strip
  end

end  # class KittyReport


text:
# encoding: utf-8

## load all *.file in data folder

def find_datafiles( root='.' )
  files = []
  ## todo/check: include all subfolders - why? why not?
  Dir.glob( root + '/**/*.csv' ).each do |file|
    files << file
  end
  files
end

def read_datafiles( data_dir: './data' )
  files = find_datafiles( data_dir )
  pp files

  ## todo: check if files found

  ## for speed - turn off logging for info/debug/etc. levels
  ActiveRecord::Base.logger.level = :warn

  ## pp TRAIT_IDS_CACHE

  ## add / read / load all datafiles
  files.each_with_index do |file,i|

    puts "== #{i+1}/#{files.size} reading datafile '#{file}'..."

    kitties_headers = CsvReader.header( file )
    pp kitties_headers

    ##  todo: fix - use field index not name!!!! - why? why not?
    ## check format
    if kitties_headers.include?( 'id' ) &&
       kitties_headers.include?( 'gen' ) &&
       kitties_headers.include?( 'matron_id' ) &&
       kitties_headers.include?( 'sire_id' ) &&
       kitties_headers.include?( 'birthdate' ) &&
       kitties_headers.include?( 'genes' ) &&
       kitties_headers.include?( 'name' )
       ## "standard" format
       ##   required headers include: id, gen, matron_id, sire_id, birthdate, genes, name

       ##  todo: fix - use field index not name!!!! - why? why not?
       headers = {
         'id'         => 'id',
         'gen'        => 'gen',
         'matron_id'  => 'matron_id',
         'sire_id'    => 'sire_id',
         'birthdate'  => 'birthdate',
         'genes'      => 'genes',
         'name'       => 'name'
       }
    elsif kitties_headers.include?( 'id' ) &&
          kitties_headers.include?( 'matron_id' ) &&
          kitties_headers.include?( 'sire_id' ) &&
          kitties_headers.include?( 'gen' ) &&
          kitties_headers.include?( 'birth_date' ) &&
          kitties_headers.include?( 'genes_kai' )
      ## "kittydex" format
      ##   see  https://cryptokittydex.com/resources
      ##    required headers include: id, matron_id, sire_id, gen, birth_date, genes_kai
       headers = {
         'id'         => 'id',
         'matron_id'  => 'matron_id',
         'sire_id'    => 'sire_id',
         'gen'        => 'gen',
         'birthdate'  => 'birth_date',
         'genes'      => 'genes_kai',
         'name'       => 'name'   ## note: will always be nil (is missing in kittydex)
       }
    else
      ## unknown format
      puts "!!! unknown datafile format; matching headers NOT found / missing"
      exit 1
    end

    ## start of kitties blockchain / genesis
    genesisdate = Date.new( 2017, 11, 23)   ## 2017-11-23

    ## note: for now use first 5 rows for testing
    ## kitties[0..4].each do |row|

    kitties = CsvHash.read( file )
    kitties.each do |row|
      ## puts row['id'] + '|' + row['gen'] + '|' + row['genes_kai']
      k = Copycats::Model::Kitty.new
      k.id        = row[headers['id']].to_i
      k.gen       = row[headers['gen']].to_i
      k.matron_id = row[headers['matron_id']].to_i   unless row[headers['matron_id']].blank? || row[headers['matron_id']] == '0'
      k.sire_id   = row[headers['sire_id']].to_i     unless row[headers['sire_id']].blank?   || row[headers['sire_id']] == '0'
      k.name      = row[headers['name']]             unless row[headers['name']].blank?

      ##  founder cats - first one hundret (1 to 100 - note: includes genesis (1))
      k.is_founder = true    if k.id >= 1 && k.id <= 100

      ## todo: pretty print (format genes !!!!)
      k.genes_kai = row[headers['genes']]   ### .gsub( ' ', '' )  ## remove all spaces - why? why not?

      ##  pp row['birthdate']
      birthdate = DateTime.strptime( row[headers['birthdate']], '%Y-%m-%d %H:%M:%S' )
      k.birthdate =  birthdate
      k.day_count = (birthdate.to_date.jd - genesisdate.jd)+1

      genome = Genome.new( k.genes_kai )
      genes =  genome.genes

      k.body_id      = TRAIT_IDS_CACHE[:body][:kai][genes[:body].d]
      k.pattern_id   = TRAIT_IDS_CACHE[:pattern][:kai][genes[:pattern].d]
      k.coloreyes_id = TRAIT_IDS_CACHE[:coloreyes][:kai][genes[:coloreyes].d]
      k.eyes_id      = TRAIT_IDS_CACHE[:eyes][:kai][genes[:eyes].d]
      k.color1_id    = TRAIT_IDS_CACHE[:color1][:kai][genes[:color1].d]
      k.color2_id    = TRAIT_IDS_CACHE[:color2][:kai][genes[:color2].d]
      k.color3_id    = TRAIT_IDS_CACHE[:color3][:kai][genes[:color3].d]
      k.wild_id      = TRAIT_IDS_CACHE[:wild][:kai][genes[:wild].d]
      k.mouth_id     = TRAIT_IDS_CACHE[:mouth][:kai][genes[:mouth].d]

      ## pp k

      ## print ids for progress report - why? why not?
      print "#{k.id}."
      k.save!

      ## add genes
      TRAITS.each_with_index do |(trait_key, trait_hash),i|
        gene    = genes[trait_key]
        next  if gene.nil?   ## skip future_1, future_2, etc. for now - add - why? why not?

        ##  puts "#{trait_hash[:name]} (Genes #{trait_hash[:genes]})\n\n"

        ## note: start counting for d.n with 1 (NOT 0)
        ##  use idx for zero-based counting - why? why not?

        d = Copycats::Model::Gene.new
        d.kitty_id = k.id
        d.n        = (i*4)     # gene number/pos  0-47  (start w/ 1 why? why not?)
        d.gene     = 'd'     # d (0), r1 (1), r2 (2), r3 (3)
        d.gene_n   = 0       # 0-3  (0=d,1=r1,2=r2,3=r3)
        d.trait_id = TRAIT_IDS_CACHE[trait_key][:kai][gene.d]
        d.save!

        r1 = Copycats::Model::Gene.new
        r1.kitty_id = k.id
        r1.n        = (i*4)+1     # gene number/pos  0-47  (start w/ 1 why? why not?)
        r1.gene     = 'r1'     # d (0), r1 (1), r2 (2), r3 (3)
        r1.gene_n   = 1       # 0-3  (0=d,1=r1,2=r2,3=r3)
        r1.trait_id = TRAIT_IDS_CACHE[trait_key][:kai][gene.r1]
        r1.save!

        r2 = Copycats::Model::Gene.new
        r2.kitty_id = k.id
        r2.n        = (i*4)+2   # gene number/pos  0-47  (start w/ 1 why? why not?)
        r2.gene     = 'r2'     # d (0), r1 (1), r2 (2), r3 (3)
        r2.gene_n   = 2       # 0-3  (0=d,1=r1,2=r2,3=r3)
        r2.trait_id = TRAIT_IDS_CACHE[trait_key][:kai][gene.r2]
        r2.save!

        r3 = Copycats::Model::Gene.new
        r3.kitty_id = k.id
        r3.n        = (i*4)+3     # gene number/pos  0-47  (start w/ 1 why? why not?)
        r3.gene     = 'r3'     # d (0), r1 (1), r2 (2), r3 (3)
        r3.gene_n   = 3       # 0-3  (0=d,1=r1,2=r2,3=r3)
        r3.trait_id = TRAIT_IDS_CACHE[trait_key][:kai][gene.r3]
        r3.save!
      end

    end
    print "\n"
  end
end


text:
# encoding: utf-8

def connect( config={} )
  if config.empty?
    puts "ENV['DATBASE_URL'] - >#{ENV['DATABASE_URL']}<"

    ### change default to ./copycats.db ?? why? why not?
    db = URI.parse( ENV['DATABASE_URL'] || 'sqlite3:///kitties.db' )

    if db.scheme == 'postgres'
        config = {
          adapter: 'postgresql',
          host: db.host,
          port: db.port,
          username: db.user,
          password: db.password,
          database: db.path[1..-1],
          encoding: 'utf8'
        }
    else # assume sqlite3
       config = {
         adapter: db.scheme, # sqlite3
         database: db.path[1..-1] # world.db (NB: cut off leading /, thus 1..-1)
       }
    end
  end

  puts "Connecting to db using settings: "
  pp config
  ActiveRecord::Base.establish_connection( config )
  ActiveRecord::Base.logger = Logger.new( STDOUT )
  ## ActiveRecord::Base.colorize_logging = false  - no longer exists - check new api/config setting?

  ## if sqlite3 add (use) some pragmas for speedups
  if config[:adapter] == 'sqlite3'
      ## check/todo: if in memory e.g. ':memory:' no pragma needed!!
      con = ActiveRecord::Base.connection
      con.execute( 'PRAGMA synchronous=OFF;' )
      con.execute( 'PRAGMA journal_mode=OFF;' )
      con.execute( 'PRAGMA temp_store=MEMORY;' )
  end
end  ## method connect

def setup_db
  ## build schema
  CreateDb.new.up
end

### todo/fix:
##  make trait_ids_cache more (re)usable - fix global!!!!
TRAIT_IDS_CACHE = {}

def setup_traits
  ## for speed - turn off logging for info/debug/etc. levels
  ActiveRecord::Base.logger.level = :warn

  ### add traits
  TRAITS.each do |trait_key, trait_hash|

    trait_t = Copycats::Model::TraitType.new
    trait_t.name = trait_hash[:name]
    trait_t.key  = trait_key.to_s
    trait_t.save!

    cache = {}

    Kai::ALPHABET.each_with_index do |kai,n|
      name = trait_hash[:kai][kai]
      name = '?'  if name.nil? || name.empty?

      tier = Gene::TIER[kai]

      puts "Kai: #{kai} (#{n}) /#{tier}, Cattribute: #{name}"
      trait  = Copycats::Model::Trait.new
      trait.name          = name
      trait.kai           = kai
      trait.n             = n
      trait.tier          = tier
      trait.trait_type_id = trait_t.id
      trait.save!

      cache[ kai ] = trait.id
    end

    TRAIT_IDS_CACHE[ trait_key ] = { id:  trait_t.id,
                                     kai: cache }
  end
end


text:
## $:.unshift(File.dirname(__FILE__))

## minitest setup

require 'minitest/autorun'

## our own code

require 'copycats'


text:
# encoding: utf-8

###
#  to run use
#     ruby -I ./lib -I ./test test/test_models.rb

require 'helper'

class TestModels < MiniTest::Test

def test_create
  ## connect to local kitties.db database
  connect( adapter:  'sqlite3',
           database: ':memory:' )

  setup_db
  setup_traits  # note: also builds TRAIT_IDS_CACHE for read_datafiles for now

  ## print some (record) stats
  kitty_count = Copycats::Model::Kitty.count
  gene_count  = Copycats::Model::Gene.count
  trait_count = Copycats::Model::Trait.count

  puts "kitties: #{kitty_count}"
  puts "genes:   #{gene_count}"
  puts "traits:  #{trait_count}"

  assert_equal   0, kitty_count
  assert_equal   0, gene_count
  assert_equal 384, trait_count
end

end  # class TestModels


text:
# encoding: utf-8

###
#  to run use
#     ruby -I ./lib -I ./test test/test_genome.rb

require 'helper'

class TestGenome < MiniTest::Test

def test_kitty_100000
  ## see https://cryptokittydex.com/kitties/100000
  ##     https://www.cryptokitties.co/kitty/100000

  kai = "dadc 557j 4aaa gb9g 1161 383k 7dbe 774b 6667 8438 dd9a cbbd"

  genome = Genome.new( kai )
  pp genome

  puts "body:      #{genome.body}"
  puts "coloreyes: #{genome.coloreyes}"
  puts "eyes:      #{genome.eyes}"
  puts "pattern:   #{genome.pattern}"
  puts "mouth:     #{genome.mouth}"
  puts "color1:    #{genome.color1}"
  puts "color2:    #{genome.color2}"
  puts "color3:    #{genome.color3}"

  assert_equal "munchkin",    genome.body
  assert_equal "strawberry",  genome.coloreyes
  assert_equal "crazy",       genome.eyes
  assert_equal "luckystripe", genome.pattern
  assert_equal "soserious",   genome.mouth
  assert_equal "greymatter",  genome.color1
  assert_equal "lemonade",    genome.color2
  assert_equal "bloodred",    genome.color3

  puts genome.genes[:color1].p
  puts genome.genes[:color1].h1
  puts genome.genes[:color1].h2
  puts genome.genes[:color1].h3

  assert_equal genome.genes[:color1].p,  genome.genes[:color1].d
  assert_equal genome.genes[:color1].h1, genome.genes[:color1].r1
  assert_equal genome.genes[:color1].h2, genome.genes[:color1].r2
  assert_equal genome.genes[:color1].h3, genome.genes[:color1].r3

  puts genome.genes[:eyes].p
  puts genome.genes[:eyes].h1
  puts genome.genes[:eyes].h2
  puts genome.genes[:eyes].h3

  puts TRAITS[:eyes][ genome.genes[:eyes].p ]
  puts TRAITS[:eyes][ genome.genes[:eyes].h1 ]
  puts TRAITS[:eyes][ genome.genes[:eyes].h2 ]
  puts TRAITS[:eyes][ genome.genes[:eyes].h3 ]
end

def test_kitty_99895
  ## see https://cryptokittydex.com/kitties/99895
  ##     https://www.cryptokitties.co/kitty/99895

  kai = "cdad 55r6 7f2f g9bg 1111 3757 d99d 272h 8k78 k884 9da9 fdee"

  genome = Genome.new( kai )
  pp genome

  puts "body:      #{genome.body}"
  puts "coloreyes: #{genome.coloreyes}"
  puts "eyes:      #{genome.eyes}"
  puts "pattern:   #{genome.pattern}"
  puts "mouth:     #{genome.mouth}"
  puts "color1:    #{genome.color1}"
  puts "color2:    #{genome.color2}"
  puts "color3:    #{genome.color3}"

  assert_equal "sphynx",      genome.body
  assert_equal "mintgreen",   genome.coloreyes
  assert_equal "thicccbrowz", genome.eyes
  assert_equal "calicool",    genome.pattern
  assert_equal "soserious",   genome.mouth
  assert_equal "cloudwhite",  genome.color1
  assert_equal "coffee",      genome.color2
  assert_equal "kittencream", genome.color3
end

end  # class TestGenome


text:
# encoding: utf-8

###
#  to run use
#     ruby -I ./lib -I ./test test/test_version.rb

require 'helper'

class TestVersion < MiniTest::Test

def test_version
   pp Copycats.version
   pp Copycats.banner
   pp Copycats.root

   assert true  ## (for now) everything ok if we get here
end

end  # class TestVersion


text:
# encoding: utf-8

###
#  to run use
#     ruby -I ./lib -I ./test test/test_traits.rb

require 'helper'

class TestTraits < MiniTest::Test

def test_traits
    pp TRAITS
    assert true  ## (for now) everything ok if we get here

    ## todo: assert number of traits  use traits.keys.size !!!
end

def test_fancies
    pp FANCIES
    assert true  ## (for now) everything ok if we get here

    ## todo: assert number of fancies  use fancies.keys.size !!!
end

def test_kai

  ## Kitty #1001
  ##  see https://cryptokittydex.com/kitties/1001

   kai    = "aaaa 7885 22f2 agff 1661 7755 e979 2441 6667 7664 a9aa cfff".gsub( ' ', '' )

   puts "kai.length: #{kai.length}"  ## 48
   puts "  first: #{kai[0]}"
   puts "  last:  #{kai[-1]}"
   puts "  last:  #{kai[47]}"

   puts kai.reverse
   puts kai.reverse[0,4]
   puts kai.reverse[4,4]
   puts kai.reverse[8,4]
   eyes = kai.reverse[12,4]
   puts "eyes:"
   puts eyes

   puts  TRAITS[:eyes]
   pp    eyes[0]
   puts  TRAITS[:eyes][eyes[0]]
   puts  TRAITS[:eyes][eyes[1]]
   puts  TRAITS[:eyes][eyes[2]]
   puts  TRAITS[:eyes][eyes[3]]

   color1 = kai.reverse[16,4]
   puts "color1:"
   puts color1
   pp    color1[0]
   puts  TRAITS[:color1][color1[0]]
   puts  TRAITS[:color1][color1[1]]
   puts  TRAITS[:color1][color1[2]]
   puts  TRAITS[:color1][color1[3]]

   assert true
end

end  # class TestTraits


text:
# encoding: utf-8

###
#  to run use
#     ruby -I ./lib -I ./test test/test_mixgenes.rb

## note:
##  rand(100) - random number between 0 and 100 inclusively
##    todo/check: use rand(99) - why? why not?  e.g. 0 to 99 inclusive (100 values)

require 'helper'

class TestMixGenes < MiniTest::Test

def setup
  Lottery.random = Random.new( 1234 )   ## seed == 1234
end

def mixgenes( matron, sire )
  matron.mix( sire )
end

def test_random
  assert_equal 47, Lottery.rand(100)
  assert_equal 83, Lottery.rand(100)
  assert_equal 38, Lottery.rand(100)
  assert_equal 53, Lottery.rand(100)
end

def test_baby

  kai1 = "dadc 557j 4aaa gb9g 1161 383k 7dbe 774b 6667 8438 dd9a cbbd"  # kitty 100000
  kai2 = "cdad 55r6 7f2f g9bg 1111 3757 d99d 272h 8k78 k884 9da9 fdee"  # kitty 99895

  matron = Genome.new( kai1 )
  sire   = Genome.new( kai2 )
  baby = mixgenes( matron, sire )
  pp baby

  puts "body:      #{baby.body}"
  puts "coloreyes: #{baby.coloreyes}"
  puts "eyes:      #{baby.eyes}"
  puts "pattern:   #{baby.pattern}"
  puts "mouth:     #{baby.mouth}"
  puts "color1:    #{baby.color1}"
  puts "color2:    #{baby.color2}"
  puts "color3:    #{baby.color3}"

  assert_equal "laperm",     baby.body
  assert_equal "strawberry", baby.coloreyes
  assert_equal "simple",     baby.eyes
  assert_equal "calicool",   baby.pattern
  assert_equal "soserious",  baby.mouth
  assert_equal "salmon",     baby.color1
  assert_equal "coffee",     baby.color2
  assert_equal "bloodred",   baby.color3
end

end  # class TestMixGenes


text:
require 'hoe'
require './lib/bitcat/version.rb'

Hoe.spec 'bitcat' do

  self.version = Bitcat::VERSION

  self.summary = "bitcat - bit cat(alog) browser - browse your (digital) bit(s) collections; browse your (crypto) kitties and more"
  self.description = summary

  self.urls    = ['https://github.com/openblockchains/bitcat']

  self.author  = 'Gerald Bauer'
  self.email   = 'wwwmake@googlegroups.com'

  # switch extension to .markdown for gihub formatting
  self.readme_file  = 'README.md'
  self.history_file = 'HISTORY.md'

  self.extra_deps = [
    ['sinatra', '>=2.0'],
    ['sass'],   ## used for css style preprocessing (scss)
    ['copycats', '>=0.6.1'],
  ]

  self.licenses = ['Public Domain']

  self.spec_extras = {
    required_ruby_version: '>= 2.3'
  }

end


text:
HISTORY.md
LICENSE.md
Manifest.txt
README.md
Rakefile
bin/bitcat
lib/bitcat.rb
lib/bitcat/app.rb
lib/bitcat/public/style.css
lib/bitcat/version.rb
lib/bitcat/views/index.erb
lib/bitcat/views/kitty.erb
lib/bitcat/views/layout.erb


text:
### 0.0.1 / 2018-03-24

* Everything is new. First release


text:
# bitcat - Bit Catalog Kitty Browser

browse your (digital) bit(s) collections -
browse your (crypto) kitties and more

* home  :: [github.com/openblockchains/bitcat](https://github.com/openblockchains/bitcat)
* bugs  :: [github.com/openblockchains/bitcat/issues](https://github.com/openblockchains/bitcat/issues)
* gem   :: [rubygems.org/gems/bitcat](https://rubygems.org/gems/bitcat)
* rdoc  :: [rubydoc.info/gems/bitcat](http://rubydoc.info/gems/bitcat)

## Usage

### Step 0 - Database Setup

Note: The bitcat server machinery requires a (local) single-file SQLite database,
that is, `kitties.db`, in your working folder.

See the [Copycats page on how to setup a (local) single-file SQLite database »](https://github.com/openblockchains/copycats#database-setup)

### Step 1 - Start the Bitcat Server

Start up the bitcat server on the command line:

    $ bitcat

### Step 3 - Open Bitcat Page in Browser

Open up the browser at `localhost:4567`.

![](bitcat.png)

That's it. Enjoy.

## Installation - I Can Has Bitcat?

Use:

```
$ gem install bitcat
```

## Questions? Comments?

Post them on the [cryptokitties reddit](https://www.reddit.com/r/cryptokitties). Thanks.

## License

![](https://publicdomainworks.github.io/buttons/zero88x31.png)

The `bitcat` scripts are dedicated to the public domain.
Use it as you please with no restrictions whatsoever.


text:
#!/usr/bin/env ruby

###################
# DEV TIPS:
#
# For local testing run like:
#
#    ruby -I./lib bin/bitcat
#
# Set the executable bit in Linux. Example:
#
#    % chmod a+x bin/bitcat
#

require 'bitcat'

Bitcat.main


text:
# encoding: utf-8

require 'pp'

## 3rd party
require 'copycats'
require 'sinatra/base'   ## note: use "modular" class sinatra style

## our own code
require 'bitcat/version'    # note: let version always go first
require 'bitcat/app'

module Bitcat

   ## convenience method
   def self.run!
     app    = App
     port   = 4567
     Rack::Handler::WEBrick.run( app, Port:port ) do |server|
       ## todo: add traps here - why, why not??
     end
   end

   def self.main
     p banner
     ##  p root   #=> e.g. "C:/Sites/openblockchains/bitcat"

     database = './kitties.db'

     unless File.exist?( database )
       puts "*** error - no SQLite database found - expected >#{database}<"
       puts
       puts "  note: for how to setup a database,"
       puts "        see <https://github.com/openblockchains/copycats#database-setup>"
       puts
       exit 1
     end

     ## connect to local kitties.db database
     connect( adapter:  'sqlite3',
              database:  database )

     ## print some (record) stats
     puts "kitties: #{Copycats::Model::Kitty.count}"
     puts "genes:   #{Copycats::Model::Gene.count}"
     puts "traits:  #{Copycats::Model::Trait.count}"

     run!
   end
end # module Bitcat


text:
# encoding: utf-8

module Bitcat

class App < Sinatra::Base

  set :root, "#{Bitcat.root}/lib/bitcat"

  get '/' do
    erb :index
  end

  post '/' do
    puts "post '/'"
    pp params

    id = params['kitty']['id']
    if id.blank?
      ## id required - todo: add flash message
      redirect "/"
    else
      redirect "/kitty/#{id}"
    end
  end

  get '/kitty/:id' do
    puts "get '/kitty/:id'"
    pp params

    kitty = Copycats::Model::Kitty.find( params['id'] )
    pp kitty

    erb :kitty, :locals => { kitty: kitty }
  end

## helpers
  def h(text)
    Rack::Utils.escape_html(text)
  end

end  # class App
end # module Bitcat


text:
# encoding: utf-8

module Bitcat

  MAJOR = 0
  MINOR = 1
  PATCH = 0
  VERSION = [MAJOR,MINOR,PATCH].join('.')

  def self.version
    VERSION
  end

  def self.banner
    "bitcat/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
  end

  def self.root
    "#{File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) )}"
  end

end # module Bitcat


text:
body {
  font-family: arial,sans-serif;
  color: #222;
}


text:
« Index

Kitty #<%= kitty.id %>

Generation: <%= kitty.gen %>

Birthdate:  <%= kitty.birthdate %>  (<%= Date.today.jd - kitty.birthdate.to_date.jd %> days ago)

Day count:  <%= kitty.day_count %>

Genes (Kai): <%= kitty.genes_kai %>

<% kitty.genes.each do |gene|
      trait      = Copycats::Model::Trait.find( gene.trait_id )
      trait_type = Copycats::Model::TraitType.find( trait.trait_type_id )
   %>

   <%= gene.n %> --
   <%= gene.gene %> -
   <%= trait.name %>
   (<%= trait.kai %>) -
   <%= trait_type.name %>

<% end %>

More Profile Pages:

CryptoKitty - <a href="https://www.cryptokitties.co/kitty/<%= kitty.id %>">#<%= kitty.id %>
      (<a href="https://api.cryptokitties.co/kitties/<%= kitty.id %>">API)

CryptoKittydex - <a href="https://cryptokittydex.com/kitties/<%= kitty.id %>">#<%= kitty.id %>

KittyCalc - <a href="https://kittycalc.co/read/?k1=<%= kitty.id %>&k2=461679">#<%= kitty.id %>


text:
<!DOCTYPE html>
<html>
<head>
  <title>Bitcat - Bit Catalog Kitty Browser</title>
  <link href='/style.css' rel='stylesheet'>
</head>
<body>

<%= yield %>

</body>
</html>


text:
Bitcat - Bit Catalog Kitty Browser

  <form action='/' method='post'>
    <input type='text' name='kitty[id]' placeholder='1,2,3,...'>
    <input type='submit' value='Show Kitty'>
  </form>

Try
  <% Copycats::Model::Kitty.limit(10).each do |kitty| %>
     <a href="kitty/<%= kitty.id %>">#<%= kitty.id %>
  <% end %>