โ˜• My First Java Game ยท Episode 2 of 7 ยท See All Episodes
๐Ÿงฑ Episode 2 ยท Beginner ยท Builds on Episode 1

Smash It!
Brick Breaker

Bounce a ball off your paddle to smash a wall of bricks. You will learn 2D arrays, bounce physics, mouse-free paddle control and lives, the classic arcade formula, in pure Java.

๐Ÿ‘ถ Ages 11+ โฑ๏ธ ~2 Hours โ˜• Java โœ“ Free
๐Ÿ“ Paddle control โšช Ball physics ๐Ÿงฑ 2D arrays ๐Ÿ’ฅ Collision response โค๏ธ Lives system ๐Ÿ† Win/lose states
โญ
0 XP
Level 1
๐Ÿ”ฅ0
Your Progress 0 / 6 steps
1
๐Ÿงฐ
Project Setup & Game Window
Reuse the Episode 1 pattern: JFrame + GamePanel + Timer
Active
๐ŸŽฏ
Goal for this step

Create a fresh project with the game-loop pattern from Episode 1 ready to go.

  • 1Make a new folder BrickBreaker with Main.java and GamePanel.java.
  • 2Main.java is identical to Episode 1, JFrame, add a GamePanel, pack, show. Set the title to "Brick Breaker".
  • 3GamePanel: 700ร—520 pixels, dark background, Timer(16, this), update() + repaint() pattern.
  • 4Add the KeyListener plumbing too (addKeyListener(this), setFocusable(true)), the paddle needs arrows.
  • 5Run it and confirm an empty window appears with no errors.
GamePanel.java (skeleton)
public class GamePanel extends JPanel
        implements ActionListener, KeyListener {
    Timer timer = new Timer(16, this);

    public GamePanel() {
        setPreferredSize(new Dimension(700, 520));
        setBackground(new Color(15, 18, 32));
        addKeyListener(this);
        setFocusable(true);
        timer.start();
    }

    public void actionPerformed(ActionEvent e) { update(); repaint(); }
    void update() {}
}public class GamePanel extends JPanel
        implements ActionListener, KeyListener {
    Timer timer = new Timer(16, this);

    public GamePanel() {
        setPreferredSize(new Dimension(700, 520));
        setBackground(new Color(15, 18, 32));
        addKeyListener(this);
        setFocusable(true);
        timer.start();
    }

    public void actionPerformed(ActionEvent e) { update(); repaint(); }
    void update() {}
}
๐Ÿ’ก
Notice how fast setup is the second time? That JFrame + GamePanel + Timer pattern is your reusable game template for the whole series.
๐Ÿ“ฆ Full starter template , the Swing window + game loop

The Main.java launcher plus the GamePanel game loop from Episode 1. Set the window to 700ร—520 for Breakout in setPreferredSizesetPreferredSize. Save as Main.java, run javac Main.java && java Mainjavac Main.java && java Main. Every step below adds to this file.

Main.java
// The starting template every game in this series uses.
// Save as Main.java, then run:  javac Main.java   and   java Main
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Main {
    public static void main(String[] args) {
        JFrame frame = new JFrame("My Java Game");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new GamePanel());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

class GamePanel extends JPanel implements ActionListener {
    Timer timer = new Timer(16, this);         // ~60 FPS
    public GamePanel() {
        setPreferredSize(new Dimension(700, 520));
        setBackground(new Color(25, 30, 60));   // night sky
        timer.start();
    }
    public void actionPerformed(ActionEvent e) {
        update();                               // move everything
        repaint();                              // then draw everything
    }
    void update() { /* movement goes here */ }
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        // drawing goes here
    }
}// The starting template every game in this series uses.
// Save as Main.java, then run:  javac Main.java   and   java Main
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Main {
    public static void main(String[] args) {
        JFrame frame = new JFrame("My Java Game");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new GamePanel());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

class GamePanel extends JPanel implements ActionListener {
    Timer timer = new Timer(16, this);         // ~60 FPS
    public GamePanel() {
        setPreferredSize(new Dimension(700, 520));
        setBackground(new Color(25, 30, 60));   // night sky
        timer.start();
    }
    public void actionPerformed(ActionEvent e) {
        update();                               // move everything
        repaint();                              // then draw everything
    }
    void update() { /* movement goes here */ }
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        // drawing goes here
    }
}
โœ๏ธ
Fill in the Blanks
+15 XP
Our game template has three parts: a window, a GamePanel that extends , and a that drives the loop at ~60 FPS.
๐Ÿง 
Knowledge Check
+15 XP
Which method pair makes up the game loop each tick?
Astart() and stop()
Bupdate() and repaint()
Cmain() and pack()
2
๐Ÿ“
The Paddle
A rectangle at the bottom that slides with the arrow keys
Locked
๐ŸŽฏ
Goal for this step

Draw a paddle that moves smoothly left/right and cannot leave the screen.

  • 1Add paddle variables: padX, plus fixed width 110, height 14, near the bottom (y = 470).
  • 2Move it in update() while an arrow key is held, exactly like the platformer player.
  • 3Clamp it: never let padX go below 0 or beyond 700, padWidth.
  • 4Draw it as a rounded orange rectangle with fillRoundRect.
GamePanel.java
    int padX = 295, padW = 110, padH = 14;
    boolean leftHeld, rightHeld;

    void update() {
        if (leftHeld)  padX -= 7;
        if (rightHeld) padX += 7;
        padX = Math.max(0, Math.min(700 - padW, padX));
    }

    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(new Color(255, 119, 0));
        g.fillRoundRect(padX, 470, padW, padH, 8, 8);
    }    int padX = 295, padW = 110, padH = 14;
    boolean leftHeld, rightHeld;

    void update() {
        if (leftHeld)  padX -= 7;
        if (rightHeld) padX += 7;
        padX = Math.max(0, Math.min(700 - padW, padX));
    }

    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(new Color(255, 119, 0));
        g.fillRoundRect(padX, 470, padW, padH, 8, 8);
    }
โŒจ๏ธ
Code Challenge
+20 XP
Stop the paddle sliding off the screen. Fill in the clamp:
GamePanel.java
padX = Math.padX = Math.(0, Math.(0, Math.(700 - padW, padX));(700 - padW, padX));
๐Ÿ’ก Hint: max(0, โ€ฆ) stops it going below zero; min(rightEdge, โ€ฆ) stops it going past the right side.
๐Ÿง 
Knowledge Check
+15 XP
What does Math.max(0, Math.min(590, padX)) guarantee?
ApadX is always exactly 0 or 590
BpadX always stays between 0 and 590, clamped on screen
CpadX moves faster near the edges
3
โšช
The Bouncing Ball
Velocity, wall bounces and the paddle rebound
Locked
๐ŸŽฏ
Goal for this step

Get a ball bouncing around the arena and rebounding off your paddle.

  • 1Ball variables: position bx, by, velocity bvx, bvy, size 14.
  • 2Each frame add velocity to position. Hitting left/right walls flips bvx; the ceiling flips bvy.
  • 3Paddle hit: if the ball rectangle intersects the paddle while moving down, flip bvy upward.
  • 4Pro touch: change bvx based on where it hits the paddle, edges send it wide, centre sends it straight.
  • 5Draw with fillOval in white.
GamePanel.java
    int bx = 340, by = 300, bs = 14;
    int bvx = 4, bvy = -4;

    void update() {
        // ... paddle code ...
        bx += bvx;  by += bvy;
        if (bx <= 0 || bx + bs >= 700) bvx = -bvx;   // side walls
        if (by <= 0) bvy = -bvy;                     // ceiling

        Rectangle ball = new Rectangle(bx, by, bs, bs);
        Rectangle pad  = new Rectangle(padX, 470, padW, padH);
        if (ball.intersects(pad) && bvy > 0) {
            bvy = -bvy;
            int hit = (bx + bs/2) - (padX + padW/2);
            bvx = hit / 12;              // steer with the paddle!
            if (bvx == 0) bvx = 1;
        }
    }    int bx = 340, by = 300, bs = 14;
    int bvx = 4, bvy = -4;

    void update() {
        // ... paddle code ...
        bx += bvx;  by += bvy;
        if (bx <= 0 || bx + bs >= 700) bvx = -bvx;   // side walls
        if (by <= 0) bvy = -bvy;                     // ceiling

        Rectangle ball = new Rectangle(bx, by, bs, bs);
        Rectangle pad  = new Rectangle(padX, 470, padW, padH);
        if (ball.intersects(pad) && bvy > 0) {
            bvy = -bvy;
            int hit = (bx + bs/2) - (padX + padW/2);
            bvx = hit / 12;              // steer with the paddle!
            if (bvx == 0) bvx = 1;
        }
    }
๐Ÿ’ก
That hit / 12 line is the secret sauce of Breakout, skilled players aim shots by choosing where on the paddle to catch the ball.
โœ๏ธ
Fill in the Blanks
+15 XP
To bounce off a side wall we flip the horizontal velocity: bvx = . We only rebound off the paddle when bvy > , meaning the ball is moving .
๐Ÿง 
Knowledge Check
+15 XP
The ball hits the RIGHT wall. Which value changes?
Abvy flips to negative
Bbvx flips sign so the ball travels left
Cbx resets to 0
4
๐Ÿงฑ
The Brick Wall
Build a grid of bricks with a 2D array
Locked
๐ŸŽฏ
Goal for this step

Fill the top of the screen with a colourful wall of bricks stored in a 2D array.

  • 1Declare boolean[][] bricks = new boolean[5][10];, 5 rows, 10 columns, true = alive.
  • 2In the constructor, set every brick to true with nested for loops.
  • 3Each brick is 64ร—22 pixels with a little gap; row and column convert to pixels: x = col*68 + 12, y = row*26 + 50.
  • 4Draw alive bricks with a different colour per row (use an array of Colors).
  • 5Run it, a proper arcade brick wall!
GamePanel.java
    boolean[][] bricks = new boolean[5][10];
    Color[] rowColors = { new Color(248,113,113), new Color(251,146,60),
        new Color(250,204,21), new Color(74,222,128), new Color(96,165,250) };

    // in constructor:
    for (int r = 0; r < 5; r++)
        for (int c = 0; c < 10; c++)
            bricks[r][c] = true;

    // in paintComponent:
    for (int r = 0; r < 5; r++)
        for (int c = 0; c < 10; c++)
            if (bricks[r][c]) {
                g.setColor(rowColors[r]);
                g.fillRect(c*68 + 12, r*26 + 50, 64, 22);
            }    boolean[][] bricks = new boolean[5][10];
    Color[] rowColors = { new Color(248,113,113), new Color(251,146,60),
        new Color(250,204,21), new Color(74,222,128), new Color(96,165,250) };

    // in constructor:
    for (int r = 0; r < 5; r++)
        for (int c = 0; c < 10; c++)
            bricks[r][c] = true;

    // in paintComponent:
    for (int r = 0; r < 5; r++)
        for (int c = 0; c < 10; c++)
            if (bricks[r][c]) {
                g.setColor(rowColors[r]);
                g.fillRect(c*68 + 12, r*26 + 50, 64, 22);
            }
โŒจ๏ธ
Code Challenge
+20 XP
Complete the nested loop that walks every brick in the grid:
GamePanel.java
for (int r = 0; r < for (int r = 0; r < ; r++)
    for (int c = 0; c < ; r++)
    for (int c = 0; c < ; c++)
        bricks[r][c] = ; c++)
        bricks[r][c] = ;;
๐Ÿ’ก Hint: The array was declared new boolean[5][10], rows first, then columns. Alive bricks are true.
๐Ÿง 
Knowledge Check
+15 XP
What does bricks[2][7] refer to?
ARow 2, column 7 of the grid (counting from 0)
BThe 27th brick
CA brick that is 2 wide and 7 tall
5
๐Ÿ’ฅ
Smashing Bricks
Ball-brick collision, scoring and the bounce back
Locked
๐ŸŽฏ
Goal for this step

Make the ball destroy bricks it touches, bounce off them, and rack up score.

  • 1In update(), loop over the grid. For each alive brick, build its Rectangle and test ball.intersects(brick).
  • 2On hit: set that cell to false (brick destroyed), add 10 score, flip bvy, and break out so one hit kills one brick.
  • 3Track score in an int and draw it top-left.
  • 4Higher rows can be worth more: score += (5 - r) * 10;, smashing deep pays better!
GamePanel.java (update)
    int score = 0;

    outer:
    for (int r = 0; r < 5; r++)
        for (int c = 0; c < 10; c++)
            if (bricks[r][c]) {
                Rectangle brick = new Rectangle(c*68 + 12, r*26 + 50, 64, 22);
                if (ball.intersects(brick)) {
                    bricks[r][c] = false;
                    score += (5 - r) * 10;
                    bvy = -bvy;
                    break outer;   // one brick per frame
                }
            }    int score = 0;

    outer:
    for (int r = 0; r < 5; r++)
        for (int c = 0; c < 10; c++)
            if (bricks[r][c]) {
                Rectangle brick = new Rectangle(c*68 + 12, r*26 + 50, 64, 22);
                if (ball.intersects(brick)) {
                    bricks[r][c] = false;
                    score += (5 - r) * 10;
                    bvy = -bvy;
                    break outer;   // one brick per frame
                }
            }
๐Ÿ’ก
break outer; jumps out of BOTH loops at once, a labelled break. Without it the ball could vaporise a whole column in one frame.
โœ๏ธ
Fill in the Blanks
+15 XP
When the ball hits a brick we set bricks[r][c] = , flip , and use a labelled so only one brick dies per frame.
๐Ÿง 
Knowledge Check
+15 XP
Why flip bvy when a brick is destroyed?
ATo slow the ball down
BSo the ball bounces off the brick instead of drilling through the whole wall
CTo reset the ball position
6
โค๏ธ
Lives, Losing & Winning
Three lives, a game-over screen and the victory check
Locked
๐ŸŽฏ
Goal for this step

Finish the game: lose a life when the ball drops, win when every brick is gone.

  • 1Add int lives = 3; and a boolean gameOver = false;.
  • 2If by > 520 (ball fell past the paddle): lives--, reset the ball to the centre. If lives hits 0, gameOver = true.
  • 3Count alive bricks each frame, zero alive means you win.
  • 4When the game is over (won or lost), stop updating and draw a big message + final score.
  • 5Draw lives as โค symbols top-right. Press R to restart if you want a challenge, reset every variable!
GamePanel.java
    int lives = 3;
    boolean gameOver = false, won = false;

    void update() {
        if (gameOver || won) return;    // freeze the game
        // ... all previous code ...

        if (by > 520) {                 // ball lost!
            lives--;
            bx = 340; by = 300; bvx = 4; bvy = -4;
            if (lives == 0) gameOver = true;
        }

        int alive = 0;
        for (int r = 0; r < 5; r++)
            for (int c = 0; c < 10; c++)
                if (bricks[r][c]) alive++;
        if (alive == 0) won = true;
    }    int lives = 3;
    boolean gameOver = false, won = false;

    void update() {
        if (gameOver || won) return;    // freeze the game
        // ... all previous code ...

        if (by > 520) {                 // ball lost!
            lives--;
            bx = 340; by = 300; bvx = 4; bvy = -4;
            if (lives == 0) gameOver = true;
        }

        int alive = 0;
        for (int r = 0; r < 5; r++)
            for (int c = 0; c < 10; c++)
                if (bricks[r][c]) alive++;
        if (alive == 0) won = true;
    }
โŒจ๏ธ
Code Challenge
+20 XP
Finish the life-lost logic:
GamePanel.java
if (by > 520) {
    livesif (by > 520) {
    lives;
    bx = 340; by = 300;
    if (lives == ;
    bx = 340; by = 300;
    if (lives == ) gameOver = ) gameOver = ;
};
}
๐Ÿ’ก Hint: The decrement operator takes one away. The game ends when no lives remain.
๐Ÿง 
Knowledge Check
+15 XP
Why does update() start with if (gameOver || won) return;?
ATo make the game faster
BTo freeze all movement once the game has ended, while still drawing the final screen
CJava requires a return in every method
โœ… See the complete finished Breakout , all 6 steps assembled

A clamped paddle, a bouncing ball with paddle-spin steering, a colour-banded brick wall, break-and-score, three lives and win/lose screens , one runnable file. Save as Main.javaMain.java, run javac Main.java && java Mainjavac Main.java && java Main, and play. Stuck? Compare your file against this.

Main.java
// Complete Java Breakout. Run with:  javac Main.java  then  java Main
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.List;
import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Brick Breaker");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new GamePanel());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

class GamePanel extends JPanel implements ActionListener, KeyListener {
    Timer timer = new Timer(16, this);
    Color[] rowColours = {
        new Color(248,113,113), new Color(251,146,60), new Color(250,204,21),
        new Color(74,222,128), new Color(96,165,250)
    };

    int padX = 295, padW = 110, padH = 14;
    int bx = 340, by = 300, bs = 14, bvx = 4, bvy = -4;
    boolean leftHeld, rightHeld;
    int score = 0, lives = 3;
    String state = "PLAY";           // PLAY / WIN / LOSE
    List<Rectangle> bricks = new ArrayList<>();

    public GamePanel() {
        setPreferredSize(new Dimension(700, 520));
        setBackground(new Color(15, 18, 32));
        addKeyListener(this);
        setFocusable(true);
        buildBricks();
        timer.start();
    }

    void buildBricks() {
        bricks.clear();
        for (int row = 0; row < 5; row++)
            for (int col = 0; col < 10; col++)
                bricks.add(new Rectangle(col*68 + 12, row*26 + 50, 64, 22));
    }
    int rowOf(Rectangle b) { return (b.y - 50) / 26; }

    void serve() { bx = 343; by = 300; bvx = 4; bvy = -4; }

    void reset() {
        padX = 295; score = 0; lives = 3; state = "PLAY";
        buildBricks(); serve();
    }

    public void actionPerformed(ActionEvent e) { update(); repaint(); }

    void update() {
        if (!state.equals("PLAY")) return;

        if (leftHeld)  padX -= 7;
        if (rightHeld) padX += 7;
        padX = Math.max(0, Math.min(700 - padW, padX));

        bx += bvx; by += bvy;
        if (bx <= 0 || bx + bs >= 700) bvx = -bvx;
        if (by <= 0) bvy = -bvy;

        Rectangle ball = new Rectangle(bx, by, bs, bs);
        Rectangle pad  = new Rectangle(padX, 470, padW, padH);
        if (ball.intersects(pad) && bvy > 0) {
            bvy = -bvy;
            int hit = (bx + bs/2) - (padX + padW/2);
            bvx = hit / 12;
            if (bvx == 0) bvx = 1;
        }

        for (int i = 0; i < bricks.size(); i++) {
            Rectangle brick = bricks.get(i);
            if (ball.intersects(brick)) {
                score += (5 - rowOf(brick)) * 10;
                bvy = -bvy;
                bricks.remove(i);
                break;
            }
        }

        if (by - bs > 520) {
            lives--;
            if (lives <= 0) state = "LOSE";
            else serve();
        }
        if (bricks.isEmpty()) state = "WIN";
    }

    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        for (Rectangle b : bricks) {
            g.setColor(rowColours[rowOf(b) % rowColours.length]);
            g.fillRect(b.x, b.y, b.width, b.height);
        }
        g.setColor(new Color(255, 119, 0));
        g.fillRoundRect(padX, 470, padW, padH, 8, 8);
        g.setColor(Color.WHITE);
        g.fillOval(bx, by, bs, bs);
        g.setFont(new Font("Arial", Font.BOLD, 18));
        g.drawString("Score: " + score, 16, 26);
        g.drawString("Lives: " + Math.max(0, lives), 590, 26);
        if (!state.equals("PLAY")) {
            g.setFont(new Font("Arial", Font.BOLD, 48));
            g.drawString(state.equals("WIN") ? "YOU WIN!" : "GAME OVER", 200, 250);
            g.setFont(new Font("Arial", Font.PLAIN, 18));
            g.drawString("Press R to play again", 270, 290);
        }
    }

    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_LEFT)  leftHeld = true;
        if (e.getKeyCode() == KeyEvent.VK_RIGHT) rightHeld = true;
        if (e.getKeyCode() == KeyEvent.VK_R && !state.equals("PLAY")) reset();
    }
    public void keyReleased(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_LEFT)  leftHeld = false;
        if (e.getKeyCode() == KeyEvent.VK_RIGHT) rightHeld = false;
    }
    public void keyTyped(KeyEvent e) {}
}// Complete Java Breakout. Run with:  javac Main.java  then  java Main
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.List;
import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Brick Breaker");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new GamePanel());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

class GamePanel extends JPanel implements ActionListener, KeyListener {
    Timer timer = new Timer(16, this);
    Color[] rowColours = {
        new Color(248,113,113), new Color(251,146,60), new Color(250,204,21),
        new Color(74,222,128), new Color(96,165,250)
    };

    int padX = 295, padW = 110, padH = 14;
    int bx = 340, by = 300, bs = 14, bvx = 4, bvy = -4;
    boolean leftHeld, rightHeld;
    int score = 0, lives = 3;
    String state = "PLAY";           // PLAY / WIN / LOSE
    List<Rectangle> bricks = new ArrayList<>();

    public GamePanel() {
        setPreferredSize(new Dimension(700, 520));
        setBackground(new Color(15, 18, 32));
        addKeyListener(this);
        setFocusable(true);
        buildBricks();
        timer.start();
    }

    void buildBricks() {
        bricks.clear();
        for (int row = 0; row < 5; row++)
            for (int col = 0; col < 10; col++)
                bricks.add(new Rectangle(col*68 + 12, row*26 + 50, 64, 22));
    }
    int rowOf(Rectangle b) { return (b.y - 50) / 26; }

    void serve() { bx = 343; by = 300; bvx = 4; bvy = -4; }

    void reset() {
        padX = 295; score = 0; lives = 3; state = "PLAY";
        buildBricks(); serve();
    }

    public void actionPerformed(ActionEvent e) { update(); repaint(); }

    void update() {
        if (!state.equals("PLAY")) return;

        if (leftHeld)  padX -= 7;
        if (rightHeld) padX += 7;
        padX = Math.max(0, Math.min(700 - padW, padX));

        bx += bvx; by += bvy;
        if (bx <= 0 || bx + bs >= 700) bvx = -bvx;
        if (by <= 0) bvy = -bvy;

        Rectangle ball = new Rectangle(bx, by, bs, bs);
        Rectangle pad  = new Rectangle(padX, 470, padW, padH);
        if (ball.intersects(pad) && bvy > 0) {
            bvy = -bvy;
            int hit = (bx + bs/2) - (padX + padW/2);
            bvx = hit / 12;
            if (bvx == 0) bvx = 1;
        }

        for (int i = 0; i < bricks.size(); i++) {
            Rectangle brick = bricks.get(i);
            if (ball.intersects(brick)) {
                score += (5 - rowOf(brick)) * 10;
                bvy = -bvy;
                bricks.remove(i);
                break;
            }
        }

        if (by - bs > 520) {
            lives--;
            if (lives <= 0) state = "LOSE";
            else serve();
        }
        if (bricks.isEmpty()) state = "WIN";
    }

    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        for (Rectangle b : bricks) {
            g.setColor(rowColours[rowOf(b) % rowColours.length]);
            g.fillRect(b.x, b.y, b.width, b.height);
        }
        g.setColor(new Color(255, 119, 0));
        g.fillRoundRect(padX, 470, padW, padH, 8, 8);
        g.setColor(Color.WHITE);
        g.fillOval(bx, by, bs, bs);
        g.setFont(new Font("Arial", Font.BOLD, 18));
        g.drawString("Score: " + score, 16, 26);
        g.drawString("Lives: " + Math.max(0, lives), 590, 26);
        if (!state.equals("PLAY")) {
            g.setFont(new Font("Arial", Font.BOLD, 48));
            g.drawString(state.equals("WIN") ? "YOU WIN!" : "GAME OVER", 200, 250);
            g.setFont(new Font("Arial", Font.PLAIN, 18));
            g.drawString("Press R to play again", 270, 290);
        }
    }

    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_LEFT)  leftHeld = true;
        if (e.getKeyCode() == KeyEvent.VK_RIGHT) rightHeld = true;
        if (e.getKeyCode() == KeyEvent.VK_R && !state.equals("PLAY")) reset();
    }
    public void keyReleased(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_LEFT)  leftHeld = false;
        if (e.getKeyCode() == KeyEvent.VK_RIGHT) rightHeld = false;
    }
    public void keyTyped(KeyEvent e) {}
}
๐ŸŽ‰๐Ÿ†๐ŸŽฎโœจ๐ŸŽ‰
Workshop Complete!
Paddle, ball, bricks, lives, you rebuilt an arcade legend from nothing. Episode 3 sends you to space!
0
Total XP
1
Level
0
Best Streak
0%
Accuracy
โ–ถ Episode 3: Space Shooter โ†’
โญ View My Progress & Certificates

This workshop was free and took many hours to build. If it helped you learn something new, consider supporting the project.

☕ Support on Ko-fi