diff options
author | sanine <sanine.not@pm.me> | 2023-11-05 22:35:03 -0600 |
---|---|---|
committer | sanine <sanine.not@pm.me> | 2023-11-05 22:35:03 -0600 |
commit | 639a559dd44d924c6b2e545c7d2eb28b11d4a314 (patch) | |
tree | b204025fca8c4eb695ae49dc98a6f912bbc9b4b5 | |
parent | 19d89c43e5552f16080240ebf739cce142515c94 (diff) |
begin implementing agent proposals
-rw-r--r-- | src/util.js | 7 | ||||
-rw-r--r-- | src/world/agent.js | 83 | ||||
-rw-r--r-- | src/world/agent.test.js | 44 |
3 files changed, 134 insertions, 0 deletions
diff --git a/src/util.js b/src/util.js index 4e23d9d..8b85ba7 100644 --- a/src/util.js +++ b/src/util.js @@ -14,3 +14,10 @@ export function random_choice(collection, r) { const idx = Math.floor(collection.length * r);
return collection[idx];
}
+
+
+export function pairs(arr1, arr2) {
+ return arr1
+ .map((x, i) => arr2.map(y => [x, y]))
+ .flat();
+}
diff --git a/src/world/agent.js b/src/world/agent.js new file mode 100644 index 0000000..7ab501c --- /dev/null +++ b/src/world/agent.js @@ -0,0 +1,83 @@ +import { pairs } from '../util.js'; + +/* agent structure: + * { + * id: string + * net: network + * state: network_state + * x, y: number + * flags: object + * } + */ + +/* action structure: + * { + * name: string, + * propose: (agent) => proposal[] + * } + */ + + +/* 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 + * and must be removed + * { + * world_changes: proposal_world_change[]? + * agent_changes: proposal_agent_change[]? + * } + */ + +/* proposal_world_change + * { + * x, y: number + * from: string + * to: string + * } + */ + +/* proposal_agent_change + * { + * agent_id: string + * x, y: number? + * flags: object? + * } + */ + + +function world_change_conflict(a, b) { + if (a.x != b.x) { return [false, false]; } + if (a.y != b.y) { return [false, false]; } + if (a.to != b.to) { return [true, false]; } + // x, y, and to all match -- merge + return [false, true]; +} + + +function proposal_conflict_merge(a, b) { + const [world_conflict, world_merge] = pairs(a.world_changes, b.world_changes).reduce( + (acc, [a, b]) => { + const [conflict, merge] = world_change_conflict(a, b); + return [acc[0] || conflict, acc[1] || merge]; + }, + [false, false] + ) + return [world_conflict, world_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/agent.test.js b/src/world/agent.test.js new file mode 100644 index 0000000..ea64fcb --- /dev/null +++ b/src/world/agent.test.js @@ -0,0 +1,44 @@ +import { + proposal_merge, +} from './agent.js'; + + +// --===== proposal conflicts =====-- + +test("proposals changing different tiles don't conflict", () => { + const a = { + world_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable' }], + }; + + const b = { + world_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 = { + world_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable' }], + }; + + const b = { + world_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 = { + world_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable' }], + }; + + const b = { + world_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable' }], + }; + + expect(proposal_merge([a], b)).toEqual([a]); +}); |