// Piece.java 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. Typical client code looks like... <pre> Piece pyra = new Piece(PYRAMID_STR); // Create piece from string int width = pyra.getWidth(); // 3 Piece pyra2 = pyramid.computeNextRotation(); // get rotation, slow way Piece[] pieces = Piece.getPieces(); // the array of root pieces Piece stick = pieces[STICK]; int width = stick.getWidth(); // get its width Piece stick2 = stick.fastRotation(); // get the next rotation, fast way </pre> */ public class Piece { // Starter code specs out a few basic things, leaving // the algorithms to be done. private TPoint[] body; private int[] skirt; private int width; private int height; private Piece next; // "next" rotation static private Piece[] pieces; // singleton static array of first rotations /** Defines a new piece given a TPoint[] array of its body. Makes its own copy of the array and the TPoints inside it. */ public Piece(TPoint[] points) { body = copyPoints(points); computeWidth(); computeHeight(); computeSkirt(); } /** * Alternate constructor, takes a String with the x,y body points * all separated by spaces, such as "0 0 1 0 2 0 1 1". * (provided) */ public Piece(String points) { this(parsePoints(points)); } /** 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 TPoint[] 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 is useful for computing where the piece will land. The caller should not modify this array. */ public int[] getSkirt() { return skirt; } /** * enables the client to set the next pointer * @param p */ public void setNext(Piece p) { next = p; } /** Returns a new piece that is 90 degrees counter-clockwise rotated from the receiver. */ public Piece computeNextRotation() { TPoint[] newPoints = copyPoints(body); swapXY(newPoints); mirrorHorizontal(newPoints, height-1); return new Piece(newPoints); } /** Returns a pre-computed piece that is 90 degrees counter-clockwise rotated from the receiver. Fast because the piece is pre-computed. This only works on pieces set up by makeFastRotations(), and otherwise just returns null. */ public Piece fastRotation() { 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(Object obj) { // standard equals() technique 1 if (obj == this) return true; // standard equals() technique 2 // (null will be false) if (!(obj instanceof Piece)) return false; Piece other = (Piece)obj; List<TPoint> myBody = Arrays.asList(body); List<TPoint> otherBody = Arrays.asList(other.getBody()); return myBody.containsAll(otherBody); } public String toString() { String representation = "Shape {\n"; representation += "width=" + width + "; height=" + height + "; skirt=" + Arrays.toString(skirt) + "\n"; representation += "body=" + Arrays.toString(body) + "\n"; representation += getStringVisualization(); return representation; } /** * debugging method, should not be called regularly (taxing) * @return a text representation of the shape */ public String getStringVisualization() { char[][] grid = new char[height][width]; for(int i = 0; i < height; i++) Arrays.fill(grid[i], ' '); for(int i = 0; i < body.length; i++) grid[Math.abs(body[i].y - height + 1)][body[i].x] = 'X'; String visual = ""; for(int i = 0; i < height; i++) visual += new String(grid[i]) + "\n"; return visual; } // String constants for the standard 7 tetris pieces public static final String STICK_STR = "0 0 0 1 0 2 0 3"; public static final String L1_STR = "0 0 0 1 0 2 1 0"; public static final String L2_STR = "0 0 1 0 1 1 1 2"; public static final String S1_STR = "0 0 1 0 1 1 2 1"; public static final String S2_STR = "0 1 1 1 1 0 2 0"; public static final String SQUARE_STR = "0 0 0 1 1 0 1 1"; public static final String PYRAMID_STR = "0 0 1 0 1 1 2 0"; // Indexes for the standard 7 pieces in the pieces array public static final int STICK = 0; public static final int L1 = 1; public static final int L2 = 2; public static final int S1 = 3; public static final int S2 = 4; public static final int SQUARE = 5; public static final int PYRAMID = 6; /** Returns an array containing the first rotation of each of the 7 standard tetris pieces in the order STICK, L1, L2, S1, S2, SQUARE, PYRAMID. The next (counterclockwise) rotation can be obtained from each piece with the {@link #fastRotation()} message. In this way, the client can iterate through all the rotations until eventually getting back to the first rotation. (provided code) */ public static Piece[] getPieces() { // lazy evaluation -- create static array if needed if (Piece.pieces==null) { // use makeFastRotations() to compute all the rotations for each piece Piece.pieces = new Piece[] { makeFastRotations(new Piece(STICK_STR)), makeFastRotations(new Piece(L1_STR)), makeFastRotations(new Piece(L2_STR)), makeFastRotations(new Piece(S1_STR)), makeFastRotations(new Piece(S2_STR)), makeFastRotations(new Piece(SQUARE_STR)), makeFastRotations(new Piece(PYRAMID_STR)), }; } return Piece.pieces; } // ------- Private Methods -------- // /** Given the "first" root rotation of a piece, computes all the other rotations and links them all together in a circular list. The list loops back to the root as soon as possible. Returns the root piece. fastRotation() relies on the pointer structure setup here. */ /* Implementation: uses computeNextRotation() and Piece.equals() to detect when the rotations have gotten us back to the first piece. */ private static Piece makeFastRotations(Piece root) { Piece currentPiece = root; while(true) { Piece nextPiece = currentPiece.computeNextRotation(); if(nextPiece.equals(root)) break; currentPiece.setNext(nextPiece); currentPiece = nextPiece; } currentPiece.setNext(root); return root; } /** Given a string of x,y pairs ("0 0 0 1 0 2 1 0"), parses the points into a TPoint[] array. (Provided code) */ private static TPoint[] parsePoints(String string) { List<TPoint> points = new ArrayList<TPoint>(); StringTokenizer tok = new StringTokenizer(string); try { while(tok.hasMoreTokens()) { int x = Integer.parseInt(tok.nextToken()); int y = Integer.parseInt(tok.nextToken()); points.add(new TPoint(x, y)); } } catch (NumberFormatException e) { throw new RuntimeException("Could not parse x,y string:" + string); } // Make an array out of the collection TPoint[] array = points.toArray(new TPoint[0]); return array; } /** * @param points * @return a copy of the passed point array */ private TPoint[] copyPoints(TPoint[] points) { TPoint[] copy = new TPoint[points.length]; for (int i = 0; i < points.length; i++) copy[i] = new TPoint(points[i]); return copy; } /** * computes the width of the shape */ private void computeWidth() { width = 0; for (int i = 0; i < body.length; i++) if(body[i].x > width) width = body[i].x; width++; } /** * computes the height of the shape */ private void computeHeight() { height = 0; for (int i = 0; i < body.length; i++) if(body[i].y > height) height = body[i].y; height++; } /** * computes the skirt of the shape * * note: destroys the old skirt */ private void computeSkirt() { skirt = new int[width]; Arrays.fill(skirt, 3); // fill the array with a value larger than any possible value for (int i = 0; i < body.length; i++) if(skirt[body[i].x] > body[i].y) skirt[body[i].x] = body[i].y; } /** * swaps the x and y values for every point in the passed array */ private void swapXY(TPoint[] points) { for (int i = 0; i < points.length; i++) points[i].swapXY(); } /** * mirrors the points horizontally * @param points */ private void mirrorHorizontal(TPoint[] points, int offset) { for (int i = 0; i < points.length; i++) points[i].x = Math.abs(points[i].x - offset); } /** * mirrors the points vertically * @param points */ private void mirrorVertical(TPoint[] points, int offset) { for (int i = 0; i < points.length; i++) points[i].y = Math.abs(points[i].y - offset); } }