// Board.java import java.awt.*; import java.util.*; /* Charles Meyer * Tetris II: Lab 10 * April 23, 2008 * Comp Sci 210 */ /* So basically I wrote evey method of substance in this things, everything other * then the names of the global variables, and the getters. Place takes a peice * and a location and puts that piece on the board with the lower left of the * piecce at that spot. ClearRows goes through the board and removes any rows * that are filled. Those, aside from undue and commit, wich I will explain in * a moment, are all the important features. First though, an explanation of * heights and widths. Widths holds the number of peices in each row, while * heights holds the position ot the highest peiece in each coloumn. MaxHeight * simply holds the highest height. Sanity check is an optional tool that (when * DEBUG is set to true) checks heights and widths and maxHeight against the grid. * PrintBoard simply goes through and prints the board as well as the widths and heights. * This leave the undu and commit features. Undu is run to revert the board * back to its last committed state. When the board leaves the committed state, it * copies the grid, widths, and heights, into backup arrays. When undu is pressed, * the backup arrays are passed back to the primarry arrays. When commit is pressed, * the board is committed again. Up to one place and one clearRows can be run * between commits, hence the board has one deep undo capability. */ /** Represents a Tetris board -- essentially a 2-d grid of booleans. Supports tetris pieces and row clearning. Has an "undo" feature that allows clients to add and remove pieces efficiently. Does not do any drawing or have any idea of pixels. Intead, just represents the abtsract 2-d board. See Tetris-Architecture.html for an overview. This is the starter file version -- a few simple things are filled in already @author Nick Parlante @version 1.0, Mar 1, 2001 */ public final class Board { //these are the primarry data structures private int width; //the width of the board private int height; //the height of the board private boolean[][] grid; //the actual boward, a spot is true if it is ocupied private int maxHeight; //the highest height private int widths[]; //the number of blocks in each row private int heights[]; //the height of each coloumn //whether the board is comitted (cannot be undone) private boolean committed; //refers to whether clear row has been run //(since this can be run once while uncomitted) private boolean CRCommitted; // backup data structures private boolean[][] bGrid; private int[] bWidths; private int[] bHeights; private int bMaxHeight; //controls whether sanity check is ran private boolean DEBUG = false; //I believe that this is the lead in space at the top of the board private static final int HEIGHT = 2; //controls whether error messages are printed when undo is called //while comitted and commit is called while already comitted //(the mule does both of the above, but in my opinion a good //program would not) private static final boolean PRINT_MESSAGES = false; /** Creates an empty board of the given width and height measured in blocks. This is not quite done, you still need to initialize your backup data structures (and erase this comment!) */ public Board(int inWidth, int inHeight) { //sets the height and width of the grid based on the input width = inWidth; height = inHeight + HEIGHT; //this sets up the data structures based on the width and height grid = new boolean[width][height]; widths = new int[height]; heights = new int[width]; maxHeight = 0; //the board starts committed committed = true; //this initializes the grid and heights for (int i = 0; i < width; i++) { heights[i] = 0; for (int j = 0; j < height; j++) { grid[i][j] = false; } } //initializes the widths for (int i = 0; i < height; i++) { widths[i] = 0; } sanityCheck(); } /** Returns the width of the board in blocks. */ public int getWidth() { return width; } /** Returns the height of the board in blocks. */ public int getHeight() { return height; } /** Returns the max column height present in the board. For an empty board this is 0. Remember, this should be constant! */ public int getMaxHeight() { return maxHeight; } /** Checks the board for internal consistency -- used for debugging. */ //also fixes the board if it is not consistant based on grid //this is actually sortof useless right now, since the exceptions just crash the program, //but if one changed the exceptions to something less terminal, this might be helpfull public void sanityCheck() { if (DEBUG) { //these will be calculated like Heights and Widths, and //then checked against the originals for consistancy int[] tempHeights = new int[width]; int[] tempWidths = new int[height]; int tempMaxheight = 0; //caluculates tempHeights and tempWidths for (int k = 0; k < grid.length; k++) { for (int i = 0; i < grid[k].length; i++) { if (grid[k][i]) { //increases heights if this piece is higher then //the current value (+1 to acount for 0 index) if (i +1 > tempHeights[k]) { tempHeights[k] = i+1; } //updates temMaxHeight if necessarry if (tempHeights[k] > tempMaxheight) { tempMaxheight = tempHeights[k]; } //increments widths if necessarry tempWidths[i] ++; } } } //this will be set to true if there is inconsistancy boolean throwException = false; //checks tempHeights against Heights for (int k =0; k < heights.length; k++) { //if there is a mismatch if (heights[k] != tempHeights[k]) { throwException = true; } //updates temMaxHeight if necessarry if (tempHeights[k] > tempMaxheight) { tempMaxheight = tempHeights[k]; } } //throws an exception and resets heights, if necessarry if (throwException) { heights = tempHeights; throw new RuntimeException("Sanity check failure on heights"); } //resets throwException throwException = false; //repeats the above process for the widths for (int k = 0; k< widths.length; k++) { if (widths[k] != tempWidths[k]) { throwException = true; } } //throws an exception and resets widths if necessarry if (throwException) { widths = tempWidths; printBoard(); throw new RuntimeException("Sanity check failure on widths"); } //checks to see that the maxHeights match and resets maxHeights if so if (tempMaxheight != maxHeight) { printBoard(); throw new RuntimeException("Sanity check failure on maxHeight"); } } } /** Given a piece and an x, returns the y value where the piece would come to rest if it were dropped straight down at that x.

Implementation: use the skirt and the col heights to compute this fast -- O(skirt length). */ public int dropHeight(Piece piece, int x) { int toReturn = 0; //this goes through the entire skirt and calculates what drop height //is dictated by each piece, and sets toreturn to the highest for (int k = 0; k < piece.getSkirt().length; k++) { //if the piece given doe not fit on the board if (k + x >= width) { printBoard(); System.err.println("ilegal piece given to dropHeight"); } //increases toReturn if it needs to else if (heights[k + x] - piece.getSkirt()[k] > toReturn){ toReturn = heights[k + x] - piece.getSkirt()[k]; } } if (DEBUG) { System.out.println("debug: DropHeight called, returned " + toReturn); } return toReturn; } /** Returns the height of the given column -- i.e. the y value of the highest block + 1. The height is 0 if the column contains no blocks. */ public int getColumnHeight(int x) { return heights[x]; } /** Returns the number of filled blocks in the given row. */ public int getRowWidth(int y) { return widths[y]; } /** Returns true if the given block is filled in the board. Blocks outside of the valid width/height area always return true. */ public final boolean getGrid(int x, int y) { return grid[x][y]; } public static final int PLACE_OK = 0; public static final int PLACE_ROW_FILLED = 1; public static final int PLACE_OUT_BOUNDS = 2; public static final int PLACE_BAD = 3; /** Attempts to add the body of a piece to the board. Copies the piece blocks into the board grid. Returns PLACE_OK for a regular placement, or PLACE_ROW_FILLED for a regular placement that causes at least one row to be filled.

Error cases: If part of the piece would fall out of bounds, the placement does not change the board at all, and PLACE_OUT_BOUNDS is returned. If the placement is "bad" --interfering with existing blocks in the grid -- then the placement is halted partially complete and PLACE_BAD is returned. An undo() will remove the bad placement. */ public int place(Piece piece, int x, int y) { if (DEBUG) { System.out.println("debug: place called"); } if (!committed) { if (PRINT_MESSAGES) { System.err.println("Error, place called while uncommitted"); } return PLACE_BAD; } //the board is now not comitted, hence: committed = false; //sets up the backup data structures bGrid = new boolean[grid.length][grid[0].length]; //system.arraycopy does not appear to work for 2-d arrays: for (int k =0; k = width || piece.getBody()[k].getY() >= height) { return PLACE_OUT_BOUNDS; } } boolean rowFilled = false; //this now places every point on the grid for (int k = 0; k < piece.getBody().length; k++) { //hold the x and y position of this part of the body, saves a bunch of typing int currentX = (int)piece.getBody()[k].getX() + x; int currentY = (int)piece.getBody()[k].getY() + y; //if the spot is already ocupied if (grid[currentX][currentY]) { return PLACE_BAD; } else { grid[currentX][currentY] =true; //increments the widths arrays widths[(int)piece.getBody()[k].getY() + y]++; //this increases heights to the correct heights //note the +1 to compensate for index starting at zero if (heights[currentX] < currentY + 1) { heights[currentX] = currentY + 1; //increases maxHeight if need be if (maxHeight < heights[currentX]) { maxHeight = heights[currentX]; } } //flags a variable if the widths is full if (widths[(int)piece.getBody()[k].getY()] == width -1) { rowFilled =true; } } } sanityCheck(); if (rowFilled) { return PLACE_ROW_FILLED; } return PLACE_OK; } /** Deletes rows that are filled all the way across, moving things above down. Returns true if any row clearing happened.

Implementation: This is complicated. Ideally, you want to copy each row down to its correct location in one pass. Note that more than one row may be filled. */ public boolean clearRows() { if (DEBUG) { System.out.println("debug: clearRows called"); } if (!CRCommitted) { System.err.println("Error: clear rows called while CRComitted"); return false; } CRCommitted = false; if (!committed) { committed = false; //sets up the backup data structures bGrid = new boolean[grid.length][grid[0].length]; //system.arraycopy does not appear to work for 2-d arrays: for (int k =0; k tempHeights[j]) { tempHeights[j] = k +1; } } } //a special case, it the row to shift from is above height, //then we just fill it with falses else { widths[k] = 0; for (int j = 0; j < width; j++) { grid[j][k] = false; if (grid[j][k] && k +1 > tempHeights[j]) { tempHeights[j] = k +1; } } } } maxHeight = 0; //passes the reference heights = tempHeights; //finds out what new maxHeight for (int k =0; k < width; k++) { if (heights[k] > maxHeight) { maxHeight = heights[k]; } } sanityCheck(); //if a change was made if (rowsDeleted != 0) { return true; } return false; } /** If a place() happens, optionally followed by a clearRows(), a subsequent undo() reverts the board to its state before the place(). If the conditions for undo() are not met, such as calling undo() twice in a row, then the second undo() does nothing. See the overview docs. */ public void undo() { if (DEBUG) { System.out.println("debug: undue called"); } if (!committed) { //the board is now comitted to the previous state committed = true; CRCommitted = true; //resets the bakcup data structures //this weird little deal with temp is taken straight from //the asignement sheet, and seems to be needed to get java //to do this properly boolean[][] temp = grid; grid = bGrid; bGrid = temp; int[] temp2 = heights; heights = bHeights; bHeights = temp2; int[] temp3 = widths; widths = bWidths; bWidths = temp3; maxHeight = bMaxHeight; } else if (PRINT_MESSAGES) { System.err.println("Error: undo called while committed"); } } /** Puts the board in the committed state. See the overview docs. */ public void commit() { if (DEBUG) { System.out.println("debug: commit called"); } if (committed && PRINT_MESSAGES) { System.err.println("Note: commit called while already committed"); } committed = true; CRCommitted = true; } //this just goes thhrough and prints the state of everthing, //usefull for debugging public void printBoard() { System.out.println("Printing board state:"); for (int k =0; k < height;k++) { for (int j = 0; j