Tetris Game in Java

Tetris Game in Java Tetris Game in Java Today, we are going to learn how to build the classic Tetris Game in Java with Swing. The game requires the player to rotate and move falling Tetris pieces to an appropriate position so that the player can fill the entire row without a gap, once the row is filled it's automatically cleared and the score increases. But, if the pieces reach the top, the game is over!

Today, we are going to learn how to build the classic Tetris Game in Java with Swing. The game requires the player to rotate and move falling Tetris pieces to an appropriate position so that the player can fill the entire row without a gap, once the row is filled it’s automatically cleared and the score increases. But, if the pieces reach the top, the game is over!

By creating this game we will learn the concepts like creating graphics with the help of Swing, game loops, and collision detection. Ultimately, we have a fully functional game with a beautiful UI.

Project Overview: Tetris Game in Java

Project Name:Tetris Game in Java
Abstract:It’s a GUI-based project used with the swing library to organize all the elements that work under the Tetris Game.
Language Used:Java
IDE:VS Code
Java version (Recommended):Java SE 18.0. 2.1
Database:None
Type:Desktop Application
Recommended for:Beginners of Java
Time to build:1-2 hours

What will you learn? 

  • Building the logic of the game with the help of conditionals and loops
  • Learn the OOPs paradigm with the help of classes, objects, and inheritance
  • Creating pleasing graphics with the help of Java Swing and Java AWT

Features:

  • The player will be able to rotate the pieces left and right by pressing the up and down key
  • The player will be able to move the pieces left and right by pressing the left and right key
  • To quickly fall down a piece, the player can use the Space Bar key
  • The score is updated when a row gets cleared

Complete Code for Tetris Game in Java

Create a folder for the project and a file in it named Tetris.java. Now, copy the below lines of code in the file. Comments are provided for explanation.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.Timer;
// this is class for all the shapes and their rotations
class Shape {
Tetrominoe pieceShape; // the shape of the piece
int coords[][]; // the coordinates of the piece
int[][][] coordsTable; // the coordinates of the piece in all its rotations
public Shape() {
initShape(); // initialize the shape
}
void initShape() {
coords = new int[4][2]; // initialize the size of the piece
setShape(Tetrominoe.NoShape); // set the shape to NoShape
}
// set the shape of the piece to the given shape and set the coordinates of the piece to the coordinates of the given shape
protected void setShape(Tetrominoe shape) {
coordsTable = new int[][][] {
{ { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } },
{ { 0, -1 }, { 0, 0 }, { -1, 0 }, { -1, 1 } },
{ { 0, -1 }, { 0, 0 }, { 1, 0 }, { 1, 1 } },
{ { 0, -1 }, { 0, 0 }, { 0, 1 }, { 0, 2 } },
{ { -1, 0 }, { 0, 0 }, { 1, 0 }, { 0, 1 } },
{ { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } },
{ { -1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } },
{ { 1, -1 }, { 0, -1 }, { 0, 0 }, { 0, 1 } }
};
for (int i = 0; i < 4 ; i++) { // for the no of rows in the shape
for (int j = 0; j < 2; ++j) { // for the no of columns in the shape
coords[i][j] = coordsTable[shape.ordinal()][i][j]; // set the coordinates of the piece to the coordinates of the given shape
}
}
pieceShape = shape;
}
void setX(int index, int x) { coords[index][0] = x; } // set the x coordinate of the piece
void setY(int index, int y) { coords[index][1] = y; } // set the y coordinate of the piece
public int x(int index) { return coords[index][0]; } // get the x coordinate of the piece
public int y(int index) { return coords[index][1]; } // get the y coordinate of the piece
public Tetrominoe getShape() { return pieceShape; } // get the shape of the piece
// set the shape of the piece to a random shape everytime a new piece is created
public void setRandomShape() {
Random r = new Random(); // create a random object
int x = Math.abs(r.nextInt()) % 7 + 1; // get a random number between 1 and 7
Tetrominoe[] values = Tetrominoe.values(); // get all the shapes
setShape(values[x]); // set the shape of the piece to a random shape
}
// get the minimum x coordinate of the piece
public int minX() {
int m = coords[0][0];
for (int i=0; i < 4; i++) { // for the no of rows in the shape
m = Math.min(m, coords[i][0]); // get the minimum x coordinate of the piece
}
return m; // return the minimum x coordinate of the piece
}
// get the minimum y coordinate of the piece
public int minY() {
int m = coords[0][1];
for (int i=0; i < 4; i++) { // for the no of rows in the shape
m = Math.min(m, coords[i][1]); // get the minimum y coordinate of the piece
}
return m; // return the minimum y coordinate of the piece
}
// rotate the piece to the left
public Shape rotateLeft() {
if (pieceShape == Tetrominoe.SquareShape) // if the shape is a square shape
return this; // return the shape without rotation
Shape result = new Shape(); // create a new shape
result.pieceShape = pieceShape; // set the shape of the new shape to the shape of the piece
for (int i = 0; i < 4; ++i) { // for the no of rows in the shape
result.setX(i, y(i)); // set the x coordinate of the new shape to the y coordinate of the piece
result.setY(i, -x(i)); // set the y coordinate of the new shape to the negative x coordinate of the piece
}
return result; // return the new shape
}
// rotate the piece to the right
public Shape rotateRight() {
if (pieceShape == Tetrominoe.SquareShape) // if the shape is a square shape
return this; // return the shape without rotation
Shape result = new Shape(); // create a new shape
result.pieceShape = pieceShape; // set the shape of the new shape to the shape of the piece
for (int i = 0; i < 4; ++i) { // for the no of rows in the shape
result.setX(i, -y(i)); // set the x coordinate of the new shape to the negative y coordinate of the piece
result.setY(i, x(i)); // set the y coordinate of the new shape to the x coordinate of the piece
}
return result; // return the new shape
}
}
enum Tetrominoe { NoShape, ZShape, SShape, LineShape,
TShape, SquareShape, LShape, MirroredLShape };
// this is the class for the board
class Board extends JPanel {
static final long serialVersionUID = 1L;
final int BOARD_WIDTH = 10; // the width of the board
final int BOARD_HEIGHT = 22; // the height of the board
final int INITIAL_DELAY = 100; // the initial delay of the timer
final int PERIOD_INTERVAL = 300; // the period interval of the timer
Timer timer;
boolean isFallingFinished = false; // check if the piece has finished falling
boolean isStarted = false; // check if the game has started
boolean isPaused = false; // check if the game is paused
int numLinesRemoved = 0; // the number of lines removed
int curX = 0; // the current x coordinate of the piece
int curY = 0; // the current y coordinate of the piece
JLabel statusbar;
Shape curPiece;
Tetrominoe[] board;
public Board(Tetris parent) {
initBoard(parent);
}
// initialize the board
void initBoard(Tetris parent) {
setFocusable(true);
setBorder(BorderFactory.createLineBorder(Color.pink, 4));
timer = new Timer();
timer.scheduleAtFixedRate(new ScheduleTask(),
INITIAL_DELAY, PERIOD_INTERVAL);
curPiece = new Shape();
statusbar = parent.getStatusBar();
board = new Tetrominoe[BOARD_WIDTH * BOARD_HEIGHT];
addKeyListener(new TAdapter());
clearBoard();
}
int squareWidth() {
return (int) getSize().getWidth() / BOARD_WIDTH;
}
int squareHeight() {
return (int) getSize().getHeight() / BOARD_HEIGHT;
}
Tetrominoe shapeAt(int x, int y) {
return board[(y * BOARD_WIDTH) + x];
}
public void start() {
isStarted = true;
clearBoard();
newPiece();
}
void pause() {
if (!isStarted) {
return;
}
isPaused = !isPaused;
if (isPaused) {
statusbar.setText("Paused");
} else {
statusbar.setText(String.valueOf(numLinesRemoved));
}
}
// draw the board
void doDrawing(Graphics g) {
Dimension size = getSize();
int boardTop = (int) size.getHeight() - BOARD_HEIGHT * squareHeight();
for (int i = 0; i < BOARD_HEIGHT; ++i) {
for (int j = 0; j < BOARD_WIDTH; ++j) {
Tetrominoe shape = shapeAt(j, BOARD_HEIGHT - i - 1);
if (shape != Tetrominoe.NoShape) {
drawSquare(g, 0 + j * squareWidth(),
boardTop + i * squareHeight(), shape);
}
}
}
if (curPiece.getShape() != Tetrominoe.NoShape) { // if the shape of the piece is not a no shape
for (int i = 0; i < 4; ++i) { // for the no of rows in the shape
int x = curX + curPiece.x(i); // get the x coordinate of the piece
int y = curY - curPiece.y(i); // get the y coordinate of the piece
drawSquare(g, 0 + x * squareWidth(),
boardTop + (BOARD_HEIGHT - y - 1) * squareHeight(),
curPiece.getShape()); // draw the piece on the board
}
}
}
// draw the square on the board
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
// draw the new coordinates of the piece while falling down
void dropDown() {
int newY = curY; // set the new y coordinate to the current y coordinate
while (newY > 0) { // while the new y coordinate is greater than 0
if (!tryMove(curPiece, curX, newY - 1)) { // if the piece cannot move to the new coordinates
break; // break the loop
}
--newY; // decrement the new y coordinate
}
pieceDropped(); // call the piece dropped method
}
// draw the new coordinates of the piece while moving
void oneLineDown() {
if (!tryMove(curPiece, curX, curY - 1)) {
pieceDropped();
}
}
void clearBoard() {
for (int i = 0; i < BOARD_HEIGHT * BOARD_WIDTH; ++i) {
board[i] = Tetrominoe.NoShape;
}
}
void pieceDropped() {
for (int i = 0; i < 4; ++i) {
int x = curX + curPiece.x(i);
int y = curY - curPiece.y(i);
board[(y * BOARD_WIDTH) + x] = curPiece.getShape();
}
removeFullLines(); // remove the full lines
if (!isFallingFinished) { // if the piece has not finished falling
newPiece(); // create a new piece
}
}
// create a new piece
void newPiece() {
curPiece.setRandomShape(); // set the shape of the piece to a random shape
curX = BOARD_WIDTH / 2 + 1; // set the x coordinate of the piece to the middle of the board
curY = BOARD_HEIGHT - 1 + curPiece.minY();
if (!tryMove(curPiece, curX, curY)) { // if the piece cannot move to the new coordinates
curPiece.setShape(Tetrominoe.NoShape); // set the shape of the piece to a no shape
timer.cancel();
isStarted = false;
statusbar.setText("GAME OVER!");
}
}
// check if the piece can move to the new coordinates
boolean tryMove(Shape newPiece, int newX, int newY) {
for (int i = 0; i < 4; ++i) {
int x = newX + newPiece.x(i);
int y = newY - newPiece.y(i);
if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT) { // if the new coordinates are out of the board
return false; // return false
}
if (shapeAt(x, y) != Tetrominoe.NoShape) { // if the new coordinates are not a no shape
return false; // return false
}
}
curPiece = newPiece; // set the current piece to the new piece
curX = newX; // set the current x coordinate to the new x coordinate
curY = newY; // set the current y coordinate to the new y coordinate
repaint(); // repaint the board with the new coordinates of the piece
return true; // return true
}
// remove the full line from the board
void removeFullLines() {
int numFullLines = 0; // set the number of full lines to 0
for (int i = BOARD_HEIGHT - 1; i >= 0; --i) { // for the no of rows in the board
boolean lineIsFull = true; // set the line is full to true
for (int j = 0; j < BOARD_WIDTH; ++j) { // for the no of columns in the board
if (shapeAt(j, i) == Tetrominoe.NoShape) { // if the shape at the coordinates is a no shape
lineIsFull = false; // set the line is full to false
break; // break the loop
}
}
if (lineIsFull) { // if the line is full
++numFullLines; // increment the number of full lines
for (int k = i; k < BOARD_HEIGHT - 1; ++k) { // for the no of rows in the board
for (int j = 0; j < BOARD_WIDTH; ++j) { // for the no of columns in the board
board[(k * BOARD_WIDTH) + j] = shapeAt(j, k + 1); // set the shape at the coordinates to the shape at the coordinates below
}
}
}
}
if (numFullLines > 0) { // if the number of full lines is greater than 0
numLinesRemoved += numFullLines; // increment the number of lines removed by the number of full lines
statusbar.setText("Score: "+String.valueOf(numLinesRemoved)); // set the text of the score to the number of lines removed
isFallingFinished = true; // set the piece has finished falling to true
curPiece.setShape(Tetrominoe.NoShape); // set the shape of the piece to a no shape
repaint();
}
}
// draw the square on the board
void drawSquare(Graphics g, int x, int y,
Tetrominoe shape) {
Color colors[] = {
new Color(0, 0, 0), new Color(204, 102, 102),
new Color(102, 204, 102), new Color(102, 102, 204),
new Color(204, 204, 102), new Color(204, 102, 204),
new Color(102, 204, 204), new Color(218, 170, 0),
};
Color color = colors[shape.ordinal()];
g.setColor(color);
g.fillRect(x + 1, y + 1, squareWidth() - 2, squareHeight() - 2);
g.setColor(color.brighter());
g.drawLine(x, y + squareHeight() - 1, x, y);
g.drawLine(x, y, x + squareWidth() - 1, y);
g.setColor(color.darker());
g.drawLine(x + 1, y + squareHeight() - 1,
x + squareWidth() - 1, y + squareHeight() - 1);
g.drawLine(x + squareWidth() - 1, y + squareHeight() - 1,
x + squareWidth() - 1, y + 1);
}
void doGameCycle() {
update();
repaint();
}
void update() {
if (isPaused) { // if the game is paused
return; // return
}
if (isFallingFinished) { // if the piece has finished falling
isFallingFinished = false; // set the piece has finished falling to false
newPiece(); // create a new piece
} else {
oneLineDown(); // move the piece down one line
}
}
// used to check the pressed keys
class TAdapter extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
if (!isStarted || curPiece.getShape() == Tetrominoe.NoShape) { // if the game has not started or the shape of the piece is a no shape
return; // return
}
int keycode = e.getKeyCode(); // get the key code of the pressed key
if (keycode == KeyEvent.VK_ENTER) { // if the pressed key is the enter key
pause(); // pause the game
return; // return
}
if (isPaused) {
return;
}
switch (keycode) { // switch the key code of the pressed key
case KeyEvent.VK_LEFT: // if the pressed key is the left arrow
tryMove(curPiece, curX - 1, curY); // try to move the piece to the left
break;
case KeyEvent.VK_RIGHT: // if the pressed key is the right arrow
tryMove(curPiece, curX + 1, curY); // try to move the piece to the right
break;
case KeyEvent.VK_DOWN: // if the pressed key is the down arrow
tryMove(curPiece.rotateRight(), curX, curY); // try to rotate the piece to the right
break;
case KeyEvent.VK_UP: // if the pressed key is the up arrow
tryMove(curPiece.rotateLeft(), curX, curY); // try to rotate the piece to the left
break;
case KeyEvent.VK_SPACE: // if the pressed key is the space bar
dropDown(); // drop the piece down
break;
case KeyEvent.VK_D: // if the pressed key is the d key
oneLineDown(); // move the piece down one line
break;
}
}
}
class ScheduleTask extends TimerTask {
@Override
public void run() {
doGameCycle();
}
}
}
// main class of the game
class Tetris extends JFrame {
static final long serialVersionUID = 1L;
JLabel statusbar;
public Tetris() {
initUI();
}
void initUI() {
JPanel panel = new JPanel();
panel.setBackground(new Color(0XF5EBE0));
statusbar = new JLabel("Score: 0");
statusbar.setFont(new Font("MV Boli", Font.BOLD, 30));
panel.add(statusbar, BorderLayout.NORTH);
Board board = new Board(this);
add(panel, BorderLayout.NORTH);
add(board);
board.setBackground(new Color(0Xf0e2d3));
board.start();
setTitle("Tetris");
setSize(400, 600);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);
setLocationRelativeTo(null);
}
public JLabel getStatusBar() {
return statusbar;
}
// run the game from here
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
Tetris game = new Tetris();
game.setVisible(true);
});
}
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Output:

Image output:

output for Tetris Game in Java

Video output:

Congratulations!! Hope you enjoyed building this cool project Tetris Game in Java.


Also Read:

Share:

Author: Ayush Purawr