From 078a01a078dae15b5e8e3482b57f156ecd0c7fae Mon Sep 17 00:00:00 2001 From: sanine Date: Mon, 6 Nov 2023 00:34:06 -0600 Subject: implement flag conflicts --- src/world/agent.js | 91 ++++++++++++++++++++++++++++++++++++++----------- src/world/agent.test.js | 68 ++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/world/agent.js b/src/world/agent.js index f7e5a63..95d869c 100644 --- a/src/world/agent.js +++ b/src/world/agent.js @@ -46,39 +46,90 @@ import { pairs, deepEqual } from '../util.js'; */ +function flags_compatible(a, b) { + 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 world_changes are incompatible // merge is true if the two world changes are identical // otherwise they are false 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]; + 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; + } +} + + +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) && - ((a.x != b.x) || (a.y != b.y)) - ) { - // same agent, different tiles: conflict - return [true, false]; - } else if ( - (a.agent_id != b.agent_id) && - (a.x === b.x) && - (a.y === b.y) - ) { - // different agents, same tile: conflict - return [true, false]; + } 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 { - // no conflict c: - return [false, false]; + // 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]; + } } } diff --git a/src/world/agent.test.js b/src/world/agent.test.js index 2a86908..fe73b25 100644 --- a/src/world/agent.test.js +++ b/src/world/agent.test.js @@ -5,6 +5,8 @@ import { // --===== proposal conflicts =====-- +// tile updates + test("proposals changing different tiles don't conflict", () => { const a = { world_changes: [{ x: 4, y: 3, from: 'empty', to: 'mutable' }], @@ -41,6 +43,32 @@ 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' } }], + }; + const b = { + world_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 = { + world_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' } }], + }; + + 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 }], @@ -53,6 +81,7 @@ test("proposals moving two agents to the same tile conflict", () => { }); +// agent updates test("proposals moving two agents to different tiles do not conflict", () => { const a = { agent_changes: [{ agent_id: 'aaa', x: 4, y: 3 }], @@ -87,3 +116,42 @@ test("proposals moving the same agent to the same tile merge", () => { 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 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([]); +}); -- cgit v1.2.1