summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2023-11-08 00:47:06 -0600
committersanine <sanine.not@pm.me>2023-11-08 00:47:06 -0600
commitf78493e192daf4639b91352909e4029b9970fcdb (patch)
tree5b43952cc5da05ffcc942d079f0c3b60674060be
parent3ae8f476f3865982ca69d0e39b05b5aa8ec43694 (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.js22
-rw-r--r--src/world/proposal.test.js20
-rw-r--r--src/world/sense.js17
-rw-r--r--src/world/sense.test.js33
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();
+});