From 5f2683c0aff63880794b2f3262e0d6abc76bd80a Mon Sep 17 00:00:00 2001
From: sanine <sanine.not@pm.me>
Date: Wed, 8 Nov 2023 23:09:41 -0600
Subject: export extra topology functions & add agent.js

---
 src/mind/topology.js    |  4 ++--
 src/world/agent.js      | 55 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/world/agent.test.js | 49 +++++++++++++++++++++++++++++++++++++++++++
 src/world/proposal.js   |  2 +-
 4 files changed, 107 insertions(+), 3 deletions(-)
 create mode 100644 src/world/agent.js
 create mode 100644 src/world/agent.test.js

(limited to 'src')

diff --git a/src/mind/topology.js b/src/mind/topology.js
index 127351e..9c5ecf7 100644
--- a/src/mind/topology.js
+++ b/src/mind/topology.js
@@ -46,7 +46,7 @@ function is_hidden(n, index) {
 
 // returns a new network with an edge between the given nodes
 // with the given weight
-function network_connect(n, source, sink, 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");
@@ -146,7 +146,7 @@ function get_value(n, index, input, prev, cache) {
 
 // compute a network's output and new hidden state
 // given the input and previous hidden state
-function network_compute(n, input, state) {
+export function network_compute(n, input, state) {
 	// validate input
 	if (input.length !== n.input_count) {
 		throw new Error("incorrect number of input elements");
diff --git a/src/world/agent.js b/src/world/agent.js
new file mode 100644
index 0000000..d91481b
--- /dev/null
+++ b/src/world/agent.js
@@ -0,0 +1,55 @@
+'use strict';
+
+import { sense_read } from './sense.js';
+import { proposal_merge } from './proposal.js';
+
+
+export function agent_decide(lattice, agent, senses, actions) {
+  const inputs = senses.map(s => sense_read(lattice, agent, s)).flat();
+  const [result, state] = agent.net.compute(inputs, agent.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(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
new file mode 100644
index 0000000..5d5828f
--- /dev/null
+++ b/src/world/agent.test.js
@@ -0,0 +1,49 @@
+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: (agent, head) => [{ agent_changes: [{ agent_id: 3, flags: { act1: head[0] } }] }] },
+    { size: 1, propose: (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, [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/proposal.js b/src/world/proposal.js
index 9f98fd4..0969540 100644
--- a/src/world/proposal.js
+++ b/src/world/proposal.js
@@ -21,7 +21,7 @@ import { pairs, deepEqual } from '../util.js';
 /* action structure:
  * {
  *   name: string,
- *   propose: (agent) => proposal[]
+ *   propose: (agent, output) => proposal[]
  * }
  */
 
-- 
cgit v1.2.1