My program runs out of memory (Stack overflow) 9 out of 10 times. But where is the issue in my algorithm? - algorithm

I've made the following program to simulate the classic card game "War". But when I try to run it, it experiences stack overflow. Why? I thought my algorithm was as effective as it could be. Another weird thing that happens is that 1 out of 10 times the program will finish, returning a VERY low vale for the round count (around 26). The code is as follows:
Firstly, I have a class named Card.
package {
public class Card {
public var cardName:String;
public var suit:String;
public var number:int;
public function Card() {
}
}
}
Then I have the following code:
import flash.utils.getDefinitionByName;
var cardDeck:Array = new Array();
var suits:Array = new Array();
suits = ["Hearts", "Clubs", "Spades", "Diamonds"];
for each (var suit in suits)
{
for (var a = 1; a <= 13; a++)
{
var card:Card = new Card();
card.cardName = suit + a;
card.number = a;
card.suit = suit;
cardDeck.push(card);
}
}
var shuffledCardDeck:Array = new Array();
var randomPos:int = 0;
for (var b = 0; b < 52; b++) {
randomPos = Math.random()*cardDeck.length;
shuffledCardDeck[b] = cardDeck[randomPos];
cardDeck.splice(randomPos, 1);
}
var handOne:Array = new Array();
var handTwo:Array = new Array();
for (var c = 0; c < 26; c++) {
handOne.push(shuffledCardDeck[c]);
}
for (var d = 26; d < 52; d++) {
handTwo.push(shuffledCardDeck[d]);
}
var roundCount:int;
round();
function round() {
roundCount+=1;
var cardOne:Card = handOne[0];
var cardTwo:Card = handTwo[0];
if (cardOne.number < cardTwo.number) {
// Player two wins
handTwo.push(cardOne);
handOne.splice(0, 1);
} else if (cardOne.number > cardTwo.number) {
// Player one wins
handOne.push(cardTwo);
handTwo.splice(0, 1);
} else {
// Draw
handOne.splice(0,1)
handOne.push(cardOne);
handTwo.splice(0,1)
handTwo.push(cardTwo);
}
if (handOne.length == 0 || handTwo.length == 0) {
trace("Good game")
} else {
round();
}
}
trace(roundCount);

Flash runtime’s maximum recursion limit is default 1000, but can be set via the compiler argument default-script-limits. Instead of just recursion, call the function in an interval to prevent such stack overflow. It will be helpful even when you decide to show visually the process to the gamers.
setTimeout(round,50); //in place of round();
Source: http://www.designswan.com/archives/as3-recursive-functions-vs-loop-functions.html

Related

Grouping multiple moving averages in dc.js

I have a dc.js example going where I'd like to calculate moving averages over two different windows of the dataset and group them. Ultimately, I'd like to also group the ratio between the two moving averages with them so I can access them using a key.
I've got a handle on how to do single moving avg using reductio, but I'm not sure how to do two of them concurrently, make a ratio, and show all three (MA1,MA2,Ratio) in the graph.
Here is how I'm doing each independent MA:
var date_array = [];
var mapped_date_array = [];
var activities_infinity = activityDistanceByDayGroup.top(Infinity);
var i = 0;
for (i=0; i < activities_infinity.length; i++) {
date_array.push(activities_infinity[i].key);
}
date_array.sort(function (date1, date2) {
if (date1 > date2) return 1;
if (date1 < date2) return -1;
})
mapped_date_array = date_array.map(function(e) { return e.toDateString();
});
// For Chronic Load
var cLoadMovingAvg = activityByDay.groupAll();
cReducer = reductio().groupAll(function(record) {
var idx = mapped_date_array.indexOf(record.dtg.toDateString());
if (record.dtg < date_array[9]) {
return [date_array[idx]];
} else {
var i = 0;
var return_array = [];
for (i = 9; i >= 0; i--) {
return_array.push(date_array[idx - i]);
}
return return_array;
}
}).count(true).sum(dc.pluck('Distance')).avg(true)(cLoadMovingAvg);
// For Acute Load
var aLoadMovingAvg = activityByDay.groupAll();
aReducer = reductio().groupAll(function(record) {
var idx = mapped_date_array.indexOf(record.dtg.toDateString());
if (record.dtg < date_array[3]) {
return [date_array[idx]];
} else {
var i = 0;
var return_array = [];
for (i = 3; i >= 0; i--) {
return_array.push(date_array[idx - i]);
}
return return_array;
}
}).count(true).sum(dc.pluck('Distance')).avg(true)(aLoadMovingAvg);
jsFiddle is here: http://jsfiddle.net/gasteps/hLh5frc8/2/
Thanks so much for any help on this!

How to create all possible variations from single string presented in special format?

Let's say, I have following template.
Hello, {I'm|he is} a {notable|famous} person.
Result should be
Hello, I'm a notable person.
Hello, I'm a famous person.
Hello, he is a notable person.
Hello, he is a famous person.
The only possible solution I have in mind - full search, but it is not effective.
May be there is a good algorithm for such kind of job but I do not know what task about. All permutations in array is very close to this but I have no idea how to use it here.
Here is working solution (it's part of object, so here is only relevant part).
generateText() parses string and converts 'Hello, {1|2}, here {3,4}' into ['Hello', ['1', '2'], 'here', ['3', '4']]]
extractText() takes this multidimensional array and creates all possible strings
STATE_TEXT: 'TEXT',
STATE_INSIDE_BRACKETS: 'INSIDE_BRACKETS',
generateText: function(text) {
var result = [];
var state = this.STATE_TEXT;
var length = text.length;
var simpleText = '';
var options = [];
var singleOption = '';
var i = 0;
while (i < length) {
var symbol = text[i];
switch(symbol) {
case '{':
if (state === this.STATE_TEXT) {
simpleText = simpleText.trim();
if (simpleText.length) {
result.push(simpleText);
simpleText = '';
}
state = this.STATE_INSIDE_BRACKETS;
}
break;
case '}':
if (state === this.STATE_INSIDE_BRACKETS) {
singleOption = singleOption.trim();
if (singleOption.length) {
options.push(singleOption);
singleOption = '';
}
if (options.length) {
result.push(options);
options = [];
}
state = this.STATE_TEXT;
}
break;
case '|':
if (state === this.STATE_INSIDE_BRACKETS) {
singleOption = singleOption.trim();
if (singleOption.length) {
options.push(singleOption);
singleOption = '';
}
}
break;
default:
if (state === this.STATE_TEXT) {
simpleText += symbol;
} else if (state === this.STATE_INSIDE_BRACKETS) {
singleOption += symbol;
}
break;
}
i++;
}
return result;
},
extractStrings(generated) {
var lengths = {};
var currents = {};
var permutations = 0;
var length = generated.length;
for (var i = 0; i < length; i++) {
if ($.isArray(generated[i])) {
lengths[i] = generated[i].length;
currents[i] = lengths[i];
permutations += lengths[i];
}
}
var strings = [];
for (var i = 0; i < permutations; i++) {
var string = [];
for (var k = 0; k < length; k++) {
if (typeof lengths[k] === 'undefined') {
string.push(generated[k]);
continue;
}
currents[k] -= 1;
if (currents[k] < 0) {
currents[k] = lengths[k] - 1;
}
string.push(generated[k][currents[k]]);
}
strings.push(string.join(' '));
}
return strings;
},
The only possible solution I have in mind - full search, but it is not effective.
If you must provide full results, you must run full search. There is simply no way around it. You don't need all permutations, though: the number of results is equal to the product of the number of alternatives in each template.
Although there are multiple ways to implement this, recursion is among the most popular approaches. Here is some pseudo-code to get you started:
string[][] templates = {{"I'm", "he is"}, {"notable", "famous", "boring"}}
int[] pos = new int[templates.Length]
string[] fills = new string[templates.Length]
recurse(templates, fills, 0)
...
void recurse(string[][] templates, string[] fills, int pos) {
if (pos == fills.Length) {
formatResult(fills);
} else {
foreach option in templates[pos] {
fills[pos] = option
recurse(templates, fills, pos+1);
}
}
}
It seems like the best solution here is going to be n*m where n=the first array and m= the second array . There are nm required lines of output, which means that as long as you are only doing nm you aren't doing any extra work
The generic running time for this is where there is more than 2 arrays with options, it would be
n1*n2...*nm where each of those is equal to the size of the respective list
A nested loop where you just print out the value for the current index of the outer loop along with the current value for the index of the inner loop should do this properly

javascript cannot read property undefined of undefined

I need help and quick since this project is for this wednesday and i dont know what its wrong with my code and its screwing everything.
This is supposed to be a battleships game
the click handler keeps screwing me over and i hope you can help me`
this.Handler = function (getRow, getColumn) {
var myId = "#grid_" + getRow + "_" + getColumn;
if (this.GridArray[getRow][getColumn] == 0) {
$(myId).removeClass('grid');
$(myId).addClass('gridMiss');
this.bullets--;
}
else if (this.GridArray[getRow][getColumn] == 1) {
$(myId).removeClass('grid');
$(myId).addClass('gridHit');
explosion.pause();
explosion.play();
Ships.isHit(getRow, getColumn);
this.ShipStatus();
this.bullets--;
this.bulletStatus();
//call the function that keep track of the hits of the ships
}
}
this.ClickHandler = function () {
refOutside = this
$('.grid').click(function (eventData) {
var getRow = $(this).attr('data-Row');
var getColumn = $(this).attr('data-Column');
refOutside.Handler(getRow, getColumn);
});
}
}
And in here i have the creation of the array
this.CreateEmptyGrid = function () {
for (i = 0; i < ROWS; i++) {
this.GridArray[i] = [];
for (j = 0; j < COLUMNS; j++) {
this.GridArray[i][j] = 0;
}
}
}

Finding path from cell x to cell y in a grid so that all cells are parsed once

I am trying to code an algorithm so that it can start from any "start" cell of a grid ( eg.cell no. 4 in the pic) and parse through each cell of a grid once. The grid can be of any size 3x3, 4x4, 8x8 etc.
The following codes generates such paths. And works fine for many cells in 8x8 grid. But for many cells, it takes forever to search a path.
I was wondering if there is some better solution that can be referenced, so that the solution can be optimized.
package
{
import flash.display.MovieClip;
public class Main extends MovieClip
{
private var rand_Num;
public var node0_Mc:MovieClip ,node1_Mc:MovieClip,node2_Mc:MovieClip,node3_Mc:MovieClip,node4_Mc:MovieClip,node5_Mc:MovieClip,node6_Mc:MovieClip,node7_Mc:MovieClip,node8_Mc:MovieClip,node9_Mc:MovieClip,
node10_Mc:MovieClip,node11_Mc:MovieClip,node12_Mc:MovieClip,node13_Mc:MovieClip,node14_Mc:MovieClip,node15_Mc:MovieClip,node16_Mc:MovieClip,node17_Mc:MovieClip,node18_Mc:MovieClip,node19_Mc:MovieClip,
node20_Mc:MovieClip,node21_Mc:MovieClip,node22_Mc:MovieClip,node23_Mc:MovieClip,node24_Mc:MovieClip,node25_Mc:MovieClip,node26_Mc:MovieClip,node27_Mc:MovieClip,node28_Mc:MovieClip,node29_Mc:MovieClip,
node30_Mc:MovieClip,node31_Mc:MovieClip,node32_Mc:MovieClip,node33_Mc:MovieClip,node34_Mc:MovieClip,node35_Mc:MovieClip,node36_Mc:MovieClip,node37_Mc:MovieClip,node38_Mc:MovieClip,node39_Mc:MovieClip,
node40_Mc:MovieClip,node41_Mc:MovieClip,node42_Mc:MovieClip,node43_Mc:MovieClip,node44_Mc:MovieClip,node45_Mc:MovieClip,node46_Mc:MovieClip,node47_Mc:MovieClip,node48_Mc:MovieClip,node49_Mc:MovieClip,
node50_Mc:MovieClip,node51_Mc:MovieClip,node52_Mc:MovieClip,node53_Mc:MovieClip,node54_Mc:MovieClip,node55_Mc:MovieClip,node56_Mc:MovieClip,node57_Mc:MovieClip,node58_Mc:MovieClip,node59_Mc:MovieClip,
node60_Mc:MovieClip, node61_Mc:MovieClip, node62_Mc:MovieClip, node63_Mc:MovieClip;
public const NUM_COLS:Number = 8;
// 3 ;// 4 ;
public const NUM_ROWS:Number = 8;// 3 ;// 4 ;
var chain_Arr:Array = [];
var blockIndex_Arr:Array = [];
var nodearr:Array;
var adjacentNodeArray_Arr:Array = [];
var parsedNodeIndex_Arr:Array = [];
var validNextAdjacentNodeIndexArray_Arr:Array = [];
var validPreviousAdjacentNodeIndexArray_Arr:Array = [];
var savePair_Arr:Array = [];
var countChain_Num:Number = 0;
var saveParent_Arr:Array = [];
public function Main()
{
// constructor code
nodearr = [node0_Mc,node1_Mc,node2_Mc,node3_Mc,node4_Mc,node5_Mc,node6_Mc,node7_Mc,node8_Mc,node9_Mc,node10_Mc,node11_Mc,node12_Mc,node13_Mc,node14_Mc,node15_Mc,node16_Mc,node17_Mc,node18_Mc,node19_Mc,node20_Mc,node21_Mc,node22_Mc,node23_Mc,node24_Mc,node25_Mc,node26_Mc,node27_Mc,node28_Mc,node29_Mc,node30_Mc,node31_Mc,node32_Mc,node33_Mc,node34_Mc,node35_Mc,node36_Mc,node37_Mc,node38_Mc,node39_Mc,node40_Mc,node41_Mc,node42_Mc,node43_Mc,node44_Mc,node45_Mc,node46_Mc,node47_Mc,node48_Mc,node49_Mc,node50_Mc,node51_Mc,node52_Mc,node53_Mc,node54_Mc,node55_Mc,node56_Mc,node57_Mc,node58_Mc,node59_Mc,node60_Mc,node61_Mc,node62_Mc,node63_Mc];
var possibleAdjacentNodeIndex_Arr:Array = [];
initValidNextAdjacentNodeIndexArray();
initValidPreviousAdjacentNodeIndexArray();
savePair_Arr = [];
var startIndex_num:Number = 45;// 0 ;
rand_Num = 62;// randomRange(10, (NUM_COLS * NUM_ROWS) - 1);
getAllChainsFromParamNodeIndexParamParentChain(startIndex_num,[startIndex_num]);
}
function initValidNextAdjacentNodeIndexArray():void
{
for (var index_num = 0; index_num < NUM_COLS*NUM_ROWS; index_num++)
{
validNextAdjacentNodeIndexArray_Arr[index_num] = getValidNodeIndexArrayAdjacentToParamNodeIndex(index_num);
//trace(validNextAdjacentNodeIndexArray_Arr[index_num]);
}
}
function initValidPreviousAdjacentNodeIndexArray():void
{
for (var index_num = 0; index_num < NUM_COLS*NUM_ROWS; index_num++)
{
validPreviousAdjacentNodeIndexArray_Arr[index_num] = getValidNodeIndexArrayAdjacentToParamNodeIndex(index_num);
//trace(validNextAdjacentNodeIndexArray_Arr[index_num]);
}
}
//function getAllChainsFromParamNodeIndexParamParentChain( path_arr:Array,index_param_num:Number ):void
function getAllChainsFromParamNodeIndexParamParentChain( index_param_num:Number, parent_arr:Array ):void
{
var i;
if ( countChain_Num > 15 )
{
return;
}
reinitPath();
var adjacent_arr = getValidNodeIndexArrayAdjacentToParamNodeIndex(index_param_num);
for (i = 0; i < adjacent_arr.length; i++)
{
reinitPath();
if ( ( checkRepeat(chain_Arr)) )
{
continue;
}
chain_Arr.push(adjacent_arr[i]);
//trace("chain starts from", chain_Arr);
var nodeIndex_num:Number = adjacent_arr[i];
var nodeIndexExist_num = 0;
var parentNode_num:Number = parent_arr[parent_arr.length - 1];
var bool:Boolean;
while ( isNaN(nodeIndex_num) == false)
{
//var childNodeIndex_num = manageValidAdjacentIndexArray(parent_arr[parent_arr.length-1], parentNode_num , nodeIndex_num );
var childNodeIndex_num = manageValidAdjacentIndexArray(adjacent_arr[i],parentNode_num,nodeIndex_num);
//var childNodeIndex_num = manageValidAdjacentIndexArray(parentNode_num, parentNode_num , nodeIndex_num );
parentNode_num = nodeIndex_num;
nodeIndex_num = childNodeIndex_num;
if ( ( checkRepeat(chain_Arr)) )
{
break;
}
if ( isNaN(nodeIndex_num) == false)
{
chain_Arr.push(nodeIndex_num);
}
}
if (chain_Arr.length > NUM_COLS * NUM_ROWS - 1)
{
//if ( !( checkRepeat(chain_Arr)) )
{
trace(chain_Arr);
saveParent_Arr[saveParent_Arr.length ] = new Array();
for (var k = 0; k < rand_Num; k++)
{
saveParent_Arr[saveParent_Arr.length - 1].push( chain_Arr[k]);
}
countChain_Num++;
}
}
};
for (i = 0; i < adjacent_arr.length; i++)
{
var arr2:Array = parent_arr.slice();
arr2.push(adjacent_arr[i]);
getAllChainsFromParamNodeIndexParamParentChain( adjacent_arr[i], arr2 );
}
function reinitPath():void
{
chain_Arr = [];
chain_Arr = chain_Arr.concat(parent_arr);
}
}
private function checkRepeat(chain_arr:Array):Boolean
{
var bool:Boolean;
var num = rand_Num;
if (chain_arr.length >= num - 1)
{
for (var i = 0; i < saveParent_Arr.length; i++)
{
//trace( saveParent_Arr[i][0], saveParent_Arr[i][1]);
if (saveParent_Arr[i] != undefined)
{
var z = 0;
for (var j = 0; j < num; j++)
{
if (saveParent_Arr[i][j] == chain_arr[j])
{
z++;
if ( z >= num)
{
bool = true;
break;
}
}
else
{
break;
}
}
if (bool)
{
break;
}
}
}
}
return bool;
}
function randomRange( minNum:Number, maxNum:Number):Number
{
return (Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum);
}
function randomizeArray(arr:Array ):void
{
for (var i = 0; i < arr.length; i++)
{
var random = randomRange(0,arr.length - 1);
var temp = arr[i];
arr[i] = arr[random];
arr[random] = temp;
}
}
//will send NaN if no valid adjacent index array is possible
private function manageValidAdjacentIndexArray(chainStartIndex_num:Number, parentNodeIndex_param_num:Number, nodeIndex_num:Number):Number
{
var num_nan:Number;
var ret_num:Number;
var j;
//var tot:Number = validNextAdjacentNodeIndexArray_Arr[nodeIndex_num].length ;
j = 0;
var arr:Array = validNextAdjacentNodeIndexArray_Arr[nodeIndex_num];// getValidNodeIndexArrayAdjacentToParamNodeIndex(nodeIndex_num) ;
randomizeArray(arr);
while (arr.length > 0 )// && isNaN(ret_num))
{
ret_num = arr[j];//validNextAdjacentNodeIndexArray_Arr[nodeIndex_num][j] ;
//if this index is present in chain then remove it off
if (chain_Arr.indexOf(ret_num) >= 0)
{
//j++ ;
ret_num = num_nan;
}
j++;
if ( j >= arr.length )
{
ret_num = num_nan;
break;
}
}
return ret_num;
}
private function getValidAdjacentIndexToParamNodeIndex(nodeIndex_param_num:Number):Number
{
var adjacentNode_arr:Array = [];
var adjacentRow_arr:Array = [];
var adjacentCol_arr:Array = [];
var allIndex_arr:Array = [];
var r:Number;
var c:Number;
var row_num:Number = int(nodeIndex_param_num / NUM_COLS);
var col_num:Number = (nodeIndex_param_num % NUM_COLS);
allIndex_arr = getAllAdjacentNodeIndexArray(row_num,col_num);
validateIndices(allIndex_arr);
var ret_num:Number;
ret_num = allIndex_arr[0];
return ret_num;
}
function getValidNodeIndexArrayAdjacentToParamNodeIndex(nodeIndex_param_num:Number ):Array
{
var adjacentNode_arr = [];
for (var positionId_num = 0; positionId_num < 8; positionId_num++)
{
var num:Number = getNodeIndexAtParamPositionIdOfParamIndex(positionId_num,nodeIndex_param_num);
if (isNaN(num))
{
}
else
{
adjacentNode_arr.push(num);
}
}
return adjacentNode_arr;
}
private function getAllAdjacentNodeIndexArray(row_num:Number, col_num:Number,within_arr:Array=null):Array
{
var num:Number;
var index_arr:Array = [num,num,num,num,num,num,num,num];
var r:Number;
var c:Number;
if ( row_num > 0 )
{
r = row_num - 1;
c = col_num;
index_arr[0] = r * NUM_COLS + c;
}
if ( col_num < NUM_COLS-1)
{
r = row_num;
c = col_num + 1;
index_arr[1] = r * NUM_COLS + c;
}
if ( row_num < NUM_ROWS-1)
{
r = row_num + 1;
c = col_num;
index_arr[2] = r * NUM_COLS + c;
}
if ( col_num > 0 )
{
r = row_num;
c = col_num - 1;
index_arr[3] = r * NUM_COLS + c;
}
///////////////////////////////////////////
if ( row_num > 0 && col_num > 0)
{
r = row_num - 1;
c = col_num - 1;
index_arr[4] = r * NUM_COLS + c;
}
if ( row_num > 0 && col_num < NUM_COLS-1)
{
r = row_num - 1;
c = col_num + 1;
index_arr[5] = r * NUM_COLS + c;
}
if ( row_num < NUM_ROWS-1 && col_num < NUM_COLS-1)
{
r = row_num + 1;
c = col_num + 1;
index_arr[6] = r * NUM_COLS + c;
}
if ( row_num < NUM_ROWS-1 && col_num > 0 )
{
r = row_num + 1;
c = col_num - 1;
index_arr[7] = r * NUM_COLS + c;
}
return index_arr;
}
//the adjacent node must be present in within_arr, which we splice, one time for variation
function getNodeIndexAtParamPositionIdOfParamIndex( n:Number , nodeIndex_param_num:Number):Number
{
var adjacentNode_arr:Array = [];
var adjacentRow_arr:Array = [];
var adjacentCol_arr:Array = [];
var index_arr:Array = [];
var r:Number;
var c:Number;
var index_num:Number;
var row_num:Number = int(nodeIndex_param_num / NUM_COLS);
var col_num:Number = (nodeIndex_param_num % NUM_COLS);
index_arr = getAllAdjacentNodeIndexArray(row_num,col_num);
validateIndices(index_arr);
var ret_num:Number;
ret_num = index_arr[n];
return ret_num;
}
private function validateIndices(index_arr:Array):void
{
for (var i = 0; i < index_arr.length; i++)
{
if (chain_Arr.indexOf(index_arr[i]) == -1)
{
}
else
{
var num:Number;
index_arr[i] = num;//pushing NaN
}
}
}
}
}
It's a Hamiltonian path problem. It's NP complete and afaik there is still no efficient solution.
check this out: hamilton paths in grid graphs (pdf)
short randomized algorithm explanation: Princeton Hamiltonian path problem
or wikipedia: Hamiltonian path problem
edit:
I did some more research and for your special case of strongly connected square grids (nxn chessboards) a variation of the warndoffs rule for finding a knights tour might give you a solution in near linear time. See here: A method for finding hamiltonian paths and knight tours (pdf)

Set visibility of MarkerClusterer

Trying to toggle the visibility of MarkerClusterer (V3):
var hydrantsShowing = true;
function ToggleHydrants() {
var markers = hydrantsClusterer.getMarkers();
for (var i = 0; i < markers.length; i++) {
markers[i].setVisible(!hydrantsShowing);
}
hydrantsShowing = !hydrantsShowing;
}
The markers do toggle but with two problems:
1. The map must be panned a bit to the change can take place.
2. The MarkerClusterer icons (with the numbers) are always there, even after the markers are not visible.
I've also tried using the setMap approach, but with similar behavior:
var hydrantsShowing = true;
function ToggleHydrants() {
var markers = hydrantsClusterer.getMarkers();
if (hydrantsShowing) {
for (var i = 0; i < markers.length; i++) {
markers[i].setMap(null);
}
}
else {
for (var i = 0; i < markers.length; i++) {
markers[i].setMap(gmap);
}
}
hydrantsShowing = !hydrantsShowing;
}
Solved it by using MarkerClustererPlus instead.
var hydrantsShowing = true;
function ToggleHydrants() {
var markers = hydrantsClusterer.getMarkers();
for (var i = 0; i < markers.length; i++) {
markers[i].setVisible(!hydrantsShowing);
}
hydrantsClusterer.repaint();
hydrantsShowing = !hydrantsShowing;
}
Calling repaint() after setting visibility sorted all issues.
The original MarkerClusterer don't have such a function.

Resources