Related
I'm having problems with my implementation of the MiniMax algoritm for my chess game. Most parts of it seems to work, but it either never makes the good moves or something is wrong with the evaluation (score based of both players active pieces) of them.
For example if I set up check (fool's mate for example) the ai does something random instead of killing the king. I really can't pin out what I'm doing wrong.
The class that evaluates the board, StandardBoardEvaluator, seems to work after some testing, so the problem is most likely somewhere within the MiniMax implementation. The game is made up from a class Board, which has and 2D array with 8x8 objects of my own class Square, which in itself has a reference to an Piece (that can be null, or any of the typical chess pieces).
In the algoritm i constantly makes new Board instances as going down the searchthree, which is why i made these "deep clone" constructors in Board and Square, so that does not seem to be the problem. Like this:
public Board(Board originalBoard) {
this.turnIsWhite = originalBoard.getTurnIsWhite();
winner = null;
squares = new Square[8][8];
for (int rank=0; rank<squares.length; rank++) {
for(int file=0; file<squares[rank].length; file++) {
squares[rank][file] = new Square(originalBoard.getSquare(posStringFromFileRank(rank, file)));
}
}
}
AND
public Square(Square originalSquare) {
this.pos = new String(originalSquare.getPos());
this.piece = originalSquare.getPiece();
}
I have an typical command class, MovePiece, for moving pieces. This uses another class, MoveCheck, to check if the move command is legal. MovePiece returns a boolean representing if the move is legal. Both these classes have been heavily tested and are working, so I don't think the problem is within these classes.
Here is the algoritm:
public class MiniMax implements MoveStrategy{
BoardEveluator bV;
MoveGenerator mGen;
int depth;
public MiniMax(int depth){
bV = new StandardBoardEvaluator();
mGen = new MoveGenerator();
this.depth = depth;
}
#Override
public MovePiece execute(Board board) {
MovePiece bestMove = null;
int lowestValue = Integer.MAX_VALUE;
int highestValue = Integer.MIN_VALUE;
int currentValue = 0;
String color = (board.getTurnIsWhite() ? "white" : "black");
System.out.println(color + " is evaluation best move with MiniMax depth " + depth);
List<MovePiece> allPossibleMoves = mGen.getLegalMoves(board, board.getTurnIsWhite());
for (MovePiece mp : allPossibleMoves){
Board tempBoard = new Board(board);
mp.setBoard(tempBoard);
if (mp.execute()){
currentValue = tempBoard.getTurnIsWhite() ? min(tempBoard, depth -1) : max(tempBoard, depth -1);
if (board.getTurnIsWhite() && currentValue >= highestValue){
highestValue = currentValue;
bestMove = mp;
}
else if (!board.getTurnIsWhite() && currentValue <= lowestValue){
lowestValue = currentValue;
bestMove = mp;
}
mp.unexecute();
}
}
return bestMove;
}
int min (Board board, int depth){
if (depth == 0 || board.getWinner() != null){
return bV.eveluate(board);
}
int lowestValue = Integer.MAX_VALUE;
List<MovePiece> legalMoves = mGen.getLegalMoves(board, board.getTurnIsWhite());
for (MovePiece mp : legalMoves){
Board tempBoard = new Board(board);
mp.setBoard(tempBoard);
if (mp.execute()){
int currentValue = max(tempBoard, depth - 1);
if (currentValue <= lowestValue){
lowestValue = currentValue;
}
mp.unexecute();
}
}
return lowestValue;
}
int max (Board board, int depth){
if (depth == 0 || board.getWinner() != null){
return bV.eveluate(board);
}
int highestValue = Integer.MIN_VALUE;
List<MovePiece> legalMoves = mGen.getLegalMoves(board, board.getTurnIsWhite());
for (MovePiece mp : legalMoves){
Board tempBoard = new Board(board);
mp.setBoard(tempBoard);
if (mp.execute()){
int currentValue = min(tempBoard, depth - 1);
if (currentValue >= highestValue){
highestValue = currentValue;
}
mp.unexecute();
}
}
return highestValue;
}
And the evalutor class
public class StandardBoardEvaluator implements BoardEveluator {
private int scorePlayer(Board board, boolean isWhite){
return pieceValue(board, isWhite) + mobolity(isWhite, board);
}
private int mobolity(boolean isWhite, Board board){
return (int) (board.getActiveSquares(isWhite).size() * 1.5);
}
private static int pieceValue(Board board, boolean isWhite){
int piceValueScore = 0;
for (Square square : board.getActiveSquares(isWhite)){
piceValueScore += square.getPiece().getPieceValue();
}
return piceValueScore;
}
#Override
public int eveluate(Board board) {
return scorePlayer(board, true) - scorePlayer(board, false);
}
}
Here is the MovePiece class:
private Square from;
private Square to;
private Board board;
private MoveCheck mCheck;
private RulesCheck rCheck;
private boolean done = false;
private Piece killed;
public MovePiece(Board board, String from, String to) {
this.board = board;
this.from = board.getSquare(from);
this.to = board.getSquare(to);
mCheck = new MoveCheck();
}
public MovePiece(Board board, Square from, Square to) {
this.board = board;
this.from = from;
this.to = to;
mCheck = new MoveCheck();
rCheck = new RulesCheck(board);
}
public void setBoard(Board board) {
this.board = board;
}
public Board getBoard() {
return board;
}
public Square getFrom() {
return from;
}
public Square getTo() {
return to;
}
public void setFrom(Square from) {
this.from = from;
}
public void setTo(Square to) {
this.to = to;
}
public void setFrom(String from) {
this.from = board.getSquare(from);
}
public void setTo(String to) {
this.to = board.getSquare(to);
}
#Override
public boolean execute() {
rCheck = new RulesCheck(board);
if (done) {
board.movePiece(from, to);
return true;
}
else if (mCheck.isLegal(board, from, to)){
if (to.getPiece() != null) {
killed = to.getPiece();
rCheck.winCheck(killed);
}
board.setGameOutput("Moved " + from.pieceToString() + " at " + from.getPos() + " - to " + to.getPos() + "(" + to.pieceToString() + ")");
board.movePiece(from, to);
rCheck.checkPromotion(to);
done = true;
return true;
}
return false;
}
#Override
public void unexecute() {
if (to.getPiece().getClass() == Pawn.class)
((Pawn) to.getPiece()).decreaseMoves();
board.movePiece(to, from);
if (killed != null) {
to.setPiece(killed);
}
}
The MoveCheck class merely looks if the move is legal for the piece (path is clear, target is an enemy or empty and so on), don't think it's relevant for my problem since the code is tested and works.
The piece value is declared as an int in the subclasses (all the types of pieces) of the abstract class Piece. 100 points for a pawn, 300 for bishop and knight, 500 for rook, 900 for queen and 10 000 for the king.
If anyone could help me figure out the problem i would be eternally grateful! Please let me know if you need to se some other code i haven't showed.
You haven't shared the MovePiece implementation neither the main game loop, but I indentified two possible problems inside MiniMax.execute method:
currentValue = tempBoard.getTurnIsWhite() ? min(tempBoard, depth -1) : max(tempBoard, depth -1)
According to the above code, you are assuming that the MinMax player will always be black, as it evaluates min for white and max for black. For a generic algorithm this is a wrong assumption, don't know if it works for you though.
Second thing is after calling mp.execute() and assigning bestMove = mp you call mp.unexecute(), so effectively call bestMove.unexecute() since the variables point to the same object.
Please consider the suggestions above and if it does not fix the problem, share the abovementioned implementation pieces.
(I hope this is not a duplicate as the many questions I came into do not fit my need)
I'm developping a 2D grid based game with 2 players with grid. There are two players: blue and red, each one places a stone in cells. So I want to find a path passing throught all cells with the same color back to the starting point, BUT ONLY if there is at least ONE cell that contains opponent's stone.
From the screenshot above: The red stones here in the upper right do not form a valid path. And those in the center are not forming a path neither even though that should be one.
I'm able to find a path but it is somehow broken, it doesn't work as expected.
EDIT:
Pather class
public class Pather {
private static final int MIN_PATH_LENGTH = 3;
public enum Neighbor{
UP_RIGHT(0,1,-1),
RIGHT(1,1,0),
DOWN_RIGHT(2,1,1),
DOWN(3,0,1),
DOWN_LEFT(4,-1,1),
LEFT(5,-1,0),
UP_LEFT(6,-1,-1),
UP(7,0,-1);
public int index, x, y;
Neighbor(int index, int x, int y){
this.index = index;
this.x = x;
this.y = y;
}
}
private static Neighbor[] neighbors = Neighbor.values();
public static ArrayList<Path> findPaths(Stone[][] gameBoard){
ArrayList<Path> paths = new ArrayList<>();
ArrayList<Point> checkedPoints = new ArrayList<>();
for (int i = 0; i < gameBoard.length ; i++) {
for (int j = 0; j < gameBoard[0].length; j++) {
if(gameBoard[i][j] != null){
//set the origin of a potential new path
ArrayList<Point> potentialPath = new ArrayList<>();
Point origin = new Point (i,j);
if(!checkedPoints.contains(origin)) {
potentialPath.add(origin);
checkedPoints.add(origin);
potentialPath = findPath(gameBoard, i, j, potentialPath, gameBoard[i][j].getPaint(), checkedPoints, Neighbor.RIGHT.index); //Changed from Neighbor.DOWN.index
if (potentialPath != null) {
paths.add(new Path(potentialPath, gameBoard[i][j].getPaint()));
}
}
}
}
}
return paths;
}
private static ArrayList<Point> findPath(Stone[][] gameBoard, int x, int y, ArrayList<Point> path, Paint color, ArrayList<Point> checkedPoints, int cameFrom){
int startClockwiseScanAtDirection = cameFrom + 5;
for (int i = startClockwiseScanAtDirection; i < startClockwiseScanAtDirection + 7; i++) {
// avoid ArrayIndexOutOfBounds
if(x+neighbors[i%8].x < 0 || y+neighbors[i%8].y < 0 || x+neighbors[i%8].x >= gameBoard.length || y+neighbors[i%8].y >= gameBoard[0].length)
continue;
// check if there's a stone that matches the current stone, we're scanning around
if(gameBoard[x+neighbors[i%8].x][y+neighbors[i%8].y] != null && gameBoard[x+neighbors[i%8].x][y+neighbors[i%8].y].getPaint() == color){
// found one
Point nextStone = new Point(x+neighbors[i%8].x,y+neighbors[i%8].y);
// is the point we just found the origin of the path?
if(nextStone.equals(path.get(0)) && path.size() > MIN_PATH_LENGTH) { //This seems to prevent drawing a path when we have less stone to form a path with
path.add(nextStone);
checkedPoints.add(nextStone);
return path;
}
// otherwise if it's already part of the path ignore it
if (path.contains(nextStone)) {
continue;
}
// else add it to the path and keep going
path.add(nextStone);
checkedPoints.add(nextStone);
// recurse on the next stone in the path
ArrayList<Point> newPath = findPath(gameBoard,x+neighbors[i%8].x, y+neighbors[i%8].y, path, color, checkedPoints, i%8);
if (newPath == null){
// didn't find a way to continue, so backtrack
path.remove(path.size()-1);
} else {
// we have a completed path to return
return newPath;
}
}
}
return null;
}
}
Path class
public class Path {
public Paint getColor() {
return color;
}
public void setColor(Paint color) {
this.color = color;
}
public ArrayList<Point> getCoordinateList() {
return coordinateList;
}
public void setCoordinateList(ArrayList<Point> coordinateList) {
this.coordinateList = coordinateList;
}
private ArrayList<Point> coordinateList;
private Paint color;
public Path(ArrayList<Point> coordinatePath, Paint color){
this.coordinateList = coordinatePath;
this.color = color;
}
#Override
public String toString() {
return coordinateList.toString();
}
}
Here some case test:
Called in the MainActivity's onCreate():
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
gameGrid = findViewById(R.id.gameGrid);
bluePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bluePaint.setStyle(Paint.Style.FILL_AND_STROKE);
bluePaint.setColor(Color.BLUE);
redPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
redPaint.setStyle(Paint.Style.FILL);
redPaint.setColor(Color.RED);
bgrBluePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bgrBluePaint.setStyle(Paint.Style.STROKE);
bgrBluePaint.setStrokeWidth(bgrStrokeWdth);
bgrBluePaint.setColor(Color.BLUE);
bgrRedPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
bgrRedPaint.setStyle(Paint.Style.STROKE);
bgrRedPaint.setStrokeWidth(bgrStrokeWdth);
bgrRedPaint.setColor(Color.RED);
bluePlayer = new Stone(1,bluePaint, bgrBluePaint);
redPlayer = new Stone(2, redPaint, bgrRedPaint);
gameBoard = new Stone[100][100];
gameBoard[47][47]= redPlayer;
gameBoard[46][47]= bluePlayer;
gameBoard[44][48]= redPlayer; //REDs form a path when you place this stone in the last positioon
gameBoard[44][49]= redPlayer;
gameBoard[45][47]= redPlayer;
gameBoard[45][48]= bluePlayer;
gameBoard[45][49]= bluePlayer;
gameBoard[45][50]= redPlayer;
gameBoard[46][50]= bluePlayer;
gameBoard[46][49]= redPlayer;
gameBoard[46][48]= redPlayer;
gameBoard[47][50]= bluePlayer;
gameBoard[47][48]= bluePlayer;
gameBoard[47][49]= redPlayer;
gameBoard[48][50]= redPlayer;
gameBoard[48][49]= redPlayer;
gameBoard[48][48]= redPlayer;
gameBoard[49][50]= bluePlayer;
gameBoard[48][51]= redPlayer;
gameBoard[44][50] = bluePlayer;
ArrayList<Path> paths = Pather.findPaths(gameBoard);
gameGrid.setPaths(paths);
gameGrid.setGameBoard(gameBoard);
}
Placing stones at the following positions clears the path:
//Adding the following deletes the path
gameBoard[43][50] = redPlayer; //Adding this one in last position clears the path
gameBoard[45][51] = redPlayer;
I need to figure out how to make a condition that check for an opponent nearby first then validate the path.
EDIT 2:
Stone.java
public class Stone{
private int _player;
private Paint _paint, _bgrPaint;
public Stone(int player, Paint paint, Paint bgrPaint){
_player = player;
_paint = paint;
_bgrPaint = bgrPaint;
}
public int getPlayer() {
return _player;
}
public Paint getPaint() {
return _paint;
}
public Paint get_bgrPaint() {
return _bgrPaint;
}
}
Point.java
public class Point {
int x, y;
public Point(int x, int y){
this.x = x;
this.y = y;
}
#Override
public boolean equals(Object point) {
return this.x == ((Point) point).x && this.y == ((Point) point).y;
}
#Override
public String toString() {
return "("+x+","+y+")";
}
}
Screenshoot of what a valid path should look
A more-or-less standard way to approach this kind of problem is a "sweep line" algorithm. For simplicity, say we're looking for blue paths wrapping red points.
(You can process red paths wrapping blue points at the same time or in a second pass, but you can work that out later.)
You can search for "sweep line algorithm" to see how they work in related applications. The Wikipedia page isn't bad.
For this problem, the sweep line is a set of y-intervals. It's initialized using the leftmost (least x) blue point(s). It gets one interval for each vertically adjacent set of blue points. Each interval represents a vertical slice through a potential blue polygon.
The rest of the algorithm is to design the rules needed to update the scan line when it is moved one position to the right, incrementing x. This will be a matter of updating each interval. When a step finds a disconnected set of vertically adjacent points, a new interval is added. In some cases, intervals will "die out" because the potential polygon boundary dead-ends (think of a C shape). In other cases, they will "merge" because, at the corresponding x-coordinate, there is a set of 1 or more vertically adjacent connecting points. In still other cases, the polygon will complete successfully with a final set of 1 or more vertically adjacent points.
The details will be fiddly, but not hard to work out by case analysis.
To trace successful polygons, intervals can include two chains of preceding points: the upper and lower polygon boundaries.
The last consideration is whether a successfully closed polygon encloses at least one red point. But this is easy. If for any x-coordinate, the interval representing a polygon bracketed a red point, then the answer is yes. This can be recorded as an initially false boolean maintained in the interval, which is set true every time such a red point is seen. When a polygon is successfully closed, check the flag to see whether it should be used or not.
All the above can be made efficient for very large grids by using suitable data structures: interval trees for example. But if the grid is comparatively small, it should be fine to use simple lists. At any rate, consider prototyping it with a list for the sweep line first first and optimize with more complicated data structures later if needed.
As I wrote in my comments, without mvce it is very hard to offer detailed help.
From what I see in the code I figure you are trying to map all cyclic single-color paths on the board.
I made some documented changes in the code, hoping (without being able to properly check it) that it may help you improve your code.
Note that as Stone class was not posted, I changed the representation of the board to int[][]
import java.awt.Point;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Phather {
private static final int RED = 2, BLUE = 1;
private static final int MIN_PATH_LENGTH = 3;
public enum Neighbor{
UP_RIGHT ( 1,-1),
RIGHT ( 1, 0),
DOWN_RIGHT( 1, 1),
DOWN ( 0, 1),
DOWN_LEFT (-1, 1),
LEFT (-1, 0),
UP_LEFT (-1,-1),
UP ( 0,-1);
int x, y;
Neighbor(int x, int y){
this.x = x;
this.y = y;
}
}
public static Set<Path> findPaths(int[][] gameBoard){
//use set to prevent duplicate paths
Set<Path> paths = new HashSet<>();
for (int x = 0; x < gameBoard.length ; x++) {
for (int y = 0; y < gameBoard[0].length; y++) {
//note that array indexes are [y][x] while point arguments are x,y
if(gameBoard[y][x] != 0){
//use set to prevent duplicate elements. initialize it to allow for
//overlapping paths (paths that contain some shared points)
Set<Point> checkedPoints = new HashSet<>();
//set the origin of a potential new path
ArrayList<Point> potentialPath = new ArrayList<>();
Point origin = new Point (x,y);
if(checkedPoints.add(origin)) { //add returns false if duplicate
potentialPath.add(origin);
potentialPath = findPath(gameBoard, x, y, potentialPath, checkedPoints);
if (potentialPath != null) {
paths.add(new Path(potentialPath, gameBoard[y][x]));
}
}
}
}
}
return paths;
}
private static ArrayList<Point> findPath(int[][] gameBoard, int x, int y,
ArrayList<Point> path, Set<Point> checkedPoints){
int color = gameBoard[y][x]; //no need for color as argument. get from stone
for(Neighbor neighbor : Neighbor.values()) {
int neighborX = x + neighbor.x, neighborY = y + neighbor.y;
// avoid ArrayIndexOutOfBounds
//todo: refactor to method isValidAddress(x,y,maxX, maxY)
if((neighborX < 0) || ( neighborY < 0) || (neighborY >= gameBoard.length)
|| (neighborX >= gameBoard[0].length)) {
continue;
}
// check if there's a stone that matches the current stone, we're scanning around
if((gameBoard[neighborY][neighborX] != 0) && (gameBoard[neighborY][neighborX] == color)){
// found one
Point nextStone = new Point(neighborX,neighborY);
// is the point we just found the origin of the path ?
if(nextStone.equals(path.get(0)) && (path.size() > MIN_PATH_LENGTH)) {
path.add(nextStone); //do you want it in path twice ?
//checkedPoints.add(nextStone); //if added to path before, it is already in checkedPoints
return path;
}
// otherwise if it's already part of the path ignore it
if (path.contains(nextStone)) {
continue;
}
// else add it to the path and keep going
path.add(nextStone);
checkedPoints.add(nextStone);
// recurse on the next stone in the path
ArrayList<Point> newPath = findPath(gameBoard, neighborX, neighborY, path, checkedPoints);
if (newPath == null){
// didn't find a way to continue, so backtrack
path.remove(path.size()-1);
} else {
// we have a completed path to return
return newPath;
}
}
}
return null;
}
}
class Path {
private ArrayList<Point> coordinateList;
private int color;
Path(ArrayList<Point> coordinatePath, int color){
coordinateList = coordinatePath;
this.color = color;
}
int getColor() { return color; }
#Override
public String toString() {
return coordinateList.toString();
}
List<Point> getPoints() { return coordinateList; }
int size() { return coordinateList.size(); }
#Override
public boolean equals(Object p){
if (p == this) { return true; }
if (p == null) { return false;}
if (!(p instanceof Path)) {return false; }
Path path = (Path)p;
return getPoints().containsAll(path.getPoints())
&& path.getPoints().containsAll(getPoints());
}
}
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);
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);
}
}
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.