diff options
Diffstat (limited to 'src')
34 files changed, 0 insertions, 3863 deletions
diff --git a/src/genome/README.md b/src/genome/README.md deleted file mode 100644 index 1135ed0..0000000 --- a/src/genome/README.md +++ /dev/null @@ -1,12 +0,0 @@ -src/genome
-==========
-
-Genomes represent the neural network that underlies a creature. They are an array of tuples of the form `[ source, sink, weight ]`, where:
-
- * `source` is an integer in the range `[0, N - num_outputs)`, representing the index of the source neuron
- * `sink` is an integer in the range `[num_inputs, N)`, representing the index of the sink neuron
- * `weight` is a floating-point value in the range `[-4, 4]`, representing the weight of the connection
-
-`num_input` and `num_output` are fixed by the environment (as they are the input senses and output actions of the creature, respectively).
-`N` is not fixed, but is instead determined by the maximum index present in the genome. As long as the maximum index is greater than or
-equal to `num_input + num_output`, the genome is considered valid.
diff --git a/src/genome/genome.js b/src/genome/genome.js deleted file mode 100644 index 20974fc..0000000 --- a/src/genome/genome.js +++ /dev/null @@ -1,218 +0,0 @@ -'use strict';
-
-import { random_choice } from '../util.js';
-import { network } from '../mind/topology.js';
-
-
-// check if a given genome is valid and compute its size
-export function validate_genome(genome) {
- const { n_input, n_internal, n_output } = genome;
- console.log(n_input + n_internal);
- return genome.genes.reduce(
- (acc, [source, sink, weight]) => acc && (
- (source < n_input+n_internal) &&
- (sink >= n_input)
- ),
- true
- );
-}
-
-// parse a genome into a useable neural net
-export function parse_genome(genome) {
- const { n_input, n_internal, n_output } = genome;
-
- const n = genome.genes.reduce(
- (acc, [source, sink, weight]) => acc.connect(source, sink, weight),
- network(n_input, n_internal, n_output)
- );
-
- return n;
-}
-
-
-// --===== mutations =====--
-
-function clamp(value, min, max) {
- if (value > max) { return max; }
- if (value < min) { return min; }
- return value;
-}
-
-// adjust the source input of a gene
-export function mut_gene_source(n_input, n_internal, n_output, gene, r) {
- const [source, sink, weight] = gene;
-
- const new_source = r < 0.5 ? source-1 : source+1;
-
- return [
- clamp(new_source, 0, n_input+n_internal-1),
- sink,
- weight,
- ];
-}
-
-
-// adjust the sink target of a gene
-export function mut_gene_sink(n_input, n_internal, n_output, gene, r) {
- const [source, sink, weight] = gene;
-
- const new_sink = r < 0.5 ? sink-1 : sink+1;
-
- return [
- source,
- clamp(new_sink, n_input+n_internal, n_input+n_internal+n_output-1),
- weight,
- ];
-}
-
-
-// modify a gene's weight
-// only adjusts the weight by performing a weighted average, so as to
-// more gently modify the generated net
-export function mut_gene_weight(weight_max, gene, r) {
- const [source, sink, weight] = gene;
-
- const rr = (2*r)-1;
- const move = weight_max * rr;
- const new_weight = (2*weight + move)/3;
-
- return [
- source,
- sink,
- clamp(new_weight, -weight_max, weight_max),
- ];
-}
-
-
-
-// expand the size of the neural net encoded by the genome
-// relabels internal indices so that there is one extra internal neuron
-export function mut_genome_expand(genome, r) {
- const expand_index = Math.floor(genome.n_internal * r) + genome.n_input;
- const new_genes = genome.genes.map(([source, sink, weight]) => [
- source >= expand_index ? source+1 : source,
- sink >= expand_index ? sink+1 : sink,
- weight,
- ]);
-
- return {
- ...genome,
- n_internal: genome.n_internal+1,
- genes: new_genes,
- };
-}
-
-
-// contract the size of the neural net encoded by the genome
-// relabels internal indices so that there is one less internal neuron
-export function mut_genome_contract(genome, r) {
- const { n_input, n_internal, n_output } = genome;
- const contract_idx = Math.floor(n_internal * r) + n_input;
-
- // decrement sources on the contract index too, to prevent invalid genomes
- const new_source = (source) => source >= contract_idx ? source-1 : source;
- // decrement sinks only after the contract index
- const new_sink = (sink) => sink > contract_idx ? sink-1 : sink;
-
- const new_genes = genome.genes.map(([source, sink, weight]) => [
- new_source(source),
- new_sink(sink),
- weight,
- ]);
-
- return {
- ...genome,
- n_internal: n_internal-1,
- genes: new_genes,
- };
-}
-
-
-// append a newly generated gene to the end of the genome
-export function mut_genome_insert(genome, weight_max, r1=Math.random(), r2=Math.random(), r3=Math.random()) {
- const { n_input, n_internal, n_output } = genome;
- const source = Math.floor((n_input + n_internal) * r1);
- const sink = Math.floor((n_internal + n_output) * r2) + n_input;
- const weight = weight_max * ((2*r3)-1);
-
- return {
- ...genome,
- genes: [...genome.genes, [source, sink, weight]],
- };
-}
-
-
-// delete a gene from the genome
-export function mut_genome_delete(genome, r) {
- const del_idx = Math.floor(r * genome.genes.length);
- const genes = genome.genes.filter((_, idx) => idx != del_idx);
- return { ...genome, genes };
-}
-
-
-function mut_gene(
- [n_input, n_internal, n_output, genome],
- weight_max, r1, r2, r3
-) {
- const gene_idx = Math.floor(genome.length * r1);
- const mod = random_choice(['source', 'sink', 'weight'], r2);
- let new_gene;
- if (mod == 'source') {
- new_gene = mut_gene_source(
- n_input, n_internal, n_output,
- genome[gene_idx],
- r3
- );
- } else if (mod == 'sink') {
- new_gene = mut_gene_sink(
- n_input, n_internal, n_output,
- genome[gene_idx],
- r3
- );
- } else {
- new_gene = mut_gene_weight(
- weight_max, genome[gene_idx], r3
- );
- }
-
- const new_genome = genome.map((gene, idx) => {
- if (idx == gene_idx) { return new_gene; }
- return gene;
- });
-
- return [
- n_input, n_internal, n_output, new_genome
- ];
-}
-
-
-export function mutate_genome(obj, weight_max) {
- const mut = random_choice([
- 'gene', 'gene', 'gene',
- 'gene', 'gene', 'gene',
- 'gene', 'gene', 'gene',
- 'insert', 'delete',
- 'insert', 'delete',
- 'expand', 'contract',
- ], Math.random());
-
- if (mut == 'gene') {
- return mut_gene(
- obj, weight_max,
- Math.random(), Math.random(), Math.random()
- );
- } else if (mut == 'insert') {
- return mut_genome_insert(
- obj, weight_max,
- Math.random(), Math.random(), Math.random()
- );
- } else if (mut == 'delete') {
- return mut_genome_delete(obj, Math.random());
- } else if (mut == 'expand') {
- return mut_genome_expand(obj, Math.random());
- } else if (mut == 'contract') {
- return mut_genome_contract(obj, Math.random());
- } else {
- throw new Error(`bad mut value: ${mut}`);
- }
-}
diff --git a/src/genome/genome.test.js b/src/genome/genome.test.js deleted file mode 100644 index bc64e4e..0000000 --- a/src/genome/genome.test.js +++ /dev/null @@ -1,282 +0,0 @@ -'use strict';
-
-// genome structure
-// {
-// genes: gene[]
-// n_input, n_internal, n_output
-// }
-
-
-import {
- validate_genome,
- parse_genome,
- mut_gene_source,
- mut_gene_sink,
- mut_gene_weight,
- mut_genome_expand,
- mut_genome_contract,
- mut_genome_insert,
- mut_genome_delete,
-} from './genome';
-
-
-test('genome validation', () => {
- expect(validate_genome({
- n_input: 0, n_internal: 1, n_output: 0,
- genes: [[0, 0, 1.0]],
- })).toBe(true);
- expect(validate_genome({
- n_input: 2, n_internal: 0, n_output: 1,
- genes: [[0, 2, 1]],
- })).toBe(true);
- expect(validate_genome({
- n_input: 2, n_internal: 0, n_output: 1,
- genes: [[2, 0, 1]],
- })).toBe(false);
- expect(validate_genome({
- n_input: 2, n_internal: 0, n_output: 1,
- genes: [[2, 2, 1]],
- })).toBe(false);
- expect(validate_genome({
- n_input: 2, n_internal: 1, n_output: 1,
- genes: [[3, 2, 1]],
- })).toBe(false);
-});
-
-
-test('parse a genome into a neural net', () => {
- const n = parse_genome({
- n_input: 1, n_internal: 1, n_output: 1,
- genes: [
- [0, 1, 1],
- [1, 1, 1],
- [1, 2, 1]
- ]
- });
-
- expect(n.input_count).toBe(1);
- expect(n.output_count).toBe(1);
- expect(n.compute([2], [-1])).toEqual([
- [ Math.tanh( Math.tanh( 2-1 ) ) ],
- [ Math.tanh( 2-1 ) ],
- ]);
-});
-
-
-test('mutate gene source', () => {
- const n_input = 3;
- const n_internal = 4;
- const n_output = 5;
-
- expect(mut_gene_source(
- n_input, n_internal, n_output,
- [0, 4, 0],
- 0.0
- )).toEqual([0, 4, 0]);
-
- expect(mut_gene_source(
- n_input, n_internal, n_output,
- [0, 4, 0],
- 1.0
- )).toEqual([1, 4, 0]);
-
- expect(mut_gene_source(
- n_input, n_internal, n_output,
- [6, 4, 0],
- 0.0
- )).toEqual([5, 4, 0]);
-
- expect(mut_gene_source(
- n_input, n_internal, n_output,
- [6, 4, 0],
- 1.0
- )).toEqual([6, 4, 0]);
-});
-
-
-test('mutate gene sink', () => {
- const n_input = 3;
- const n_internal = 4;
- const n_output = 5;
-
- expect(mut_gene_sink(
- n_input, n_internal, n_output,
- [0, 7, 0],
- 0.0
- )).toEqual([0, 7, 0]);
-
- expect(mut_gene_sink(
- n_input, n_internal, n_output,
- [0, 7, 0],
- 1.0
- )).toEqual([0, 8, 0]);
-
- expect(mut_gene_sink(
- n_input, n_internal, n_output,
- [6, 11, 0],
- 0.0
- )).toEqual([6, 10, 0]);
-
- expect(mut_gene_sink(
- n_input, n_internal, n_output,
- [6, 11, 0],
- 1.0
- )).toEqual([6, 11, 0]);
-});
-
-
-test('mutate gene weight', () => {
- const weight_max = 4.0;
-
- expect(mut_gene_weight(
- weight_max, [0, 0, 1], 0.0
- )).toEqual([0, 0, (2 - 4)/3]);
-
- expect(mut_gene_weight(
- weight_max, [0, 0, -4], 1.0
- )).toEqual([0, 0, (-8 + 4)/3]);
-
- expect(mut_gene_weight(
- weight_max, [0, 0, 3], 0.5
- )).toEqual([0, 0, (6+0)/3]);
-});
-
-
-test('expand genome', () => {
- const n_input = 1;
- const n_internal = 3;
- const n_output = 1;
-
- const genome = {
- n_input, n_internal, n_output,
- genes: [
- [0, 1, 0],
- [1, 2, 0],
- [2, 3, 0],
- [3, 4, 0],
- ],
- };
-
- expect(mut_genome_expand(genome, 0.0)).toEqual({
- n_input, n_internal: n_internal+1, n_output,
- genes: [
- [0, 2, 0],
- [2, 3, 0],
- [3, 4, 0],
- [4, 5, 0],
- ],
- });
-
- expect(mut_genome_expand(genome, 0.5)).toEqual({
- n_input, n_internal: n_internal+1, n_output,
- genes: [
- [0, 1, 0],
- [1, 3, 0],
- [3, 4, 0],
- [4, 5, 0],
- ],
- });
-
- expect(mut_genome_expand(genome, 0.99)).toEqual({
- n_input, n_internal: n_internal+1, n_output,
- genes: [
- [0, 1, 0],
- [1, 2, 0],
- [2, 4, 0],
- [4, 5, 0],
- ],
- });
-});
-
-
-test('contract genome', () => {
- const n_input = 1;
- const n_internal = 3;
- const n_output = 1;
-
- const genome = {
- n_input, n_internal, n_output,
- genes: [
- [0, 1, 0],
- [1, 2, 1],
- [2, 3, 2],
- [3, 4, 3],
- ],
- };
-
- expect(mut_genome_contract(genome, 0.0)).toEqual({
- n_input, n_internal: n_internal-1, n_output,
- genes: [
- [0, 1, 0],
- [0, 1, 1],
- [1, 2, 2],
- [2, 3, 3],
- ],
- });
-
- expect(mut_genome_contract(genome, 0.5)).toEqual({
- n_input, n_internal: n_internal-1, n_output,
- genes: [
- [0, 1, 0],
- [1, 2, 1],
- [1, 2, 2],
- [2, 3, 3],
- ],
- });
-
- expect(mut_genome_contract(genome, 0.99)).toEqual({
- n_input, n_internal: n_internal-1, n_output,
- genes: [
- [0, 1, 0],
- [1, 2, 1],
- [2, 3, 2],
- [2, 3, 3],
- ],
- });
-});
-
-
-
-test('insert new genes', () => {
- const n_input = 1;
- const n_internal = 2;
- const n_output = 1;
- const weight_max = 4;
-
- expect(mut_genome_insert({
- n_input, n_internal, n_output,
- genes: []
- }, weight_max, 0, 0.5, 0)).toEqual({
- n_input, n_internal, n_output,
- genes: [[0, 2, -4]]
- });
-
- expect(mut_genome_insert({
- n_input, n_internal, n_output,
- genes: [[0, 2, -4]]
- }, weight_max, 0.99, 0, 1)).toEqual({
- n_input, n_internal, n_output,
- genes: [[0, 2, -4], [2, 1, 4]]
- });
-});
-
-
-test('remove genes', () => {
- const n_input = 0;
- const n_output = 0;
- const n_internal = 3;
- const genome = {
- n_input, n_internal, n_output,
- genes: [[0, 1, 0], [1, 2, 0]],
- };
-
- expect(mut_genome_delete(genome, 0.0)).toEqual({
- n_input, n_internal, n_output,
- genes: [[1, 2, 0]],
- });
-
- expect(mut_genome_delete(genome, 0.99)).toEqual({
- n_input, n_internal, n_output,
- genes: [[0, 1, 0]],
- });
-});
diff --git a/src/genome/trial.js b/src/genome/trial.js deleted file mode 100644 index 2ce23bf..0000000 --- a/src/genome/trial.js +++ /dev/null @@ -1,37 +0,0 @@ -import { - get_size, - mut_genome_insert, mutate_genome, -} from './genome.js'; - -const recurse = (f, x0, n) => { - if (n == 0) { - return x0; - } else { - return f(recurse(f, x0, n-1)); - } -}; - - -const n_input = 5; -const n_output = 5; - - -const [_1, _2, _3, genome] = recurse( - s => mut_genome_insert( - s, 4, - Math.random(), Math.random(), Math.random() - ), - [n_input, 10, n_output, []], - 20); - - -const n_internal = get_size(n_input, n_output, genome) - n_input - n_output; -console.log([n_input, n_internal, n_output, genome]); - -const mutation = recurse( - s => mutate_genome(s, 4), - [n_input, n_internal, n_output, genome], - 40 -); - -console.log(mutation); diff --git a/src/index.html b/src/index.html deleted file mode 100644 index 6b7ebf7..0000000 --- a/src/index.html +++ /dev/null @@ -1,14 +0,0 @@ -<!doctype html> -<html> - <head> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width, initial-scale=1"> - <title>nerine</title> - <style> - </style> - <script type="module" src="ui/index.js"></script> - </head> - <body> - <canvas id="canvas" width="95vw" height="95vw"> - </body> -</html> diff --git a/src/mind/README.md b/src/mind/README.md deleted file mode 100644 index 1ece125..0000000 --- a/src/mind/README.md +++ /dev/null @@ -1,37 +0,0 @@ -mind -==== - -This module is used to create arbitrary stateful neural networks. - -The only export is the following function: - -``` -network(input_count, internal_count, output_count, weight_max = 4 : number) -``` - -This function returns an object that represents a neural network with `input_count` input neurons, -`internal_count` internal (and stateful) neurons, and `output_count` output neurons. `max_weight` determines -the maximum absolute value allowed for connection weights. - -A network object has two methods: - -``` -connect(source, sink, weight : number) -``` - -This method returns a new network object that is an exact copy of the original, except with a new -connection between the neuron indexed by `source` and `sink`, with weight `weight`. Neuron indices -are zero-indexed, and span all neurons, first the inputs, then the internal neurons, and finally the outputs. -An error will be thrown if `source` is in the range of output neurons or if `sink` is in the range of input -neurons. - - -``` -compute(inputs, state : array[number]) -``` - -This method returns a tuple `[output, newState]`, where `output` is an array of `output_count` values -corresponding to the output neuron's computed values, and `newState` is the new state of the internal neurons. - -`input` must be an array of numbers with length equal to `input_count`, and `state` must be an array of numbers -with length equal to `internal_count` or an error will be raised. diff --git a/src/mind/topology.js b/src/mind/topology.js deleted file mode 100644 index 946dd86..0000000 --- a/src/mind/topology.js +++ /dev/null @@ -1,183 +0,0 @@ -'use strict'; - -import { create } from '../util.js'; - - -const DEFAULT_WEIGHT_MAX = 4; - - -// prototype for network objects -const network_proto = { - connect: function(source, sink, weight) { - return network_connect(this, source, sink, weight); - }, - compute: function(inputs, state) { - return network_compute(this, inputs, state); - }, -}; - - -// create a new network -export function network(input_count, internal_count, output_count, weight_max = 4) { - const count = input_count + internal_count + output_count; - const n = create({ - input_count, - output_count, - adjacency: new Array(count).fill([]), - weight: [], - }, network_proto); - return n; -} - - -// check index is an input -function is_input(n, index) { - return index < n.input_count; -} -// check if index is an output -function is_output(n, index) { - return index >= (n.adjacency.length - n.output_count); -} -// check if index is a hidden neuron -function is_hidden(n, index) { - return (!is_input(n, index)) && (!is_output(n, index)); -} - - -// returns a new network with an edge between the given nodes -// with the given weight -export function network_connect(n, source, sink, weight) { - if (is_input(n, sink)) { - // inputs cannot be sinks - throw new Error(`attempt to use input as sink (${source} -> ${sink})`); - } - if (is_output(n, source)) { - // outputs cannot be sources - throw new Error(`attempt to use output as source (${source} -> ${sink})`); - } - - return create({ - ...n, - adjacency: n.adjacency.map((row, i) => { - if (i === source && i === sink) { - // self-loop - return [...row, 2]; - } else if (i === source) { - return [...row, 1]; - } else if (i === sink) { - return [...row, -1]; - } else { - return [...row, 0]; - } - }), - weight: [...n.weight, weight], - }, network_proto); -} - - -// gets the indices of the edges incident on the given adjacency list -function incident_edges(n, adj) { - const incident = adj - .map((edge, index) => (edge < 0) || (edge === 2) ? index : null) - .filter(index => index !== null); - - return incident; -} - - -// get the indices of the ends of an edge -// in the case of self-loops, both values are the same -function edge_ends(n, edge) { - const ends = n.adjacency - .map((adj, index) => adj[edge] !== 0 ? index : null) - .filter(index => index != null); - - ends.sort((a, b) => n.adjacency[a][edge] < n.adjacency[b][edge] ? -1 : 1); - - if (ends.length === 1) { - return { source: ends[0], sink: ends[0] }; - } else if (ends.length === 2) { - return { source: ends[1], sink: ends[0] }; - } else { - throw new Error("something bad happened with the ends"); - } -} - - -// recursively get the value of a node from the input nodes, -// optionally caching the computed values -function get_value(n, index, input, prev, cache) { - // check if value is cached - if (cache !== undefined && cache[index]) { - return cache[index]; - } - // check if value is input - if (is_input(n, index)) { - return input[index]; - } - - const adj = n.adjacency[index]; // get adjacency list - const incident = incident_edges(n, adj); // get incident edges - const weight = incident.map(x => n.weight[x]); // edge weights - const sources = incident // get ancestor nodes - .map(x => edge_ends(n, x).source); - - // get the value of each ancestor - const values = sources - .map(x => x === index // if the ancestor is this node - ? prev[x - n.input_count] // then the value is the previous value - : get_value(n, x, input, prev, cache)); // else recurse - - const sum = values // compute the weighted sum of the values - .reduce((acc, x, i) => acc + (weight[i] * x), 0); - - if (sum !== sum) { // NaN test - console.log(n); - console.log(sources); - console.log(input); - throw new Error(`failed to get output for index ${index}`); - } - - // compute result - const value = Math.tanh(sum); - - // !!! impure caching !!! - // cache result - if (cache !== undefined) { - cache[index] = value; - } - - return value; -} - - -// compute a network's output and new hidden state -// given the input and previous hidden state -export function network_compute(n, input, state) { - // validate input - if (input.length !== n.input_count) { - throw new Error("incorrect number of input elements"); - } - // validate state - const hidden_count = n.adjacency.length - n.input_count - n.output_count; - if (state.length !== hidden_count) { - throw new Error("incorrect number of state elements"); - } - - // !!! impure caching !!! - const value_cache = {}; - - const result = Object.freeze(n.adjacency - .map((x, i) => is_output(n, i) ? i : null) // output index or null - .filter(i => i !== null) // remove nulls - .map(x => get_value(n, x, input, state, value_cache)) // map to computed value - ); - - const newstate = Object.freeze(n.adjacency - .map((x, i) => is_hidden(n, i) ? i : null) // hidden index or null - .filter(i => i !== null) // remove nulls - .map(x => get_value(n, x, input, state, value_cache)) // map to computed value (using cache) - ); - - return Object.freeze([result, newstate]); -} diff --git a/src/mind/topology.test.js b/src/mind/topology.test.js deleted file mode 100644 index 52c196f..0000000 --- a/src/mind/topology.test.js +++ /dev/null @@ -1,235 +0,0 @@ -'use strict'; - -import { network } from './topology'; - - -test('basic network functionality', () => { - const n = network(0, 5, 0); - expect(n).toEqual({ - input_count: 0, - output_count: 0, - adjacency: [ [], [], [], [], [] ], - weight: [], - }); - - expect(() => n.adjacency = []).toThrow(); - expect(() => n.weight = []).toThrow(); - - const nn = n.connect(0, 1, -2); - expect(nn).toEqual({ - input_count: 0, - output_count: 0, - adjacency: [ - [ 1 ], - [ -1 ], - [ 0 ], - [ 0 ], - [ 0 ] - ], - weight: [ -2 ], - }); - - expect(() => nn.adjacency = []).toThrow(); - expect(() => nn.weight = []).toThrow(); - - const nnn = nn.connect(2, 4, 3); - expect(nnn).toEqual({ - input_count: 0, - output_count: 0, - adjacency: [ - [ 1, 0 ], - [ -1, 0 ], - [ 0, 1 ], - [ 0, 0 ], - [ 0, -1 ] - ], - weight: [ -2, 3 ], - }); - - expect(() => nnn.adjacency = []).toThrow(); - expect(() => nnn.weight = []).toThrow(); -}); - - -test( -'networks are restricted from sinking to inputs or sourcing from outputs', -() => { - const n = network(2, 2, 2); - - expect(n.connect(1,2,0)).toEqual({ - input_count: 2, - output_count: 2, - adjacency: [ - [ 0 ], - [ 1 ], - [ -1 ], - [ 0 ], - [ 0 ], - [ 0 ], - ], - weight: [ 0 ], - }); - expect(() => n.connect(2, 1, 0)).toThrow(); - - expect(n.connect(3, 4, 2)).toEqual({ - input_count: 2, - output_count: 2, - adjacency: [ - [ 0 ], - [ 0 ], - [ 0 ], - [ 1 ], - [ -1 ], - [ 0 ], - ], - weight: [ 2 ], - }); - expect(() => n.connect(4, 3, 2)).toThrow(); -}); - - -test('self-connections work correctly', () => { - const n = network(0, 1, 0).connect(0, 0, 2.0); - expect(n).toEqual({ - input_count: 0, - output_count: 0, - adjacency: [ - [ 2 ], - ], - weight: [ 2 ], - }); -}); - - -test('network computations', () => { - const n = network(1, 0, 1).connect(0, 1, 2.0); - const input = [ -0.5 ]; - const state = []; - const result = n.compute(input, state); - expect(result).toEqual([ - [ Math.tanh(-0.5 * 2.0) ], - [], - ]); - - expect(input).toEqual([ -0.5 ]); - expect(state).toEqual([]); - - expect(() => result[0] = 'hi').toThrow(); - expect(() => result[0].push('hi')).toThrow(); - expect(() => result[1] = 'hi').toThrow(); - expect(() => result[1].push('hi')).toThrow(); -}); - - -test('multiple input network', () => { - const n = network(4, 0, 1) - .connect(0, 4, -1.0) - .connect(1, 4, -2.0) - .connect(2, 4, 1.0) - .connect(3, 4, 2.0) - - expect(n.compute([1, 2, 3, 5], [])).toEqual([ - [ Math.tanh( - (-1.0 * 1) + - (-2.0 * 2) + - (1.0 * 3) + - (2.0 * 5))], - [], - ]); -}); - - -test('multiple outputs', () => { - const n = network(4, 0, 2) - .connect(0, 4, -1) - .connect(1, 4, 1) - .connect(2, 5, -1) - .connect(3, 5, 1); - - expect(n.compute([1,2,3,5], [])).toEqual([ - [ Math.tanh(2-1), Math.tanh(5-3) ], - [], - ]); -}); - - -test('hidden neurons', () => { - const n = network(4, 2, 1) - .connect(0, 4, -1) - .connect(1, 4, 1) - .connect(2, 5, -1) - .connect(3, 5, 1) - .connect(4, 6, -1) - .connect(5, 6, 1); - - expect(n.compute([1,2,3,5], [ 0, 0 ])).toEqual([ - [ Math.tanh( Math.tanh(5-3) - Math.tanh(2-1) ) ], - [ Math.tanh(2-1), Math.tanh(5-3) ], - ]); -}); - - -test('arbitrary hidden neurons', () => { - const n = network(1, 2, 1) - .connect(0, 1, 1) - .connect(1, 2, -1) - .connect(2, 3, 2) - - const [output, state] = n.compute([1], [0, 0]); - - expect(output).toEqual([ - Math.tanh( - 2*Math.tanh( - -1*Math.tanh(1) - ) - ) - ]); - - expect(state).toEqual([ - Math.tanh(1), - Math.tanh( -Math.tanh(1) ), - ]); -}); - - -test('memory', () => { - const n = network(0, 1, 1).connect(0, 0, -0.5).connect(0, 1, 2); - - expect(n.compute([], [1])).toEqual([ - [ Math.tanh( 2 * Math.tanh( -0.5 * 1 ) ) ], - [ Math.tanh( -0.5 * 1) ], - ]); -}); - - -test('memory and input', () => { - const n = network(1, 1, 1) - .connect(0, 1, 1) - .connect(1, 1, 1) - .connect(1, 2, 1); - - expect(n.compute([2], [-1])).toEqual([ - [ Math.tanh( Math.tanh( 2-1 ) ) ], - [ Math.tanh( 2-1 ) ], - ]); -}); - - -test('input and state must be the correct size', () => { - const n = network(2, 1, 1) - .connect(0, 2, 1) - .connect(1, 2, 1) - .connect(2, 3, 1); - - // wrong input size - expect(() => n.compute([], [4])).toThrow(); - expect(() => n.compute([1], [4])).toThrow(); - expect(() => n.compute([1, 1, 1], [4])).toThrow(); - - // wrong state size - expect(() => n.compute([1, 1], [])).toThrow(); - expect(() => n.compute([1, 1], [4, 4])).toThrow(); - - // prove correct sizes work - n.compute([1, 1], [4]); -}); diff --git a/src/simulation/actions.js b/src/simulation/actions.js deleted file mode 100644 index ad08572..0000000 --- a/src/simulation/actions.js +++ /dev/null @@ -1,260 +0,0 @@ -'use strict'; - -const threshold = 0.5; - - -const move_forward = { - size: 1, - propose: (world, agent, head) => { - if (agent.flags.frozen === true) { return []; } - if (head[0] > threshold) { - console.log('move forward'); - console.log(agent.id, agent.x, agent.y); - const dx = { n: 0, e: 1, s: 0, w: -1 }[agent.flags.orientation]; - const dy = { n: -1, e: 0, s: 1, w: 0 }[agent.flags.orientation]; - return [{ - agent_changes: [{ - agent_id: agent.id, - x: agent.x + dx, - y: agent.y + dy, - }], - }]; - } else { - return []; - } - }, -}; - - -const move_backward = { - size: 1, - propose: (world, agent, head) => { - if (agent.flags.frozen === true) { return []; } - if (head[0] > threshold) { - console.log('move backward'); - const dx = { n: 0, e: 1, s: 0, w: -1 }[agent.flags.orientation]; - const dy = { n: -1, e: 0, s: 1, w: 0 }[agent.flags.orientation]; - return [{ - agent_changes: [{ - agent_id: agent.id, - x: agent.x - dx, - y: agent.y - dy, - }], - }]; - } else { - return []; - } - }, -}; - - -const turn_left = { - size: 1, - propose: (world, agent, head) => { - if (agent.flags.frozen === true) { return []; } - if (head[0] > threshold) { - console.log('turn left'); - const orientation = { n: 'w', e: 'n', s: 'e', w: 's' }[agent.flags.orientation]; - return [{ - agent_changes: [{ - agent_id: agent.id, - flags: { orientation }, - }], - }]; - } else { - return []; - } - }, -}; - - -const turn_right = { - size: 1, - propose: (world, agent, head) => { - if (agent.flags.frozen === true) { return []; } - if (head[0] > threshold) { - console.log('turn right'); - const orientation = { n: 'e', e: 's', s: 'w', w: 'n' }[agent.flags.orientation]; - return [{ - agent_changes: [{ - agent_id: agent.id, - flags: { orientation }, - }], - }]; - } else { - return []; - } - }, -}; - - -const place = { - size: 1, - propose: (world, agent, head) => { - if (head[0] < threshold) { return []; } - else { - const dx = { n: 0, e: 1, s: 0, w: -1 }[agent.flags.orientation]; - const dy = { n: -1, e: 0, s: 1, w: 0 }[agent.flags.orientation]; - return [ - { lattice_changes: [{ - x: agent.x + dx, y: agent.y + dy, - from: 'empty', to: 'mutable', - flags: { emit: [1, 0, 0, 0, 0, 0, 0, 0] }, - }]} - ]; - } - }, -}; - - -const trigger = { - size: 1, - propose: (world, agent, head) => { - if (head[0] < threshold) { return []; } - else { - const dx = { n: 0, e: 1, s: 0, w: -1 }[agent.flags.orientation]; - const dy = { n: -1, e: 0, s: 1, w: 0 }[agent.flags.orientation]; - return [ - { lattice_changes: [{ - x: agent.x + dx, y: agent.y + dy, - from: 'mutable', to: 'active', - flags: { emit: [1, 0, 0, 0, 0, 0, 0, 0] }, - }]} - ]; - } - }, -}; - - -const pretend_frozen = { - size: 1, - propose: (world, agent, head) => { - if (head[0] < threshold) { return [{ - agent_changes: [{ - agent_id: agent.id, - flags: { pretend_frozen: false }, - }], - }]; } - else { - return [{ - agent_changes: [{ - agent_id: agent.id, - flags: { pretend_frozen: true }, - }] - }]; - } - }, -}; - - -const unfreeze = { - size: 1, - propose: (world, agent, head) => { - if (head[0] < threshold) { return []; } - else { - const dx = { n: 0, e: 1, s: 0, w: -1 }[agent.flags.orientation]; - const dy = { n: -1, e: 0, s: 1, w: 0 }[agent.flags.orientation]; - const target = world.agents.filter( - a => a.x === agent.x+dx && a.y === agent.y+dy - )[0]; - if (target === undefined) { return []; } - return [ - { agent_changes: [{ - agent_id: target.id, - flags: { frozen: false, emit: [0, 1, 0, 0, 0, 0, 0, 0] }, - }]} - ]; - } - }, -}; - - -const take_flag = { - size: 1, - propose: (world, agent, head) => { - if (head[0] < threshold) { return []; } - else { - const dx = { n: 0, e: 1, s: 0, w: -1 }[agent.flags.orientation]; - const dy = { n: -1, e: 0, s: 1, w: 0 }[agent.flags.orientation]; - const target = world.agents.filter( - a => a.x === agent.x+dx && a.y === agent.y+dy - )[0]; - if (target === undefined || !target.flags.flag) { - return [ - { - lattice_changes: [{ - x: agent.x + dx, y: agent.y + dy, - from: 'flag', to: 'empty', - flags: { emit: [ 0, 0, 1, 0, 0, 0, 0, 0 ] }, - }], - agent_changes: [{ - agent_id: agent.id, - flags: { flag: true }, - }], - } - ]; - } else { - return [{ - agent_changes: [ - { - agent_id: target.id, - flags: { flag: false }, - }, - { - agent_id: agent.id, - flags: { flag: true }, - }, - ], - }]; - } - } - }, -}; - - -const drop_flag = { - size: 1, - propose: (world, agent, head) => { - if (head[0] < threshold) { return []; } - else if (!agent.flags.flag) { return []; } - else { - const dx = { n: 0, e: 1, s: 0, w: -1 }[agent.flags.orientation]; - const dy = { n: -1, e: 0, s: 1, w: 0 }[agent.flags.orientation]; - return [ - { - lattice_changes: [{ - x: agent.x + dx, y: agent.y + dy, - from: 'empty', to: 'flag', - flags: { emit: [ 0, 0, -1, 0, 0, 0, 0, 0 ] }, - }], - agent_changes: [{ - agent_id: agent.id, - flags: { flag: false }, - }], - } - ]; - } - }, -}; - - -const speak = { - size: 8, - propose: (world, agent, head) => { - return [ - { - agent_changes: [{ - agent_id: agent.id, - flags: { emit: head }, - }], - }, - ]; - }, -}; - - -export const actions = [ - move_forward, /*move_backward, */turn_left, turn_right, - place, trigger, pretend_frozen, unfreeze, take_flag, drop_flag, - speak, -]; diff --git a/src/simulation/actions.test.js b/src/simulation/actions.test.js deleted file mode 100644 index e8406bc..0000000 --- a/src/simulation/actions.test.js +++ /dev/null @@ -1,294 +0,0 @@ -'use strict'; - -import { actions } from './actions.js'; - - -const [ - move_forward, move_backward, - turn_left, turn_right, - place, trigger, - pretend_frozen, unfreeze, - take_flag, - ...rest -] = actions; - - -test("move forward", () => { - const n = { id: 0, x: 0, y: 0, flags: { orientation: 'n' } }; - const s = { id: 0, x: 0, y: 0, flags: { orientation: 's' } }; - const e = { id: 0, x: 0, y: 0, flags: { orientation: 'e' } }; - const w = { id: 0, x: 0, y: 0, flags: { orientation: 'w' } }; - - expect(move_forward.propose(null, n, [1])).toEqual([ - { agent_changes: [{ agent_id: 0, x: 0, y: -1 }] }, - ]); - expect(move_forward.propose(null, s, [1])).toEqual([ - { agent_changes: [{ agent_id: 0, x: 0, y: 1 }] }, - ]); - expect(move_forward.propose(null, e, [1])).toEqual([ - { agent_changes: [{ agent_id: 0, x: 1, y: 0 }] }, - ]); - expect(move_forward.propose(null, w, [1])).toEqual([ - { agent_changes: [{ agent_id: 0, x: -1, y: 0 }] }, - ]); - - expect(move_forward.propose(null, n, [0])).toEqual([]); - expect(move_forward.propose(null, s, [-1])).toEqual([]); - expect(move_forward.propose(null, e, [0])).toEqual([]); - expect(move_forward.propose(null, w, [-1])).toEqual([]); -}); - - -test("move backward", () => { - const n = { id: 0, x: 0, y: 0, flags: { orientation: 'n' } }; - const s = { id: 0, x: 0, y: 0, flags: { orientation: 's' } }; - const e = { id: 0, x: 0, y: 0, flags: { orientation: 'e' } }; - const w = { id: 0, x: 0, y: 0, flags: { orientation: 'w' } }; - - expect(move_backward.propose(null, n, [1])).toEqual([ - { agent_changes: [{ agent_id: 0, x: 0, y: 1 }] }, - ]); - expect(move_backward.propose(null, s, [1])).toEqual([ - { agent_changes: [{ agent_id: 0, x: 0, y: -1 }] }, - ]); - expect(move_backward.propose(null, e, [1])).toEqual([ - { agent_changes: [{ agent_id: 0, x: -1, y: 0 }] }, - ]); - expect(move_backward.propose(null, w, [1])).toEqual([ - { agent_changes: [{ agent_id: 0, x: 1, y: 0 }] }, - ]); - - expect(move_backward.propose(null, n, [0])).toEqual([]); - expect(move_backward.propose(null, s, [-1])).toEqual([]); - expect(move_backward.propose(null, e, [0])).toEqual([]); - expect(move_backward.propose(null, w, [-1])).toEqual([]); -}); - - -test("turn_left", () => { - const n = { id: 0, x: 0, y: 0, flags: { orientation: 'n' } }; - const s = { id: 0, x: 0, y: 0, flags: { orientation: 's' } }; - const e = { id: 0, x: 0, y: 0, flags: { orientation: 'e' } }; - const w = { id: 0, x: 0, y: 0, flags: { orientation: 'w' } }; - - expect(turn_left.propose(null, n, [1])).toEqual([ - { agent_changes: [{ agent_id: 0, flags: { orientation: 'w' } }] }, - ]); - expect(turn_left.propose(null, s, [1])).toEqual([ - { agent_changes: [{ agent_id: 0, flags: { orientation: 'e' } }] }, - ]); - expect(turn_left.propose(null, e, [1])).toEqual([ - { agent_changes: [{ agent_id: 0, flags: { orientation: 'n' } }] }, - ]); - expect(turn_left.propose(null, w, [1])).toEqual([ - { agent_changes: [{ agent_id: 0, flags: { orientation: 's' } }] }, - ]); - - expect(turn_left.propose(null, n, [0])).toEqual([]); - expect(turn_left.propose(null, s, [-1])).toEqual([]); - expect(turn_left.propose(null, e, [0])).toEqual([]); - expect(turn_left.propose(null, w, [-1])).toEqual([]); -}); - - -test("turn_right", () => { - const n = { id: 0, x: 0, y: 0, flags: { orientation: 'n' } }; - const s = { id: 0, x: 0, y: 0, flags: { orientation: 's' } }; - const e = { id: 0, x: 0, y: 0, flags: { orientation: 'e' } }; - const w = { id: 0, x: 0, y: 0, flags: { orientation: 'w' } }; - - expect(turn_right.propose(null, n, [1])).toEqual([ - { agent_changes: [{ agent_id: 0, flags: { orientation: 'e' } }] }, - ]); - expect(turn_right.propose(null, s, [1])).toEqual([ - { agent_changes: [{ agent_id: 0, flags: { orientation: 'w' } }] }, - ]); - expect(turn_right.propose(null, e, [1])).toEqual([ - { agent_changes: [{ agent_id: 0, flags: { orientation: 's' } }] }, - ]); - expect(turn_right.propose(null, w, [1])).toEqual([ - { agent_changes: [{ agent_id: 0, flags: { orientation: 'n' } }] }, - ]); - - expect(turn_right.propose(null, n, [0])).toEqual([]); - expect(turn_right.propose(null, s, [-1])).toEqual([]); - expect(turn_right.propose(null, e, [0])).toEqual([]); - expect(turn_right.propose(null, w, [-1])).toEqual([]); -}); - - -test("place", () => { - const n = { id: 0, x: 0, y: 0, flags: { orientation: 'n' } }; - const s = { id: 0, x: 0, y: 0, flags: { orientation: 's' } }; - const e = { id: 0, x: 0, y: 0, flags: { orientation: 'e' } }; - const w = { id: 0, x: 0, y: 0, flags: { orientation: 'w' } }; - - expect(place.propose(null, n, [1])).toEqual([ - { lattice_changes: [ - { - x: 0, y: -1, from: 'empty', to: 'mutable', - flags: { emit: [1, 0, 0, 0, 0, 0, 0, 0] } - } - ]} - ]); - expect(place.propose(null, s, [1])).toEqual([ - { lattice_changes: [ - { - x: 0, y: 1, from: 'empty', to: 'mutable', - flags: { emit: [1, 0, 0, 0, 0, 0, 0, 0] } - } - ]} - ]); - expect(place.propose(null, e, [1])).toEqual([ - { lattice_changes: [ - { - x: 1, y: 0, from: 'empty', to: 'mutable', - flags: { emit: [1, 0, 0, 0, 0, 0, 0, 0] } - } - ]} - ]); - expect(place.propose(null, w, [1])).toEqual([ - { lattice_changes: [ - { - x: -1, y: 0, from: 'empty', to: 'mutable', - flags: { emit: [1, 0, 0, 0, 0, 0, 0, 0] } - } - ]} - ]); - - expect(place.propose(null, n, [0])).toEqual([]); - expect(place.propose(null, s, [-1])).toEqual([]); - expect(place.propose(null, e, [0])).toEqual([]); - expect(place.propose(null, w, [-1])).toEqual([]); -}); - - -test("trigger", () => { - const n = { id: 0, x: 0, y: 0, flags: { orientation: 'n' } }; - const s = { id: 0, x: 0, y: 0, flags: { orientation: 's' } }; - const e = { id: 0, x: 0, y: 0, flags: { orientation: 'e' } }; - const w = { id: 0, x: 0, y: 0, flags: { orientation: 'w' } }; - - expect(trigger.propose(null, n, [1])).toEqual([ - { lattice_changes: [{ - x: 0, y: -1, from: 'mutable', to: 'active', - flags: { emit: [1, 0, 0, 0, 0, 0, 0, 0] }, - }]}, - ]); - expect(trigger.propose(null, s, [1])).toEqual([ - { lattice_changes: [{ - x: 0, y: 1, from: 'mutable', to: 'active', - flags: { emit: [1, 0, 0, 0, 0, 0, 0, 0] }, - }]}, - ]); - expect(trigger.propose(null, e, [1])).toEqual([ - { lattice_changes: [{ - x: 1, y: 0, from: 'mutable', to: 'active', - flags: { emit: [1, 0, 0, 0, 0, 0, 0, 0] }, - }]}, - ]); - expect(trigger.propose(null, w, [1])).toEqual([ - { lattice_changes: [{ - x: -1, y: 0, from: 'mutable', to: 'active', - flags: { emit: [1, 0, 0, 0, 0, 0, 0, 0] }, - }]}, - ]); - - expect(trigger.propose(null, n, [0])).toEqual([]); - expect(trigger.propose(null, s, [-1])).toEqual([]); - expect(trigger.propose(null, e, [0])).toEqual([]); - expect(trigger.propose(null, w, [-1])).toEqual([]); - }); - - -test("pretend frozen", () => { - const agent = { id: 2, x: 0, y: 0, flags: { orientation: 'n' } }; - - expect(pretend_frozen.propose(null, agent, [1])).toEqual([{ - agent_changes: [{ - agent_id: 2, - flags: { pretend_frozen: true }, - }] - }]); - - expect(pretend_frozen.propose(null, agent, [0])).toEqual([{ - agent_changes: [{ - agent_id: 2, - flags: { pretend_frozen: false }, - }] - }]); -}); - - -test("unfreeze", () => { - const agent1 = { id: 0, x: 0, y: 0, flags: { orientation: 'n' } }; - const agent2 = { id: 1, x: 0, y: -1 }; - const agent3 = { id: 10, x: -1, y: 0 }; - const agents = [ agent1, agent2, agent3 ]; - - expect(unfreeze.propose({agents}, agent1, [1])).toEqual([{ - agent_changes: [{ - agent_id: 1, - flags: { frozen: false, emit: [ 0, 1, 0, 0, 0, 0, 0, 0 ] }, - }] - }]); - - agent1.flags.orientation = 'w'; - - expect(unfreeze.propose({agents}, agent1, [1])).toEqual([{ - agent_changes: [{ - agent_id: 10, - flags: { frozen: false, emit: [ 0, 1, 0, 0, 0, 0, 0, 0 ] }, - }] - }]); - - agent1.flags.orientation = 's'; - expect(unfreeze.propose({agents}, agent1, [1])).toEqual([]); - - expect(unfreeze.propose({agents}, agent1, [0])).toEqual([]); -}); - - -test("take flag", () => { - const agent = { id: 0, x: 0, y: 0, flags: { orientation: 'n' } }; - const other1 = { id: 1, x: 0, y: -1, flags: { flag: true } }; - const other2 = { id: 2, x: 0, y: 1, flags: { flag: false } }; - - const world = { agents: [ agent, other1, other2 ] }; - - expect(take_flag.propose(world, agent, [1])).toEqual([ - { agent_changes: [ - { agent_id: 1, flags: { flag: false } }, - { agent_id: 0, flags: { flag: true } }, - ]}, - ]); - - agent.flags.orientation = 's'; - expect(take_flag.propose(world, agent, [1])).toEqual([ - { - lattice_changes: [ - { - x: 0, y: 1, from: 'flag', to: 'empty', - flags: { emit: [ 0, 0, 1, 0, 0, 0, 0, 0 ] }, - }, - ], - agent_changes: [ - { agent_id: 0, flags: { flag: true } }, - ] - }, - ]); - - expect(take_flag.propose(world, agent, [0])).toEqual([]); -}); - - -test("frozen agents cannot move", () => { - const agent = { id: 0, x: 0, y: 0, flags: { orientation: 'n', frozen: true } }; - - const world = { agents: [agent] }; - - expect(move_forward.propose(world, agent, [1])).toEqual([]); - expect(move_backward.propose(world, agent, [1])).toEqual([]); - expect(turn_left.propose(world, agent, [1])).toEqual([]); - expect(turn_right.propose(world, agent, [1])).toEqual([]); -}); diff --git a/src/simulation/game.js b/src/simulation/game.js deleted file mode 100644 index 978289d..0000000 --- a/src/simulation/game.js +++ /dev/null @@ -1,255 +0,0 @@ -'use strict'; - - -import { random_choice, apply, shuffle } from '../util.js'; -import { mut_genome_insert, parse_genome } from '../genome/genome.js'; -import { world_update } from '../world/world.js'; -import { senses } from './senses.js'; -import { actions } from './actions.js'; -import { lattice_rules } from './lattice_rules.js'; -import { validity } from './validity.js'; -import { postprocess } from './postprocess.js'; - - -function is_wall(size, x, y) { - return ( - x === 0 || x === size-1 || - y === 0 || y === size-1 - ); -} -function is_corner(size, x, y) { - const subsize = Math.floor(size/3); - return ( - (x < subsize || x >= 2*subsize) && - (y < subsize || y >= 2*subsize) - ); -} -export function get_team(size, x, y) { - const subsize = Math.floor(size/3); - if (y < subsize) { - return 0; - } else if (x >= 2*subsize) { - return 1; - } else if (y >= 2*subsize) { - return 2; - } else if (x < subsize) { - return 3; - } else { - return undefined; - } -} - -export function setup_board(size) { - const lattice = [...Array(size)] - .map(() => [...Array(size)]) - .map((row, y) => row.map((_, x) => { - if (is_wall(size, x, y)) { - return { type: 'immutable', flags: {} }; - } else if (is_corner(size, x, y)) { - return { type: 'immutable', flags: {} }; - } else { - const team = get_team(size, x, y); - if (Math.random() > 0.95 && get_team(size, x, y) === undefined) { - return { type: 'flag', flags: { team } }; - } else { - return { type: 'empty', flags: { team } }; - } - } - })); - return lattice; -} - - -export function create_world(size, teams) { - const lattice = setup_board(size); - - const agents = teams.reduce( - (agents, team, team_num) => { - const team_cells = lattice.map((row, y) => row.map((cell, x) => [x, y, cell])).flat() - // only check cells with the right team - .filter(([x, y, cell]) => cell.type === 'empty' && cell.flags.team === team_num) - return agents.concat(team.reduce( - (acc, agent) => { - const available_cells = team_cells.filter(([x, y, cell]) => acc.reduce( - (occupied, a) => occupied && ((a.x !== x) || (a.y !== y)), - true - )) - const [x, y, ..._] = random_choice(available_cells); - const orientation = random_choice([ 'n', 'e', 's', 'w' ]); - return [...acc, {...agent, x, y, flags: { ...agent.flags, team: team_num, orientation } }]; - }, - [] - )); - }, - [] - ).flat(); - - return { lattice, lattice_rules, agents, actions, senses, validity }; -}; - - -// team structure: -// { -// agents -// genome -// score -// games -// } - -const N_INPUT = senses.reduce((acc, sense) => acc + sense.size, 0); -const N_OUTPUT = actions.reduce((acc, action) => acc + action.size, 0); -const MAX_MUTATIONS = 15; - - -let agent_id = 0; - -export function create_agent(genome, n_internal) { - return { - id: agent_id++, // !!!! side effect !!!! - net: parse_genome(genome), - state: [...Array(n_internal)].map(_ => (2*Math.random()) - 1), - } -} - - - -export function create_team(size, genome_size, n_internal) { - const genome = apply( - s => mut_genome_insert(s, 4), - genome_size, - {n_input: N_INPUT, n_internal, n_output: N_OUTPUT, genes: []}, - ); - console.log(N_INPUT, N_OUTPUT, genome); - - const agents = [...Array(size)].map(_ => create_agent(genome, n_internal)); - return { agents, genome, score: 0, games: 0 }; -} - - -export function child_team(team, keep=Math.floor(team.size/2)) { - const n_internal = get_size(team.genome) - N_INPUT - N_OUTPUT; - const genome = apply( - s => mutate_genome(s, 4), - Math.floor(MAX_MUTATIONS * Math.random()), - [N_INPUT, n_internal, N_OUTPUT, team.genome] - ); - - const old_agents = [...Array(team.agents.length - keep)].reduce( - (acc, _) => { - const idx = Math.floor(Math.random() * acc.length); - acc.splice(idx, 1); - return acc; - }, - ); - const new_agents = [...Array(team.agents.length - keep)].map(_ => create_agent(genome, n_internal)); - const agents = [old_agents, new_agents].flat(); - - return { agents, genome, score: 0, games: 0 }; -} - - -// game structure: -// { -// world: world -// team_indices: number[] -// time: number -// } - - -export function create_game(size, teams, team_indices) { - const world = create_world(size, team_indices.map(i => teams[i].agents)); - return { world, team_indices, time: 0 }; -} - - -export function step_game(game) { - return { - ...game, - world: world_update(game.world, postprocess), - time: game.time + 1, - }; -} - - -function score(lattice, team_num) { - const size = lattice.length; - - // count number of flags in the team's area - return lattice - .map((row, y) => row.map((cell, x) => [x, y, cell])) - .flat() - .filter(([x, y, cell]) => get_team(size, x, y) === team_num && cell.type === 'flag') - .length; -} - -export function finish_game(teams, game) { - const scores = [0, 1, 2, 3].map(t => score(game.world.lattice, t)); - return game.team_indices.reduce( - (acc, idx, i) => { - const team = teams[idx]; - acc.splice(idx, 1, {...team, score: team.score + scores[i], games: team.games+1}); - return acc; - }, - teams, - ); -} - - - -// epoch structure -// { -// game -// time -// teams -// } - -function random_indices(teams) { - return [...Array(teams.length - 4)].reduce( - (acc) => { - const idx = Math.floor(Math.random() * acc.length); - acc.splice(idx, 1); - return acc; - }, - [...teams.keys()] - ); -} - -let epoch_num = 0; -export function create_epoch(size, teams) { - return { - game: create_game(size, teams, random_indices(teams)), - time: 0, - epoch: epoch_num++, // !!!! side effects !!!! - size, - teams, - } -} - - -const GAME_STEPS = 10000 -const EPOCH_STEPS = 200 - -export function update_epoch(epoch) { - if (epoch.game.time < GAME_STEPS) { - return { ...epoch, game: step_game(epoch.game) }; - } else if (epoch.time < EPOCH_STEPS) { - return { - ...epoch, - teams: finish_game(epoch.teams, epoch.game), - game: create_game(size, epoch.teams, random_indices(epoch.teams)), - time: epoch.time+1 - }; - } else { - // epoch complete!! - const source_teams = epoch.teams - .map(team => { - const normalized_score = team.score/team.games; - const count = Math.ceil(16*normalized_score); - return [...Array(count > 0 ? count : 1)].map(x => team) - }) - .flat(); - const new_teams = [...Array(epoch.teams.length)] - .reduce((acc) => child_team(random_choice(source_teams)), []); - return create_epoch(epoch.size, new_teams); - } -} diff --git a/src/simulation/game.test.js b/src/simulation/game.test.js deleted file mode 100644 index 219dce6..0000000 --- a/src/simulation/game.test.js +++ /dev/null @@ -1,59 +0,0 @@ -'use strict'; - -import { apply } from '../util.js'; -import { setup_board, create_world } from './game.js'; - - -test("set up boards correctly", () => { - const _ = { type: 'empty', flags: {} }; - const a = { type: 'empty', flags: { team: 0 } }; - const b = { type: 'empty', flags: { team: 1 } }; - const c = { type: 'empty', flags: { team: 2 } }; - const d = { type: 'empty', flags: { team: 3 } }; - const W = { type: 'immutable', flags: {} }; - - expect(setup_board(6)).toEqual([ - [ W, W, W, W, W, W, ], - [ W, W, a, a, W, W, ], - [ W, d, _, _, b, W, ], - [ W, d, _, _, b, W, ], - [ W, W, c, c, W, W, ], - [ W, W, W, W, W, W, ], - ]); - expect(setup_board(9)).toEqual([ - [ W, W, W, W, W, W, W, W, W, ], - [ W, W, W, a, a, a, W, W, W, ], - [ W, W, W, a, a, a, W, W, W, ], - [ W, d, d, _, _, _, b, b, W, ], - [ W, d, d, _, _, _, b, b, W, ], - [ W, d, d, _, _, _, b, b, W, ], - [ W, W, W, c, c, c, W, W, W, ], - [ W, W, W, c, c, c, W, W, W, ], - [ W, W, W, W, W, W, W, W, W, ], - ]); -}); - - -test("creating a world works correctly", () => { - const id = 0; - const agent = (id) => ({ id, net: `${id}`, state: `s${id}` }); - const team1 = [agent(0), agent(1)]; - const team2 = [agent(2), agent(3)]; - const team3 = [agent(4), agent(5)]; - const team4 = [agent(6), agent(7)]; - - const world = create_world(6, [team1, team2, team3, team4]); - const agent_cell = (agent) => { - const { x, y } = agent; - return world.lattice[y][x]; - }; - - expect(agent_cell(world.agents[0])).toEqual({ type: 'empty', flags: { team: 0 } }); - expect(agent_cell(world.agents[1])).toEqual({ type: 'empty', flags: { team: 0 } }); - expect(agent_cell(world.agents[2])).toEqual({ type: 'empty', flags: { team: 1 } }); - expect(agent_cell(world.agents[3])).toEqual({ type: 'empty', flags: { team: 1 } }); - expect(agent_cell(world.agents[4])).toEqual({ type: 'empty', flags: { team: 2 } }); - expect(agent_cell(world.agents[5])).toEqual({ type: 'empty', flags: { team: 2 } }); - expect(agent_cell(world.agents[6])).toEqual({ type: 'empty', flags: { team: 3 } }); - expect(agent_cell(world.agents[7])).toEqual({ type: 'empty', flags: { team: 3 } }); -}); diff --git a/src/simulation/lattice_rules.js b/src/simulation/lattice_rules.js deleted file mode 100644 index cc2fc5b..0000000 --- a/src/simulation/lattice_rules.js +++ /dev/null @@ -1,77 +0,0 @@ -import { pairs } from '../util.js'; -import { get_team } from './game.js'; - -function mod(k, n) { - return ((k % n) + n) % n; -} - -function pos_wrap(lattice, x, y) { - const height = lattice.length; - const width = lattice[0].length; - return [mod(x, width), mod(y, height)]; -} - - -function neighbors(lattice, x, y) { - const offsets = [-1, 0, 1]; - const positions = pairs(offsets, offsets) - .filter(([dx, dy]) => dx !== 0 || dy !== 0) - .map(([dx, dy]) => pos_wrap(lattice, x+dx, y+dy)); - const neighbors = positions - .map(([x, y]) => [x, y, lattice[y][x]]); - return neighbors; -} - - -export const lattice_rules = { - - empty: (lattice, x, y) => { - const num_active_neighbors = neighbors(lattice, x, y) - .map(([x, y, cell]) => cell.type) - .filter(type => type === 'mutable' || type === 'active') - .length; - if (num_active_neighbors === 3) { - return { world_updates: [{ - x, y, from: 'empty', to: 'active', - flags: { emit: [0, 0, 0, 0, 0, 0, 0, 1] }, - }]}; - } else { - return { world_updates: [{ - x, y, flags: { team: get_team(lattice.length, x, y) }, - }]}; - } - }, - - active: (lattice, x, y) => { - const num_active_neighbors = neighbors(lattice, x, y) - .map(([x, y, cell]) => cell.type) - .filter(type => type === 'mutable' || type === 'active') - .length; - const die = { world_updates: [{ - x, y, from: 'active', to: 'empty', - flags: { emit: [0, 0, 0, 0, 0, 0, 0, -1] }, - }]}; - if (num_active_neighbors < 2) { - return die; - } else if (num_active_neighbors > 3) { - return die; - } - }, - - mutable: (lattice, x, y) => { - const num_active_neighbors = neighbors(lattice, x, y) - .map(([x, y, cell]) => cell.type) - .filter(type => type === 'active') - .length; - if (num_active_neighbors > 0) { - // become living cell - return { world_updates: [{ - x, y, from: 'empty', to: 'active', - flags: { emit: [0, 0, 0, 0, 0, 0, 0, 1] }, - }]}; - } - }, - immutable: () => {}, - flag: () => {}, - -}; diff --git a/src/simulation/lattice_rules.test.js b/src/simulation/lattice_rules.test.js deleted file mode 100644 index c285711..0000000 --- a/src/simulation/lattice_rules.test.js +++ /dev/null @@ -1,147 +0,0 @@ -import { world_update } from '../world/world.js'; -import { lattice_rules } from './lattice_rules.js'; - - -function apply(f, n, x0) { - if (n == 0) { - return x0; - } else { - return f(apply(f, n-1, x0)); - } -} - - -// remove cell team information -function clean_team(lattice) { - return lattice.map(row => row.map(cell => ({...cell, flags: {...cell.flags, team: undefined } }))); -} - - -test("blinker", () => { - const L = { type: 'active', flags: { team: undefined, } }; - const _ = { type: 'empty', flags: { team: undefined, } }; - const l = { type: 'active', flags: { team: undefined, emit: [0, 0, 0, 0, 0, 0, 0, 1] } }; - const d = { type: 'empty', flags: { team: undefined, emit: [0, 0, 0, 0, 0, 0, 0, -1] } }; - const lattice = [ - [ _, _, _, _, _ ], - [ _, _, _, _, _ ], - [ _, L, L, L, _ ], - [ _, _, _, _, _ ], - [ _, _, _, _, _ ], - ]; - - const world = { lattice, lattice_rules, agents: [], senses: [], actions: [], validity: [] }; - expect(clean_team(world_update(world).lattice)).toEqual([ - [ _, _, _, _, _ ], - [ _, _, l, _, _ ], - [ _, d, L, d, _ ], - [ _, _, l, _, _ ], - [ _, _, _, _, _ ], - ]); - expect(clean_team(world_update(world_update(world)).lattice)).toEqual([ - [ _, _, _, _, _ ], - [ _, _, d, _, _ ], - [ _, l, L, l, _ ], - [ _, _, d, _, _ ], - [ _, _, _, _, _ ], - ]); -}); - - -test("glider", () => { - const L = { type: 'active', flags: { team: undefined, } }; - const _ = { type: 'empty', flags: { team: undefined, } }; - const l = { type: 'active', flags: { team: undefined, emit: [0, 0, 0, 0, 0, 0, 0, 1] } }; - const d = { type: 'empty', flags: { team: undefined, emit: [0, 0, 0, 0, 0, 0, 0, -1] } }; - const lattice = [ - [ _, _, _, _, _, _ ], - [ _, _, _, L, _, _ ], - [ _, L, _, L, _, _ ], - [ _, _, L, L, _, _ ], - [ _, _, _, _, _, _ ], - [ _, _, _, _, _, _ ], - ]; - - const world = { lattice, lattice_rules, agents: [], senses: [], actions: [], validity: [] }; - //expect(clean_team(world_update(world).lattice)).toEqual([ - expect(clean_team(apply(world_update, 1, world).lattice)).toEqual([ - [ _, _, _, _, _, _ ], - [ _, _, l, d, _, _ ], - [ _, d, _, L, l, _ ], - [ _, _, L, L, _, _ ], - [ _, _, _, _, _, _ ], - [ _, _, _, _, _, _ ], - ]); - expect(clean_team(apply(world_update, 2, world).lattice)).toEqual([ - [ _, _, _, _, _, _ ], - [ _, _, d, l, _, _ ], - [ _, _, _, d, L, _ ], - [ _, _, L, L, l, _ ], - [ _, _, _, _, _, _ ], - [ _, _, _, _, _, _ ], - ]); - expect(clean_team(apply(world_update, 3, world).lattice)).toEqual([ - [ _, _, _, _, _, _ ], - [ _, _, _, d, _, _ ], - [ _, _, l, _, L, _ ], - [ _, _, d, L, L, _ ], - [ _, _, _, l, _, _ ], - [ _, _, _, _, _, _ ], - ]); - expect(clean_team(apply(world_update, 4, world).lattice)).toEqual([ - [ _, _, _, _, _, _ ], - [ _, _, _, _, _, _ ], - [ _, _, d, _, L, _ ], - [ _, _, l, d, L, _ ], - [ _, _, _, L, l, _ ], - [ _, _, _, _, _, _ ], - ]); -}); - - -test("beehive", () => { - const L = { type: 'active', flags: { team: undefined, } }; - const _ = { type: 'empty', flags: { team: undefined, } }; - const lattice = [ - [ _, _, _, _, _, _ ], - [ _, _, L, L, _, _ ], - [ _, L, _, _, L, _ ], - [ _, _, L, L, _, _ ], - [ _, _, _, _, _, _ ], - ]; - - const world = { lattice, lattice_rules, agents: [], senses: [], actions: [], validity: [] }; - //expect(clean_team(world_update(world).lattice)).toEqual([ - expect(clean_team(apply(world_update, 1, world).lattice)).toEqual(lattice); -}); - - -test("mutables are activated by neighboring actives", () => { - const L = { type: 'active', flags: { team: undefined, } }; - const _ = { type: 'empty', flags: { team: undefined, } }; - const l = { type: 'active', flags: { team: undefined, emit: [0, 0, 0, 0, 0, 0, 0, 1] } }; - const d = { type: 'empty', flags: { team: undefined, emit: [0, 0, 0, 0, 0, 0, 0, -1] } }; - const M = { type: 'mutable', flags: { team: undefined, } }; - - const lattice = [ - [ _, _, _, _, ], - [ _, L, M, _, ], - [ _, M, M, _, ], - [ _, _, _, _, ], - ]; - - const world = { lattice, lattice_rules, agents: [], senses: [], actions: [], validity: [] }; - - expect(clean_team(apply(world_update, 1, world).lattice)).toEqual([ - [ _, _, _, _, ], - [ _, L, l, _, ], - [ _, l, l, _, ], - [ _, _, _, _, ], - ]); - expect(clean_team(apply(world_update, 2, world).lattice)).toEqual([ - [ _, _, _, _, ], - [ _, L, L, _, ], - [ _, L, L, _, ], - [ _, _, _, _, ], - ]); -}); diff --git a/src/simulation/postprocess.js b/src/simulation/postprocess.js deleted file mode 100644 index d50839e..0000000 --- a/src/simulation/postprocess.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -export const postprocess = [ - (world) => ({ - ...world, - agents: world.agents.map(a => { - const {x, y} = a; - if ( - world.lattice[y][x].type === 'mutable' || - world.lattice[y][x].type === 'active' - ) { - return { ...a, flags: {...a.flags, frozen: true } }; - } else { - return a; - } - }), - }), -]; diff --git a/src/simulation/postprocess.test.js b/src/simulation/postprocess.test.js deleted file mode 100644 index dc77c6a..0000000 --- a/src/simulation/postprocess.test.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -import { world_update } from '../world/world.js'; -import { postprocess } from './postprocess.js'; - -test("agents freeze when finishing on a mutable or active", () => { - const agent = { - id: 1, - net: { compute: () => [[1], null] }, - state: null, - x: 0, y: 0, - flags: {}, - }; - - const lattice = [[{ type: 'empty', flags: {} }]]; - - const world = { - lattice, - lattice_rules: { empty: ()=>{}, active: ()=>{}, mutable: ()=>{} }, - agents: [agent], - senses: [], - actions: [], - validity: [], - }; - - expect(world_update(world, postprocess).agents[0]).toEqual(agent); - world.lattice[0][0].type = 'mutable'; - expect(world_update(world, postprocess).agents[0]).toEqual({ - ...agent, - flags: { frozen: true }, - }); - world.lattice[0][0].type = 'active'; - expect(world_update(world, postprocess).agents[0]).toEqual({ - ...agent, - flags: { frozen: true }, - }); -}); diff --git a/src/simulation/senses.js b/src/simulation/senses.js deleted file mode 100644 index 970f86b..0000000 --- a/src/simulation/senses.js +++ /dev/null @@ -1,182 +0,0 @@ -'use strict'; - - -const frozen = { - size: 1, - read: (world, agent) => { - if (agent.flags.frozen === true) { - return [ 1 ]; - } else { - return [ 0 ]; - } - }, -}; - - -// add two arrays together element-wise with a scaling factor -function array_scalesum(a, s, b) { - return a.map((x, i) => x + (s*b[i])); -} -// determine the square of the distance between two cells -function lattice_dist2(x0, y0, x1, y1) { - if (x0 === x1 && y0 === y1) { return 1; } // not proper distance but avoids divide-by-zero errors c: - return ((x0-x1)**2) + ((y0-y1)**2); -} -const hear = { - size: 8, - read: (world, agent) => { - const {x, y} = agent; - const lattice_sounds = world.lattice - .map((row, cy) => row.map((cell, cx) => [ 1/lattice_dist2(x, y, cx, cy), cell ])) - .flat() - .filter(([scale, cell]) => cell.flags.emit !== undefined) - .reduce( - (acc, [scale, cell]) => array_scalesum(acc, scale, cell.flags.emit), - [0, 0, 0, 0, 0, 0, 0, 0] - ); - const agent_sounds = world.agents - .filter(a => a.flags.emit !== undefined) - .reduce( - (acc, a) => array_scalesum(acc, 1/lattice_dist2(x, y, a.x, a.y), a.flags.emit), - [0, 0, 0, 0, 0, 0, 0, 0] - ); - - return array_scalesum(lattice_sounds, 1, agent_sounds).map(ch => Math.tanh(ch)); - }, -}; - - -const [VIS_WIDTH, VIS_HEIGHT] = [31, 31]; -const [VIS_HWIDTH, VIS_HHEIGHT] = [Math.floor(VIS_WIDTH/2), Math.floor(VIS_HEIGHT/2)]; -function identity_mod(n, p) { - const mod = (n % (2*p)) - p + 1; - return mod/p; -} - -function world_pos_to_vision_pos(world, agent, x, y) { - const dx = x - agent.x; - const dy = y - agent.y; - const orientation = agent.flags.orientation || 'n'; - switch (orientation) { - case 'n': - return [VIS_HWIDTH+dx, VIS_HEIGHT+dy-1]; - case 's': - return [VIS_HWIDTH-dx, VIS_HEIGHT-dy-1]; - case 'e': - return [VIS_HWIDTH+dy, VIS_HEIGHT-dx-1]; - case 'w': - return [VIS_HWIDTH-dy, VIS_HEIGHT+dx-1]; - } -} - -function vision_pos_to_world_pos(world, agent, x, y) { - const dx = x-VIS_HWIDTH; - const dy = y-VIS_HEIGHT+1; - const orientation = agent.flags.orientation || 'n'; - switch (orientation) { - case 'n': - return [agent.x + dx, agent.y + dy]; - case 's': - return [agent.x - dx, agent.y - dy]; - case 'e': - return [agent.x - dy, agent.y - dx]; - case 'w': - return [agent.x + dy, agent.y + dx]; - } -} - -function world_pos_to_vision_idx(world, agent, x, y) { - const [vx, vy] = world_pos_to_vision_pos(world, agent, x, y); - return (VIS_WIDTH * vy) + vx; -} -function vision_idx_to_world_pos(world, agent, idx) { - const vx = idx % VIS_WIDTH; - const vy = Math.floor(idx / VIS_WIDTH); - const result = vision_pos_to_world_pos(world, agent, vx, vy); - return result; -} - -function see_cell(world, x, y) { - const team = 0; - const orientation = 0; - if (!world.lattice[y] || !world.lattice[y][x]) { - // beyond the map edge - return [ 0, 0, 0 ]; - } - const type = { - active: -0.8, - mutable: -0.4, - empty: 0.0, - immutable: 0.4, - flag: 0.8, - }[world.lattice[y][x].type]; - return [team, orientation, type]; -} - - -function relative_orientation(viewer, agent) { - switch(viewer.flags.orientation) { - case 'n': return { n: -0.8, e: -0.4, s: 0.4, w: 0.8 }[agent.flags.orientation]; - case 'e': return { e: -0.8, s: -0.4, w: 0.4, n: 0.8 }[agent.flags.orientation]; - case 's': return { s: -0.8, w: -0.4, n: 0.4, e: 0.8 }[agent.flags.orientation]; - case 'w': return { w: -0.8, n: -0.4, e: 0.4, s: 0.8 }[agent.flags.orientation]; - } -} - - -function see_agent(viewer, agent) { - const team = { - 0: -0.8, - 1: -0.4, - 2: 0.4, - 3: 0.8, - }[agent.flags.team] + (identity_mod(agent.id, 11)/16); - const orientation = relative_orientation(viewer, agent) + (identity_mod(agent.id, 37)/16); - const frozen = agent.flags.frozen || agent.flags.pretend_frozen; - const type = (() => { - if (frozen && agent.flags.flag) { - return -0.4; - } else if (frozen) { - return -0.8; - } else if (agent.flags.flag) { - return 0.8; - } else { - return 0.0; - } - })() + (identity_mod(agent.id, 499)/16); - - return [team, orientation, type]; -} - - -const see = { - size: 3*VIS_WIDTH * VIS_HEIGHT, - read: (world, agent) => { - const indices = [...Array(VIS_WIDTH*VIS_HEIGHT).keys()] - const vision = indices - .map(idx => { - const [x, y] = vision_idx_to_world_pos(world, agent, idx); - return see_cell(world, x, y); - }); - const result = world.agents.reduce( - (acc, a) => { - const idx = world_pos_to_vision_idx(world, agent, a.x, a.y); - - if (idx < 0 || idx >= VIS_WIDTH*VIS_HEIGHT) { - return acc; - } else { - acc.splice(idx, 1, see_agent(agent, a)); - return acc; - } - }, - vision - ); - return result.flat(); - }, -}; - - - -export const senses = [ - frozen, hear, see, { size: 1, read: () => [1] }, -]; diff --git a/src/simulation/senses.test.js b/src/simulation/senses.test.js deleted file mode 100644 index d3941c7..0000000 --- a/src/simulation/senses.test.js +++ /dev/null @@ -1,499 +0,0 @@ -'use strict'; - -import { senses } from './senses.js'; - -const [ frozen, hear, see, ...rest ] = senses; - - -test("frozen sense", () => { - const agent = { - id: 0, x: 0, y: 0, - flags: { frozen: true, }, - }; - - expect(frozen.read(null, agent)).toEqual([1]); - agent.flags.frozen = false; - expect(frozen.read(null, agent)).toEqual([0]); -}); - - -// --===== hearing =====-- - -test("hear nothing", () => { - const agent = { id: 4, x: 1, y: 1, flags: {} }; - const o = { type: 'empty', flags: {} }; - const world = { - lattice: [ - [ o, o, o ], - [ o, o, o ], - [ o, o, o ], - ], - agents: [agent], - }; - - expect(hear.read(world, agent)).toEqual([ - Math.tanh(0), Math.tanh(0), Math.tanh(0), Math.tanh(0), - Math.tanh(0), Math.tanh(0), Math.tanh(0), Math.tanh(0), - ]); -}); - - -test("hear self", () => { - const agent = { id: 4, x: 1, y: 1, flags: { emit: [1, 0, 0.5, 0, 0, 0, 0, 1] } }; - const o = { type: 'empty', flags: {} }; - const world = { - lattice: [ - [ o, o, o ], - [ o, o, o ], - [ o, o, o ], - ], - agents: [agent], - }; - - expect(hear.read(world, agent)).toEqual([ - Math.tanh(1), Math.tanh(0), Math.tanh(0.5), Math.tanh(0), - Math.tanh(0), Math.tanh(0), Math.tanh(0), Math.tanh(1), - ]); -}); - - -test("hear cells", () => { - const agent = { id: 4, x: 2, y: 2, flags: {} }; - const o = { type: 'empty', flags: {} }; - const s = { type: 'empty', flags: { emit: [1, 0.5, 0.25, 0.125, 0, 0, 0, 0] } }; - const world = { - lattice: [ - [ o, o, s, o, o ], - [ o, o, o, o, o ], - [ o, s, o, o, o ], - [ o, o, o, o, o ], - [ o, o, o, o, o ], - ], - agents: [agent], - }; - - expect(hear.read(world, agent)).toEqual([ - Math.tanh(1.25), Math.tanh(0.625), Math.tanh(0.3125), Math.tanh(0.15625), - Math.tanh(0), Math.tanh(0), Math.tanh(0), Math.tanh(0), - ]); -}); - - -test("hear cells & agents", () => { - const agent = { id: 4, x: 2, y: 2, flags: {} }; - const agent2 = { id: 0, x: 2, y: 4, flags: { emit: [0, 0, 0, 0, 1, 1, 1, 1] } }; - const o = { type: 'empty', flags: {} }; - const s = { type: 'empty', flags: { emit: [1, 0.5, 0.25, 0.125, 0, 0, 0, 0] } }; - const world = { - lattice: [ - [ o, o, s, o, o ], - [ o, o, o, o, o ], - [ o, s, o, o, o ], - [ o, o, o, o, o ], - [ o, o, o, o, o ], - ], - agents: [agent, agent2], - }; - - expect(hear.read(world, agent)).toEqual([ - Math.tanh(1.25), Math.tanh(0.625), Math.tanh(0.3125), Math.tanh(0.15625), - Math.tanh(0.25), Math.tanh(0.25), Math.tanh(0.25), Math.tanh(0.25), - ]); -}); - - -// --===== vision =====-- - - -test("see agents", () => { - const o = { type: 'empty', flags: {} }; - const lattice = [ - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - [ o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o ], - ]; - const agentA = { id: 0, x: 15, y: 30, flags: { team: 0, orientation: 'n' } }; - const agentB = { id: 1, x: 15, y: 0, flags: { team: 1, orientation: 's' } }; - const agentC = { id: 2, x: 0, y: 15, flags: { team: 2, orientation: 'e', flag: true } }; - const agentD = { id: 3, x: 30, y: 15, flags: { team: 3, orientation: 'w', frozen: true } }; - const agentE = { id: 4, x: 30, y: 30, flags: { team: 0, orientation: 's', flag: true, frozen: true } }; - const world = { - lattice, - agents: [agentA, agentB, agentC, agentD, agentE], - }; - - const an = [ - -0.8 + 0.0625*(-10/11), // team + identity - -0.8 + 0.0625*(-36/37), // orientation + identity - 0.0 + 0.0625*(-498/499), // class + identity - ]; - const as = [ - -0.8 + 0.0625*(-10/11), // team + identity - 0.4 + 0.0625*(-36/37), // orientation + identity - 0.0 + 0.0625*(-498/499), // class + identity - ]; - const ae = [ - -0.8 + 0.0625*(-10/11), // team + identity - -0.4 + 0.0625*(-36/37), // orientation + identity - 0.0 + 0.0625*(-498/499), // class + identity - ]; - const aw = [ - -0.8 + 0.0625*(-10/11), // team + identity - 0.8 + 0.0625*(-36/37), // orientation + identity - 0.0 + 0.0625*(-498/499), // class + identity - ]; - - const bn = [ - -0.4 + 0.0625*(-9/11), // team + identity - -0.8 + 0.0625*(-35/37), // orientation + identity - 0.0 + 0.0625*(-497/499), // class + identity - ]; - const bs = [ - -0.4 + 0.0625*(-9/11), // team + identity - 0.4 + 0.0625*(-35/37), // orientation + identity - 0.0 + 0.0625*(-497/499), // class + identity - ]; - const be = [ - -0.4 + 0.0625*(-9/11), // team + identity - -0.4 + 0.0625*(-35/37), // orientation + identity - 0.0 + 0.0625*(-497/499), // class + identity - ]; - const bw = [ - -0.4 + 0.0625*(-9/11), // team + identity - 0.8 + 0.0625*(-35/37), // orientation + identity - 0.0 + 0.0625*(-497/499), // class + identity - ]; - - const cn = [ - 0.4 + 0.0625*(-8/11), // team + identity - -0.8 + 0.0625*(-34/37), // orientation + identity - 0.8 + 0.0625*(-496/499), // class + identity - ]; - const cs = [ - 0.4 + 0.0625*(-8/11), // team + identity - 0.4 + 0.0625*(-34/37), // orientation + identity - 0.8 + 0.0625*(-496/499), // class + identity - ]; - const ce = [ - 0.4 + 0.0625*(-8/11), // team + identity - -0.4 + 0.0625*(-34/37), // orientation + identity - 0.8 + 0.0625*(-496/499), // class + identity - ]; - const cw = [ - 0.4 + 0.0625*(-8/11), // team + identity - 0.8 + 0.0625*(-34/37), // orientation + identity - 0.8 + 0.0625*(-496/499), // class + identity - ]; - - const dn = [ - 0.8 + 0.0625*(-7/11), // team + identity - -0.8 + 0.0625*(-33/37), // orientation + identity - -0.8 + 0.0625*(-495/499), // class + identity - ]; - const ds = [ - 0.8 + 0.0625*(-7/11), // team + identity - 0.4 + 0.0625*(-33/37), // orientation + identity - -0.8 + 0.0625*(-495/499), // class + identity - ]; - const de = [ - 0.8 + 0.0625*(-7/11), // team + identity - -0.4 + 0.0625*(-33/37), // orientation + identity - -0.8 + 0.0625*(-495/499), // class + identity - ]; - const dw = [ - 0.8 + 0.0625*(-7/11), // team + identity - 0.8 + 0.0625*(-33/37), // orientation + identity - -0.8 + 0.0625*(-495/499), // class + identity - ]; - - const en = [ - -0.8 + 0.0625*(-6/11), // team + identity - -0.8 + 0.0625*(-32/37), // orientation + identity - -0.4 + 0.0625*(-494/499), // class + identity - ]; - const es = [ - -0.8 + 0.0625*(-6/11), // team + identity - 0.4 + 0.0625*(-32/37), // orientation + identity - -0.4 + 0.0625*(-494/499), // class + identity - ]; - const ee = [ - -0.8 + 0.0625*(-6/11), // team + identity - -0.4 + 0.0625*(-32/37), // orientation + identity - -0.4 + 0.0625*(-494/499), // class + identity - ]; - const ew = [ - -0.8 + 0.0625*(-6/11), // team + identity - 0.8 + 0.0625*(-32/37), // orientation + identity - -0.4 + 0.0625*(-494/499), // class + identity - ]; - - const _ = [ - 0.0, 0.0, 0.0, - ]; - - - expect(see.read(world, agentA)).toEqual([ - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, bs, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - ce, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, dw, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, an, _, _, _, _, _, _, _, _, _, _, _, _, _, _, es, - ]); - expect(see.read(world, agentB)).toEqual([ - en, _, _, _, _, _, _, _, _, _, _, _, _, _, _, as, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - de, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, cw, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, bn, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - ]); - expect(see.read(world, agentC)).toEqual([ - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, ds, _, _, _, _, _, _, _, _, _, _, _, _, _, _, ee, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - be, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, aw, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, cn, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - ]); - expect(see.read(world, agentD)).toEqual([ - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, cs, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - ae, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, bw, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - ew, _, _, _, _, _, _, _, _, _, _, _, _, _, _, dn, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - ]); -}); - - -test("seeing 'nothing' looks like teamless empty squares", () => { - const lattice = [[]]; - const agent = { id: 0, x: 0, y: 0, flags: { team: 0, orientation: 'n' } }; - const world = { - lattice, agents: [agent], - }; - - const _ = [ - 0.0, 0.0, 0.0, - ]; - const an = [ - -0.8 + 0.0625*(-10/11), // team + identity - -0.8 + 0.0625*(-36/37), // orientation + identity - 0.0 + 0.0625*(-498/499), // class + identity - ]; - - expect(see.read(world, agent)).toEqual([ - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, an, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - ]); -}); - - -test("agents pretending to be frozen appear frozen", () => { - const lattice = [[]]; - const agent = { id: 0, x: 0, y: 0, flags: { team: 0, orientation: 'n', pretend_frozen: true } }; - const world = { - lattice, agents: [agent], - }; - - const _ = [ - 0.0, 0.0, 0.0, - ]; - const an = [ - -0.8 + 0.0625*(-10/11), // team + identity - -0.8 + 0.0625*(-36/37), // orientation + identity - -0.8 + 0.0625*(-498/499), // class + identity - ]; - - expect(see.read(world, agent)).toEqual([ - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, an, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, - ]); -}); diff --git a/src/simulation/trial.js b/src/simulation/trial.js deleted file mode 100644 index b4462c5..0000000 --- a/src/simulation/trial.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; - -import { create_team, create_epoch, update_epoch } from './game.js'; - -const start_teams = [...Array(50)].map(x => create_team(32, 5, 5)); - -let epoch = create_epoch(start_teams); - -while (epoch.epoch < 1) { - console.log(epoch.epoch, epoch.time, epoch.game.time); - epoch = update_epoch(epoch); -} diff --git a/src/simulation/validity.js b/src/simulation/validity.js deleted file mode 100644 index de4acd1..0000000 --- a/src/simulation/validity.js +++ /dev/null @@ -1,14 +0,0 @@ -export const validity = [ - // prevent agents from moving onto immutables - (world, proposal) => (proposal.agent_changes || []).reduce( - (acc, change) => { - const {x, y} = change; - if (x !== undefined && y !== undefined && world.lattice[y][x].type === 'immutable') { - return false; - } else { - return acc; - } - }, - true, - ), -]; diff --git a/src/simulation/validity.test.js b/src/simulation/validity.test.js deleted file mode 100644 index ba9e684..0000000 --- a/src/simulation/validity.test.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -import { world_update } from '../world/world.js'; -import { validity } from './validity.js'; - -test("agents are not allowed to move into immutables", () => { - const actions = [{ - size: 1, propose: (world, agent, head) => { - return [{ - agent_changes: [{ - agent_id: agent.id, - x: agent.x + 1, y: agent.y, - }], - }]; - }, - }]; - - const agent = { - id: 1, - net: { compute: () => [[1], null] }, - state: null, - x: 0, y: 0, - flags: {}, - }; - - const lattice = [[{ type: 'empty', flags: {} }, { type: 'immutable', flags: {} }]]; - - const world = { - lattice, - lattice_rules: { empty: ()=>{}, immutable: ()=>{} }, - agents: [agent], - senses: [], - actions, - validity, - }; - - expect(world_update(world).agents[0]).toEqual(agent); - world.validity = []; - expect(world_update(world).agents[0]).toEqual({ - ...agent, - x: 1, y: 0, - }); -}); diff --git a/src/ui/canvas.js b/src/ui/canvas.js deleted file mode 100644 index b04b966..0000000 --- a/src/ui/canvas.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -export function draw(canvas, size, fn) { - const ctx = canvas.getContext("2d"); - const scale = canvas.clientWidth/size; - ctx.save(); - ctx.scale(scale, scale); - ctx.clearRect(0, 0, size, size); - fn(ctx); - ctx.restore(); -} diff --git a/src/ui/index.js b/src/ui/index.js deleted file mode 100644 index 01068cb..0000000 --- a/src/ui/index.js +++ /dev/null @@ -1,75 +0,0 @@ -import { draw } from './canvas.js'; -import { create_team, create_epoch, update_epoch } from '../simulation/game.js'; - - -console.log("generating agents..."); -const start_teams = [...Array(4)].map(x => create_team(2, 400, 50)); -console.log("creating epoch..."); -let epoch = create_epoch(60, start_teams); -console.log("ready!"); - - -function draw_cell(ctx, x, y, cell) { - ctx.fillStyle = (() => { - switch (cell.type) { - case 'empty': - return '#ffffff'; - case 'immutable': - return '#0000ff'; - case 'mutable': - return '#555555'; - case 'active': - return '#000000'; - case 'flag': - return '#ffff00'; - default: - return '#00ff00'; - } - })(); - ctx.fillRect(x, y, 1, 1); -} - -function draw_agent(ctx, agent) { - ctx.beginPath(); - ctx.fillStyle = '#ff0000'; - const { x, y } = agent; - ctx.arc(x+.5, y+.5, .5, 0, 2*Math.PI); - ctx.fill(); -} - - -function render(canvas) { - draw(canvas, epoch.size, (ctx) => { - for (let y=0; y<epoch.size; y++) { - for (let x=0; x<epoch.size; x++) { - draw_cell(ctx, x, y, epoch.game.world.lattice[y][x]); - } - } - epoch.game.world.agents.forEach(a => draw_agent(ctx, a)); - }); -} - - -function update(canvas) { - console.log('update'); - epoch = update_epoch(epoch); - render(canvas); - setTimeout(() => update(canvas), 1); -} - - -function main() { - const canvas = document.getElementById('canvas'); - window.onresize = () => { - const size = 0.95*window.innerWidth - canvas.width = size; - canvas.height = size; - render(canvas); - } - window.onresize(); - console.log("c:"); - update(canvas); -} - - -window.onload = main; diff --git a/src/util.js b/src/util.js deleted file mode 100644 index f83039f..0000000 --- a/src/util.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict';
-
-
-export function create(obj, proto=Object.prototype) {
- const props = Object.keys(obj)
- .map((key) => [ key, { value: obj[key], enumerable: true } ])
- .reduce((acc, [ key, value ]) => ({ ...acc, [key]: value }), {});
-
- return Object.create(proto, props);
-};
-
-
-export function random_choice(collection, r=Math.random()) {
- const idx = Math.floor(collection.length * r);
- return collection[idx];
-}
-
-
-export function pairs(arr1, arr2) {
- return arr1
- .map((x, i) => arr2.map(y => [x, y]))
- .flat();
-}
-
-
-export function deepEqual(a, b, debug=false) {
- if (typeof(a) === 'object') {
- if (typeof(b) === 'object') {
- // do deep equality
- return [...new Set(Object.keys(a).concat(Object.keys(b)))].reduce(
- (acc, key) => {
- return acc && deepEqual(a[key], b[key]);
- },
- true
- );
- } else {
- // one object, one non-object
- return false;
- }
- } else {
- return a === b;
- }
-}
-
-
-export function apply(f, n, x0) {
- if (n == 0) {
- return x0;
- } else {
- return f(apply(f, n-1, x0));
- }
-}
-
-
-export function shuffle(arr) {
- const shuffled = [...arr];
- for (let i=arr.length-1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i+1));
- [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]
- }
- return shuffled;
-}
diff --git a/src/world/README.md b/src/world/README.md deleted file mode 100644 index a88eadc..0000000 --- a/src/world/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# world - -The world is composed of a cell lattice. Each cell can contain a specific type of cell, and these cells -evolve according to fixed, regular rules. - -Agents exist on top of the cell lattice. They occupy specific cell locations, but those locations are -otherwise considered empty. Agents exist independent of the cells and evolve independent of them, though of -course they are intertwined. - -Time step process: - - * Agents update internal state and propose moves. - * World updates - * Agent moves are resolved in the new world, and agent status is updated. - * World state is updated to include agents. - -Cell types: -*(all cells, except for Empties and Flags, count as GoL living)* - - * Empty (territory-colored, GoL dead) - * Immutable - * Mutable - * Active (GoL living) - * Flag - -Agent aspects: (64) - * team (4) - * orientation (4) - * mobile/frozen (2) - * has flag (2) - * identity (3-channel) - -Agents have *internal state* corresponding to the state of their internal neurons (and roughly corresponding to their memories and experiences) and *status*, which refers to things like their current orientation, position, and mobility. - - -Agent senses: - * Hearing (inverse square, infinite range) - * Sight (2D top-down, see further ahead than behind or to the sides, 7-channel) - * Match timer - * Epoch timer - -Agent actions: - * Move forward/backward - * Turn left/right - * Place mutable - * Trigger mutable -> active (5x5 square ahead of agent) - * Play frozen - * Unfreeze - * Take flag - * Drop flag (immediately ahead of agent) - - - When carrying a flag, an agent counts as a flag tile (and other agents can steal the flag from them!). Otherwise, an agent counts as an empty tile. diff --git a/src/world/agent.js b/src/world/agent.js deleted file mode 100644 index 2be7420..0000000 --- a/src/world/agent.js +++ /dev/null @@ -1,56 +0,0 @@ -'use strict'; - -import { sense_read } from './sense.js'; -import { proposal_merge } from './proposal.js'; - - -export function agent_decide(world, agent, senses, actions) { - const inputs = senses.map(s => sense_read(world, agent, s)).flat(); - const [result, state] = agent.net.compute(inputs, agent.state); - console.log(result, state); - - const new_agent = { ...agent, state }; - const [proposals, _] = actions.reduce( - ([proposals, result], action) => { - const head = result.slice(0, action.size); - const tail = result.slice(action.size); - - const props = action - .propose(world, new_agent, head) - .reduce( - (acc, proposal) => proposal_merge(acc, proposal), - proposals - ); - - return [props, tail]; - }, - [[], result] - ); - - return [new_agent, proposals]; -} - - -function change_apply(agent, ch) { - const { x, y, flags } = ch; - return { - ...agent, - x: (x || agent.x), y: (y || agent.y), - flags: { ...agent.flags, ...flags }, - }; -} - - -export function agent_apply(agent, proposals) { - return proposals - .filter(p => p.agent_changes) - .reduce( - (acc, p) => p.agent_changes - .filter(ch => ch.agent_id === agent.id) - .reduce( - (acc_, ch) => change_apply(acc_, ch), - acc - ), - agent - ); -} diff --git a/src/world/agent.test.js b/src/world/agent.test.js deleted file mode 100644 index d10a43a..0000000 --- a/src/world/agent.test.js +++ /dev/null @@ -1,49 +0,0 @@ -import { agent_decide, agent_apply } from './agent.js'; - - -test("simple agent decisions", () => { - const lattice = null; - const agent = { - id: 3, - net: { compute: () => [[0, 1], 'state'] }, - state: null, - x: 0, y: 0, - flags: {}, - }; - - const senses = []; - const actions = [ - { size: 1, propose: (world, agent, head) => [{ agent_changes: [{ agent_id: 3, flags: { act1: head[0] } }] }] }, - { size: 1, propose: (world, agent, head) => [{ agent_changes: [{ agent_id: 3, flags: { act2: head[0] } }] }] }, - ]; - - expect(agent_decide(lattice, agent, senses, actions)).toEqual([ - { ...agent, state: 'state' }, - actions.map((a, idx) => a.propose(null, null, [idx])).flat(), - ]); -}); - - -test("apply proposals to agent", () => { - const props = [ - { agent_changes: [{ agent_id: 14, x: 4, y: 3 }] }, - { agent_changes: [{ agent_id: 16, x: 5, y: 3 }] }, - { agent_changes: [{ agent_id: 14, flags: { frozen: true } }] }, - ]; - - const agent = { - id: 14, - net: null, - state: null, - x: 0, y: 0, - flags: { frozen: false, emit: 6 } - }; - - expect(agent_apply(agent, props)).toEqual({ - id: 14, - net: null, - state: null, - x: 4, y: 3, - flags: { frozen: true, emit: 6 } - }); -}); diff --git a/src/world/lattice.js b/src/world/lattice.js deleted file mode 100644 index 066a5ca..0000000 --- a/src/world/lattice.js +++ /dev/null @@ -1,54 +0,0 @@ -'use strict'; - -// get the proposals for cell updates -export function lattice_update(lattice, update_rules) { - return lattice - .map((row, y) => row.map((cell, x) => [x, y, cell.type])) - .flat() - .reduce((acc, [x, y, type]) => [...acc, update_rules[type](lattice, x, y)], []) - .filter(x => x !== undefined) -} - - -// check if, given the current lattice configuration, a proposal is valid -export function lattice_valid(lattice, proposal) { - if (!proposal.world_updates) { return true; } - return proposal.world_updates.reduce( - (acc, update) => { - const valid = - (update.x >= 0 && update.x < lattice[0].length) && - (update.y >= 0 && update.y < lattice.length) && - (lattice[update.y][update.x].type == update.from) - return valid && acc; - }, - true - ); -} - - -// apply a set of proposals, returning the new lattice -export function lattice_apply(lattice, proposals) { - const result = proposals.reduce( - (acc, prop) => { - const change = (prop.world_updates || []).reduce( - (acc_, update) => { - const cell = acc_[update.y][update.x]; - if (update.to) { cell.type = update.to; } - if (update.flags) { - cell.flags = { ...(cell.flags || {}), ...update.flags }; - //cell.flags = cell.flags || {} - //// this is very side-effect-y but i couldn't think of a nicer compatible way of doing it 😔 - //for (let k of Object.keys(update.flags)) { - // cell.flags[k] = update.flags[k]; - //} - } - return acc_ - }, - [...acc] - ); - return change; - }, - [...lattice] - ); - return result; -} diff --git a/src/world/lattice.test.js b/src/world/lattice.test.js deleted file mode 100644 index d1bdd13..0000000 --- a/src/world/lattice.test.js +++ /dev/null @@ -1,111 +0,0 @@ -'use strict'; - -import { lattice_update, lattice_valid, lattice_apply } from './lattice.js'; - - -test("growth update rule", () => { - const lattice = [[ - { type: 'empty', flags: {} }, - { type: 'empty', flags: {} }, - { type: 'plant', flags: {} }, - ]]; - const update_rules = { - plant: () => {}, - empty: (lattice, x, y) => { - if (lattice[y][x+1].type === 'plant') { - return { world_updates: [{ x, y, from: 'empty', to: 'plant' }] }; - } - }, - }; - - expect(lattice_update(lattice, update_rules)).toEqual([ - { world_updates: [{ x: 1, y: 0, from: 'empty', to: 'plant' }] }, - ]); - - lattice[0][1] = { type: 'plant' }; - - expect(lattice_update(lattice, update_rules)).toEqual([ - { world_updates: [{ x: 0, y: 0, from: 'empty', to: 'plant' }] }, - ]); - - lattice[0][0] = { type: 'plant' }; - - expect(lattice_update(lattice, update_rules)).toEqual([]); -}); - - -//test("agents cannot move into non-empty tiles", () => { -// const lattice = [[ {type: 'empty', flags: {}}, {type: 'filled', flags: {}} ]]; -// const bad_prop = [{ agent_updates: [{ agent_id: 14, x: 1, y: 0 }] }]; -// expect(lattice_valid(lattice, bad_prop)).toBe(false); -// const good_prop = [{ agent_updates: [{ agent_id: 14, x: 0, y: 0 }] }]; -// expect(lattice_valid(lattice, bad_prop)).toBe(true); -//}); - - -test("growth update rule applied", () => { - const lattice = [[ - { type: 'empty', flags: {} }, - { type: 'empty', flags: {} }, - { type: 'plant', flags: {} }, - ]]; - expect(lattice_apply(lattice, [{ world_updates:[{ x: 1, y: 0, from: 'empty', to: 'plant' }]}])).toEqual([[ - { type: 'empty', flags: {} }, - { type: 'plant', flags: {} }, - { type: 'plant', flags: {} }, - ]]); - - expect(lattice_apply(lattice, [ - { world_updates: [{ x: 2, y: 0, from: 'plant', to: 'empty' } ]}, - { world_updates: [{ x: 1, y: 0, from: 'empty', to: 'plant' } ]}, - { world_updates: [{ x: 0, y: 0, from: 'empty', to: 'plant' } ]}, - ])).toEqual([[ - { type: 'plant', flags: {} }, - { type: 'plant', flags: {} }, - { type: 'empty', flags: {} }, - ]]); -}); - - -test("check proposals agains lattice for validity", () => { - const lattice = [[ { type: 'empty' }, { type: 'empty' }, { type: 'plant' } ]]; - expect(lattice_valid(lattice, { world_updates: [{ x: -1, y: 0, from: 'blah', to: 'blah' }] })).toBe(false); - expect(lattice_valid(lattice, { world_updates: [{ x: 0, y: 0, from: 'blah', to: 'blah' }] })).toBe(false); - expect(lattice_valid(lattice, { world_updates: [{ x: 0, y: 0, from: 'empty', to: 'blah' }] })).toBe(true); - expect(lattice_valid(lattice, { world_updates: [{ x: 2, y: 0, from: 'empty', to: 'blah' }] })).toBe(false); - expect(lattice_valid(lattice, { world_updates: [{ x: 2, y: 1, from: 'empty', to: 'blah' }] })).toBe(false); -}); - - -// this test is no longer relevant because resetting the cell flags is taken care of by world_update, -// not lattice_apply -// -//test("proposals update cell flags appropriately", () => { -// const lattice = [ -// [ -// { type: 'empty', flags: { step: 1} }, -// { type: 'empty', flags: {} }, -// { type: 'plant', flags: { foo: 'bar' } }, -// ] -// ]; -// -// // flags are reset each time step -// expect(lattice_apply(lattice, [{ world_updates:[{ x: 1, y: 0, from: 'empty', to: 'plant' }]}])).toEqual([[ -// { type: 'empty', flags: {} }, -// { type: 'plant', flags: {} }, -// { type: 'plant', flags: {} }, -// ]]); -// -// // flags are combined when updating -// expect(lattice_apply(lattice, [ -// { world_updates: [{ x: 1, y: 0, flags: { foo: 'bar' } } ]}, -// { world_updates: [{ x: 1, y: 0, from: 'empty', to: 'plant', flags: { baz: 'baz' } } ]}, -// { world_updates: [{ x: 0, y: 0, from: 'empty', to: 'plant', flags: { foo: 'foo' } } ]}, -// { world_updates: [{ x: 0, y: 0, flags: { beep: 'boop' } } ]}, -// ])).toEqual([[ -// { type: 'plant', flags: { foo: 'foo', beep: 'boop' } }, -// { type: 'plant', flags: { foo: 'bar', baz: 'baz' } }, -// { type: 'plant', flags: {} }, -// ]]); -// -//}); diff --git a/src/world/proposal.js b/src/world/proposal.js deleted file mode 100644 index 8baca06..0000000 --- a/src/world/proposal.js +++ /dev/null @@ -1,183 +0,0 @@ -import { pairs, deepEqual } from '../util.js'; - -/* agent structure: - * { - * id: string - * net: network - * state: network_state - * x, y: number - * flags: object - * } - */ - - -/* cell structure: - * { - * type: string - * flags: object - * } - */ - -/* action structure: - * { - * name: string, - * propose: (agent, output) => proposal[] - * } - */ - - -/* proposal structure - * all proposed lattice and agent changes must be included for the proposed action to be valid - * similarly, if any lattice or agent change creates merge conflicts, the proposal cannot be merged - * and must be removed - * { - * lattice_changes: proposal_lattice_change[]? - * agent_changes: proposal_agent_change[]? - * } - */ - -/* proposal_lattice_change - * { - * x, y: number - * from: string - * to: string - * flags: object? - * } - */ - -/* proposal_agent_change - * { - * agent_id: string - * x, y: number? - * flags: object? - * } - */ - - -// check that two flags objects are compatible -// flags are considered compatible if they do not have any common keys with different values -function flags_compatible(a, b) { - if (a === undefined || b === undefined) { return true; } - const keys = [...new Set(Object.keys(a).concat(Object.keys(b)))]; - return keys.reduce( - (acc, key) => { - const eq = (a[key] === undefined || b[key] === undefined) ? true : deepEqual(a[key], b[key]); - return acc && eq; - }, - true - ); -} - - -// return a tuple [conflict, merge] -// conflict is true if the two lattice_changes are incompatible -// merge is true if the two lattice changes are identical -// otherwise they are false -function lattice_change_conflict(a, b) { - if (deepEqual(a, b)) { - // merge - return [false, true]; - } - if ( - a.x === b.x && - a.y === b.y && - (a.to != b.to || !flags_compatible((a.flags || {}), (b.flags || {}))) - ) { - // conflict! - return [true, false]; - } else { - // no conflict c: - return [false, false]; - } -} - - -// returns true as long as x & y are both defined -function pos_exists(a) { - if (a.x !== undefined && a.y !== undefined) { - return true; - } else { - return false; - } -} - - -// check the equality of two objects with (x,y) keys -function pos_equal(a, b) { - if (a.x !== b.x) { return false; } - if (a.y !== b.y) { return false; } - return true; -} - - -// agent changes merge if they are identical -// they conflict if two agents are trying to move to the same tile, or -// if the same agent is trying to move to two different tiles, or if an agent -// is being updated to incompatible flags -// -// -function agent_change_conflict(a, b) { - if (deepEqual(a, b)) { - // identical: merge - return [false, true]; - } else if (a.agent_id === b.agent_id) { - if ( - pos_exists(a) && pos_exists(b) && !pos_equal(a, b) - ) { - // same agent, different positions: conflict - return [true, false]; - } else if (!flags_compatible(a.flags, b.flags)) { - // same agent, incompatible flags: conflict - return [true, false]; - } else { - // no conflict c: - return [false, false]; - } - } else { - // different agents - if (pos_exists(a) && pos_exists(b) && pos_equal(a, b)) { - // different agents, same position: conflict - return [true, false]; - } else { - // no conflict c: - return [false, false]; - } - } -} - - -// combine lattice_change and agent_change conflict/merge tuples for a pair of proposals -function proposal_conflict_merge(a, b) { - const [lattice_conflict, lattice_merge] = pairs(a.lattice_changes || [], b.lattice_changes || []).reduce( - (acc, [a, b]) => { - const [conflict, merge] = lattice_change_conflict(a, b); - return [acc[0] || conflict, acc[1] || merge]; - }, - [false, false] - ); - const [agent_conflict, agent_merge] = pairs(a.agent_changes || [], b.agent_changes || []).reduce( - (acc, [a, b]) => { - const [conflict, merge] = agent_change_conflict(a, b); - return [acc[0] || conflict, acc[1] || merge]; - }, - [false, false] - ); - return [lattice_conflict || agent_conflict, lattice_merge || agent_merge]; -} - - -// merge proposals -// if two sub-updates conflict, they are both omitted from the final merged proposal -export function proposal_merge(arr, proposal) { - const conflict_merge = arr.map(x => proposal_conflict_merge(x, proposal)); - - // if any conflicts are detected then don't merge - if (conflict_merge.reduce((acc, [c, m]) => acc || c, false)) { - const conflict_free = arr.filter((x, i) => !conflict_merge[i][0]); - return conflict_free; - } else { - // no conflicts, but need to merge identical actions - const no_merge = arr.filter((x, i) => !conflict_merge[i][1]); - return [...no_merge, proposal]; - } -} diff --git a/src/world/proposal.test.js b/src/world/proposal.test.js deleted file mode 100644 index 2f870e6..0000000 --- a/src/world/proposal.test.js +++ /dev/null @@ -1,197 +0,0 @@ -import { - proposal_merge, -} from './proposal.js'; - - -// tile updates - -test("proposals changing different tiles don't conflict", () => { - const a = { - lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable' }], - }; - const b = { - lattice_changes: [{ x: 4, y: 4, from: 'empty', to: 'flag' }], - }; - - expect(proposal_merge([a], b)).toEqual([a, b]); -}); - - -test("proposals changing the same tile to different states conflict", () => { - const a = { - lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable' }], - }; - const b = { - lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'flag' }], - }; - - expect(proposal_merge([a], b)).toEqual([]); -}); - - -test("proposals changing the same tile to the same state merge to a single proposal", () => { - const a = { - lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable' }], - }; - const b = { - lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable' }], - }; - - expect(proposal_merge([a], b)).toEqual([a]); -}); - - -test("proposals with identical tile updates but incompatible tile flags conflict", () => { - const a = { - lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable', flags: { v: 'a' } }], - }; - const b = { - lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable', flags: { v: 'b' } }], - }; - - expect(proposal_merge([a], b)).toEqual([]); -}); - - -test("proposals with identical tile updates but compatible tile flags do not conflict", () => { - const a = { - lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable', flags: { v: 'a', r: 'd' } }], - }; - const b = { - lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable', flags: { v: 'a', u: 'b' } }], - }; - - expect(proposal_merge([a], b)).toEqual([a, b]); -}); - - - - -test("proposals moving two agents to the same tile conflict", () => { - const a = { - agent_changes: [{ agent_id: 'aaa', x: 4, y: 3 }], - }; - const b = { - agent_changes: [{ agent_id: 'bbb', x: 4, y: 3 }], - }; - - expect(proposal_merge([a], b)).toEqual([]); -}); - - -// agent updates -test("proposals moving two agents to different tiles do not conflict", () => { - const a = { - agent_changes: [{ agent_id: 'aaa', x: 4, y: 3 }], - }; - const b = { - agent_changes: [{ agent_id: 'bbb', x: 3, y: 3 }], - }; - - expect(proposal_merge([a], b)).toEqual([a, b]); -}); - - -test("proposals moving the same agent to different tiles conflict", () => { - const a = { - agent_changes: [{ agent_id: 'aaa', x: 4, y: 3 }], - }; - const b = { - agent_changes: [{ agent_id: 'aaa', x: 3, y: 3 }], - }; - - expect(proposal_merge([a], b)).toEqual([]); -}); - - -test("proposals moving the same agent to the same tile merge", () => { - const a = { - agent_changes: [{ agent_id: 'aaa', x: 4, y: 3 }], - }; - const b = { - agent_changes: [{ agent_id: 'aaa', x: 4, y: 3 }], - }; - - expect(proposal_merge([a], b)).toEqual([a]); -}); - - -test("proposals setting flags on different agents do not conflict", () => { - const a = { - agent_changes: [{ agent_id: 'aaa', flags: { frozen: false } }], - }; - - const b = { - agent_changes: [{ agent_id: 'bbb', flags: { frozen: false } }], - }; - - expect(proposal_merge([a], b)).toEqual([a, b]); -}); - - -test("setting the same agent to compatible flags does not conflict", () => { - const a = { - agent_changes: [{ agent_id: 'aaa', flags: { frozen: false } }], - }; - - const b = { - agent_changes: [{ agent_id: 'aaa', flags: { crumpet: 'hi' } }], - }; - - expect(proposal_merge([a], b)).toEqual([a, b]); -}); - - -test("setting the same agent to compatible object flags does not conflict", () => { - const a = { - agent_changes: [{ agent_id: 'aaa', flags: { emit: [0, 1, 1, 0] } }], - }; - - const b = { - agent_changes: [{ agent_id: 'aaa', flags: { emit: [0, 1, 1, 0], hi: 4 } }], - }; - - expect(proposal_merge([a], b)).toEqual([a, b]); -}); - - -test("setting the same agent to incompatible flags does conflict", () => { - const a = { - agent_changes: [{ agent_id: 'aaa', flags: { frozen: false } }], - }; - - const b = { - agent_changes: [{ agent_id: 'aaa', flags: { frozen: true, crumpet: 'hi' } }], - }; - - expect(proposal_merge([a], b)).toEqual([]); -}); - - -test("setting the same agent to incompatible object flags does conflict", () => { - const a = { - agent_changes: [{ agent_id: 'aaa', flags: { emit: [0, 1, 1, 0] } }], - }; - - const b = { - agent_changes: [{ agent_id: 'aaa', flags: { emit: [0, 1, 1, 1], hi: 4 } }], - }; - - expect(proposal_merge([a], b)).toEqual([]); -}); - - -test("setting the same agent to identical flags merges", () => { - const a = { - agent_changes: [{ agent_id: 'aaa', flags: { emit: [0, 1, 1, 0] } }], - }; - - const b = { - agent_changes: [{ agent_id: 'aaa', flags: { emit: [0, 1, 1, 0] } }], - }; - - expect(proposal_merge([a], b)).toEqual([a]); -}); - - - diff --git a/src/world/sense.js b/src/world/sense.js deleted file mode 100644 index 9b5c7d4..0000000 --- a/src/world/sense.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -/* sense structure: - * { - * size: number - * read: function(lattice, agent) -> number[size] - * } - */ - - -export function sense_read(world, agent, sense) { - const result = sense.read(world, agent); - if (result.length !== sense.size) { - throw new Error(`Expected result of size ${sense.size}, but got ${result.length} instead.`); - } - return result; -} diff --git a/src/world/sense.test.js b/src/world/sense.test.js deleted file mode 100644 index 27ee2b5..0000000 --- a/src/world/sense.test.js +++ /dev/null @@ -1,33 +0,0 @@ -import { sense_read } from './sense.js'; - - -test("basic sense works", () => { - const flag_sense = { - size: 1, - read: (world, agent) => { - const {x, y} = agent; - return [ world.lattice[y-1][x].type === 'flag' ? 1.0 : 0.0 ] - }, - }; - - const lattice = [[ { type: 'flag' } ]]; - const agent = { x: 0, y: 1 }; - - expect(sense_read({lattice}, agent, flag_sense)).toEqual([1.0]); -}); - - -test("senses throw if the size is incorrect", () => { - const flag_sense = { - size: 2, - read: (world, agent) => { - const {x, y} = agent; - return [ world.lattice[y-1][x].type === 'flag' ? 1.0 : 0.0 ] - }, - } - - const lattice = [[ { type: 'flag' } ]]; - const agent = { x: 0, y: 1 }; - - expect(() => sense_read({lattice}, agent, flag_sense)).toThrow(); -}); diff --git a/src/world/world.js b/src/world/world.js deleted file mode 100644 index 63d974a..0000000 --- a/src/world/world.js +++ /dev/null @@ -1,47 +0,0 @@ -import { lattice_update, lattice_valid, lattice_apply } from './lattice.js'; -import { agent_decide, agent_apply } from './agent.js'; -import { proposal_merge } from './proposal.js'; - - -// world structure: -// { -// lattice -// lattice_rules: object -// agents: agent[] -// senses: sense[] -// actions: action[] -// validity: (function(proposal) => bool)[] -// } - - -export function world_update(world, postprocess=[]) { - const lattice_props = lattice_update(world.lattice, world.lattice_rules); - const intermediate_lattice = lattice_apply( - world.lattice.map(row => row.map(cell => ({ ...cell, flags: {} }))), - lattice_props - ); - - const decisions = world.agents - .map(a => agent_decide(world, a, world.senses, world.actions)) - .reduce( - ([agents, props], [agent, prop]) => [[...agents, agent], [...props, prop]], - [[], []] - ); - const intermediate_agents = decisions[0]; - const agent_props = world.validity.reduce( - (acc, rule) => acc.filter(prop => rule({...world, lattice: intermediate_lattice}, prop)), - decisions[1] - .flat() - .reduce((acc, prop) => proposal_merge(acc, prop), []) - .filter(prop => lattice_valid(intermediate_lattice, prop)) - ); - - const lattice = lattice_apply(intermediate_lattice, agent_props); - const agents = intermediate_agents.map(a => agent_apply(a, agent_props)); - - const new_world = {...world, lattice, agents}; - return postprocess.reduce( - (acc, f) => f(acc), - new_world - ); -} |