Homework #9 CMSC 131
Due April 22nd, 2004 Object-Oriented Programming I
  Spring 2004

 

Tetris

This homework will give you experience working with two-dimensional arrays, and will give you an opportunity to start thinking about algorithm design.

You will implement the model of an interactive Tetris game.  We have provided the view and controller which provide the graphical representation of your model, the user interaction, and also the core game loop that keeps the game going.  We first describe some general information about Tetris, and then in the section "Your Assignment", we describe which parts you actually have to do.

You can try the applet online which implements a solution to this homework (screenshot below). If you are not familiar with Tetris, the rules are as follows.  Note that you do not have to implement all these rules.  Most are implemented by the framework we supply.  These rules are just for your game-playing pleasure.

This version of Tetris can be played in two ways.  The first allows a person to play the game manually, and a second version uses simple Artificial Intelligence (AI) to play the game automatically. You will also design and implement the algorithm that implements the core AI method to determine where to position the next piece in automatic play mode. 

There is also a lot of information and discussion about Tetris available. There is a Slashdot poll on Tetris, asking people "What is their favorite Tetris piece". There are a bunch of interesting links and stories, including this interesting one including some background on Tetris, links to articles on NP-completeness results, and AI for Tetris. Here is a movie of someone playing a very good game of Tetris.  Finally, our implementation of Tetris calculates the score based just on the number of pieces played.  But ideally, the scoring function should be more complex.

For this homework, you must implement three core methods of the Tetris game as described below.  You will write your methods to implement the TetrisLogic interface that runs within the provided framework. The supplied framework renders the game, and provides the interaction and timing. The framework uses the Cell utility class to represent one cell of the board or piece (which may be filled or unfilled, and has a color).  It uses the Position class to represent a position on the board.  The board and pieces are stored as two-dimensional arrays where the structure is an array of rows, and each row is an array of Cells.

The board is represented by the type "Cell[][]".  As with all two-dimensional arrays, you can examine "board.length" to see how many rows there are, or look at "board[0].length" to see how many items there are in the first row.  Since this is the same for all rows in this project, that will tell you how many columns there are in the board.  Then, you can access individual cells with "Cell cell = board[row][col]".  The top-most row is in "board[0]".  The bottom-most row is in "board[board.length - 1]". Once you have a cell, you can check to see whether it is filled by calling "cell.isFilled()".

Each piece is also represented by a two-dimensional array of cells - but in this case, the array is just big enough to contain a specific piece.  So, for example, the piece shown in the picture above (the zig-zag piece) fits in an array with two rows and three columns, each of which contains a Cell, but where the top-left and bottom-right cells are empty (i.e., calling their isFilled() method would return false.)  The following figure shows the object structure for the above zig-zag piece.

Finally, several of the methods in this project use the Position class to represent the position of a piece on the board.  Position is a simple utility class that contains two integers which specify an integer row and column.  This project uses Position to represent the position of the top-left corner of a piece.  So, for the piece in the board screenshot above, its position is (8, 4).  That is, the top-left cell (filled or unfilled) of a piece is in the 8th row from the top (starting at 0) and in the 4th column from the left (starting at 0).

Suggestion: Make sure you are familiar with the TextEditor example we discussed in class.  It has a good example of working with a two-dimensional array including manipulation of rows.

Getting the Code Distribution

We will be using CVS with AutoCVS as we have for previous assignments.  You can access the assignment code by checking it out from CVS with Eclipse (it is called 'hw9'). You can read the API documentation here.

Your Assignment

You should modify the supplied Tetris class which holds a shell for your solution.  The default one line implementations of the three methods make it so Tetris does something to start, but for it to work properly, you must implement the first two methods.  The last method evaluates potential piece positions, and enables Tetris to play in automatic mode. The methods you must write follow.


public boolean isLegalPosition(Cell[][] board, Cell[][] piece,
                               Position position);

isLegalPosition should determine if the specified piece is in a legal position. A position is legal if it is entirely on the board, and does not overlap any filled board cell. This method is used to make sure that rotated pieces end up in legal positions, to generate potential piece positions in automatic mode, and to detect if the game is over. The board and piece must not be modified.

TetrisFrame relies on isLegalPosition() for several uses.  isLegalPosition is called on every time step to determine whether there is space for a piece to move further downward. If it is no longer legal, then it implies that the piece can no longer move down and has reached its resting place.  isLegalPosition is also used when you press the space bar to drop a piece.  In this case, it is called once for each subsequent row until it finds the first illegal position, and that places the piece in the row just before that.  isLegalPosition is also called when a user presses the up arrow to rotate a piece.  In this case, isLegalPosition is called for the new potential position.  Since only legal positions are allowed, TetrisFrame will keep rotating the piece until a legal position is found.  This feature will, for example, make it impossible to rotate a vertically oriented piece when it is on the edge of the board.  Finally, isLegalPosition is used in the automatic game play mode to make sure that only legal positions of pieces are tried (see computeMoveQuality below for more detail on this.)


public Cell[][] collapseRows(Cell[][] board);

collapseRows should return a new board based on the specified board where all rows that are completely filled are collapsed with higher rows falling down to replace collapsed rows. This method is used to remove full rows. The input board must not be modified.  Thus, we suggest you start by duplicating the board, and then modify and return the duplicated the board. For this, you will find the "TetrisFrame.cloneBoard(...)" method useful (see API docs)

collapseRows() is called when a piece has reached its resting place. All rows that are completely filled must be collapsed (cleared), and consequently, all rows above the newly cleared row must fall to the bottom. It is your job to figure out how to collapse the rows - but again, we encourage you to first be familiar with the TextEditor example from class since that does something similar.


Tetris offers an "automatic" mode without any user intervention.  In this mode, TetrisFrame picks a random piece and then uses the computeMoveQuality method which you must write (see below) to determine the best position and orientation for that piece.  The game plays completely automatically - it picks a piece, calls your method for each possible position and orientation and then plays it in the best position, and then keeps going until the game is over. Thus, computeMoveQuality effectively implements the "brains" of this Artificial Intelligence.  If you determine a good position for the piece, then the game will play better in automatic mode.  For this assignment, we give you the algorithm you should use for computing move quality, but you are welcome to also try more complex algorithms for a challenge problem.

public double computeMoveQuality(Cell[][] board, Cell[][] piece,
                                 Position position, Cell[][] nextPiece);

computeMoveQuality should compute a measure of quality for the specified piece where larger values are better. This is guaranteed to not be called unless the piece is in a legal position, as determined by isLegalPosition(). This method is used by the automatic mode to determine which is the best position for a new piece. The board and piece must not be modified.  Again, consider using TetrisFrame.cloneBoard(). The "nextPiece" parameter can be ignored - its only purpose is to support the challenge problem.

The algorithm you should use in the computeMoveQuality method is:


Submitting Your Assignment

Submit your program by right-clicking on your project and select "Submit Project". Note that this option will only appear if you have successfully installed AutoCVS as described previously.  If you decide that you would like to make a change after you have submitted your project, you can resubmit as many times as you like before the extended deadline.  We will grade the last project submitted.

/* Name
 * Student ID
 * Homework #9
 */

<your code goes here>

Open Assignment

This is an "open" assignment.  You are free to get whatever help you want to get this assignment done.  You may look on the web, and talk to friends.  You can even look at other student's solutions or have other students help you debug your code.  But there are two conditions:

  1. You must document all outside help you get in your timelog.  If you have any non-trivial interaction with anyone about this homework, you must describe it (i.e., "<student xxx> helped me debug my program for 2 hours").  You will not be penalized for this help, but we want to know how much help people are getting to do this work - and we want you to be explicitly aware of how much help you are getting.

  2. You are responsible for understanding the code you turn in.  We expect you to know what you did as if you did it entirely privately.  You need to be able to do this level of work at this point in the semester if you expect to succeed in following projects and in exams.

Challenge Problem

For an extra challenge, you can write a better version of computeMoveQuality() that takes into consideration the next piece that will be used.