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); }); } }
Output:
Image output:
Video output:
Congratulations!! Hope you enjoyed building this cool project Tetris Game in Java.
Also Read:
- Dino Game in Java
- Java Games Code | Copy And Paste
- Supply Chain Management System in Java
- Survey Management System In Java
- Phone Book in Java
- Email Application in Java
- Inventory Management System Project in Java
- Blood Bank Management System Project in Java
- Electricity Bill Management System Project in Java
- CGPA Calculator App In Java
- Chat Application in Java
- 100+ Java Projects for Beginners 2023
- Airline Reservation System Project in Java
- Password and Notes Manager in Java
- GUI Number Guessing Game in Java
- How to create Notepad in Java?
- Memory Game in Java
- Simple Car Race Game in Java
- ATM program in Java
- Drawing Application In Java
- Tetris Game in Java
- Pong Game in Java
- Hospital Management System Project in Java
- Ludo Game in Java
- Restaurant Management System Project in Java
- Flappy Bird Game in Java
- ATM Simulator In Java
- Brick Breaker Game in Java
- Best Java Roadmap for Beginners 2023
- Snake Game in Java