// Piece.java import java.awt.*; import java.util.*; /** An immutable representation of a tetris piece in a particular rotation. Each piece is defined by the blocks that make up its body. See the Tetris-Architecture.html for an overview. This is the starter file version -- a few simple things are filled in already @author Nick Parlante @author Eric Chown @author Laura Toma @author Nathan Merritt @version April 10, 2008 */ public final class Piece { /* Implementation notes: -The starter code does out a few simple things for you -Store the body as a Point[] array -Do not assume there are 4 points in the body -- use array.length to keep the code general */ private Point[] body; // The body of the piece, each point is a coordinate specifying a block private int[] skirt; // Each element specifies how high the piece will land in the corresponding column private int width; // The width of the piece for the current rotation private int height; // The height of the piece for the current rotation private Piece next; // The "next" rotation static private Piece[] pieces; // singleton array of first rotations /** Defines a new piece given the Points that make up its body. Makes its own copy of the array and the Point inside it. Does not set up the rotations. This constructor is PRIVATE -- if a client wants a piece object, they must use Piece.getPieces(). getPieces() will therefore make all of the calls to the constructor. As with all constructors, your variables should be initialized here. This means you'll need to calculate width and height as well as setting up the skirt (doing these things once in the constructor means you don't have to do them on the fly during game play). The one exception to this is the "next" variable. You'll want to set that in the pieceRow method. */ private Piece(Point[] points) { body = points; int[] row_counts = new int[body.length]; int[] col_counts = new int[body.length]; // start at zero, use the largest X and Y component that we find height = width = 0; for (int i = 0; i < body.length; i++) { if ((int)body[i].getX() > width) { width = (int)body[i].getX(); } if ((int)body[i].getY() > height) { height = (int)body[i].getY(); } } // and increment both because we were dealing with indicies height++; width++; skirt = new int[width]; // start all the skirt values at (height - 1) for (int i = 0; i < skirt.length; i++) { skirt[i] = height - 1; } /* now look through all the points, and if we have a lower y value then set the skirt to that instead */ for (int i = 0; i < body.length; i++) { int bx = (int)body[i].getX(); int by = (int)body[i].getY(); if (skirt[bx] > by) { skirt[bx] = by; } } next = null; } /* A simple toString method to help in testing our shapes */ public String toString() { String string; string = "height: " + height; string += " width: " + width; string += " Points: "; for (int i = 0; i < body.length; i++) { string += body[i]; } return string; } /** Returns the width of the piece measured in blocks. */ public int getWidth() { return(width); } /** Returns the height of the piece measured in blocks. */ public int getHeight() { return(height); } /** Returns a pointer to the piece's body. The caller should not modify this array. */ public Point[] getBody() { return(body); } /** Returns a pointer to the piece's skirt. For each x value across the piece, the skirt gives the lowest y value in the body. This useful for computing where the piece will land. The caller should not modify this array. */ public int[] getSkirt() { return(skirt); } /** Returns a piece that is 90 degrees counter-clockwise rotated from the receiver. Implementation: The Piece class pre-computes all the rotations once. This method just hops from one pre-computed rotation to the next in constant time. */ public Piece nextRotation() { return next; } /** Returns true if two pieces are the same -- their bodies contain the same points. Interestingly, this is not the same as having exactly the same body arrays, since the points may not be in the same order in the bodies. Used internally to detect if two rotations are effectively the same. */ public boolean equals(Piece other) { /* we're going to solve this problem by adding all the points to a vector, and then deleting them from the vector as we see them in Piece other. If we can't delete a point, we return false. If we still have points left at the end we return false. */ Vector these_points = new Vector (); // add all points in body to the vector for (int i = 0; i < body.length; i++) { these_points.add(body[i]); } // now start checking against the other Piece Point[] others_body = other.getBody(); if (others_body.length != body.length) { return false; } // if the point doesn't exist in others_body -> false for (int i = 0; i < others_body.length; i++) { if (!these_points.contains(others_body[i])) { return false; } these_points.remove(others_body[i]); } // and if everything isn't gone, return false as well if (!these_points.isEmpty()) { return false; } return true; } /** * This is where most of your work will be done. pieceRow takes * the initial rotation of a piece and should create all of the * rest of the rotations in a kind of circular list. Essentially * you get the body array of the initial configuration of the * Piece, you need to calculate the other body arrays. */ public static Piece pieceRow(Piece starter) { /* the rotational matrix (see: Wikipedia -> Rotation_mathematics) [[0, -1], [1, 0]] */ Matrix ccw90 = new Matrix(new int[][] { new int[] {0, -1}, new int[] {1, 0} }); /* now, as long as the pieces we create aren't the same as the starter piece we'll loop along making new rotations */ Piece current = starter; Piece lastPiece; do { Point[] current_body = current.getBody();; Point[] rotated_body = new Point[current_body.length]; /* for each point in the body: rotate 90deg CCW by matrix multiplication (overkill, but so, so badass - and I can do n degree rotations too!)*/ for (int i = 0; i < current_body.length; i++) { Matrix prod = ccw90.multiply(new Matrix (current_body[i])); rotated_body[i] = prod.toPoint(); } /* now that we've rotated each point, adjust so that we're back on 0,0 */ // first find the minimum on each axis, X and Y int minx = 0; int miny = 0; for (int i = 0; i < rotated_body.length; i++) { if (rotated_body[i].getX() < minx) { minx = (int)rotated_body[i].getX(); } if (rotated_body[i].getY() < miny) { miny = (int)rotated_body[i].getY(); } } /* now, if the minimum is less than zero, adjust each point by it so that the minimum on each axis is zero */ for (int i = 0; i < rotated_body.length; i++) { if (minx < 0) { rotated_body[i].setLocation((rotated_body[i].getX() - minx), rotated_body[i].getY()); } if (miny < 0) { rotated_body[i].setLocation(rotated_body[i].getX(), (rotated_body[i].getY() - miny)); } } Piece newPiece = new Piece(rotated_body); current.next = newPiece; // and update for the next pass lastPiece = current; current = newPiece; } while (!current.equals(starter)); // and get the last link so the circle is complete lastPiece.next = starter; return starter; } /** Returns an array containing the first rotation of each of the 7 standard tetris pieces. The next (counterclockwise) rotation can be obtained from each piece with nextRotation(). In this way, the client can iterate through all the rotations until eventually getting back to the first rotation. */ public static Piece[] getPieces() { // Makes seven calls to pieceRow for each of the seven standard Tetris pieces. Places // the results of each call into an array and returns the array. pieces = new Piece[] { pieceRow(new Piece(parsePoints("0 0 0 1 0 2 0 3"))), // 0 pieceRow(new Piece(parsePoints("0 0 0 1 0 2 1 0"))), // 1 pieceRow(new Piece(parsePoints("0 0 1 0 1 1 1 2"))), // 2 pieceRow(new Piece(parsePoints("0 0 1 0 1 1 2 1"))), // 3 pieceRow(new Piece(parsePoints("0 1 1 1 1 0 2 0"))), // 4 pieceRow(new Piece(parsePoints("0 0 0 1 1 0 1 1"))), // 5 pieceRow(new Piece(parsePoints("0 0 1 0 1 1 2 0"))), // 6 }; return pieces; } /** Given a string of x,y pairs ("0 0 0 1 0 2 1 0"), parses the points into a Point[] array. (Provided code) */ private static Point[] parsePoints(String string) { Vector points = new Vector (); StringTokenizer tok = new StringTokenizer(string); try { while(tok.hasMoreTokens()) { int x = Integer.parseInt(tok.nextToken()); int y = Integer.parseInt(tok.nextToken()); points.addElement(new Point(x, y)); } } catch (NumberFormatException e) { throw new RuntimeException("Could not parse x,y string:" + string); // cheap way to do assert } // Make an array out of the Vector Point[] array = new Point[points.size()]; points.copyInto(array); return(array); } }