I am currently working on an assignment to build a functioning Rubik's cube. The program does not need a GUI. But it must simulate a 3 X 3 cube with rotation behaviors and provide a graphical representation of the cube (I'm going with a flat lettered structure). My code has a class for facets which make a Face (another class) and then there is a cube class which contains the rotation methods.
I am having trouble creating/choosing an algorithm to use that would simulate the cube and all possible rotations accurately. I found a solution on this site referencing a paper proposing 7 different ways to do it (the link in below). But which method is the most intuitive/easy to code? And more importantly, which would best fit the behavior outlined below (in the pseudocode)?
I am having trouble understanding how I could use any method to account for the changes on each face at once especially when taking into account the behavior of the face rotations (as opposed to the rows and columns).
How would you represent a Rubik's Cube in code?
Rotation Pseudocode:
map each cube numbers 1-54, faces 1 – 4 are 1 – 36 while top face is 37 - 45 and bottom is 46 – 54
1 turn: clockwise = +9
1 turn: counterclockwise = -9 loops back to 45
1 turn: up = + 37
1 turn: down = -37 loops back to 46
2 turns: clockwise = +9(2)
2 turns: counterclockwise = -9(2)
2 turns: up = +37(2)
2 turns: down = -37(2)
3 turns: clockwise = +9(3)
3 turns: counterclockwise = -9(3)
3 turns: up = +37(3)
3 turns: down = -37(3)
This pseudocode does not account for the face changes.
Is there a better/easier way to do this, different from the method my pseudocode proposes? How do I account for the face changes?
Example: (front face, 1 turn, clockwise)
123 741
456 852
789 963
Note: I am leaning towards the 54 element vector but am unsure how to manipulate it.
Also, this is my first question so let me know if something is wrong (not enough info, too much, wrong topic, etc.)
Thank you!
Note: This is the code I am working with.
Facet Class:
public class Facets {
public Color color;
public Facets(Color color){
}
public enum Color {
B, G, R, Y, O, P
}
public String getName(){
return this.color.name();
}
}
Face Class:
import java.util.Arrays;
public class Face {
public Facets[] face;
/*public Face(Facets.Color color, Facets.Color[] array){
face = new Facets[9];
for(int i = 0; i < face.length; i++){
face[i] = new Facets(array[i]);
face[i] = new Facets(color);
}
}*/
public Face(Facets.Color color){
face = new Facets[9];
for(int i = 0; i < face.length; i++){
face[i] = new Facets(color);
}
}
public Face(Facets.Color[] array){
face = new Facets[9];
for (int i = 0; i < face.length; i++){
face[i] = new Facets(array[i]);
//face[i] = face[i].toString();
}
}
//Returns a textual representation of Face
public String printFace(){
StringBuilder faceString = new StringBuilder();
for(Facets f: face){
faceString.append(f.getName());
System.out.println(f.toString());
}
return faceString.toString();
}
public static void main(String[] args){
Face face = new Face(Facets.Color.B);
System.out.println(face.toString());
}
}
Cube Class:
public class Cube {
public Cube(Face front, Face right, Face back, Face left, Face top, Face bottom){
}
public Cube createCube(){
}
public Cube rotate(int row, int column, String direction, int turns){
/*Turns must be between 0 - 4
Row must be 1 or 2, column must be 1 or 2, direction must be clockwise, counterclockwise, up or down (0 means no turn, 1 is top row or left column; 2 is bottom row or right column)
*/
}
public int turns(){
}
public Cube row(){
}
public Cube column(){
}
public Cube clockwise(){
}
public Cube counterClockwise(){
}
public Cube up(){
}
public Cube down(){
}
public Cube random(Cube cube){
}
public Cube configuration(Cube cube){
}
}
Rubik's Cube:
public class RubiksCube {
public RubiksCube(Cube cube){
}
public RubiksCube(Face front, Face rightOfFront, Face back, Face leftOfFront, Face top, Face bottom){
}
//calls face and colors and initializes the arrays into a object
//default config: solid color on each side so each array, representing a face, is set to a solid color
public void createNewCube(){
}
public void rotation(Cube cube, int row, int column, String direction, int turns){
}
public Cube configuration(Cube cube){//should return 6 Faces? or cube?
return cube;
}
}
Think about what data structure makes it the easiest for you to conceptualize the cube. The different solutions in the link you provided all have pros and cons. You can have a more terse structure (i.e. the five integer representation) which takes up less memory and optimizes performance. But that representation might be difficult to work with if you are new to the problem. On the other end of the spectrum is an object oriented representation that models the way most people would think about a rubik cube. That might be the best approach for someone new to rubik's cubes.
After you have a data structure that makes sense to you, think about the different operations that can be performed on the cube. You can capture each of these moves in a function that alters the state of the data. If you have a real rubik cube to play with, figure out what all the different turns are and how they change the values of the rubik cube. Then try to model them in their own functions. i.e. a top wise turn would cause five of the six faces to change, right. It would cause four of the faces to receive the top row of their neighbour. And it would cause a shift in the top face as well. The bottom face would be uneffected.
If you are feeling overwhelmed with the complexity try to break your problem down into a smaller one. Maybe you can try to write a representation for a rubik cube that is 2 x 2 rather than 3 x 3. Once you've mastered the smaller problem, return to the larger one.
i just started to write some code to manipulate a cube.
package p;
import java.util.Arrays;
import static p.Facet.*;
enum Facet { // used for face, color, and operations
u(1),d(1),r(2),l(0),f(1),b(3); // maybe FRULBD instead?
Facet(int pad) {
this.pad=pad;
}
// u pad n+1
// l pad 0
// f pad n+1
// r pad 2(n+1)
// b pad 3(n+1) or at the bottom.
// d pad n+1
final int pad; // for formatting
}
class Face {
Face(int n,Facet facet) {
this.n=n;
this.facet=facet;
facets=new Facet[n][n];
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
facets[i][j]=facet;
}
Face(Face face) {
this.n=face.n;
this.facet=face.facet;
facets=new Facet[n][n];
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
facets[i][j]=face.facets[i][j];
}
#Override public String toString() {
return toString(0);
}
public String toString(int pad) {
StringBuffer stringBuffer=new StringBuffer();
for(int i=0;i<n;i++) {
stringBuffer.append(pad(pad,n));
for(int j=0;j<n;j++)
stringBuffer.append(facets[i][j]);
stringBuffer.append('\n');
}
return stringBuffer.toString();
}
String pad() {
return pad(facet.pad,n);
}
static String pad(int length) {
StringBuffer stringBuffer=new StringBuffer(length);
for(int i=0;i<length;i++)
stringBuffer.append(" ");
return stringBuffer.toString();
}
static String pad(int length,int n) {
return pad(length*(n+1));
}
#Override public int hashCode() {
final int prime=31;
int result=1;
result=prime*result+((facet==null)?0:facet.hashCode());
result=prime*result+Arrays.deepHashCode(facets);
result=prime*result+n;
return result;
}
#Override public boolean equals(Object obj) {
if(this==obj) return true;
if(obj==null) return false;
if(getClass()!=obj.getClass()) return false;
Face other=(Face)obj;
if(facet!=other.facet) return false;
if(!Arrays.deepEquals(facets,other.facets)) return false;
if(n!=other.n) return false;
return true;
}
static void rotateClockwise(Object[][] objects) {
Object temp;
int n=objects.length;
// For each concentric square around the middle of the matrix to rotate...
// This value will be used as (m, n) offset when moving in.
// Integer division by 2 will skip center if odd length.
for(int i=0;i<n/2;i++)
// for the length of this ring
for(int j=0;j<n-2*i-1;j++) {
temp=objects[i][i+j];
objects[i][i+j]=objects[n-i-j-1][i];
objects[n-i-j-1][i]=objects[n-i-1][n-i-j-1];
objects[n-i-1][n-i-j-1]=objects[i+j][n-i-1];
objects[i+j][n-i-1]=temp;
}
}
static void rotateCounterClockwise(Object[][] objects) {
int n=objects.length;
for(int i=0;i<n/2;i++) {
for(int j=i;j<n-i-1;j++) {
Object temp=objects[i][j];
objects[i][j]=objects[j][n-1-i]; // move values from right to top
objects[j][n-1-i]=objects[n-1-i][n-1-j]; // move values from bottom to right
objects[n-1-i][n-1-j]=objects[n-1-j][i]; // move values from left to bottom
objects[n-1-j][i]=temp;
}
}
}
final int n;
final Facet facet;
final Facet[][] facets;
}
class Cube {
Cube(int n) {
u=new Face(n,Facet.u);
d=new Face(n,Facet.d);
r=new Face(n,Facet.r);
l=new Face(n,Facet.l);
f=new Face(n,Facet.f);
b=new Face(n,Facet.b);
}
void rotatex(Face face,Face old) {
for(int i=0;i<face.n;i++)
for(int j=0;i<face.n;j++)
face.facets[i][j]=old.facets[i][j];
}
void rotateConterClockwise(Face f,Face u,Face l,Face d,Face r) {
for(int i=0;i<3;i++)
rotate(f,u,l,d,r);
}
void rotate(Face f,Face u,Face l,Face d,Face r) {
Face.rotateClockwise(f.facets);
Facet[] top=new Facet[u.n];
for(int i=0;i<u.n;i++)
top[i]=u.facets[u.n-1][i];
for(int i=0;i<u.n;i++) // move left to u
u.facets[u.n-1][i]=l.facets[u.n-i-1][u.n-1];
for(int i=0;i<u.n;i++) // bottom to left
l.facets[u.n-i-1][u.n-1]=d.facets[0][i];
for(int i=0;i<u.n;i++) // right to bottom
d.facets[0][i]=r.facets[i][0];
for(int i=0;i<u.n;i++)
r.facets[i][0]=top[i];
}
void op(Facet facet) {
switch(facet) {
case b:
rotate(b,u,r,d,l);
break;
case d:
rotate(d,f,l,b,r);
break;
case f:
rotate(f,u,l,d,r);
break;
case l:
rotate(l,u,b,d,f);
break;
case r:
rotate(r,u,f,d,b);
break;
case u:
rotate(u,d,l,f,r);
break;
}
}
void opInverse(Facet facet) {
switch(facet) {
case b:
rotateConterClockwise(b,u,r,d,l);
break;
case d:
rotateConterClockwise(d,f,l,b,r);
break;
case f:
rotateConterClockwise(f,u,l,d,r);
break;
case l:
rotateConterClockwise(l,u,b,d,f);
break;
case r:
rotateConterClockwise(r,u,f,d,b);
break;
case u:
rotateConterClockwise(u,d,l,f,r);
break;
}
}
#Override public int hashCode() {
final int prime=31;
int result=1;
result=prime*result+((b==null)?0:b.hashCode());
result=prime*result+((d==null)?0:d.hashCode());
result=prime*result+((f==null)?0:f.hashCode());
result=prime*result+((l==null)?0:l.hashCode());
result=prime*result+((r==null)?0:r.hashCode());
result=prime*result+((u==null)?0:u.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;
Cube other=(Cube)obj;
if(b==null) {
if(other.b!=null) return false;
} else if(!b.equals(other.b)) return false;
if(d==null) {
if(other.d!=null) return false;
} else if(!d.equals(other.d)) return false;
if(f==null) {
if(other.f!=null) return false;
} else if(!f.equals(other.f)) return false;
if(l==null) {
if(other.l!=null) return false;
} else if(!l.equals(other.l)) return false;
if(r==null) {
if(other.r!=null) return false;
} else if(!r.equals(other.r)) return false;
if(u==null) {
if(other.u!=null) return false;
} else if(!u.equals(other.u)) return false;
return true;
}
#Override public String toString() { //ulfrbd
StringBuffer stringBuffer=new StringBuffer(3*4*(u.n+1));
for(int i=0;i<u.n;i++) {
stringBuffer.append(u.pad());
for(int j=0;j<u.n;j++)
stringBuffer.append(u.facets[i][j]);
stringBuffer.append('\n');
}
for(int i=0;i<u.n;i++) {
stringBuffer.append(l.pad());
for(int j=0;j<u.n;j++)
stringBuffer.append(l.facets[i][j]);
stringBuffer.append(' ');
for(int j=0;j<u.n;j++)
stringBuffer.append(f.facets[i][j]);
stringBuffer.append(' ');
for(int j=0;j<u.n;j++)
stringBuffer.append(r.facets[i][j]);
stringBuffer.append(' ');
for(int j=0;j<u.n;j++)
stringBuffer.append(b.facets[i][j]);
stringBuffer.append('\n');
}
for(int i=0;i<u.n;i++) {
stringBuffer.append(d.pad());
for(int j=0;j<u.n;j++)
stringBuffer.append(d.facets[i][j]);
stringBuffer.append('\n');
}
return stringBuffer.toString();
}
final Face u,d,r,l,f,b;
}
public class Main {
public static void main(String[] args) {
Cube initial=new Cube(3);
Cube cube=new Cube(3);
System.out.println(cube.u.facets[0][0]);
System.out.println(cube.u);
System.out.println(cube);
for(int i=0;i<4;i++) {
cube.op(Facet.f);
System.out.println(cube);
}
// F' U L' U'
cube=new Cube(3);
int i=0;
do {
fpulpup(cube);
System.out.println(i);
System.out.println(cube);
i++;
} while(!cube.equals(initial));
}
private static void fpulpup(Cube cube) {
cube.opInverse(f);
cube.op(u);
cube.opInverse(l);
cube.opInverse(f);
}
}
Related
I'm having an issue and I'm not certain if it's recursion-based. I created a GUI maze that solves itself but the curser jumps over any recursion-traveled square instead of re-traveling the square. Even though it ultimately finds the goal, I want to show it's entire path but I can't stop the curser from jumping around.
I'm using Runnable to track the maze in real-time so I can see it bounce but without the recursion-travel keeping it bound, the recursive functions cease to work (it just bounces back and forth which, again, I don't quite understand.) I started java about three months ago in an accelerated program so I'm not sure if the issue is my understanding of recursion, or a simple addition to a method, or if I'll have to rewrite a large portion of code.
I included the whole code just in case but really it's an issue that's within the travel method or the visited method. Would the answer be to write an entirely new method that re-travels the changed "visited" string maze? I've been struggling with this for a bit and I just need some direction toward an answer.
import java.awt.*;
import javax.swing.*;
class extraCreditMaze extends JPanel implements Runnable { //uses Runnable to execute jPanel
private String [][] ratMaze = //string maze
{{"blocked","blocked","blocked","blocked","blocked","blocked","blocked","blocked"},
{"blocked","open","blocked","blocked","blocked","blocked","blocked","blocked"},
{"blocked","open","open","open","open","open","open","blocked"},
{"blocked","blocked","open","blocked","open","blocked","open","blocked"},
{"blocked","blocked","open","blocked","open","blocked","open","goal"},
{"blocked","open","open","open","blocked","open","open","blocked"},
{"blocked","blocked","blocked","open","open","open","blocked","blocked"},
{"blocked","blocked","blocked","blocked","blocked","blocked","blocked","blocked"}};
final private int SquareSize = 15;
final private int BoardSize = 17;
private boolean free = false;
int axisX = 1, axisY = 1;
public void paintComponent(Graphics color) //paint components for char
{
super.paintComponent(color);
for(int row = 0; row < ratMaze.length; row++)
{
for(int col = 0; col< ratMaze.length; col++)
{
if(row==axisX && col==axisY) //traveling curser = blue
{
color.setColor(Color.blue);
color.fillOval(col*15,row*15,15,15);
}
else if(ratMaze[row][col]=="blocked") //empty = black
{
color.setColor(Color.black);
color.fillRect(col*SquareSize,row*SquareSize,BoardSize,BoardSize);
}
else if(ratMaze[row][col]=="goal")
{
color.setColor(Color.red); //goal = red
color.fillOval(col*15,row*15,15,15);
}
else if(ratMaze[row][col]=="visited")
{
color.setColor(Color.green); //path traveled = green
color.fillOval(col*15,row*15,15,15);
}
else
{
color.setColor(Color.white); //empty space = white
color.fillRect(col*SquareSize,row*SquareSize,BoardSize,BoardSize);
}
}
}
}
public void run () //starts run at (1,1)
{
travel(1,1);
}
public boolean goal(int x, int y){ //method to check goal (true/false)
if(ratMaze[x][y]=="goal")
return true;
else
return false;
}
public void changedVisited(int x, int y) //method to change traveled
{
ratMaze[x][y] = "visited";
axisX = x;
axisY = y;
}
public boolean boundaries(int x, int y) //check boundaries
{
if(ratMaze[x][y]=="blocked")
return true;
else
return false;
}
public boolean visited(int x, int y) //check if visited
{
if(ratMaze[x][y]=="visited")
return true;
else
return false;
}
private void travel(int x, int y)
{
if(boundaries(x,y)) //makes sure it's within bounds
return;
if(visited(x,y)) //makes sure it hasn't already been visited
return;
if(goal(x,y)) //checks if it's the goal/changes boolean
{
free = true;
JOptionPane.showMessageDialog(this, "You did it, Dr. Cui!"); //fun message!
}
if(!free) //all recursion functions if free=false
{
changedVisited(x,y); //changes traveled block to "visited"
repaint(); //repaints visited
try {Thread.sleep(300); } catch (Exception e) { }//slows down the traveling curser
//I do not understand catch (Exception e)
travel(x-1,y); //recursive travel functions
travel(x+1,y);
travel(x,y-1);
travel(x,y+1);
}
}
}
public class runExtraCreditMaze {
public static void main (String [] args) { //JFrame panel and perimeters
JFrame output = new JFrame();
output.setSize(115, 150);
output.setTitle("The Rat Maze");
output.setLocationRelativeTo(null);
extraCreditMaze Maze = new extraCreditMaze();
output.setContentPane(Maze);
output.setVisible(true);
Thread runnable = new Thread(Maze); //Creates Runnable thread for Maze object
runnable.start(); //Starts Runnable thread of Maze object
}
}
Problem is, as you wrote with the "visited". You are missing an decision tree on what to do, when there is no valid move and you are not in the goal. You will need to allow your rat to back track itself. You will probably need to "free" the visited cells when returning from no valid move.
I will try to add some code samples when I get to IDE :)
update: this is very badly written, and it is a bit lagging. but it should work. It needs a bit of cleaning and verification... I reused your boolean variable, which is bad .. :) and switched the true/false. I will do a bit of cleaning up tomorrow just to leave a nicer answer, but I think you will manage to understand what is going on.
update2:I have cleaned it a bit. Important lessons here are as follows:
1) backtracking needs to be done when all 4 steps fails. When your rat have nowhere to go, you need to disqualify the cell from your shortest path (ratMaze[x][y]="open")
2) You need to change position of your rat, when you return from recursion call, but before you continue with next step into. You will also need to let your program know that you are returning from recursion (thus the isBacktracking)
private void repaintMaze(int x, int y) {
changedVisited(x, y); //changes traveled block to "visited"
repaint(); //repaints visited
isBacktracking = false;
try {
Thread.sleep(500);
} catch (Exception e) {
}
}
private boolean travel(int x, int y) {
if (goal(x, y)) //checks if it's the goal/changes boolean
{
JOptionPane.showMessageDialog(this, "You did it, Dr. Cui!");//fun message!
return true;
}
if (boundaries(x, y) || visited(x, y)) //makes sure it's within bounds
return false;
repaintMaze(x, y);
boolean result; //recursive travel functions
result = travel(x - 1, y);
if (result) {
return true;
}
if (isBacktracking) {
repaintMaze(x, y);
}
result = travel(x + 1, y);
if (result) {
return true;
}
if (isBacktracking) {
repaintMaze(x, y);
}
result = travel(x, y - 1);
if (result) {
return true;
}
if (isBacktracking) {
repaintMaze(x, y);
}
result = travel(x, y + 1);
if (result) {
return true;
}
if (isBacktracking) {
repaintMaze(x, y);
}
ratMaze[x][y] = "open";
isBacktracking = true;
return false;
}
you should also add exit on close to your JFrame :) It will stop the application once you click the X button on the window itself
output.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
So I am making a maze generation algorithm using depth first search. I do this in a 2D array of characters. Walls are represented as '#' & paths are '.' I create a new maze each time I call the "Maze" class.
I do this using three methods:
1 - hasUnvisited: checks if a cell has any cells around it that have not been visited before by the algorithm. Only checks cells +2 units away either up or down or left or right. (UDLR)
2 - pather: carves a path through a grid made of just walls. Checks if a path is in not on the edge of the grid. Then turns that cell into a path. Then checks if the cell hasUnvisited. If it does, it chooses a random direction to go (UDLR). If that direction +2's (ex: left +2, right+2..etc.) cell is clear then it changes the direction's +1 into a wall and then calls pather for the directions +2. (this will in turn clear the +2 in the direction randomly chosen and recursively repeat until path does not haveUnvisited.
3 - mazer: this method is for aesthetics purposes only, so I have commented out a lot of it to focus on the underlying issue. It basically makes a grid, initializes it with all '#'. Then calls pather with starting row (sr) and starting column(sc) 1,1. Then returns that grid of chars.
For some reason, however, my code makes this weird path of 'columns' at the bottom of the maze every time I run it. I am 99% sure it is in the "clipping" section of the code in 'pather' method, but I have no idea how to end the method and stop it from going out of bounds at that point.
As you can see, this is Java, but I've tried the code in C++ and it does the same thing so I'm pretty sure this is language-independent.
Here is the code:
import java.util.Random;
public class Maze {
private char[][] grid;
private final int WIDTH;
private final int HEIGHT;
Random randomGen = new Random();
//Checks to see if any of the surrounding cells are un
private boolean hasUnvisited (char[][] grid, int sr, int sc) {
if (sc+2 > HEIGHT-1) {
} else if (grid[sr][sc+2]=='#') {
return true;
}
if (sc-2 < 0) {
} else if (grid[sr][sc-2]=='#') {
return true;
}
if (sr+2 > WIDTH-1) {
} else if (grid[sr+2][sc]=='#') {
return true;
}
if (sr-2 < 0) {
} else if (grid[sr-2][sc]=='#') {
return true;
}
return false;
}
//Visits each cell, turns it to '.'
private void pather (char[][] grid, int sr, int sc) {
//Sets current cell to '.' to mark as visited
grid[sr][sc] = '.';
//"Clipping": if it is at edge of grid, don't carve any more, just return.
if (sr>WIDTH-2||sr<1||sc>HEIGHT-2||sc<1) {
return;
}
//Gets a number between 0-3
switch (randomGen.nextInt(4)) {
case 0:
if(hasUnvisited(grid,sr,sc)) {
if(sc+2>HEIGHT-1) {
}else if(grid[sr][sc+2]!='.'){
grid[sr][sc+1]='.';
pather(grid,sr,sc+2);
}
pather(grid,sr,sc);
}
break;
case 1:
if(hasUnvisited(grid,sr,sc)) {
if(sc-2<0) {
} else if(grid[sr][sc-2]!='.'){
grid[sr][sc-1]='.';
pather(grid,sr,sc-2);
}
pather(grid,sr,sc);
}
break;
case 2:
if(hasUnvisited(grid,sr,sc)) {
if(sr+2>WIDTH-1) {
}else if(grid[sr+2][sc]!='.'){
grid[sr+1][sc]='.';
pather(grid,sr+2,sc);
}
pather(grid,sr,sc);
}
break;
case 3:
if(hasUnvisited(grid,sr,sc)) {
if(sr-2<0) {
} else if(grid[sr-2][sc]!='.') {
grid[sr-1][sc]='.';
pather(grid,sr-2,sc);
}
pather(grid,sr,sc);
}
break;
}
}
//Returns a complete maze, gets the carved out paths from the pather function,
//then 'cleans it up' to return a useable maze format for the game.
private char[][] mazer() {
grid = new char[WIDTH][HEIGHT];
//Initialize Grid with all walls
for (int i=0;i<WIDTH;i++)
{
for (int j=0;j<HEIGHT;j++)
{
grid[i][j]= '#';
}
}
//Starting from row and column 1 and 1, respectively.
int sr=1,sc=1;
//Carve Out the Grid
pather(grid,sr,sc);
/*
//Draw Vertical Surrounding Walls
for (int j=0;j<HEIGHT;j++)
{
grid [0][j]= '#';
grid [WIDTH-1][j]= '#';
}
//Draw Horizontal Surrounding Walls
for (int j=0;j<WIDTH;j++)
{
grid [j][0]= '#';
grid [j][HEIGHT-1]= '#';
}
*/
//JUST FOR DEBUGGING:
for (int i=0;i<HEIGHT;i++)
{
for (int j=0;j<WIDTH;j++)
{
System.out.print(grid[j][i]);
}
System.out.println("");
}
//JUST FOR DEBUGGING ERASE IMMEDIATELY AFTER DONE WITH
return grid;
}
public Maze (int wIn, int hIn) {
WIDTH = wIn;
HEIGHT = hIn;
grid = mazer();
}
//After Debugging the maze:
public static void main(String[] args) {
Maze maze = new Maze(15,10);
}
}
I fix your solution, the problem is the corner cases checking.
The main changes:
sc+2 > HEIGHT-1 => sc+2 > HEIGHT-2
sr+2 > WIDTH-1 => sr+2 > WIDTH-2
The updated code:
import java.util.Random;
public class Maze {
private char[][] grid;
private final int WIDTH;
private final int HEIGHT;
Random randomGen = new Random();
//Checks to see if any of the surrounding cells are un
private boolean hasUnvisited (char[][] grid, int sr, int sc) {
if (sc+2 > HEIGHT-2) {
} else if (grid[sr][sc+2]=='#') {
return true;
}
if (sc-2 < 0) {
} else if (grid[sr][sc-2]=='#') {
return true;
}
if (sr+2 > WIDTH-2) {
} else if (grid[sr+2][sc]=='#') {
return true;
}
if (sr-2 < 0) {
} else if (grid[sr-2][sc]=='#') {
return true;
}
return false;
}
//Visits each cell, turns it to '.'
private void pather (char[][] grid, int sr, int sc) {
//Sets current cell to '.' to mark as visited
grid[sr][sc] = '.';
//"Clipping": if it is at edge of grid, don't carve any more, just return.
if (sr>WIDTH-2||sr<1||sc>HEIGHT-2||sc<1) {
return;
}
//Gets a number between 0-3
switch (randomGen.nextInt(4)) {
case 0:
if(hasUnvisited(grid,sr,sc)) {
if(sc+2>HEIGHT-2) {
}else if(grid[sr][sc+2]!='.'){
grid[sr][sc+1]='.';
pather(grid,sr,sc+2);
}
pather(grid,sr,sc);
}
break;
case 1:
if(hasUnvisited(grid,sr,sc)) {
if(sc-2<0) {
} else if(grid[sr][sc-2]!='.'){
grid[sr][sc-1]='.';
pather(grid,sr,sc-2);
}
pather(grid,sr,sc);
}
break;
case 2:
if(hasUnvisited(grid,sr,sc)) {
if(sr+2>WIDTH-2) {
}else if(grid[sr+2][sc]!='.'){
grid[sr+1][sc]='.';
pather(grid,sr+2,sc);
}
pather(grid,sr,sc);
}
break;
case 3:
if(hasUnvisited(grid,sr,sc)) {
if(sr-2<0) {
} else if(grid[sr-2][sc]!='.') {
grid[sr-1][sc]='.';
pather(grid,sr-2,sc);
}
pather(grid,sr,sc);
}
break;
}
}
//Returns a complete maze, gets the carved out paths from the pather function,
//then 'cleans it up' to return a useable maze format for the game.
private char[][] mazer() {
grid = new char[WIDTH][HEIGHT];
//Initialize Grid with all walls
for (int i=0;i<WIDTH;i++)
{
for (int j=0;j<HEIGHT;j++)
{
grid[i][j]= '#';
}
}
//Starting from row and column 1 and 1, respectively.
int sr=1,sc=1;
//Carve Out the Grid
pather(grid,sr,sc);
/*
//Draw Vertical Surrounding Walls
for (int j=0;j<HEIGHT;j++)
{
grid [0][j]= '#';
grid [WIDTH-1][j]= '#';
}
//Draw Horizontal Surrounding Walls
for (int j=0;j<WIDTH;j++)
{
grid [j][0]= '#';
grid [j][HEIGHT-1]= '#';
}
*/
//JUST FOR DEBUGGING:
for (int i=0;i<HEIGHT;i++)
{
for (int j=0;j<WIDTH;j++)
{
System.out.print(grid[j][i]);
}
System.out.println("");
}
//JUST FOR DEBUGGING ERASE IMMEDIATELY AFTER DONE WITH
return grid;
}
public Maze (int wIn, int hIn) {
WIDTH = wIn;
HEIGHT = hIn;
grid = mazer();
}
//After Debugging the maze:
public static void main(String[] args) {
Maze maze = new Maze(17,17);
}
}
OUTPUT(17*17):
#################
#.#.............#
#.#######.#####.#
#.......#...#...#
#######.#.#.#####
#.#.....#.#.#...#
#.#.#####.#.#.#.#
#...#.....#...#.#
#.#######.#####.#
#.......#.....#.#
#######.#######.#
#.......#.....#.#
#.#######.###.#.#
#.#.......#.#.#.#
#.#######.#.#.#.#
#.........#.....#
#################
But when it comes to even width and height, it output a little bit weird(but correct, anyway) because of the move is +2.
OUTPUT(18*18):
################
#.......#.....##
#######.###.#.##
#.#.....#...#.##
#.#.#####.###.##
#.#.#.......#.##
#.#.#######.#.##
#...#.....#.#.##
#.###.###.#.#.##
#...#.#.#.#.#.##
###.#.#.#.#.#.##
#.#.#.#...#.#.##
#.#.#.#.###.#.##
#.....#.....#.##
################
################
My suggestion is try to refactor your solution to generate the path by 1 step move. Your code never generate a path like
#####
#.#.#
#...#
#####
since it walks the map by 2 step move.
TLDR at bottom
I've been assigned a programming project at school to build a percolation model and i've come across an issue which has given me quite some confusion. First off, we were supposed to build an api to run a percolation simulation
public class Percolation{
private int grid[][];
public int size;
QuickFindUF unionFind;
//WeightedQuickUnionUF unionFind;
public Percolation(int n)
{
if(n<1){
throw new IllegalArgumentException ("grid must be larger than 0");
}
grid=new int[n][n];
size=n;
unionFind=new QuickFindUF(size*size);
//unionFind=new WeightedQuickUnionUF(size*size);
//initially set all to blocked
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
grid[i][j]=1;
}
}
}
public void open(int x, int y)
{
grid[x][y]=0;
//Check below to see if you can
//if you are not on the bottom row
if(y>0)
{
if(grid[x][y]==0 && grid[x][y-1]==0){unionFind.union(x+y*size,x+(y-1)*size);}
}
//check to see to the right (x->)
if(x<size-1){
if(grid[x][y]==0 && grid[x+1][y]==0){unionFind.union(x+y*size,x+1+y*size);}
}
//check if can union to the left
if(x>0)
{
if(grid[x][y]==0 && grid[x-1][y]==0){unionFind.union(x+y*size,x-1+y*size);}
}
//check for above
if(y<size-1){
if(grid[x][y]==0 && grid[x][y+1]==0){unionFind.union(x+y*size,x+(y+1)*size);}
}
}
public boolean isOpen(int x, int y)
{
if(x>=size || y>=size){return false;}
if(grid[x][y]==0){return true;}
return false;
}
public boolean isFull(int x, int y)
{
if(x>=size || y>=size){return false;}//if input is out of bounds
for(int i=0;i<size;i++){
if(unionFind.connected(x+y*size,i+((size-1)*size)))
return true;
}
return false;
}
public boolean percolates()
{
for(int i=0;i<size;i++){
for(int j=0;j<size;j++){
if(unionFind.connected(i,(size-1)*size+j)){
//System.out.println(i+" "+((size-1)*size+j));
return true;
}
}
}
return false;
}
}
Now, the book kindly provides the quickfindUF and WeightedQuickUnionUF. All the classmates i've talked to have gotten the expected results when timing with a PercolationStats class which we've been instructed to make but my results very greatly. Here's the class
class PercolationStats{
private Percolation perc;
private double[] array;
private int expCount;
public PercolationStats(int gridSize, int numOfExperiments){
if(gridSize <= 0 || numOfExperiments <=0)
throw new IllegalArgumentException("gridSize and numOfExperiments needs to be more than 0");
array=new double[numOfExperiments];
expCount=numOfExperiments;
for(int i=0;i<numOfExperiments;i++){
perc=new Percolation(gridSize);
int count=0;
while(!perc.percolates()){
int x=StdRandom.uniform(gridSize),y=StdRandom.uniform(gridSize);
if(!perc.isOpen(x,y)){
perc.open(x,y);
count++;
}
}
array[i]=(double) count/(gridSize*gridSize);
}
}
public double mean(){
return StdStats.mean(array);
}
public double stddev(){
return StdStats.stddev(array);
}
public double confidenceLo(){
return mean() - ((1.96 * stddev()) / Math.sqrt(expCount));
}
public double confidenceHi(){
return mean()+((1.96 * stddev()) / Math.sqrt(expCount));
}
public static void main(String[] args){
Stopwatch timer=new Stopwatch();
PercolationStats percStats=new PercolationStats(200,100);
System.out.println("mean: "+ percStats.mean() +"stddev: "+percStats.stddev()+" confidence Lo: "+percStats.confidenceLo()+" confidence hi: "+percStats.confidenceHi());
System.out.println(timer.elapsedTime());
percStats=new PercolationStats(200,100);
System.out.println("mean: "+ percStats.mean() +"stddev: "+percStats.stddev()+" confidence Lo: "+percStats.confidenceLo()+" confidence hi: "+percStats.confidenceHi());
percStats=new PercolationStats(2,100000);
System.out.println("mean: "+ percStats.mean() +"stddev: "+percStats.stddev()+" confidence Lo: "+percStats.confidenceLo()+" confidence hi: "+percStats.confidenceHi());
}
}
When I run this with the QuickFindUF, at percStats(200,100), it takes me about 7 seconds, and if I run it at the same 200,100 with WeightedQuickUnionUF, It takes about 50+ seconds?? I was quite certain that the weighted quick union was supposed to be faster, and it's not just a matter of getting unlucky with my horrendous worst case random number generator. I ran it quite a few times and still the results were about the same and I've been staring here at the code for quite a while and can't figure out why my code is so wrong..
TLDR
Correct results, incorrect timing. Slower api is faster for some reason and I can't figure out why. QuickFindUF faster than WeightedQuickUnionUF. (about 7-8 times faster). What am I doing wrong?
Haha I'm dumb. I saw online that others were using a virtual top so I added one and now it works fine :P
http://www.cstutoringcenter.com/problems/problems.php?id=103
For those who doesn't want to click it, it basically says there's a stepping stone, "-" and soldier "#", soldiers can only move right. If the soldier is behind another soldier, he must wait for the soldier to move first. The ending condition is when all soldiers reaches the end.
The number of ways 2 soldier can move across 5 stepping stones.
1) ##--- #-#-- -##-- -#-#- --##- --#-# ---##
2) ##--- #-#-- -##-- -#-#- -#--# --#-# ---##
3) ##--- #-#-- #--#- -#-#- --##- --#-# ---##
4) ##--- #-#-- #--#- -#-#- -#--# --#-# ---##
5) ##--- #-#-- #--#- #---# -#--# --#-# ---##
I'm using a breadth first search, with 5 stones, it's running within seconds, but with 10 stones, it's taking hours, the time is increasing exponentially with the depth. How can I deal with this?
My Codes:
States.java
import java.util.ArrayList;
public class State {
public int stones;
public Soldiers[] soldiers;
public String currentState ="";
public boolean visited = false;
public State(int stones,int Numsoldiers){
System.out.println(Numsoldiers);
this.stones = stones;
soldiers = new Soldiers[Numsoldiers];
System.out.println("length" + soldiers.length);
initState();
}
public State(int stones,Soldiers[] soldiers){
this.stones = stones;
this.soldiers = soldiers;
paintState();
}
public void initState(){
for(int i=0;i<soldiers.length;i++)
{
soldiers[i] = new Soldiers();
soldiers[i].position =i;
currentState+="#";
}
for(int j=soldiers.length;j<stones;j++)
{
currentState+="-";
}
}
private void paintState(){
for(int j=0;j<stones;j++)
{
currentState+="-";
}
char[] stateChar = currentState.toCharArray();
currentState = "";
for(int i=0;i<soldiers.length;i++){
stateChar[soldiers[i].position] = '#';
}
for(int k=0; k<stateChar.length;k++){
currentState += stateChar[k];
}
}
public void printState(){
System.out.println(currentState);
}
public ArrayList<State> getNextStates(){
ArrayList<State> States = new ArrayList<State>();
for(int i=0;i<soldiers.length;i++){
Soldiers[] newSoldiers = new Soldiers[soldiers.length];
for(int j=0;j<soldiers.length;j++){
newSoldiers[j] = new Soldiers(soldiers[j].position);
}
if(!((newSoldiers[i].position+1)==stones))
{
if((currentState.charAt((newSoldiers[i].position+1))=='-'))
{
newSoldiers[i].move();
States.add(new State(stones,newSoldiers));
}
}
}
if(States.size()==0)
{
TestSoldiers.count++;
}
return States;
}
}
Soldiers.java
public class Soldiers {
int position = 0;
public Soldiers(){
position =0;
}
public Soldiers(int pos){
position = pos;
}
public void move(){
position ++;
}
}
TestSoldiers.java
import java.util.LinkedList;
import java.util.Queue;
public class TestSoldiers {
public static int count=0;
public static void main(String[] args){
TestSoldiers t = new TestSoldiers();
}
public TestSoldiers()
{
State s = new State(10,3);
breadthFirstTraversal(s);
System.out.println(count);
}
public void breadthFirstTraversal(State rootNode){
Queue<State> q = new LinkedList<State>();
q.add(rootNode);
while(!q.isEmpty()){
State n = (State)q.poll();
n.printState();
for(State adj : n.getNextStates()){
q.add(adj);
}
}
}
}
How can I make it so that I will only consider each State once while maintaining the integrity of the total number of ways to end (counts in TestSoldiers.java)?
For those of you who want to modify the parameters, it's the new State(n,k) where n is the number of stones and k is the number of soldiers.
Memoization might come in handy.
The idea would be to run depth-first search to count the number of ways to get from the current state to the end, and store this result, then look up the already-calculated value if ever that state is repeated.
For instance, there are 2 ways to reach the end from -#-#-, so, storing this result when we get there via -##--, we could simply look up 2 when we get there via #--#-.
The simplest (but far from most efficient) way to store these would simply be to have a:
Map<Pair<Integer (Position1), Integer (Position2)>, Integer (Count)>
More generically, you could perhaps make that Pair a List.
A more efficient approach would be to have a bitmap where each bit corresponds to whether or not there's a soldier at some given position. So -#-#- would correspond to 01010, which could simply be stored in an int as 10 in decimal - if there are more than 64 stones (i.e. what would fit into a long), you could use a BitSet.
You might be better using combinatorics to compute the number of paths.
For example, suppose there are 2 soldiers and 5 steps.
Represent the distance the first soldier has moved by y, and the distance the second soldier has moved by x.
You are trying to count the number of monotonic paths from 0,0 to 3,3 such that y is never greater than x.
This is a well known problem and the answer is given by the Catalan numbers. In this case, the answer is given by the Catalan number for n=3, which is 5.
When you have more than 2 soldiers you will need to use multidimensional Catalan numbers. A useful guide and formula can be found on OEIS:
T(m, n) = 0! * 1! * .. * (n-1)! * (m * n)! / ( m! * (m+1)! * .. * (m+n-1)! )
My solution runs 10 positions in less than 1 second. The solution is quick and dirty, but the algorithm is what you should be interested in right?
The idea of my algorithm is:
manage a set of paths to compute. start with the path where both soldiers are at the left most positions.
if the set of paths to compute is not empty pick any path and remove it from the set.
if the path is terminated (both soldiers are at the most right positions) print the path. continue with 2.
extend the path by moving the head soldier if possible and put it into the set.
extend the path by moving the tail soldier if possible and put it into the set.
That's it.
public static void main(String[] args) {
List<Node> nodes = Node.newRootNode(10);
while (!nodes.isEmpty()) {
Node node = nodes.remove(0);
if (node.isLeaf()) node.printPath();
else {
if (node.headSoldierCanMove()) nodes.add(node.moveHeadSoldier());
if (node.tailSoldierCanMove()) nodes.add(node.moveTailSoldier());
}
}
}
static final class Node {
static List<Node> newRootNode(final int maxPos) {
return new ArrayList<Node>() {{
add(new Node(1, 2, maxPos, ""));
}};
}
private final int maxPos;
private final String path;
private int tailPos = 1;
private int headPos = tailPos + 1;
private Node(int tailPos, int headPos, int maxPos, String path) {
this.maxPos = maxPos;
this.tailPos = tailPos;
this.headPos = headPos;
this.path = addPath(path);
}
boolean tailSoldierCanMove() {
return tailPos < headPos - 1;
}
Node moveTailSoldier() {
return new Node(tailPos + 1, headPos, maxPos, path);
}
boolean headSoldierCanMove() {
return headPos < maxPos;
}
Node moveHeadSoldier() {
return new Node(tailPos, headPos + 1, maxPos, path);
}
void printPath() {
System.out.println(path);
}
boolean isLeaf() {
return headPos == maxPos && tailPos == headPos - 1;
}
private String addPath(String prefix) {
StringBuilder builder = new StringBuilder(prefix);
for (int pos = 1; pos <= maxPos; pos++) {
builder.append(tailPos == pos || headPos == pos ? "#" : "-");
}
return builder.append(" ").toString();
}
}
The method evalBoard() below takes brd, and copies it's parameters into pos. It then iterates through some possible values, making changes to pos by calling makeMove(). These calls to pos.makeMove are somehow changing the values inside brd as well. It can be seen clearly by stepping through the area I've marked in evalBoard. Why is this happening?
public Move evalBoard(Board brd) {
// evaluates current board and returns point of best move
Move best;
Move tmp = new Move(-1, -1, LOSE, brd.getPlayer());
if (brd.getPlayer() == PLAYER1) {
best = new Move(-1, -1, LOSE, PLAYER1);
} else {
best = new Move(-1, -1, WIN, PLAYER2);
}
// check if board is empty if so return optimal move
//
if (brd.isEmpty()) {
return (new Move(0, 0, 0, PLAYER1));
}
// iterate through possible moves
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 3; y++) {
if (brd.getSpace(x, y) == ' ') {
// copy all of brd to pos
pos=new Board(brd.getBoard(),brd.getPlayer());
tmp.setX(x);
tmp.setY(y);
tmp.setPlayer(brd.getPlayer());
/*
* problem can be seen by stepping through the next line. brd is changed at the same time.
*/
pos.makeMove(tmp);//Problem is here this call is changing brd as well
// evaluate for immediate win or loss
if (brd.getPlayer() == PLAYER1) {
if (pos.win()) {// immediate win
tmp.setValue(WIN);
return tmp;
} else { // not a winning move check it's recursive
// value
pos.setPlayer(PLAYER2);
tmp.copyFrom(evalBoard(pos));
if (tmp.getValue() >= best.getValue()) {
best.copyFrom(tmp);
}
}
} else {
if (pos.win()) {// immediate loss
tmp.setValue(LOSE);
return tmp;
} else {// not a losing move check it's recursive value
pos.setPlayer(PLAYER1);
tmp.copyFrom(evalBoard(pos));
if (tmp.getValue() <= best.getValue()) {
best.copyFrom(tmp);
}
}
}
}
}
}
return best;
}
Here's the Board class:
public class Board {
final boolean PLAYER1=true;
final boolean PLAYER2=false;
public char[][] board = new char[3][3];
public boolean Turn;
public Board(char[][] brd,boolean plr1){
board=brd;
Turn=plr1;
}
public char getPlayerChar(){
if (Turn==PLAYER1){
return 'X';
}else{
return 'O';
}
}
public char[][] getBoard(){
return board;
}
public void setBoard(char[][] brd){
board=brd;
}
public void displayBoard(){
for(int y=0;y<3;y++){
for(int x=0;x<3;x++){
System.out.print(board[x][y]);
if (x<2){
System.out.print(" | ");
}
}
System.out.println("");
if (y<2){
System.out.println("----------");
}
}
}
public void makeMove(Move mv){
//System.out.println("hit");
if (mv.getPlayer()==PLAYER1){
board[mv.getX()][mv.getY()]='X';
}else{
board[mv.getX()][mv.getY()]='O';
}
}
public void setPlayer(boolean plr){
Turn=plr;
}
public boolean getPlayer(){
return Turn;
}
public char getSpace(int x,int y){
return board[x][y];
}
public boolean isEmpty(){
for(int x=0;x<3;x++){
for(int y=0;y<3;y++){
if(!(board[x][y]==' ')){
return false;
}
}
}
return true;
}
public boolean win(){
char plrChar;
//returns true if board is winning position for current player
if (Turn==PLAYER1){
plrChar='X';
}else {
plrChar='O';
}
//Across
for(int y=0;y<3;y++){
if (board[0][y]+board[1][y]+board[2][y]==(plrChar+plrChar+plrChar)){
return true;
}
}
//Up/Down
for(int x=0;x<3;x++){
if (board[x][0]+board[x][1]+board[x][2]==(plrChar+plrChar+plrChar)){
return true;
}
}
//Diagonals
//top left to bottom right
if (board[0][0]+board[1][1]+board[2][2]==(plrChar+plrChar+plrChar)){
return true;
}
//bottom left to top right
if (board[0][2]+board[1][1]+board[2][0]==(plrChar+plrChar+plrChar)){
return true;
}
return false;
}
}
The problem is that when you do this:
pos=new Board(brd.getBoard(),brd.getPlayer());
you are creating a new Board (in pos) that shares its board array with the original Board (in brd). That's what the Board constructor is doing here:
public Board(char[][] brd,boolean plr1){
board=brd;
...
Naturally, since there is only one array, when you update it via one Board the changes are visible via the other Board.
If you don't want the old and new Board instances to share the same char array, then you should not assign like that. Instead, you should use a nested loop to copy the state from brd to board.
The Lesson to be learned
This is an example of a "leaky abstraction". The getBoard and setBoard methods allow code external to the Board to damage the state of a Board instance ... in a rather unexpected way. Non-leaky versions of these methods and the Board constructor would copy the contents of the array (may be to a new array) rather than allowing the Boards array instance to be accessible outside of the abstraction boundary.