diff options
author | sanine <sanine.not@pm.me> | 2023-11-08 00:47:06 -0600 |
---|---|---|
committer | sanine <sanine.not@pm.me> | 2023-11-08 00:47:06 -0600 |
commit | f78493e192daf4639b91352909e4029b9970fcdb (patch) | |
tree | 5b43952cc5da05ffcc942d079f0c3b60674060be | |
parent | 3ae8f476f3865982ca69d0e39b05b5aa8ec43694 (diff) |
implement basic sensing & refactor world/cells -> lattice
-rw-r--r-- | src/world/lattice.js (renamed from src/world/cell.js) | 22 | ||||
-rw-r--r-- | src/world/lattice.test.js (renamed from src/world/cell.test.js) | 44 | ||||
-rw-r--r-- | src/world/proposal.js | 22 | ||||
-rw-r--r-- | src/world/proposal.test.js | 20 | ||||
-rw-r--r-- | src/world/sense.js | 17 | ||||
-rw-r--r-- | src/world/sense.test.js | 33 |
6 files changed, 104 insertions, 54 deletions
diff --git a/src/world/cell.js b/src/world/lattice.js index 8795623..243a47d 100644 --- a/src/world/cell.js +++ b/src/world/lattice.js @@ -1,24 +1,24 @@ 'use strict'; // get the proposals for cell updates -export function cells_update(cells, update_rules) { - return cells +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](cells, x, y)], []) + .reduce((acc, [x, y, type]) => [...acc, update_rules[type](lattice, x, y)], []) .filter(x => x !== undefined) } -// check if, given the current cells configuration, a proposal is valid -export function cells_valid(cells, proposal) { +// 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 < cells[0].length) && - (update.y >= 0 && update.y < cells.length) && - (cells[update.y][update.x].type == update.from) + (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 @@ -26,8 +26,8 @@ export function cells_valid(cells, proposal) { } -// apply a set of proposals, returning the new cells -export function cells_apply(cells, proposals) { +// apply a set of proposals, returning the new lattice +export function lattice_apply(lattice, proposals) { return proposals.reduce( (acc, prop) => { const change = (prop.world_updates || []).reduce( @@ -47,6 +47,6 @@ export function cells_apply(cells, proposals) { ); return change; }, - [...cells].map(row => row.map(cell => ({ ...cell, flags: {}, }))) + [...lattice].map(row => row.map(cell => ({ ...cell, flags: {}, }))) ); } diff --git a/src/world/cell.test.js b/src/world/lattice.test.js index fb901eb..7c71d04 100644 --- a/src/world/cell.test.js +++ b/src/world/lattice.test.js @@ -1,52 +1,52 @@ 'use strict'; -import { cells_update, cells_valid, cells_apply } from './cell.js'; +import { lattice_update, lattice_valid, lattice_apply } from './lattice.js'; test("growth update rule", () => { - const cells = [[ + const lattice = [[ { type: 'empty', flags: {} }, { type: 'empty', flags: {} }, { type: 'plant', flags: {} }, ]]; const update_rules = { plant: () => {}, - empty: (cells, x, y) => { - if (cells[y][x+1].type === 'plant') { + empty: (lattice, x, y) => { + if (lattice[y][x+1].type === 'plant') { return { world_updates: [{ x, y, from: 'empty', to: 'plant' }] }; } }, }; - expect(cells_update(cells, update_rules)).toEqual([ + expect(lattice_update(lattice, update_rules)).toEqual([ { world_updates: [{ x: 1, y: 0, from: 'empty', to: 'plant' }] }, ]); - cells[0][1] = { type: 'plant' }; + lattice[0][1] = { type: 'plant' }; - expect(cells_update(cells, update_rules)).toEqual([ + expect(lattice_update(lattice, update_rules)).toEqual([ { world_updates: [{ x: 0, y: 0, from: 'empty', to: 'plant' }] }, ]); - cells[0][0] = { type: 'plant' }; + lattice[0][0] = { type: 'plant' }; - expect(cells_update(cells, update_rules)).toEqual([]); + expect(lattice_update(lattice, update_rules)).toEqual([]); }); test("growth update rule applied", () => { - const cells = [[ + const lattice = [[ { type: 'empty', flags: {} }, { type: 'empty', flags: {} }, { type: 'plant', flags: {} }, ]]; - expect(cells_apply(cells, [{ world_updates:[{ x: 1, y: 0, from: 'empty', to: 'plant' }]}])).toEqual([[ + 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(cells_apply(cells, [ + 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' } ]}, @@ -58,18 +58,18 @@ test("growth update rule applied", () => { }); -test("check proposals agains cells for validity", () => { - const cells = [[ { type: 'empty' }, { type: 'empty' }, { type: 'plant' } ]]; - expect(cells_valid(cells, { world_updates: [{ x: -1, y: 0, from: 'blah', to: 'blah' }] })).toBe(false); - expect(cells_valid(cells, { world_updates: [{ x: 0, y: 0, from: 'blah', to: 'blah' }] })).toBe(false); - expect(cells_valid(cells, { world_updates: [{ x: 0, y: 0, from: 'empty', to: 'blah' }] })).toBe(true); - expect(cells_valid(cells, { world_updates: [{ x: 2, y: 0, from: 'empty', to: 'blah' }] })).toBe(false); - expect(cells_valid(cells, { world_updates: [{ x: 2, y: 1, from: 'empty', to: 'blah' }] })).toBe(false); +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); }); test("proposals update cell flags appropriately", () => { - const cells = [ + const lattice = [ [ { type: 'empty', flags: { step: 1} }, { type: 'empty', flags: {} }, @@ -78,14 +78,14 @@ test("proposals update cell flags appropriately", () => { ]; // flags are reset each time step - expect(cells_apply(cells, [{ world_updates:[{ x: 1, y: 0, from: 'empty', to: 'plant' }]}])).toEqual([[ + 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(cells_apply(cells, [ + 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' } } ]}, diff --git a/src/world/proposal.js b/src/world/proposal.js index 775466c..9f98fd4 100644 --- a/src/world/proposal.js +++ b/src/world/proposal.js @@ -27,16 +27,16 @@ import { pairs, deepEqual } from '../util.js'; /* proposal structure - * all proposed world and agent changes must be included for the proposed action to be valid - * similarly, if any world or agent change creates merge conflicts, the proposal cannot be merged + * 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 * { - * world_changes: proposal_world_change[]? + * lattice_changes: proposal_lattice_change[]? * agent_changes: proposal_agent_change[]? * } */ -/* proposal_world_change +/* proposal_lattice_change * { * x, y: number * from: string @@ -69,10 +69,10 @@ function flags_compatible(a, b) { // return a tuple [conflict, merge] -// conflict is true if the two world_changes are incompatible -// merge is true if the two world changes are identical +// 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 world_change_conflict(a, b) { +function lattice_change_conflict(a, b) { if (deepEqual(a, b)) { // merge return [false, true]; @@ -145,11 +145,11 @@ function agent_change_conflict(a, b) { } -// combine world_change and agent_change conflict/merge tuples for a pair of proposals +// combine lattice_change and agent_change conflict/merge tuples for a pair of proposals function proposal_conflict_merge(a, b) { - const [world_conflict, world_merge] = pairs(a.world_changes || [], b.world_changes || []).reduce( + const [lattice_conflict, lattice_merge] = pairs(a.lattice_changes || [], b.lattice_changes || []).reduce( (acc, [a, b]) => { - const [conflict, merge] = world_change_conflict(a, b); + const [conflict, merge] = lattice_change_conflict(a, b); return [acc[0] || conflict, acc[1] || merge]; }, [false, false] @@ -161,7 +161,7 @@ function proposal_conflict_merge(a, b) { }, [false, false] ); - return [world_conflict || agent_conflict, world_merge || agent_merge]; + return [lattice_conflict || agent_conflict, lattice_merge || agent_merge]; } diff --git a/src/world/proposal.test.js b/src/world/proposal.test.js index a1e1203..2f870e6 100644 --- a/src/world/proposal.test.js +++ b/src/world/proposal.test.js @@ -7,10 +7,10 @@ import { test("proposals changing different tiles don't conflict", () => { const a = { - world_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable' }], + lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable' }], }; const b = { - world_changes: [{ x: 4, y: 4, from: 'empty', to: 'flag' }], + lattice_changes: [{ x: 4, y: 4, from: 'empty', to: 'flag' }], }; expect(proposal_merge([a], b)).toEqual([a, b]); @@ -19,10 +19,10 @@ test("proposals changing different tiles don't conflict", () => { test("proposals changing the same tile to different states conflict", () => { const a = { - world_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable' }], + lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable' }], }; const b = { - world_changes: [{ x: 4, y: 3, from: 'empty', to: 'flag' }], + lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'flag' }], }; expect(proposal_merge([a], b)).toEqual([]); @@ -31,10 +31,10 @@ test("proposals changing the same tile to different states conflict", () => { test("proposals changing the same tile to the same state merge to a single proposal", () => { const a = { - world_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable' }], + lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable' }], }; const b = { - world_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable' }], + lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable' }], }; expect(proposal_merge([a], b)).toEqual([a]); @@ -43,10 +43,10 @@ test("proposals changing the same tile to the same state merge to a single propo test("proposals with identical tile updates but incompatible tile flags conflict", () => { const a = { - world_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable', flags: { v: 'a' } }], + lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable', flags: { v: 'a' } }], }; const b = { - world_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable', flags: { v: 'b' } }], + lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable', flags: { v: 'b' } }], }; expect(proposal_merge([a], b)).toEqual([]); @@ -55,10 +55,10 @@ test("proposals with identical tile updates but incompatible tile flags conflict test("proposals with identical tile updates but compatible tile flags do not conflict", () => { const a = { - world_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable', flags: { v: 'a', r: 'd' } }], + lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable', flags: { v: 'a', r: 'd' } }], }; const b = { - world_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable', flags: { v: 'a', u: 'b' } }], + lattice_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable', flags: { v: 'a', u: 'b' } }], }; expect(proposal_merge([a], b)).toEqual([a, b]); diff --git a/src/world/sense.js b/src/world/sense.js new file mode 100644 index 0000000..47329b7 --- /dev/null +++ b/src/world/sense.js @@ -0,0 +1,17 @@ +'use strict'; + +/* sense structure: + * { + * size: number + * read: function(lattice, agent) -> number[size] + * } + */ + + +export function sense_read(lattice, agent, sense) { + const result = sense.read(lattice, agent); + if (result.length !== sense.size) { + throw new Error(`Expected result of size ${sense.size}, but got ${result.length} instead.`); + } + return sense.read(lattice, agent); +} diff --git a/src/world/sense.test.js b/src/world/sense.test.js new file mode 100644 index 0000000..1ef7bce --- /dev/null +++ b/src/world/sense.test.js @@ -0,0 +1,33 @@ +import { sense_read } from './sense.js'; + + +test("basic sense works", () => { + const flag_sense = { + size: 1, + read: (lattice, agent) => { + const {x, y} = agent; + return [ 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: (lattice, agent) => { + const {x, y} = agent; + return [ 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(); +}); |