Recursion (done) - java

I wrote this path finding algorithm that should be working, but I'm getting tons of java.lang.ArrayIndexOutOfBoundsException. The goal of the program is to find the shortest path from one point to another that costs the least. Here's my code:
public boolean travel(int[][] path, int cX, int cY, int eX, int eY)
{
boolean returned = false;
System.out.println("the current X position on the GRID is: "+cX+"the current y position on the GRID is: "+cY);
path[cX][cY]=1;
if(cost>lowestCost - grid[cX][cY]){
return false;
}
cost += grid[cX][cY];
if(cX>=eX && cY>=eY){
return true;
}
if(cX+1>=eX && cY+1<eY){
return false;
}
if(cY+1>=eY && cX+1<eX){
return false;
}
if(travel(path,cX+1,cY+1,eX,eY)==true){
returned=true;
replace(newBest, path);
}
if(travel(path,cX,cY+1,eX,eY)==true){
returned=true;
replace(newBest, path);
}
if(travel(path,cX+1,cY,eX,eY)==true){
returned=true;
replace(newBest, path);
}
return(returned);
}
cX is the current X position in the array, cY is the current Y position in the array, eX and eY are the destination coordinates. path[][] is the array used to store the path. If you have any answers please tell me! also don't suggest any other algorithms, just some edits to the actual code. grid[][] is the array that stores the cost to go from one to another. Thanks very much
if(travel(newBest,0,0,rows,columns)==true)
{
lowestCost=cost;
}
This is how I call the method to find the shortest path.
This is the entire applet:
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class GridWorld extends Applet implements Runnable, MouseListener, KeyListener, MouseMotionListener
{
public int worldx;
public int worldy;
public int columns;
public int rows;
public int destX, destY;
public int cost, lowestCost;
public boolean sizeD;
public int[][] grid;
public int[][] prevBest;
public int[][] newBest;
Graphics bufferGraphics; //Set up double buffer
Image offscreen;
Thread thread;//Sets up a Thread called thread
public void init()
{
worldx=1000;
worldy=1000;
cost=0;
lowestCost=5000;
sizeD=false;
columns=5;
rows=5;
destX=0;
destY=0;
grid= new int[rows][columns];
prevBest= new int[rows][columns];
newBest = new int[rows][columns];
offscreen = createImage(worldx,worldy); //create a new image that's the size of the applet DOUBLE BUFFER SET UP
bufferGraphics = offscreen.getGraphics(); //set bufferGraphics to the graphics of the offscreen image. DOUBLE BUFFER SET UP
addKeyListener(this);//setup all the listeners
addMouseListener(this);//setup all the listeners
addMouseMotionListener(this);//setup all the listeners
thread = new Thread(this); //constructs a new thread
thread.start(); //starts the thread
}//init()
public void fillGrid()
{
prevBest= new int[rows][columns];
newBest = new int[rows][columns];
lowestCost = 0;
for(int ro = 0;ro<rows;ro++)
{
for(int col = 0;col<columns;col++)
{
grid[ro][col]=(int)(Math.random()*100);
newBest[ro][col]=0;
prevBest[ro][col]=0;
if(ro==col)
{
prevBest[ro][col]=1;
lowestCost+=grid[ro][col];
}
}
}
destX=(rows-1);
destY=(columns-1);
}
public boolean baseCase(int ct, int lowct, int destR, int destC, int cX, int cY)
{
boolean returned=false;
if(ct>=lowct)
{
returned=true;
}
if(cX+1==rows)
{
returned=true;
}
if(cY+1==columns)
{
returned=true;
}
if(cX==destR && cY==destC)
{
returned=true;
}
return(returned);
}
public boolean isValid(int x, int y, int[][] path, int eX, int eY) {
//not valid if: cordinates are into grid dimensions
if (!((x >= 0 && x < grid.length) && (y >= 0 && y < grid.length)))
return false;
//valid if: not visited yet, or is destiny
if (path[x][y] == 0 || (x == eX && y == eY))
return true;
return true;
}
/*public int traverse(int steps, int destR, int destC, int curX, int curY)
{
int direction = 0;
if(cost>=lowestCost)
{
//System.out.println("Greater cost Base Case");
direction=4;
}
if(curX+1>=destR && curY+1<destC)
{
System.out.println("Reached the farthest row Base Case");
direction=1;
}
if(curY+1>=destC && curY+1<destR)
{
System.out.println("Reached the farthest columns Base Case");
direction=2;
}
if(curX+1>=destR && curY+1>=destC)
{
System.out.println("At destination Base Case");
direction=4;
}
switch(direction)
{
case 0: newBest[curX][curY]=1;
cost+=grid[curX][curY];
System.out.println("the current X position on the GRID is: "+curX+"the current y position on the GRID is: "+curY);
return(traverse(steps+1,destR,destC,curX+1,curY+1)); //diag
case 1: newBest[curX][curY]=1;
cost+=grid[curX][curY];
return(traverse(steps+1,destR,destC,curX,curY+1)); //right
case 2: newBest[curX][curY]=1;
cost+=grid[curX][curY];
return(traverse(steps+1,destR,destC,curX+1,curY));//down
case 3:
return(5000);
case 4: System.out.println("the Grid's cost is: "+cost);
return(cost);
default: return(0);
}
}*/
public int[][] replace(int[][] p1, int[][] p2)
{
for(int col=0;col<columns;col++)
{
for(int ro=0;ro<rows;ro++)
{
p1[ro][col]=p2[ro][col];
}
}
return(p1);
}
public boolean travel(int[][] path, int cX, int cY, int eX, int eY)
{
boolean returned = false;
System.out.println("cX: "+ cX+" , cY: "+ cY+", eX: "+eX+", eY: " +eY+" Path 1 length: "+path[0].length+" Path 2 length: "+path[1].length);
path[cX][cY]=1;
if(cost>lowestCost - grid[cX][cY]){
System.out.println("1");
return false;
}
cost += grid[cX][cY];
}
if(travel(path,cX+1,cY+1,eX,eY)==true && isValid(cX+1,cY+1,newBest,eX,eY)){
System.out.println("the current X position on the GRID is: "+cX+"the current y position on the GRID is: "+cY);
returned=true;
replace(newBest, path);
}
if(travel(path,cX,cY+1,eX,eY)==true && isValid(cX,cY+1,newBest,eX,eY)){
System.out.println("the current X position on the GRID is: "+cX+"the current y position on the GRID is: "+cY);
returned=true;
replace(newBest, path);
}
if(travel(path,cX+1,cY,eX,eY)==true && isValid(cX+1,cY,newBest,eX,eY)){
System.out.println("the current X position on the GRID is: "+cX+"the current y position on the GRID is: "+cY);
returned=true;
replace(newBest, path);
}
return(returned);
}
public void paint(Graphics g)
{// paint() is used to display things on the screen
setSize(worldx,worldy);
//clear the offscreen image
bufferGraphics.clearRect(0,0,worldx,worldy);
bufferGraphics.setColor(Color.black);
//bufferGraphics.fillRect(0,0,worldx,worldy);
if(sizeD==true)
{
if(travel(newBest,0,0,rows,columns)==true)
{
lowestCost=cost;
}
}
for(int ro = 0;ro<rows;ro++)
{
for(int col = 0;col<columns;col++)
{
if(sizeD==true)
{
if(newBest[ro][col]==1)
{
bufferGraphics.setColor(Color.red);
bufferGraphics.fillRect((50*col),(50*ro),50,50);
bufferGraphics.setColor(Color.black);
}
if(prevBest[ro][col]==1)
{
bufferGraphics.setColor(Color.gray);
bufferGraphics.fillRect((50*col),(50*ro),50,50);
bufferGraphics.setColor(Color.black);
}
bufferGraphics.drawLine(0,(50*ro),50*columns,(50*ro));
bufferGraphics.drawLine((50*col),0,(50*col),50*rows);
bufferGraphics.drawString(""+grid[ro][col],(50*ro)+25,(50*col)+25);
}
}
}
if(sizeD==false)
{
bufferGraphics.drawRect(200,300,100,100);
bufferGraphics.drawString("5",250,350);
bufferGraphics.drawRect(400,300,100,100);
bufferGraphics.drawString("10",450,350);
bufferGraphics.drawRect(600,300,100,100);
bufferGraphics.drawString("20",650,350);
}
g.drawImage(offscreen,0,0,worldx,worldy,this);//Draw the screen
}// paint()
public void mouseDragged(MouseEvent e) {
}
public void mouseMoved(MouseEvent e){
}
public void mousePressed(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
public void mouseEntered(MouseEvent e)
{
System.out.println("Mouse entered");
}
public void mouseExited(MouseEvent e)
{
System.out.println("Mouse exited");
}
public void mouseClicked(MouseEvent e)
{
System.out.println("Mouse clicked (# of clicks: "+ e.getClickCount() + ")");
int mX=e.getX();
int mY=e.getY();
if(new Rectangle(200,300,100,100).contains(mX,mY) && sizeD==false)
{
columns=5;
rows=5;
grid= new int[rows][columns];
fillGrid();
sizeD=true;
}
if(new Rectangle(400,300,100,100).contains(mX,mY) && sizeD==false)
{
columns=10;
rows=10;
grid= new int[rows][columns];
fillGrid();
sizeD=true;
}
if(new Rectangle(600,300,100,100).contains(mX,mY) && sizeD==false)
{
columns=20;
rows=20;
grid= new int[rows][columns];
fillGrid();
sizeD=true;
}
}
public void keyPressed( KeyEvent event )
{
String keyin; // define a non‐public variable to hold the string representing the key input
keyin = ""+event.getKeyText( event.getKeyCode());
System.out.println("Key pressed "+keyin);
}//keyPressed()
public void keyReleased( KeyEvent event )
{
String keyin;
keyin = ""+event.getKeyText( event.getKeyCode());
System.out.println ("Key released: "+ keyin);
}//keyReleased()
public void keyTyped( KeyEvent event )
{
char keyin;
keyin = event.getKeyChar(); //getKeyChar() returns the character of the printable key pressed.
System.out.println ("Key Typed: "+ keyin);
}//keyTyped()
public void update (Graphics g)
{
paint(g);
}//Update the graphics
public void run()
{
while(true) // this thread loop forever and runs the paint method and then sleeps.
{
repaint();
try {
thread.sleep(50);
}
catch (Exception e){ }
}//while
}// run()
}//Applet

You are getting tons of java.lang.ArrayIndexOutOfBoundsException because you didn't proper manage the flow to enter the last three if-block. The code will enter the last three if-block since you only return the state at return(returned); even you have set boundary check (first two if-block). So, path[cX][cY] and grid[cX][cY] may encounter index out of bound when cX and cY is a large value (depends on the index setup of path and grid).
Also, the checking logic for first four if-block is not in correct order and you should return state when the condition meet.
The first four if-block should rearrange as:
if(cost>=lowestCost){
return false;
}
if(cX==eX && cY==eY){
return true;
}
if(cX+1>=eX && cY+1<eY){
return false;
}
if(cY+1>=eY && cX+1<eX){
return false;
}
BTW, please ensure your boundary checking (cX+1>=eX && cY+1<eY and cY+1>=eY && cX+1<eX) is correct. This will makes the code cannot access points in ([eX-1,eX],[0,eY-2]) and ([0,eX-2],[eY-1,eY]).
One more point, you might get trouble for cost>=lowestCost for rare cases like all possible shortest path has cost equal to pre-set value of lowestCost. One way to handle this is remove equal sign.
One more again, you might get trouble for cost>=lowestCost for extreme cases like cost = Integer.MAX_VALUE + 1. To handle this, you may try
if(cost>lowestCost - grid[cX][cY]){
return false;
}
cost += grid[cX][cY];

Here is a recursive solution with backtraking, code is explained:
public class FindShortestPath {
//the map is represented as a graph, and the graph is represented as an adyacent matrix
int map[][] = {{-1, 1, 1, 20}, //-1 = there is no edge between two vertexs
{-1, -1, 3, 1},
{-1, -1, -1, 1},
{-1, -1, -1, -1}};
int distSol = Integer.MAX_VALUE; //we want to minimize this value
List<Integer> solution = new ArrayList<Integer>(); //path solution
public static void main(String[] args) {
new FindShortestPath().start();
}
void start() {
/* In this case we find shortest path from 0 to 3 */
findShortestPath(new boolean[map.length], 0, 3, 0, new ArrayList<Integer>());
System.out.println("Distance: " + distSol);
System.out.println(solution.toString());
}
/**
*
* #param visit auxiliary array to mark visited
* #param c actual vertex
* #param destiny destiny vertex
* #param distAct actual distance traveled
* #param path actual path traveled
*/
void findShortestPath(boolean visit[], int c, int destiny, int distAct, List<Integer> path) {
if (c == destiny) { //base case: we reach destiny
if (distAct <= distSol) { //check if actual distance is better than solution
distSol = distAct;
solution = new ArrayList<Integer>(path); //replace old solution
solution.add(destiny);
}
} else { //recursive case
path.add(c); //add actual vertex as a possible candidate into actual path
visit[c] = true;
for (int i = 0; i < map.length; i++) { //visit every adyacent vertex that was no visited yet
if (map[c][i] != -1 && !visit[i]) {
findShortestPath(visit, i, destiny, distAct + map[c][i], path);
}
}
path.remove(path.size() - 1); //remove this vertex from the path
visit[c] = false;
}
}
}
Output for this example:
Distance: 2
[0, 2, 3]
EDIT
The OP wants the map to be a grid:
public class FindShortestPath2 {
int lowestCost = Integer.MAX_VALUE;
int grid[][] = {{0, 9, 0, 0},
{ 0, 5, 1, 1},
{ 0, 0, 2, 0},
{ 0, 0, 0, 0}};
int sol[][] = new int[4][4]; //this is the path solution
public static void main(String[] args) {
new FindShortestPath2().start();
}
void start() {
travel(new int[4][4], 0, 0, 2, 2, 0);
System.out.println("Lowest cost: " + lowestCost);
printPath(sol); //print solution
}
public void travel(int[][] path, int cX, int cY, int eX, int eY, int cost) {
if (cX == eX && cY == eY) { //reach destiny cordinates
if (cost < lowestCost) {
lowestCost = cost;
path[cX][cY] = 1;
replace(sol, path);
}
} else {
path[cX][cY] = 1; //mark path
if (isValid(cX + 1, cY + 1, path, eX, eY)) { //move in diagonal (rigth-down)
travel(path, cX + 1, cY + 1, eX, eY, cost + grid[cX + 1][cY+1]);
}
if (isValid(cX, cY + 1, path, eX, eY)) { //move rigth
travel(path, cX, cY + 1, eX, eY, cost + grid[cX][cY+1]);
}
if (isValid(cX + 1, cY, path, eX, eY)) { //move down
travel(path, cX + 1, cY, eX, eY, cost + grid[cX + 1][cY]);
}
path[cX][cY] = 0; //unmark path
}
}
boolean isValid(int x, int y, int[][] path, int eX, int eY) {
//not valid if: cordinates are into grid dimensions
if (!((x >= 0 && x < grid.length) && (y >= 0 && y < grid.length)))
return false;
//valid if: not visited yet, or is destiny
if (path[x][y] == 0 || (x == eX && y == eY))
return true;
return true;
}
void replace(int[][] p1, int[][] p2) {
for (int col = 0; col < p1.length; col++) {
for (int ro = 0; ro < p1.length; ro++) {
p1[ro][col] = p2[ro][col];
}
}
}
void printPath(int[][] p) {
for (int col = 0; col < p.length; col++) {
for (int ro = 0; ro < p.length; ro++) {
System.out.print(p[col][ro] + " ");
}
System.out.println();
}
System.out.println();
}
}
Output for this example:
Lowest cost: 2
1 0 0 0
1 0 0 0
0 1 1 0
0 0 0 0

This may be off topic, but have you tried implementing Dijkstra's Algorithm in Java. In the following algorithm, the code u := vertex in Q with min dist[u], searches for the vertex u in the vertex set Q that has the least dist[u] value. length(u, v) returns the length of the edge joining (i.e. the distance between) the two neighbor-nodes u and v. The variable alt on line 17 is the length of the path from the root node to the neighbor node v if it were to go through u. If this path is shorter than the current shortest path recorded for v, that current path is replaced with this alt path. The previous array is populated with a pointer to the "next-hop" node on the source graph to get the shortest route to the source. (taken from Wikipedia's artice on Dijkstra's Algorithm. This work is not my own.
function Dijkstra(Graph, source):
dist[source] := 0 // Distance from source to source
for each vertex v in Graph: // Initializations
if v ≠ source
dist[v] := infinity // Unknown distance function from source to v
previous[v] := undefined // Previous node in optimal path from source
end if
add v to Q // All nodes initially in Q (unvisited nodes)
end for
while Q is not empty: // The main loop
u := vertex in Q with min dist[u] // Source node in first case
remove u from Q
for each neighbor v of u: // where v has not yet been removed from Q.
alt := dist[u] + length(u, v)
if alt < dist[v]: // A shorter path to v has been found
dist[v] := alt
previous[v] := u
end if
end for
end while
return dist[], previous[]
end function

Related

What's a good Graph class to implement my logical game board?

I'm creating a board game on my computer using Java Swing.
Here's an image of the Swing GUI. I realize that it's hard to read the text of the board squares in this image. The requirement is that the player can move clockwise or counter-clockwise on the outside of the board, and through a stockholders meeting.
When it came time to create a logical model of the game board, I ran into problems.
Here's an enlarged image of the lower left of the game board.
The first logical model I tried was a List of my AbstractSquare class.
package com.ggl.stockmarket.game.model.board;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import com.ggl.stockmarket.game.model.GameStatus;
import com.ggl.stockmarket.game.view.StockMarketFont;
public abstract class AbstractSquare {
public static final int IMAGE_WIDTH = 160;
public static final int IMAGE_HEIGHT = 192;
public static final Insets INSETS = new Insets(4, 2, 4, 2);
/**
* The key to the square in the bidirectional graph.
*/
protected Integer graphKey;
/**
* The direction to move on the next turn. +1 means clockwise, -1 means
* counter-clockwise, and zero means that an odd dice roll is clockwise, while
* an even dice roll is counter-clockwise.
*/
protected int direction;
/**
* The direction and distance to move the market. A positive integer means the
* market moves down. A negative integer means the market moves up.
*/
protected int marketAmount;
/**
* Pointer to the location in the List of the previous board square.
*/
protected int previousPointer;
/**
* Pointer to the locatio0n in the List of the next board square.
*/
protected int nextPointer;
/**
* The amount to multiply the stock. As an example; "1 for 1" multiplies the
* stock by 2.
*/
protected int stockMultiplier;
protected Color backgroundColor;
protected Dimension squareSize;
protected Rectangle boardLocation;
protected String multiplierText;
public Integer getGraphKey() {
return graphKey;
}
public void setGraphKey(Integer graphKey) {
this.graphKey = graphKey;
}
/**
* The direction to move on the next turn.
*
* #param roll - The dice roll total.
* #return +1 is clockwise and -1 is counter-clockwise.
*/
public int getDirection(int roll) {
if (direction == 0) {
if ((roll / 2 * 2) == roll) {
return -1;
} else {
return +1;
}
} else {
return direction;
}
}
public void setDirection(int direction) {
this.direction = direction;
}
public int getDirection() {
return direction;
}
public int getMarketAmount() {
return marketAmount;
}
public void setMarketAmount(int marketAmount) {
this.marketAmount = marketAmount;
}
public Color getBackgroundColor() {
return backgroundColor;
}
public void setBackgroundColor(Color backgroundColor) {
this.backgroundColor = backgroundColor;
}
public Rectangle getBoardLocation() {
return boardLocation;
}
public void setBoardLocation(Rectangle boardLocation) {
this.boardLocation = boardLocation;
}
public int getPreviousPointer() {
return previousPointer;
}
public void setPreviousPointer(int previousPointer) {
this.previousPointer = previousPointer;
}
public int getNextPointer() {
return nextPointer;
}
public void setNextPointer(int nextPointer) {
this.nextPointer = nextPointer;
}
public int getStockMultiplier() {
return stockMultiplier;
}
public void setStockMultiplier(int stockMultiplier) {
this.stockMultiplier = stockMultiplier;
}
public Dimension getSquareSize() {
return squareSize;
}
public void setSquareSize(Dimension squareSize) {
this.squareSize = squareSize;
}
public String getMultiplierText() {
return multiplierText;
}
public void setMultiplierText(String multiplierText) {
this.multiplierText = multiplierText;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((graphKey == null) ? 0 : graphKey.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AbstractSquare other = (AbstractSquare) obj;
if (graphKey == null) {
if (other.graphKey != null)
return false;
} else if (!graphKey.equals(other.graphKey))
return false;
return true;
}
public List<String> splitStockName(String name) {
List<String> list = new ArrayList<String>();
int pos = name.lastIndexOf(' ');
if (pos < 0) {
list.add(name);
} else {
list.add(name.substring(0, pos));
list.add(name.substring(pos + 1));
}
return list;
}
public BufferedImage drawOutsideImage() {
int width = squareSize.width;
int height = squareSize.height;
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D) bufferedImage.getGraphics();
drawOutsideSquare(g, width, height, INSETS);
drawMovementText(g, width, height);
drawMovementArrows(g, width, height);
g.dispose();
return bufferedImage;
}
protected BufferedImage drawOutsideImage(int width, int height, Insets insets) {
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D) bufferedImage.getGraphics();
drawOutsideSquare(g, width, height, insets);
drawMovementText(g, width, height);
drawMovementArrows(g, width, height);
return bufferedImage;
}
protected void drawOutsideSquare(Graphics2D g, int width, int height, Insets insets) {
g.setColor(Color.black);
g.fillRect(0, 0, width, height);
if (backgroundColor == null) {
g.setColor(Color.white);
} else {
g.setColor(backgroundColor);
}
g.fillRect(insets.left, insets.top, width - insets.right - insets.left, height - insets.bottom - insets.top);
}
protected void drawMovementText(Graphics2D g, int width, int height) {
Font directionFont = StockMarketFont.getBoldFont(16);
FontRenderContext frc = g.getFontRenderContext();
StringBuilder sb = new StringBuilder();
if (marketAmount < 0) {
sb.append("Down ");
sb.append(Math.abs(marketAmount));
} else if (marketAmount > 0) {
sb.append("Up ");
sb.append(marketAmount);
} else {
sb.append("Odd Even");
}
setTextColor(g);
TextLayout layout = new TextLayout(sb.toString(), directionFont, frc);
Rectangle2D bounds = layout.getBounds();
float fx = (float) (bounds.getX()) + (float) (width - bounds.getWidth()) * 0.5F;
float fy = (float) height - 10.0F;
layout.draw(g, fx, fy);
}
public void setTextColor(Graphics2D g) {
g.setColor(Color.black);
if ((backgroundColor != null) && (backgroundColor.equals(Color.blue))) {
g.setColor(Color.white);
}
}
protected void drawMovementArrows(Graphics2D g, int width, int height) {
if (direction == 0) {
int w = (width - 30) / 2;
int x = 10;
int y = height - 40;
drawArrow(g, x, y, +1, w);
x = width - w - 10;
drawArrow(g, x, y, -1, w);
} else {
int w = (width - 40);
int x = (width - w) / 2;
int y = height - 40;
drawArrow(g, x, y, direction, w);
}
}
protected void drawArrow(Graphics2D g, int x, int y, int direction, int length) {
// arrow thickness, arrow point height and width
int t = 4;
int h = 14;
int w = 20;
Polygon p = new Polygon();
if (direction > 0) {
g.fillRect(x + h, y, length - h, t);
p.addPoint(x + h, y - ((w - t) / 2));
p.addPoint(x + h, y + ((w + t) / 2));
p.addPoint(x, y + (t / 2));
} else {
g.fillRect(x, y, length - h, t);
p.addPoint(x + length - h, y - ((w - t) / 2));
p.addPoint(x + length - h, y + ((w + t) / 2));
p.addPoint(x + length, y + (t / 2));
}
g.fillPolygon(p);
}
public BufferedImage drawInsideImage(Insets insets) {
int width = squareSize.width;
int height = squareSize.height;
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D) bufferedImage.getGraphics();
g.dispose();
return bufferedImage;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(graphKey);
return builder.toString();
}
public abstract void execute(GameStatus gameStatus);
}
For the moment, ignore the graphKey field. The AbstractSquare class contains all the fields needed for all of the sqaures on the game board. No square uses all of the fields. Each sqaure uses the fields that are needed for the sqaure.
The problem is that the board cannot be represented by a simple graph. As you can see on the enlarged game board, your move direction is determined by the arrow at the bottom of the square you start your turn on. You can move from the start square in either direction, depending on whether the dice roll was odd or even.
When you land on a stocholders meeting square during your roll, you have the choice whether to continue moving on the outside of the board or move into the stockholders meeting, assuming you own at least one share of that stock.
After I decided a List of AbstractSquares wasn't going to work, I investigated directed graphs. I found this code on Geeks For Geeks
// Java program to implement Graph
// with the help of Generics
import java.util.*;
class Graph<T> {
// We use Hashmap to store the edges in the graph
private Map<T, List<T> > map = new HashMap<>();
// This function adds a new vertex to the graph
public void addVertex(T s)
{
map.put(s, new LinkedList<T>());
}
// This function adds the edge
// between source to destination
public void addEdge(T source,
T destination,
boolean bidirectional)
{
if (!map.containsKey(source))
addVertex(source);
if (!map.containsKey(destination))
addVertex(destination);
map.get(source).add(destination);
if (bidirectional == true) {
map.get(destination).add(source);
}
}
// This function gives the count of vertices
public void getVertexCount()
{
System.out.println("The graph has "
+ map.keySet().size()
+ " vertex");
}
// This function gives the count of edges
public void getEdgesCount(boolean bidirection)
{
int count = 0;
for (T v : map.keySet()) {
count += map.get(v).size();
}
if (bidirection == true) {
count = count / 2;
}
System.out.println("The graph has "
+ count
+ " edges.");
}
// This function gives whether
// a vertex is present or not.
public void hasVertex(T s)
{
if (map.containsKey(s)) {
System.out.println("The graph contains "
+ s + " as a vertex.");
}
else {
System.out.println("The graph does not contain "
+ s + " as a vertex.");
}
}
// This function gives whether an edge is present or not.
public void hasEdge(T s, T d)
{
if (map.get(s).contains(d)) {
System.out.println("The graph has an edge between "
+ s + " and " + d + ".");
}
else {
System.out.println("The graph has no edge between "
+ s + " and " + d + ".");
}
}
// Prints the adjancency list of each vertex.
#Override
public String toString()
{
StringBuilder builder = new StringBuilder();
for (T v : map.keySet()) {
builder.append(v.toString() + ": ");
for (T w : map.get(v)) {
builder.append(w.toString() + " ");
}
builder.append("\n");
}
return (builder.toString());
}
}
// Driver Code
public class Main {
public static void main(String args[])
{
// Object of graph is created.
Graph<Integer> g = new Graph<Integer>();
// edges are added.
// Since the graph is bidirectional,
// so boolean bidirectional is passed as true.
g.addEdge(0, 1, true);
g.addEdge(0, 4, true);
g.addEdge(1, 2, true);
g.addEdge(1, 3, true);
g.addEdge(1, 4, true);
g.addEdge(2, 3, true);
g.addEdge(3, 4, true);
// print the graph.
System.out.println("Graph:\n"
+ g.toString());
// gives the no of vertices in the graph.
g.getVertexCount();
// gives the no of edges in the graph.
g.getEdgesCount(true);
// tells whether the edge is present or not.
g.hasEdge(3, 4);
// tells whether vertex is present or not
g.hasVertex(5);
}
}
The problem with this code is that my AbstractSqare instances had no natural key that I could use to create the graph of the game board.
My question is, how can I use a graph to implement the logical model of my game board?
I need to determine the intermediate squares of a dice roll so I can animate the movement of the piece. I also need to ask the player whether or not to enter a stockholders meeting, again assuming the player owns at least one share of the stock.
After a week of trial and error, I came up with a modified version of the Graph class from Geeks For Geeks.
package com.ggl.stockmarket.game.model;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* This class creates a directed or a bidirectional graph.
*
* #author Copied from the website GeeksForGeeks. Modified heavily by Gilbert Le
* Blanc
*
* #param <T> - Class to be modeled as a graph..
*/
public class Graph<T> {
// We use a List to store the objects
private List<T> list = new ArrayList<>();
// We use a Hashmap to store the key and edges in the graph
private Map<Integer, List<Integer>> map = new HashMap<>();
/**
* This method adds the class object and returns an Integer
* key to the class object.
* #param e - The class object to add.
* #return The Integer key to the class object.
*/
public Integer add(T e) {
list.add(e);
return list.size() - 1;
}
/**
* This method retrieves the Integer key for the class object.
*
* #param e - The class object to test.
* #return The Integer key to the class object, or -1 if the class object is not
* in the List.
*/
public Integer getKey(T e) {
for (int i = 0; i < list.size(); i++) {
T g = list.get(i);
if (g.equals(e)) {
return i;
}
}
return -1;
}
public T get(int key) {
return list.get(key);
}
public int size() {
return list.size();
}
/**
* This method adds the vertexes and edge between the Integer source and
* destination keys.
*
* #param source - Source key.
* #param destination - Destination key.
* #param bidirectional - True if the edge is bidirectio0nal, false if the edge
* is in one direction.
*
*/
public void addEdge(Integer source, Integer destination, boolean bidirectional) {
if (!map.containsKey(source)) {
addVertex(source);
}
if (!map.containsKey(destination)) {
addVertex(destination);
}
map.get(source).add(destination);
if (bidirectional == true) {
map.get(destination).add(source);
}
}
/**
* This method adds an Integer vertex to the graph.
*
* #param s - Key to add as a vertex.
*/
public void addVertex(Integer s) {
map.put(s, new LinkedList<>());
}
// This function gives the count of vertices
public String getVertexCount() {
return "The graph has " + map.keySet().size() + " vertex";
}
// This function gives the count of edges
public String getEdgesCount() {
int count = 0;
for (Integer v : map.keySet()) {
count += map.get(v).size();
}
return "The graph has " + count + " edges.";
}
// This method returns the number of edges for a vertex
public int getEdgeCount(Integer key) {
return map.get(key).size();
}
// This method returns the edges for a vertex
public List<Integer> getEdges(Integer key) {
return map.get(key);
}
// This function gives whether a vertex is present or not.
public String hasVertex(Integer s) {
if (map.containsKey(s)) {
return "The graph contains " + s + " as a vertex.";
} else {
return "The graph does not contain " + s + " as a vertex.";
}
}
// This function gives whether an edge is present or not.
public String hasEdge(Integer s, Integer d) {
if (map.get(s).contains(d)) {
return "The graph has an edge between " + s + " and " + d + ".";
} else {
return "The graph has no edge between " + s + " and " + d + ".";
}
}
// Prints the adjacency list of each vertex.
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (Integer v : map.keySet()) {
builder.append(v.toString() + ": ");
for (Integer w : map.get(v)) {
builder.append(w.toString() + " ");
}
builder.append(System.lineSeparator());
}
return (builder.toString());
}
}
My inclination to store the AbstractSquare instances in an ArrayList turned out to be correct. What I was missing was a good way to store the edges and vertexes of the graph.
I stored each AbstractSquare in an ArrayList. As I added each AbstractSquare instace to the List, I saved the index of the instace as an edge. I then created vertices to connect the edges.
Here's a display of the first 13 square edges and their vertices. There are 120 logical squares on the board, so I'm not going to list all the vertices. I had to add the stockholders meeting squares to the logical model twice. Once to account for a clockwsie movement through the syockholders meeting and once to account for a counter-clockwise movement through the stockholders meeting.
0: 1 47
1: 0 2
2: 1 3
3: 2 4 111
4: 3 5
5: 4 6
6: 5 7
7: 6 8
8: 7 9
9: 8 10 48
10: 9 11
11: 10 12
12: 11 13
Square 0 can either move to square 47 or square 1. There are 48 squares along the outside of the game board. As you can see, a player in this part of the logical model can move in 2 or 3 directions.
Here's another part of the graph. This is one of the 8 stockholders meeting graphs.
111: 112
112: 113
113: 114
114: 115
115: 116
116: 117
117: 118
118: 119
119: 45
Once inside a stockholders meeting, the player can only move in one direction.
Here's a little bit of code from the factory class that builds the logical model of the board. The entire class is 862 lines. You can see how I save the edges and create the vertices of the graph.
// Position 0, starting at the bottom of the board,
// going clockwise;
StartSquare startSquare = createStartSquare();
Integer originKey = board.add(startSquare);
startSquare.setGraphKey(originKey);
startSquareGraphKey[0] = originKey;
BuySquare buySquare = createBuySquare(gameStatus, 3, +1, +1);
Integer key2 = board.add(buySquare);
board.addEdge(originKey, key2, true);
buySquare.setGraphKey(key2);
BuySquare buySquare2 = createBuySquare(gameStatus, 0, +1, -2);
Integer key = board.add(buySquare2);
board.addEdge(key2, key, true);
buySquare2.setGraphKey(key);
BuyLimitSquare buyLimitSquare = createBuyLimitSquare(gameStatus, 6, +1, +3, -1, 45);
key2 = board.add(buyLimitSquare);
board.addEdge(key, key2, true);
buyLimitSquare.setGraphKey(key2);
meetings[6] = buyLimitSquare;
Because I know I'm going to be asked, my logical model is separate from my view and my controller classes. I have each square draw itself, and I have classes that take each square image and locate them on the board image. I save the image rectangle of each square in the logical model, so I know where to draw the player pieces on the board image.
The point of this question and answer was to pass along my modified Graph class, in the hopes that it helps someone wrestling with a graph that's more complicated than the simple examples of graphs found in textbooks and online.

How do i check, if two Circles are intersecting in Processing?

So I'm currently learning Java, and i struggle a lot to get my Code working.
I made a "Fun" Code in which some circles are popping up and it calculates how many of them are on the Upper Side of the Screen. (I know, some silly code.)
I'm coding it in the "Processing" Environment, Language is Java.
Here's my main File:
Circle[] circles = new Circle[50];
int index = 0;
boolean finished = false;
void setup() {
size(900, 900);
background(0);
for(int i = 0; i < circles.length; i++) {
circles[i] = new Circle();
}
if(finished = true) {
}
}
void draw() {
if(index < circles.length) {
circles[index].show(circles);
index++;
} else {
finished = true;
}
}
void count(Circle[] arr) {
int n = 0;
for(Circle c : arr) {
if(c.getY() > height / 2) {
n++;
}
}
println(n);
}
And here's the "Problem" Circle class:
class Circle {
private int x, y;
private float r = random(10, 25);
Circle() {
this.x = Math.round(random(0 + r, width - r));
this.y = Math.round(random(0 + r, height - r));
}
public int getX() {
return this.x;
}
public int getY() {
return this.y;
}
public void show(Circle[] arr) {
if(isColliding(arr)) {
this.x = Math.round(random(0 + r, width - r));
this.y = Math.round(random(0 + r, height - r));
} else {
ellipse(this.x, this.y, r * 2, r * 2);
stroke(255);
fill(255);
}
}
public boolean isColliding(Circle[] arr) {
boolean result = false;
if(arr == null) {
result = false;
} else {
for(Circle c : arr) {
float d = dist(c.getX(), c.getY(), this.x, this.y);
if(d < r * 2) {
result = true;
println("Collision at: " + c.getX() + " " + c.getY());
break;
}
}
}
return result;
}
}
As you can see, i already have a isColliding Method, and the Outputs in the Console seem to be right, however it won't work in the show() Method, the Circles won't stop intersecting each other.
So how can i make it work, that the Position is re-calculated when it is colliding?
Are you sure your collision method works? Unless I'm missing something, it should ALWAYS return true as you're passing in an array that includes itself.
That aside, I'd start looking at how your show() logic is laid out. You are checking for overlap, and then assigning a new random position if it finds any. This new position could very likely be on a circle that has already been drawn and in a good position.
Put your re-positioning in a loop so that it checks to make sure that it didn't just place itself onto an existing circle.
public void show(Circle[] arr)
{
/*
You could potentially get into a situation where you will NEVER find an empty spot.
Add an escape method for the loop.
*/
int failLimit = 500;
while(failLimit-- > 0 && isColliding(arr))
{
this.x = Math.round(random(0 + r, width - r));
this.y = Math.round(random(0 + r, height - r));
}
ellipse(this.x, this.y, r * 2, r * 2);
stroke(255);
fill(255);
}
You could simplify this and make it a bit more efficient by spawning in circles one at a time, checking to make sure their positions are good then.

Can't find the NullPointerException Error

I'm trying to build a Snake Game, where the snake is eating square orbs.
Before, the program was running perfectly, but when I ran it a couple of days ago, it yelled at me for something about a NullPointerException. I tried looking for what caused it, and it was in my Snake class.
Here is the code for the main class:
Snake s;
Score score;
//Menu m;
int sc1 = 20;
PVector food;
void setup() {
size(700, 700);
//m = new menu;
//m.show();
s = new Snake();
score = new Score();
//m.startGame();
frameRate(10);
}
void pickLocation() {
int cols = width/sc1;
int rows = height/sc1;
food = new PVector(floor(random(cols-20)), floor(random(rows-20)));
food.mult(sc1);
}
void draw() {
background(51);
if (s.eat(food)) {
pickLocation();
score.addPoints(10);
}
pickLocation();
score.show();
s.update();
s.show();
s.death();
if (s.dead == true) {
score.highScores();
}
if (score.totalScore != s.i/10) {
score.totalScore = s.i * 10;
}
if (s.dead && score.totalScore < score.highScore) {
score.totalScore = 0;
}
fill(255, 0, 100);
rect(food.x, food.y, sc1, sc1);
}
void keyPressed() {
if (keyCode == UP) {
s.dir(0, -1);
} else if (keyCode == DOWN) {
s.dir(0, 1);
} else if (keyCode == RIGHT) {
s.dir(1, 0);
} else if (keyCode == LEFT) {
s.dir(-1, 0);
}
}
The menu I commented out right now.
The Score class:
class Score {
int totalScore = 0; //will add the total score to the
int highScore; //will hold the user's high score in it.
int tempScore; //will hold the user's score after the snake dies.
Score() {
}
//this method is used when the snake eats the
//food. Eating the food will give 10 points to it.
void addPoints(int x) {
totalScore = totalScore + x;
}
//this method will calculate to see if the user
//has a new high score, only if the snake has
//officially died.
void highScores() {
if (totalScore > highScore) {
text("new highscore!", height/2, width/2);
highScore = totalScore;
totalScore = 0;
}
}
void show() {
text("Score: " + totalScore, 20, 20);
text("High Score: " + highScore, 20, 40);
}
}
And finally, my Snake class, where the problem is located at:
class Snake {
float x, y;
float xSpeed = 1;
float ySpeed = 0;
int total = 0;
ArrayList<PVector> tail = new ArrayList<PVector>();
boolean dead = false;
int i = 0;
Snake() {
}
boolean eat (PVector pos) {
float d = dist(x, y, pos.x, pos.y);
if (d < 1) {
total++;
return true;
} else {
return false;
}
}
void dir(float x, float y) {
xSpeed = x;
ySpeed = y;
}
void death() {
for (i = 0; i < tail.size(); i++) {
PVector pos = tail.get(i);
float d = dist(x, y, pos.x, pos.y);
if (d < 1) {
println("starting over");
total = 0;
tail.clear();
dead = true;
} else {
dead = false;
}
}
}
void update() {
if (total > 0) {
if (total == tail.size() && !tail.isEmpty()) {
tail.remove(0);
}
tail.add(new PVector(x, y));
}
x = x + xSpeed * sc1;
y = y + ySpeed * sc1;
x = constrain(x, 0, width-sc1);
y = constrain(y, 0, height-sc1);
}
void show() {
fill(0, 255, 0);
for (PVector v : tail) {
rect(v.x, v.y, sc1, sc1);
}
rect(x, y, sc1, sc1);
//rect(x, y, w, h);
}
}
My question is, is there something who can recognize the error and what should I do to fix such an error, please.
You need to get into the habit of debugging your code to understand exactly what's going on. You know that this line is throwing the NPE:
float d = dist(x, y, pos.x, pos.y);
So next, you need to understand the values of every variable on that line. You could just print them out:
boolean eat (PVector pos) {
println("x: " + x);
println("y: " + y);
println("pos: " + pos);
float d = dist(x, y, pos.x, pos.y);
If you do this, you'll see this output:
x: 0.0
y: 0.0
pos: null
This tells you that your pos variable is null, which is what's causing your NullPointerException.
Now you can trace backwards through your code to understand why the eat() function is being given a null argument.
In the future, please narrow your problem down to a MCVE instead of posting your whole program.

Cannot get sprite to collide with map images (java)

I am creating a 2D game which the zombie moves with WASD keys and is supposed to collide with the walls and not enter them, as well as collide with the brains and removes them. Every type of code I have used does not create collision. I am using a zombie sprite sheet i found on google as well as 2 backgroundless images for walls and brains.
After I figure out collision, I then then to implement a autorun sequence to where it bounces around like a screensaver and does the same thing just automatically until all brains are collected.
The EZ is just a library that is utilized by UH Manoa, that can be found here: EZ Graphics
Main
import java.awt.Color;
import java.io.FileReader;
import java.util.Scanner;
public class ZombieMain {
static EZImage[] walls = new EZImage[500];
static EZImage[] sideWalls = new EZImage[500];
static EZImage[] brains = new EZImage[50];
static int wallsCount = 0;
static int sideWallsCount = 0;
static int brainsCount = 0;
/*public static void addWall(EZImage wall) {
walls[wallsCount] = wall;
wallsCount++;
}
public static void addCoin(EZImage brain) {
brains[brainsCount] = brain;
brainsCount++;
}*/
/*public static void CollisingCoin(EZImage me) {
int x = me.getXCenter();
int y = me.getYCenter();
for (int i = 0; i < brainsCount; i++) {
if ((brains[i].isPointInElement(me.getXCenter() - 30, me.getYCenter() - 30))
|| (brains[i].isPointInElement(me.getXCenter() + 30, me.getYCenter() - 30))
|| (brains[i].isPointInElement(me.getXCenter() - 30, me.getYCenter() + 30))
|| (brains[i].isPointInElement(me.getXCenter() + 30, me.getYCenter() + 30))) {
brains[i].translateTo(-20, -20);
System.out.println("You ate a brain!");
}
}
}*/
public static void main(String[] args) throws java.io.IOException {
//initialize scanner
Scanner fScanner = new Scanner(new FileReader("boundaries.txt"));
int w = fScanner.nextInt();
int h = fScanner.nextInt();
String inputText = fScanner.nextLine();
//create backdrop
EZ.initialize(w*33,h*32);
EZ.setBackgroundColor(new Color(0, 0,0));
Zombie me = new Zombie("zombieSheet.png", 650, 450, 65, 63, 10);
//set reading parameters and establish results of case readings
int row = 0;
while(fScanner.hasNext()) {
inputText = fScanner.nextLine();
for (int column = 0; column < inputText.length(); column++){
char ch = inputText.charAt(column);
switch(ch){
case 'W':
walls[wallsCount] = EZ.addImage("barbwire.jpg", column*32, row*32);
wallsCount++;
break;
case 'M':
sideWalls[wallsCount] = EZ.addImage("barb.jpg", column*32, row*32);
wallsCount++;
break;
case 'B':
brains[brainsCount] = EZ.addImage("brains.png", column*32, row*32);
brainsCount++;
break;
default:
// Do nothing
break;
}
//printed count of walls, side walls, and brains
System.out.println("W = " + wallsCount);
System.out.println("M = " + sideWallsCount);
System.out.println("B = " + brainsCount);
}
row++;
}
fScanner.close();
while (true) {
// check if going to collide with wall
// we want to check this before we actually move
// otherwise, we get "stuck" in a situation where we can't move
// if no collision, we can move
/*if (EZInteraction.isKeyDown('a')) {
if (!isCollisingWall(me, -2, 0)) {
me.translateBy(-2, 0);
}
} else if (EZInteraction.isKeyDown('d')) {
if (!isCollisingWall(me, 2, 0)) {
me.translateBy(2, 0);
}
} else if (EZInteraction.isKeyDown('w')) {
if (!isCollisingWall(me, 0, -2)) {
me.translateBy(0, -2);
}
} else if (EZInteraction.isKeyDown('s')) {
if (!isCollisingWall(me, 0, 2)) {
me.translateBy(0, 2);
}
}*/
me.go();
EZ.refreshScreen();
}
}
}
Sprite
public class Zombie {
EZImage zombieSheet;
int x = 0; // Position of Sprite
int y = 0;
int zombieWidth; // Width of each sprite
int zombieHeight; // Height of each sprite
int direction = 0; // Direction character is walking in
int walkSequence = 0; // Walk sequence counter
int cycleSteps; // Number of steps before cycling to next animation step
int counter = 0; // Cycle counter
Zombie(String imgFile, int startX, int startY, int width, int height, int steps) {
x = startX; // position of the sprite character on the screen
y = startY;
zombieWidth = width; // Width of the sprite character
zombieHeight = height; // Height of the sprite character
cycleSteps = steps; // How many pixel movement steps to move before changing the sprite graphic
zombieSheet = EZ.addImage(imgFile, x, y);
setImagePosition();
}
private void setImagePosition() {
// Move the entire sprite sheet
zombieSheet.translateTo(x, y);
// Show only a portion of the sprite sheet.
// Portion is determined by setFocus which takes 4 parameters:
// The 1st two numbers is the top left hand corner of the focus region.
// The 2nd two numbers is the bottom right hand corner of the focus region.
zombieSheet.setFocus(walkSequence * zombieWidth, direction, walkSequence * zombieWidth + zombieWidth, direction + zombieHeight);
}
public void moveDown(int stepSize) {
y = y + stepSize;
direction = 0;
if ((counter % cycleSteps) == 0) {
walkSequence++;
if (walkSequence > 6)
walkSequence = 0;
}
counter++;
setImagePosition();
}
public void moveLeft(int stepSize) {
x = x - stepSize;
direction = zombieHeight * 2;
if ((counter % cycleSteps) == 0) {
walkSequence--;
if (walkSequence < 0)
walkSequence = 6;
}
counter++;
setImagePosition();
}
public void moveRight(int stepSize) {
x = x + stepSize;
direction = zombieHeight;
if ((counter % cycleSteps) == 0) {
walkSequence++;
if (walkSequence > 6)
walkSequence = 0;
}
counter++;
setImagePosition();
}
public void moveUp(int stepSize) {
y = y - stepSize;
direction = zombieHeight * 3;
if ((counter % cycleSteps) == 0) {
walkSequence--;
if (walkSequence < 0)
walkSequence = 6;
}
setImagePosition();
counter++;
}
// Keyboard controls for moving the character.
public void go() {
if (EZInteraction.isKeyDown('w')) {
moveUp(2);
} else if (EZInteraction.isKeyDown('a')) {
moveLeft(2);
} else if (EZInteraction.isKeyDown('s')) {
moveDown(2);
} else if (EZInteraction.isKeyDown('d')) {
moveRight(2);
}
}
public void translateBy(int i, int j) {
// TODO Auto-generated method stub
}
public int getXCenter() {
// TODO Auto-generated method stub
return x;
}
public int getYCenter() {
// TODO Auto-generated method stub
return y;
}
public int getWidth() {
// TODO Auto-generated method stub
return 0;
}
public int getHeight() {
// TODO Auto-generated method stub
return 0;
}
}
EZElement provides a getBounds property, which returns a java.awt.Shape object; why is this important? Because the Java 2D Graphics API already provides some hit detection.
From this, we then need to determine the player shape's intersection with any other shapes. To do this, we need to wrap both shapes in a Area and use it to make the final determinations.
Area meArea = new Area(me.getBounds());
Area checkArea = new Area(elementToCheck.getBounds());
checkArea(meArea);
if (!checkArea.isEmpty()) {
//... We have collision
}
Obviously, this should all be wrapped up in some kind of method to handle the core functionality, but you could have a helper method which simply took two EZElements and return true/false if the collide
For brevity and testing, I stripped back your example, but the basic idea should continue to work
import java.awt.Color;
import java.awt.Shape;
import java.awt.geom.Area;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test {
private List<EZImage> brains = new ArrayList<>(25);
private Zombie me;
public static void main(String[] args) throws java.io.IOException {
new Test();
}
public Test() {
int w = 10;
int h = 10;
//create backdrop
EZ.initialize(w * 33, h * 32);
EZ.setBackgroundColor(new Color(0, 0, 0));
me = new Zombie("Zombie.png", 0, 0);
brains.add(EZ.addImage("Brains.png", (w * 33) / 2, (h * 32 / 2)));
while (true) {
detectCollision();
// check if going to collide with wall
// we want to check this before we actually move
// otherwise, we get "stuck" in a situation where we can't move
// if no collision, we can move
/*if (EZInteraction.isKeyDown('a')) {
if (!isCollisingWall(me, -2, 0)) {
me.translateBy(-2, 0);
}
} else if (EZInteraction.isKeyDown('d')) {
if (!isCollisingWall(me, 2, 0)) {
me.translateBy(2, 0);
}
} else if (EZInteraction.isKeyDown('w')) {
if (!isCollisingWall(me, 0, -2)) {
me.translateBy(0, -2);
}
} else if (EZInteraction.isKeyDown('s')) {
if (!isCollisingWall(me, 0, 2)) {
me.translateBy(0, 2);
}
}*/
me.go();
EZ.refreshScreen();
}
}
public boolean doesCollide(EZElement element, EZElement with) {
Area a = new Area(element.getBounds());
Area b = new Area(with.getBounds());
a.intersect(b);
return !a.isEmpty();
}
public void detectCollision() {
Iterator<EZImage> obstacles = brains.iterator();
while (obstacles.hasNext()) {
EZElement next = obstacles.next();
if (doesCollide(me.zombieSheet, next)) {
System.out.println("Me = " + me.getBounds().getBounds());
System.out.println("next = " + next.getBounds().getBounds());
EZ.removeEZElement(next);
obstacles.remove();
}
}
}
public class Zombie {
EZImage zombieSheet;
int x = 0; // Position of Sprite
int y = 0;
Zombie(String imgFile, int startX, int startY) {
x = startX; // position of the sprite character on the screen
y = startY;
zombieSheet = EZ.addImage(imgFile, x, y);
setImagePosition();
}
public Shape getBounds() {
return zombieSheet.getBounds();
}
private void setImagePosition() {
// Move the entire sprite sheet
zombieSheet.translateTo(x, y);
}
public void moveDown(int stepSize) {
y = y + stepSize;
setImagePosition();
}
public void moveLeft(int stepSize) {
x = x - stepSize;
setImagePosition();
}
public void moveRight(int stepSize) {
x = x + stepSize;
setImagePosition();
}
public void moveUp(int stepSize) {
y = y - stepSize;
setImagePosition();
}
// Keyboard controls for moving the character.
public void go() {
if (EZInteraction.isKeyDown('w')) {
moveUp(2);
} else if (EZInteraction.isKeyDown('a')) {
moveLeft(2);
} else if (EZInteraction.isKeyDown('s')) {
moveDown(2);
} else if (EZInteraction.isKeyDown('d')) {
moveRight(2);
}
}
}
}
I would recommend that you give each entity (and block/tile) a collision box, then test if a specific entity's bounding box collided with another entity's bounding box, then make it so that the entities can't move in that direction until there isn't a bounding box in a direction, if that made any since.
Do the same for testing for the brains, though I recommend making an ArrayList of brains, and removing specific ones if that brain had been touched.

Simple Java 2d array maze sample

I am working or understanding how to create a simple java 2d maze that should look like this:
int [][] maze =
{ {1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,1,0,1,0,1,0,0,0,0,0,1},
{1,0,1,0,0,0,1,0,1,1,1,0,1},
{1,0,0,0,1,1,1,0,0,0,0,0,1},
{1,0,1,0,0,0,0,0,1,1,1,0,1},
{1,0,1,0,1,1,1,0,1,0,0,0,1},
{1,0,1,0,1,0,0,0,1,1,1,0,1},
{1,0,1,0,1,1,1,0,1,0,1,0,1},
{1,0,0,0,0,0,0,0,0,0,1,0,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1}
};
Ones this has been created the idea is to set a starting point and goal point and by using recursive depth first find the path. but must say i am having difficulties to create the maze.
Do you have any suggestion on how to do it?
Or perhaps a link to a tutorial?
The main focus for me right now is just to create the maze.
Maze implementation has a lot of variations.
All depends on which of there aspects you want to use?
Here is some start point Maze generation algorithm.
I tried to solve this problem in the past. Instead of many words how I tried this, I guess to show code snippet.
maze generator code:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Random;
public class MyMaze {
private int dimensionX, dimensionY; // dimension of maze
private int gridDimensionX, gridDimensionY; // dimension of output grid
private char[][] grid; // output grid
private Cell[][] cells; // 2d array of Cells
private Random random = new Random(); // The random object
// initialize with x and y the same
public MyMaze(int aDimension) {
// Initialize
this(aDimension, aDimension);
}
// constructor
public MyMaze(int xDimension, int yDimension) {
dimensionX = xDimension;
dimensionY = yDimension;
gridDimensionX = xDimension * 4 + 1;
gridDimensionY = yDimension * 2 + 1;
grid = new char[gridDimensionX][gridDimensionY];
init();
generateMaze();
}
private void init() {
// create cells
cells = new Cell[dimensionX][dimensionY];
for (int x = 0; x < dimensionX; x++) {
for (int y = 0; y < dimensionY; y++) {
cells[x][y] = new Cell(x, y, false); // create cell (see Cell constructor)
}
}
}
// inner class to represent a cell
private class Cell {
int x, y; // coordinates
// cells this cell is connected to
ArrayList<Cell> neighbors = new ArrayList<>();
// solver: if already used
boolean visited = false;
// solver: the Cell before this one in the path
Cell parent = null;
// solver: if used in last attempt to solve path
boolean inPath = false;
// solver: distance travelled this far
double travelled;
// solver: projected distance to end
double projectedDist;
// impassable cell
boolean wall = true;
// if true, has yet to be used in generation
boolean open = true;
// construct Cell at x, y
Cell(int x, int y) {
this(x, y, true);
}
// construct Cell at x, y and with whether it isWall
Cell(int x, int y, boolean isWall) {
this.x = x;
this.y = y;
this.wall = isWall;
}
// add a neighbor to this cell, and this cell as a neighbor to the other
void addNeighbor(Cell other) {
if (!this.neighbors.contains(other)) { // avoid duplicates
this.neighbors.add(other);
}
if (!other.neighbors.contains(this)) { // avoid duplicates
other.neighbors.add(this);
}
}
// used in updateGrid()
boolean isCellBelowNeighbor() {
return this.neighbors.contains(new Cell(this.x, this.y + 1));
}
// used in updateGrid()
boolean isCellRightNeighbor() {
return this.neighbors.contains(new Cell(this.x + 1, this.y));
}
// useful Cell representation
#Override
public String toString() {
return String.format("Cell(%s, %s)", x, y);
}
// useful Cell equivalence
#Override
public boolean equals(Object other) {
if (!(other instanceof Cell)) return false;
Cell otherCell = (Cell) other;
return (this.x == otherCell.x && this.y == otherCell.y);
}
// should be overridden with equals
#Override
public int hashCode() {
// random hash code method designed to be usually unique
return this.x + this.y * 256;
}
}
// generate from upper left (In computing the y increases down often)
private void generateMaze() {
generateMaze(0, 0);
}
// generate the maze from coordinates x, y
private void generateMaze(int x, int y) {
generateMaze(getCell(x, y)); // generate from Cell
}
private void generateMaze(Cell startAt) {
// don't generate from cell not there
if (startAt == null) return;
startAt.open = false; // indicate cell closed for generation
ArrayList<Cell> cells = new ArrayList<>();
cells.add(startAt);
while (!cells.isEmpty()) {
Cell cell;
// this is to reduce but not completely eliminate the number
// of long twisting halls with short easy to detect branches
// which results in easy mazes
if (random.nextInt(10)==0)
cell = cells.remove(random.nextInt(cells.size()));
else cell = cells.remove(cells.size() - 1);
// for collection
ArrayList<Cell> neighbors = new ArrayList<>();
// cells that could potentially be neighbors
Cell[] potentialNeighbors = new Cell[]{
getCell(cell.x + 1, cell.y),
getCell(cell.x, cell.y + 1),
getCell(cell.x - 1, cell.y),
getCell(cell.x, cell.y - 1)
};
for (Cell other : potentialNeighbors) {
// skip if outside, is a wall or is not opened
if (other==null || other.wall || !other.open) continue;
neighbors.add(other);
}
if (neighbors.isEmpty()) continue;
// get random cell
Cell selected = neighbors.get(random.nextInt(neighbors.size()));
// add as neighbor
selected.open = false; // indicate cell closed for generation
cell.addNeighbor(selected);
cells.add(cell);
cells.add(selected);
}
}
// used to get a Cell at x, y; returns null out of bounds
public Cell getCell(int x, int y) {
try {
return cells[x][y];
} catch (ArrayIndexOutOfBoundsException e) { // catch out of bounds
return null;
}
}
public void solve() {
// default solve top left to bottom right
this.solve(0, 0, dimensionX - 1, dimensionY -1);
}
// solve the maze starting from the start state (A-star algorithm)
public void solve(int startX, int startY, int endX, int endY) {
// re initialize cells for path finding
for (Cell[] cellrow : this.cells) {
for (Cell cell : cellrow) {
cell.parent = null;
cell.visited = false;
cell.inPath = false;
cell.travelled = 0;
cell.projectedDist = -1;
}
}
// cells still being considered
ArrayList<Cell> openCells = new ArrayList<>();
// cell being considered
Cell endCell = getCell(endX, endY);
if (endCell == null) return; // quit if end out of bounds
{ // anonymous block to delete start, because not used later
Cell start = getCell(startX, startY);
if (start == null) return; // quit if start out of bounds
start.projectedDist = getProjectedDistance(start, 0, endCell);
start.visited = true;
openCells.add(start);
}
boolean solving = true;
while (solving) {
if (openCells.isEmpty()) return; // quit, no path
// sort openCells according to least projected distance
Collections.sort(openCells, new Comparator<Cell>(){
#Override
public int compare(Cell cell1, Cell cell2) {
double diff = cell1.projectedDist - cell2.projectedDist;
if (diff > 0) return 1;
else if (diff < 0) return -1;
else return 0;
}
});
Cell current = openCells.remove(0); // pop cell least projectedDist
if (current == endCell) break; // at end
for (Cell neighbor : current.neighbors) {
double projDist = getProjectedDistance(neighbor,
current.travelled + 1, endCell);
if (!neighbor.visited || // not visited yet
projDist < neighbor.projectedDist) { // better path
neighbor.parent = current;
neighbor.visited = true;
neighbor.projectedDist = projDist;
neighbor.travelled = current.travelled + 1;
if (!openCells.contains(neighbor))
openCells.add(neighbor);
}
}
}
// create path from end to beginning
Cell backtracking = endCell;
backtracking.inPath = true;
while (backtracking.parent != null) {
backtracking = backtracking.parent;
backtracking.inPath = true;
}
}
// get the projected distance
// (A star algorithm consistent)
public double getProjectedDistance(Cell current, double travelled, Cell end) {
return travelled + Math.abs(current.x - end.x) +
Math.abs(current.y - current.x);
}
// draw the maze
public void updateGrid() {
char backChar = ' ', wallChar = 'X', cellChar = ' ', pathChar = '*';
// fill background
for (int x = 0; x < gridDimensionX; x ++) {
for (int y = 0; y < gridDimensionY; y ++) {
grid[x][y] = backChar;
}
}
// build walls
for (int x = 0; x < gridDimensionX; x ++) {
for (int y = 0; y < gridDimensionY; y ++) {
if (x % 4 == 0 || y % 2 == 0)
grid[x][y] = wallChar;
}
}
// make meaningful representation
for (int x = 0; x < dimensionX; x++) {
for (int y = 0; y < dimensionY; y++) {
Cell current = getCell(x, y);
int gridX = x * 4 + 2, gridY = y * 2 + 1;
if (current.inPath) {
grid[gridX][gridY] = pathChar;
if (current.isCellBelowNeighbor())
if (getCell(x, y + 1).inPath) {
grid[gridX][gridY + 1] = pathChar;
grid[gridX + 1][gridY + 1] = backChar;
grid[gridX - 1][gridY + 1] = backChar;
} else {
grid[gridX][gridY + 1] = cellChar;
grid[gridX + 1][gridY + 1] = backChar;
grid[gridX - 1][gridY + 1] = backChar;
}
if (current.isCellRightNeighbor())
if (getCell(x + 1, y).inPath) {
grid[gridX + 2][gridY] = pathChar;
grid[gridX + 1][gridY] = pathChar;
grid[gridX + 3][gridY] = pathChar;
} else {
grid[gridX + 2][gridY] = cellChar;
grid[gridX + 1][gridY] = cellChar;
grid[gridX + 3][gridY] = cellChar;
}
} else {
grid[gridX][gridY] = cellChar;
if (current.isCellBelowNeighbor()) {
grid[gridX][gridY + 1] = cellChar;
grid[gridX + 1][gridY + 1] = backChar;
grid[gridX - 1][gridY + 1] = backChar;
}
if (current.isCellRightNeighbor()) {
grid[gridX + 2][gridY] = cellChar;
grid[gridX + 1][gridY] = cellChar;
grid[gridX + 3][gridY] = cellChar;
}
}
}
}
}
// simply prints the map
public void draw() {
System.out.print(this);
}
// forms a meaningful representation
#Override
public String toString() {
updateGrid();
String output = "";
for (int y = 0; y < gridDimensionY; y++) {
for (int x = 0; x < gridDimensionX; x++) {
output += grid[x][y];
}
output += "\n";
}
return output;
}
// run it
public static void main(String[] args) {
MyMaze maze = new MyMaze(20);
maze.solve();
maze.draw();
}
}
It isn't the best solution, my task at this time was implement this algorithm by myself. It has clear comments.
Output:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
X * X ********* X ***** X X X
X * X * XXXXX * X * X * X X X X
X ***** X ***** X * X * X X X X
XXXXXXXXX * XXXXX * X * X X X X
X X ***** X * X * X X X
X X XXXXX * X * X * XXXXXXXXX X
X X X ***** X * X
X XXXXXXXXXXXXXXXXX * XXXXXXXXXXXXX
X ***************** X ***** X X
X * XXXXXXXXXXXXX * XXXXX * X X X
X ***** X X ********* X X X
XXXXX * X XXXXXXXXXXXXXXXXXXXXX X
X ***** X ***** X ***** X
X * XXXXXXXXXXXXX * X * XXXXX * X * X
X ************* X * X * X ***** X * X
XXXXXXXXXXXXX * X * X * X * XXXXX * X
X ***** X ***** X * X
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
I hope it will be useful as an illustration of some solution.
I know this is probably completely outdated, but...
First, you should realize that the underlying structure of such a maze is an undirected graph on a 2-dimensional grid. Now to create a so called "perfect maze", you just have to create any spanning tree of a full grid graph. And to do that there are plenty of algorithms, from random graph traversals (BFS, DFS) over algorithms derived from the known minimum-spanning tree algorithms (Kruskal, Prim, Boruvka, Reverse-Delete) to algorithms creating "uniformly random" spanning trees (Wilson, Aldous-Broder) to other algorithms that don't fit into these categories like "recursive division", "Eller's" etc.
I implemented lots of these algorithms based on a grid graph structure and you can find my implementation here:
https://github.com/armin-reichert/mazes
If I understand your question correctly, what I would do is:
1. create a board of a specific size (change all the coordinates to your desired number - in your example '1').
I wouldn't use a recursive function, because you will probably end up drawing the whole board (think about what will make the recursion stop).
you can create a function that receives a starting coordination, an ending coordination,
and the array (the board).
pseudo code of the function:
set a variable for the next direction of painting (set it to the starting coordination).
paint the next coordination 0.
while the next coordination != to the ending coordination:
paint the next coordination 0.
use Random to set the coordination to one of the 4 directions.
you should add limits (if the next coordination is a painted one/the border of the maze etc... chose a different coordination).
good luck!

Categories