I have a flood-fill algorithm (Flood-fill) to fill a 24x24 matrix as follows (matrix is 24x24 here but will be much bigger in production):
Main code: var cspots, // number of spots per group gArr=[]; // global array which contains all group spots var tArr = new Array(gArr.length); // touch array for flood-fill for(var spot in inArr) { for (var tspot in tArr) // initialise touch array tArr[tspot]=0; for(gspot in gArr) { // find lowest open y*24+x ordinal if (gArr[gspot][0] == 0) break; tArr[gspot]=1; } cspots = inArr[spot].GD; userFill(gArr[gspot][1],gArr[gspot][2],inArr[spot].KY,tArr); } function userFill(x,y,elem,tArr) { var gord, qt=0; if (!cspots) return; if ((x >= 0) && (x <= 23) && (y >= 0) && (y <= 23)) { gord = y*24 + x; if (gArr[gord][0] != 0 || tArr[gord]) return; gArr[gord][0] = elem; tArr[gord] = 1; --cspots; userFill(x+1,y,elem,tArr); userFill(x-1,y,elem,tArr); // before the y-change we need to see if there are any open spots on this line for(gord=y*24; gord<=(y*24)+23; gord++) { if (gArr[gord][0] == 0) { qt=1; break; } } if (!qt) { userFill(x,y+1,elem,tArr); userFill(x,y-1,elem,tArr); } } }; This is a standard flood-fill recursive algorithm (with an accompanying touch array to mark any touches) with the additional code that I check if all x-values are set to non-zero on each x-plane before changing the y-value. This produces a matrix like this:
The problem is that it doesn't look very good (imo) as most of the areas are strung-out along the x-plane. What I want is each different group area to be in the shape of a square as much as I can. Sort-of like this example (using letters to indicate the different group areas):
V V V W W W W X X X X X V V Y W W W W X X X X Z Y Y Y W W W W Z Z Z Z Z Y Y W W W W Z Z Z Z Z ... and so on So I have changed the userFill to look at a boxX variable which is just the (sqrt of each area)+1, which hopefully I can use to limit each area to make a square-shape. And a preX variable to store the anchor point from each group area so I know how many spots have been added. Here's the new userFill:
Main code: var tArr = new Array(gArr.length); for(var spot in inArr) { for (var tspot in tArr) // initialise touch array tArr[tspot]=0; for(gspot in gArr) { // find lowest open y*24+x ordinal if (gArr[gspot][0] == 0) break; tArr[gspot]=1; } cspots = inArr[spot].GD; boxX = Math.ceil(Math.sqrt(cspots)); preX = gArr[gspot][1]; userFill(gArr[gspot][1],gArr[gspot][2],inArr[spot].KY,tArr); } function userFill(x,y,elem,tArr) { var gord, qt=0; if (!cspots) return; if ((x >= 0) && (x <= 23) && (y >= 0) && (y <= 23)) { gord = y*24 + x; if (gArr[gord][0] != 0 || tArr[gord]) return; gArr[gord][0] = elem; tArr[gord] = 1; --cspots; // before the x-change we need to see if we have done a boxX number of changes to maintain square-shape if (Math.abs(x-preX) == boxX) { userFill(preX,y+1,elem,tArr); userFill(preX,y-1,elem,tArr); return; } userFill(x+1,y,elem,tArr); userFill(x-1,y,elem,tArr); // before the y-change we need to see if there are any open spots on this line for(gord=y*24; gord<=(y*24)+boxX; gord++) { if (gArr[gord][0] == 0) { qt=1; break; } } if (!qt) { userFill(x,y+1,elem,tArr); userFill(x,y-1,elem,tArr); } } }; The only difference is that I check if boxX spots have been added and then call userFill recursively to change the y-plane.
Here's the output and it looks better as most areas are square-like but obviously it needs work (missing most of the spots, pale-blue group area is very oddly-shaped and not square-like at all), but I wonder if there is a better algorithm out there that changes a flood-fill from line-based to square based.
UPDATE:
I think I figured out an algorithm. You start with a single point at the lowest point in the matrix. You then add a surrounding square of points around that point followed by a larger square until cspots is exhausted. So:
0 (first spot in matrix (0,0)) 1 1 0 1 (add 3 spots to make it a 2x2 square) 2 2 2 1 1 2 0 1 2 (add 5 spots to make it a 3x3 square) and so on This can be done even if a number of squares are drawn and you are building on top of other squares. For example (X and Y are preexisting squares):
X X X Y Y X X Y Y Place first spot of new group in the lowest point in matrix (0,2)
0 X X X Y Y X X Y Y Try and build 1st level square (an 'X' will be blocking one of the 1-level spots)
1 1 0 X X X Y Y X X Y Y Try to build 2nd level square
2 2 2 1 1 2 0 X 2 X X Y Y X X Y Y 3rd level
3 3 3 3 2 2 2 3 1 1 2 3 0 X 2 3 X X Y Y X X Y Y 4th level
4 4 4 4 4 3 3 3 3 4 2 2 2 3 4 1 1 2 3 4 0 X 2 3 4 X X Y Y X X Y Y .. and so on. I just need to find the implementation now.
UPDATE 2
I have created the breadth-first algorithm as suggested below and it works much better. Here's the code and the image produced (which is very square-like for each of the 10 groups).
function bfsFill(x,y,elem,tArr) { var gord, i=0, pt, queue=[], cnt=0; if (!cspots) return; if (isOutOfBounds(x,y)) return; queue.push([x,y]); while(cspots>0 && queue.length>0) { pt = queue.shift(); gord = pt[1]*24 + pt[0]; tArr[gord] = 1; gArr[gord][0] = elem; --cspots; var rArr = neighbours(pt); async.eachSeries(rArr, function(el, cb2) { if (!isOutOfBounds(el[0],el[1])) { gord = el[1]*24 + el[0]; if (tArr[gord] == 0 && gArr[gord][0] == 0) { for(var qi in queue) { if (queue[qi][0] == el[0] && queue[qi][1]==el[1]) { cb2(); return; } } queue.push(el); } } cb2(); }, function(err) { }); } }; Image file produced:


