import java.util.Collection; import java.util.LinkedList; /** * Piece represents a rectangular piece in the puzzle. Pieces can move * horizontally, vertically, or both, but movement occurrs in only one direction * at a time. Piece position and dimensions are immutable. Movement is acheived * by generating a list of new pieces in the various possible positions * resulting from the "movement" of this piece. * * @author Michael Leonhard (mleonhar) * @version 2005-10-30 * @version JDK 1.5.0.5, Eclipse 3.1.0, Windows XP * @version CS 340, Fall 2005, Instructor: Pat Troy, TA: Nitin Jindal */ public class Piece { private String name; // the name of the piece private int x, y, width, height; // position in the puzzle and size private char movement;// bits showing horizontal or vertical movability public static final char H_MOVE = 0x01; // piece can move horizontally public static final char V_MOVE = 0x02; // piece can move vertically /** * Constructor: initializes a piece that is not yet part of any puzzle * * @param x the column where the piece lives * @param y the row where the piece lives * @param width the width of the piece (in columns) * @param height the height of the piece (in rows) * @param movement bit mask of piece's movement ability: V_MOVE, H_MOVE * @throws PuzzleException if any of the parameters are invalid */ public Piece(int x, int y, int width, int height, char movement) throws PuzzleException { // check parameters if (x < 0 | y < 0) throw new PuzzleException( "The position of the piece is invalid."); if (width < 1 | height < 1) throw new PuzzleException( "The dimensions of the piece are invalid."); if (((movement & ~(H_MOVE | V_MOVE)) != 0) | movement == 0) throw new PuzzleException( "The piece's movement mask contains an unknown flag."); if ((movement & (H_MOVE | V_MOVE)) == 0) throw new PuzzleException( "The piece has no movement flag (immovable piece is not supported)."); // save data this.x = x; this.y = y; this.width = width; this.height = height; this.movement = movement; } /** * Private Constructor: used for cloning, does no error checking * * @param x the column where the piece lives * @param y the row where the piece lives * @param width the width of the piece (in columns) * @param height the height of the piece (in rows) * @param movement bit mask of piece's movement ability: V_MOVE, H_MOVE */ public Piece(int x, int y, int width, int height, char movement, String name) { // save data this.x = x; this.y = y; this.width = width; this.height = height; this.movement = movement; this.name = name; } /** * Checks if the piece can fit into the puzzle without overlapping any other * piece or hanging off the edge of the board. * * @param puzzle the puzzle to check if the piece will fit * @throws PuzzleException if the piece cannot fit into the puzzle */ public void checkFit(Puzzle puzzle) throws PuzzleException { // check if we can fit in the puzzle for (int x = this.x; x < this.x + this.width; x++) for (int y = this.y; y < this.y + this.height; y++) if (!puzzle.isEmpty(x, y, null)) throw new PuzzleException( "Piece cannot fit into board. It would overlap with" + "another piece or hang off the edge of the board."); } /** * Informs the piece that it has been added to the specified puzzle. This * method registers the locations that the piece occupies in the puzzle * board. No error checking is performed so one must call checkFit() before * calling this method. * * @param puzzle the puzzle to which the piece has been added */ public void addedToPuzzle(Puzzle puzzle) { // add ourself to the puzzle for (int x = this.x; x < this.x + this.width; x++) for (int y = this.y; y < this.y + this.height; y++) puzzle.occupyLocation(x, y, this); } /** * Generates a string representation of the piece * * @return human readable string with information about the piece */ public String toString() { String movementString = "illegal"; if (((movement & H_MOVE) != 0) & ((movement & V_MOVE) != 0)) movementString = "b"; else if ((movement & H_MOVE) != 0) movementString = "h"; else if ((movement & V_MOVE) != 0) movementString = "v"; return "Piece(" + name + ", " + x + "," + y + " " + width + "x" + height + " " + movementString + ")"; } /** * Mutator for name * * @param newName the new name of the piece */ public void setName(String newName) { this.name = newName; } /** * Accessor for name * * @return the name of the piece */ public String getName() { return name; } /** * Accessor for x * * @return Returns the column where the piece lives */ public int getX() { return x; } /** * Accessor for y * * @return Returns the row where the piece lives */ public int getY() { return y; } /** * Accessor for width * * @return Returns the width of the piece (number of columns spanned) */ public int getWidth() { return width; } /** * Accessor for height * * @return Returns the height of the piece (number of rows spanned) */ public int getHeight() { return height; } /** * Accessor for axes of movement * * @return axes in which this piece can move: V_MOVE, H_MOVE, or both */ public char getMovement() { return movement; } /** * Finds a list of all valid moves that the piece can make. * * @param puzzle the puzzle where the piece lives * @return the list of valid moves */ public Collection getValidMoves(Puzzle puzzle) { // allocate the list of moves LinkedList moves = new LinkedList(); // the directions in which the piece can travel boolean xPosTravel, xNegTravel, yPosTravel, yNegTravel; // the piece can travel horizontally xPosTravel = xNegTravel = (this.movement & H_MOVE) != 0; // the piece can travel vertically yPosTravel = yNegTravel = (this.movement & V_MOVE) != 0; // check distances from 1 until no more travel is allowed for (int dist = 1; xPosTravel | xNegTravel | yPosTravel | yNegTravel; dist++) { // MP4.dprint("checking distance " + dist+ (xPosTravel ? " xPos" : // "")+ (xNegTravel ? " xNeg" : "")+ (yPosTravel ? " yPos" : "")+ // (yNegTravel ? " yNeg" : "")); // add the move of the specified distance if (xPosTravel) xPosTravel = addMove(puzzle, moves, H_MOVE, dist); if (xNegTravel) xNegTravel = addMove(puzzle, moves, H_MOVE, -dist); if (yPosTravel) yPosTravel = addMove(puzzle, moves, V_MOVE, dist); if (yNegTravel) yNegTravel = addMove(puzzle, moves, V_MOVE, -dist); } // return the list of moves return moves; } /** * Checks if the piece fits after moving delta spaces along the specified * axis. A copy of the piece in the destination position is then created. A * PieceMove object is created and added to the provided list. * * @param puzzle the puzzle where the piece lives * @param moves the list of moves (new move gets added to this) * @param axis the axis of movement: H_MOVE or V_MOVE * @param delta the distance and direction of travel along the axis * @return true if the move is valid, otherwise false * @throws IllegalArgumentException if moves is null, delta is zero, or axis * is not equal to H_MOVE and not equal to V_MOVE */ private boolean addMove(Puzzle puzzle, Collection moves, char axis, int delta) { // check parameters if (moves == null) throw new IllegalArgumentException( "moves list is null"); if (delta == 0) throw new IllegalArgumentException( "delta may not be zero"); int newX = this.x; int newY = this.y; // horizontal movement if (axis == H_MOVE) newX += delta; // vertical movement else if (axis == V_MOVE) newY += delta; // error else throw new IllegalArgumentException("invalid axis"); // MP4.dprint("checking " + this + " at (" + newX + "," + newY + ") on // Puzzle:\r\n"+this.puzzle.prettyString()); // check if we fit in the new position (overlapping with self is ok) for (int x = newX; x < newX + this.width; x++) { for (int y = newY; y < newY + this.height; y++) { // the position is occupied by a piece other than us, so give up // on this move if (!puzzle.isEmpty(x, y, this)) { // MP4.dprint("not empty at (" + x + "," + y + ")"); return false; } // MP4.dprint("open at (" + x + "," + y + ")"); } } // make a copy of ourself with the new position Piece newPiece = new Piece( newX, newY, this.width, this.height, this.movement, this.name); // make a move object to represent the move PieceMove move = new PieceMove(this, newPiece, axis, delta); // add it to the list of moves moves.add(move); // success! return true; } }