Object Oriented Programming | Week 14: Pong Game

 PBO-A 2025 

    Pertemuan ini berfokus pada cara membuat game Pong menggunakan prinsip Pemrograman Berorientasi Objek (OOP) dan bahasa pemrograman Java. Permainan Pong ini dirancang untuk mendukung mode 1 pemain atau 2 pemain. Mode 1 Pemain: Pemain dapat bermain melawan bot yang tingkat kesulitannya bisa diatur sebelum permainan dimulai. Mode 2 Pemain (Local Multiplayer): Dua pemain dapat berinteraksi pada komputer yang sama, di mana setiap pemain mengendalikan paddle (pemukul) mereka masing-masing.

    Implementasi game Pong ini menggunakan empat kelas utama, yang masing-masing memiliki peran spesifik:

  • Class Ball: Kelas ini mengelola semua aspek yang berkaitan dengan bola. Ini mencakup posisi, arah, dan kecepatan gerak bola, serta penanganan pantulan terhadap dinding atas/bawah dan collision dengan paddle pemain. Kelas ini juga bertugas menyesuaikan kecepatan bola setelah beberapa kali bersentuhan dengan paddle dan menerapkan logika penghitungan skor. Ketika pemain mencetak skor, fungsi spawn() dipanggil untuk mereset posisi bola kembali ke tengah lapangan.

  • Class Paddle: Kelas ini merepresentasikan dua paddle pemain (kiri dan kanan). Tugas utamanya adalah menyimpan posisi paddle, mengontrol pergerakan vertikal pemain, memastikan paddle tidak keluar dari area permainan, dan menyimpan nilai skor pemain yang bersangkutan. Kelas ini juga bertanggung jawab untuk menampilkan representasi visual paddle di layar.

  • Class Renderer: Bertindak sebagai kanvas atau area gambar tempat semua objek game divisualisasikan. Kelas ini merupakan subkelas dari JPanel yang mengimplementasikan metode paintComponent() dan kemudian memanggil fungsi pong.renderer() untuk memastikan tampilan game diperbarui secara konsisten di setiap frame.

  • Class Pong: Ini adalah kelas pengontrol game utama yang mengatur keseluruhan alur permainan. Tanggung jawabnya meliputi pembuatan jendela (window) game, menjalankan game loop menggunakan timer, memproses input keyboard dari pemain, mengelola status permainan (seperti menu, pause, play, game over), mengelola mode bot, melacak skor, dan mengatur proses update serta render untuk semua objek dalam game.


Ball.java:

/**
 * Write a description of class Ball here.
 *
 * @author Muhammad Zaky Zein
 * @version 2025.11.25
 */

import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;

public class Ball
{
	public int x, y, width = 25, height = 25;
	public int motionX, motionY;
	public Random random;
	private Pong pong;
	public int amountOfHits;

	public Ball(Pong pong) {
		this.pong = pong;

		this.random = new Random();

		spawn();
	}

	public void update(Paddle paddle1, Paddle paddle2) {
		int speed = 5;

		this.x += motionX * speed;
		this.y += motionY * speed;

		if (this.y + height - motionY > pong.height || this.y + motionY < 0) {
			if (this.motionY < 0) {
				this.y = 0;
				this.motionY = random.nextInt(4);

				if (motionY == 0) motionY = 1;
			} else {
				this.motionY = -random.nextInt(4);
				this.y = pong.height - height;

				if (motionY == 0) motionY = -1;
			}
		}

		if (checkCollision(paddle1) == 1) {
			this.motionX = 1 + (amountOfHits / 5);
			this.motionY = -2 + random.nextInt(4);

			if (motionY == 0) motionY = 1;

			amountOfHits++;
		} else if (checkCollision(paddle2) == 1) {
			this.motionX = -1 - (amountOfHits / 5);
			this.motionY = -2 + random.nextInt(4);

			if (motionY == 0) motionY = 1;

			amountOfHits++;
		}

		if (checkCollision(paddle1) == 2) {
			paddle2.score++;
			spawn();
		} else if (checkCollision(paddle2) == 2) {
			paddle1.score++;
			spawn();
		}
	}

	public void spawn() {
		this.amountOfHits = 0;
		this.x = pong.width / 2 - this.width / 2;
		this.y = pong.height / 2 - this.height / 2;

		this.motionY = -2 + random.nextInt(4);

		if (motionY == 0) motionY = 1;

		if (random.nextBoolean()) motionX = 1;
		else motionX = -1;
	}

	public int checkCollision(Paddle paddle) {
		if (this.x < paddle.x + paddle.width && this.x + width > paddle.x && this.y < paddle.y + paddle.height && this.y + height > paddle.y) return 1;
		else if ((paddle.x > x && paddle.paddleNumber == 1) || (paddle.x < x - width && paddle.paddleNumber == 2)) return 2;
		return 0;
	}

	public void render(Graphics g) {
		g.setColor(Color.WHITE);
		g.fillOval(x, y, width, height);
	}

}


Paddle.java:

/**
 * Write a description of class Paddle here.
 *
 * @author Muhammad Zaky Zein
 * @version 2025.11.25
 */

import java.awt.Color;
import java.awt.Graphics;

public class Paddle
{

	public int paddleNumber;

	public int x, y, width = 50, height = 250;

	public int score;

	public Paddle(Pong pong, int paddleNumber) {
		this.paddleNumber = paddleNumber;

		if (paddleNumber == 1) this.x = 0;
		if (paddleNumber == 2) this.x = pong.width - width;

		this.y = pong.height / 2 - this.height / 2;
	}

	public void render(Graphics g) {
		g.setColor(Color.WHITE);
		g.fillRect(x, y, width, height);
	}

	public void move(boolean up) {
		int speed = 15;

		if (up) {
			if (y - speed > 0) y -= speed;
			else y = 0;
		} else {
			if (y + height + speed < Pong.pong.height) y += speed;
			else y = Pong.pong.height - height;
		}
	}
}


Renderer.java:

/**
 * Write a description of class Renderer here.
 *
 * @author Muhammad Zaky Zein
 * @version 2025.11.25
 */

import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

public class Renderer extends JPanel
{
	private static final long serialVersionUID = 1L;

	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);

		Pong.pong.render((Graphics2D) g);
	}

}

Pong.java

/**
 * Write a description of class Pong here.
 *
 * @author Muhammad Zaky Zein
 * @version 2025.11.25
 */

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.Timer;

public class Pong implements ActionListener, KeyListener
{
	public static Pong pong;
	public int width = 700, height = 700;
	public Renderer renderer;
	public Paddle player1;
	public Paddle player2;
	public Ball ball;
	public boolean bot = false, selectingDifficulty;
	public boolean w, s, up, down;
	public int gameStatus = 0, scoreLimit = 7, playerWon; //0 = Menu, 1 = Paused, 2 = Playing, 3 = Over
	public int botDifficulty, botMoves, botCooldown = 0;
	public Random random;
	public JFrame jframe;

	public Pong() {
		Timer timer = new Timer(20, this);
		random = new Random();

		jframe = new JFrame("Pong");

		renderer = new Renderer();

		jframe.setSize(width + 15, height + 35);
		jframe.setVisible(true);
		jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		jframe.add(renderer);
		jframe.addKeyListener(this);

		timer.start();
	}

	public void start() {
		gameStatus = 2;
		player1 = new Paddle(this, 1);
		player2 = new Paddle(this, 2);
		ball = new Ball(this);
	}

	public void update() {
		if (player1.score >= scoreLimit) {
			playerWon = 1;
			gameStatus = 3;
		}

		if (player2.score >= scoreLimit) {
			gameStatus = 3;
			playerWon = 2;
		}

		if (w) player1.move(true);
		if (s) player1.move(false);

		if (!bot) {
			if (up) player2.move(true);
			if (down) player2.move(false);
		} else {
			if (botCooldown > 0) {
				botCooldown--;

				if (botCooldown == 0) botMoves = 0;
			}

			if (botMoves < 10) {
				if (player2.y + player2.height / 2 < ball.y) {
					player2.move(false);
					botMoves++;
				}

				if (player2.y + player2.height / 2 > ball.y) {
					player2.move(true);
					botMoves++;
				}

				if (botDifficulty == 0) botCooldown = 20;
				if (botDifficulty == 1) botCooldown = 15;
				if (botDifficulty == 2) botCooldown = 10;
			}
		}

		ball.update(player1, player2);
	}

	public void render(Graphics2D g) {
		g.setColor(Color.BLACK);
		g.fillRect(0, 0, width, height);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

		if (gameStatus == 0) {
			g.setColor(Color.WHITE);
			g.setFont(new Font("Arial", 1, 50));

			g.drawString("PONG", width / 2 - 75, 50);

			if (!selectingDifficulty) {
				g.setFont(new Font("Arial", 1, 30));

				g.drawString("Press Space to Play", width / 2 - 150, height / 2 - 25);
				g.drawString("Press Shift to Play with Bot", width / 2 - 200, height / 2 + 25);
				g.drawString("<< Score Limit: " + scoreLimit + " >>", width / 2 - 150, height / 2 + 75);
			}
		}

		if (selectingDifficulty) {
			String string = botDifficulty == 0 ? "Easy" : (botDifficulty == 1 ? "Medium" : "Hard");

			g.setFont(new Font("Arial", 1, 30));

			g.drawString("<< Bot Difficulty: " + string + " >>", width / 2 - 180, height / 2 - 25);
			g.drawString("Press Space to Play", width / 2 - 150, height / 2 + 25);
		}

		if (gameStatus == 1) {
			g.setColor(Color.WHITE);
			g.setFont(new Font("Arial", 1, 50));
			g.drawString("PAUSED", width / 2 - 103, height / 2 - 25);
		}

		if (gameStatus == 1 || gameStatus == 2) {
			g.setColor(Color.WHITE);

			g.setStroke(new BasicStroke(5f));

			g.drawLine(width / 2, 0, width / 2, height);

			g.setStroke(new BasicStroke(2f));

			g.drawOval(width / 2 - 150, height / 2 - 150, 300, 300);

			g.setFont(new Font("Arial", 1, 50));

			g.drawString(String.valueOf(player1.score), width / 2 - 90, 50);
			g.drawString(String.valueOf(player2.score), width / 2 + 65, 50);

			player1.render(g);
			player2.render(g);
			ball.render(g);
		}

		if (gameStatus == 3) {
			g.setColor(Color.WHITE);
			g.setFont(new Font("Arial", 1, 50));

			g.drawString("PONG", width / 2 - 75, 50);

			if (bot && playerWon == 2) g.drawString("The Bot Wins!", width / 2 - 170, 200);
			else g.drawString("Player " + playerWon + " Wins!", width / 2 - 165, 200);

			g.setFont(new Font("Arial", 1, 30));

			g.drawString("Press Space to Play Again", width / 2 - 185, height / 2 - 25);
			g.drawString("Press ESC for Menu", width / 2 - 140, height / 2 + 25);
		}
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		if (gameStatus == 2) update();

		renderer.repaint();
	}

	public static void main(String[] args) {
		pong = new Pong();
	}

	@Override
	public void keyPressed(KeyEvent e) {
		int id = e.getKeyCode();

		if (id == KeyEvent.VK_W) w = true;
		else if (id == KeyEvent.VK_S) s = true;
		else if (id == KeyEvent.VK_UP) up = true;
		else if (id == KeyEvent.VK_DOWN) down = true;
		else if (id == KeyEvent.VK_RIGHT) {
			if (selectingDifficulty) {
				if (botDifficulty < 2) botDifficulty++;
				else botDifficulty = 0;
			} else if (gameStatus == 0) scoreLimit++;
		} else if (id == KeyEvent.VK_LEFT) {
			if (selectingDifficulty) {
				if (botDifficulty > 0) botDifficulty--;
				else botDifficulty = 2;
			} else if (gameStatus == 0 && scoreLimit > 1) scoreLimit--;
		} else if (id == KeyEvent.VK_ESCAPE && (gameStatus == 2 || gameStatus == 3)) gameStatus = 0;
		else if (id == KeyEvent.VK_SHIFT && gameStatus == 0) {
			bot = true;
			selectingDifficulty = true;
		} else if (id == KeyEvent.VK_SPACE) {
			if (gameStatus == 0 || gameStatus == 3) {
				if (!selectingDifficulty) bot = false;
				else selectingDifficulty = false;

				start();
			} else if (gameStatus == 1) gameStatus = 2;
			else if (gameStatus == 2) gameStatus = 1;
		}
	}

	@Override
	public void keyReleased(KeyEvent e) {
		int id = e.getKeyCode();

		if (id == KeyEvent.VK_W) w = false;
		else if (id == KeyEvent.VK_S) s = false;
		else if (id == KeyEvent.VK_UP) up = false;
		else if (id == KeyEvent.VK_DOWN) down = false;
	}
	
        @Override
	public void keyTyped(KeyEvent e){

	}
}


Output:





Muhammad Zaky Zein
5025241148
PBO A


Comments