CSE131 Lab 9: Tetris Game using Object-Oriented Design

Assigned: Monday, April 18
Due: Friday, April 29 (but may be turned in until Tuesday, May 3 without penalty)

Goals: By the end of this lab, you should...


Part I: Practice with Class Hierarchies

Consider the following classes and answer the questions that follow. You will benefit most by doing the problems before you look at the solutions.)
        import java.util.*;

        abstract class Bird {
          String name;
          boolean canFly = true;
          Flock flock;

          public Bird(String name) { this.name = name; }
          public String getName() { return name; }
          public abstract String preferredClimate();
          public boolean likes(Bird b) {return true;}

          public Flock getFlock() {
            if (flock == null) {
              flock = new Flock();
              flock.add(this);
            }
            return flock;
          }

          public void joinFlockOf(Bird b) {
            if (likes(b)) {
              if (flock != null)
                flock.remove(this);
              flock = b.getFlock();
              flock.add(this);
            }
          }

          public String toString() {
            String result = name + " lives in " + preferredClimate();
            if (canFly)
              return "flying " + result;
            else
              return result;
          }
        }

        class BirdPuppet extends Bird {
          public BirdPuppet(String name) {
            super(name);
            canFly = false;
          }

          public String preferredClimate() { return "room temperature"; }

          public boolean likes(Bird b) {
            return (b instanceof BirdPuppet);
          }

          public void joinFlockOf(Bird b) {
            b.joinFlockOf(this);
          }
        }

        class SongBird extends Bird {
          public SongBird(String name) {
            super(name);
          }

          public String preferredClimate() { return "warm weather"; }

          public boolean migrates() {
            return true;
          }

          public boolean likes(Bird b) {
            return ((b instanceof SongBird) && ((SongBird) b).migrates());
          }
        }

        class Warbler extends SongBird {
          public Warbler() {
            super("Warbler");
          }

          public boolean likes(Bird b) {
            return super.likes(b) || (b instanceof BirdPuppet);
          }
        }

        class BigBird extends BirdPuppet {
          public BigBird() {
            super("BigBird");
          }

          public String preferredClimate() { return "Sesame Street"; }
        }

        class Flock extends LinkedList {  // LinkedList is from package java.util
          public boolean add(Object x) {
            return super.add((Bird) x);
          }

          public String toString() {
            String result = "";
            Iterator it = iterator();
            while (it.hasNext())
              result += " " + ((Bird) it.next()).getName();
            return result;
          }   
        }
  
  1. Draw the class hierarchy for the six classes given above.
  2. In the following code, put an "X" by each line that would result in a compilation error, and put two stars ("**") by each line that would compile but would result in a run-time error. In considering each line, assume that all the correct lines above it have executed.
        Bird b1, b2, b3, b4;
        b1 = new Bird("Cardinal");
        b2 = new SongBird("Goldfinch");
        b3 = new BigBird();
        Warbler w = new Warbler();
        SongBird sb1 = b2;
        SongBird sb2 = (SongBird) b2;
        SongBird sb3 = b3;
        SongBird sb4 = (SongBird) b3;
        SongBird sb5 = w;
        SongBird sb6 = (SongBird) w;
    
        Warbler warbler = new Warbler();
        Bird big = new BigBird();
        String pigeon = "pigeon";
        warbler.joinFlockOf(big);
        warbler.joinFlockOf(pigeon);
        warbler.getFlock().add(pigeon);
        warbler.migrates();
        big.migrates();
        warbler = big;
        warbler = (Warbler) big;
          
  3. What output that would be printed by executing the following procedure?
  4.     void birdsOfAFeatherFlockTogether() {
          SongBird sb = new SongBird("Goldfinch");
          Warbler w = new Warbler();
          BigBird bb = new BigBird();
          System.out.println("sb = " + sb);
          System.out.println("w = " + w);
          System.out.println("bb = " + bb);
    
          System.out.println("sb likes w? " + sb.likes(w));
          System.out.println("sb likes bb? " + sb.likes(bb));
          System.out.println("w likes sb? " + w.likes(sb));
          System.out.println("w likes bb? " + w.likes(bb));
          System.out.println("bb likes sb? " + bb.likes(sb));
          System.out.println("bb likes w? " + bb.likes(w));
    
          sb.joinFlockOf(w);
          bb.joinFlockOf(sb);
          System.out.println("Flock of sb: " + sb.getFlock());
          System.out.println("Flock of bb: " + bb.getFlock());
    
          bb.joinFlockOf(w);
          System.out.println("Flock of w: " + w.getFlock());
          System.out.println("Flock of bb: " + bb.getFlock());
        }
          


Part II: Tetris

In this lab you will implement a variant of the classic game Tetris and then extend it with new features. Tetris is played on a vertical board consisting of a grid of square spaces 32 rows by 16 columns. The playing pieces are randomly selected configurations of four squares. Playing pieces fall, one at a time, from the top of a board until they either reach the bottom of the board or reach a square on the board that is already filled. When a piece stops falling, it fills in the squares of the board that it occupies, and then the next piece begins falling.

Using arrow keys on the keyboard, the player can rotate a piece or move it left or right as it is falling. Whenever an entire row of the board is filled, that row is removed and all the rows above it are moved down. The objective is to keep playing as long as possible, which is accomplished by filling in rows in order to remove them. When there is no longer room on the board to drop another piece, the game ends.

Scoring: Each piece dropped scores one point. Each row removed scores 100. To make the game more challenging, the pieces drop faster as the score increases.

User Controls:

Directions: Begin by completing the implementaton of the game according to the specification given above. The following directions provide some suggestions for things that will help you, but most of the details are left up to you. Rather than write long methods, try to break up the functionality into more short methods, so that when you create subclasses, it will be easier to override specific parts of the functionality of the class.

  1. Download the provided code into your cse131 folder and import it into a new project. Put your name, lab section, and email address at the top of each file.
  2. Run the game in the provided file Tetris.java. You should see a striped board and a score of 0 at the top. Random pieces will appear at the top of the board, and the score will increase, but the pieces don't move.
  3. Study the provided code to get a feel for what it does. Some of the graphics details (for example, how graphics components are created, added to containers, and painted) haven't been covered in CSE131, but go ahead and read through the files Tetris.java, Grid.java, Board.java, Piece.java, and PieceFactory.java to understand the way the implementation is organized. Notice that the Tetris class provides some static methods for getting references to the board and the tetris game itself so that other objects can call methods on them, for example to check if a board position would be legal, or to add points to the score.
  4. The class Grid is the parent of both the Board and Piece classes. Study its implementation. Notice that a Grid object is created by passing it an array of rows, where each row is an array of integers representing the squares in the columns of that row. If an array entry is 0, the corresponding square is considered to be empty. Look at the paint method to see how a grid paints itself based on its array.

  5. The Board class extends Grid. It will be helpful to add the following methods:

  6. The Piece class also extends Grid. Notice that its currentX (column) position is represented as an int, but its currentY (row) position is represented as a double. This is so you can drop the piece gradually, probably 1/5 of a square at a time. To determine the next whole row number, the method Math.ceil may help you.


Part III: Tetris++

In this part of the lab, you'll extend the game to add new features.

Implement four of the following new types of pieces. Create at least one new class for each new feature. Try to keep your classes small, inheriting as much as possible. As you create each new type of piece, modify the switch statement in the PieceFactory to create the various types of pieces at random.

On paper, draw a class hierarchy diagram for all the provided classes their ancestors, all the way up to the class Object. Add your classes to the diagram.

Test thoroughly. It is recommended that you test each feature right after you implement it.

What To Turn In:

Part I: You do not need to turn anything in for Part I, but you should work the practice problems carefully and check your answers.

Parts II and III: Turn in a completed cover sheet with your class hierarchy diagram and a paper printout of all of the code you have written for Parts II and III, including the provided files you have modified.

Demo: Once your lab packet is ready, bring it to a TA during consulting hours or your lab section (before the end of the "grace period") and demonstrate your program for the TA. The TA will record a demo grade on your cover sheet and will keep the packet so the code can be graded.


Jason Fritts (fritts@wustl.edu)