import { pairs, deepEqual } 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 * flags: object? * } */ /* proposal_agent_change * { * agent_id: string * x, y: number? * flags: object? * } */ // 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]; } 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 { // no conflict c: return [false, false]; } } 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] ); const [agent_conflict, agent_merge] = pairs(a.agent_changes || [], b.agent_changes || []).reduce( (acc, [a, b]) => { const [conflict, merge] = agent_change_conflict(a, b); return [acc[0] || conflict, acc[1] || merge]; }, [false, false] ); return [world_conflict || agent_conflict, world_merge || agent_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]; } }