This assignment is the final assignment in a multi assignment project. It is don
ID: 3670560 • Letter: T
Question
This assignment is the final assignment in a multi assignment project. It is done in Visual Studio,. As you can see it is very basic, no graphics or separate classes are needed. Just clearing of the lines when they are full and scoring based on clearing the lines.
Thank you for any help!
***
You have already displayed the Tetris Bucket, started dropping the shapes, stopped them at the bottom of the Bucket, dropped another shape from the top, got the user input and moved/rotated the shape in response to the user input.
In this assignment, you need to:
Determine whether any of the rows are complete.
If a row is complete, remove that line and drop all the shapes above the line by one cell.
Compute and display the score.
Add plenty of narrative comments. Your program must be compilable and executable.
Here is my program to this point:
#include "stdafx.h"
#include <stdlib.h>
#include <iostream>
#include <time.h>
#include <windows.h>
using namespace std;
#define GAME_INTERVAL 2
#define GO_DOWN 2
#define GO_LEFT 4
#define GO_RIGHT 6
#define GO_ROTATE 5
class TetrisShape
{
public:
char shapeArray[4][4];
int shapeTopLeftX = 6;
int shapeTopLeftY = 0;
void populateShapeArray(int shape);
void rotate();
template <size_t rows, size_t cols>
void setShape(char(&shape)[rows][cols]);
TetrisShape(int shape) { populateShapeArray(shape); };
TetrisShape() {};
};
void TetrisShape::rotate()
{
char _shapeArray[4][4];
_shapeArray[0][0] = shapeArray[0][3]; _shapeArray[1][0] = shapeArray[0][2]; _shapeArray[2][0] = shapeArray[0][1]; _shapeArray[3][0] = shapeArray[0][0];
_shapeArray[0][1] = shapeArray[1][3]; _shapeArray[1][1] = shapeArray[1][2]; _shapeArray[2][1] = shapeArray[1][1]; _shapeArray[3][1] = shapeArray[1][0];
_shapeArray[0][2] = shapeArray[2][3]; _shapeArray[1][2] = shapeArray[2][2]; _shapeArray[2][2] = shapeArray[2][1]; _shapeArray[3][2] = shapeArray[2][0];
_shapeArray[0][3] = shapeArray[3][3]; _shapeArray[1][3] = shapeArray[3][2]; _shapeArray[2][3] = shapeArray[3][1]; _shapeArray[3][3] = shapeArray[3][0];
for (int _s = 0; _s < 4; _s++)
{
for (int _a = 0; _a < 4; _a++)
{
shapeArray[_s][_a] = _shapeArray[_s][_a];
}
}
}
void TetrisShape::populateShapeArray(int shape)
{
switch (shape)
{
case 1:
shapeArray[0][0] = ' '; shapeArray[1][0] = ' '; shapeArray[2][0] = ' '; shapeArray[3][0] = ' ';
shapeArray[0][1] = ' '; shapeArray[1][1] = 'X'; shapeArray[2][1] = ' '; shapeArray[3][1] = ' ';
shapeArray[0][2] = ' '; shapeArray[1][2] = 'X'; shapeArray[2][2] = ' '; shapeArray[3][2] = ' ';
shapeArray[0][3] = ' '; shapeArray[1][3] = 'X'; shapeArray[2][3] = 'X'; shapeArray[3][3] = ' ';
break;
case 2:
shapeArray[0][0] = ' '; shapeArray[1][0] = 'X'; shapeArray[2][0] = ' '; shapeArray[3][0] = ' ';
shapeArray[0][1] = ' '; shapeArray[1][1] = 'X'; shapeArray[2][1] = ' '; shapeArray[3][1] = ' ';
shapeArray[0][2] = ' '; shapeArray[1][2] = 'X'; shapeArray[2][2] = ' '; shapeArray[3][2] = ' ';
shapeArray[0][3] = ' '; shapeArray[1][3] = 'X'; shapeArray[2][3] = ' '; shapeArray[3][3] = ' ';
break;
case 3:
shapeArray[0][0] = ' '; shapeArray[1][0] = ' '; shapeArray[2][0] = ' '; shapeArray[3][0] = ' ';
shapeArray[0][1] = ' '; shapeArray[1][1] = 'X'; shapeArray[2][1] = ' '; shapeArray[3][1] = ' ';
shapeArray[0][2] = ' '; shapeArray[1][2] = 'X'; shapeArray[2][2] = 'X'; shapeArray[3][2] = ' ';
shapeArray[0][3] = ' '; shapeArray[1][3] = ' '; shapeArray[2][3] = 'X'; shapeArray[3][3] = ' ';
break;
case 4:
shapeArray[0][0] = ' '; shapeArray[1][0] = ' '; shapeArray[2][0] = ' '; shapeArray[3][0] = ' ';
shapeArray[0][1] = ' '; shapeArray[1][1] = ' '; shapeArray[2][1] = 'X'; shapeArray[3][1] = ' ';
shapeArray[0][2] = ' '; shapeArray[1][2] = 'X'; shapeArray[2][2] = 'X'; shapeArray[3][2] = ' ';
shapeArray[0][3] = ' '; shapeArray[1][3] = 'X'; shapeArray[2][3] = ' '; shapeArray[3][3] = ' ';
break;
case 5:
shapeArray[0][0] = ' '; shapeArray[1][0] = ' '; shapeArray[2][0] = ' '; shapeArray[3][0] = ' ';
shapeArray[0][1] = ' '; shapeArray[1][1] = ' '; shapeArray[2][1] = 'X'; shapeArray[3][1] = ' ';
shapeArray[0][2] = ' '; shapeArray[1][2] = ' '; shapeArray[2][2] = 'X'; shapeArray[3][2] = ' ';
shapeArray[0][3] = ' '; shapeArray[1][3] = 'X'; shapeArray[2][3] = 'X'; shapeArray[3][3] = ' ';
break;
case 6:
shapeArray[0][0] = ' '; shapeArray[1][0] = ' '; shapeArray[2][0] = ' '; shapeArray[3][0] = ' ';
shapeArray[0][1] = ' '; shapeArray[1][1] = ' '; shapeArray[2][1] = ' '; shapeArray[3][1] = ' ';
shapeArray[0][2] = ' '; shapeArray[1][2] = 'X'; shapeArray[2][2] = 'X'; shapeArray[3][2] = ' ';
shapeArray[0][3] = ' '; shapeArray[1][3] = 'X'; shapeArray[2][3] = 'X'; shapeArray[3][3] = ' ';
break;
}
}
int currentShape = -1; // this is the shape that is currently in play.
int x[4] = { -1, -1, -1, -1 };
int y[4] = { -1, -1, -1, -1 }; // the x, y location for each of the shapes.
bool isDropping = false; // gameTick globals.
int currentTick = 0;
template <size_t rows, size_t cols>
void generateBucket(char(&bucket)[rows][cols]); // create the bucket
void generateShapeStream(); // shapes, in drop and in queue, constantly
void dropShape(); // drop the shape
bool moveShape(int direction); // GO_DOWN (falling)
template <size_t rows, size_t cols>
bool gameTick(char(&bucket)[rows][cols], char(&perm_bucket)[rows][cols]); // handles game events
template <size_t rows, size_t cols>
void landShape(char(&bucket)[rows][cols]); // shape hitting the bottom
template <size_t rows, size_t cols>
void drawBucket(char(&bucket)[rows][cols]); // display bucket
int getUserInput(); // gets the key pressed from the user.
template <size_t rows, size_t cols>
bool canEnter(int direction, char(&bucket)[rows][cols]); // is space available for the block
void setCursorTo(int x, int y); // set cursor to appropriate position
int previousX = 6, previousY = 0;
int shapes[256];
TetrisShape activeShape;
int main()
{
// set 2 buckets, one permanent and one for display
char bucket[12][25];
char _bucket[12][25];
int shapes[256] = {};
int shapeIndex = 0;
bool gameOver = false;
generateBucket(bucket);
generateBucket(_bucket);
generateShapeStream();
drawBucket(bucket);
while (!gameOver)
{
gameOver = gameTick(bucket, _bucket);
Sleep(500);
currentTick++;
}
setCursorTo(25, 6);
cout << "GAME OVER" << endl;
system("pause");
}
void setCursorTo(int x, int y)
{
HANDLE handle;
COORD position;
handle = GetStdHandle(STD_OUTPUT_HANDLE);
position.X = x;
position.Y = y;
SetConsoleCursorPosition(handle, position);
}
/* generateBucket */
template <size_t rows, size_t cols>
void generateBucket(char(&bucket)[rows][cols])
{
for (int w = 0; w < 12; w++)
{
for (int z = 0; z < 25; z++)
{
if (((w == 0) || (w == 11)) && (z == 0))
{
bucket[w][z] = '.';
}
else if (((w % 12 == 0) || (w % 12 == 11)) && ((z > 0) && (z < 24)))
{
bucket[w][z] = '|';
}
else if (((w == 0) || (w == 11)) && (z == 24))
{
bucket[w][z] = '+';
}
else if (z == 24)
{
bucket[w][z] = '-';
}
else
{
bucket[w][z] = ' ';
}
}
}
}
/* generateShapeStream - generates the shapes preparing to fall */
void generateShapeStream()
{
// Initialize the random number generator
srand(time(NULL));
for (int p = 0; p < 256; p++)
{
shapes[p] = rand() % 6 + 1;
}
}
/* drawBucket -displays bucket and shapes to fall */
template <size_t rows, size_t cols>
void drawBucket(char(&bucket)[rows][cols])
{
setCursorTo(0, 0);
for (int m = 0; m < 25; m++)
{
for (int k = 0; k < 12; k++)
{
cout << bucket[k][m];
}
cout << endl;
}
}
/* gameTick - everything happening in game, and game over procedures*/
template <size_t rows, size_t cols>
bool gameTick(char(&bucket)[rows][cols], char(&perm_bucket)[rows][cols])
{
drawBucket(bucket);
if (!isDropping)
{
currentShape++;
activeShape = TetrisShape(shapes[currentShape]);
if (!canEnter(GO_DOWN, perm_bucket))
{
return true;
}
else
{
isDropping = true;
updateBucket(bucket, false);
}
}
else
{
if (currentTick % GAME_INTERVAL == 1)
{
// we are on a drop interval.
if (canEnter(GO_DOWN, perm_bucket))
{
updateBucket(bucket, moveShape(GO_DOWN));
}
else
{
landShape(perm_bucket);
}
}
}
int direction = getUserInput();
if (canEnter(direction, perm_bucket))
{
updateBucket(bucket, moveShape(direction));
}
if (!canEnter(GO_DOWN, perm_bucket))
{
landShape(perm_bucket);
}
return false;
}
/* moveShape - handles the shape dropping down. */
bool moveShape(int direction)
{
previousX = activeShape.shapeTopLeftX;
previousY = activeShape.shapeTopLeftY;
switch (direction)
{
case GO_DOWN:
activeShape.shapeTopLeftY++;
return false;
break;
case GO_RIGHT:
activeShape.shapeTopLeftX++;
return false;
break;
case GO_LEFT:
activeShape.shapeTopLeftX--;
return false;
break;
case GO_ROTATE:
activeShape.rotate();
return true;
break;
}
}
/* updateBucket - place new shape into bucket, and removing old shape*/
template <size_t rows, size_t cols>
void updateBucket(char(&bucket)[rows][cols], bool isRotation)
{
for (int _l = 0; _l < 4; _l++)
{
for (int _g = 0; _g < 4; _g++)
{
if (!isRotation)
{
if ((activeShape.shapeArray[_l][_g] != ' ') && (bucket[_l + previousX][_g + previousY] != '|') && (bucket[_l + previousX][_g + previousY] != '-'))
{
bucket[_l + previousX][_g + previousY] = ' ';
}
}
else {
if ((bucket[_l + previousX][_g + previousY] != '|') && (bucket[_l + previousX][_g + previousY] != '-'))
{
bucket[_l + previousX][_g + previousY] = ' ';
}
}
}
}
for (int _l = 0; _l < 4; _l++)
{
for (int _g = 0; _g < 4; _g++)
{
if (activeShape.shapeArray[_l][_g] != ' ')
{
bucket[_l + activeShape.shapeTopLeftX][_g + activeShape.shapeTopLeftY] = activeShape.shapeArray[_l][_g];
}
}
}
}
/* landShape - Sets the shape in place once it hits the bottom of the bucket. Moves the shape to the permanent bucket (_bucket)*/
template <size_t rows, size_t cols>
void landShape(char(&bucket)[rows][cols])
{
updateBucket(bucket, false);
previousX = 6; previousY = 0;
isDropping = false;
}
/* getUserInput - Reads the user input from the player*/
int getUserInput() {
setCursorTo(35, 9);
if ((GetKeyState(VK_DOWN) != 0) && (GetKeyState(VK_DOWN) != 1)) { return GO_DOWN; }
if ((GetKeyState(VK_RIGHT) != 0) && (GetKeyState(VK_RIGHT) != 1)) { return GO_RIGHT; }
if ((GetKeyState(VK_LEFT) != 0) && (GetKeyState(VK_LEFT) != 1)) { return GO_LEFT; }
if ((GetKeyState(VK_UP) != 0) && (GetKeyState(VK_UP) != 1)) { return GO_ROTATE; }
return 0;
}
/* canRotate - can we rotate? if we are adjacent to another shape NO! */
template <size_t rows, size_t cols>
bool canRotate(char(&bucket)[rows][cols])
{
// creating a copy of the shape, rotating it so we can determine where in the bucket it will land.
TetrisShape _tmp = TetrisShape(activeShape);
_tmp.rotate();
for (int _t = 0; _t < 4; _t++)
{
for (int _z = 0; _z < 4; _z++)
{
if (_tmp.shapeArray[_t][_z] != ' ')
{
if (bucket[_tmp.shapeTopLeftX + _t][_tmp.shapeTopLeftY + _z] != ' ')
{
return false;
}
}
}
}
return true;
}
/* canEnter - Tests the direction in which a shape can enter*/
template <size_t rows, size_t cols>
bool canEnter(int dir, char(&bucket)[rows][cols])
{
// Check for collisions between shapes or with the bucket
// Determining in which direction the shapes are moving
int delta_x = 0, delta_y = 0;
switch (dir)
{
case GO_DOWN:
delta_y++;
break;
case GO_LEFT:
delta_x--;
break;
case GO_RIGHT:
delta_x++;
break;
case GO_ROTATE:
return canRotate(bucket);
break;
}
// Create the starting {x, y} position to test for collision
int test_b = activeShape.shapeTopLeftX + delta_x;
int test_k = activeShape.shapeTopLeftY + delta_y;
for (int _b = 0; _b < 4; _b++)
{
for (int _k = 0; _k < 4; _k++)
{
if (activeShape.shapeArray[_b][_k] != ' ')
{
if (bucket[test_b + _b][test_k + _k] != ' ')
{
return false;
}
}
}
}
return true;
}
Explanation / Answer
Tetris
The Tetris game is one of the most popular computer games ever created. The original game was designed and programmed by a Russian programmer Alexey Pajitnov in 1985. Since then, Tetris is available on almost every computer platform in lots of variations. Even my mobile phone has a modified version of the Tetris game.
Tetris is called a falling block puzzle game. In this game, we have seven different shapes called tetrominoes. S-shape, Z-shape, T-shape, L-shape, Line-shape, MirroredL-shape and a Square-shape. Each of these shapes is formed with four squares. The shapes are falling down the board. The object of the Tetris game is to move and rotate the shapes, so that they fit as much as possible. If we manage to form a row, the row is destroyed and we score. We play the tetris game until we top out.
/*
Tetris Applet
*/
import java.awt.*;
import java.applet.*;
import java.awt.event.*;
import java.util.Random;
import java.net.URL;
import java.net.MalformedURLException;
public class Tetris extends Applet {
//
// STATIC MEMBERS
//
private final static int INITIAL_DELAY = 1000;
private final static byte ROWS = 18;
private final static byte COLUMNS = 10;
private final static int EMPTY = -1;
private final static int DELETED_ROWS_PER_LEVEL = 5;
private final static Color PIECE_COLORS[] = {
new Color(0xFF00FF), // fucia
new Color(0xDC143C), // crimson
new Color(0x00CED1), // dark turquoise
new Color(0xFFD700), // gold
new Color(0x32CD32), // lime green
new Color(0x008080), // teal
new Color(0xFFA500), // orange
};
private final static Color BACKGROUND_COLORS[] = {
new Color(0xFFDAB9), // peachpuff
new Color(0xFFC0CB), // pink
new Color(0xFF99CC), // hot pink
new Color(0x0099CC), // sky blue
new Color(0x9966CC), // lavender
};
private final static Color BACKGROUND_COLOR = new Color(0x99FFCC);
// * ** * * * *
// * * * ** ** ** **
// * * ** * * * **
// *
// 0 1 2 3 4 5 6
private final static boolean PIECE_BITS[][][] = {
{
{false, true, false, false},
{false, true, false, false},
{false, true, false, false},
{false, true, false, false},
},
{
{false, false, false, false},
{false, true, true, false},
{false, true, false, false},
{false, true, false, false},
},
{
{false, false, false, false},
{false, true, false, false},
{false, true, false, false},
{false, true, true, false},
},
{
{false, false, false, false},
{false, true, false, false},
{false, true, true, false},
{false, false, true, false},
},
{
{false, false, false, false},
{false, false, true, false},
{false, true, true, false},
{false, true, false, false},
},
{
{false, false, false, false},
{false, true, false, false},
{false, true, true, false},
{false, true, false, false},
},
{
{false, false, false, false},
{false, false, false, false},
{false, true, true, false},
{false, true, true, false},
},
};
private static boolean tmp_grid[][] = new boolean[4][4]; // scratch space
private static Random random = new Random();
private static class TetrisLabel extends Label {
private final static Font LABEL_FONT = new Font("Serif", Font.BOLD, 18);
private TetrisLabel(String text) {
super(text);
setFont(LABEL_FONT);
}
private void addValue(int val) {
setText(Integer.toString((Integer.parseInt(getText())) + val ));
}
}
//
// INSTANCE DATA
//
private int grid[][] = new int[ROWS][COLUMNS];
private int next_piece_grid[][] = new int[4][4];
private int num_rows_deleted = 0;
private GridCanvas game_grid = new GridCanvas(grid, true);
private GridCanvas next_piece_canvas = new GridCanvas(next_piece_grid, false);
private Timer timer;
private TetrisPiece cur_piece;
private TetrisPiece next_piece = randomPiece();
private TetrisSound sounds;// = new TetrisSound(this);
private TetrisLabel rows_deleted_label = new TetrisLabel("0");
private TetrisLabel level_label = new TetrisLabel("1");
private TetrisLabel score_label = new TetrisLabel("0");
private TetrisLabel high_score_label = new TetrisLabel("");
final Button start_newgame_butt = new TetrisButton("Start");
final Button pause_resume_butt = new TetrisButton("Pause");
//
// INNER CLASSES
//
private class TetrisButton extends Button {
public TetrisButton(String label) {
super(label);
}
public Dimension getPreferredSize() {
return new Dimension(120, super.getPreferredSize().height);
}
}
private class TetrisPiece {
private boolean squares[][];
private int type;
private Point position = new Point(3, -4); // -4 to start above top row
public int getX() { return position.x; }
public int getY() { return position.y; }
public void setX(int newx) { position.x = newx; }
public void setY(int newy) { position.y = newy; }
public void setPosition(int newx, int newy) { setX(newx); setY(newy); }
public TetrisPiece(int type) {
this.type = type;
this.squares = new boolean[4][4];
for(int i=0; i<4; i++)
for(int j=0; j<4; j++)
this.squares[i][j] = PIECE_BITS[type][i][j];
}
public boolean canStepDown() {
synchronized(timer) {
cut();
position.y++;
boolean OK = canPaste();
position.y--;
paste();
return OK;
}
}
public boolean canPaste() {
for(int i=0; i<4; i++) {
for(int j=0; j<4; j++) {
int to_x = j + position.x;
int to_y = i + position.y;
if(squares[i][j]) { // piece contains this square?
if(0 > to_x || to_x >= COLUMNS // square too far left or right?
|| to_y >= ROWS) // square off bottom?
{
return false;
// note: it's always considered OK to paste a square
// *above* the grid though attempting to do so does nothing.
// This allows the user to move a piece before it drops
// completely into view.
}
if(to_y >= 0 && grid[to_y][to_x] != EMPTY)
return false;
}
}
}
return true;
}
public void stepDown() {
position.y++;
}
public void cut() {
for(int i=0; i<4; i++)
for(int j=0; j<4; j++)
if(squares[i][j] && position.y+i>=0)
grid[position.y + i][position.x + j] = EMPTY;
}
/**
* Paste the color info of this piece into the given grid
*/
public void paste(int into[][]) {
for(int i=0; i<4; i++)
for(int j=0; j<4; j++)
if(squares[i][j] && position.y+i>=0)
into[position.y + i][position.x + j] = type;
}
/**
* No argument version assumes pasting into main game grid
*/
public void paste() {
paste(grid);
}
public void rotate() {
// copy the piece's data into a temp array
for(int i=0; i<4; i++)
for(int j=0; j<4; j++)
tmp_grid[i][j] = squares[i][j];
// copy back rotated 90 degrees
for(int i=0; i<4; i++)
for(int j=0; j<4; j++)
squares[j][i] = tmp_grid[i][3-j];
}
public void rotateBack() {
// copy originally saved version back
// this of course assumes this call was preceeded by
// a call to rotate() for the same piece
for(int i=0; i<4; i++)
for(int j=0; j<4; j++)
squares[i][j] = tmp_grid[i][j];
}
// this method is a bit of a hack to check for the case
// where a piece may be safely on the grid but have one or more
// rows of empty squares that are above the grid and therefore OK
public boolean isTotallyOnGrid() {
for(int i=0; i<4; i++) {
if(position.y + i >= 0)
return true; //everything from here down is on grid
// this row is above grid so look for non-empty squares
for(int j=0; j<4; j++)
if(squares[i][j])
return false;
}
System.err.println("TetrisPiece.isTotallyOnGrid internal error");
return false;
}
} // end class TetrisPiece
private class Timer extends Thread {
private long m_delay;
private boolean m_paused = true;
private boolean m_fast = false;
private ActionListener m_cb;
public Timer(long delay, ActionListener cb) {
setDelay(delay);
m_cb = cb;
}
public void setPaused(boolean pause) {
m_paused = pause;
if(m_paused) {
sounds.stopSoundtrack();
}
else {
sounds.playSoundtrack();
synchronized(this) {
this.notify();
}
}
}
public boolean isPaused() { return m_paused; }
public void setDelay(long delay) { m_delay = delay; }
public boolean isRunning() { return !m_paused; }
public void setFast(boolean fast) {
m_fast = fast;
if(m_fast) {
try {
this.checkAccess();
this.interrupt(); // no exception, so OK to interrupt
} catch(SecurityException se) {}
}
}
public boolean isFast() { return m_fast; }
public void faster() {
m_delay = (int)(m_delay * .9); //increase the speed exponentially in reverse
}
public void run() {
while(true) {
try {
sleep(m_fast ? 30 : m_delay);
} catch (Exception e) {}
if(m_paused) {
try {
synchronized(this) {
this.wait();
}
} catch(InterruptedException ie) {}
}
synchronized(this) {
m_cb.actionPerformed(null);
}
}
}
} // end class Timer
private class GridCanvas extends DoubleBufferedCanvas {
private int grid[][];
private boolean paint_background;
public GridCanvas(int[][] grid, boolean do_background) {
this.grid = grid;
paint_background = do_background;
clear();
}
private void clear() {
for(int i=0; i<grid.length; i++)
for(int j=0; j<grid[0].length; j++)
grid[i][j] = EMPTY;
}
public Dimension getPreferredSize() {
return new Dimension(grid[0].length * 30, grid.length * 30);
}
public void paint(Graphics g) {
g = this.startPaint(g); // returned g paints into offscreen image
int width = this.getSize().width;
int height = this.getSize().height;
g.clearRect(0, 0, width, height);
int cell_size, xstart, ystart;
double panel_aspect_ratio = (double)width/height;
double grid_aspect_ratio = (double)grid[0].length/grid.length;
if(panel_aspect_ratio > grid_aspect_ratio) {
// extra space on sides
cell_size = (int)((double)height/grid.length + 0.5);
xstart = (int)(width/2 - (grid[0].length/2.0 * cell_size + 0.5));
ystart = 0;
}
else {
// extra vertical space
cell_size = (int)((double)width/grid[0].length + 0.5);
xstart = 0;
ystart = (int)(height/2 - (grid.length/2.0 * cell_size + 0.5));
}
if(paint_background) {
g.setColor(BACKGROUND_COLORS[(num_rows_deleted / DELETED_ROWS_PER_LEVEL) % BACKGROUND_COLORS.length]);
g.fillRect(xstart, ystart, COLUMNS*cell_size, ROWS*cell_size);
}
for(int i=0; i<grid.length; i++) {
for(int j=0; j<grid[0].length; j++) {
if(grid[i][j] != EMPTY) {
g.setColor(PIECE_COLORS[grid[i][j]]);
int x = xstart + j*cell_size;
int y = ystart + i*cell_size;
g.fill3DRect(x, y, cell_size, cell_size, true);
}
}
}
this.endPaint(); // paints accumulated image in one shot
}
} // end class GridCanvas
private class TetrisSound {
private AudioClip soundTrack = null;
private AudioClip destroyRowSounds[] = new AudioClip[4];
private AudioClip gameOverSound = null;
private AudioClip getClip(String name) throws MalformedURLException {
URL soundFileUrl = new URL(getCodeBase(), name);
try {
AudioClip clip = getAudioClip(soundFileUrl);
return clip;
} catch(NullPointerException npe) {
System.err.println("exception " + npe);
return null;
}
}
public TetrisSound() {
//load sound files
try {
soundTrack = getClip("theme.au");
destroyRowSounds[0] = getClip("quiteImpressive.au");
destroyRowSounds[1] = getClip("smashing.au");
destroyRowSounds[2] = getClip("yeahbaby.au");
destroyRowSounds[3] = getClip("great.au");
gameOverSound = getClip("groovy.au");
}
catch (Exception e) {
System.err.println(e.getMessage());
}
}
public void playSoundtrack() {
if(soundTrack == null)
return;
soundTrack.loop();
}
public void playDestroyRows(int rows) {
int soundid = rows - 1;
if(0 > soundid || soundid >= destroyRowSounds.length || destroyRowSounds[soundid] == null)
return;
destroyRowSounds[soundid].play();
}
public void playGameOverSound() {
if(gameOverSound == null)
return;
gameOverSound.play();
}
public void stopSoundtrack() {
if(soundTrack == null)
return;
soundTrack.stop();
}
} // end class TetrisSound
//
// INSTANCE METHODS
//
private TetrisPiece randomPiece() {
int rand = Math.abs(random.nextInt());
return new TetrisPiece(rand % (PIECE_COLORS.length));
}
private void installNewPiece() {
next_piece_canvas.clear();
cur_piece = next_piece;
cur_piece.setPosition(3, -4); //-4 to start above top of grid
if(cur_piece.canPaste()) {
next_piece = randomPiece();
next_piece.setPosition(0, 0);
next_piece.paste(next_piece_grid);
next_piece_canvas.repaint();
}
else
gameOver();
}
private void gameOver() {
System.out.println("Game Over!");
timer.setPaused(true);
pause_resume_butt.setEnabled(false);
int score = Integer.parseInt(score_label.getText());
int high_score = high_score_label.getText().length() > 0 ?
Integer.parseInt(high_score_label.getText()) : 0;
if(score > high_score)
high_score_label.setText("" + score);
sounds.playGameOverSound();
}
private boolean rowIsFull(int row) {
for(int i=0; i<COLUMNS; i++)
if(grid[row][i] == EMPTY)
return false;
return true;
}
private int countFullRows() {
int n_full_rows = 0;
for(int i=0; i<ROWS; i++)
if(rowIsFull(i))
n_full_rows++;
return n_full_rows;
}
private void removeRow(int row) {
for(int j=0; j<COLUMNS; j++)
grid[row][j] = EMPTY;
for(int i=row; i>0; i--) {
for(int j=0; j<COLUMNS; j++) {
grid[i][j] = grid[i-1][j];
}
}
}
private void removeFullRows() {
int n_full = countFullRows();
score_label.addValue((int)(10 * Math.pow(2, n_full) - 10)); //give points exponentially
if(n_full == 0)
return;
sounds.playDestroyRows(n_full);
if(num_rows_deleted / DELETED_ROWS_PER_LEVEL != (num_rows_deleted+n_full) / DELETED_ROWS_PER_LEVEL) {
timer.faster();
level_label.addValue(n_full / DELETED_ROWS_PER_LEVEL + 1);
level_label.repaint();
}
rows_deleted_label.addValue(n_full);
num_rows_deleted += n_full;
for(int i=ROWS-1; i>=0; i--)
while(rowIsFull(i))
removeRow(i);
game_grid.repaint();
}
public void start() {
timer = new Timer(INITIAL_DELAY, new ActionListener() {
public void actionPerformed(ActionEvent ae) {
synchronized(timer) {
if(cur_piece.canStepDown()) {
cur_piece.cut();
cur_piece.stepDown();
cur_piece.paste();
if(timer.isFast())
score_label.addValue(1); // a small reward for using fast mode
}
else { // it hit something
timer.setFast(false);
if( ! cur_piece.isTotallyOnGrid())
gameOver();
else {
removeFullRows();
installNewPiece();
}
}
}
game_grid.repaint();
}
});
timer.start(); // pauses immediately
}
public void stop() {
pauseGame();
synchronized(timer){
timer.stop();
}
timer = null;
}
private void startGame() {
timer.setDelay(INITIAL_DELAY);
timer.setPaused(false);
start_newgame_butt.setLabel("Start New Game");
pause_resume_butt.setEnabled(true); // stays enabled from here on
pause_resume_butt.setLabel("Pause");
pause_resume_butt.validate();
sounds.playSoundtrack();
}
private void newGame() {
game_grid.clear();
installNewPiece();
num_rows_deleted = 0;
rows_deleted_label.setText("0");
level_label.setText("1");
score_label.setText("0");
startGame();
}
private void pauseGame() {
timer.setPaused(true);
pause_resume_butt.setLabel("Resume");
sounds.stopSoundtrack();
}
private void resumeGame() {
timer.setPaused(false);
pause_resume_butt.setLabel("Pause");
sounds.playSoundtrack();
}
public void init() {
sounds = new TetrisSound(); // NOTE: Must be initialized after Applet fully constructed!
installNewPiece();
pause_resume_butt.setEnabled(false);
start_newgame_butt.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
if(start_newgame_butt.getLabel().equals("Start"))
startGame();
else
newGame();
}
});
pause_resume_butt.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
if(pause_resume_butt.getLabel().equals("Pause"))
pauseGame();
else
resumeGame();
}
});
//create key listener for rotating, moving left, moving right
KeyListener key_listener = new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if(timer.isPaused()) //don't do anything if game is paused
return;
if (e.getKeyCode() == 37 || e.getKeyCode() == 39) { //left or right arrow pressed
int dir = e.getKeyCode() == 37 ? -1 : 1;
synchronized(timer) {
cur_piece.cut();
cur_piece.setX(cur_piece.getX() + dir); // try to move
if( ! cur_piece.canPaste())
cur_piece.setX(cur_piece.getX() - dir); // undo move
cur_piece.paste();
}
game_grid.repaint();
}
else if (e.getKeyCode() == 38) { //rotate
synchronized(timer) {
cur_piece.cut();
cur_piece.rotate();
if( ! cur_piece.canPaste())
cur_piece.rotateBack();
cur_piece.paste();
}
game_grid.repaint();
}
if (e.getKeyCode() == 40) { //down arrow pressed; drop piece
timer.setFast(true);
}
}
};
// add the key listener to all components that might get focus
// so that it'll work regardless of which has focus
start_newgame_butt.addKeyListener(key_listener);
pause_resume_butt.addKeyListener(key_listener);
Panel right_panel = new Panel(new GridLayout(3, 1));
right_panel.setBackground(BACKGROUND_COLOR);
Panel control_panel = new Panel();
control_panel.add(start_newgame_butt);
control_panel.add(pause_resume_butt);
control_panel.setBackground(BACKGROUND_COLOR);
right_panel.add(control_panel);
Panel tmp = new Panel(new BorderLayout());
tmp.add("North", new TetrisLabel(" Next Piece:"));
tmp.add("Center", next_piece_canvas);
tmp.setBackground(BACKGROUND_COLOR);
right_panel.add(tmp);
Panel stats_panel = new Panel(new GridLayout(4, 2));
stats_panel.add(new TetrisLabel(" Rows Deleted: "));
stats_panel.add(rows_deleted_label);
stats_panel.add(new TetrisLabel(" Level: "));
stats_panel.add(level_label);
stats_panel.add(new TetrisLabel(" Score: "));
stats_panel.add(score_label);
stats_panel.add(new TetrisLabel(" High Score: "));
stats_panel.add(high_score_label);
tmp = new Panel(new BorderLayout());
tmp.setBackground(BACKGROUND_COLOR);
tmp.add("Center", stats_panel);
right_panel.add(tmp);
// finaly, add all the main panels to the applet panel
this.setLayout(new GridLayout(1, 2));
this.add(game_grid);
this.add(right_panel);
this.setBackground(BACKGROUND_COLOR);
this.validate();
}
public static void main(String[] args) {
Frame frame = new Frame("Tetris");
Tetris tetris = new Tetris();
frame.add(tetris);
tetris.init();
tetris.start();
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
frame.setSize(489, 441);
frame.setResizable(false);
frame.setVisible(true);
}
} // end class Tetris
class DoubleBufferedCanvas extends Canvas {
private Image mActiveOffscreenImage = null;
private Dimension mOffscreenSize = new Dimension(-1,-1);
private Graphics mActiveOffscreenGraphics = null;
private Graphics mSystemGraphics = null;
DoubleBufferedCanvas() {
/*
this.addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
repaint();
}
});*/
}
/**
* NOTE: when extending applets:
* this overrides update() to *not* erase the background before painting
*/
public void update(Graphics g) {
paint(g);
}
public Graphics startPaint (Graphics sysgraph) {
mSystemGraphics = sysgraph;
// Initialize if this is the first pass or the size has changed
Dimension d = getSize();
if ((mActiveOffscreenImage == null) ||
(d.width != mOffscreenSize.width) ||
(d.height != mOffscreenSize.height))
{
mActiveOffscreenImage = createImage(d.width, d.height);
mActiveOffscreenGraphics = mActiveOffscreenImage.getGraphics();
mOffscreenSize = d;
mActiveOffscreenGraphics.setFont(getFont());
}
//mActiveOffscreenGraphics.clearRect(0, 0, mOffscreenSize.width, mOffscreenSize.height);
return mActiveOffscreenGraphics;
}
public void endPaint () {
// Start copying the offscreen image to this canvas
// The application will begin drawing into the other one while this happens
mSystemGraphics.drawImage(mActiveOffscreenImage, 0, 0, null);
}
}