'use strict'; const frozen = { size: 1, read: (world, agent) => { if (agent.flags.frozen === true) { return [ 1 ]; } else { return [ 0 ]; } }, }; // add two arrays together element-wise with a scaling factor function array_scalesum(a, s, b) { return a.map((x, i) => x + (s*b[i])); } // determine the square of the distance between two cells function lattice_dist2(x0, y0, x1, y1) { if (x0 === x1 && y0 === y1) { return 1; } // not proper distance but avoids divide-by-zero errors c: return ((x0-x1)**2) + ((y0-y1)**2); } const hear = { size: 8, read: (world, agent) => { const {x, y} = agent; const lattice_sounds = world.lattice .map((row, cy) => row.map((cell, cx) => [ 1/lattice_dist2(x, y, cx, cy), cell ])) .flat() .filter(([scale, cell]) => cell.flags.emit !== undefined) .reduce( (acc, [scale, cell]) => array_scalesum(acc, scale, cell.flags.emit), [0, 0, 0, 0, 0, 0, 0, 0] ); const agent_sounds = world.agents .filter(a => a.flags.emit !== undefined) .reduce( (acc, a) => array_scalesum(acc, 1/lattice_dist2(x, y, a.x, a.y), a.flags.emit), [0, 0, 0, 0, 0, 0, 0, 0] ); return array_scalesum(lattice_sounds, 1, agent_sounds).map(ch => Math.tanh(ch)); }, }; const [VIS_WIDTH, VIS_HEIGHT] = [31, 31]; const [VIS_HWIDTH, VIS_HHEIGHT] = [Math.floor(VIS_WIDTH/2), Math.floor(VIS_HEIGHT/2)]; function identity_mod(n, p) { const mod = (n % (2*p)) - p + 1; return mod/p; } function world_pos_to_vision_pos(world, agent, x, y) { const dx = x - agent.x; const dy = y - agent.y; const orientation = agent.flags.orientation || 'n'; switch (orientation) { case 'n': return [VIS_HWIDTH+dx, VIS_HEIGHT+dy-1]; case 's': return [VIS_HWIDTH-dx, VIS_HEIGHT-dy-1]; case 'e': return [VIS_HWIDTH+dy, VIS_HEIGHT-dx-1]; case 'w': return [VIS_HWIDTH-dy, VIS_HEIGHT+dx-1]; } } function vision_pos_to_world_pos(world, agent, x, y) { const dx = x-VIS_HWIDTH; const dy = y-VIS_HEIGHT+1; const orientation = agent.flags.orientation || 'n'; switch (orientation) { case 'n': return [agent.x + dx, agent.y + dy]; case 's': return [agent.x - dx, agent.y - dy]; case 'e': return [agent.x - dy, agent.y - dx]; case 'w': return [agent.x + dy, agent.y + dx]; } } function world_pos_to_vision_idx(world, agent, x, y) { const [vx, vy] = world_pos_to_vision_pos(world, agent, x, y); return (VIS_WIDTH * vy) + vx; } function vision_idx_to_world_pos(world, agent, idx) { const vx = idx % VIS_WIDTH; const vy = Math.floor(idx / VIS_WIDTH); const result = vision_pos_to_world_pos(world, agent, vx, vy); return result; } function see_cell(world, x, y) { const team = 0; const orientation = 0; if (!world.lattice[y] || !world.lattice[y][x]) { // beyond the map edge return [ 0, 0, 0 ]; } const type = { active: -0.8, mutable: -0.4, empty: 0.0, immutable: 0.4, flag: 0.8, }[world.lattice[y][x].type]; return [team, orientation, type]; } function relative_orientation(viewer, agent) { switch(viewer.flags.orientation) { case 'n': return { n: -0.8, e: -0.4, s: 0.4, w: 0.8 }[agent.flags.orientation]; case 'e': return { e: -0.8, s: -0.4, w: 0.4, n: 0.8 }[agent.flags.orientation]; case 's': return { s: -0.8, w: -0.4, n: 0.4, e: 0.8 }[agent.flags.orientation]; case 'w': return { w: -0.8, n: -0.4, e: 0.4, s: 0.8 }[agent.flags.orientation]; } } function see_agent(viewer, agent) { const team = { 0: -0.8, 1: -0.4, 2: 0.4, 3: 0.8, }[agent.flags.team] + (identity_mod(agent.id, 11)/16); const orientation = relative_orientation(viewer, agent) + (identity_mod(agent.id, 37)/16); const frozen = agent.flags.frozen || agent.flags.pretend_frozen; const type = (() => { if (frozen && agent.flags.flag) { return -0.4; } else if (frozen) { return -0.8; } else if (agent.flags.flag) { return 0.8; } else { return 0.0; } })() + (identity_mod(agent.id, 499)/16); return [team, orientation, type]; } const see = { size: 3*VIS_WIDTH * VIS_HEIGHT, read: (world, agent) => { const indices = [...Array(VIS_WIDTH*VIS_HEIGHT).keys()] const vision = indices .map(idx => { const [x, y] = vision_idx_to_world_pos(world, agent, idx); return see_cell(world, x, y); }); const result = world.agents.reduce( (acc, a) => { const idx = world_pos_to_vision_idx(world, agent, a.x, a.y); if (idx < 0 || idx >= VIS_WIDTH*VIS_HEIGHT) { return acc; } else { acc.splice(idx, 1, see_agent(agent, a)); return acc; } }, vision ); return result.flat(); }, }; export const senses = [ frozen, hear, see, { size: 1, read: () => [1] }, ];