From 87e98e00e0ca5ac4229091aa9bb0e5c09093f50f Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Thu, 13 Dec 2012 22:14:24 -0800 Subject: Tetris project from TCSS305 class. Original project was in SVN on the school servers, so the versioning history is gone. --- src/tetris/audio/SoundPlayer.java | 323 +++++++++++ src/tetris/audio/TetrisSounds.java | 73 +++ src/tetris/audio/papercrumple.wav | Bin 0 -> 369638 bytes src/tetris/audio/pencileraser.wav | Bin 0 -> 474152 bytes src/tetris/audio/pencilsharpen.aif | Bin 0 -> 1303926 bytes src/tetris/board/EventTypes.java | 47 ++ src/tetris/board/TetrisBoard.java | 620 +++++++++++++++++++++ src/tetris/board/TetrisBoardEvent.java | 37 ++ src/tetris/gui/BoardUI.java | 110 ++++ src/tetris/gui/ImagePanel.java | 70 +++ src/tetris/gui/InfoPanel.java | 212 +++++++ src/tetris/gui/NextPieceDisplay.java | 124 +++++ src/tetris/gui/TetrisGUI.java | 252 +++++++++ src/tetris/gui/TetrisPieceDisplay.java | 85 +++ src/tetris/gui/WrittenButton.java | 155 ++++++ src/tetris/gui/animations/Animation.java | 193 +++++++ .../gui/animations/TetrisPieceAnimation.java | 86 +++ src/tetris/gui/events/TetrisKeyListener.java | 69 +++ src/tetris/gui/handwriting.ttf | Bin 0 -> 95576 bytes src/tetris/gui/images/ImageRes.java | 73 +++ src/tetris/gui/images/background.png | Bin 0 -> 386891 bytes src/tetris/gui/images/gameover.png | Bin 0 -> 9655 bytes src/tetris/gui/images/gameover_background.png | Bin 0 -> 537717 bytes src/tetris/gui/images/paused.png | Bin 0 -> 6142 bytes src/tetris/gui/images/square.png | Bin 0 -> 3689 bytes src/tetris/gui/images/writing_pencil.png | Bin 0 -> 20101 bytes src/tetris/model/IntPoint.java | 83 +++ src/tetris/piece/IPiece.java | 43 ++ src/tetris/piece/JPiece.java | 38 ++ src/tetris/piece/LPiece.java | 39 ++ src/tetris/piece/OPiece.java | 39 ++ src/tetris/piece/SPiece.java | 39 ++ src/tetris/piece/TPiece.java | 38 ++ src/tetris/piece/TetrisPiece.java | 171 ++++++ src/tetris/piece/TetrisPieces.java | 103 ++++ src/tetris/piece/ZPiece.java | 38 ++ src/tetris/tests/board/TetrisBoardTest.java | 219 ++++++++ src/tetris/tests/piece/IPieceTest.java | 39 ++ src/tetris/tests/piece/JPieceTest.java | 42 ++ src/tetris/tests/piece/LPieceTest.java | 43 ++ src/tetris/tests/piece/OPieceTest.java | 41 ++ src/tetris/tests/piece/SPieceTest.java | 42 ++ src/tetris/tests/piece/TPieceTest.java | 43 ++ src/tetris/tests/piece/TetrisPieceTest.java | 182 ++++++ src/tetris/tests/piece/ZPieceTest.java | 42 ++ 45 files changed, 3853 insertions(+) create mode 100644 src/tetris/audio/SoundPlayer.java create mode 100644 src/tetris/audio/TetrisSounds.java create mode 100644 src/tetris/audio/papercrumple.wav create mode 100644 src/tetris/audio/pencileraser.wav create mode 100644 src/tetris/audio/pencilsharpen.aif create mode 100644 src/tetris/board/EventTypes.java create mode 100644 src/tetris/board/TetrisBoard.java create mode 100644 src/tetris/board/TetrisBoardEvent.java create mode 100644 src/tetris/gui/BoardUI.java create mode 100644 src/tetris/gui/ImagePanel.java create mode 100644 src/tetris/gui/InfoPanel.java create mode 100644 src/tetris/gui/NextPieceDisplay.java create mode 100644 src/tetris/gui/TetrisGUI.java create mode 100644 src/tetris/gui/TetrisPieceDisplay.java create mode 100644 src/tetris/gui/WrittenButton.java create mode 100644 src/tetris/gui/animations/Animation.java create mode 100644 src/tetris/gui/animations/TetrisPieceAnimation.java create mode 100644 src/tetris/gui/events/TetrisKeyListener.java create mode 100755 src/tetris/gui/handwriting.ttf create mode 100644 src/tetris/gui/images/ImageRes.java create mode 100644 src/tetris/gui/images/background.png create mode 100644 src/tetris/gui/images/gameover.png create mode 100644 src/tetris/gui/images/gameover_background.png create mode 100644 src/tetris/gui/images/paused.png create mode 100644 src/tetris/gui/images/square.png create mode 100644 src/tetris/gui/images/writing_pencil.png create mode 100644 src/tetris/model/IntPoint.java create mode 100644 src/tetris/piece/IPiece.java create mode 100644 src/tetris/piece/JPiece.java create mode 100644 src/tetris/piece/LPiece.java create mode 100644 src/tetris/piece/OPiece.java create mode 100644 src/tetris/piece/SPiece.java create mode 100644 src/tetris/piece/TPiece.java create mode 100644 src/tetris/piece/TetrisPiece.java create mode 100644 src/tetris/piece/TetrisPieces.java create mode 100644 src/tetris/piece/ZPiece.java create mode 100644 src/tetris/tests/board/TetrisBoardTest.java create mode 100644 src/tetris/tests/piece/IPieceTest.java create mode 100644 src/tetris/tests/piece/JPieceTest.java create mode 100644 src/tetris/tests/piece/LPieceTest.java create mode 100644 src/tetris/tests/piece/OPieceTest.java create mode 100644 src/tetris/tests/piece/SPieceTest.java create mode 100644 src/tetris/tests/piece/TPieceTest.java create mode 100644 src/tetris/tests/piece/TetrisPieceTest.java create mode 100644 src/tetris/tests/piece/ZPieceTest.java (limited to 'src') diff --git a/src/tetris/audio/SoundPlayer.java b/src/tetris/audio/SoundPlayer.java new file mode 100644 index 0000000..6e2cf8f --- /dev/null +++ b/src/tetris/audio/SoundPlayer.java @@ -0,0 +1,323 @@ +/* + * Audio Clip Player TCSS 305 - Autumn 2008 (Zimmerman) + */ + +package tetris.audio; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Clip; +import javax.sound.sampled.Line; +import javax.sound.sampled.LineEvent; +import javax.sound.sampled.LineListener; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.UnsupportedAudioFileException; + +/** + * A class to play audio clips. Caches previously-played clips, allowing fast + * re-playback of previously played sounds. + * + * @author Marty Stepp + * @author Daniel M. Zimmerman (code cleanup) + * @version 1.0 + */ + +public class SoundPlayer { + /** + * A cache of previously-played audio clips. + */ + + private final Map my_clips = new HashMap(); + + /** + * Plays the audio file with the given file name. This method returns + * instantly, without waiting for the clip to finish playing. + * + * @param the_filename The name of the file (relative to the current working + * directory) to play. + * @return a Clip object representing the sound played. (for convenience; this + * result need not be used/stored). + * @exception IllegalArgumentException if there is a problem reading from the + * sound file. + */ + + public Clip play(final String the_filename) throws IllegalArgumentException { + final Clip clip = getClip(the_filename); + + if (clip != null) { + clip.start(); + } + + return clip; + } + + /** + * Plays the audio file with the given file name, waiting until the clip is + * done playing before returning. + * + * @param the_filename The name of the file (relative to the current working + * directory) to play. + * @return a Clip object representing the sound played. (for convenience; this + * result need not be used/stored). + * @exception IllegalArgumentException if there is a problem reading from the + * sound file. + */ + + public Clip playAndWait(final String the_filename) throws IllegalArgumentException { + final Clip clip = getClip(the_filename); + + if (clip != null) { + clip.start(); + try { + synchronized (clip) { + while (clip.isRunning()) { + clip.wait(); // wait for clip to finish + } + } + } catch (final InterruptedException ie) { + // ignored + return clip; + } + } + + return clip; + } + + /** + * Plays the clip with the given file name in a continuous loop. The clip + * keeps looping until it is later stopped by calling the stop() method. This + * function returns instantly + * + * @param the_filename The name of the file (relative to the current working + * directory) to play. + * @return a Clip object representing the sound played. (for convenience; this + * result need not be used/stored). + * @exception IllegalArgumentException if there is a problem reading from the + * sound file. + */ + + public Clip loop(final String the_filename) throws IllegalArgumentException { + return loop(the_filename, Clip.LOOP_CONTINUOUSLY); + } + + /** + * Plays the clip with the given file name in a loop. The clip loops until it + * has played the specified number of times, or until it is later stopped by + * calling the stop() method. This function returns instantly, without waiting + * for the clip to finish looping. + * + * @param the_filename The name of the file (relative to the current working + * directory) to play. + * @param the_number_of_times The number of times to loop the clip. + * @return a Clip object representing the sound played. (for convenience; this + * result need not be used/stored). + * @exception IllegalArgumentException if there is a problem reading from the + * sound file. + */ + + public Clip loop(final String the_filename, final int the_number_of_times) + throws IllegalArgumentException { + final Clip clip = getClip(the_filename); + + if (clip != null) { + clip.loop(the_number_of_times); + } + + return clip; + } + + /** + * Plays the clip with the given file name in a loop. The clip loops + * continuously until it is stopped by calling the stop() method. This method + * waits until the clip is done looping before returning. Note that since the + * clip loops continuously, this method will not return unless some other + * thread stops the clip. + * + * @param the_filename The name of the file (relative to the current working + * directory) to play. + * @return a Clip object representing the sound played. (for convenience; this + * result need not be used/stored). + * @exception IllegalArgumentException if there is a problem reading from the + * sound file. + */ + + public Clip loopAndWait(final String the_filename) throws IllegalArgumentException { + return loopAndWait(the_filename, Clip.LOOP_CONTINUOUSLY); + } + + /** + * Plays the clip with the given file name in a loop. The clip loops until it + * has played the specified number of times, or until it is stopped by calling + * the stop() method. This method waits until the clip is done looping before + * returning. + * + * @param the_filename The name of the file (relative to the current working + * directory) to play. + * @param the_number_of_times The number of times to loop the clip. + * @return a Clip object representing the sound played. (for convenience; this + * result need not be used/stored). + * @exception IllegalArgumentException if there is a problem reading from the + * sound file. + */ + + public Clip loopAndWait(final String the_filename, final int the_number_of_times) + throws IllegalArgumentException { + final Clip clip = getClip(the_filename); + + if (clip != null) { + clip.loop(the_number_of_times); + try { + while (clip.isRunning()) { + clip.wait(); // wait for clip to finish + } + } catch (final InterruptedException ie) { + return clip; + } + } + + return clip; + } + + /** + * Pauses the clip with the given file name. If the clip is later played, it + * will resume from where it was paused. Calling this method does not resume a + * thread that is suspended on a playAndWait() or a loopAndWait(). + * + * If stop() is called on a paused clip, it will reset to the beginning of the + * clip for the next play. + * + * @param the_filename The name of the file (relative to the current working + * directory) to pause. + * @exception IllegalArgumentException if there is a problem reading from or + * playing the sound file. + */ + + public void pause(final String the_filename) throws IllegalArgumentException { + final Clip clip = getClip(the_filename); + + if (clip != null) { + final int pos = clip.getFramePosition(); + clip.stop(); + clip.setFramePosition(pos); + } + } + + /** + * Stops the clip with the specified filename (and wakes up any threads + * waiting for it to finish playing). + * + * @param the_filename The name of the file (relative to the current working + * directory) to stop. + * @return a Clip object representing the sound stopped. + * @exception IllegalArgumentException if there is a problem reading from the + * sound file. + */ + + public Clip stop(final String the_filename) throws IllegalArgumentException { + final Clip clip = getClip(the_filename); + stopClip(clip); + + return clip; + } + + /** + * Stops all currently playing sound clips (and wakes up the threads waiting + * for them to finish playing). + */ + + public void stopAll() { + for (Clip clip : my_clips.values()) { + stopClip(clip); + } + } + + /** + * Preloads the clip at the given file name. This means the clip will be + * available faster, when requested for playing the first time. + * + * @param the_filename The name of the file (relative to the current working + * directory) to preload. + * @return a Clip object representing the preloaded sound. + * @exception IllegalArgumentException if there is a problem reading from the + * sound file. + */ + + public Clip preLoad(final String the_filename) throws IllegalArgumentException { + return getClip(the_filename); + } + + /** + * Returns a Clip object for a filename, either by creating a new one or + * loading it from the cache. + * + * @param the_filename The name of the file (relative to the current working + * directory) to load. + * @return a Clip object, or null if one is not found. + * @exception IllegalArgumentException if there is a problem reading from the + * sound file. + */ + + private Clip getClip(final String the_filename) throws IllegalArgumentException { + Clip clip = null; + AudioInputStream ais = null; + + if (my_clips.containsKey(the_filename)) { + clip = my_clips.get(the_filename); + } else { + // read audio file from disk + try { + ais = AudioSystem.getAudioInputStream(ClassLoader.getSystemResource(the_filename)); + clip = (Clip) AudioSystem.getLine(new Line.Info(Clip.class)); + clip.open(ais); + clip.addLineListener(new LineListener() { + /** + * Responds to audio events generated by clips. + * + * @param the_event The event generated. + */ + + public void update(final LineEvent the_event) { + if (the_event.getType() == LineEvent.Type.STOP) { + // clip is done playing + stopClip((Clip) the_event.getSource()); + } + } + }); + my_clips.put(the_filename, clip); + } catch (final UnsupportedAudioFileException uafe) { + throw new IllegalArgumentException("Not a valid supported audio file: \"" + + the_filename + "\""); + } catch (final LineUnavailableException lue) { + throw new IllegalArgumentException("Line is not available to play sound \"" + + the_filename + "\""); + } catch (final IOException ioe) { + throw new IllegalArgumentException("I/O error while reading file: \"" + the_filename + + "\""); + } + } + + return clip; + } + + /** + * Stops the playing of the specified clip. + * + * @param the_clip The clip. + */ + + private void stopClip(final Clip the_clip) { + if (the_clip != null) { + synchronized (the_clip) { + the_clip.stop(); + the_clip.setFramePosition(0); + the_clip.notifyAll(); // awaken threads waiting for this Clip + } + } + } +} + +// end of class SoundPlayer diff --git a/src/tetris/audio/TetrisSounds.java b/src/tetris/audio/TetrisSounds.java new file mode 100644 index 0000000..b28e49a --- /dev/null +++ b/src/tetris/audio/TetrisSounds.java @@ -0,0 +1,73 @@ +/* + * Jesse Morgan + * + * TCSS 305 - Autumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.audio; + +import java.util.Observable; +import java.util.Observer; + +import tetris.board.TetrisBoardEvent; + + +/** + * Class to play sounds for a tetris game. + * + * @author Jesse Morgan + * @version 1.0 23 November 2009 + */ +public class TetrisSounds implements Observer { + // Private fields. + /** + * The SoundPlayer. + */ + private final SoundPlayer my_soundplayer; + + /** + * Create a Tetris Sound Effects player. + */ + public TetrisSounds() { + my_soundplayer = new SoundPlayer(); + } + + /** + * Handle notifications. + * + * @param the_observable The TetrisBoard. + * @param the_arg The Event. + */ + public void update(final Observable the_observable, final Object the_arg) { + if (the_arg instanceof TetrisBoardEvent) { + final TetrisBoardEvent event = (TetrisBoardEvent) the_arg; + + try { + switch (event.getType()) { + case NEW_GAME: + my_soundplayer.play("tetris/audio/pencilsharpen.aif"); + break; + + case LINES_CLEARED: + my_soundplayer.play("tetris/audio/pencileraser.wav"); + break; + + case GAME_OVER: + my_soundplayer.play("tetris/audio/papercrumple.wav"); + break; + + default: + } + + } catch (final IllegalArgumentException the_exception) { + // Couldn't play for some reason... + // Sounds aren't important anyways, gracefully ignore + return; + } + } + } + + +} diff --git a/src/tetris/audio/papercrumple.wav b/src/tetris/audio/papercrumple.wav new file mode 100644 index 0000000..ed8e759 Binary files /dev/null and b/src/tetris/audio/papercrumple.wav differ diff --git a/src/tetris/audio/pencileraser.wav b/src/tetris/audio/pencileraser.wav new file mode 100644 index 0000000..ca371ec Binary files /dev/null and b/src/tetris/audio/pencileraser.wav differ diff --git a/src/tetris/audio/pencilsharpen.aif b/src/tetris/audio/pencilsharpen.aif new file mode 100644 index 0000000..a7344b5 Binary files /dev/null and b/src/tetris/audio/pencilsharpen.aif differ diff --git a/src/tetris/board/EventTypes.java b/src/tetris/board/EventTypes.java new file mode 100644 index 0000000..dbb0293 --- /dev/null +++ b/src/tetris/board/EventTypes.java @@ -0,0 +1,47 @@ +/* + * Jesse Morgan + * + * TCSS 305 - Autumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.board; + +/** + * Enumeration of the various event types a TetrisBoard generates. + * + * @author Jesse Morgan + * @version 1.0 11 December 2009. + */ +public enum EventTypes { + /** + * New Game Event. + */ + NEW_GAME, + + /** + * Game Over Event. + */ + GAME_OVER, + + /** + * Game Paused/Unpaused. + */ + PAUSE_CHANGED, + + /** + * Lines Cleared. + */ + LINES_CLEARED, + + /** + * Current piece updated. + */ + CURRENT_PIECE_UPDATE, + + /** + * Points Changed. + */ + SCORE_CHANGED +} 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 + * + * 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 + * @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> my_board; + + /** + * Board width. + */ + private final int my_board_width; + + /** + * Board height. + */ + private final int my_board_height; + + /** + * Future Tetris Pieces. + */ + private List my_future_pieces; + + /** + * Testing Pieces. + */ + private List 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>(); + 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(); + my_testing_pieces = new ArrayList(); + } + + /** + * 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 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 getTetrisBlocks() { + final List points = new ArrayList(); + + // 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 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 generateTetrisRow() { + final List row = new ArrayList(); + + // 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); + } +} diff --git a/src/tetris/board/TetrisBoardEvent.java b/src/tetris/board/TetrisBoardEvent.java new file mode 100644 index 0000000..5514369 --- /dev/null +++ b/src/tetris/board/TetrisBoardEvent.java @@ -0,0 +1,37 @@ +/* + * Jesse Morgan + * + * TCSS 305 - Autumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.board; + +/** + * Base class for tetris board events. + * + * @author Jesse Morgan + * @version 1.0 11 December 2009. + */ +public class TetrisBoardEvent { + /** + * The type of this event. + */ + private final EventTypes my_event; + + /** + * Create an event. + * @param the_event The event type. + */ + public TetrisBoardEvent(final EventTypes the_event) { + my_event = the_event; + } + + /** + * @return the associated EventType. + */ + public EventTypes getType() { + return my_event; + } +} diff --git a/src/tetris/gui/BoardUI.java b/src/tetris/gui/BoardUI.java new file mode 100644 index 0000000..2bdfb6f --- /dev/null +++ b/src/tetris/gui/BoardUI.java @@ -0,0 +1,110 @@ +/* + * Jesse Morgan + * + * TCSS 305 � Autumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.gui; + +import java.awt.Graphics; +import java.awt.Image; +import java.util.Observable; + +import tetris.board.TetrisBoard; +import tetris.gui.images.ImageRes; + +/** + * Panel that draws the tetris board. + * + * @author Jesse Morgan + * @version 1.0 23 November 2009 + */ +@SuppressWarnings("serial") +public class BoardUI extends TetrisPieceDisplay { + /** + * Flag to indicate game over. + */ + private boolean my_game_over; + + /** + * Flag to indicate game paused. + */ + private boolean my_game_paused; + + /** + * The Game Over graphic. + */ + private final Image my_gameover_graphic; + + /** + * The Paused graphic. + */ + private final Image my_paused_graphic; + + /** + * Constructor. + * + * @param the_width Number of columns. + * @param the_height Number of rows. + * + */ + public BoardUI(final int the_width, final int the_height) { + // Call parent + super(the_width, the_height); + + my_game_over = false; + my_game_paused = false; + + my_gameover_graphic = ImageRes.loadImage(ImageRes.GAME_OVER_LABEL); + + my_paused_graphic = ImageRes.loadImage(ImageRes.PAUSED_LABEL); + + } + + /** + * Update the board. + * + * @param the_observable The tetris board we receive updates for. + * @param the_argument Unused argument. + */ + public void update(final Observable the_observable, final Object the_argument) { + if (the_observable instanceof TetrisBoard) { + final TetrisBoard tb = (TetrisBoard) the_observable; + + my_bricks = tb.getTetrisBlocks(); + my_game_over = tb.isGameOver(); + my_game_paused = tb.isPaused(); + + repaint(); + } + + + } + + /** + * Paint Game Over or Paused if needed. + * + * @param the_graphics The Graphics to draw on (expected to be a Graphics2D). + */ + protected void paintComponent(final Graphics the_graphics) { + super.paintComponent(the_graphics); + + if (my_game_over) { + the_graphics.drawImage(my_gameover_graphic, + 0, + (getHeight() - my_gameover_graphic.getHeight(null)) / 2, + null); + + } else if (my_game_paused) { + the_graphics.drawImage(my_paused_graphic, + 0, + (getHeight() - my_paused_graphic.getHeight(null)) / 2, + null); + } + } + + + +} diff --git a/src/tetris/gui/ImagePanel.java b/src/tetris/gui/ImagePanel.java new file mode 100644 index 0000000..d558788 --- /dev/null +++ b/src/tetris/gui/ImagePanel.java @@ -0,0 +1,70 @@ +/* + * Jesse Morgan + * + * TCSS 305 � Autumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Image; + +import javax.swing.JPanel; + +/** + * Custom panel that displays an image in the background. + * @author Jesse Morgan + * @version 1.0 4 Dec 2009 + */ +@SuppressWarnings("serial") +public class ImagePanel extends JPanel { + /** + * The image that contains the background. + */ + private Image my_image; + + /** + * Constructor. Defaults to BorderLayout. + * @param the_image The background Image. + */ + public ImagePanel(final Image the_image) { + super(); + + setLayout(new BorderLayout()); + + my_image = the_image; + setPreferredSize(new Dimension(the_image.getWidth(null), the_image.getHeight(null))); + } + + /** + * @return the current background Image. + */ + public Image getBackgroundImage() { + return my_image; + } + + /** + * Set a new background Image. + * + * @param the_image the new Image. + */ + public void setBackgroundImage(final Image the_image) { + my_image = the_image; + setPreferredSize(new Dimension(the_image.getWidth(null), the_image.getHeight(null))); + } + + /** + * Draws the background image. + * + * @param the_graphics The Graphics Context. + */ + public void paintComponent(final Graphics the_graphics) { + setOpaque(false); + the_graphics.drawImage(my_image, 0, 0, null); + super.paintComponent(the_graphics); + } +} \ No newline at end of file diff --git a/src/tetris/gui/InfoPanel.java b/src/tetris/gui/InfoPanel.java new file mode 100644 index 0000000..a3bdd8f --- /dev/null +++ b/src/tetris/gui/InfoPanel.java @@ -0,0 +1,212 @@ +/* + * Jesse Morgan + * + * TCSS 305 - Autumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.gui; + +import java.awt.Font; +import java.awt.FontFormatException; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; +import java.io.InputStream; +import java.util.Observable; +import java.util.Observer; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import tetris.board.TetrisBoard; + +/** + * JPanel of info about the game. + * + * @author Jesse Morgan + * @version 1.0 11 December 2009 + */ +@SuppressWarnings("serial") +public class InfoPanel extends JPanel implements Observer { + // Private Constants + /** + * The wording of the new game button. + */ + private static final String NEWGAME_LABEL = "New Game"; + + /** + * The wording of the pause button. + */ + private static final String PAUSE_LABEL = "Pause"; + + /** + * The wording of the unpause button. + */ + private static final String UNPAUSE_LABEL = "Unpause"; + + // Obnoxious UI layout constants follow + /** + * Space between the top of the panel and the Score. + */ + private static final int UPPER_SPACING = 18; + + /** + * Space between the level and the next piece. + */ + private static final int MIDDLE_SPACING = 38; + + /** + * Size of the score. + */ + private static final float SCORE_FONT_SIZE = 32f; + + /** + * Size of the level. + */ + private static final float LEVEL_FONT_SIZE = 35f; + + /** + * The TetrisBoard we're displaying info for. + */ + private final TetrisBoard my_board; + + /** + * The score label. + */ + private final JLabel my_score_label; + + /** + * The level label. + */ + private final JLabel my_level_label; + + /** + * Construct the Info Panel. + * + * @param the_board The tetris board we belong to. + */ + public InfoPanel(final TetrisBoard the_board) { + super(); + + my_board = the_board; + the_board.addObserver(this); + + // Setup Panel + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + setOpaque(false); + + // Create Components + my_score_label = new JLabel("0"); + my_level_label = new JLabel("Level 1"); + + setupPanel(); + } + + /** + * Handle notifications. + * + * @param the_observable The TetrisBoard. + * @param the_arg Unused argument. + */ + public void update(final Observable the_observable, final Object the_arg) { + if (the_observable instanceof TetrisBoard) { + final TetrisBoard tb = (TetrisBoard) the_observable; + + my_score_label.setText(String.valueOf(tb.getScore())); + + my_level_label.setText("Level " + tb.getLevel()); + } + } + + + /** + * Helper method to setup the east side. + */ + private void setupPanel() { + Font base_font; + + // Load the font. + try { + final InputStream fis = getClass().getResourceAsStream("/tetris/gui/handwriting.ttf"); + base_font = Font.createFont(Font.TRUETYPE_FONT, fis); + fis.close(); + + } catch (final FontFormatException the_exception) { + base_font = Font.getFont(Font.SANS_SERIF); + + } catch (final IOException the_exception) { + base_font = Font.getFont(Font.SANS_SERIF); + } + + // Add a Strut to move everything onto a line. + add(Box.createVerticalStrut(UPPER_SPACING)); + + // Display the Score + my_score_label.setFont(base_font.deriveFont(SCORE_FONT_SIZE)); + my_score_label.setAlignmentX(CENTER_ALIGNMENT); + add(my_score_label); + + + // Display the level + my_level_label.setFont(base_font.deriveFont(LEVEL_FONT_SIZE)); + my_level_label.setAlignmentX(CENTER_ALIGNMENT); + add(my_level_label); + + // Add a bit of space between the level and the next piece display. + add(Box.createVerticalStrut(MIDDLE_SPACING)); + + // Add the next piece component + final NextPieceDisplay nextpiece = new NextPieceDisplay(); + add(nextpiece); + nextpiece.setAlignmentX(CENTER_ALIGNMENT); + my_board.addObserver(nextpiece); + + + + // New Game Button + final WrittenButton new_game = new WrittenButton(); + new_game.setText(NEWGAME_LABEL); + new_game.setAlignmentX(CENTER_ALIGNMENT); + add(new_game); + + // Pause Game Button + final WrittenButton pause_game = new WrittenButton(); + pause_game.setText(PAUSE_LABEL); + pause_game.setAlignmentX(CENTER_ALIGNMENT); + add(pause_game); + + // Setup Action Listeners + + // New Game + new_game.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent the_event) { + my_board.newGame(); + } + }); + + // Pause Game + pause_game.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent the_event) { + final WrittenButton b = (WrittenButton) the_event.getSource(); + + // Do nothing if the game is over. + if (my_board.isGameOver()) { + return; + } + + if (b.getText().equals(PAUSE_LABEL)) { + my_board.setPaused(true); + b.setText(UNPAUSE_LABEL); + + } else { + my_board.setPaused(false); + b.setText(PAUSE_LABEL); + } + } + }); + } +} diff --git a/src/tetris/gui/NextPieceDisplay.java b/src/tetris/gui/NextPieceDisplay.java new file mode 100644 index 0000000..4f0f543 --- /dev/null +++ b/src/tetris/gui/NextPieceDisplay.java @@ -0,0 +1,124 @@ +/* + * Jesse Morgan + * + * TCSS 305 � Autumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.gui; + +import java.awt.Graphics; +import java.awt.Image; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Observable; + +import javax.swing.Timer; + +import tetris.board.TetrisBoard; +import tetris.gui.animations.TetrisPieceAnimation; +import tetris.gui.images.ImageRes; +import tetris.model.IntPoint; +import tetris.piece.TPiece; +import tetris.piece.TetrisPiece; + +/** + * Panel that draws the tetris board. + * + * @author Jesse Morgan + * @version 1.0 1 Dec 2009 + */ +@SuppressWarnings("serial") +public class NextPieceDisplay extends TetrisPieceDisplay { + /** + * The Pencil Graphic. + */ + private static final Image PENCIL = ImageRes.loadImage(ImageRes.WRITING_PENCIL); + + + /** + * The next piece. + */ + private TetrisPiece my_next_piece; + + /** + * Our animator. + */ + private TetrisPieceAnimation my_animation; + + /** + * Animation timer. + */ + private final Timer my_timer; + + + /** + * Constructor. + */ + public NextPieceDisplay() { + // Call parent + super(TetrisPiece.TETRIS_PIECE_SIZE, TetrisPiece.TETRIS_PIECE_SIZE); + + my_animation = new TetrisPieceAnimation(new TPiece(0, 0)); + + my_timer = new Timer(TetrisPieceAnimation.FRAME_RATE, new ActionListener() { + public void actionPerformed(final ActionEvent the_event) { + my_animation.stepAnimation(); + repaint(); + } + }); + } + + /** + * Update the next piece display. + * + * @param the_observable The tetris board we receive updates for. + * @param the_argument Unused argument. + */ + public void update(final Observable the_observable, final Object the_argument) { + if (the_observable instanceof TetrisBoard) { + final TetrisBoard tb = (TetrisBoard) the_observable; + + // If the next piece we know about is not the same next piece... + if (tb.getNextPiece() != my_next_piece) { + my_next_piece = tb.getNextPiece(); + my_bricks.clear(); + + for (IntPoint p : my_next_piece.getBoardCoordinates()) { + my_bricks.add(new IntPoint(p.getX() - my_next_piece.getX(), + p.getY() - my_next_piece.getY() + + TetrisBoard.NEW_PIECE_BUFFER)); + } + + my_animation = new TetrisPieceAnimation(my_next_piece); + startAnimation(); + } + + repaint(); + } + } + + @Override + protected void paintComponent(final Graphics the_graphics) { + super.paintComponent(the_graphics); + + if (my_animation.isRunning()) { + final int x = my_animation.getX(); + final int y = my_animation.getY() - PENCIL.getHeight(null); + the_graphics.drawImage(PENCIL, x, y, null); + // Normally you'd draw the animation image here, but we just want the pencil to move. + } + } + + /** + * Start the animation and timer. + */ + private void startAnimation() { + my_animation.start(); + my_timer.start(); + } + + + +} diff --git a/src/tetris/gui/TetrisGUI.java b/src/tetris/gui/TetrisGUI.java new file mode 100644 index 0000000..fd463a8 --- /dev/null +++ b/src/tetris/gui/TetrisGUI.java @@ -0,0 +1,252 @@ +/* + * Jesse Morgan + * + * TCSS 305 - Autumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.gui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.Observable; +import java.util.Observer; + +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.Timer; + +import tetris.audio.TetrisSounds; +import tetris.board.TetrisBoard; +import tetris.board.TetrisBoardEvent; +import tetris.gui.events.TetrisKeyListener; +import tetris.gui.images.ImageRes; + +/** + * Tetris UI Main Frame. + * + * @author Jesse Morgan + * @version 1.0 23 November 2009 + */ +@SuppressWarnings("serial") +public class TetrisGUI extends JFrame implements Observer { + /** + * UI Padding at the top of the frame. + */ + private static final int NORTH_PADDING = 100; + + /** + * UI Padding on the west side of the frame. + */ + private static final int WEST_PADDING = 75; + + /** + * The TetrisBoard we're playing on. + */ + private final TetrisBoard my_board; + + /** + * The gameplay timer. + */ + private Timer my_timer; + + /** + * Stores the background image panel. + */ + private final ImagePanel my_background_panel; + + /** + * Constructor for the tetris UI. + */ + public TetrisGUI() { + super("TCSS 305 Tetris"); + + // Setup Global UI Elements + my_background_panel = new ImagePanel(ImageRes.loadImage(ImageRes.GAME_BACKGROUND)); + add(my_background_panel); + + // Create the board. + my_board = new TetrisBoard(TetrisBoard.STANDARD_BOARD_WIDTH, + TetrisBoard.STANDARD_BOARD_HEIGHT); + + // We Observe the Board. + my_board.addObserver(this); + + // Add a board UI + final BoardUI board_ui = new BoardUI(TetrisBoard.STANDARD_BOARD_WIDTH, + TetrisBoard.STANDARD_BOARD_HEIGHT); + my_background_panel.add(board_ui, BorderLayout.CENTER); + + my_board.addObserver(board_ui); + + // Add an InfoPanel + final InfoPanel info = new InfoPanel(my_board); + my_background_panel.add(info, BorderLayout.EAST); + + // Create a tetris sound player + final TetrisSounds ts = new TetrisSounds(); + my_board.addObserver(ts); + + setupFrame(); + setupUIPadding(); + setupListeners(); + } + + /** + * Start the game. + */ + public void start() { + my_board.setPaused(false); + my_timer.start(); + } + + /** + * Pause the game. + */ + public void pause() { + my_board.setPaused(true); + my_timer.stop(); + } + + /** + * Update the level and score. + * + * @param the_observable Should be an instance of TetrisBoard + * @param the_arg Unused by this method. + */ + public void update(final Observable the_observable, final Object the_arg) { + if (the_observable instanceof TetrisBoard) { + final TetrisBoard tb = (TetrisBoard) the_observable; + final TetrisBoardEvent event = (TetrisBoardEvent) the_arg; + + switch (event.getType()) { + case NEW_GAME: + my_background_panel.setBackgroundImage(ImageRes.loadImage(ImageRes.GAME_BACKGROUND)); + repaint(); + start(); + // Purposefully falling through.... + + case SCORE_CHANGED: + my_timer.setDelay(tb.getSpeed()); + break; + + case GAME_OVER: + my_background_panel.setBackgroundImage(ImageRes. + loadImage(ImageRes.GAMEOVER_BACKGROUND)); + repaint(); + break; + + default: + } + } + } + + /** + * Game Entry Point. + * + * @param the_args Unused command line arguments. + */ + public static void main(final String[] the_args) { + final TetrisGUI ui = new TetrisGUI(); + + ui.setVisible(true); + } + + /** + * Helper method to setup various frame attributes. + */ + private void setupFrame() { + // Set frame attributes + setSize(my_background_panel.getPreferredSize()); + setResizable(false); + setDefaultCloseOperation(EXIT_ON_CLOSE); + } + + /** + * Helper method to setup the padding on the UI. + */ + private void setupUIPadding() { + // North Side + final JPanel north_panel = new JPanel(); + north_panel.setOpaque(false); + north_panel.setPreferredSize(new Dimension(0, NORTH_PADDING)); + + // West Side + final JPanel west_panel = new JPanel(); + west_panel.setOpaque(false); + west_panel.setPreferredSize(new Dimension(WEST_PADDING, 0)); + + my_background_panel.add(north_panel, BorderLayout.NORTH); + my_background_panel.add(west_panel, BorderLayout.WEST); + } + + /** + * Helper methods to setup event listeners. + */ + private void setupListeners() { + // Add Key Listener + addKeyListener(new TetrisKeyListener(my_board)); + + // Add Window Listener + addWindowListener(new TetrisWindowListener()); + + // Add a timer + my_timer = new Timer(my_board.getSpeed(), new ActionListener() { + public void actionPerformed(final ActionEvent the_event) { + my_board.progressBoard(); + } + }); + } + + /** + * Starts and stops the timer based off of WindowEvents. + * + * @author Jesse Morgan + * @version 1.0 1 Dec 2009 + */ + private class TetrisWindowListener extends WindowAdapter { + /** + * Flag to store if we lost focus. + */ + private boolean my_lost_focus_flag; + + /** + * Create the TetrisWindowListener. + */ + public TetrisWindowListener() { + super(); + my_lost_focus_flag = false; + } + + @Override + public void windowActivated(final WindowEvent the_event) { + super.windowActivated(the_event); + if (my_lost_focus_flag && my_board.isPaused()) { + start(); + my_lost_focus_flag = false; + } + } + + @Override + public void windowClosed(final WindowEvent the_event) { + super.windowClosed(the_event); + pause(); + } + + @Override + public void windowDeactivated(final WindowEvent the_event) { + super.windowDeactivated(the_event); + + // Don't pause if we're already paused. + if (!my_board.isPaused()) { + my_lost_focus_flag = true; + pause(); + } + } + } +} diff --git a/src/tetris/gui/TetrisPieceDisplay.java b/src/tetris/gui/TetrisPieceDisplay.java new file mode 100644 index 0000000..409942c --- /dev/null +++ b/src/tetris/gui/TetrisPieceDisplay.java @@ -0,0 +1,85 @@ +/* + * Jesse Morgan + * + * TCSS 305 � Autumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.gui; + +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Image; +import java.util.ArrayList; +import java.util.List; +import java.util.Observer; + +import javax.swing.JPanel; + +import tetris.board.TetrisBoard; +import tetris.gui.images.ImageRes; +import tetris.model.IntPoint; + +/** + * Abstract component capable of drawing out tetris pieces. + * + * @author Jesse Morgan + * @version 1.0 1 Dec 2009 + */ +@SuppressWarnings("serial") +public abstract class TetrisPieceDisplay extends JPanel implements Observer { + // Private contants + /** + * Pixel size of one tetris piece. + */ + private static final int BRICK_SIZE = 27; + + //Private fields + /** + * List of the bricks on the screen. + */ + protected List my_bricks; + + /** + * Storage for the brick image. + */ + private final Image my_brick_image; + + /** + * Constructor. + * + * @param the_width Number of columns. + * @param the_height Number of rows. + */ + public TetrisPieceDisplay(final int the_width, final int the_height) { + // Call parent + super(); + + // Load brick image + my_brick_image = ImageRes.loadImage(ImageRes.TETRIS_BLOCK); + + // Create our brick list + my_bricks = new ArrayList(); + + // Set some hints for the layout manager + final Dimension d = new Dimension(the_width * my_brick_image.getWidth(null), + the_height * my_brick_image.getHeight(null)); + setPreferredSize(d); + setMinimumSize(d); + setMaximumSize(d); + setOpaque(false); + } + + @Override + protected void paintComponent(final Graphics the_graphics) { + super.paintComponent(the_graphics); + + for (IntPoint brick : my_bricks) { + the_graphics.drawImage(my_brick_image, + brick.getX() * BRICK_SIZE, + (brick.getY() - TetrisBoard.NEW_PIECE_BUFFER) * BRICK_SIZE, + null); + } + } +} diff --git a/src/tetris/gui/WrittenButton.java b/src/tetris/gui/WrittenButton.java new file mode 100644 index 0000000..2a0e55f --- /dev/null +++ b/src/tetris/gui/WrittenButton.java @@ -0,0 +1,155 @@ +package tetris.gui; + +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontFormatException; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.io.InputStream; + +import javax.swing.AbstractButton; +import javax.swing.JLabel; + +import tetris.gui.images.ImageRes; + +/** + * WrittenButton is a button with a handwritten font and a pencil roll-over. + * + * @author Jesse Morgan + * @version 1.0 8 December 2009. + */ +@SuppressWarnings("serial") +public class WrittenButton extends AbstractButton { + /** + * The pencil icon used in the roll-over. + */ + private static final Image PENCIL = ImageRes.loadImage(ImageRes.WRITING_PENCIL); + + /** + * Default font size. + */ + private static final float DEFAULT_FONT_SIZE = 36; + + /** + * How much pencil to show. + */ + private static final int PENCIL_WIDTH = 50; + + /** + * An adjustment to the Y of the pencil. + */ + private static final int PENCIL_ADJUSTMENT = 10; + + /** + * The button label. + */ + private final JLabel my_label; + + /** + * Is the mouse over the button? + */ + private boolean my_rolledover; + + /** + * Default constrcutor. + */ + public WrittenButton() { + super(); + + my_rolledover = false; + + // Setup Button + addMouseListener(new MouseHandler()); + setOpaque(false); + + // Setup Label + my_label = new JLabel(); + add(my_label); + } + + @Override + public void setText(final String the_label) { + super.setText(the_label); + + Font base_font; + + try { + final InputStream fis = getClass().getResourceAsStream("/tetris/gui/handwriting.ttf"); + base_font = Font.createFont(Font.TRUETYPE_FONT, fis); + fis.close(); + + } catch (final FontFormatException the_exception) { + base_font = Font.getFont(Font.SANS_SERIF); + + } catch (final IOException the_exception) { + base_font = Font.getFont(Font.SANS_SERIF); + } + + my_label.setText(the_label); + my_label.setFont(base_font.deriveFont(DEFAULT_FONT_SIZE)); + + // Set some hints for the layout manager. + final Dimension ls = my_label.getPreferredSize(); + ls.width += PENCIL_WIDTH; + setPreferredSize(ls); + setMinimumSize(ls); + setMaximumSize(ls); + } + + @Override + public String getText() { + return my_label.getText(); + } + + @Override + protected void paintComponent(final Graphics the_graphics) { + super.paintComponent(the_graphics); + + if (my_rolledover) { + final int x = my_label.getX() + my_label.getWidth(); + final int y = my_label.getY() + my_label.getHeight() - + PENCIL.getHeight(null) - PENCIL_ADJUSTMENT; + + the_graphics.drawImage(PENCIL, x, y, null); + } + } + + /** + * Mouse handler for the button. + * + * @author Jesse Morgan + * @version 1.0 8 Dec 2009 + */ + private class MouseHandler extends MouseAdapter { + @Override + public void mouseEntered(final MouseEvent the_event) { + super.mouseEntered(the_event); + + my_rolledover = true; + repaint(); + } + + @Override + public void mouseExited(final MouseEvent the_event) { + super.mouseExited(the_event); + + my_rolledover = false; + repaint(); + } + + @Override + public void mouseClicked(final MouseEvent the_event) { + super.mouseClicked(the_event); + + final ActionEvent e = new ActionEvent(WrittenButton.this, + ActionEvent.ACTION_PERFORMED, + getText()); + + WrittenButton.this.fireActionPerformed(e); + } + } +} diff --git a/src/tetris/gui/animations/Animation.java b/src/tetris/gui/animations/Animation.java new file mode 100644 index 0000000..899459d --- /dev/null +++ b/src/tetris/gui/animations/Animation.java @@ -0,0 +1,193 @@ +/* + * Jesse Morgan + * + * TCSS 305 - Autumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.gui.animations; + +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.image.BufferedImage; + +import tetris.model.IntPoint; + + +/** + * Abstract class that processes animations. + * + * @author Jesse Morgan + * @version 1.0 11 Dec 2009 + */ +public abstract class Animation { + // Public Constants + /** + * How quickly animation should be updated. + */ + public static final int FRAME_RATE = 60; + + // Private Constants + /** + * How much we show in each step. + */ + private static final int R = 5; + + /** + * The current step in the animation. + */ + private int my_step; + + /** + * The current position in the animation. + */ + private IntPoint my_current_pos; + + /** + * Current Step Point. + */ + private IntPoint my_step_point; + + /** + * Animation Running Flag. + */ + private boolean my_animation_running; + + /** + * What the animation presently looks like. + */ + private final BufferedImage my_image; + + /** + * What the animation will look like. + */ + private final Image my_end_image; + + /** + * Create a new animation. + * + * @param the_end_image End result. + * @param the_width Animation width. + * @param the_height Animation height. + */ + public Animation(final Image the_end_image, final int the_width, final int the_height) { + my_step = 0; + my_animation_running = false; + + my_current_pos = new IntPoint(0, 0); + my_step_point = new IntPoint(0, 0); + + my_end_image = the_end_image; + my_image = new BufferedImage(the_width, the_height, + BufferedImage.TYPE_INT_ARGB); + + } + + /** + * Start the animation. + */ + public void start() { + my_step = 0; + + my_current_pos = getStep(0); + my_step_point = getStep(0); + + my_animation_running = true; + } + + /** + * Stop the animation. + */ + public void stop() { + my_animation_running = false; + } + + /** + * @return true if the animation is running. + */ + public boolean isRunning() { + return my_animation_running; + } + + /** + * Draw the next increment of the animation. + */ + public void stepAnimation() { + // If we're near our destination, move to the next step. + if ( + Math.abs(my_current_pos.getX() - my_step_point.getX()) < R && + Math.abs(my_current_pos.getY() - my_step_point.getY()) < R + ) { + // If we're out of steps, stop. + if (my_step >= stepCount() - 1) { + my_animation_running = false; + return; + } + + my_step++; + my_step_point = getStep(my_step); + } + + // Draw! + final Graphics2D g2d = my_image.createGraphics(); + + g2d.drawImage(my_end_image, + my_current_pos.getX(), my_current_pos.getY(), + my_current_pos.getX() + R, my_current_pos.getY() + R, + + my_current_pos.getX() % my_end_image.getWidth(null), + my_current_pos.getY() % my_end_image.getHeight(null), + (my_current_pos.getX() + R) % my_end_image.getWidth(null), + (my_current_pos.getY() + R) % my_end_image.getHeight(null), + null); + + // Some math to draw in the correct "direction" + final int x = my_step_point.getX() - my_current_pos.getX(); + final int y = my_step_point.getY() - my_current_pos.getY(); + double theta = Math.asin(y / Math.sqrt(x * x + y * y)); + + // Correction if we're moving toward the Y axis. + if (x < 0) { + theta = -theta + Math.PI; + } + + // Calculate the next point + my_current_pos = new IntPoint((int) (my_current_pos.getX() + R * Math.cos(theta)), + (int) (my_current_pos.getY() + R * Math.sin(theta))); + + } + + /** + * @return the image of the current animation. + */ + public Image getImage() { + return my_image; + } + + /** + * @return the current X in the animation. + */ + public int getX() { + return my_current_pos.getX(); + } + + /** + * @return the current Y in the animation. + */ + public int getY() { + return my_current_pos.getY(); + } + + /** + * Returns the point at the given step. + * @param the_step The Step. + * @return the Point. + */ + protected abstract IntPoint getStep(final int the_step); + + /** + * @return the number of steps in the animation. + */ + protected abstract int stepCount(); +} diff --git a/src/tetris/gui/animations/TetrisPieceAnimation.java b/src/tetris/gui/animations/TetrisPieceAnimation.java new file mode 100644 index 0000000..b503c88 --- /dev/null +++ b/src/tetris/gui/animations/TetrisPieceAnimation.java @@ -0,0 +1,86 @@ +/* + * Jesse Morgan + * + * TCSS 305 - Autumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.gui.animations; + +import tetris.gui.images.ImageRes; +import tetris.model.IntPoint; +import tetris.piece.TetrisPiece; + +/** + * Animation that knows how to animate a tetris piece. + * + * @author Jesse Morgan + * @version 1.0 11 December 2009 + */ +public class TetrisPieceAnimation extends Animation { + /** + * Size of a brick. + */ + private static final int BRICK_SIZE = 30; + + + /** + * Size of the animation area. + */ + private static final int ANIMATION_SIZE = BRICK_SIZE * 4; + + /** + * The steps of a brick animation. + */ + private static final IntPoint[] STEPS = { + new IntPoint(0, 0), + new IntPoint(26, 0), + new IntPoint(26, 26), + new IntPoint(0, 26), + new IntPoint(0, 0), + + new IntPoint(8, 0), + new IntPoint(0, 8), + new IntPoint(3, 8), + new IntPoint(16, 2), + new IntPoint(4, 20), + new IntPoint(20, 4), + new IntPoint(12, 20), + new IntPoint(24, 14), + new IntPoint(24, 20), + new IntPoint(20, 26), + }; + + /** + * A list of tetris piece points. + */ + private final IntPoint[] my_bricks; + + /** + * Animate the drawing of a tetris piece. + * + * @param the_piece the piece to draw. + */ + public TetrisPieceAnimation(final TetrisPiece the_piece) { + super(ImageRes.loadImage(ImageRes.TETRIS_BLOCK), ANIMATION_SIZE, ANIMATION_SIZE); + + my_bricks = the_piece.translate(-the_piece.getX(), + -the_piece.getY()).getBoardCoordinates(); + } + + @Override + protected IntPoint getStep(final int the_step) { + final IntPoint ani_step = STEPS[the_step % STEPS.length]; + final int brick = the_step / STEPS.length; + + return new IntPoint(ani_step.getX() + my_bricks[brick].getX() * BRICK_SIZE, + ani_step.getY() + my_bricks[brick].getY() * BRICK_SIZE); + } + + @Override + protected int stepCount() { + return my_bricks.length * STEPS.length; + } + +} diff --git a/src/tetris/gui/events/TetrisKeyListener.java b/src/tetris/gui/events/TetrisKeyListener.java new file mode 100644 index 0000000..10b999e --- /dev/null +++ b/src/tetris/gui/events/TetrisKeyListener.java @@ -0,0 +1,69 @@ +/* + * Jesse Morgan + * + * TCSS 305 � Autumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.gui.events; + +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; + +import tetris.board.TetrisBoard; + +/** + * Keyboard Input handler for Tetris board. + * + * @author Jesse Morgan + * @version 1.0 30 Nov 2009 + */ +public class TetrisKeyListener extends KeyAdapter { + /** + * The tetris board we update. + */ + + private final TetrisBoard my_board; + + /** + * Constructor. + * + * @param the_board The board to update. + */ + public TetrisKeyListener(final TetrisBoard the_board) { + super(); + + my_board = the_board; + } + + @Override + public void keyPressed(final KeyEvent the_event) { + super.keyPressed(the_event); + + switch (the_event.getKeyCode()) { + case KeyEvent.VK_LEFT: + my_board.moveLeft(); + break; + + case KeyEvent.VK_RIGHT: + my_board.moveRight(); + break; + + case KeyEvent.VK_UP: + my_board.rotateRight(); + break; + + case KeyEvent.VK_DOWN: + my_board.moveDown(); + break; + + case KeyEvent.VK_SPACE: + my_board.dropPiece(); + break; + + default: + } + } + +} diff --git a/src/tetris/gui/handwriting.ttf b/src/tetris/gui/handwriting.ttf new file mode 100755 index 0000000..a70b0fc Binary files /dev/null and b/src/tetris/gui/handwriting.ttf differ diff --git a/src/tetris/gui/images/ImageRes.java b/src/tetris/gui/images/ImageRes.java new file mode 100644 index 0000000..ba44104 --- /dev/null +++ b/src/tetris/gui/images/ImageRes.java @@ -0,0 +1,73 @@ +/* + * Jesse Morgan + * + * TCSS 305 - Autumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.gui.images; + +import java.awt.Image; +import java.io.IOException; + +import javax.imageio.ImageIO; + +/** + * Enumeration of the various image resources. + * + * @author Jesse Morgan + * @version 1.0 23 November 2009 + */ +public final class ImageRes { + /** + * Game board background image. + */ + public static final String GAME_BACKGROUND = "background.png"; + + /** + * Game over board background image. + */ + public static final String GAMEOVER_BACKGROUND = "gameover_background.png"; + + /** + * Tetris block. + */ + public static final String TETRIS_BLOCK = "square.png"; + + /** + * Game Over wording. + */ + public static final String GAME_OVER_LABEL = "gameover.png"; + + /** + * Game Over wording. + */ + public static final String PAUSED_LABEL = "paused.png"; + + /** + * Writing Pencil. + */ + public static final String WRITING_PENCIL = "writing_pencil.png"; + + /** + * You're not allowed to create an instance of this class. + */ + private ImageRes() { } + + /** + * Utility to handle loading an image. + * + * @param the_file The filename we need to load. + * @return The loaded Image or null if the image can't be loaded. + */ + public static Image loadImage(final String the_file) { + try { + return ImageIO.read(ImageRes.class. + getResourceAsStream(the_file)); + + } catch (final IOException the_exception) { + return null; + } + } +} diff --git a/src/tetris/gui/images/background.png b/src/tetris/gui/images/background.png new file mode 100644 index 0000000..39b58bf Binary files /dev/null and b/src/tetris/gui/images/background.png differ diff --git a/src/tetris/gui/images/gameover.png b/src/tetris/gui/images/gameover.png new file mode 100644 index 0000000..f8c6c48 Binary files /dev/null and b/src/tetris/gui/images/gameover.png differ diff --git a/src/tetris/gui/images/gameover_background.png b/src/tetris/gui/images/gameover_background.png new file mode 100644 index 0000000..25b7cd5 Binary files /dev/null and b/src/tetris/gui/images/gameover_background.png differ diff --git a/src/tetris/gui/images/paused.png b/src/tetris/gui/images/paused.png new file mode 100644 index 0000000..3d26abe Binary files /dev/null and b/src/tetris/gui/images/paused.png differ diff --git a/src/tetris/gui/images/square.png b/src/tetris/gui/images/square.png new file mode 100644 index 0000000..75fc613 Binary files /dev/null and b/src/tetris/gui/images/square.png differ diff --git a/src/tetris/gui/images/writing_pencil.png b/src/tetris/gui/images/writing_pencil.png new file mode 100644 index 0000000..f4250de Binary files /dev/null and b/src/tetris/gui/images/writing_pencil.png differ diff --git a/src/tetris/model/IntPoint.java b/src/tetris/model/IntPoint.java new file mode 100644 index 0000000..73bf07c --- /dev/null +++ b/src/tetris/model/IntPoint.java @@ -0,0 +1,83 @@ +/* + * Jesse Morgan + * + * TCSS 305 РAutumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.model; + +/** + * Class to represent an int point in two-space. + * + * @author Jesse Morgan + * @version 1.0 17 November 2009 + */ +public class IntPoint { + /** + * The x-coordinate. + */ + private final int my_x; + + /** + * The y-coordinate. + */ + private final int my_y; + + /** + * Create a new point. + * + * @param the_x The x-coordinate. + * @param the_y The y-coordinate. + */ + public IntPoint(final int the_x, final int the_y) { + my_x = the_x; + my_y = the_y; + } + + /** + * @return The x-coordinate. + */ + public int getX() { + return my_x; + } + + /** + * @return The y-coordinate. + */ + public int getY() { + return my_y; + } + + /** + * Compare IntPoint to another object. + * @param the_other The object to compare to. + * @return True if x and y are the same. False otherwise. + */ + public boolean equals(final Object the_other) { + boolean result = false; + + if (the_other != null && the_other.getClass() == getClass()) { + final IntPoint other_point = (IntPoint) the_other; + + if (getX() == other_point.getX() && getY() == other_point.getY()) { + result = true; + } + } + + return result; + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "(" + getX() + "," + getY() + ")"; + } + + +} diff --git a/src/tetris/piece/IPiece.java b/src/tetris/piece/IPiece.java new file mode 100644 index 0000000..592887f --- /dev/null +++ b/src/tetris/piece/IPiece.java @@ -0,0 +1,43 @@ +/* + * Jesse Morgan + * + * TCSS 305 РAutumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.piece; + +/** + * Class to represent a Tetris I piece. + * + * @author Jesse Morgan + * @version 1.0 17 November 2009 + */ +public class IPiece extends TetrisPiece { + // Private Constants + /** + * X Rotation Point. + */ + private static final double ROTATION_X = 1.5; + + /** + * Y Rotation Point. + */ + private static final double ROTATION_Y = 2; + + /** + * Array of of points for the I Piece (since it contains a 3). + */ + private static final double[] POINTS = new double[] {0, 2, 1, 2, 2, 2, 3, 2}; + + /** + * Setup the I Piece. + * + * @param the_x Tetris piece X-coord. + * @param the_y Tetris piece Y-coord. + */ + public IPiece(final int the_x, final int the_y) { + super(the_x, the_y, POINTS, ROTATION_X, ROTATION_Y); + } +} diff --git a/src/tetris/piece/JPiece.java b/src/tetris/piece/JPiece.java new file mode 100644 index 0000000..711a492 --- /dev/null +++ b/src/tetris/piece/JPiece.java @@ -0,0 +1,38 @@ +/* + * Jesse Morgan + * + * TCSS 305 РAutumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.piece; + +/** + * Class to represent a Tetris J piece. + * + * @author Jesse Morgan + * @version 1.0 17 November 2009 + */ +public class JPiece extends TetrisPiece { + // Private Constants + /** + * X Rotation Point. + */ + private static final double ROTATION_X = 1; + + /** + * Y Rotation Point. + */ + private static final double ROTATION_Y = 1; + + /** + * Setup the J Piece. + * + * @param the_x Tetris piece X-coord. + * @param the_y Tetris piece Y-coord. + */ + public JPiece(final int the_x, final int the_y) { + super(the_x, the_y, new double[] {1, 0, 1, 1, 1, 2, 0, 2}, ROTATION_X, ROTATION_Y); + } +} \ No newline at end of file diff --git a/src/tetris/piece/LPiece.java b/src/tetris/piece/LPiece.java new file mode 100644 index 0000000..3c1861e --- /dev/null +++ b/src/tetris/piece/LPiece.java @@ -0,0 +1,39 @@ +/* + * Jesse Morgan + * + * TCSS 305 РAutumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.piece; + +/** + * Class to represent a Tetris L piece. + * + * @author Jesse Morgan + * @version 1.0 17 November 2009 + */ +public class LPiece extends TetrisPiece { + // Private Constants + /** + * X Rotation Point. + */ + private static final double ROTATION_X = 1; + + /** + * Y Rotation Point. + */ + private static final double ROTATION_Y = 1; + + + /** + * Setup the L Piece. + * + * @param the_x Tetris piece X-coord. + * @param the_y Tetris piece Y-coord. + */ + public LPiece(final int the_x, final int the_y) { + super(the_x, the_y, new double[] {1, 0, 1, 1, 1, 2, 2, 2}, ROTATION_X, ROTATION_Y); + } +} diff --git a/src/tetris/piece/OPiece.java b/src/tetris/piece/OPiece.java new file mode 100644 index 0000000..1934110 --- /dev/null +++ b/src/tetris/piece/OPiece.java @@ -0,0 +1,39 @@ +/* + * Jesse Morgan + * + * TCSS 305 РAutumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.piece; + +/** + * Class to represent a Tetris O piece. + * + * @author Jesse Morgan + * @version 1.0 17 November 2009 + */ +public class OPiece extends TetrisPiece { + // Private Constants + /** + * X Rotation Point. + */ + private static final double ROTATION_X = 1.5; + + /** + * Y Rotation Point. + */ + private static final double ROTATION_Y = 1.5; + + + /** + * Setup the O Piece. + * + * @param the_x Tetris piece X-coord. + * @param the_y Tetris piece Y-coord. + */ + public OPiece(final int the_x, final int the_y) { + super(the_x, the_y, new double[] {1, 1, 1, 2, 2, 1, 2, 2}, ROTATION_X, ROTATION_Y); + } +} \ No newline at end of file diff --git a/src/tetris/piece/SPiece.java b/src/tetris/piece/SPiece.java new file mode 100644 index 0000000..92e5901 --- /dev/null +++ b/src/tetris/piece/SPiece.java @@ -0,0 +1,39 @@ +/* + * Jesse Morgan + * + * TCSS 305 РAutumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.piece; + +/** + * Class to represent a Tetris S piece. + * + * @author Jesse Morgan + * @version 1.0 17 November 2009 + */ +public class SPiece extends TetrisPiece { + // Private Constants + /** + * X Rotation Point. + */ + private static final double ROTATION_X = 1; + + /** + * Y Rotation Point. + */ + private static final double ROTATION_Y = 1.5; + + + /** + * Setup the S Piece. + * + * @param the_x Tetris piece X-coord. + * @param the_y Tetris piece Y-coord. + */ + public SPiece(final int the_x, final int the_y) { + super(the_x, the_y, new double[] {1, 1, 2, 1, 0, 2, 1, 2}, ROTATION_X, ROTATION_Y); + } +} \ No newline at end of file diff --git a/src/tetris/piece/TPiece.java b/src/tetris/piece/TPiece.java new file mode 100644 index 0000000..a3e5c30 --- /dev/null +++ b/src/tetris/piece/TPiece.java @@ -0,0 +1,38 @@ +/* + * Jesse Morgan + * + * TCSS 305 РAutumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.piece; + +/** + * Class to represent a Tetris T piece. + * + * @author Jesse Morgan + * @version 1.0 17 November 2009 + */ +public class TPiece extends TetrisPiece { + // Private Constants + /** + * X Rotation Point. + */ + private static final double ROTATION_X = 1; + + /** + * Y Rotation Point. + */ + private static final double ROTATION_Y = 1.5; + + /** + * Setup the T Piece. + * + * @param the_x Tetris piece X-coord. + * @param the_y Tetris piece Y-coord. + */ + public TPiece(final int the_x, final int the_y) { + super(the_x, the_y, new double[] {1, 1, 0, 2, 1, 2, 2, 2}, ROTATION_X, ROTATION_Y); + } +} \ No newline at end of file diff --git a/src/tetris/piece/TetrisPiece.java b/src/tetris/piece/TetrisPiece.java new file mode 100644 index 0000000..e518fc3 --- /dev/null +++ b/src/tetris/piece/TetrisPiece.java @@ -0,0 +1,171 @@ +/* + * Jesse Morgan + * + * TCSS 305 - Autumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.piece; + +import java.awt.geom.AffineTransform; + +import tetris.model.IntPoint; + +/** + * Base Class to Represent a tetris piece. + * + * @author Jesse Morgan + * @version 1.0 17 November 2009 + */ +public class TetrisPiece { + // Public constants + /** + * Width and Height of a Tetris piece. + */ + public static final int TETRIS_PIECE_SIZE = 4; + + // Private Constants + /** + * Number of characters in each toString line. + */ + private static final int CHARS_PER_TOSTRING_LINE = 5; + + // Private Instance Fields + /** + * Storage for the piece points. + */ + private final double[] my_points; + + /** + * Our X-coordinate. + */ + private final int my_x; + + /** + * Our Y-coordinate. + */ + private final int my_y; + + /** + * Our X-coordinate for the point of rotation. + */ + private final double my_rotatex; + + /** + * Our Y-coordinate for the point of rotation. + */ + private final double my_rotatey; + + /** + * Setup the AbstractTetrisPiece. + * + * @param the_x Tetris piece X-coord. + * @param the_y Tetris piece Y-coord. + * @param the_points The points that make up this piece. + * @param the_rotatex X-Coordinate to rotate the piece about. + * @param the_rotatey Y-Coordinate to rotate the piece about. + */ + protected TetrisPiece(final int the_x, final int the_y, final double[] the_points, + final double the_rotatex, final double the_rotatey) { + my_x = the_x; + my_y = the_y; + + my_points = the_points.clone(); + my_rotatex = the_rotatex; + my_rotatey = the_rotatey; + } + + /** + * Rotates the tetris piece counter-clockwise 90 degrees. + * + * @return the rotated piece. + */ + public TetrisPiece rotateLeft() { + return rotate(-Math.PI / 2); + } + + /** + * Rotates the tetris piece clockwise 90 degrees. + * + * @return the rotated piece. + */ + public TetrisPiece rotateRight() { + return rotate(Math.PI / 2); + } + + /** + * Move the tetris piece. + * + * @param the_dx Movement change along the X-axis. + * @param the_dy Movement change along the Y-axis. + * @return the moved piece. + */ + public TetrisPiece translate(final int the_dx, final int the_dy) { + return new TetrisPiece(my_x + the_dx, my_y + the_dy, my_points, my_rotatex, my_rotatey); + } + + /** + * @return The X position. + */ + public int getX() { + return my_x; + } + + /** + * @return The Y position. + */ + public int getY() { + return my_y; + } + + /** + * @return An int[] containing the piece's points in board-space. + * Sorted x1, y1, x2, y2, ..., xN, yN (N = number of points). + */ + public IntPoint[] getBoardCoordinates() { + final IntPoint[] result = new IntPoint[my_points.length / 2]; + + for (int i = 0; i < my_points.length - 1; i = i + 2) { + final int x = (int) my_points[i]; + final int y = (int) my_points[i + 1]; + + // i is always even. + result[i / 2] = new IntPoint(x + my_x, y + my_y); + } + + return result; + } + + /** + * {@inheritDoc} + */ + public String toString() { + final char[] output = "....\n....\n....\n....".toCharArray(); + + for (int i = 0; i < my_points.length - 1; i = i + 2) { + // For each point, mark the coresponding element true + final int x = (int) my_points[i]; + final int y = (int) my_points[i + 1]; + output[x + CHARS_PER_TOSTRING_LINE * y] = 'X'; + } + + return new String(output); + } + + /** + * Rotates the tetris piece by the_degrees. + * + * @param the_degrees The degrees in radians to rotate the piece. + * @return the rotated piece. + */ + private TetrisPiece rotate(final double the_degrees) { + final AffineTransform trans = AffineTransform.getRotateInstance(the_degrees, + my_rotatex, my_rotatey); + final double[] new_points = new double[my_points.length]; + + trans.transform(my_points, 0, new_points, 0, my_points.length / 2); + + return new TetrisPiece(my_x, my_y, new_points, my_rotatex, my_rotatey); + } +} diff --git a/src/tetris/piece/TetrisPieces.java b/src/tetris/piece/TetrisPieces.java new file mode 100644 index 0000000..642be97 --- /dev/null +++ b/src/tetris/piece/TetrisPieces.java @@ -0,0 +1,103 @@ +/* + * Jesse Morgan + * + * TCSS 305 РAutumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.piece; + +import java.util.Random; + +/** + * Enumeration of the various tetris pieces. + * + * @author Jesse Morgan + * @version 1.0 23 November 2009 + */ +public enum TetrisPieces { + // Enumeration definition. + /** + * The I Piece. + */ + I_PIECE, + + /** + * The J Piece. + */ + J_PIECE, + + /** + * The L Piece. + */ + L_PIECE, + + /** + * The O Piece. + */ + O_PIECE, + + /** + * The S Piece. + */ + S_PIECE, + + /** + * The T Piece. + */ + T_PIECE, + + /** + * The Z Piece. + */ + Z_PIECE; + + // Private Constants + /** + * A Random that we use for generating random pieces. + */ + private static final Random RANDOM = new Random(); + + /** + * @param the_x X coordinate for this piece. + * @param the_y Y coordinate for this piece. + * @return a random tetris piece. + */ + public static TetrisPiece getRandomPiece(final int the_x, final int the_y) { + TetrisPiece piece; + + switch (values()[RANDOM.nextInt(values().length)]) { + case I_PIECE: + piece = new IPiece(the_x, the_y); + break; + + case J_PIECE: + piece = new JPiece(the_x, the_y); + break; + + case L_PIECE: + piece = new LPiece(the_x, the_y); + break; + + case O_PIECE: + piece = new OPiece(the_x, the_y); + break; + + case S_PIECE: + piece = new SPiece(the_x, the_y); + break; + + case T_PIECE: + piece = new TPiece(the_x, the_y); + break; + + case Z_PIECE: + default: // This is a fail-safe should something very bizarre happen. + piece = new ZPiece(the_x, the_y); + break; + } + + return piece; + } +} diff --git a/src/tetris/piece/ZPiece.java b/src/tetris/piece/ZPiece.java new file mode 100644 index 0000000..152d9c2 --- /dev/null +++ b/src/tetris/piece/ZPiece.java @@ -0,0 +1,38 @@ +/* + * Jesse Morgan + * + * TCSS 305 РAutumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.piece; + +/** + * Class to represent a Tetris Z piece. + * + * @author Jesse Morgan + * @version 1.0 17 November 2009 + */ +public class ZPiece extends TetrisPiece { + // Private Constants + /** + * X Rotation Point. + */ + private static final double ROTATION_X = 1; + + /** + * Y Rotation Point. + */ + private static final double ROTATION_Y = 1.5; + + /** + * Setup the Z Piece. + * + * @param the_x Tetris piece X-coord. + * @param the_y Tetris piece Y-coord. + */ + public ZPiece(final int the_x, final int the_y) { + super(the_x, the_y, new double[] {0, 1, 1, 1, 1, 2, 2, 2}, ROTATION_X, ROTATION_Y); + } +} \ No newline at end of file diff --git a/src/tetris/tests/board/TetrisBoardTest.java b/src/tetris/tests/board/TetrisBoardTest.java new file mode 100644 index 0000000..d14f13b --- /dev/null +++ b/src/tetris/tests/board/TetrisBoardTest.java @@ -0,0 +1,219 @@ +/* + * Jesse Morgan + * + * TCSS 305 � Autumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.tests.board; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.BeforeClass; +import org.junit.Test; + +import tetris.board.TetrisBoard; +import tetris.model.IntPoint; +import tetris.piece.IPiece; +import tetris.piece.JPiece; +import tetris.piece.LPiece; +import tetris.piece.OPiece; +import tetris.piece.SPiece; +import tetris.piece.TPiece; +import tetris.piece.TetrisPiece; +import tetris.piece.ZPiece; + +/** + * Test class for the tetris board. + * @author Jesse Morgan + * @version 1.0 30 November 2009. + */ +public class TetrisBoardTest { + /** + * Width and Height of a Tetris piece. + */ + private static final int TETRIS_PIECE_SIZE = 4; + + /** + * + */ + private static final String TEST1_FILENAME = + "tetris.tests.board.TetrisBoardTest.expected-output.txt"; + + /** + * The test pieces. + */ + private static final TetrisPiece[] TEST1 = { + new TPiece(0, 0), + new OPiece(2, 0), + new IPiece(5, 0).rotateLeft().translate(-1, 0), + new OPiece(5, 0), + + new IPiece(0, 0).rotateLeft().translate(-1, 0), + new ZPiece(2, 0).rotateRight(), + + new OPiece(7, 0), + + // Bottom two lines have disappeared now. + + new OPiece(0, 0), + new JPiece(1, 0).rotateLeft(), + new IPiece(6, 0), + new LPiece(4, 0).rotateRight(), + new SPiece(7, 0), + new TPiece(6, 0).rotateLeft().rotateLeft(), + + // Third (5th) row has now disappeared. + + new OPiece(3, 0), + new SPiece(6, 0), + new LPiece(1, 0), + new TPiece(4, 0), + new LPiece(4, 0).rotateLeft().rotateLeft(), + new ZPiece(2, 0).rotateLeft(), + new JPiece(0, 0).rotateRight(), + + new IPiece(6, 0).rotateLeft().translate(-1, 0), + new LPiece(7, 0).rotateLeft().rotateLeft(), + new JPiece(7, 0).rotateRight(), + + new OPiece(-1, 0), // 1 square buffer on the either side + new JPiece(1, 0).rotateLeft(), + new IPiece(3, 0), + new IPiece(7, 0).rotateLeft().translate(-1, 0), + new TPiece(7, 0).rotateLeft().rotateLeft(), + new SPiece(5, 0), + new ZPiece(5, 0).rotateLeft(), + + new TPiece(4, 0), + new LPiece(4, 0).rotateLeft(), + new LPiece(4, 0) + }; + + /** + * The expected output of test 1. + */ + private static String my_test1_output; + + /** + * This method loads the expected output into a file. + */ + @BeforeClass + public static void loadExpectedOutput() { + try { + final BufferedReader reader = + new BufferedReader(new FileReader(TEST1_FILENAME)); + + final StringBuilder sb = new StringBuilder(); + String line; + + try { + line = reader.readLine(); + while (line != null) { + sb.append(line); + sb.append("\n"); + + line = reader.readLine(); + } + + my_test1_output = sb.toString(); + + reader.close(); + + } catch (final IOException the_exception) { + fail("Could not load file: " + the_exception.getMessage()); + } + + } catch (final FileNotFoundException the_exception) { + fail("Could not find file: " + the_exception.getMessage()); + } + + } + + + /** + * JUnit test to check that the board functions as expected. + */ + @Test + public void testTheBoard() { + final TetrisBoard tb = new TetrisBoard(TetrisBoard.STANDARD_BOARD_WIDTH, + TetrisBoard.STANDARD_BOARD_HEIGHT, + Arrays.asList(TEST1)); + + tb.newGame(); + + // Loop through game play + final int moves_per_piece = TetrisBoard.STANDARD_BOARD_HEIGHT * TETRIS_PIECE_SIZE; + + for (int i = 0; i < TEST1.length * moves_per_piece && !tb.isGameOver(); i++) { + tb.progressBoard(); + } + + + assertEquals("Tetris board output not as expected.", my_test1_output, tb.toString()); + } + + /** + * JUnit test for the moving functions. + */ + @Test + public void testMoves() { + final List pieces = new ArrayList(); + + pieces.add(new LPiece(-1, 0)); + pieces.add(new LPiece(-1, 0)); + + final TetrisBoard tb = new TetrisBoard(TetrisBoard.STANDARD_BOARD_WIDTH, + TetrisBoard.STANDARD_BOARD_HEIGHT, + pieces); + tb.newGame(); + + final IntPoint[] points = + {new IntPoint(0, 0), new IntPoint(0, 1), new IntPoint(0, 2), new IntPoint(1, 2)}; + + // Test inital + checkPoints(new IntPoint(0, 0), points, tb.getTetrisBlocks()); + + // Test Right + tb.moveRight(); + checkPoints(new IntPoint(1, 0), points, tb.getTetrisBlocks()); + + // Test Left + tb.moveLeft(); + checkPoints(new IntPoint(1 - 1, 0), points, tb.getTetrisBlocks()); + + // Test Down + tb.moveDown(); + checkPoints(new IntPoint(0, 1), points, tb.getTetrisBlocks()); + } + + /** + * Checks that an array of expected points match a List actual points. + * @param the_modifier A point to modify the other points by. + * @param the_expected The expected points. + * @param the_actual The actual points. + */ + private void checkPoints(final IntPoint the_modifier, + final IntPoint[] the_expected, final List the_actual) { + + assertEquals("Extraneous points on the board.", the_expected.length, the_actual.size()); + + for (int i = 0; i < the_expected.length; i++) { + final IntPoint modded = new IntPoint(the_expected[i].getX() + the_modifier.getX(), + the_expected[i].getY() + the_modifier.getY()); + + assertTrue("Missing a point.", the_actual.contains(modded)); + } + } +} diff --git a/src/tetris/tests/piece/IPieceTest.java b/src/tetris/tests/piece/IPieceTest.java new file mode 100644 index 0000000..031b1e8 --- /dev/null +++ b/src/tetris/tests/piece/IPieceTest.java @@ -0,0 +1,39 @@ +/* + * Jesse Morgan + * + * TCSS 305 РAutumn 2009 Tetris Project 17 November 2009 + */ + +package tetris.tests.piece; + +import tetris.piece.IPiece; +import tetris.piece.TetrisPiece; + +/** + * JUnit Test for the I Piece. + * + * @author Jesse Morgan + * @version 1.0 17 November 2009 + */ +public class IPieceTest extends TetrisPieceTest { + /** + * Array of the string representations of various rotations. + */ + private static final String[] ROTATION_STRINGS = + new String[] {"....\n....\nXXXX\n....", ".X..\n.X..\n.X..\n.X.."}; + + @Override + protected int[] getOriginalPoints() { + return new int[] {0, 2, 1, 2, 2, 2, 3, 2}; // Copied from the class. + } + + @Override + protected TetrisPiece getPiece(final int the_x, final int the_y) { + return new IPiece(the_x, the_y); + } + + @Override + protected String[] getRotationStrings() { + return ROTATION_STRINGS.clone(); + } +} diff --git a/src/tetris/tests/piece/JPieceTest.java b/src/tetris/tests/piece/JPieceTest.java new file mode 100644 index 0000000..1b04781 --- /dev/null +++ b/src/tetris/tests/piece/JPieceTest.java @@ -0,0 +1,42 @@ +/* + * Jesse Morgan + * + * TCSS 305 РAutumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.tests.piece; + +import tetris.piece.JPiece; +import tetris.piece.TetrisPiece; + +/** + * JUnit Test for the J Piece. + * + * @author Jesse Morgan + * @version 1.0 17 November 2009 + */ +public class JPieceTest extends TetrisPieceTest { + /** + * Array of the string representations of various rotations. + */ + private static final String[] ROTATION_STRINGS = + new String[] {".X..\n.X..\nXX..\n....", "....\nXXX.\n..X.\n....", + ".XX.\n.X..\n.X..\n....", "X...\nXXX.\n....\n...."}; + + @Override + protected int[] getOriginalPoints() { + return new int[] {1, 0, 1, 1, 1, 2, 0, 2}; // Copied from the class. + } + + @Override + protected TetrisPiece getPiece(final int the_x, final int the_y) { + return new JPiece(the_x, the_y); + } + + @Override + protected String[] getRotationStrings() { + return ROTATION_STRINGS.clone(); + } +} diff --git a/src/tetris/tests/piece/LPieceTest.java b/src/tetris/tests/piece/LPieceTest.java new file mode 100644 index 0000000..f5e045d --- /dev/null +++ b/src/tetris/tests/piece/LPieceTest.java @@ -0,0 +1,43 @@ +/* + * Jesse Morgan + * + * TCSS 305 РAutumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.tests.piece; + +import tetris.piece.LPiece; +import tetris.piece.TetrisPiece; + +/** + * JUnit Test for the L Piece. + * + * @author Jesse Morgan + * @version 1.0 17 November 2009 + */ +public class LPieceTest extends TetrisPieceTest { + /** + * Array of the string representations of various rotations. + */ + private static final String[] ROTATION_STRINGS = + new String[] {".X..\n.X..\n.XX.\n....", "..X.\nXXX.\n....\n....", + "XX..\n.X..\n.X..\n....", "....\nXXX.\nX...\n...."}; + + @Override + protected int[] getOriginalPoints() { + return new int[] {1, 0, 1, 1, 1, 2, 2, 2}; // Copied from the class. + } + + @Override + protected TetrisPiece getPiece(final int the_x, final int the_y) { + return new LPiece(the_x, the_y); + } + + @Override + protected String[] getRotationStrings() { + return ROTATION_STRINGS.clone(); + } + +} diff --git a/src/tetris/tests/piece/OPieceTest.java b/src/tetris/tests/piece/OPieceTest.java new file mode 100644 index 0000000..67447c2 --- /dev/null +++ b/src/tetris/tests/piece/OPieceTest.java @@ -0,0 +1,41 @@ +/* + * Jesse Morgan + * + * TCSS 305 РAutumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.tests.piece; + +import tetris.piece.OPiece; +import tetris.piece.TetrisPiece; + +/** + * JUnit Test for the O Piece. + * + * @author Jesse Morgan + * @version 1.0 17 November 2009 + */ +public class OPieceTest extends TetrisPieceTest { + /** + * Array of the string representations of various rotations. + */ + private static final String[] ROTATION_STRINGS = new String[] {"....\n.XX.\n.XX.\n...."}; + + @Override + protected int[] getOriginalPoints() { + return new int[] {1, 1, 1, 2, 2, 1, 2, 2}; // Copied from the class. + } + + @Override + protected TetrisPiece getPiece(final int the_x, final int the_y) { + return new OPiece(the_x, the_y); + } + + @Override + protected String[] getRotationStrings() { + return ROTATION_STRINGS.clone(); + } + +} diff --git a/src/tetris/tests/piece/SPieceTest.java b/src/tetris/tests/piece/SPieceTest.java new file mode 100644 index 0000000..3125f61 --- /dev/null +++ b/src/tetris/tests/piece/SPieceTest.java @@ -0,0 +1,42 @@ +/* + * Jesse Morgan + * + * TCSS 305 РAutumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.tests.piece; + +import tetris.piece.SPiece; +import tetris.piece.TetrisPiece; + +/** + * JUnit Test for the S Piece. + * + * @author Jesse Morgan + * @version 1.0 17 November 2009 + */ +public class SPieceTest extends TetrisPieceTest { + /** + * Array of the string representations of various rotations. + */ + private static final String[] ROTATION_STRINGS = + new String[] {"....\n.XX.\nXX..\n....", "X...\nXX..\n.X..\n...."}; + + @Override + protected int[] getOriginalPoints() { + return new int[] {1, 1, 2, 1, 0, 2, 1, 2}; // Copied from the class. + } + + @Override + protected TetrisPiece getPiece(final int the_x, final int the_y) { + return new SPiece(the_x, the_y); + } + + @Override + protected String[] getRotationStrings() { + return ROTATION_STRINGS.clone(); + } + +} diff --git a/src/tetris/tests/piece/TPieceTest.java b/src/tetris/tests/piece/TPieceTest.java new file mode 100644 index 0000000..ff68a44 --- /dev/null +++ b/src/tetris/tests/piece/TPieceTest.java @@ -0,0 +1,43 @@ +/* + * Jesse Morgan + * + * TCSS 305 РAutumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.tests.piece; + +import tetris.piece.TPiece; +import tetris.piece.TetrisPiece; + +/** + * JUnit Test for the T Piece. + * + * @author Jesse Morgan + * @version 1.0 17 November 2009 + */ +public class TPieceTest extends TetrisPieceTest { + /** + * Array of the string representations of various rotations. + */ + private static final String[] ROTATION_STRINGS = + new String[] {"....\n.X..\nXXX.\n....", ".X..\nXX..\n.X..\n....", + "....\nXXX.\n.X..\n....", "X...\nXX..\nX...\n...."}; + + @Override + protected int[] getOriginalPoints() { + return new int[] {1, 1, 0, 2, 1, 2, 2, 2}; // Copied from the class. + } + + @Override + protected TetrisPiece getPiece(final int the_x, final int the_y) { + return new TPiece(the_x, the_y); + } + + @Override + protected String[] getRotationStrings() { + return ROTATION_STRINGS.clone(); + } + +} diff --git a/src/tetris/tests/piece/TetrisPieceTest.java b/src/tetris/tests/piece/TetrisPieceTest.java new file mode 100644 index 0000000..f085207 --- /dev/null +++ b/src/tetris/tests/piece/TetrisPieceTest.java @@ -0,0 +1,182 @@ +/* + * Jesse Morgan + * + * TCSS 305 РAutumn 2009 Tetris Project 17 November 2009 + */ + +package tetris.tests.piece; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import tetris.model.IntPoint; +import tetris.piece.TetrisPiece; + +/** + * Parent JUnit test class for Tetris Pieces. + * + * @author Jesse Morgan + * @version 1.0 17 November 2009 + */ +public abstract class TetrisPieceTest { + // Private Constants + /** + * Random X position we use to test. + */ + private static final int X_POSITION = 5; + + /** + * Random Y position we use to test. + */ + private static final int Y_POSITION = 3; + + /** + * Number of rotations require to rotate a piece 360 degrees. + */ + private static final int NUMBER_OF_ROTATIONS = 4; + + /** + * Test that a piece is created properly. + */ + @Test + public void testPieceConstruction() { + final TetrisPiece piece = getPiece(5, 3); + + // Test the X and Y coords. + assertEquals("Inital X", X_POSITION, piece.getX()); + assertEquals("Inital Y", Y_POSITION, piece.getY()); + + // Test Board Position + assertTrue("Initial Board Positon Fails", + testBoardPosition(piece, X_POSITION, Y_POSITION)); + } + + /** + * Test that a piece is the same after four rotations. + */ + @Test + public void testPieceAfterRotations() { + final TetrisPiece before = getPiece(5, 3); + TetrisPiece after; + + // Left rotations + after = getPiece(X_POSITION, Y_POSITION); + for (int i = 0; i < NUMBER_OF_ROTATIONS; i++) { + after = after.rotateLeft(); + } + + assertEquals("Pieces differ after 4 left rotations", before.toString(), after.toString()); + + // Right rotations + after = getPiece(X_POSITION, Y_POSITION); + for (int i = 0; i < NUMBER_OF_ROTATIONS; i++) { + after = after.rotateRight(); + } + + assertEquals("Pieces differ after 4 right rotations", before.toString(), after.toString()); + } + + /** + * Test Translations. + */ + @Test + public void testTranslations() { + TetrisPiece piece = getPiece(X_POSITION, Y_POSITION); + + // Negative Translation + piece = piece.translate(-1, -1); + assertTrue("Negative Translations Fail", + testBoardPosition(piece, X_POSITION - 1 , Y_POSITION - 1)); + + // Positive Translation + piece = piece.translate(1, 1); + assertTrue("Positive Translations Fail", + testBoardPosition(piece, X_POSITION - 1 + 1, Y_POSITION - 1 + 1)); + } + + /** + * Test each left rotation String. + */ + @Test + public void testLeftRotations() { + TetrisPiece piece = getPiece(1, 0); + + final String[] rotations = getRotationStrings(); + + for (int i = 0; i <= NUMBER_OF_ROTATIONS; i++) { + assertEquals(i + "th Left Rotation Failed", + rotations[i % rotations.length], piece.toString()); + + piece = piece.rotateLeft(); + } + } + + /** + * Test each right rotation String. + */ + @Test + public void testRightRotations() { + TetrisPiece piece = getPiece(1, 0); + + final String[] rotations = getRotationStrings(); + + for (int i = NUMBER_OF_ROTATIONS; i >= 0; i--) { + assertEquals((i - NUMBER_OF_ROTATIONS) + "th Right Rotation Failed", + rotations[i % rotations.length], piece.toString()); + + piece = piece.rotateRight(); + } + } + + /** + * Tests the board position. + * @param the_piece The tetris piece. + * @param the_x Board X. + * @param the_y Board y. + * @return true if all points are correct, false otherwise. + */ + protected boolean testBoardPosition(final TetrisPiece the_piece, + final int the_x, final int the_y) { + boolean result = true; + + final int[] orig_positions = getOriginalPoints(); + final IntPoint[] cur_positions = the_piece.getBoardCoordinates(); + + // Both sets should contain the same number of points. + assertSame("Original and Current point counts differ.", + orig_positions.length, 2 * cur_positions.length); + + for (int i = 0; i < orig_positions.length - 1; i = i + 2) { + // Check that each X and Y was shifted properly. + if (orig_positions[i] + the_x != cur_positions[i / 2].getX()) { + result = false; + } + + if (orig_positions[i + 1] + the_y != cur_positions[i / 2].getY()) { + result = false; + } + } + + return result; + } + + /** + * @param the_x X-coord. + * @param the_y X-coord. + * @return Return a tetris piece of the type we're testing. + */ + protected abstract TetrisPiece getPiece(final int the_x, final int the_y); + + /** + * @return int[] containing the orignal points for the piece. + */ + protected abstract int[] getOriginalPoints(); + + /** + * @return int[] containing the string representation of various rotation states + */ + protected abstract String[] getRotationStrings(); +} diff --git a/src/tetris/tests/piece/ZPieceTest.java b/src/tetris/tests/piece/ZPieceTest.java new file mode 100644 index 0000000..106dc00 --- /dev/null +++ b/src/tetris/tests/piece/ZPieceTest.java @@ -0,0 +1,42 @@ +/* + * Jesse Morgan + * + * TCSS 305 РAutumn 2009 + * Tetris Project + * 17 November 2009 + */ + +package tetris.tests.piece; + +import tetris.piece.TetrisPiece; +import tetris.piece.ZPiece; + +/** + * JUnit Test for the Z Piece. + * + * @author Jesse Morgan + * @version 1.0 17 November 2009 + */ +public class ZPieceTest extends TetrisPieceTest { + /** + * Array of the string representations of various rotations. + */ + private static final String[] ROTATION_STRINGS = + new String[] {"....\nXX..\n.XX.\n....", ".X..\nXX..\nX...\n...."}; + + @Override + protected int[] getOriginalPoints() { + return new int[] {0, 1, 1, 1, 1, 2, 2, 2}; // Copied from the class. + } + + @Override + protected TetrisPiece getPiece(final int the_x, final int the_y) { + return new ZPiece(the_x, the_y); + } + + @Override + protected String[] getRotationStrings() { + return ROTATION_STRINGS.clone(); + } + +} -- cgit v1.2.3