CS 101 (Spring 1999)
Lab 11: The Ultimate Tetris Lab

Lab Assigned Design Due
(Mondays 2 PM)
Lab Due
(Fridays 2 PM)
16 Apr None 26 Apr (No late coupon)


Overview:

Tetris: (click the name to play somebody else's implementation)

Goals:

By the end of this lab, you should Most labs contains two parts, design and implementation, due on the dates shown above by 2 PM. This lab doesn't. The design is provided in this document.


Before starting:

[[[ Download PC zip ]]]

Zip includes:

Remember to type the following lines at the top of any files that use the terminal or canvas classes.

import cs101.terminal.*;
import cs101.canvas.*;

Particulars

The design of this lab is somewhat different than Lab 10. The notion of a Side is gone. The reason is that the tetris pieces will occupy space on a grid-like canvas. It is therefore the area of the squares that count, and not the inducing vector.

Thus, pieces are constructed differently than before. For examples, take a look at TetrisGamma and its antimatter twin TetrisAmmag. You will have to construct the other Tetris pieces yourself.

This document describes the design without telling you the coding details. That is, the overall structure of the solution is given, but actual code is provided by you.


What makes a tetris piece?

It is helpful to think of a tetris shape as if it were constructed of squares that are glued together:

Above, you see a tetris piece (the Gamma piece, named for the shape of the Greek letter) in outline form, as you would see the piece when you play. Above, you see how the piece is constructed using four square blocks. Check out the constructor in TetrisGamma.java.

You see that one of the square blocks is distinguished. It is the main component, an instance of TetrisPiece, which is a special kind of TetrisBlock. In other words, TetrisPiece extends TetrisBlock.

A Tetris piece is constructed by gluing TetrisBlocks together. When block B is glued to block A, block B must follow block A around, wherever A might go. When block B is glued to block A, block B is told its offset, grid-wise, from block A.

When a TetrisBlock moves around, so must the blocks glued to it. The blocks glued to it are listeners. Since a block has four sides, it can have at most four listeners. You will keep track of a block's listeners using an array .

A block is moved by calling its moved method. That block, in turn, must call its listeners' moved methods.

A block is rotated by calling its rotated method. That block, in turn, must call its listeners' rotated methods.


The APIs:

CS101CanvasGrid

To ease the placement of tetris shapes, you will extend CS101Canvas to CS101CanvasGrid, so that the space can be regarded as a grid of rectangles, each of which is occupied or not occupied.

Additionally, this kind of canvas is setup so that it can listen to key presses and call methods based on them. The class can register an ArrowListener, so that methods of the listener are called based on key presses as follows:

On some computers, for some windowing systems, the arrow keypresses may not be transmitted. Thus, each gesture is duplicated by a text key. The keys are chosen to mimic the intended direction, based on the number keys' arrangement on the numeric keypad of most keyboards.
Key Press Method Called
2
Down Arrow
down()
4
Left Arrow
left()
6
Right Arrow
right()
8
Up Arrow
rotate()

Since TetrisGame controls the game, it will want to register itself as the ArrowListener of its CS101CanvasGrid.

Here is the API:

CS101CanvasGrid(int nc, int nr)
The constructor takes in the number of columns and rows. The superclass constructor is called to initialize a CS101Canvas.

Then, a grid (two-dimensional array) of GridSquares is set up. Initially, all grid locations are unoccupied except those at the extreme left, right, and bottom of the canvas.

setOccupied(int i, int j, boolean val)
establishes that the grid location at column i and row j is occupied (if val is true) or unoccupied (if val is false).
boolean isOccupied(int i, int j)
returns the occupied status of the grid location at column i and row j.
GridSquare
This class extends Rect so that a rectangle can remember whether it is occupied or not. In the extension, the following methods must be provided:
GridSquare(int col, int row, int squaresize)
The constructor calls its superconstructor to establish a Rect at the desired column and row. Remember to convert from column and row to pixel coordinates, using the supplied square size.
public void setOccupied(boolean val)
sets the occupation status to the supplied parameter.
public boolean isOccupied()
returns the occupation status of the rectangle.
When the rectangle becomes occupied, it sets its color to Color.red. When the rectangle is unoccupied, it sets its color to Color.white.
Startup
Contains the usual testing calls, and then instantiates a TetrisGame. You will need to add selfTest calls for any classes you add.
TetrisGame
Constructs a tetris game and repeatedly places pieces and listens for user-driven instructions to move the piece. In this class, curPiece is the piece currently being manipulated on the screen.
TetrisGame(int cols, int rows, int pieces)
constructs a Tetris game with the specified number of columns and rows on the grid. The pieces parameter could be used to limit the number of pieces offered in a game. If your game continues indefinitely, you may choose to ignore this parameter.
TetrisPiece genPiece()
is responsible for generating the next Tetris piece for the game. Use Math.random() so that the pieces are randomly and uniformly distributed.

Note that polymorphism allows a particular shape (TetrisGamma, TetrisTee, etc.) to be treated as the generic TetrisPiece.

void advance()
is called whenever a motion has been performed on the piece. This method ensures progress of the game by every so often (1 in 3 times as the code is given to you) making the pieces move down one square.

If we used Java threads and concurrency, we could advance the piece down the screen automatically even if the user didn't do anything. This is what a real game would do, and you will learn about concurrency in CS 102.

Because TetrisGame is an ArrowListener, it must implement the methods in that interface:
void left()
The left arrow (or its equivalent number key) is pressed.
void right()
The right arrow (or its equivalent number key) is pressed.
void down()
The down arrow (or its equivalent number key) is pressed.
void up()
The up arrow (or its equivalent number key) is pressed.
TetrisBlock
This class represents a block (square) that can be part of a Tetris piece. You have to set up the block as a listener to its inducing side. You also have to place the block's four sides, given the analysis in this document.
TetrisBlock(leader, canvas, dX, dY)
constructs a TetrisBlock that is in relationship to leader, offset in grid positions by dX and dy.
addListener(TetrisBlock listener)
adds a listener (a TetrisBlock) to this TetrisBlock. There can be at most 4 listeners. You must implement the listeners as an array.

For each of the following methods, once "this" TetrisBlock has performed its intended action, the block should invoke the same method on each of its listeners.

rotated()
is called when this piece is to be rotated counterclockwise from its current relationship with its leader. Its dX and dY must be adjusted, as discussed in class.
moved()
is called when this block is possibly moved. The block must determine its new column an row coordinates by adding dX and dY to its leader's coordinates.
allClear()
is called to determine if
  • the Tetris board is unoccupied at this block's current coordinates, and
  • all listeners of this block are similarly allClear.
pickUp()
If the piece is down, picks up the piece by informing the canvas that its grid location is unoccupied.
putDown()
If the piece is up, puts down the piece by informing the canvas that its grid location is occupied.
TetrisPiece
is a special kind of TetrisBlock.
TetrisPiece(CS101CanvasGrid canvas, int col, int row)
calls the superconstructor with dX and dY of 0. The column and row for "this" block is then established as the parameters' values.
boolean proposeMove(int newCol, int newRow, int oldCol, int oldRow)
This is an important method. It proposes a move to the new parameter values. The following sequence of actions should occur:
  1. The piece is picked up.
  2. The piece is moved.
  3. It is determined if the board is allClear at the new location.
    • If so, the piece is put down.
    • If not, the piece is moved back to its old location and put down.
  4. The method returns true if the move was successful; otherwise, false is returned.
Because of the way listeners are set up, the actions on "this" TetrisPiece (and therefore, on "this" TetrisBlock) are propagated to all blocks making up this piece.

exercise
This exercises a piece, moving it, rotating it, and then randomly applying those transformations.
abstract String name()
This method makes this class abstract. Each subclasses must provide a name for its piece.

Each of the following actions, called from TetrisGame, can be reduced to a call to proposeMove. Each returns true if the move is successful; otherwise, false is returned.

boolean moveLeft()
Try to move this piece left (negative x direction) one square.
boolean moveRight()
Try to move this piece right (positive x direction) one square.
boolean moveUp()
Try to move this piece up (negative y direction) one square.
boolean moveDown()
Try to move this piece down (positive y direction) one square.

Rotation requires a little more work. We have to try the rotation, and if it doesn't work out, then we have to unrotate the piece. Hint: You can unrotate a piece by rotating it three more times counterclockwise.

boolean rotateCounterClockwise()
The following sequence of steps is performed:
  1. The piece is picked up.
  2. The piece is rotated. This updates the blocks' dX and dY values.
  3. The piece is informed that it has moved, so as to recompute the blocks' locations.
  4. It is determined if the board is allClear at the new location.
    • If so, the piece is put down.
    • If not, the piece is unrotated and put down.
  5. The method returns true if the move was successful; otherwise, false is returned.
boolean rotateClockwise()
Can be accomplished by multiple calls to rotateCounterClockwise.

Each of the following objects extends TetrisPIece.java to obtain a particular kind of Tetris piece.

TetrisSquare
The four-square Tetris square.
TetrisGamma
The Gamma piece, shown in this document.
TetrisAmmag
The mirror image of TetrisGamma
TetrisTee
Three blocks in a row, with one block below in the middle.
TetrisZigZag
The piece described in Lab 10.
TetrisGazGiz
The mirror image of TetrisZigZag
TetrisStraight
Four blocks in a row.

What to turn in:

Implementation
  1. Complete a code cover sheet.
  2. Provide a transcript from your self-tests.
  3. Complete the classes as described above.
  4. Provide a printout of any files you have modified.
  5. Be prepared to demo in lab.


Last modified 12:38:04 CDT 22 April 1999 by Ron K. Cytron