// Board.java import java.awt.*; import java.util.*; /** 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 @author Nathan Merritt @version April 17, 2008 */ public final class Board { private int width; private int height; private boolean[][] grid; private int maxHeight; private int widths[]; private int heights[]; private boolean committed; // backup data structures private boolean[][] bGrid; private int[] bWidths; private int[] bHeights; private int bMaxHeight; private boolean DEBUG = true; private static final int HEIGHT = 2; /** 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 aWidth, int aHeight) { width = aWidth; height = aHeight + HEIGHT; grid = new boolean[height][width]; widths = new int[height]; heights = new int[width]; maxHeight = 0; bMaxHeight = 0; committed = true; // start the grid off entirely empty, and widths, heights at 0 for (int i = 0; i < height; i++) { widths[i] = 0; for (int j = 0; j < width; j++) { grid[i][j] = false; } } for (int i = 0; i < width; i++) { heights[i] = 0; } // initialize the backup data structures bGrid = new boolean[height][width]; bWidths = new int[height]; bHeights = new int[width]; // and populate them for the first time createBackup(); } /** 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; } /* updates the widths[] array with the correct values after we commit() the board state. Searches for the max Height grid block that is set to true in any given column. */ public void calcHeights() { for (int col = 0; col < width; col++) { int maxHeight = 0; boolean empty = true; // whether or not any blocks are filled for (int row = 0; row < height; row++) { if (grid[row][col] == true) { if (row > maxHeight) maxHeight = row; empty = false; } } if (empty) heights[col] = 0; else heights[col] = maxHeight + 1; } } /* calculates width in a row by tallying all the filled spaces in that row. To save time, we only check the rows between start and stop (inclusive). This translates to only updating widths where our piece is active. */ public void calcWidths(int start, int stop) { for (int row = start; row <= stop; row++) { int curr_width = 0; for (int col = 0; col < width; col++) { if (grid[row][col]) { curr_width++; } } widths[row] = curr_width; } } /** Checks the board for internal consistency -- used for debugging. */ public void sanityCheck() { if (DEBUG) { // consistency check the board state // Prof. Toma said we didn't "have" to write this function so I // didn't... } } /** Given a piece and the x [at its origin], 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) { // we want the shortest distance the piece could travel int minDist = getHeight(); int minDist_X = x; int[] skirt = piece.getSkirt(); for (int i = 0; i < skirt.length; i++) { if (skirt[i] - getColumnHeight(x + i) < minDist) { minDist = skirt[i] - getColumnHeight(x + i); minDist_X = x + i; } } // now return: (height of the row w/ the shortest dist) return getColumnHeight(minDist_X); } /** 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) { assert (x > 0 && x < width); 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) { if ( (x < 0 || x > width) || (y < 0 || y > height) ) { return true; } return grid[y][x]; } 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) { /* first we have to create a backup so we can undo this placement later if the client wants to... */ createBackup(); committed = false; Point[] body = piece.getBody(); System.out.print("placing... "); // check to see if we're in bounds if ((x + piece.getWidth() > width) || (y + piece.getHeight() > height)) return PLACE_OUT_BOUNDS; // find the boundaries of the piece // used later to save run-time int minRow = height + 1; int maxRow = 0; for (int i = 0; i < body.length; i++) { int px = (int) body[i].getX() + x; int py = (int) body[i].getY() + y; // find the minimums and maximums if (py > maxRow) maxRow = py; if (py < minRow) minRow = py; // uh-oh, the grid is already filled! if (getGrid(px, py)) { System.out.println("PLACE_BAD"); return PLACE_BAD; } // we got this far, so fill the grid grid[py][px] = true; System.out.print(px + " " + py + " | "); } // update maxHeight variable if (maxRow > maxHeight) maxHeight = maxRow + 1; // now - did we fill a row? calcWidths(minRow, maxRow); // and look for any rows with a width equal to board width // NOTE: this only searches in rows that our piece exists in // to save run-time for (int row = minRow; row < maxRow; row++) { if (getRowWidth(row) >= width) { System.out.println("FULL ROW"); return PLACE_ROW_FILLED; } } System.out.println(" PLACE OK"); 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() { boolean cleared = false; // start in the bottom, clear out any full rows for (int row = 0; row < height; row++) { // clears out the rows while (widths[row] >= width) { cleared = true; shiftDown(row); // update the width of this row calcWidths(row, row + 1); } } return cleared; } /* Does all the work for clearRows(): Shifts down the entire board so that the contents of target_row are lost. We'll move all the rows above target row down one by swapping pointers (fast!). Also, we have to re-zero out the top row afterwards to preserve state and not break things... */ public void shiftDown(int target_row) { assert (target_row >= 0); // move everything down one row, stop before we hit the top for (int row = target_row; row + 1 < height; row++) { if (widths[row] == 0) break; grid[row] = grid[row+1]; } // shift the widths down as well if (target_row + 1 < height) widths[target_row] = widths[target_row + 1]; // and put a fresh, empty row in at the top grid[height - 1] = new boolean[width]; for (int i = 0; i < grid[0].length; i++) { grid[height -1][i] = false; } } /* Backs up the following data to support 1-deep undo(): -maxHeight -> bMaxHeight -widths[] -> bWidths[] -heights[] -> bHeights[] -grid[][] -> bGrid[][] Haven't decided yet whether or not this function cares about the state of commited, going with no at the moment... */ public void createBackup() { System.out.println("backing up..."); bMaxHeight = maxHeight; System.arraycopy(widths, 0, bWidths, 0, widths.length); System.arraycopy(heights, 0, bHeights, 0, heights.length); for (int i = 0; i < grid.length; i++) { System.arraycopy(grid[i], 0, bGrid[i], 0, bGrid[i].length); } } /** 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() { // do nothing if we have nothing to undo if (committed) { return; } System.out.println("undoing"); /* otherwise, restore all the data from createBackup() with a bunch of pointer swapping */ int[] temp = widths; widths = bWidths; bWidths = temp; temp = heights; heights = bHeights; bHeights = temp; int temp2 = maxHeight; maxHeight = bMaxHeight; bMaxHeight = temp2; boolean[][] temp3 = grid; grid = bGrid; bGrid = temp3; // we'll have just reverted to a committed state committed = true; } /** Puts the board in the committed state. See the overview docs. */ public void commit() { committed = true; // necessary after any change that we make permanant calcHeights(); } }