diff options
Diffstat (limited to 'src/world')
-rw-r--r-- | src/world/README.md | 53 | ||||
-rw-r--r-- | src/world/agent.js | 56 | ||||
-rw-r--r-- | src/world/agent.test.js | 49 | ||||
-rw-r--r-- | src/world/lattice.js | 54 | ||||
-rw-r--r-- | src/world/lattice.test.js | 111 | ||||
-rw-r--r-- | src/world/proposal.js | 183 | ||||
-rw-r--r-- | src/world/proposal.test.js | 197 | ||||
-rw-r--r-- | src/world/sense.js | 17 | ||||
-rw-r--r-- | src/world/sense.test.js | 33 | ||||
-rw-r--r-- | src/world/world.js | 47 |
10 files changed, 0 insertions, 800 deletions
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 - ); -} |