From f5d5906fbc9cb38718366c6972f7c8ea2ae8d750 Mon Sep 17 00:00:00 2001 From: "Ian D. Bollinger" Date: Tue, 19 Jul 2016 21:01:25 -0400 Subject: [PATCH] Functionally accumulating values One problem I've been having in writing PureScript bindings for rot.js is getting data out of the functions that rely on callbacks. Functions like FOV.compute require the user to pass in a callback that is obligated to accumulate mutable state. While this is possible in PureScript, it's inelegant and difficult to compose. This pull request allows the user to instead accumulate a value in a functional manner. Functions like FOV.compute now take an additional argument, `initialValue` that is passed through to the callback as the last argument, which then returns the accumulator which is again passed to the callback, and so on. Finally, FOV.compute returns the accumulator when finished. Because these have been tacked on to the parameters list, the change should be backwards compatible. ("undefined" will be accumulated.) Optimally, you'd be able to do this for the map and lighting interfaces as well. Unfortunately, they already return a value. The only value they return, however, is "this". I'd personally change them to return an accumulator as well, but this might break existing code. Anyway, I understand this is a weird request and will understand if you don't like it. --- src/fov/discrete-shadowcasting.js | 49 +++++++++++++------------ src/fov/fov.js | 2 +- src/fov/precise-shadowcasting.js | 25 ++++++------- src/fov/recursive-shadowcasting.js | 58 +++++++++++++++--------------- src/path/astar.js | 22 ++++++------ src/path/dijkstra.js | 18 +++++----- src/path/path.js | 6 ++-- 7 files changed, 95 insertions(+), 85 deletions(-) diff --git a/src/fov/discrete-shadowcasting.js b/src/fov/discrete-shadowcasting.js index 1d573c31..056ea943 100644 --- a/src/fov/discrete-shadowcasting.js +++ b/src/fov/discrete-shadowcasting.js @@ -10,19 +10,19 @@ ROT.FOV.DiscreteShadowcasting.extend(ROT.FOV); /** * @see ROT.FOV#compute */ -ROT.FOV.DiscreteShadowcasting.prototype.compute = function(x, y, R, callback) { +ROT.FOV.DiscreteShadowcasting.prototype.compute = function(x, y, R, callback, initialValue) { var center = this._coords; var map = this._map; /* this place is always visible */ - callback(x, y, 0, 1); + var accumulator = callback(x, y, 0, 1, initalValue); /* standing in a dark place. FIXME is this a good idea? */ - if (!this._lightPasses(x, y)) { return; } - + if (!this._lightPasses(x, y)) { return accumulator; } + /* start and end angles */ var DATA = []; - + var A, B, cx, cy, blocks; /* analyze surrounding cells in concentric rings, starting from the center */ @@ -35,14 +35,17 @@ ROT.FOV.DiscreteShadowcasting.prototype.compute = function(x, y, R, callback) { cy = neighbors[i][1]; A = angle * (i - 0.5); B = A + angle; - + blocks = !this._lightPasses(cx, cy); - if (this._visibleCoords(Math.floor(A), Math.ceil(B), blocks, DATA)) { callback(cx, cy, r, 1); } - - if (DATA.length == 2 && DATA[0] == 0 && DATA[1] == 360) { return; } /* cutoff? */ + if (this._visibleCoords(Math.floor(A), Math.ceil(B), blocks, DATA)) { + accumulator = callback(cx, cy, r, 1, accumulator); + } + + if (DATA.length == 2 && DATA[0] == 0 && DATA[1] == 360) { return accumulator; } /* cutoff? */ } /* for all cells in this ring */ } /* for all rings */ + return accumulator; } /** @@ -52,38 +55,38 @@ ROT.FOV.DiscreteShadowcasting.prototype.compute = function(x, y, R, callback) { * @param {int[][]} DATA shadowed angle pairs */ ROT.FOV.DiscreteShadowcasting.prototype._visibleCoords = function(A, B, blocks, DATA) { - if (A < 0) { + if (A < 0) { var v1 = arguments.callee(0, B, blocks, DATA); var v2 = arguments.callee(360+A, 360, blocks, DATA); return v1 || v2; } - + var index = 0; while (index < DATA.length && DATA[index] < A) { index++; } - + if (index == DATA.length) { /* completely new shadow */ - if (blocks) { DATA.push(A, B); } + if (blocks) { DATA.push(A, B); } return true; } - + var count = 0; - + if (index % 2) { /* this shadow starts in an existing shadow, or within its ending boundary */ while (index < DATA.length && DATA[index] < B) { index++; count++; } - + if (count == 0) { return false; } - - if (blocks) { + + if (blocks) { if (count % 2) { DATA.splice(index-count, count, B); } else { DATA.splice(index-count, count); } } - + return true; } else { /* this shadow starts outside an existing shadow, or within a starting boundary */ @@ -91,18 +94,18 @@ ROT.FOV.DiscreteShadowcasting.prototype._visibleCoords = function(A, B, blocks, index++; count++; } - + /* visible when outside an existing shadow, or when overlapping */ if (A == DATA[index-count] && count == 1) { return false; } - - if (blocks) { + + if (blocks) { if (count % 2) { DATA.splice(index-count, count, A); } else { DATA.splice(index-count, count, A, B); } } - + return true; } } diff --git a/src/fov/fov.js b/src/fov/fov.js index e663e972..8912ecb4 100644 --- a/src/fov/fov.js +++ b/src/fov/fov.js @@ -19,7 +19,7 @@ ROT.FOV = function(lightPassesCallback, options) { * @param {int} R Maximum visibility radius * @param {function} callback */ -ROT.FOV.prototype.compute = function(x, y, R, callback) {} +ROT.FOV.prototype.compute = function(x, y, R, callback, initialValue) {} /** * Return all neighbors in a concentric ring diff --git a/src/fov/precise-shadowcasting.js b/src/fov/precise-shadowcasting.js index 12005e0e..046abaff 100644 --- a/src/fov/precise-shadowcasting.js +++ b/src/fov/precise-shadowcasting.js @@ -10,16 +10,16 @@ ROT.FOV.PreciseShadowcasting.extend(ROT.FOV); /** * @see ROT.FOV#compute */ -ROT.FOV.PreciseShadowcasting.prototype.compute = function(x, y, R, callback) { +ROT.FOV.PreciseShadowcasting.prototype.compute = function(x, y, R, callback, initialValue) { /* this place is always visible */ - callback(x, y, 0, 1); + var accumulator = callback(x, y, 0, 1, initialValue); /* standing in a dark place. FIXME is this a good idea? */ - if (!this._lightPasses(x, y)) { return; } - + if (!this._lightPasses(x, y)) { return accumulator; } + /* list of all shadows */ var SHADOWS = []; - + var cx, cy, blocks, A1, A2, visibility; /* analyze surrounding cells in concentric rings, starting from the center */ @@ -32,16 +32,17 @@ ROT.FOV.PreciseShadowcasting.prototype.compute = function(x, y, R, callback) { cy = neighbors[i][1]; /* shift half-an-angle backwards to maintain consistency of 0-th cells */ A1 = [i ? 2*i-1 : 2*neighborCount-1, 2*neighborCount]; - A2 = [2*i+1, 2*neighborCount]; - + A2 = [2*i+1, 2*neighborCount]; + blocks = !this._lightPasses(cx, cy); visibility = this._checkVisibility(A1, A2, blocks, SHADOWS); - if (visibility) { callback(cx, cy, r, visibility); } + if (visibility) { accumulator = callback(cx, cy, r, visibility, accumulator); } - if (SHADOWS.length == 2 && SHADOWS[0][0] == 0 && SHADOWS[1][0] == SHADOWS[1][1]) { return; } /* cutoff? */ + if (SHADOWS.length == 2 && SHADOWS[0][0] == 0 && SHADOWS[1][0] == SHADOWS[1][1]) { return accumulator; } /* cutoff? */ } /* for all cells in this ring */ } /* for all rings */ + return accumulator; } /** @@ -82,15 +83,15 @@ ROT.FOV.PreciseShadowcasting.prototype._checkVisibility = function(A1, A2, block var visible = true; if (index1 == index2 && (edge1 || edge2)) { /* subset of existing shadow, one of the edges match */ - visible = false; + visible = false; } else if (edge1 && edge2 && index1+1==index2 && (index2 % 2)) { /* completely equivalent with existing shadow */ visible = false; } else if (index1 > index2 && (index1 % 2)) { /* subset of existing shadow, not touching */ visible = false; } - + if (!visible) { return 0; } /* fast case: not visible */ - + var visibleLength, P; /* compute the length of visible arc, adjust list of shadows (if blocking) */ diff --git a/src/fov/recursive-shadowcasting.js b/src/fov/recursive-shadowcasting.js index e0aa6ec2..cc5d50da 100644 --- a/src/fov/recursive-shadowcasting.js +++ b/src/fov/recursive-shadowcasting.js @@ -28,12 +28,13 @@ ROT.FOV.RecursiveShadowcasting.OCTANTS = [ * @param {int} R Maximum visibility radius * @param {function} callback */ -ROT.FOV.RecursiveShadowcasting.prototype.compute = function(x, y, R, callback) { +ROT.FOV.RecursiveShadowcasting.prototype.compute = function(x, y, R, callback, initialValue) { //You can always see your own tile - callback(x, y, 0, 1); + var accumulator = callback(x, y, 0, 1, initialValue); for(var i = 0; i < ROT.FOV.RecursiveShadowcasting.OCTANTS.length; i++) { - this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[i], R, callback); + accumulator = this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[i], R, callback, accumulator); } + return accumulator; } /** @@ -44,16 +45,16 @@ ROT.FOV.RecursiveShadowcasting.prototype.compute = function(x, y, R, callback) { * @param {int} dir Direction to look in (expressed in a ROT.DIRS value); * @param {function} callback */ -ROT.FOV.RecursiveShadowcasting.prototype.compute180 = function(x, y, R, dir, callback) { +ROT.FOV.RecursiveShadowcasting.prototype.compute180 = function(x, y, R, dir, callback, initialValue) { //You can always see your own tile - callback(x, y, 0, 1); + accumulator = callback(x, y, 0, 1, initialValue); var previousOctant = (dir - 1 + 8) % 8; //Need to retrieve the previous octant to render a full 180 degrees var nextPreviousOctant = (dir - 2 + 8) % 8; //Need to retrieve the previous two octants to render a full 180 degrees var nextOctant = (dir+ 1 + 8) % 8; //Need to grab to next octant to render a full 180 degrees - this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[nextPreviousOctant], R, callback); - this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[previousOctant], R, callback); - this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[dir], R, callback); - this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[nextOctant], R, callback); + accumulator = this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[nextPreviousOctant], R, callback, accumulator); + accumulator = this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[previousOctant], R, callback, accumulator); + accumulator = this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[dir], R, callback, accumulator); + return this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[nextOctant], R, callback, accumulator); } /** @@ -64,12 +65,12 @@ ROT.FOV.RecursiveShadowcasting.prototype.compute180 = function(x, y, R, dir, cal * @param {int} dir Direction to look in (expressed in a ROT.DIRS value); * @param {function} callback */ -ROT.FOV.RecursiveShadowcasting.prototype.compute90 = function(x, y, R, dir, callback) { +ROT.FOV.RecursiveShadowcasting.prototype.compute90 = function(x, y, R, dir, callback, initialValue) { //You can always see your own tile - callback(x, y, 0, 1); + var accumulator = callback(x, y, 0, 1, initialValue); var previousOctant = (dir - 1 + 8) % 8; //Need to retrieve the previous octant to render a full 90 degrees - this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[dir], R, callback); - this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[previousOctant], R, callback); + accumulator = this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[dir], R, callback, accumulator); + return this._renderOctant(x, y, ROT.FOV.RecursiveShadowcasting.OCTANTS[previousOctant], R, callback, accumulator); } /** @@ -80,9 +81,9 @@ ROT.FOV.RecursiveShadowcasting.prototype.compute90 = function(x, y, R, dir, call * @param {int} R Maximum visibility radius * @param {function} callback */ -ROT.FOV.RecursiveShadowcasting.prototype._renderOctant = function(x, y, octant, R, callback) { +ROT.FOV.RecursiveShadowcasting.prototype._renderOctant = function(x, y, octant, R, callback, accumulator) { //Radius incremented by 1 to provide same coverage area as other shadowcasting radiuses - this._castVisibility(x, y, 1, 1.0, 0.0, R + 1, octant[0], octant[1], octant[2], octant[3], callback); + return this._castVisibility(x, y, 1, 1.0, 0.0, R + 1, octant[0], octant[1], octant[2], octant[3], callback, accumulator); } /** @@ -93,14 +94,14 @@ ROT.FOV.RecursiveShadowcasting.prototype._renderOctant = function(x, y, octant, * @param {float} visSlopeStart The slope to start at * @param {float} visSlopeEnd The slope to end at * @param {int} radius The radius to reach out to - * @param {int} xx - * @param {int} xy - * @param {int} yx - * @param {int} yy + * @param {int} xx + * @param {int} xy + * @param {int} yx + * @param {int} yy * @param {function} callback The callback to use when we hit a block that is visible */ -ROT.FOV.RecursiveShadowcasting.prototype._castVisibility = function(startX, startY, row, visSlopeStart, visSlopeEnd, radius, xx, xy, yx, yy, callback) { - if(visSlopeStart < visSlopeEnd) { return; } +ROT.FOV.RecursiveShadowcasting.prototype._castVisibility = function(startX, startY, row, visSlopeStart, visSlopeEnd, radius, xx, xy, yx, yy, callback, accumulator) { + if(visSlopeStart < visSlopeEnd) { return accumulator; } for(var i = row; i <= radius; i++) { var dx = -i - 1; var dy = -i; @@ -118,23 +119,23 @@ ROT.FOV.RecursiveShadowcasting.prototype._castVisibility = function(startX, star //Range of the row var slopeStart = (dx - 0.5) / (dy + 0.5); var slopeEnd = (dx + 0.5) / (dy - 0.5); - + //Ignore if not yet at left edge of Octant if(slopeEnd > visSlopeStart) { continue; } - + //Done if past right edge if(slopeStart < visSlopeEnd) { break; } - + //If it's in range, it's visible if((dx * dx + dy * dy) < (radius * radius)) { - callback(mapX, mapY, i, 1); + accumulator = callback(mapX, mapY, i, 1, accumulator); } - + if(!blocked) { //If tile is a blocking tile, cast around it if(!this._lightPasses(mapX, mapY) && i < radius) { blocked = true; - this._castVisibility(startX, startY, i + 1, visSlopeStart, slopeStart, radius, xx, xy, yx, yy, callback); + accumulator = this._castVisibility(startX, startY, i + 1, visSlopeStart, slopeStart, radius, xx, xy, yx, yy, callback, accumulator); newStart = slopeEnd; } } else { @@ -143,7 +144,7 @@ ROT.FOV.RecursiveShadowcasting.prototype._castVisibility = function(startX, star newStart = slopeEnd; continue; } - + //Block has ended blocked = false; visSlopeStart = newStart; @@ -151,4 +152,5 @@ ROT.FOV.RecursiveShadowcasting.prototype._castVisibility = function(startX, star } if(blocked) { break; } } + return accumulator; } diff --git a/src/path/astar.js b/src/path/astar.js index bab0a629..d47b1e27 100644 --- a/src/path/astar.js +++ b/src/path/astar.js @@ -17,7 +17,7 @@ ROT.Path.AStar.extend(ROT.Path); * Compute a path from a given point * @see ROT.Path#compute */ -ROT.Path.AStar.prototype.compute = function(fromX, fromY, callback) { +ROT.Path.AStar.prototype.compute = function(fromX, fromY, callback, initialValue) { this._todo = []; this._done = {}; this._fromX = fromX; @@ -35,17 +35,19 @@ ROT.Path.AStar.prototype.compute = function(fromX, fromY, callback) { var y = neighbor[1]; var id = x+","+y; if (id in this._done) { continue; } - this._add(x, y, item); + this._add(x, y, item); } } - + var item = this._done[fromX+","+fromY]; - if (!item) { return; } - + if (!item) { return initialValue; } + + var accumulator = initialValue; while (item) { - callback(item.x, item.y); + accumulator = callback(item.x, item.y, accumulator); item = item.prev; } + return accumulator; } ROT.Path.AStar.prototype._add = function(x, y, prev) { @@ -58,9 +60,9 @@ ROT.Path.AStar.prototype._add = function(x, y, prev) { h: h } this._done[x+","+y] = obj; - + /* insert into priority queue */ - + var f = obj.g + obj.h; for (var i=0;i