Refining custom flood fill algorithm (Java) - java

I have no experience with any type of flood fill algorithms at all. But I gave it my best shot. At first, I would fill one pixel wherever the user clicked with magenta, and then went through a loop which drew a pixel on top, left, bottom, and right of each magenta pixel. This would take up to 8 seconds to complete the fill with especially large areas. So I added another loop which drew straight magenta-colored lines up, down, left, and right. This halved the time. I had the computer draw more lines... up-up-left, up-left, up-left-left, down-left-left... etc. Now it's down to between 1 & 2 seconds. What else can I do to decrease the amount of time it takes to complete the fill?
for(Point p:shiftDownPoints)
{
//down
for(int y = p.y+1; y<SCREEN_DIM.height; y++)
{
if(capture.getRGB(p.x,y)==Color.BLACK.getRGB())break;
else capture.setRGB(p.x,y,Color.MAGENTA.getRGB());
}
//up
for(int y = p.y-1; y>0; y--)
{
if(capture.getRGB(p.x,y)==Color.BLACK.getRGB())break;
else capture.setRGB(p.x,y,Color.MAGENTA.getRGB());
}
//right
for(int x = p.x+1; x<SCREEN_DIM.width; x++)
{
if(capture.getRGB(x,p.y)==Color.BLACK.getRGB())break;
else capture.setRGB(x,p.y,Color.MAGENTA.getRGB());
}
//left
for(int x = p.x-1; x>0; x--)
{
if(capture.getRGB(x,p.y)==Color.BLACK.getRGB())break;
else capture.setRGB(x,p.y,Color.MAGENTA.getRGB());
}
//down-right
for(int i = 1; i<Math.min(SCREEN_DIM.width,SCREEN_DIM.height); i++)
{
if(capture.getRGB(p.x+i,p.y+i)==Color.BLACK.getRGB())break;
else capture.setRGB(p.x+i,p.y+i,Color.MAGENTA.getRGB());
}
//down-left
for(int i = 1; i<Math.min(SCREEN_DIM.width,SCREEN_DIM.height); i++)
{
if(capture.getRGB(p.x-i,p.y+i)==Color.BLACK.getRGB())break;
else capture.setRGB(p.x-i,p.y+i,Color.MAGENTA.getRGB());
}
//up-left
for(int i = 1; i<Math.min(SCREEN_DIM.width,SCREEN_DIM.height); i++)
{
if(capture.getRGB(p.x-i,p.y-i)==Color.BLACK.getRGB())break;
else capture.setRGB(p.x-i,p.y-i,Color.MAGENTA.getRGB());
}
//up-right
for(int i = 1; i<Math.min(SCREEN_DIM.width,SCREEN_DIM.height); i++)
{
if(capture.getRGB(p.x+i,p.y-i)==Color.BLACK.getRGB())break;
else capture.setRGB(p.x+i,p.y-i,Color.MAGENTA.getRGB());
}
//up-up-left
for(int i = 1; i<Math.min(SCREEN_DIM.width,SCREEN_DIM.height); i++)
{
if(capture.getRGB(p.x-i,p.y-i*2)==Color.BLACK.getRGB())break;
else capture.setRGB(p.x-i,p.y-i*2,Color.MAGENTA.getRGB());
}
//up-left-left
for(int i = 1; i<Math.min(SCREEN_DIM.width,SCREEN_DIM.height); i++)
{
if(capture.getRGB(p.x-i*2,p.y-i)==Color.BLACK.getRGB())break;
else capture.setRGB(p.x-i*2,p.y-i,Color.MAGENTA.getRGB());
}
//down-left-left
for(int i = 1; i<Math.min(SCREEN_DIM.width,SCREEN_DIM.height); i++)
{
if(capture.getRGB(p.x-i*2,p.y+i)==Color.BLACK.getRGB())break;
else capture.setRGB(p.x-i*2,p.y+i,Color.MAGENTA.getRGB());
}
//down-down-left
for(int i = 1; i<Math.min(SCREEN_DIM.width,SCREEN_DIM.height); i++)
{
if(capture.getRGB(p.x-i,p.y+i*2)==Color.BLACK.getRGB())break;
else capture.setRGB(p.x-i,p.y+i*2,Color.MAGENTA.getRGB());
}
//down-down-right
for(int i = 1; i<Math.min(SCREEN_DIM.width,SCREEN_DIM.height); i++)
{
if(capture.getRGB(p.x+i,p.y+i*2)==Color.BLACK.getRGB())break;
else capture.setRGB(p.x+i,p.y+i*2,Color.MAGENTA.getRGB());
}
//down-right-right
for(int i = 1; i<Math.min(SCREEN_DIM.width,SCREEN_DIM.height); i++)
{
if(capture.getRGB(p.x+i*2,p.y+i)==Color.BLACK.getRGB())break;
else capture.setRGB(p.x+i*2,p.y+i,Color.MAGENTA.getRGB());
}
//up-right-right
for(int i = 1; i<Math.min(SCREEN_DIM.width,SCREEN_DIM.height); i++)
{
if(capture.getRGB(p.x+i*2,p.y-i)==Color.BLACK.getRGB())break;
else capture.setRGB(p.x+i*2,p.y-i,Color.MAGENTA.getRGB());
}
//up-up-right
for(int i = 1; i<Math.min(SCREEN_DIM.width,SCREEN_DIM.height); i++)
{
if(capture.getRGB(p.x+i,p.y-i*2)==Color.BLACK.getRGB())break;
else capture.setRGB(p.x+i,p.y-i*2,Color.MAGENTA.getRGB());
}
}
while(pixelsDrawn)
{
pixelsDrawn=false;
for(int x = 0; x<SCREEN_DIM.width; x++)
for(int y = 0; y<SCREEN_DIM.height; y++)
{
if(capture.getRGB(x,y)==Color.MAGENTA.getRGB())
{
if(capture.getRGB(x-1,y)!=Color.MAGENTA.getRGB()
&&capture.getRGB(x-1,y)!=Color.BLACK.getRGB())
{
capture.setRGB(x-1,y,Color.MAGENTA.getRGB());
pixelsDrawn = true;
}
if(capture.getRGB(x+1,y)!=Color.MAGENTA.getRGB()
&&capture.getRGB(x+1,y)!=Color.BLACK.getRGB())
{
capture.setRGB(x+1,y,Color.MAGENTA.getRGB());
pixelsDrawn = true;
}
if(capture.getRGB(x,y-1)!=Color.MAGENTA.getRGB()
&&capture.getRGB(x,y-1)!=Color.BLACK.getRGB())
{
capture.setRGB(x,y-1,Color.MAGENTA.getRGB());
pixelsDrawn = true;
}
if(capture.getRGB(x,y+1)!=Color.MAGENTA.getRGB()
&&capture.getRGB(x,y+1)!=Color.BLACK.getRGB())
{
capture.setRGB(x,y+1,Color.MAGENTA.getRGB());
pixelsDrawn = true;
}
}
}
}

Thanks to immibis's suggestion, I got my flood-fill down to half a second. In this new code, I had the computer go along the edge of the outline and create a polygon, and then just used fillPolygon from the graphics.
for(Point p:iterablePoints)
{
int dir = 2; //0:right, 1:down, 2:left, 3:up
int xPos = p.x;
int yPos = 0;
Polygon poly = new Polygon();
for(int y = p.y; y>0; y--)
{
if(capture.getRGB(p.x,y-1)==Color.BLACK.getRGB())
{
yPos = y;
break;
}
}
Vector<Point> tempRecord = new Vector<Point>();
boolean run = true;
while(run)
{
if(dir==0&&capture.getRGB(xPos+1,yPos)==Color.BLACK.getRGB())dir--;
else if(dir==1&&capture.getRGB(xPos,yPos+1)==Color.BLACK.getRGB())dir--;
else if(dir==2&&capture.getRGB(xPos-1,yPos)==Color.BLACK.getRGB())dir--;
else if(dir==3&&capture.getRGB(xPos,yPos-1)==Color.BLACK.getRGB())dir--;
else
{
if(dir==0)xPos++;
if(dir==1)yPos++;
if(dir==2)xPos--;
if(dir==3)yPos--;
dir++;
tempRecord.add(new Point(xPos,yPos));
if(tempRecord.size()>1)if(tempRecord.get(0)==tempRecord.get(1))tempRecord.remove(tempRecord.firstElement());
else startPoint = tempRecord.get(0);
if(startPoint!=null)if(startPoint.x==xPos&&startPoint.y==yPos) run=false;
poly.addPoint(xPos,yPos);capture.setRGB(xPos,yPos,Color.MAGENTA.getRGB());
}
if(dir==4)dir=0;
if(dir==-1)dir=3;
}
Graphics cg = capture.getGraphics();
cg.setColor(Color.MAGENTA);
cg.fillPolygon(poly);
cg.dispose();
}

Discussing flood fill algorithm could take a full class session. I think you should consult This Link as it explain everything, how pixel-by-pixel get painted(efficiently) together with algorithm. Hope this helps.

Related

Scrolling text effect on 2D array

For a project I am working on I would like to be abbe to "scroll" an array like so:
Here is the code I have so far:
private boolean[][] display = this.parseTo8BitMatrix("Here");
private int scroll = 0;
public void scroll() {
this.scroll++;
}
//sets the clock's display
public void setDisplay(String s) {
this.display = this.parseTo8BitMatrix(s);
}
//determines the current frame of the clock
private boolean[][] currentFrame() {
boolean[][] currentFrame = new boolean[8][32];
int length = this.display[0].length;
if(length == 32) { //do nothing
currentFrame = this.display;
} else if(length <= 24) { //center
for(int i = 0; i < 8; i++) {
for(int j = 0; j < length; j++) {
currentFrame[i][j+((32-length)/2)] = this.display[i][j];
}
}
this.display = currentFrame; //set display to currentFrame so the display doesn't get centered each time
} else { //scroll
for(int i = 0; i < 8; i++) {
for(int j = 0; j < length; j++) {
if(this.scroll+j <= 32) {
currentFrame[i][j] = this.display[i][j+this.scroll];
} else {
//?
}
}
}
}
return currentFrame;
}
The code I have is effective up until the the array needs to "wrap around" to the other side. Where have I gone wrong?
I'm assuming that you're looking for a formula that would work for the else.
Usually Modulos are very helpful for wrapping around.
What you are looking for is basically
currentFrame[i][j]= this.display[i][(j+this.scroll)%length];
which works even when it's not wrapped around.

difficulty implementing minimax algorithm for a tic-tac-toe game in java

I am trying to make a tic-tac-toe game in java,with an array of objects cell[i][j],where cell stores the value of the of ith row and jth column ,with the help of setToken() and getToken(),problem is with the minimax algorithm,player plays as maximiser('x') and the AI as minimiser('O'),irrespective of what player plays the AI is simply playing the next free box available,i highly suspect there is some problem with either of these 2 functions....please help!
'
Move findBestMove(Cell cell[][])
{
int bestVal = 1000;
Move bestMove = new Move();
bestMove.row = 3;
bestMove.col = 3;
for (int i = 0; i<3; i++)
{
for (int j = 0; j<3; j++)
{
if (cell[i][j].getToken()==' ')
{
// Make the move
cell[i][j].setToken('O');
// compute evaluation function for this
// move.
int moveVal = minimax(cell, 0, true);
// Undo the move
cell[i][j].setToken(' ');
// If the value of the current move is
// more than the best value, then update
// best/
if (moveVal < bestVal)
{
bestMove.row = i;
bestMove.col = j;
bestVal = moveVal;
}
}
}
}
System.out.println("best move " + bestMove.row + bestMove.col);
return bestMove;
}`
and of course the minimax()
`int minimax(Cell cell[][], int depth, boolean isMax)
{
int score = evaluate(cell);
// If Maximizer has won the game return his/her
// evaluated score
if (score == 10)
return score-depth;
// If Minimizer has won the game return his/her
// evaluated score
if (score == -10)
return score+depth;
// it is a tie
if (isMovesLeft(cell)==false)
return 0;
// If this maximizer's move
if (isMax)
{
int best = -1000;
// Traverse all cells
for (int i = 0; i<3; i++)
{
for (int j = 0; j<3; j++)
{
// Check if cell is empty
if (cell[i][j].getToken()==' ')
{
// Make the move
cell[i][j].setToken('X');
// Call minimax recursively and choose
best = java.lang.Math.max( best,
minimax(cell, depth+1, isMax) );
// Undo the move
cell[i][j].setToken(' ');
}
}
}
return best;
}
// If this minimizer's move
else
{
int best = 1000;
// Traverse all cells
for (int i = 0; i<3; i++)
{
for (int j = 0; j<3; j++)
{
// Check if cell is empty
if (cell[i][j].getToken()==' ')
{
// Make the move
cell[i][j].setToken('O');
best = java.lang.Math.min(best,
minimax(cell, depth+1, !isMax));
// Undo the move
cell[i][j].setToken(' ');
}
}
}
return best;
}
}
`

IF Statement Checking (Not Working Properly)

randomEmpty() returns a random coordinate on the n x n grid that is empty (Method works). randomAdjacent() uses randomEmpty() to select an EMPTY coordinate on the map. Comparisons are then made to see if this coordinate has an VALID adjacent coordinate that is NON-EMPTY. The PROBLEM is that randomAdjacent does not always return the coordinates of space with an adjacent NON-EMPTY space. It will always return valid coordinates but not the latter. I can't spot the problem. Can someone help me identify the problem?
public int[] randomEmpty()
{
Random r = new Random();
int[] random = new int[2];
int row = r.nextInt(array.length);
int column = r.nextInt(array.length);
while(!(isEmpty(row,column)))
{
row = r.nextInt(array.length);
column = r.nextInt(array.length);
}
random[0] = row+1;
random[1] = column+1;
return random;
}
public int[] randomAdjacent()
{
int[] adjacentToX = new int[8];
int[] adjacentToY = new int[8];
int[] adjacentFrom = randomEmpty();
int count;
boolean isTrue = false;
boolean oneAdjacentNotEmpty = false;
while(!(oneAdjacentNotEmpty))
{
count = 0;
if(validIndex(adjacentFrom,1,-1))
{
adjacentToX[count] = adjacentFrom[0]+1;
adjacentToY[count] = adjacentFrom[1]-1;
count++;
}
if(validIndex(adjacentFrom,0,-1))
{
adjacentToX[count] = adjacentFrom[0];
adjacentToY[count] = adjacentFrom[1]-1;
count++;
}
if(validIndex(adjacentFrom,-1,-1))
{
adjacentToX[count] = adjacentFrom[0]-1;
adjacentToY[count] = adjacentFrom[1]-1;
count++;
}
if(validIndex(adjacentFrom,-1,0))
{
adjacentToX[count] = adjacentFrom[0]-1;
adjacentToY[count] = adjacentFrom[1];
count++;
}
if(validIndex(adjacentFrom,-1,1))
{
adjacentToX[count] = adjacentFrom[0]-1;
adjacentToY[count] = adjacentFrom[1]+1;
count++;
}
if(validIndex(adjacentFrom,0,1))
{
adjacentToX[count] = adjacentFrom[0];
adjacentToY[count] = adjacentFrom[1]+1;
count++;
}
if(validIndex(adjacentFrom,1,1))
{
adjacentToX[count] = adjacentFrom[0]+1;
adjacentToY[count] = adjacentFrom[1]+1;
count++;
}
if(validIndex(adjacentFrom,1,0))
{
adjacentToX[count] = adjacentFrom[0]+1;
adjacentToY[count] = adjacentFrom[1];
count++;
}
for(int i = 0; i < count; i++)
{
if(!(isEmpty(adjacentToX[i],adjacentToY[i])))
{
oneAdjacentNotEmpty = true;
isTrue = true;
}
}
if(isTrue)
break;
else
adjacentFrom = randomEmpty();
}
return adjacentFrom;
}
public boolean validIndex(int[] a,int i, int j)
{
try
{
Pebble aPebble = array[a[0]+i][a[1]+j];
return true;
}
catch(ArrayIndexOutOfBoundsException e)
{
return false;
}
}
public void setCell(int xPos, int yPos, Pebble aPebble)
{
array[xPos-1][yPos-1] = aPebble;
}
public Pebble getCell(int xPos, int yPos)
{
return array[xPos-1][yPos-1];
}
JUNIT Test Performed:
#Test
public void testRandomAdjacent() {
final int size = 5;
final Board board2 = new Board(size);
board2.setCell(1, 1, Pebble.O);
board2.setCell(5, 5, Pebble.O);
int[] idx = board2.randomAdjacent();
int x = idx[0];
int y = idx[1];
boolean empty = true;
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
if ((i == x && j == y) || i < 1 || j < 1 || i > size || j > size) {
continue;
}
if (board2.getCell(i, j) != Pebble.EMPTY)
empty = false;
}
}
assertFalse(empty);// NEVER gets SET TO FALSE
assertEquals(Pebble.EMPTY, board2.getCell(x, y));
}
As for the answer: I got carried away optimizing your code for readability. I'd think it's most likely
if (board2.getCell(i, j) != Pebble.EMPTY)
empty = false;
causing the problem as getCell operates in 1-based coordinates, but i, j are in 0-based.
You should think about your logic overall. The way I see it, your code might never terminate as randomEmpty() could keep returning the same field over and over again for an undetermined period of time.
I took the liberty to recode your if-if-if cascade into utility method easier to read:
public boolean hasNonEmptyNeighbor(int[] adjacentFrom) {
for(int i = -1; i <= 1; ++i) {
for(int j = -1; j <= 1; ++j) {
if(validIndex(adjacentFrom, i, j) //Still inside the board
&& // AND
!isEmpty(adjacentFrom[0]+i //not empty
,adjacentFrom[1]+j)) {
return true;
}
}
}
return false;
}
Given my previous comment about random() being not the best of choices if you need to cover the full board, your main check (give me an empty cell with a non-empty neighbor) could be rewritten like this:
public void find() {
List<Point> foundPoints = new ArrayList<Point>();
for(int i = 0; i < Board.height; ++i) { //Assumes you have stored your height
for(int j = 0; j < Board.width; ++j) { //and your width
if(isEmpty(i, j) && hasNonEmptyNeighbor(new int[]{i,j})) {
//Found one.
foundPoints.add(new Point(i, j));
}
}
}
//If you need to return a RANDOM empty field with non-empty neighbor
//you could randomize over length of foundPoints here and select from that list.
}

Image-mouse intersection / hover effect issue when using arrays

I'm working on a small exercise that involves making an interface of six die faces. The goal is to change the color of each die face when I hover over it. The issue I'm having is that I can only change the color of the first die face, not the proceeding. I've been reluctant to come here and ask because I feel my issue is so insignificant but I've been trying to get this to work for the last 4 days and I just can't figure it out. I feel there is something about iteration that I'm just quite not understanding yet.
Dice[] dice = new Dice[6];
void setup(){
size(600,100);
for(int i = 0; i < dice.length; i++){
dice[i] = new Dice(i*100,0,100,100);
}
imageMode(CORNER);
}
void draw(){
for(int i = 0; i < dice.length; i++){
for(int j = 0; j < dice.length; j++){
if(j!=i && dice[i].checkHover(mouseX,mouseY)){
dice[i].drawDice(i,true);
} else {
dice[i].drawDice(i,false);
}
}
}
}
class Dice{
PImage[] diceFace = new PImage[6];
PImage[] diceFaceHover = new PImage[6];
int x;
int y;
int w;
int h;
Dice(int bx, int by, int bw, int bh){
x = bx;
y = by;
w = bw;
h = bh;
for(int i = 0; i < dice.length; i++){//loads the images
diceFace[i] = loadImage(i+".png");
diceFaceHover[i] = loadImage(i+"h.png");
}
}
void drawDice(int i, boolean hover){
if(hover){
image(diceFaceHover[i],x,y,w,h);
} else {
image(diceFace[i],x,y,w,h);
}
}
boolean checkHover(float mx, float my){
if((mx > x && mx < w) && (my > y && my < h)){
return true;
} else {
return false;
}
}
}
I'll continue searching for a solution in the meantime.
You have bad condition for checking hover. Don't forget that w and h are same for all dices but you need position not size.
if( (mx > x & mx < x+w) && ( my >y && my < y+h ) )

How to find all connected numbers in an array?

Hey all, back again. Working on a dungeon generator and I'm actually surprising myself with the progress. Yet I still have a straggling room every now and then. I was wondering if there was a way to loop through an array and see if all the '1s' (the floor tiles) are connected, and if not, how to connect them.
Thanks!
EDIT: The array is randomly filled with rooms and corridors; here's the code:
import java.util.Random;
public class Level
{
Random random = new Random();
int[][] A = new int[100][100];
int minimum = 3;
int maximum = 7;
int xFeature = 0;
int yFeature = 0;
private void feature()
{
int i = 0;
while(i>=0)
{
xFeature = random.nextInt(100-1) + 1;
yFeature = random.nextInt(100-1) + 1;
if(A[xFeature][yFeature]==1)//||A[xFeature++][yFeature]==1||A[xFeature][yFeature--]==1||A[xFeature][yFeature++]==1)
break;
i++;
}
}
private void room()
{
int safeFall = 0;
int xCoPLUS = minimum + (int)(Math.random()*minimum);
int yCoPLUS = minimum + (int)(Math.random()*minimum);
if(yCoPLUS >= xCoPLUS)
{
for(int across = xFeature; across < xFeature+xCoPLUS+2; across++)
{
for(int vert = yFeature; vert < yFeature+yCoPLUS+1; vert++)
{
if(A[vert][across] == 0)
safeFall++;
else
break;
}
}
}
if(yCoPLUS < xCoPLUS)
{
for(int across = xFeature; across < xFeature+xCoPLUS+1; across++)
{
for(int vert = yFeature; vert < yFeature+yCoPLUS+2; vert++)
{
if(A[vert][across] == 0)
safeFall++;
else
break;
}
}
}
if((safeFall== (xCoPLUS+1) * (yCoPLUS+2)) || ((safeFall== (xCoPLUS+2) * (yCoPLUS+1))))
{
for(int across = xFeature; across < xFeature+xCoPLUS; across++)
{
for(int vert = yFeature; vert < yFeature+yCoPLUS; vert++)
{
A[vert][across] = 1;
}
}
}
}
private void corridor()
{
int xCoONE = xFeature;
int yCoONE = yFeature;
int xCoTWO = random.nextInt(10)+10;
int yCoTWO = random.nextInt(10)+10;
while(xCoONE > xCoTWO)
{
A[xCoONE][yCoONE] = 1;
xCoONE--;
}
while(xCoONE < xCoTWO)
{
A[xCoONE][yCoONE] = 1;
xCoONE++;
}
while(yCoONE > yCoTWO)
{
A[xCoONE][yCoONE] = 1;
yCoONE--;
}
while(yCoONE < yCoTWO)
{
A[xCoONE][yCoONE] = 1;
yCoONE++;
}
}
public Level()
{
firstroom();
for(int i = 0; i < 500; i++)
{
int x = random.nextInt(50);
feature();
if(x > 1)
room();
else
corridor();
}
troubleShoot();
}
So basically what happens when I create an object of this class is that a 100x100 array is filled with corridors and rooms determined by a random number. (well, a couple of them) But with how I have my room non-overlapping failsafe (safeFall in room()), I get stuck with a room that is one title out of reach every now and then.
The article Maze Generation Algorithm discusses several approaches to generating a maze. It includes links to Java examples.

Categories

Resources