This is a double programming assignment. It is intended to NOT be doable in a single night. It’s long with a non-optimized solution taking about 700-800 lines of code and white space (not including comments). It’s much more complex than any of your other programs but at the time of the assignment we have already covered the critical material. This assignment synthesizes nearly everything covered so far in CSE11.
The goal is to create a Tetris game program that you play on your computer. To achieve this goal you will be using Arrays (or ArrayLists), rectangular arrays, Threads, graphical user interfaces with java Swing objects, for loops, while loops, Boolean expressions, and good program design.
This is a much more substantial program than your previous assignments and you should expect it take you much more time. Begin early. Ask questions. Be Patient and develop in stages. You have several weeks. Use them!.
A full lecture will be devoted to questions and hints for this project.
If you do nothing on this program the first week, you will find it very difficult and frustrating. Start working the first week. Read the suggested development process. The program is quite doable, but NOT if you try to solve the entire problem at once, or in a single week.
What the Final Game looks like when initialized and after being played for awhile with default the default blocksize (20 pixels)
There are seven Tetris shapes (each happens to occupy four grid squares).
When a new game is begun and empty field/board that is 10 blocks wide, 20 blocks high is created. Then a Randomly-selected shape is created and begins moving downwards toward the bottom of the grid. The shape starts roughly at the center top row. A shape may be rotated clockwise or counter-clockwise by the user. A shape may be moved left or right. And the user may drop the shape into place.
A shape may not be moved out of bounds. It may not be rotated if the rotation would make it run into an existing block on the board. When shape can no longer move downwards (hits the bottom, or runs into existing blocks, a new shape is automatically created.
The game is over when a shape cannot fit onto the grid.
The player has five keys at his/her disposal to move the current Tetris shape
- h – moves the Tetris shape left
- l – moves the Tetris shape right
- j – rotates the Tetris shape counter clockwise
- k – rotates the Tetris shape clockwise
- spacebar – drops the shape downwards from its current position until it would stop
- Every time a shape moves downwards, score should be incremented by 10.
- Rows completed when a single shape stops movement
a. 1 row – 100
b. 2 rows – 400
c. 3 rows – 800
d. 4 rows – 1600
blocks should begin movement at 1 row/second. maximum speed should be 20 rows/second.
Block movement should speed up every 2000 points, until 40000 points are reached, and then maximum speed should be maintained
The Game is over when the next random piece will not fit. You may draw the final piece or not draw it (not drawn in the is diagram)
When creating a larger program, one has to figure out how to break up the problem into smaller components. Since, this is likely the first big program you’ve ever created, this assignment guides you through some of this process. Let’s begin by looking at the most likely classes, and then expanding
- Tetris Shapes
- the grid (or field) over in which shapes are being placed
- The graphical interface
There are two less clear classes at this stage.
- A ShapeMover that causes the current shape to move under program control
- a coordinate class (basically row,column coordinates)
There can be several approaches to designing a Tetris Shape. But some of the capabilities that is must have are:
- Must be able to create one of 7 specific shapes
- Must be able to create a random shape
- must be able to rotate clockwise
- must be able to rotate counterclockwise
- should be able to move it left/right (we’ll describe why this class is NOT the best place for this functionality.
What about displaying the Shape on the canvas? That’s a good design question. This assignment recommends that you do NOT make the TetrisShape class a graphics object, but instead represent shape as an array of four coordinates (See the Coord class below). Does a shape need to know the boundaries of the field? Perhaps, but it is a simpler design if the TetrisShape does not know where it is on the field.
The Shape as an array (row,column) coordinates.
A good approach is to think about a shape as a set of coordinates, with one of the blocks being at coordinate location (0,0). This make rotation, much simpler. All shapes can be rotated, except for the 2x2 square shape.
The T-shape could be represented as coordinates [(-1,0),(0,0),(0,1),(1,0)]
One of the Ell shapes could be represented as coordinates [(-2,0),(-1,0),(0,0),(0,1)]
and the other Ell shape as coordinates [(-2,0),(-1,0),(0,0),(0,-1)]
How to do rotation: You can look up Rotation matrices for graphics on the web, if you want the full
background. But our rotation is special (90 degrees).
If a coordinate is (r,c)
- rotation in one direction makes the rotated coordinate (-c,r),
- rotation in the other direction makes the rotated coordinate (c,-r)
If you want to rotate a shape, you apply the formula for all coordinates of the shape. For example, rotating one of Ells [(-2,0),(-1,0),(0,0),(0,1)] using the first rotation above yields new coordinates of [(0,-2),(0,-1),(0,0),(-1,0)] (draw dots to see that this should be counter clockwise rotation)
One of the reasons we don’t put where the shape is located on the board, is that rotation is MUCH easier.
I suggest that you create two “getters” for this class. One getter, returns the array of coordinates as they are stored. The other returns an array of coordinates with a given offset. For example for the first Ell shape, a getter with no arguments would return [(-2,0),(-1,0),(0,0),(0,1)]
but a getter with an offset of (3,4), would add three to the first dimension and four to the second and return [(1,4),(2,4),(3,4),(3,5)]
You should find the second form very convenient for placing a shape at a particular location on the board. External to this class, you keep track of where on the board to place the shape. The shape itself keeps track of its orientation (how it has been rotated).
Notice that Tetris shape is NOT a graphics object, it is simply a logical object.
Before going on, let’s talk about the Coord class
It is highly recommended that you create a Coord class where an instance of the class is an integer pair (r,c). You probably want to write an equals() method and a toString() method, but this is not essential. You can then represent a shape as an array of Coords. (r,c) are (row, column)
Note you could use the java.awt.Point. However, I find the Point.x, Point.y to be confusing in this case. A shape occupies a set of (row,column) logical coordinates. A TetrisGrid (below) is a 2D array of (rows high x columns wide).
The TetrisGrid is the bounded X,Y coordinate plane on which the shapes are placed. Think about it as “slots” that can be (a) empty, (b) occupied with blocks from previous pieces. (c) occupied by the current piece in play.
A TetrisShape doesn’t know about other shapes, It is on the TetrisGrid where Shapes interact with one another.
The Grid is also where rules of movement are enforced.
- The piece in play can’t be moved outside of the boundaries (you might want to ignore the top boundary for simplicity)
- a Shape can’t be rotated where one of its blocks would be outside the boundary
- a Shape can’t be rotated if the result of the rotation would move it into another occupied block
- a Shape cannot be moved (left, right, down) if that movement would put any part into an occupied block
Functionality of the TetrisGrid that will be needed
- Constructor – builds a 10 wide x 20 high grid
- Get an array (or list) of blocks that are occupied
- Determine if a shape intersects any occupied block in the grid
- Determine if a shape (at a particular offset) is completely in bounds
- Add a shape to the grid
- Remove a shape from the grid
- Determine if after placing a shape, rows have been completed
- Delete completed rows
- Override toString() for Ascii Printing/debug
IMPORTANT: SO FAR, THE CLASSES THE HAVE BEEN DISCUSSED ARE ALL LOGICAL. Use Graph Paper as “TetrisGrid”, to place a TetrisShape on the Grid, add the same (row, column) offset to each one of the Shape’s Coords – this is where the shape would be actually be placed on the TetrisGrid. Please, please, please, do this by hand so that you understand how, logically, TetrisShapes are put on TetrisGrids
The TetrisGrid must be drawn . You are being given a fully-functional graphics program called GameGrid.java. You should study how it works and what it does. You will need to modify it somewhat for your purposes, but it’s a really good start on the graphics part.
It has a “mover” thread that moves a single block back and forth. You won’t use this mover thread, but you can model your final program on it’s logic. We’ll discuss this program in class, too.
The best way to think about GraphicsGrid, is that it is simply the display part of TetrisGrid.
ALL the logic of rotating pieces, completing rows, are enforced by invoking methods on TetrisGrid. GraphicsGrid simply displays in a nice way what’s stored on the TetrisGrid.
Some other moving parts that you need to think about.
Something will need to eventually tell the GUI when a move has been performed and when rows have been completed (for scoring).
Hint: When telling the GraphicsGrid that a piece has moved, I would remove all the blocks that should be drawn from GraphicsGrid, retrieve an array (or ArrayList) of occupied blocks from TetrisGrid, and then re-add all the occupied blocks to GraphicsGrid. This takes care of ALL cases of rotating pieces, moving pieces, collapsed rows, etc. Sometimes the simple solution is best.
This is the GUI interface for the game. It needs to display scores (high score) present buttons and a speed slider, and indicate when a game is over.
Hint: Each time a New Game is requested, create a new TetrisGrid
The ShapeMover must move the Current Piece downwards on a timed interval. It must also respond to keystroke commands from the user. In other words, it needs to respond to keyboard events and run on a clock (think Thread, you can copy the Mover class in the GameGrid starter code and then modify). Some more Detailed requirements of the game are given below
Some hints on ShapeMover
When trying to move/rotate the current shape
- Remove it from the TetrisGrid
- Logically move it, or rotate it to define its new position on the Grid
- Test if the new position is valid
a. Test if the shape is inbounds
b. Test if the shape intersects any of existing block
- If the new position is valid, then place it on the TetrisGrid
- If the new position is not valid
a. Place the shape back at its original position/orientation
b. If it was a downward move, then the shape can’t move anymore
c. In this case, you then need to test if rows are now completed
- At the end of a valid move, the GraphicsGrid will need to be updated.
- When a shape stops moving, the game needs to be informed that a shape can’t be moved any more and that a new shape should be created.
- A Shape is an array of logical coordinates. It knows nothing about graphics. It can be told to rotated and report information about itself.
- The TetrisGrid is the bounded X-Y playing field. It is the place where shapes interact with existing (placed) blocks. Hence it can enforce the rules of the game.
- The ShapeMover is the automated mechanism of forcing the current shape to move downwards. The ShapeMover will attempt to move/rotate the shape.
- Tetris is the Graphical interface that sets up the All components. It responds to some user input
- GraphicsGrid is the graphical representation of TetrisGrid.
- Coord is a “helper” class.
- You will need to decide exactly how information is communicated among the various classes to achieve your goals. Ask questions and think about this for a while. Understanding how your classes should interact will make your debugging much faster