// 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 @version November 30, 2007 */ /* Charles H C Meyer * April 17, 2008 * Lab 9: Tetris I */ /* * I have gone though and filled in the rest of the code for this class. First is * the contructor, wich sets up the body, the height, the width, and the skirt. I * also filled in the .equals method, wich maps each piece onto a 2-d array of boolean * values, each boolean representing a point, and then checks the two boolean arrays * against eachother. Finally there is the pieceRow method, wich takes a piece a puts all * the rotations of that peice into a circular list, co that one can always call on the * next variable and get the next roation, and the list is immutable while still being * finite. In order to aids in that last method, I also wrote a rotate method that takes * a piece and generates a 90 degrees rotation of it. There is also a printBody method * is left in for use in debugging. */ 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 - note this is how a "piece" is really a list 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) { //the body holds the points body = points; //since width and height cannot be less then zero, starts them at that width = 0; height = 0; //sets height and weight based on the most extreme values of body for (int k = 0; k < body.length; k++) { if (body[k].getX()+1 > width) { width = (int) (body[k].getX() + 1); } if (body[k].getY() +1 > height) { height = (int) (body[k].getY()+1); } } //initiates the skirt skirt = new int[width]; //sets up the skirt to a defualt of the top row of the body for (int k = 0; k < skirt.length; k++) { skirt[k] = height -1; } //goes through the body and lowers the skirt as necessarry for (int k = 0; k < body.length; k++) { if (skirt[(int)(body[k].getX())] > body[k].getY() ) { skirt[(int) body[k].getX()] = (int) body[k].getY(); } } } /** 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. */ //each piece is copied into a heightxwidth boolean array such that a //boolean is true if there is a piece at that point. Then, if the //two pieces are equal, then the boolean arrays should be the same public boolean equals(Piece other) { //sends an error out if other is null if (other == null) { System.err.println("pice.equals called on a null piece"); return true; } //these two arrays will represent a rect of booleans width x height boolean[][] objectA = new boolean[height][width]; boolean[][] objectB = new boolean[other.getHeight()][other.getWidth()]; //if the dimensions are unequal, then obviously they are not equal if ( height != other.getHeight() || width != other.getWidth()) { return false; } //sets as true any point in objectA that is ocupied by a point in body for (int k = 0; k < body.length; k++) { objectA[(int)body[k].getY()][(int)body[k].getX()] = true; } //same thing for objectB and other.body for (int k = 0; k < other.getBody().length; k++) { objectB[(int)other.getBody()[k].getY()][(int)other.getBody()[k].getX()] = true; } //this compares the two boolean arrays, they should be the same if the objects are //the same for (int k =0; k < objectA.length; k++) { for (int d = 0; d < objectA[k].length; d++) { if (( objectA[k][d] && !objectB[k][d] )|| ( !objectA[k][d] && objectB[k][d] )) { 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. */ //rather then doing everything here, I rote a seperate method to //generate the next rotation. This simply takes the next rotation and //asigns it to the last one, using a loop, until the next rotation equals //the first public static Piece pieceRow(Piece starter) { //starts with the first peice Piece last = starter; //asigns the next rotation to last, and then sets last to that //rotated piece, until the next roation is starter while (!starter.equals(rotate(last))) { last.next = rotate(last); last = last.next; } //this completes the loop by asigning next to starter last.next = starter; return starter; //I really don't know what this is suposed to return, //so I just have it return starter, seems to work } //prints the body of a piece, good for debugging private static void printBody(Piece toPrint) { for (int k = 0; k < toPrint.body.length; k++) { System.out.println(toPrint.body[k].getX() + " " + toPrint.body[k].getY()); } } //takes a piece, and returns a 90 degrees counter clockwise rotation private static Piece rotate(Piece last) { //this is going to hold all the new points Vector newBodyVec = new Vector (); //cycles through the old body generating new points //the new x = - old x and the new y = the old y (I came up //with this by using a matrix, a sugestion from Nathan Merrit) //this still has to be shifted to put the bottom left at 0,0 for (int k =0; k < last.body.length; k++) { Point temp = new Point(0 - (int)last.body[k].getY(), (int) last.body[k].getX()); newBodyVec.add(temp); } //an array that will hold the body from newBodyVec, Point[] newBody = new Point[newBodyVec.size()]; //this will be equal to the lowest x and y coordinate respectivily, //used to fix the bottom left at 0,0, defualts them to known values int Xoffset = (int) newBodyVec.elementAt(0).getX(); int Yoffset = (int) newBodyVec.elementAt(0).getY(); //fills the body array from the body vec //also finds the shift needed to put bottom left at 0,0 for (int k = 0; k < newBodyVec.size(); k++) { newBody[k] = newBodyVec.elementAt(k); //changes Xoffset if it needs to be lower //to acomidate this point if (newBody[k].getX() < Xoffset) { Xoffset = (int) newBody[k].getX(); } //ditto for the y offset if (newBody[k].getY() < Yoffset) { Yoffset = (int)newBody[k].getY(); } } //this executes the shift of YOffset and XOffset to put the bottom //left at 0,0 for (int k = 0; k < newBody.length; k++) { Point adjusted = new Point((int)newBody[k].getX() - Xoffset, (int)newBody[k].getY() - Yoffset); newBody[k] = adjusted; } //creates a piece using this body Piece toReturn = new Piece(newBody); //and returns it return toReturn; } /** 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); } }