summaryrefslogtreecommitdiff
path: root/src/tetris/board/TetrisBoard.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/tetris/board/TetrisBoard.java')
-rw-r--r--src/tetris/board/TetrisBoard.java620
1 files changed, 620 insertions, 0 deletions
diff --git a/src/tetris/board/TetrisBoard.java b/src/tetris/board/TetrisBoard.java
new file mode 100644
index 0000000..d662b38
--- /dev/null
+++ b/src/tetris/board/TetrisBoard.java
@@ -0,0 +1,620 @@
+/*
+ * Jesse Morgan <jesterpm@u.washington.edu>
+ *
+ * TCSS 305 - Autumn 2009
+ * Tetris Project
+ * 17 November 2009
+ */
+
+package tetris.board;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Observable;
+
+import tetris.model.IntPoint;
+import tetris.piece.TetrisPiece;
+import tetris.piece.TetrisPieces;
+
+/**
+ * Class to represent the tetris board.
+ *
+ * @author Jesse Morgan <jesterpm@u.washington.edu>
+ * @version 1.0 23 November 2009
+ */
+public class TetrisBoard extends Observable {
+ // Public Constants
+ /**
+ * Standard tetris board width.
+ */
+ public static final int STANDARD_BOARD_WIDTH = 10;
+
+ /**
+ * Standard tetris board height.
+ */
+ public static final int STANDARD_BOARD_HEIGHT = 20;
+
+ /**
+ * Size of the above board buffer.
+ */
+ public static final int NEW_PIECE_BUFFER = TetrisPiece.TETRIS_PIECE_SIZE;
+
+ // Private Constants
+ /**
+ * Initial progression speed in milliseconds.
+ */
+ private static final int GAME_SPEED = 1000;
+
+ /**
+ * Speed change in ms per level.
+ */
+ private static final int DSPEED_PER_LEVEL = 100;
+
+ /**
+ * Number of points per line clear.
+ */
+ private static final int POINTS_PER_LINE = 100;
+
+ /**
+ * Number of points per line that the piece was hard dropped.
+ */
+ private static final int POINTS_PER_DROPPED_LINE = 10;
+
+ /**
+ * Points per level.
+ */
+ private static final int POINTS_PER_LEVEL = 2000;
+
+ /**
+ * Y position to start new pieces.
+ */
+ private static final int INITIAL_Y_COORDINATE = 0;
+
+ // Private Fields
+ /**
+ * Flag to indicate the game is over.
+ */
+ private boolean my_game_over;
+
+ /**
+ * Flag to store if a game is paused.
+ */
+ private boolean my_game_paused;
+
+ /**
+ * Current game score.
+ */
+ private int my_score;
+
+ /**
+ * Storage for the board.
+ */
+ private final List<List<Boolean>> my_board;
+
+ /**
+ * Board width.
+ */
+ private final int my_board_width;
+
+ /**
+ * Board height.
+ */
+ private final int my_board_height;
+
+ /**
+ * Future Tetris Pieces.
+ */
+ private List<TetrisPiece> my_future_pieces;
+
+ /**
+ * Testing Pieces.
+ */
+ private List<TetrisPiece> my_testing_pieces;
+
+ /**
+ * Current Piece.
+ */
+ private TetrisPiece my_current_piece;
+
+ /**
+ * Create a new tetris board.
+ *
+ * @param the_width Board width.
+ * @param the_height Board height.
+ */
+ public TetrisBoard(final int the_width, final int the_height) {
+ super();
+
+ // Mark the game as over, since we haven't called newGame() yet
+ my_game_over = true;
+ my_game_paused = false;
+ my_score = 0;
+
+ // Setup the board.
+ my_board = new ArrayList<List<Boolean>>();
+ my_board_width = the_width;
+ my_board_height = the_height;
+
+ final int center_pos = my_board_width / 2 - TetrisPiece.TETRIS_PIECE_SIZE / 2;
+
+ // Set a current piece just to keep my_current_piece from being null.
+ my_current_piece = TetrisPieces.getRandomPiece(center_pos, INITIAL_Y_COORDINATE);
+
+ // And setup the future piece list.
+ my_future_pieces = new ArrayList<TetrisPiece>();
+ my_testing_pieces = new ArrayList<TetrisPiece>();
+ }
+
+ /**
+ * Create a tetris board with from a set of pieces.
+ *
+ * @param the_width Board width.
+ * @param the_height Board height.
+ * @param the_pieces List of pieces to place on board. the_pieces.size() > 1
+ */
+ public TetrisBoard(final int the_width, final int the_height,
+ final List<TetrisPiece> the_pieces) {
+ this(the_width, the_height);
+
+ // Set the current piece
+ my_current_piece = the_pieces.get(0);
+
+ // Set the testing pieces.
+ my_testing_pieces.addAll(the_pieces);
+ }
+
+ /**
+ * @return true if the game is over.
+ */
+ public boolean isGameOver() {
+ return my_game_over;
+ }
+
+ /**
+ * @param the_state True if the game is to be paused, false otherwise.
+ */
+ public void setPaused(final boolean the_state) {
+ if (!my_game_over && my_game_paused != the_state) {
+ my_game_paused = the_state;
+
+ setChanged();
+ notifyObservers(new TetrisBoardEvent(EventTypes.PAUSE_CHANGED));
+ }
+ }
+
+ /**
+ * @return True if the game is paused, false otherwise.
+ */
+ public boolean isPaused() {
+ return my_game_paused;
+ }
+
+ /**
+ * @return the current score.
+ */
+ public int getScore() {
+ return my_score;
+ }
+
+ /**
+ * @return the current level.
+ */
+ public int getLevel() {
+ return getScore() / POINTS_PER_LEVEL + 1;
+ }
+
+ /**
+ * @return the game speed in ms based off the level.
+ */
+ public int getSpeed() {
+ return Math.max(GAME_SPEED - DSPEED_PER_LEVEL * (getLevel() - 1), DSPEED_PER_LEVEL);
+ }
+
+ /**
+ * Reset the board to the new game state.
+ */
+ public void newGame() {
+ // The game is not over
+ my_game_over = false;
+ my_game_paused = false;
+ my_score = 0;
+
+ // Clear the board and recreate it
+ my_board.clear();
+ for (int y = 0; y < my_board_height + NEW_PIECE_BUFFER; y++) {
+ my_board.add(generateTetrisRow());
+ }
+
+ final int center_pos = my_board_width / 2 - TetrisPiece.TETRIS_PIECE_SIZE / 2;
+
+ // Setup the future pieces
+ my_future_pieces.clear();
+ my_future_pieces.addAll(my_testing_pieces);
+
+ // If we have none...
+ if (my_future_pieces.isEmpty()) {
+ // Piece the current piece.
+ my_current_piece = TetrisPieces.getRandomPiece(center_pos, INITIAL_Y_COORDINATE);
+
+ // And the next piece.
+ my_future_pieces.add(TetrisPieces.getRandomPiece(center_pos, INITIAL_Y_COORDINATE));
+
+ } else {
+ // Set the current piece to the first one...
+ my_current_piece = my_future_pieces.get(0);
+ my_future_pieces.remove(1);
+ }
+
+ // Something changed, if you couldn't tell....
+ setChanged();
+ notifyObservers(new TetrisBoardEvent(EventTypes.NEW_GAME));
+ }
+
+ /**
+ * Progress the board.
+ */
+ public void progressBoard() {
+ boolean freeze = false;
+
+ // If we're not playing, don't move.
+ if (my_game_over || my_game_paused) {
+ return;
+ }
+
+ // Move the current piece down one.
+ final TetrisPiece new_piece = my_current_piece.translate(0, 1);
+
+ if (validatePosition(new_piece)) {
+ // If it's a valid move, do it.
+ my_current_piece = new_piece;
+
+ } else {
+ // Otherwise, we'll freeze the piece here shortly.
+ freeze = true;
+ }
+
+ // Do we need to freeze the current piece?
+ if (freeze) {
+ // Check for end of game conditions
+ if (my_current_piece.getY() < INITIAL_Y_COORDINATE + NEW_PIECE_BUFFER) {
+ // If the piece is even partially above the board, end the game.
+ my_game_over = true;
+ setChanged();
+ notifyObservers(new TetrisBoardEvent(EventTypes.GAME_OVER));
+
+ // Game over... nothing else to do here.
+ return;
+ }
+
+ // Save the starting row of the current piece.
+ final int starting_row = my_current_piece.getY();
+
+ // Freeze the current piece.
+ freezeCurrentPiece();
+
+ // Check for filled lines
+ checkLines(starting_row);
+ }
+
+ // Notify the observers
+ setChanged();
+ notifyObservers(new TetrisBoardEvent(EventTypes.CURRENT_PIECE_UPDATE));
+ }
+
+ /**
+ * Move the current piece left.
+ */
+ public void moveLeft() {
+ final TetrisPiece new_piece = my_current_piece.translate(-1, 0);
+
+ if (!my_game_paused && !my_game_over && validatePosition(new_piece)) {
+ my_current_piece = new_piece;
+
+ setChanged();
+ notifyObservers(new TetrisBoardEvent(EventTypes.CURRENT_PIECE_UPDATE));
+ }
+ }
+
+ /**
+ * Move the current piece right.
+ */
+ public void moveRight() {
+ final TetrisPiece new_piece = my_current_piece.translate(1, 0);
+
+ if (!my_game_paused && !my_game_over && validatePosition(new_piece)) {
+ my_current_piece = new_piece;
+
+ setChanged();
+ notifyObservers(new TetrisBoardEvent(EventTypes.CURRENT_PIECE_UPDATE));
+ }
+ }
+
+ /**
+ * Move the current piece down.
+ */
+ public void moveDown() {
+ final TetrisPiece new_piece = my_current_piece.translate(0, 1);
+
+ if (!my_game_paused && !my_game_over && validatePosition(new_piece)) {
+ my_current_piece = new_piece;
+
+ setChanged();
+ notifyObservers(new TetrisBoardEvent(EventTypes.CURRENT_PIECE_UPDATE));
+ }
+ }
+
+ /**
+ * Drop the piece.
+ */
+ public void dropPiece() {
+ TetrisPiece new_piece = my_current_piece;
+ int number_of_moves = 0;
+
+ // Don't drop if the game is paused
+ if (my_game_paused || my_game_over) {
+ return;
+ }
+
+ // Move the piece until it can't move no more.
+ do {
+ new_piece = new_piece.translate(0, 1);
+ number_of_moves++;
+
+ } while (validatePosition(new_piece));
+
+ // That last move didn't work out so well, remove it.
+ number_of_moves = number_of_moves - 1;
+
+ // If the piece could move, move it.
+ if (number_of_moves > 0) {
+ // Update the piece
+ my_current_piece = my_current_piece.translate(0, number_of_moves);
+
+ // Update the score based off the number of lines dropped
+ incrementScore(number_of_moves * POINTS_PER_DROPPED_LINE);
+
+ // Tell the world
+ setChanged();
+ notifyObservers(new TetrisBoardEvent(EventTypes.CURRENT_PIECE_UPDATE));
+
+ }
+ }
+
+ /**
+ * Rotate the current piece left.
+ */
+ public void rotateLeft() {
+ final TetrisPiece new_piece = my_current_piece.rotateLeft();
+
+ if (!my_game_paused && !my_game_over && validatePosition(new_piece)) {
+ my_current_piece = new_piece;
+
+ setChanged();
+ notifyObservers(new TetrisBoardEvent(EventTypes.CURRENT_PIECE_UPDATE));
+ }
+ }
+
+ /**
+ * Rotate the current piece right.
+ */
+ public void rotateRight() {
+ final TetrisPiece new_piece = my_current_piece.rotateRight();
+
+ if (!my_game_paused && !my_game_over && validatePosition(new_piece)) {
+ my_current_piece = new_piece;
+
+ setChanged();
+ notifyObservers(new TetrisBoardEvent(EventTypes.CURRENT_PIECE_UPDATE));
+ }
+ }
+
+ /**
+ * @return the curent tetris piece.
+ */
+ public TetrisPiece getCurrentPiece() {
+ return my_current_piece;
+ }
+
+ /**
+ * @return the next tetris piece.
+ */
+ public TetrisPiece getNextPiece() {
+ return my_future_pieces.get(0);
+ }
+
+ /**
+ * @return An array of Point2Ds for each tetris block;
+ */
+ public List<IntPoint> getTetrisBlocks() {
+ final List<IntPoint> points = new ArrayList<IntPoint>();
+
+ // Add the current piece
+ final IntPoint[] block_points = my_current_piece.getBoardCoordinates();
+
+ points.addAll(Arrays.asList(block_points));
+
+ // Add the rest of the board pieces.
+ for (int y = 0; y < my_board.size(); y++) {
+ final List<Boolean> row = my_board.get(y);
+
+ for (int x = 0; x < row.size(); x++) {
+ if (row.get(x).booleanValue()) {
+ points.add(new IntPoint(x, y));
+ }
+ }
+ }
+
+ return points;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+
+ // Iterate through all the rows
+ for (int y = 0; y < my_board_height + NEW_PIECE_BUFFER; y++) {
+ // Iterate through each column
+ for (int x = 0; x < my_board_width; x++) {
+
+ if (my_board.get(y).get(x).booleanValue()) {
+ sb.append('X');
+
+ } else {
+ sb.append('.');
+ }
+ }
+
+ // Add a new line at the end of each row.
+ sb.append('\n');
+ }
+
+ // Add the current piece
+ final IntPoint[] points = my_current_piece.getBoardCoordinates();
+
+ for (int i = 0; i < points.length; i++) {
+ final int pos = (my_board_width + 1) * points[i].getY() + points[i].getX();
+ sb.replace(pos, pos + 1, "O");
+ }
+
+ // Now we add a dashed line after the 4th row
+ final StringBuilder dashes = new StringBuilder();
+ for (int x = 0; x < my_board_width; x++) {
+ dashes.append('-');
+ }
+ dashes.append('\n');
+
+ sb.insert((my_board_width + 1) * NEW_PIECE_BUFFER, dashes);
+
+ // Is the game over?
+ if (my_game_over) {
+ sb.append("Game Over\n");
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Increment the tetris score.
+ *
+ * @param the_points to add to the score.
+ */
+ private void incrementScore(final int the_points) {
+ if (getLevel() > GAME_SPEED / DSPEED_PER_LEVEL) {
+ /*
+ * Multipler: For any level above the one which results in speed 1,
+ * multiply the points to add by the number of levels above that
+ * threshold.
+ */
+ final int multiplier = getLevel() - GAME_SPEED / DSPEED_PER_LEVEL + 1;
+
+ my_score += the_points * multiplier;
+
+ } else {
+ my_score += the_points;
+ }
+
+ setChanged();
+ notifyObservers(new TetrisBoardEvent(EventTypes.SCORE_CHANGED));
+ }
+
+ /**
+ * Creates a row for the tetris piece list.
+ *
+ * @return a list containing an empty row.
+ */
+ private List<Boolean> generateTetrisRow() {
+ final List<Boolean> row = new ArrayList<Boolean>();
+
+ // Loop through the columns.
+ for (int x = 0; x < my_board_width; x++) {
+ row.add(Boolean.FALSE);
+ }
+
+ return row;
+ }
+
+ /**
+ * Checks if a piece can actually occupy the space it's trying to occupy.
+ *
+ * @param the_piece The piece in question.
+ * @return true if it's cool, false if it's not.
+ */
+ private boolean validatePosition(final TetrisPiece the_piece) {
+ final IntPoint[] points = the_piece.getBoardCoordinates();
+
+ for (int i = 0; i < points.length; i++) {
+ // Check that it's on the board
+ if (points[i].getX() < 0 || points[i].getX() >= my_board_width || points[i].getY() < 0 ||
+ points[i].getY() >= my_board_height + NEW_PIECE_BUFFER) {
+ return false;
+ }
+
+ // Check that it doesn't hit another piece.
+ if (my_board.get(points[i].getY()).get(points[i].getX()).booleanValue()) {
+ return false;
+ }
+ }
+
+ // Each point passed all tests.
+ return true;
+ }
+
+ /**
+ * Freeze the current piece and set a new one.
+ */
+ private void freezeCurrentPiece() {
+ final IntPoint[] points = my_current_piece.getBoardCoordinates();
+
+ for (int i = 0; i < points.length; i++) {
+ my_board.get(points[i].getY()).set(points[i].getX(), Boolean.TRUE);
+ }
+
+ // Get a new piece.
+ my_current_piece = my_future_pieces.get(0);
+ my_future_pieces.remove(0);
+ if (my_future_pieces.isEmpty()) {
+ final int xpos = (int) (my_board_width / 2 - TetrisPiece.TETRIS_PIECE_SIZE / 2);
+ my_future_pieces.add(TetrisPieces.getRandomPiece(xpos, INITIAL_Y_COORDINATE));
+ }
+ }
+
+ /**
+ * Check the TETRIS_PIECE_SIZE lines after a certain row.
+ *
+ * @param the_starting_row The row to start at.
+ */
+ private void checkLines(final int the_starting_row) {
+ int last_row;
+ int lines_cleared = 0;
+
+ if (the_starting_row + TetrisPiece.TETRIS_PIECE_SIZE > my_board.size()) {
+ last_row = my_board.size();
+
+ } else {
+ last_row = the_starting_row + TetrisPiece.TETRIS_PIECE_SIZE;
+ }
+
+ for (int y = the_starting_row; y < last_row; y++) {
+ if (!my_board.get(y).contains(Boolean.FALSE)) {
+ my_board.remove(y);
+
+ // Add a new row to the top, after the buffer space.
+ my_board.add(TetrisPiece.TETRIS_PIECE_SIZE, generateTetrisRow());
+
+ // Increment lines cleared
+ lines_cleared++;
+
+ setChanged();
+ }
+ }
+
+ // Notify about the cleared lines (has to come before incrementScore)
+ notifyObservers(new TetrisBoardEvent(EventTypes.LINES_CLEARED));
+
+ // Update Score
+ incrementScore(lines_cleared * POINTS_PER_LINE * lines_cleared);
+ }
+}