diff --git a/src/main/java/edu/unl/cse/soft160/tetris/Tetris.java b/src/main/java/edu/unl/cse/soft160/tetris/Tetris.java index eb2841b67bf47a33de6b03b6fe884bf69edf72b5..4190fb10183abb1a95f915cf9662b7e39134dfbe 100644 --- a/src/main/java/edu/unl/cse/soft160/tetris/Tetris.java +++ b/src/main/java/edu/unl/cse/soft160/tetris/Tetris.java @@ -17,338 +17,338 @@ import java.nio.file.Paths; import java.nio.file.StandardOpenOption; public class Tetris extends JFrame { - private static final long serialVersionUID = -6851893161385783635L; - - protected static void log(String message) { - try { - Files.write(Paths.get("logs/tetris.log"), (message + "\n").getBytes(), StandardOpenOption.CREATE, - StandardOpenOption.APPEND); - } catch (IOException exception) { - exception.printStackTrace(); - } - } - - protected void logFailedMove(String moveName) { - log("Attempted move failed: " + moveName + " on shape " + shape + " (\"" + SHAPE_NAMES[shape] + "\")"); - } - - protected static int mod(int value, int modulus) { - int result = value % modulus; - return result < 0 ? result + modulus : result; - } - - protected static final int DEFAULT_SQUARE_SIZE = 60; // pixels - - protected static final Font TEXT_FONT = new Font(Font.SANS_SERIF, Font.BOLD, DEFAULT_SQUARE_SIZE / 2); - protected static final Color TEXT_COLOR = new Color(255, 255, 255, 127); - - protected static final String ROWS_CLEARED = "Rows cleared"; - protected static final String FINAL_SCORE = "Final score"; - protected static final String SCORE_SEPARATOR = ": "; - - protected static final int ROTATE_LEFT = KeyEvent.VK_DOWN; - protected static final int ROTATE_RIGHT = KeyEvent.VK_UP; - protected static final int SHIFT_LEFT = KeyEvent.VK_LEFT; - protected static final int SHIFT_RIGHT = KeyEvent.VK_RIGHT; - protected static final int DROP = KeyEvent.VK_SPACE; - protected static final int PAUSE = KeyEvent.VK_P; - protected static final int NEW_GAME = KeyEvent.VK_N; - - protected static final int WIDTH = 7; // squares - protected static final int HEIGHT = 11; // squares - protected static final int START_Y = 1; // squares from top - - protected static final Color EMPTY = new Color(0, 0, 0); - protected static final Color[] COLORS = { new Color(0, 0, 204), new Color(142, 0, 204), new Color(204, 204, 0), - new Color(204, 0, 204), new Color(0, 204, 204), new Color(0, 204, 0), new Color(204, 0, 0), }; - - protected static final String[] SHAPE_NAMES = { "O", "L", "J", "S", "Z", "T", "I", }; - protected static final int[] SHAPE_WIDTHS = { 2, 3, 3, 3, 3, 3, 4, }; - protected static final int[][][][] SHAPES = { - { // O - { { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } }, }, - { // L - { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 2, 1 } }, { { 1, -1 }, { 1, 0 }, { 1, 1 }, { 0, 1 } }, - { { 0, -1 }, { 0, 0 }, { 1, 0 }, { 2, 0 } }, { { 1, -1 }, { 2, -1 }, { 1, 0 }, { 1, 1 } }, }, - { // J - { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 0, 1 } }, { { 0, -1 }, { 1, -1 }, { 1, 0 }, { 1, 1 } }, - { { 2, -1 }, { 0, 0 }, { 1, 0 }, { 2, 0 } }, { { 1, -1 }, { 1, 0 }, { 1, 1 }, { 2, 1 } }, }, - { // S - { { 0, 0 }, { 1, 0 }, { 1, 1 }, { 2, 1 } }, { { 1, 0 }, { 0, 1 }, { 1, 1 }, { 0, 2 } }, }, - { // Z - { { 1, 0 }, { 2, 0 }, { 0, 1 }, { 1, 1 } }, { { 0, 0 }, { 0, 1 }, { 1, 1 }, { 1, 2 } }, }, - { // T - { { 1, 0 }, { 0, 1 }, { 1, 1 }, { 2, 1 } }, { { 1, 0 }, { 1, 1 }, { 2, 1 }, { 1, 2 } }, - { { 0, 1 }, { 1, 1 }, { 2, 1 }, { 1, 2 } }, { { 1, 0 }, { 0, 1 }, { 1, 1 }, { 1, 2 } }, }, - { // I - { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 } }, { { 0, 0 }, { 0, 1 }, { 0, 2 }, { 0, 3 } }, }, }; - - protected Color[][] board; - protected int score; - - protected boolean isOccupied(int x, int y) { - return x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT || board[x][y] != EMPTY; - } - - protected void emptyBoard() { - board = new Color[WIDTH][HEIGHT]; - for (int x = 0; x < WIDTH; ++x) { - for (int y = 0; y < HEIGHT; ++y) { - board[x][y] = EMPTY; - } - } - } - - protected void clearRows() { - int fallTo = HEIGHT - 1; - for (int y = HEIGHT; y-- > 0;) { - boolean full = true; - for (int x = 0; x < WIDTH; ++x) { - if (board[x][y] == EMPTY) { - full = false; - break; - } - } - if (full) { - ++score; - } else { - for (int x = 0; x < WIDTH; ++x) { - board[x][fallTo] = board[x][y]; - } - --fallTo; - } - } - for (int x = 0; x < WIDTH; ++x) { - for (int y = fallTo; y >= 0; --y) { - board[x][y] = EMPTY; - } - } - } - - protected long getDelay() { - return 20000 / (score + 50); - } - - protected ArrayList<Integer> currentShuffle = new ArrayList<Integer>(); - protected int currentShuffleIndex; - - protected int nextShape() { - if (currentShuffleIndex >= currentShuffle.size()) { - currentShuffleIndex = 0; - currentShuffle.clear(); - for (int i = 0; i < SHAPES.length; ++i) { - currentShuffle.add(i); - } - Collections.shuffle(currentShuffle); - } - return currentShuffle.get(currentShuffleIndex++); - } - - protected int shape; - protected int orientation; - protected int x; - protected int y; - - protected void createShape() { - shape = nextShape(); - orientation = 0; - x = (WIDTH - SHAPE_WIDTHS[shape]) / 2; - y = START_Y; - } - - protected int[][] getFallingSquares() { - int[][] result = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }; - int[][][] entry = SHAPES[shape]; - int[][] offsets = entry[mod(orientation, entry.length)]; - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < 2; ++j) { - result[i][j] = (j == 0 ? x : y) + offsets[i][j]; - } - } - return result; - } - - protected boolean shapeCollides() { - for (int[] square : getFallingSquares()) { - if (isOccupied(square[0], square[1])) { - return true; - } - } - return false; - } - - protected void drawShape(Color color) { - for (int[] square : getFallingSquares()) { - board[square[0]][square[1]] = color; - } - } - - protected void placeShape() { - drawShape(COLORS[shape]); - JPanel panel = (JPanel)getContentPane(); - panel.paintImmediately(0, 0, panel.getWidth(), panel.getHeight()); - } - - protected void unplaceShape() { - drawShape(EMPTY); - } - - protected boolean playing = false; - protected boolean paused = false; - - protected boolean isPlaying() { - return playing && !paused; - } - - protected void togglePause() { - paused = !paused; - } - - protected void beginGame() { - emptyBoard(); - createShape(); - placeShape(); - score = 0; - playing = true; - paused = false; - } - - protected void endGame() { - playing = false; - } - - protected boolean maybeAdjustShape(int rotation, int dx, int dy) { - if (!isPlaying()) { - return true; - } - unplaceShape(); - orientation += rotation; - x += dx; - y += dy; - boolean collides = shapeCollides(); - if (collides) { - orientation -= rotation; - x -= dx; - y -= dy; - } - placeShape(); - return collides; - } - - protected boolean sinkShape() { - if (!isPlaying()) { - return true; - } - if (maybeAdjustShape(0, 0, 1)) { - clearRows(); - createShape(); - if (shapeCollides()) { - endGame(); - } else { - placeShape(); - } - return true; - } - return false; - } - - protected void dropShape() { - while (!sinkShape()) - ; - } - - protected void rotateShapeLeft() { - maybeAdjustShape(-1, 0, 0); - } - - protected void rotateShapeRight() { - maybeAdjustShape(1, 0, 0); - } - - protected void shiftShapeLeft() { - maybeAdjustShape(0, -1, 0); - } - - protected void shiftShapeRight() { - maybeAdjustShape(0, 1, 0); - } - - public Tetris(int width, int height) { - super("Tetris"); - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setSize(width, height); - setContentPane(new JPanel() { - @Override - public void paint(Graphics graphics) { - super.paint(graphics); - int xScale = getWidth() / Tetris.WIDTH; - int yScale = getHeight() / Tetris.HEIGHT; - for (int x = 0; x < Tetris.WIDTH; ++x) { - for (int y = 0; y < Tetris.HEIGHT; ++y) { - graphics.setColor(board[x][y]); - graphics.fillRect(xScale * x, yScale * y, xScale, yScale); - } - } - String scoreString = (playing ? ROWS_CLEARED : FINAL_SCORE) + SCORE_SEPARATOR + String.valueOf(score); - graphics.setFont(TEXT_FONT); - graphics.setColor(TEXT_COLOR); - graphics.drawString(scoreString, (getWidth() - graphics.getFontMetrics().stringWidth(scoreString)) / 2, - (getHeight() / 2 - graphics.getFontMetrics().getHeight()) / 2); - } - }); - addKeyListener(new KeyListener() { - public void keyPressed(KeyEvent event) { - switch (event.getKeyCode()) { - case ROTATE_LEFT: - rotateShapeLeft(); - break; - case ROTATE_RIGHT: - rotateShapeRight(); - break; - case SHIFT_LEFT: - shiftShapeLeft(); - break; - case SHIFT_RIGHT: - shiftShapeRight(); - break; - case DROP: - dropShape(); - break; - case PAUSE: - togglePause(); - break; - case NEW_GAME: - if (playing) { - endGame(); - } - beginGame(); - break; - } - } - - public void keyReleased(KeyEvent e) { - } - - public void keyTyped(KeyEvent e) { - } - }); - new Thread() { - @Override - public void run() { - while (true) { - try { - Thread.sleep(getDelay()); - sinkShape(); - } catch (InterruptedException ignored) { - } - } - } - }.start(); - } - - public static void main(String... arguments) { - Tetris tetris = new Tetris(WIDTH * DEFAULT_SQUARE_SIZE, HEIGHT * DEFAULT_SQUARE_SIZE); - tetris.setVisible(true); - tetris.beginGame(); - } + private static final long serialVersionUID = -6851893161385783635L; + + protected static void log(String message) { + try { + Files.write(Paths.get("logs/tetris.log"), (message + "\n").getBytes(), StandardOpenOption.CREATE, + StandardOpenOption.APPEND); + } catch (IOException exception) { + exception.printStackTrace(); + } + } + + protected void logFailedMove(String moveName) { + log("Attempted move failed: " + moveName + " on shape " + shape + " (\"" + SHAPE_NAMES[shape] + "\")"); + } + + protected static int mod(int value, int modulus) { + int result = value % modulus; + return result < 0 ? result + modulus : result; + } + + protected static final int DEFAULT_SQUARE_SIZE = 60; // pixels + + protected static final Font TEXT_FONT = new Font(Font.SANS_SERIF, Font.BOLD, DEFAULT_SQUARE_SIZE / 2); + protected static final Color TEXT_COLOR = new Color(255, 255, 255, 127); + + protected static final String ROWS_CLEARED = "Rows cleared"; + protected static final String FINAL_SCORE = "Final score"; + protected static final String SCORE_SEPARATOR = ": "; + + protected static final int ROTATE_LEFT = KeyEvent.VK_DOWN; + protected static final int ROTATE_RIGHT = KeyEvent.VK_UP; + protected static final int SHIFT_LEFT = KeyEvent.VK_LEFT; + protected static final int SHIFT_RIGHT = KeyEvent.VK_RIGHT; + protected static final int DROP = KeyEvent.VK_SPACE; + protected static final int PAUSE = KeyEvent.VK_P; + protected static final int NEW_GAME = KeyEvent.VK_N; + + protected static final int WIDTH = 7; // squares + protected static final int HEIGHT = 11; // squares + protected static final int START_Y = 1; // squares from top + + protected static final Color EMPTY = new Color(0, 0, 0); + protected static final Color[] COLORS = { new Color(0, 0, 204), new Color(142, 0, 204), new Color(204, 204, 0), + new Color(204, 0, 204), new Color(0, 204, 204), new Color(0, 204, 0), new Color(204, 0, 0), }; + + protected static final String[] SHAPE_NAMES = { "O", "L", "J", "S", "Z", "T", "I", }; + protected static final int[] SHAPE_WIDTHS = { 2, 3, 3, 3, 3, 3, 4, }; + protected static final int[][][][] SHAPES = { + { // O + { { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } }, }, + { // L + { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 2, 1 } }, { { 1, -1 }, { 1, 0 }, { 1, 1 }, { 0, 1 } }, + { { 0, -1 }, { 0, 0 }, { 1, 0 }, { 2, 0 } }, { { 1, -1 }, { 2, -1 }, { 1, 0 }, { 1, 1 } }, }, + { // J + { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 0, 1 } }, { { 0, -1 }, { 1, -1 }, { 1, 0 }, { 1, 1 } }, + { { 2, -1 }, { 0, 0 }, { 1, 0 }, { 2, 0 } }, { { 1, -1 }, { 1, 0 }, { 1, 1 }, { 2, 1 } }, }, + { // S + { { 0, 0 }, { 1, 0 }, { 1, 1 }, { 2, 1 } }, { { 1, 0 }, { 0, 1 }, { 1, 1 }, { 0, 2 } }, }, + { // Z + { { 1, 0 }, { 2, 0 }, { 0, 1 }, { 1, 1 } }, { { 0, 0 }, { 0, 1 }, { 1, 1 }, { 1, 2 } }, }, + { // T + { { 1, 0 }, { 0, 1 }, { 1, 1 }, { 2, 1 } }, { { 1, 0 }, { 1, 1 }, { 2, 1 }, { 1, 2 } }, + { { 0, 1 }, { 1, 1 }, { 2, 1 }, { 1, 2 } }, { { 1, 0 }, { 0, 1 }, { 1, 1 }, { 1, 2 } }, }, + { // I + { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 } }, { { 0, 0 }, { 0, 1 }, { 0, 2 }, { 0, 3 } }, }, }; + + protected Color[][] board; + protected int score; + + protected boolean isOccupied(int x, int y) { + return x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT || board[x][y] != EMPTY; + } + + protected void emptyBoard() { + board = new Color[WIDTH][HEIGHT]; + for (int x = 0; x < WIDTH; ++x) { + for (int y = 0; y < HEIGHT; ++y) { + board[x][y] = EMPTY; + } + } + } + + protected void clearRows() { + int fallTo = HEIGHT - 1; + for (int y = HEIGHT; y-- > 0;) { + boolean full = true; + for (int x = 0; x < WIDTH; ++x) { + if (board[x][y] == EMPTY) { + full = false; + break; + } + } + if (full) { + ++score; + } else { + for (int x = 0; x < WIDTH; ++x) { + board[x][fallTo] = board[x][y]; + } + --fallTo; + } + } + for (int x = 0; x < WIDTH; ++x) { + for (int y = fallTo; y >= 0; --y) { + board[x][y] = EMPTY; + } + } + } + + protected long getDelay() { + return 20000 / (score + 50); + } + + protected ArrayList<Integer> currentShuffle = new ArrayList<Integer>(); + protected int currentShuffleIndex; + + protected int nextShape() { + if (currentShuffleIndex >= currentShuffle.size()) { + currentShuffleIndex = 0; + currentShuffle.clear(); + for (int i = 0; i < SHAPES.length; ++i) { + currentShuffle.add(i); + } + Collections.shuffle(currentShuffle); + } + return currentShuffle.get(currentShuffleIndex++); + } + + protected int shape; + protected int orientation; + protected int x; + protected int y; + + protected void createShape() { + shape = nextShape(); + orientation = 0; + x = (WIDTH - SHAPE_WIDTHS[shape]) / 2; + y = START_Y; + } + + protected int[][] getFallingSquares() { + int[][] result = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }; + int[][][] entry = SHAPES[shape]; + int[][] offsets = entry[mod(orientation, entry.length)]; + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 2; ++j) { + result[i][j] = (j == 0 ? x : y) + offsets[i][j]; + } + } + return result; + } + + protected boolean shapeCollides() { + for (int[] square : getFallingSquares()) { + if (isOccupied(square[0], square[1])) { + return true; + } + } + return false; + } + + protected void drawShape(Color color) { + for (int[] square : getFallingSquares()) { + board[square[0]][square[1]] = color; + } + } + + protected void placeShape() { + drawShape(COLORS[shape]); + JPanel panel = (JPanel)getContentPane(); + panel.paintImmediately(0, 0, panel.getWidth(), panel.getHeight()); + } + + protected void unplaceShape() { + drawShape(EMPTY); + } + + protected boolean playing = false; + protected boolean paused = false; + + protected boolean isPlaying() { + return playing && !paused; + } + + protected void togglePause() { + paused = !paused; + } + + protected void beginGame() { + emptyBoard(); + createShape(); + placeShape(); + score = 0; + playing = true; + paused = false; + } + + protected void endGame() { + playing = false; + } + + protected boolean maybeAdjustShape(int rotation, int dx, int dy) { + if (!isPlaying()) { + return true; + } + unplaceShape(); + orientation += rotation; + x += dx; + y += dy; + boolean collides = shapeCollides(); + if (collides) { + orientation -= rotation; + x -= dx; + y -= dy; + } + placeShape(); + return collides; + } + + protected boolean sinkShape() { + if (!isPlaying()) { + return true; + } + if (maybeAdjustShape(0, 0, 1)) { + clearRows(); + createShape(); + if (shapeCollides()) { + endGame(); + } else { + placeShape(); + } + return true; + } + return false; + } + + protected void dropShape() { + while (!sinkShape()) + ; + } + + protected void rotateShapeLeft() { + maybeAdjustShape(-1, 0, 0); + } + + protected void rotateShapeRight() { + maybeAdjustShape(1, 0, 0); + } + + protected void shiftShapeLeft() { + maybeAdjustShape(0, -1, 0); + } + + protected void shiftShapeRight() { + maybeAdjustShape(0, 1, 0); + } + + public Tetris(int width, int height) { + super("Tetris"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(width, height); + setContentPane(new JPanel() { + @Override + public void paint(Graphics graphics) { + super.paint(graphics); + int xScale = getWidth() / Tetris.WIDTH; + int yScale = getHeight() / Tetris.HEIGHT; + for (int x = 0; x < Tetris.WIDTH; ++x) { + for (int y = 0; y < Tetris.HEIGHT; ++y) { + graphics.setColor(board[x][y]); + graphics.fillRect(xScale * x, yScale * y, xScale, yScale); + } + } + String scoreString = (playing ? ROWS_CLEARED : FINAL_SCORE) + SCORE_SEPARATOR + String.valueOf(score); + graphics.setFont(TEXT_FONT); + graphics.setColor(TEXT_COLOR); + graphics.drawString(scoreString, (getWidth() - graphics.getFontMetrics().stringWidth(scoreString)) / 2, + (getHeight() / 2 - graphics.getFontMetrics().getHeight()) / 2); + } + }); + addKeyListener(new KeyListener() { + public void keyPressed(KeyEvent event) { + switch (event.getKeyCode()) { + case ROTATE_LEFT: + rotateShapeLeft(); + break; + case ROTATE_RIGHT: + rotateShapeRight(); + break; + case SHIFT_LEFT: + shiftShapeLeft(); + break; + case SHIFT_RIGHT: + shiftShapeRight(); + break; + case DROP: + dropShape(); + break; + case PAUSE: + togglePause(); + break; + case NEW_GAME: + if (playing) { + endGame(); + } + beginGame(); + break; + } + } + + public void keyReleased(KeyEvent e) { + } + + public void keyTyped(KeyEvent e) { + } + }); + new Thread() { + @Override + public void run() { + while (true) { + try { + Thread.sleep(getDelay()); + sinkShape(); + } catch (InterruptedException ignored) { + } + } + } + }.start(); + } + + public static void main(String... arguments) { + Tetris tetris = new Tetris(WIDTH * DEFAULT_SQUARE_SIZE, HEIGHT * DEFAULT_SQUARE_SIZE); + tetris.setVisible(true); + tetris.beginGame(); + } }