diff options
| author | sanine <sanine.not@pm.me> | 2023-11-16 14:50:00 -0600 | 
|---|---|---|
| committer | sanine <sanine.not@pm.me> | 2023-11-16 14:50:00 -0600 | 
| commit | f9fc4d26ec5fca9ee175c8a6fbcdd0fa36f10947 (patch) | |
| tree | f190e6e465bb563c608a916f41fc8bf686ea2897 /src | |
| parent | 7825b92ce3be95a0ce1dfea9388adbaadce83b1f (diff) | |
clear out js files
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 -  ); -}  | 
