|
12 | 12 | * - assumePresent: If true, missing pawns are created when calling getPawn(pawnId). Defaults to true. |
13 | 13 | * - loop: If true, the last space is followed by the first. Defaults to false. |
14 | 14 | * - gridMovement: If true, possible movement is defined through connections on spaces. Defaults to false. |
| 15 | + * - connectRadius: If set to a numeric value, each space will connect to all spaces within that distance |
| 16 | + * unless source or target spaces have explicit connections set by the 'connectsTo' property. |
15 | 17 | * - symmetricConnections: If true, any connections between spaces are assumed to go both ways. |
16 | 18 | * Only relevant if gridMovement is true. Defaults to true. |
17 | 19 | * - cacheGraph: If true, the created map of connections between spaces is stored between game iterations. |
@@ -59,40 +61,60 @@ class Track { |
59 | 61 | * Rebuilds track data. Needed when new spaces are added. |
60 | 62 | */ |
61 | 63 | rebuild() { |
62 | | - // Build a graph of how the spaces connect, if advanced movement is used. |
| 64 | + // Map space IDs to indices, for quicker reference. And update space indices. |
| 65 | + this.spaceMapping = {}; |
| 66 | + for (let i in this.spaces) { |
| 67 | + this.spaceMapping[this.spaces[i].id] = i; |
| 68 | + this.spaces[i].index = parseInt(i); |
| 69 | + } |
| 70 | + |
| 71 | + // Build a graph of how the spaces connect, if grid movement is used. |
63 | 72 | // Data used by the a-star algorithm, to find paths in the grid. |
64 | 73 | if (this.gridMovement) { |
| 74 | + // Reset pawn paths. |
| 75 | + this.pawnPaths = {}; |
| 76 | + |
| 77 | + // Attempt to fetch cached data, if relevant. |
65 | 78 | if (this.cacheGraph) { |
66 | 79 | this.graph = getCache('track.' + this.id + '.grid'); |
67 | 80 | this.heuristic = getCache('track.' + this.id + '.heuristic'); |
68 | 81 | } |
69 | 82 | else |
70 | 83 | this.graph = false; |
| 84 | + |
| 85 | + // Build new graph data, if necessary. |
71 | 86 | if (!this.graph) { |
72 | 87 | this.graph = []; |
73 | 88 | for (let i = 0; i < this.spaces.length; i++) |
74 | 89 | this.graph.push([]); |
75 | 90 | for (let s of this.spaces) { |
76 | | - for (let c of s.connectsTo) { |
77 | | - let target = new ObjectFilter({id: c}).findFirstInArray(this.spaces); |
78 | | - this.graph[s.index][target.index] = 1; |
| 91 | + let connected = []; |
| 92 | + // Use connectRadius, if appropriate. |
| 93 | + if (this.connectRadius && !s.connectsTo.length) { |
| 94 | + let filter = new ObjectFilter().or().addEqualsCondition({id: s.id}).addNotEmptyCondition('connectsTo'); |
| 95 | + connected = this.getSpacesWithinRadius(s, this.connectRadius); |
| 96 | + filter.removeFromArray(connected); |
| 97 | + connected = this.convertSpaceData(connected, 'object', 'index'); |
| 98 | + } |
| 99 | + // Otherwise, use explicit connections set on the space. |
| 100 | + else { |
| 101 | + connected = this.convertSpaceData(s.connectsTo, 'id', 'index'); |
| 102 | + } |
| 103 | + // Connect to each relevant space, and the reverse if relevant. |
| 104 | + for (let targetIndex of connected) { |
| 105 | + this.graph[s.index][targetIndex] = 1; |
79 | 106 | if (this.symmetricConnections) |
80 | | - this.graph[target.index][s.index] = 1; |
| 107 | + this.graph[targetIndex][s.index] = 1; |
81 | 108 | } |
82 | 109 | } |
83 | 110 | let row = Array(this.graph.length).fill(1); |
84 | 111 | this.heuristic = Array(this.graph.length).fill(row); |
85 | | - setCache('track.' + this.id + '.grid', this.graph); |
86 | | - setCache('track.' + this.id + '.heuristic', this.heuristic); |
| 112 | + // Cache for future reference, if relevant. |
| 113 | + if (this.cacheGraph) { |
| 114 | + setCache('track.' + this.id + '.grid', this.graph); |
| 115 | + setCache('track.' + this.id + '.heuristic', this.heuristic); |
| 116 | + } |
87 | 117 | } |
88 | | - this.pawnPaths = {}; |
89 | | - } |
90 | | - |
91 | | - // Map space IDs to indices, for quicker reference. And update space indices. |
92 | | - this.spaceMapping = {}; |
93 | | - for (let i in this.spaces) { |
94 | | - this.spaceMapping[this.spaces[i].id] = i; |
95 | | - this.spaces[i].index = parseInt(i); |
96 | 118 | } |
97 | 119 | } |
98 | 120 |
|
@@ -265,6 +287,57 @@ class Track { |
265 | 287 | return spaces.map(x => this.convertSpaceData(x, 'index', returnType)); |
266 | 288 | } |
267 | 289 |
|
| 290 | + /** |
| 291 | + * Returns all the spaces within the radius of the given point. |
| 292 | + * |
| 293 | + * @param {object} point: An object with (at least) coordinate values for the point to search from. |
| 294 | + * @param {number} radius: A maximum distance from the point to search. |
| 295 | + * @param {string} shape: Set to 'square' to search a square instead of a circle. |
| 296 | + * |
| 297 | + * @return: An array of spaces, sorted by distance to the point. |
| 298 | + */ |
| 299 | + getSpacesWithinRadius(point, radius = 1, shape = 'circle') { |
| 300 | + // Get all spaces within the distance from the point, in a square. |
| 301 | + let filter = new ObjectFilter(); |
| 302 | + for (let c of this.coordinates) { |
| 303 | + let value = {}; |
| 304 | + value[c] = point[c] - radius; |
| 305 | + filter.addGreaterOrEqualCondition(value); |
| 306 | + value[c] = point[c] + radius; |
| 307 | + filter.addLessOrEqualCondition(value); |
| 308 | + } |
| 309 | + // Get the distance from the point for all these candidates. |
| 310 | + let spaces = []; |
| 311 | + for (let s of filter.applyOnArray(this.spaces)) { |
| 312 | + let distance = getDistance(point, s, this.coordinates); |
| 313 | + if (shape == 'square' || distance <= radius) |
| 314 | + spaces.push({ |
| 315 | + space: s, |
| 316 | + distance: distance, |
| 317 | + }); |
| 318 | + } |
| 319 | + // Sort by distance from the point. |
| 320 | + sortByProperty(spaces, 'distance', true, true)[0]['space']; |
| 321 | + return buildArrayWithProperty(spaces, 'space'); |
| 322 | + } |
| 323 | + |
| 324 | + /** |
| 325 | + * Returns the space closest to a given point. |
| 326 | + * |
| 327 | + * @param {object} point: An object with (at least) coordinate values for the point to search from. |
| 328 | + * @param {number} radius: A maximum distance from the point to search. Smaller number increases search speed. |
| 329 | + * @param {string} shape: Set to 'square' to search a square instead of a circle. |
| 330 | + * |
| 331 | + * @return {Space} The space object with coordinates closest to the given point. If several |
| 332 | + * equally near, one of these is selected randomly. |
| 333 | + */ |
| 334 | + getClosestSpace(point, radius = 1, shape = 'circle') { |
| 335 | + let spaceList = this.getSpacesWithinRadius(point, radius, shape); |
| 336 | + if (spaceList.length == 0) |
| 337 | + throw('No space is within search distance from the given point.'); |
| 338 | + return spaceList[0]; |
| 339 | + } |
| 340 | + |
268 | 341 | /** |
269 | 342 | * Does an estimation of whether there is a line of sight between two spaces. If point coordinates |
270 | 343 | * are provided, these will be used instead of spaces' coordinates. |
@@ -497,12 +570,12 @@ class Pawn { |
497 | 570 | if (this.id === undefined) |
498 | 571 | throw('Pawns must have an id property set.'); |
499 | 572 | // Add the track name + pawn to any agent matching the pawn id. |
500 | | - for (let a of gameState.agents) { |
501 | | - if (this.id == a.id) { |
502 | | - if (a[track.id] === undefined) |
503 | | - a[track.id] = {}; |
504 | | - a[track.id].pawn = this; |
505 | | - } |
| 573 | + let agent = getAgentById(this.id); |
| 574 | + if (agent) { |
| 575 | + if (agent[track.id] === undefined) |
| 576 | + agent[track.id] = { |
| 577 | + pawn: this, |
| 578 | + }; |
506 | 579 | } |
507 | 580 |
|
508 | 581 | this.track = track; |
|
0 commit comments