The Original!
Pong Classic
Recreate the 1972 legend: two paddles, one ball, first to 7. Then teach the right paddle to play by itself, your first game AI, plus proper game states (menu, playing, game over).
Recreate the 1972 legend: two paddles, one ball, first to 7. Then teach the right paddle to play by itself, your first game AI, plus proper game states (menu, playing, game over).
Draw the court and get both paddles moving with their own keys.
int p1Y = 205, p2Y = 205;
boolean wHeld, sHeld, upHeld, downHeld;
void update() {
if (wHeld) p1Y -= 6;
if (sHeld) p1Y += 6;
if (upHeld) p2Y -= 6;
if (downHeld) p2Y += 6;
p1Y = Math.max(0, Math.min(410, p1Y));
p2Y = Math.max(0, Math.min(410, p2Y));
}
// centre line in paintComponent:
for (int y = 0; y < 500; y += 24)
g.fillRect(397, y, 6, 14); int p1Y = 205, p2Y = 205;
boolean wHeld, sHeld, upHeld, downHeld;
void update() {
if (wHeld) p1Y -= 6;
if (sHeld) p1Y += 6;
if (upHeld) p2Y -= 6;
if (downHeld) p2Y += 6;
p1Y = Math.max(0, Math.min(410, p1Y));
p2Y = Math.max(0, Math.min(410, p2Y));
}
// centre line in paintComponent:
for (int y = 0; y < 500; y += 24)
g.fillRect(397, y, 6, 14);
A ball that serves from the centre, bounces off walls and paddles, and speeds up each hit.
int bx = 393, by = 243, bs = 14;
int bvx = 5, bvy = 3;
void serve(int direction) {
bx = 393; by = 243;
bvx = 5 * direction;
bvy = Math.random() < 0.5 ? 3 : -3;
}
void update() {
bx += bvx; by += bvy;
if (by <= 0 || by + bs >= 500) bvy = -bvy;
Rectangle ball = new Rectangle(bx, by, bs, bs);
if (ball.intersects(new Rectangle(20, p1Y, 14, 90)) && bvx < 0) {
bvx = -bvx + 1; // rally speeds up
bvy = ((by + bs/2) - (p1Y + 45)) / 7; // spin
}
if (ball.intersects(new Rectangle(766, p2Y, 14, 90)) && bvx > 0) {
bvx = -(bvx + 1);
bvy = ((by + bs/2) - (p2Y + 45)) / 7;
}
bvx = Math.max(-12, Math.min(12, bvx));
} int bx = 393, by = 243, bs = 14;
int bvx = 5, bvy = 3;
void serve(int direction) {
bx = 393; by = 243;
bvx = 5 * direction;
bvy = Math.random() < 0.5 ? 3 : -3;
}
void update() {
bx += bvx; by += bvy;
if (by <= 0 || by + bs >= 500) bvy = -bvy;
Rectangle ball = new Rectangle(bx, by, bs, bs);
if (ball.intersects(new Rectangle(20, p1Y, 14, 90)) && bvx < 0) {
bvx = -bvx + 1; // rally speeds up
bvy = ((by + bs/2) - (p1Y + 45)) / 7; // spin
}
if (ball.intersects(new Rectangle(766, p2Y, 14, 90)) && bvx > 0) {
bvx = -(bvx + 1);
bvy = ((by + bs/2) - (p2Y + 45)) / 7;
}
bvx = Math.max(-12, Math.min(12, bvx));
}
void serve(int direction) {
bx = void serve(int direction) {
bx = ; by = 243; // back to centre
bvx = 5 * ; by = 243; // back to centre
bvx = 5 * ; // towards the scorer's opponent
bvy = Math.random() < 0.5 ? 3 : ; // towards the scorer's opponent
bvy = Math.random() < 0.5 ? 3 : ;
};
}
Score points when the ball leaves the court, and crown a winner at 7.
int score1 = 0, score2 = 0;
boolean gameOver = false;
void update() {
if (gameOver) return;
// ... movement ...
if (bx < -20) { score2++; serve(-1); }
if (bx > 820) { score1++; serve(1); }
if (score1 == 7 || score2 == 7) gameOver = true;
}
// paintComponent:
g.setFont(new Font("Monospaced", Font.BOLD, 56));
g.drawString("" + score1, 320, 70);
g.drawString("" + score2, 450, 70); int score1 = 0, score2 = 0;
boolean gameOver = false;
void update() {
if (gameOver) return;
// ... movement ...
if (bx < -20) { score2++; serve(-1); }
if (bx > 820) { score1++; serve(1); }
if (score1 == 7 || score2 == 7) gameOver = true;
}
// paintComponent:
g.setFont(new Font("Monospaced", Font.BOLD, 56));
g.drawString("" + score1, 320, 70);
g.drawString("" + score2, 450, 70);
Replace player 2 with a computer that chases the ball but can be beaten.
boolean vsComputer = true;
int aiSpeed = 5, deadZone = 12;
void update() {
// ...
if (vsComputer) {
int padCentre = p2Y + 45;
int ballCentre = by + bs/2;
if (padCentre < ballCentre - deadZone) p2Y += aiSpeed;
if (padCentre > ballCentre + deadZone) p2Y -= aiSpeed;
} else {
if (upHeld) p2Y -= 6;
if (downHeld) p2Y += 6;
}
} boolean vsComputer = true;
int aiSpeed = 5, deadZone = 12;
void update() {
// ...
if (vsComputer) {
int padCentre = p2Y + 45;
int ballCentre = by + bs/2;
if (padCentre < ballCentre - deadZone) p2Y += aiSpeed;
if (padCentre > ballCentre + deadZone) p2Y -= aiSpeed;
} else {
if (upHeld) p2Y -= 6;
if (downHeld) p2Y += 6;
}
}
if (padCentre < ballCentre - deadZone) p2Y if (padCentre < ballCentre - deadZone) p2Y aiSpeed; if (padCentre > ballCentre + deadZone) p2Y aiSpeed; if (padCentre > ballCentre + deadZone) p2Y aiSpeed; aiSpeed;
Add a title screen and restart flow using a proper state variable.
String state = "MENU";
void update() {
if (!state.equals("PLAYING")) return;
// ... all game code ...
if (score1 == 7 || score2 == 7) state = "GAME_OVER";
}
public void keyPressed(KeyEvent e) {
if (state.equals("MENU")) {
if (e.getKeyChar() == '1') { vsComputer = true; state = "PLAYING"; }
if (e.getKeyChar() == '2') { vsComputer = false; state = "PLAYING"; }
} else if (state.equals("GAME_OVER") && e.getKeyChar() == 'r') {
score1 = 0; score2 = 0; serve(1);
state = "MENU";
}
// ... held-key booleans ...
} String state = "MENU";
void update() {
if (!state.equals("PLAYING")) return;
// ... all game code ...
if (score1 == 7 || score2 == 7) state = "GAME_OVER";
}
public void keyPressed(KeyEvent e) {
if (state.equals("MENU")) {
if (e.getKeyChar() == '1') { vsComputer = true; state = "PLAYING"; }
if (e.getKeyChar() == '2') { vsComputer = false; state = "PLAYING"; }
} else if (state.equals("GAME_OVER") && e.getKeyChar() == 'r') {
score1 = 0; score2 = 0; serve(1);
state = "MENU";
}
// ... held-key booleans ...
}
Add the little touches that make Pong feel alive, then ship it.
int flash = 0;
// when a point is scored:
// score2++; serve(-1); flash = 12;
void update() {
// ...
if (flash > 0) flash--;
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (flash > 0) {
g.setColor(new Color(255, 255, 255, 40));
g.fillRect(0, 0, 800, 500);
}
// ... everything else ...
} int flash = 0;
// when a point is scored:
// score2++; serve(-1); flash = 12;
void update() {
// ...
if (flash > 0) flash--;
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (flash > 0) {
g.setColor(new Color(255, 255, 255, 40));
g.fillRect(0, 0, 800, 500);
}
// ... everything else ...
}
// when scoring: flash = // when scoring: flash = ; // each frame: if (flash > 0) flash; // each frame: if (flash > 0) flash;;
Two paddles (W/S and the arrow keys), a serving ball that bounces off walls and paddles, paddle-spin plus rally speed-up, a scoreboard and first-to-7 win , 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.
// Complete Java Pong. Run with: javac Main.java then 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("Pong");
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);
int p1Y = 205, p2Y = 205;
boolean wHeld, sHeld, upHeld, downHeld;
int bx = 393, by = 243, bs = 14, bvx = 5, bvy = 3;
int p1Score = 0, p2Score = 0;
String state = "PLAY";
public GamePanel() {
setPreferredSize(new Dimension(800, 500));
setBackground(new Color(12, 14, 24));
addKeyListener(this);
setFocusable(true);
serve(Math.random() < 0.5 ? 1 : -1);
timer.start();
}
void serve(int direction) {
bx = 393; by = 243;
bvx = 5 * direction;
bvy = Math.random() < 0.5 ? 3 : -3;
}
void reset() {
p1Score = 0; p2Score = 0; state = "PLAY";
p1Y = 205; p2Y = 205;
serve(Math.random() < 0.5 ? 1 : -1);
}
public void actionPerformed(ActionEvent e) { update(); repaint(); }
void update() {
if (!state.equals("PLAY")) return;
if (wHeld) p1Y -= 6;
if (sHeld) p1Y += 6;
if (upHeld) p2Y -= 6;
if (downHeld) p2Y += 6;
p1Y = Math.max(0, Math.min(410, p1Y));
p2Y = Math.max(0, Math.min(410, p2Y));
bx += bvx; by += bvy;
if (by <= 0 || by + bs >= 500) bvy = -bvy;
Rectangle ball = new Rectangle(bx, by, bs, bs);
if (ball.intersects(new Rectangle(20, p1Y, 14, 90)) && bvx < 0) {
bvx = -bvx + 1; // rally speeds up
bvy = ((by + bs/2) - (p1Y + 45)) / 7; // spin
}
if (ball.intersects(new Rectangle(766, p2Y, 14, 90)) && bvx > 0) {
bvx = -(bvx + 1);
bvy = ((by + bs/2) - (p2Y + 45)) / 7;
}
bvx = Math.max(-12, Math.min(12, bvx));
if (bx < 0) { p2Score++; checkWin(); if (state.equals("PLAY")) serve(-1); }
if (bx > 800) { p1Score++; checkWin(); if (state.equals("PLAY")) serve(1); }
}
void checkWin() {
if (p1Score >= 7 || p2Score >= 7) state = "WIN";
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(new Color(60, 66, 90));
for (int y = 0; y < 500; y += 24) g.fillRect(397, y, 6, 14); // net
g.setColor(Color.WHITE);
g.fillRect(20, p1Y, 14, 90);
g.fillRect(766, p2Y, 14, 90);
g.fillOval(bx, by, bs, bs);
g.setFont(new Font("Arial", Font.BOLD, 40));
g.drawString(String.valueOf(p1Score), 340, 60);
g.drawString(String.valueOf(p2Score), 440, 60);
if (state.equals("WIN")) {
g.setFont(new Font("Arial", Font.BOLD, 44));
String who = p1Score >= 7 ? "PLAYER 1 WINS!" : "PLAYER 2 WINS!";
g.drawString(who, 240, 260);
g.setFont(new Font("Arial", Font.PLAIN, 18));
g.drawString("Press R to play again", 300, 300);
}
}
public void keyPressed(KeyEvent e) {
int k = e.getKeyCode();
if (k == KeyEvent.VK_W) wHeld = true;
if (k == KeyEvent.VK_S) sHeld = true;
if (k == KeyEvent.VK_UP) upHeld = true;
if (k == KeyEvent.VK_DOWN) downHeld = true;
if (k == KeyEvent.VK_R && !state.equals("PLAY")) reset();
}
public void keyReleased(KeyEvent e) {
int k = e.getKeyCode();
if (k == KeyEvent.VK_W) wHeld = false;
if (k == KeyEvent.VK_S) sHeld = false;
if (k == KeyEvent.VK_UP) upHeld = false;
if (k == KeyEvent.VK_DOWN) downHeld = false;
}
public void keyTyped(KeyEvent e) {}
}// Complete Java Pong. Run with: javac Main.java then 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("Pong");
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);
int p1Y = 205, p2Y = 205;
boolean wHeld, sHeld, upHeld, downHeld;
int bx = 393, by = 243, bs = 14, bvx = 5, bvy = 3;
int p1Score = 0, p2Score = 0;
String state = "PLAY";
public GamePanel() {
setPreferredSize(new Dimension(800, 500));
setBackground(new Color(12, 14, 24));
addKeyListener(this);
setFocusable(true);
serve(Math.random() < 0.5 ? 1 : -1);
timer.start();
}
void serve(int direction) {
bx = 393; by = 243;
bvx = 5 * direction;
bvy = Math.random() < 0.5 ? 3 : -3;
}
void reset() {
p1Score = 0; p2Score = 0; state = "PLAY";
p1Y = 205; p2Y = 205;
serve(Math.random() < 0.5 ? 1 : -1);
}
public void actionPerformed(ActionEvent e) { update(); repaint(); }
void update() {
if (!state.equals("PLAY")) return;
if (wHeld) p1Y -= 6;
if (sHeld) p1Y += 6;
if (upHeld) p2Y -= 6;
if (downHeld) p2Y += 6;
p1Y = Math.max(0, Math.min(410, p1Y));
p2Y = Math.max(0, Math.min(410, p2Y));
bx += bvx; by += bvy;
if (by <= 0 || by + bs >= 500) bvy = -bvy;
Rectangle ball = new Rectangle(bx, by, bs, bs);
if (ball.intersects(new Rectangle(20, p1Y, 14, 90)) && bvx < 0) {
bvx = -bvx + 1; // rally speeds up
bvy = ((by + bs/2) - (p1Y + 45)) / 7; // spin
}
if (ball.intersects(new Rectangle(766, p2Y, 14, 90)) && bvx > 0) {
bvx = -(bvx + 1);
bvy = ((by + bs/2) - (p2Y + 45)) / 7;
}
bvx = Math.max(-12, Math.min(12, bvx));
if (bx < 0) { p2Score++; checkWin(); if (state.equals("PLAY")) serve(-1); }
if (bx > 800) { p1Score++; checkWin(); if (state.equals("PLAY")) serve(1); }
}
void checkWin() {
if (p1Score >= 7 || p2Score >= 7) state = "WIN";
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(new Color(60, 66, 90));
for (int y = 0; y < 500; y += 24) g.fillRect(397, y, 6, 14); // net
g.setColor(Color.WHITE);
g.fillRect(20, p1Y, 14, 90);
g.fillRect(766, p2Y, 14, 90);
g.fillOval(bx, by, bs, bs);
g.setFont(new Font("Arial", Font.BOLD, 40));
g.drawString(String.valueOf(p1Score), 340, 60);
g.drawString(String.valueOf(p2Score), 440, 60);
if (state.equals("WIN")) {
g.setFont(new Font("Arial", Font.BOLD, 44));
String who = p1Score >= 7 ? "PLAYER 1 WINS!" : "PLAYER 2 WINS!";
g.drawString(who, 240, 260);
g.setFont(new Font("Arial", Font.PLAIN, 18));
g.drawString("Press R to play again", 300, 300);
}
}
public void keyPressed(KeyEvent e) {
int k = e.getKeyCode();
if (k == KeyEvent.VK_W) wHeld = true;
if (k == KeyEvent.VK_S) sHeld = true;
if (k == KeyEvent.VK_UP) upHeld = true;
if (k == KeyEvent.VK_DOWN) downHeld = true;
if (k == KeyEvent.VK_R && !state.equals("PLAY")) reset();
}
public void keyReleased(KeyEvent e) {
int k = e.getKeyCode();
if (k == KeyEvent.VK_W) wHeld = false;
if (k == KeyEvent.VK_S) sHeld = false;
if (k == KeyEvent.VK_UP) upHeld = false;
if (k == KeyEvent.VK_DOWN) downHeld = false;
}
public void keyTyped(KeyEvent e) {}
}