diff options
author | sanine <sanine.not@pm.me> | 2023-11-09 16:46:33 -0600 |
---|---|---|
committer | sanine <sanine.not@pm.me> | 2023-11-09 16:46:33 -0600 |
commit | 55b714abf83e01aa0ff513ad6ba4978f4b4da6cd (patch) | |
tree | 71518c2a6546681724f095424b755c2ab1ec8ecd | |
parent | 819d8a51c5ba8b1bec06163dba9c3e0212f1120a (diff) |
add basic game of life lattice rules
-rw-r--r-- | src/simulation/lattice_rules.js | 53 | ||||
-rw-r--r-- | src/simulation/lattice_rules.test.js | 25 | ||||
-rw-r--r-- | src/world/lattice.test.js | 9 | ||||
-rw-r--r-- | src/world/world.js | 19 |
4 files changed, 102 insertions, 4 deletions
diff --git a/src/simulation/lattice_rules.js b/src/simulation/lattice_rules.js new file mode 100644 index 0000000..0f46d0d --- /dev/null +++ b/src/simulation/lattice_rules.js @@ -0,0 +1,53 @@ +import { pairs } from '../util.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' }] }; + } + }, + + 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; + if (num_active_neighbors < 2) { + return { world_updates: [{ x, y, from: 'active', to: 'empty' }] }; + } else if (num_active_neighbors > 3) { + return { world_updates: [{ x, y, from: 'active', to: 'empty' }] }; + } + }, + + mutable: () => {}, + immutable: () => {}, + flag: () => {}, + +}; diff --git a/src/simulation/lattice_rules.test.js b/src/simulation/lattice_rules.test.js new file mode 100644 index 0000000..a91400e --- /dev/null +++ b/src/simulation/lattice_rules.test.js @@ -0,0 +1,25 @@ +import { world_update } from '../world/world.js'; +import { lattice_rules } from './lattice_rules.js'; + + +test("blinker", () => { + const L = { type: 'active', flags: {} }; + const D = { type: 'empty', flags: {} }; + const lattice = [ + [ D, D, D, D, D ], + [ D, D, D, D, D ], + [ D, L, L, L, D ], + [ D, D, D, D, D ], + [ D, D, D, D, D ], + ]; + + const world = { lattice, lattice_rules, agents: [], senses: [], actions: [] }; + expect(world_update(world).lattice).toEqual([ + [ D, D, D, D, D ], + [ D, D, L, D, D ], + [ D, D, L, D, D ], + [ D, D, L, D, D ], + [ D, D, D, D, D ], + ]); + expect(world_update(world_update(world)).lattice).toEqual(lattice); +}); diff --git a/src/world/lattice.test.js b/src/world/lattice.test.js index 7c71d04..c2fdb6b 100644 --- a/src/world/lattice.test.js +++ b/src/world/lattice.test.js @@ -34,6 +34,15 @@ test("growth update rule", () => { }); +//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: {} }, diff --git a/src/world/world.js b/src/world/world.js index c824e91..87d0aed 100644 --- a/src/world/world.js +++ b/src/world/world.js @@ -1,19 +1,30 @@ +import { lattice_update, lattice_valid, lattice_apply } from './lattice.js'; +import { agent_decide, agent_apply } from './agent.js'; +import { proposal_merge } from './proposal.js'; + + export function world_update(world, postprocess=[]) { const lattice_props = lattice_update(world.lattice, world.lattice_rules); const intermediate_lattice = lattice_apply(world.lattice, lattice_props); - const agent_props = world.agents + const decisions = world.agents .map(a => agent_decide(world.lattice, agent, world.senses, world.actions)) + .reduce( + ([agents, props], [agent, prop]) => [[...agents, agent], [...props, prop]], + [[], []] + ); + const intermediate_agents = decisions[0]; + const agent_props = 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 = world.agents.map(a => agent_apply(a, agent_props)); + const agents = intermediate_agents.map(a => agent_apply(a, agent_props)); - const world = {...world, lattice, agents}; + const new_world = {...world, lattice, agents}; return postprocess.reduce( (acc, f) => f(acc), - world + new_world ); } |