Rabu, 03 Desember 2025

PBO A - Pertemuan 15 - Pong!

Nama : Erlangga Rizqi Dwi Raswanto

NRP : 5025241179

Kelas : PBO A


1. Pendahuluan

Pada pertemuan 15, kita mempraktikkan pembuatan game arcade sederhana “Pong” menggunakan Java GUI (AWT & Swing).

Secara singkat, Pong adalah salah satu game video generasi awal yang terinspirasi dari permainan tenis meja (ping-pong). Secara visual, game ini sangat sederhana: hanya terdiri dari dua paddle (kayu pemukul) dan satu bola yang memantul ke kanan dan kiri. 

Di pertemuan ini, saya mengimplementasikan game Pong dengan Java Swing yang terdiri dari beberapa kelas: PongGame, GameFrame, GamePanel, Paddle, dan Ball.


2. Tujuan Praktikum

Tujuan dari tugas pertemuan 15 adalah:

  1. Menerapkan konsep GUI pada Java menggunakan Swing dan AWT.

  2. Memahami cara kerja game loop menggunakan Thread dan interface Runnable.

  3. Menggunakan event listener (KeyListener) untuk menangani input keyboard.

  4. Menerapkan pemodelan objek (OOP) ke dalam game (memecah game jadi beberapa class).

  5. Menerapkan logika sederhana collision detection dan scoring.


3. Perancangan Program

3.1 Skema Kelas

Secara garis besar, struktur program:

  • PongGame
    Kelas dengan main(). Tugasnya hanya membuat jendela game (GameFrame).

  • GameFrame
    Turunan dari JFrame. Ini adalah window utama yang berisi GamePanel.

  • GamePanel
    Turunan dari JPanel, sekaligus Runnable dan KeyListener.
    Bertanggung jawab untuk:

    • Menjalankan game loop (update & repaint)

    • Menggambar semua objek (paddle, ball, score, garis tengah)

    • Mengatur collision dan scoring

  • Paddle
    Mewakili objek paddle (kayu pemukul) milik Player 1 dan Player 2. Masing-masing bisa digerakkan dengan keyboard:

    • Player 1: W (naik), S (turun)

    • Player 2: UP dan DOWN

  • Ball
    Mewakili bola game. Bola memiliki posisi (x, y) dan kecepatan (xVelocity, yVelocity) yang di-update di setiap frame.

3.2 Alur Program

  1. Program dimulai dari PongGame.main(), yang membuat objek GameFrame.

  2. GameFrame membuat GamePanel dan menampilkannya.

  3. Di dalam GamePanel, constructor:

    • Membuat dua paddle dan satu bola.

    • Mengatur ukuran panel dan background.

    • Menambahkan KeyListener.

    • Memulai Thread game.

  4. Thread menjalankan method run(), yang berisi game loop:

    • Panggil move() → update posisi paddle & bola.

    • Panggil checkCollision() → cek pantulan & scoring.

    • Panggil repaint() → memicu paint() dan menggambar ulang layar.

  5. Saat user menekan/melepas tombol, event akan diteruskan ke Paddle melalui keyPressed() dan keyReleased().


4. Implementasi Program

4.1 PongGame.java

/**
 * Kelas utama untuk menjalankan game Pong.
 * Memanggil GameFrame sebagai window utama.
 */
public class PongGame {
    public static void main(String[] args) {
        new GameFrame();
    }
}

4.2 GameFrame.java

import javax.swing.JFrame;

/**
 * GameFrame
 * Window utama untuk game Pong.
 * Berisi satu GamePanel di dalamnya.
 */
public class GameFrame extends JFrame {

    public GameFrame() {
        this.add(new GamePanel());
        this.setTitle("Pong - Erlangga");
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.pack();                // ukuran frame mengikuti GamePanel
        this.setLocationRelativeTo(null); // tampil di tengah layar
        this.setVisible(true);
    }
}

4.3 GamePanel.java

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

/**
 * GamePanel
 * Panel utama yang menangani:
 * - Gambar objek (paddle, bola, score)
 * - Game loop (Runnable)
 * - Input keyboard (KeyListener)
 */
public class GamePanel extends JPanel implements Runnable, KeyListener {

    static final int GAME_WIDTH = 800;
    static final int GAME_HEIGHT = 600;
    static final Dimension SCREEN_SIZE = new Dimension(GAME_WIDTH, GAME_HEIGHT);

    Thread gameThread;
    Image image;
    Graphics graphics;

    Paddle paddle1;
    Paddle paddle2;
    Ball ball;

    int player1Score = 0;
    int player2Score = 0;

    boolean running = true;

    public GamePanel() {
        newPaddles();
        newBall();

        this.setPreferredSize(SCREEN_SIZE);
        this.setBackground(Color.BLACK);
        this.setFocusable(true);
        this.addKeyListener(this);

        gameThread = new Thread(this);
        gameThread.start();
    }

    /**
     * Membuat bola baru di tengah.
     */
    public void newBall() {
        ball = new Ball(GAME_WIDTH / 2 - Ball.DIAMETER / 2,
                        GAME_HEIGHT / 2 - Ball.DIAMETER / 2);
    }

    /**
     * Membuat paddle baru (reset posisi).
     */
    public void newPaddles() {
        paddle1 = new Paddle(
                20,
                GAME_HEIGHT / 2 - Paddle.PADDLE_HEIGHT / 2,
                1
        );
        paddle2 = new Paddle(
                GAME_WIDTH - 20 - Paddle.PADDLE_WIDTH,
                GAME_HEIGHT / 2 - Paddle.PADDLE_HEIGHT / 2,
                2
        );
    }

    @Override
    public void paint(Graphics g) {
        // Double buffering: gambar ke image dulu
        image = createImage(getWidth(), getHeight());
        graphics = image.getGraphics();
        draw(graphics);
        g.drawImage(image, 0, 0, this);
    }

    /**
     * Menggambar semua objek game.
     */
    public void draw(Graphics g) {
        // Background
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);

        // Garis tengah putus-putus
        g.setColor(Color.GRAY);
        for (int i = 0; i < GAME_HEIGHT; i += 30) {
            g.fillRect(GAME_WIDTH / 2 - 2, i, 4, 20);
        }

        // Score
        g.setColor(Color.WHITE);
        g.setFont(new Font("Consolas", Font.BOLD, 32));
        g.drawString(String.valueOf(player1Score), GAME_WIDTH / 2 - 60, 40);
        g.drawString(String.valueOf(player2Score), GAME_WIDTH / 2 + 40, 40);

        // Paddle & bola
        paddle1.draw(g);
        paddle2.draw(g);
        ball.draw(g);
    }

    /**
     * Update posisi paddle dan bola.
     */
    public void move() {
        paddle1.move();
        paddle2.move();
        ball.move();
    }

    /**
     * Cek benturan (collision) dan scoring.
     */
    public void checkCollision() {
        // Pantulan bola di tepi atas & bawah
        if (ball.y <= 0) {
            ball.setYDirection(-ball.yVelocity);
        }
        if (ball.y >= GAME_HEIGHT - Ball.DIAMETER) {
            ball.setYDirection(-ball.yVelocity);
        }

        // Batas gerak paddle (tidak boleh keluar layar)
        if (paddle1.y < 0) {
            paddle1.y = 0;
        }
        if (paddle1.y > GAME_HEIGHT - Paddle.PADDLE_HEIGHT) {
            paddle1.y = GAME_HEIGHT - Paddle.PADDLE_HEIGHT;
        }
        if (paddle2.y < 0) {
            paddle2.y = 0;
        }
        if (paddle2.y > GAME_HEIGHT - Paddle.PADDLE_HEIGHT) {
            paddle2.y = GAME_HEIGHT - Paddle.PADDLE_HEIGHT;
        }

        // Bola menabrak paddle kiri
        if (ball.getBounds().intersects(paddle1.getBounds())) {
            ball.x = paddle1.x + Paddle.PADDLE_WIDTH;
            ball.setXDirection(Math.abs(ball.xVelocity)); // pastikan ke kanan
        }

        // Bola menabrak paddle kanan
        if (ball.getBounds().intersects(paddle2.getBounds())) {
            ball.x = paddle2.x - Ball.DIAMETER;
            ball.setXDirection(-Math.abs(ball.xVelocity)); // pastikan ke kiri
        }

        // Bola keluar di sisi kiri (poin untuk Player 2)
        if (ball.x <= 0) {
            player2Score++;
            newBall();
            newPaddles();
        }

        // Bola keluar di sisi kanan (poin untuk Player 1)
        if (ball.x >= GAME_WIDTH - Ball.DIAMETER) {
            player1Score++;
            newBall();
            newPaddles();
        }
    }

    @Override
    public void run() {
        // Game loop ~60 FPS
        long lastTime = System.nanoTime();
        double ticksPerSecond = 60.0;
        double ns = 1000000000 / ticksPerSecond;
        double delta = 0;

        while (running) {
            long now = System.nanoTime();
            delta += (now - lastTime) / ns;
            lastTime = now;

            while (delta >= 1) {
                move();
                checkCollision();
                repaint();
                delta--;
            }
        }
    }

    // ==== KeyListener ====

    @Override
    public void keyPressed(KeyEvent e) {
        paddle1.keyPressed(e);
        paddle2.keyPressed(e);
    }

    @Override
    public void keyReleased(KeyEvent e) {
        paddle1.keyReleased(e);
        paddle2.keyReleased(e);
    }

    @Override
    public void keyTyped(KeyEvent e) {
        // tidak digunakan
    }
}

4.4 Paddle.java

import java.awt.*;
import java.awt.event.KeyEvent;

/**
 * Paddle
 * Mewakili paddle kiri dan kanan.
 * Kontrol:
 * - Paddle 1 (kiri) : W / S
 * - Paddle 2 (kanan): UP / DOWN
 */
public class Paddle {

    public static final int PADDLE_WIDTH = 20;
    public static final int PADDLE_HEIGHT = 100;

    int x;
    int y;
    int yVelocity;
    int speed = 10;

    int paddleNumber; // 1 = kiri, 2 = kanan

    public Paddle(int x, int y, int paddleNumber) {
        this.x = x;
        this.y = y;
        this.paddleNumber = paddleNumber;
    }

    public void draw(Graphics g) {
        g.setColor(Color.WHITE);
        g.fillRect(x, y, PADDLE_WIDTH, PADDLE_HEIGHT);
    }

    public void move() {
        y += yVelocity;
    }

    public Rectangle getBounds() {
        return new Rectangle(x, y, PADDLE_WIDTH, PADDLE_HEIGHT);
    }

    /**
     * Mengatur kecepatan paddle saat tombol ditekan.
     */
    public void keyPressed(KeyEvent e) {
        switch (e.getKeyCode()) {
            // Paddle 1 (kiri): W/S
            case KeyEvent.VK_W:
                if (paddleNumber == 1) {
                    yVelocity = -speed;
                }
                break;
            case KeyEvent.VK_S:
                if (paddleNumber == 1) {
                    yVelocity = speed;
                }
                break;

            // Paddle 2 (kanan): UP/DOWN
            case KeyEvent.VK_UP:
                if (paddleNumber == 2) {
                    yVelocity = -speed;
                }
                break;
            case KeyEvent.VK_DOWN:
                if (paddleNumber == 2) {
                    yVelocity = speed;
                }
                break;
        }
    }

    /**
     * Menghentikan paddle saat tombol dilepas.
     */
    public void keyReleased(KeyEvent e) {
        switch (e.getKeyCode()) {
            case KeyEvent.VK_W:
            case KeyEvent.VK_S:
                if (paddleNumber == 1) {
                    yVelocity = 0;
                }
                break;
            case KeyEvent.VK_UP:
            case KeyEvent.VK_DOWN:
                if (paddleNumber == 2) {
                    yVelocity = 0;
                }
                break;
        }
    }
}

4.5 Ball.java

import java.awt.*;
import java.util.Random;

/**
 * Ball
 * Mewakili bola pada game Pong.
 * Memiliki posisi, kecepatan, dan bisa dipantulkan.
 */
public class Ball {

    public static final int DIAMETER = 30;

    int x;
    int y;
    int xVelocity;
    int yVelocity;
    int initialSpeed = 5;

    Random random;

    public Ball(int x, int y) {
        this.x = x;
        this.y = y;
        this.random = new Random();

        // Arah awal bola random (kanan/kiri & atas/bawah)
        int randomXDirection = random.nextBoolean() ? 1 : -1;
        int randomYDirection = random.nextBoolean() ? 1 : -1;

        xVelocity = initialSpeed * randomXDirection;
        yVelocity = initialSpeed * randomYDirection;
    }

    public void setXDirection(int xDirection) {
        xVelocity = xDirection;
    }

    public void setYDirection(int yDirection) {
        yVelocity = yDirection;
    }

    /**
     * Update posisi bola berdasarkan velocity.
     */
    public void move() {
        x += xVelocity;
        y += yVelocity;
    }

    public void draw(Graphics g) {
        g.setColor(Color.GREEN);
        g.fillOval(x, y, DIAMETER, DIAMETER);
    }

    public Rectangle getBounds() {
        return new Rectangle(x, y, DIAMETER, DIAMETER);
    }
}

5. Cara Menjalankan Program

  1. Buat project Java baru

  2. Buat 5 file dengan nama:

    • PongGame.java

    • GameFrame.java

    • GamePanel.java

    • Paddle.java

    • Ball.java
      lalu copy–paste kode di atas sesuai nama kelasnya.

  3. Compile semua class.

  4. Jalankan program dari PongGame.main().

Kontrol Permainan

  • Paddle kiri (Player 1)

    • W → naik

    • S → turun

  • Paddle kanan (Player 2)

    • ↑ (UP) → naik

    • ↓ (DOWN) → turun

Setiap kali bola keluar dari sisi kiri/kanan layar, pemain lawan akan mendapatkan skor dan permainan otomatis mereset posisi bola dan paddle ke tengah.


PBO A - Pertemuan 14 - GUI

 Nama    : Erlangga Rizqi Dwi Raswanto

NRP      : 5025241179

Kelas     : PBO A

1. Pendahuluan

Pada pertemuan 14 ini, materi berfokus pada pembuatan antarmuka grafis (GUI) menggunakan Java Swing.
Berbeda dengan program console yang hanya menggunakan System.out.println, pada GUI kita mulai memakai:

  • JFrame sebagai window utama

  • JPanel sebagai kontainer komponen

  • Komponen input seperti JTextField, JPasswordField, JButton

  • Layout Manager untuk mengatur posisi komponen

  • Event Handling (misalnya ActionListener) untuk merespons aksi user seperti klik tombol.

Terdapat dua latihan utama:

  1. Latihan 1 – Membuat form login dengan username dan password.

  2. Latihan 2 – Membuat aplikasi Image Viewer sederhana untuk membuka dan menampilkan gambar.


2. Latihan 1 – Form Login (Username & Password)

2.1 Tujuan

  • Memahami cara membuat window (JFrame) sederhana.

  • Menggunakan komponen input dasar: JTextField, JPasswordField, JButton, dan JLabel.

  • Menggunakan layout manager (GridBagLayout) untuk mengatur tampilan formulir.

  • Menerapkan event handling dengan ActionListener untuk memproses aksi tombol Login dan Reset.

2.2 Desain Program

Form login memiliki komponen sebagai berikut:

  • Label Username dan Password

  • Field input:

    • JTextField untuk username

    • JPasswordField untuk password

  • Dua tombol:

    • Login → mengecek apakah username & password benar

    • Reset → mengosongkan input

  • Label status di bawah untuk menampilkan pesan error / sukses.

Validasi sederhana:

  • Username benar: "admin"

  • Password benar: "12345"

Jika benar → tampil JOptionPane dan label status hijau.
Jika salah → label status merah: “Username atau password salah.”


2.3 Source Code LoginForm.java


import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * LoginForm
 * Form login sederhana dengan username & password,
 * tombol Login dan Reset, serta label status.
 */
public class LoginForm extends JFrame implements ActionListener {

    private JTextField usernameField;
    private JPasswordField passwordField;
    private JButton loginButton;
    private JButton resetButton;
    private JLabel statusLabel;

    public LoginForm() {
        // Judul window
        setTitle("Form Login");
        // Ukuran window
        setSize(400, 220);
        // Tutup aplikasi saat window di-close
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // Posisikan di tengah layar
        setLocationRelativeTo(null);

        // Panel utama dengan GridBagLayout
        JPanel panel = new JPanel(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(8, 8, 8, 8); // jarak antar komponen
        gbc.fill = GridBagConstraints.HORIZONTAL;

        // Label Username
        JLabel usernameLabel = new JLabel("Username:");
        gbc.gridx = 0;
        gbc.gridy = 0;
        panel.add(usernameLabel, gbc);

        // TextField Username
        usernameField = new JTextField(15);
        gbc.gridx = 1;
        gbc.gridy = 0;
        panel.add(usernameField, gbc);

        // Label Password
        JLabel passwordLabel = new JLabel("Password:");
        gbc.gridx = 0;
        gbc.gridy = 1;
        panel.add(passwordLabel, gbc);

        // PasswordField
        passwordField = new JPasswordField(15);
        gbc.gridx = 1;
        gbc.gridy = 1;
        panel.add(passwordField, gbc);

        // Tombol Login dan Reset
        loginButton = new JButton("Login");
        resetButton = new JButton("Reset");

        loginButton.addActionListener(this);
        resetButton.addActionListener(this);

        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0));
        buttonPanel.add(loginButton);
        buttonPanel.add(resetButton);

        gbc.gridx = 0;
        gbc.gridy = 2;
        gbc.gridwidth = 2;
        gbc.anchor = GridBagConstraints.EAST;
        panel.add(buttonPanel, gbc);

        // Label Status
        statusLabel = new JLabel(" ");
        statusLabel.setForeground(Color.RED);
        gbc.gridx = 0;
        gbc.gridy = 3;
        gbc.gridwidth = 2;
        gbc.anchor = GridBagConstraints.CENTER;
        panel.add(statusLabel, gbc);

        // Tambahkan panel ke frame
        add(panel);

        // Tampilkan GUI
        setVisible(true);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        Object source = e.getSource();

        // Tombol Login ditekan
        if (source == loginButton) {
            String username = usernameField.getText();
            String password = new String(passwordField.getPassword());

            if (username.equals("admin") && password.equals("12345")) {
                statusLabel.setForeground(Color.GREEN.darker());
                statusLabel.setText("Login berhasil.");
                JOptionPane.showMessageDialog(this,
                        "Selamat datang, " + username + "!",
                        "Login Berhasil",
                        JOptionPane.INFORMATION_MESSAGE);
            } else {
                statusLabel.setForeground(Color.RED);
                statusLabel.setText("Username atau password salah.");
            }
        }
        // Tombol Reset ditekan
        else if (source == resetButton) {
            usernameField.setText("");
            passwordField.setText("");
            statusLabel.setForeground(Color.RED);
            statusLabel.setText(" ");
        }
    }

    public static void main(String[] args) {
        // Jalankan GUI di Event Dispatch Thread
        SwingUtilities.invokeLater(() -> new LoginForm());
    }
}

3. Latihan 2 – Aplikasi Image Viewer Sederhana

3.1 Tujuan

  • Memahami penggunaan komponen JFileChooser untuk memilih file dari disk.

  • Menampilkan gambar pada panel khusus dengan melakukan override paintComponent.

  • Mengorganisasi program menjadi beberapa kelas dengan tanggung jawab yang jelas:

    • Kelas yang menyimpan data gambar

    • Kelas panel untuk menggambar gambar

    • Kelas helper untuk membuka file

    • Kelas main / viewer yang mengatur window dan menu

3.2 Struktur Program

Struktur project (package default):

  • OFImage.java → wrapper untuk BufferedImage

  • ImagePanel.java → panel yang menampilkan gambar

  • ImageFileManager.java → baca file gambar dari disk

  • ImageViewer.java → window utama (JFrame) + menu "Open Image" dan "Exit"

Alur kerja:

  1. User menjalankan ImageViewer.

  2. User memilih menu File → Open Image.

  3. ImageFileManager membuka file menggunakan JFileChooser dan membaca BufferedImage.

  4. Gambar dibungkus ke OFImage lalu dikirim ke ImagePanel.

  5. ImagePanel memanggil repaint() dan menggambar gambar di tengah panel.


3.3 Source Code

Pastikan semua file berikut disimpan di folder project yang sama.


3.3.1 OFImage.java

import java.awt.image.BufferedImage;

/**
 * OFImage
 * Kelas pembungkus (wrapper) untuk BufferedImage
 * agar lebih mudah dikelola dalam aplikasi.
 */
public class OFImage {
    private final BufferedImage image;

    public OFImage(BufferedImage image) {
        this.image = image;
    }

    public BufferedImage getImage() {
        return image;
    }

    public int getWidth() {
        return (image != null) ? image.getWidth() : 0;
    }

    public int getHeight() {
        return (image != null) ? image.getHeight() : 0;
    }
}

3.3.2 ImagePanel.java

import javax.swing.*;
import java.awt.*;

/**
 * ImagePanel
 * Panel yang bertanggung jawab menampilkan objek OFImage.
 * Gambar akan dirender di tengah panel.
 */
public class ImagePanel extends JPanel {

    private OFImage currentImage;

    public ImagePanel() {
        setBackground(Color.DARK_GRAY);
    }

    /**
     * Set gambar yang akan ditampilkan.
     */
    public void setImage(OFImage image) {
        this.currentImage = image;
        revalidate();
        repaint();
    }

    /**
     * Hapus gambar dari panel.
     */
    public void clearImage() {
        this.currentImage = null;
        revalidate();
        repaint();
    }

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

        if (currentImage != null && currentImage.getImage() != null) {
            int imgW = currentImage.getWidth();
            int imgH = currentImage.getHeight();

            // Hitung posisi supaya gambar di tengah
            int x = (getWidth() - imgW) / 2;
            int y = (getHeight() - imgH) / 2;

            g.drawImage(currentImage.getImage(), x, y, this);
        } else {
            // Jika belum ada gambar
            g.setColor(Color.WHITE);
            g.drawString("Belum ada gambar yang dimuat.", 10, 20);
        }
    }

    @Override
    public Dimension getPreferredSize() {
        if (currentImage != null) {
            return new Dimension(currentImage.getWidth(), currentImage.getHeight());
        }
        return new Dimension(600, 400);
    }
}

3.3.3 ImageFileManager.java

import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

/**
 * ImageFileManager
 * Mengurus pemilihan file gambar dari disk dan
 * mengembalikannya sebagai OFImage.
 */
public class ImageFileManager {

    /**
     * Buka dialog untuk memilih file gambar dan
     * mengembalikan OFImage jika berhasil, null jika gagal/batal.
     */
    public static OFImage loadImage() {
        JFileChooser fileChooser = new JFileChooser();

        FileNameExtensionFilter filter = new FileNameExtensionFilter(
                "Image Files (JPG, PNG, GIF)", "jpg", "jpeg", "png", "gif");
        fileChooser.setFileFilter(filter);

        int result = fileChooser.showOpenDialog(null);

        if (result == JFileChooser.APPROVE_OPTION) {
            File selectedFile = fileChooser.getSelectedFile();
            try {
                BufferedImage bImg = ImageIO.read(selectedFile);
                if (bImg != null) {
                    return new OFImage(bImg);
                } else {
                    JOptionPane.showMessageDialog(null,
                            "File yang dipilih bukan gambar yang valid.",
                            "Error",
                            JOptionPane.ERROR_MESSAGE);
                }
            } catch (IOException e) {
                JOptionPane.showMessageDialog(null,
                        "Gagal membaca file gambar: " + e.getMessage(),
                        "Error",
                        JOptionPane.ERROR_MESSAGE);
            }
        }
        return null;
    }
}

3.3.4 ImageViewer.java

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * ImageViewer
 * Aplikasi Image Viewer sederhana:
 * - Menu File -> Open Image
 * - Menu File -> Exit
 * Menampilkan gambar di tengah window.
 */
public class ImageViewer {

    private JFrame frame;
    private ImagePanel imagePanel;

    public ImageViewer() {
        makeFrame();
    }

    /**
     * Membuat dan mengatur frame utama beserta menu dan panel.
     */
    private void makeFrame() {
        frame = new JFrame("Aplikasi Image Viewer Sederhana");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Panel untuk menampilkan gambar
        imagePanel = new ImagePanel();
        JScrollPane scrollPane = new JScrollPane(imagePanel);

        // Menu bar
        JMenuBar menuBar = new JMenuBar();
        JMenu fileMenu = new JMenu("File");
        JMenuItem openItem = new JMenuItem("Open Image");
        JMenuItem clearItem = new JMenuItem("Clear");
        JMenuItem exitItem = new JMenuItem("Exit");

        // Event menu Open
        openItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                openFile();
            }
        });

        // Event menu Clear
        clearItem.addActionListener(e -> imagePanel.clearImage());

        // Event menu Exit
        exitItem.addActionListener(e -> System.exit(0));

        fileMenu.add(openItem);
        fileMenu.add(clearItem);
        fileMenu.addSeparator();
        fileMenu.add(exitItem);
        menuBar.add(fileMenu);

        frame.setJMenuBar(menuBar);
        frame.add(scrollPane, BorderLayout.CENTER);

        frame.setSize(800, 600);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    /**
     * Memanggil ImageFileManager untuk memilih dan memuat gambar.
     */
    private void openFile() {
        OFImage image = ImageFileManager.loadImage();
        if (image != null) {
            imagePanel.setImage(image);
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new ImageViewer());
    }
}


PBO A - Pertemuan 13 - Abstract Class

Nama : Erlangga Rizqi Dwi Raswanto
NRP : 5025241179
Kelas : PBO A

Pada pertemuan ke-13, materi yang dibahas adalah abstract class dalam pemrograman berorientasi objek (OOP) menggunakan Java. Abstract class berada pada posisi cukup tinggi dalam hierarki kelas dan berfungsi sebagai blueprint bagi kelas turunannya.

Ciri utama abstract class:

  • Tidak dapat di-new (tidak bisa dibuat objek langsung).

  • Dapat berisi:

    • method konkret (punya body/isi),

    • method abstrak (hanya deklarasi, tanpa body).

  • Subclass wajib mengimplementasikan semua method abstrak, jika tidak maka subclass tersebut juga harus dideklarasikan sebagai abstract.

Pada laporan ini, implementasi abstract class ditunjukkan melalui dua latihan:

  1. Abstract class MakhlukHidup yang diturunkan ke Manusia, Hewan, dan Tumbuhan.

  2. Simulasi sederhana Foxes and Rabbits yang diubah agar menggunakan abstract class Animal.

  • Abstract class: kelas yang tidak bisa diinstansiasi secara langsung dan biasanya berperan sebagai kerangka umum.

  • Abstract method: method yang hanya memiliki signature tanpa implementasi. Implementasinya dipaksa dilakukan di subclass.

  • Abstract class cocok dipakai ketika:

    • Ada perilaku umum yang bisa disediakan bentuk default (method konkret).

    • Ada perilaku khusus yang harus berbeda di tiap subclass (method abstrak).

  • Abstract class sangat berhubungan dengan:

    • Inheritance (pewarisan)

    • Polymorphism (satu referensi parent bisa menyimpan berbagai objek subclass dan memanggil method yang sesuai jenis objeknya).


Latihan 1 – Abstract Class Makhluk Hidup

3.1 Deskripsi Program

Pada latihan ini dibuat sebuah hirarki kelas:

  • MakhlukHidup → abstract class sebagai induk.

  • Manusia, Hewan, Tumbuhan → kelas turunan yang mengimplementasikan cara bernapas dan bergerak masing-masing.

Konsep yang digunakan:

  • Abstraksi: Makhluk hidup secara umum punya nama, bisa bernapas, dan bergerak, tetapi cara bernapas & bergeraknya berbeda.

  • Inheritance: Manusia, Hewan, dan Tumbuhan mewarisi struktur dasar dari MakhlukHidup.

  • Polymorphism: Objek disimpan dalam tipe MakhlukHidup tetapi saat method dipanggil, implementasi yang dieksekusi sesuai tipe konkret (Manusia/Hewan/Tumbuhan).


3.2 Kode Program Latihan 1

3.2.1 MakhlukHidup.java

public abstract class MakhlukHidup {
    protected String nama;

    public MakhlukHidup(String nama) {
        this.nama = nama;
    }

    // Method abstrak: wajib diimplementasikan subclass
    public abstract void bernapas();

    public abstract void bergerak();

    // Method konkret: berlaku umum untuk semua makhluk hidup
    public void info() {
        System.out.println("Nama: " + nama);
    }
}

3.2.2 Manusia.java

public class Manusia extends MakhlukHidup {
    private String pekerjaan;

    public Manusia(String nama, String pekerjaan) {
        super(nama);
        this.pekerjaan = pekerjaan;
    }

    @Override
    public void bernapas() {
        System.out.println(nama + " bernapas menggunakan paru-paru.");
    }

    @Override
    public void bergerak() {
        System.out.println(nama + " berjalan menggunakan kaki.");
    }

    @Override
    public void info() {
        super.info();
        System.out.println("Pekerjaan: " + pekerjaan);
    }
}

3.2.3 Hewan.java

public class Hewan extends MakhlukHidup {
    private String jenisHewan;

    public Hewan(String nama, String jenisHewan) {
        super(nama);
        this.jenisHewan = jenisHewan;
    }

    @Override
    public void bernapas() {
        System.out.println(nama + " bernapas menggunakan paru-paru atau insang (sesuai jenis).");
    }

    @Override
    public void bergerak() {
        System.out.println(nama + " bergerak dengan cara khas hewan " + jenisHewan + ".");
    }

    @Override
    public void info() {
        super.info();
        System.out.println("Jenis Hewan: " + jenisHewan);
    }
}

3.2.4 Tumbuhan.java

public class Tumbuhan extends MakhlukHidup {
    private String jenisTumbuhan;

    public Tumbuhan(String nama, String jenisTumbuhan) {
        super(nama);
        this.jenisTumbuhan = jenisTumbuhan;
    }

    @Override
    public void bernapas() {
        System.out.println(nama + " bernapas melalui proses fotosintesis dan respirasi sel.");
    }

    @Override
    public void bergerak() {
        System.out.println(nama + " tidak bergerak aktif, tetapi tumbuh dan mengikuti arah cahaya.");
    }

    @Override
    public void info() {
        super.info();
        System.out.println("Jenis Tumbuhan: " + jenisTumbuhan);
    }
}

3.2.5 MainMakhlukHidup.java

public class MainMakhlukHidup {
    public static void main(String[] args) {
        MakhlukHidup manusia = new Manusia("Andi", "Programmer");
        MakhlukHidup hewan = new Hewan("Kucing", "Mamalia");
        MakhlukHidup tumbuhan = new Tumbuhan("Mangga", "Pohon Buah");

        System.out.println("=== Informasi Makhluk Hidup ===");

        System.out.println("\n-- Manusia --");
        manusia.info();
        manusia.bernapas();
        manusia.bergerak();

        System.out.println("\n-- Hewan --");
        hewan.info();
        hewan.bernapas();
        hewan.bergerak();

        System.out.println("\n-- Tumbuhan --");
        tumbuhan.info();
        tumbuhan.bernapas();
        tumbuhan.bergerak();
    }
}

3.3 Penjelasan Konsep OOP pada Latihan 1

  1. Abstract Class & Method

    • MakhlukHidup dideklarasikan abstract.

    • bernapas() dan bergerak() adalah method abstrak → tidak punya body di superclass, dipaksa diimplementasikan oleh Manusia, Hewan, Tumbuhan.

  2. Inheritance

    • Manusia extends MakhlukHidup

    • Hewan extends MakhlukHidup

    • Tumbuhan extends MakhlukHidup
      Ketiga kelas turunan otomatis punya atribut nama dan method info().

  3. Polymorphism

    • Di MainMakhlukHidup, variabel bertipe MakhlukHidup bisa menyimpan objek Manusia, Hewan, dan Tumbuhan.

    • Pemanggilan manusia.bernapas(), hewan.bernapas(), dll, akan mengeksekusi implementasi sesuai objek konkret.


4. Latihan 2 – Abstract Class pada Simulasi Foxes and Rabbits

4.1 Deskripsi Program

Latihan kedua menggunakan contoh simulasi dari buku: Foxes and Rabbits. Ide utamanya:

  • Terdapat sebuah grid (Field) yang berisi:

    • Rabbit (kelinci)

    • Fox (rubah)

  • Setiap langkah simulasi (step):

    • Rabbit bergerak mencari sel kosong agar tetap hidup.

    • Fox bergerak mencari Rabbit untuk dimakan. Jika tidak ada makanan dan usia/energi habis → mati.

  • Struktur umum hewan dimodelkan dalam abstract class Animal, sedangkan perilaku khusus ditulis di Rabbit dan Fox.

Konsep OOP yang ditekankan:

  • Abstract Class + Inheritance: AnimalRabbit dan Fox.

  • Polymorphism: Simulator hanya bekerja dengan tipe Animal, tetapi method act() yang dipanggil bisa milik Rabbit atau Fox sesuai objeknya.


4.2 Kode Program Latihan 2

4.2.1 Location.java

public class Location {
    private int row;
    private int col;

    public Location(int row, int col){
        this.row = row;
        this.col = col;
    }

    public int getRow(){
        return row;
    }

    public int getCol(){
        return col;
    }

    @Override
    public String toString() {
        return "(" + row + "," + col + ")";
    }
}

4.2.2 Field.java

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

public class Field {
    private Object[][] field;
    private int depth;
    private int width;

    public Field(int depth, int width){
        this.depth = depth;
        this.width = width;
        field = new Object[depth][width];
    }

    public int getDepth() {
        return depth;
    }

    public int getWidth() {
        return width;
    }

    public void clear(Location location){
        field[location.getRow()][location.getCol()] = null;
    }

    public void clear() {
        for (int row = 0; row < depth; row++) {
            for (int col = 0; col < width; col++) {
                field[row][col] = null;
            }
        }
    }

    public void place(Object object, Location location){
        field[location.getRow()][location.getCol()] = object;
    }

    public Object getObjectAt(Location location){
        return field[location.getRow()][location.getCol()];
    }

    /**
     * Mengembalikan salah satu lokasi kosong di sekitar lokasi yang diberikan
     * (dipilih secara acak). Jika tidak ada yang kosong, mengembalikan null.
     */
    public Location freeAdjacentLocation(Location location){
        List<Location> free = new ArrayList<>();
        List<Location> adjacent = adjacentLocations(location);
        for(Location loc : adjacent){
            if(getObjectAt(loc) == null){
                free.add(loc);
            }
        }
        Collections.shuffle(free);
        return free.isEmpty() ? null : free.get(0);
    }

    /**
     * Mengembalikan daftar semua lokasi valid di sekitar (8 tetangga).
     */
    public List<Location> adjacentLocations(Location location){
        List<Location> locations = new ArrayList<>();
        int row = location.getRow();
        int col = location.getCol();

        for(int roffset = -1; roffset <= 1; roffset++){
            int nextRow = row + roffset;
            if(nextRow >= 0 && nextRow < depth){
                for(int coffset = -1; coffset <= 1; coffset++){
                    int nextCol = col + coffset;
                    if(nextCol >= 0 && nextCol < width && (roffset != 0 || coffset != 0)){
                        locations.add(new Location(nextRow, nextCol));
                    }
                }
            }
        }
        return locations;
    }
}

4.2.3 Animal.java (Abstract Class)

import java.util.List;

public abstract class Animal {
    protected int age;
    private boolean alive;
    protected Field field;
    protected Location location;

    public Animal(Field field, Location location){
        this.age = 0;
        this.alive = true;
        this.field = field;
        setLocation(location);
    }

    public boolean isAlive(){
        return alive;
    }

    public void setDead(){
        alive = false;
        if(location != null){
            field.clear(location);
            location = null;
            field = null;
        }
    }

    public Location getLocation(){
        return location;
    }

    public void setLocation(Location newLocation){
        if (location != null){
            field.clear(location);
        }
        location = newLocation;
        field.place(this, newLocation);
    }

    public Field getField(){
        return field;
    }

    /**
     * Method abstrak: setiap hewan harus menentukan sendiri
     * bagaimana dia berperilaku di setiap langkah simulasi.
     */
    public abstract void act(List<Animal> newAnimals);
}

4.2.4 Rabbit.java

import java.util.List;

public class Rabbit extends Animal {
    private static final int MAX_AGE = 40;

    public Rabbit(Field field, Location location){
        super(field, location);
    }

    @Override
    public void act(List<Animal> newAnimals){
        age++;
        // Jika umur melewati batas, kelinci mati
        if (age > MAX_AGE) {
            setDead();
        }

        // Jika masih hidup, coba bergerak ke lokasi kosong di sekitar
        if (isAlive()){
            Location newLocation = getField().freeAdjacentLocation(getLocation());
            if(newLocation != null){
                setLocation(newLocation);
            } else {
                // Tidak ada tempat bergerak → "terjepit" → mati
                setDead();
            }
        }
    }
}

4.2.5 Fox.java

import java.util.List;

public class Fox extends Animal {
    private static final int MAX_AGE = 150;
    private int foodLevel;

    public Fox(Field field, Location location){
        super(field, location);
        foodLevel = 20; // stok makanan awal
    }

    @Override
    public void act(List<Animal> newAnimals){
        age++;
        foodLevel--;

        // Cek batas umur atau kelaparan
        if (age > MAX_AGE || foodLevel <= 0) {
            setDead();
            return;
        }

        if(isAlive()){
            // Coba cari makanan dulu (Rabbit di sekitar)
            Location newLocation = findFood();
            if(newLocation == null){
                // Jika tidak ada makanan, cari lokasi kosong
                newLocation = getField().freeAdjacentLocation(getLocation());
            }
            if(newLocation != null){
                setLocation(newLocation);
            } else {
                // Tidak bisa bergerak kemana-mana → mati karena "terjepit"
                setDead();
            }
        }
    }

    /**
     * Mencari Rabbit pada lokasi-lokasi sekitar.
     * Jika ditemukan, Rabbit dimakan (setDead) dan Fox pindah ke lokasi tersebut.
     */
    private Location findFood(){
        List<Location> adjacent = getField().adjacentLocations(getLocation());
        for(Location loc : adjacent){
            Object animal = getField().getObjectAt(loc);
            if(animal instanceof Rabbit){
                Rabbit rabbit = (Rabbit) animal;
                if(rabbit.isAlive()){
                    rabbit.setDead();
                    // Makan kelinci → menambah foodLevel
                    foodLevel = 20;
                    return loc;
                }
            }
        }
        return null;
    }
}

4.2.6 SimulatorView.java (GUI Sederhana)

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;

public class SimulatorView extends JPanel {
    private static final int GRID_VIEW_SCALING_FACTOR = 6;

    private int gridWidth;
    private int gridHeight;
    private int xScale;
    private int yScale;
    private Field field;

    public SimulatorView(int height, int width) {
        gridHeight = height;
        gridWidth = width;
        setPreferredSize(new Dimension(
                width * GRID_VIEW_SCALING_FACTOR,
                height * GRID_VIEW_SCALING_FACTOR));
    }

    public void showStatus(Field field) {
        this.field = field;
        repaint();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (field != null) {
            xScale = getWidth() / gridWidth;
            if (xScale < 1) {
                xScale = GRID_VIEW_SCALING_FACTOR;
            }
            yScale = getHeight() / gridHeight;
            if (yScale < 1) {
                yScale = GRID_VIEW_SCALING_FACTOR;
            }

            for (int row = 0; row < gridHeight; row++) {
                for (int col = 0; col < gridWidth; col++) {
                    Object obj = field.getObjectAt(new Location(row, col));
                    if (obj != null) {
                        if (obj instanceof Rabbit) {
                            g.setColor(Color.ORANGE); // Kelinci
                        } else if (obj instanceof Fox) {
                            g.setColor(Color.BLUE);   // Rubah
                        }
                        g.fillRect(col * xScale, row * yScale, xScale, yScale);
                    }
                }
            }
        }
    }
}

4.2.7 Simulator.java (Main Simulasi)

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class Simulator {
    private Field field;
    private List<Animal> animals;
    private int step;

    private SimulatorView view;
    private JFrame frame;
    private JLabel stepLabel;
    private JLabel populationLabel;

    private static final double FOX_CREATION_PROBABILITY = 0.02;
    private static final double RABBIT_CREATION_PROBABILITY = 0.08;

    private static final int DEFAULT_DEPTH = 50;
    private static final int DEFAULT_WIDTH = 50;

    public Simulator() {
        this(DEFAULT_DEPTH, DEFAULT_WIDTH);
    }

    public Simulator(int depth, int width){
        step = 0;
        animals = new ArrayList<>();
        field = new Field(depth, width);

        frame = new JFrame("Fox and Rabbit Simulation - Abstract Class");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        stepLabel = new JLabel("Step: 0", JLabel.CENTER);
        populationLabel = new JLabel("Population: ", JLabel.CENTER);

        view = new SimulatorView(depth, width);

        frame.add(stepLabel, BorderLayout.NORTH);
        frame.add(view, BorderLayout.CENTER);
        frame.add(populationLabel, BorderLayout.SOUTH);

        frame.pack();
        frame.setVisible(true);

        populate();
        updateView();
    }

    public void runLongSimulation() {
        Timer timer = new Timer(100, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                simulateOneStep();
            }
        });
        timer.start();
    }

    public void simulateOneStep(){
        step++;
        List<Animal> newAnimals = new ArrayList<>();

        // Iterasi pada salinan list untuk menghindari ConcurrentModificationException
        for (Animal animal : new ArrayList<>(animals)){
            if(animal.isAlive()){
                animal.act(newAnimals);
            } else {
                animals.remove(animal);
            }
        }
        animals.addAll(newAnimals);

        updateView();
    }

    private void updateView() {
        stepLabel.setText("Step: " + step);
        populationLabel.setText(getPopulationDetails());
        view.showStatus(field);
    }

    private String getPopulationDetails() {
        int rabbitCount = 0;
        int foxCount = 0;
        for (Animal animal : animals) {
            if (animal instanceof Rabbit) {
                rabbitCount++;
            } else if (animal instanceof Fox) {
                foxCount++;
            }
        }
        return "Population - Rabbit: " + rabbitCount + " | Fox: " + foxCount;
    }

    private void populate(){
        Random rand = new Random();
        field.clear();
        animals.clear();

        int depth = field.getDepth();
        int width = field.getWidth();

        for(int row = 0; row < depth; row++){
            for(int col = 0; col < width; col++){
                Location location = new Location(row, col);
                double prob = rand.nextDouble();
                if(prob <= FOX_CREATION_PROBABILITY){
                    Fox fox = new Fox(field, location);
                    animals.add(fox);
                }
                else if(prob <= FOX_CREATION_PROBABILITY + RABBIT_CREATION_PROBABILITY){
                    Rabbit rabbit = new Rabbit(field, location);
                    animals.add(rabbit);
                }
            }
        }
    }

    public static void main(String[] args) {
        Simulator simulator = new Simulator();
        simulator.runLongSimulation();
    }
}

4.3 Penjelasan Konsep OOP pada Latihan 2

  1. Abstract Class dan Polymorphism

    • Animal adalah abstract class yang mendefinisikan kerangka umum hewan (age, alive, field, location, dan method act).

    • Rabbit dan Fox meng-override method act() dengan logika masing-masing.

    • Di Simulator, list animals bertipe List<Animal>, tapi isi sebenarnya bisa berupa Rabbit atau Fox. Saat animal.act(newAnimals) dipanggil, Java menjalankan versi method yang sesuai tipe konkret objek tersebut.

  2. Encapsulation

    • Atribut age, alive, field, location dikelola melalui method seperti setDead(), setLocation(), dan isAlive().

  3. Perilaku Spesifik

    • Rabbit:

      • Fokus bertahan hidup melalui gerakan.

      • Mati jika umur melewati MAX_AGE atau tidak punya tempat bergerak.

    • Fox:

      • Memiliki foodLevel sebagai representasi energi.

      • Mencari Rabbit di sekitar sebagai makanan (method findFood()).

      • Jika tidak makan dalam beberapa langkah, foodLevel habis → mati.

  4. Catatan Kritis

    • Simulasi di atas masih sederhana:

      • Belum ada mekanisme reproduksi (newAnimals belum digunakan untuk menambah hewan baru).

      • Pola populasi cenderung menuju kepunahan (tanpa kelahiran, hanya ada kematian).

    • Namun, dari sisi konsep abstract class & polymorphism, struktur ini sudah menunjukkan:

      • Satu abstract class Animal dapat menjadi dasar banyak jenis hewan.

      • Simulator cukup mengetahui Animal, tidak perlu tahu detail tiap subclass.


Senin, 01 Desember 2025

PBO A - Pertemuan 11 - Penerapan Inheritance pada Sistem Rental Kendaraan

 Nama    : Erlangga Rizqi Dwi Raswanto

NRP      : 5025241179

Kelas     : PBO A


Penerapan Inheritance pada Sistem Rental Kendaraan berbasis Java



  1. Menerapkan konsep inheritance (pewarisan) dalam Java dengan membuat satu kelas induk Kendaraan dan beberapa kelas turunan (Mobil, Motor, Sepeda).

  2. Menunjukkan bagaimana enkapsulasi digunakan untuk melindungi data (atribut dibuat private / protected, akses lewat getter/setter).

  3. Menunjukkan contoh polimorfisme lewat pemanggilan method yang sama (getInfo(), hitungBiaya()) pada objek-objek Kendaraan yang berbeda jenis.

  4. Mengembangkan contoh sederhana menjadi aplikasi kecil yang bisa:

    • menyimpan daftar kendaraan,

    • mencatat penyewa dan transaksi rental,

    • menampilkan kendaraan yang masih tersedia dan daftar transaksi.


2.  Konsep OOP yang Dipakai

  1. Inheritance (pewarisan)

    • Mobil, Motor, Sepeda mewarisi atribut umum dari Kendaraan.

    • Semua jenis kendaraan punya merk, model, tahunProduksi, dan status tersedia.

  2. Enkapsulasi

    • Atribut dibuat private / protected.

    • Akses data melalui method getter dan setter.

  3. Polimorfisme

    • List kendaraan bertipe List<Kendaraan>, tapi isinya bisa Mobil, Motor, atau Sepeda.

    • Saat dipanggil kendaraan.getInfo() atau kendaraan.hitungBiaya(...), yang dipanggil adalah versi method dari kelas turunan masing-masing (dynamic dispatch).

  4. Abstraksi

    • Kendaraan dibuat abstract: tidak dibuat objeknya langsung.

    • Method getInfo() dan hitungBiaya() dibuat abstrak → wajib dioverride di subclass.


3. Implementasi Kelas

3.1. Kendaraan.java

Peran:

  • Menyimpan atribut umum semua kendaraan: merk, model, tahunProduksi, tersedia.

  • Memberi kontrak method abstrak getInfo() dan hitungBiaya().

  • Menyediakan method isTersedia() dan setTersedia() untuk menandai apakah kendaraan masih bisa disewa atau tidak.

/**
 * Kelas induk abstrak untuk semua jenis kendaraan
 */
public abstract class Kendaraan {
    private String merk;
    private String model;
    private int tahunProduksi;
    private boolean tersedia;

    public Kendaraan(String merk, String model, int tahunProduksi) {
        this.merk = merk;
        this.model = model;
        this.tahunProduksi = tahunProduksi;
        this.tersedia = true; // default: masih tersedia untuk disewa
    }

    public String getMerk() {
        return merk;
    }

    public String getModel() {
        return model;
    }

    public int getTahunProduksi() {
        return tahunProduksi;
    }

    public boolean isTersedia() {
        return tersedia;
    }

    public void setTersedia(boolean tersedia) {
        this.tersedia = tersedia;
    }

    /**
     * Info dasar kendaraan: merk, model, tahun produksi.
     * Dipakai oleh subclass untuk menyusun info lengkap.
     */
    protected String getInfoDasar() {
        return merk + " " + model + " (" + tahunProduksi + ")";
    }

    /**
     * Harus dioverride oleh setiap subclass untuk menampilkan info lengkap.
     */
    public abstract String getInfo();

    /**
     * Menghitung biaya sewa berdasarkan lama hari.
     * Implementasi spesifik ada di masing-masing subclass.
     */
    public abstract double hitungBiaya(int lamaHari);
}

3.2. Mobil.java

Peran:

  • Turunan Kendaraan yang mewakili mobil.

  • Menambahkan atribut jumlahRoda.

  • Menentukan cara menampilkan info dan menghitung biaya sewa khusus mobil.

/**
 * Kelas Mobil sebagai turunan dari Kendaraan
 */
public class Mobil extends Kendaraan {
    private int jumlahRoda;

    public Mobil(String merk, String model, int tahunProduksi, int jumlahRoda) {
        super(merk, model, tahunProduksi);
        this.jumlahRoda = jumlahRoda;
    }

    public int getJumlahRoda() {
        return jumlahRoda;
    }

    @Override
    public String getInfo() {
        return "Mobil  : " + getInfoDasar() + ", jumlah roda: " + jumlahRoda;
    }

    @Override
    public double hitungBiaya(int lamaHari) {
        // contoh: tarif mobil Rp350.000 per hari
        double tarifPerHari = 350_000;
        return tarifPerHari * lamaHari;
    }
}

3.3. Motor.java

Peran:

  • Turunan Kendaraan yang mewakili motor.

  • Juga memakai jumlahRoda, biasanya 2.

  • Biaya sewa berbeda dari mobil.

/**
 * Kelas Motor sebagai turunan dari Kendaraan
 */
public class Motor extends Kendaraan {
    private int jumlahRoda;

    public Motor(String merk, String model, int tahunProduksi, int jumlahRoda) {
        super(merk, model, tahunProduksi);
        this.jumlahRoda = jumlahRoda;
    }

    public int getJumlahRoda() {
        return jumlahRoda;
    }

    @Override
    public String getInfo() {
        return "Motor  : " + getInfoDasar() + ", jumlah roda: " + jumlahRoda;
    }

    @Override
    public double hitungBiaya(int lamaHari) {
        // contoh: tarif motor Rp100.000 per hari
        double tarifPerHari = 100_000;
        return tarifPerHari * lamaHari;
    }
}

3.4. Sepeda.java

Peran:

  • Turunan Kendaraan yang mewakili sepeda.

  • Menambahkan atribut jenisSepeda (misalnya: Gunung, Balap, BMX, Lipat).

  • Biaya sewanya lebih murah.

/**
 * Kelas Sepeda sebagai turunan dari Kendaraan
 */
public class Sepeda extends Kendaraan {
    private String jenisSepeda;

    public Sepeda(String merk, String model, int tahunProduksi, String jenisSepeda) {
        super(merk, model, tahunProduksi);
        this.jenisSepeda = jenisSepeda;
    }

    public String getJenisSepeda() {
        return jenisSepeda;
    }

    @Override
    public String getInfo() {
        return "Sepeda : " + getInfoDasar() + ", jenis: " + jenisSepeda;
    }

    @Override
    public double hitungBiaya(int lamaHari) {
        // contoh: tarif sepeda Rp50.000 per hari
        double tarifPerHari = 50_000;
        return tarifPerHari * lamaHari;
    }
}

3.5. Penyewa.java

Peran:

  • Menyimpan data orang yang menyewa kendaraan.

  • Di sini disimpan nama dan noIdentitas (misal: NIK, KTP, SIM, atau ID lain).

/**
 * Menyimpan data penyewa kendaraan
 */
public class Penyewa {
    private String nama;
    private String noIdentitas;

    public Penyewa(String nama, String noIdentitas) {
        this.nama = nama;
        this.noIdentitas = noIdentitas;
    }

    public String getNama() {
        return nama;
    }

    public String getNoIdentitas() {
        return noIdentitas;
    }

    public String getInfo() {
        return nama + " (ID: " + noIdentitas + ")";
    }
}

3.6. Rental.java

Peran:

  • Menghubungkan satu Penyewa dengan satu Kendaraan.

  • Menyimpan lama sewa (hari) dan total biaya sewa.

  • Saat objek Rental dibuat, status kendaraan otomatis diubah jadi tidak tersedia.

/**
 * Mewakili satu transaksi rental kendaraan
 */
public class Rental {
    private Penyewa penyewa;
    private Kendaraan kendaraan;
    private int lamaHari;
    private double totalBiaya;

    public Rental(Penyewa penyewa, Kendaraan kendaraan, int lamaHari) {
        this.penyewa = penyewa;
        this.kendaraan = kendaraan;
        this.lamaHari = lamaHari;

        // saat dirental, kendaraan tidak lagi tersedia
        this.kendaraan.setTersedia(false);

        // hitung total biaya sewa menggunakan polimorfisme
        this.totalBiaya = kendaraan.hitungBiaya(lamaHari);
    }

    public Penyewa getPenyewa() {
        return penyewa;
    }

    public Kendaraan getKendaraan() {
        return kendaraan;
    }

    public int getLamaHari() {
        return lamaHari;
    }

    public double getTotalBiaya() {
        return totalBiaya;
    }

    public String getInfo() {
        String biayaFormatted = String.format("Rp %,.0f", totalBiaya);
        return "Penyewa : " + penyewa.getInfo() +
               "\nKendaraan: " + kendaraan.getInfo() +
               "\nLama sewa: " + lamaHari + " hari" +
               "\nTotal    : " + biayaFormatted;
    }
}

3.7. RentalApp.java (Main)

Peran:

  • Menjadi titik masuk program (main).

  • Membuat daftar kendaraan (mobil, motor, sepeda).

  • Membuat beberapa penyewa dan transaksi rental.

  • Menampilkan:

    • daftar kendaraan yang masih tersedia,

    • daftar transaksi rental yang sedang berjalan.

import java.util.ArrayList;
import java.util.List;

/**
 * Kelas utama untuk menjalankan aplikasi rental kendaraan
 */
public class RentalApp {

    public static void main(String[] args) {
        // 1. Membuat daftar kendaraan
        List<Kendaraan> daftarKendaraan = new ArrayList<>();
        daftarKendaraan.add(new Mobil("Toyota", "Avanza", 2022, 4));
        daftarKendaraan.add(new Motor("Honda", "Vario", 2021, 2));
        daftarKendaraan.add(new Sepeda("Polygon", "Xtrada", 2020, "Gunung"));
        daftarKendaraan.add(new Mobil("Suzuki", "Ertiga", 2019, 4));
        daftarKendaraan.add(new Sepeda("United", "Miami", 2023, "BMX"));
        daftarKendaraan.add(new Motor("Yamaha", "NMax", 2022, 2));

        // 2. Daftar rental (transaksi)
        List<Rental> daftarRental = new ArrayList<>();

        // 3. Membuat beberapa penyewa
        Penyewa p1 = new Penyewa("Budi", "KTP001");
        Penyewa p2 = new Penyewa("Siti", "KTP002");
        Penyewa p3 = new Penyewa("Andi", "KTP003");

        // 4. Contoh transaksi:
        //    Budi sewa Avanza 2 hari, Siti sewa Sepeda Gunung 3 hari, Andi sewa Motor NMax 1 hari
        Rental r1 = new Rental(p1, daftarKendaraan.get(0), 2); // Avanza
        Rental r2 = new Rental(p2, daftarKendaraan.get(2), 3); // Sepeda Gunung
        Rental r3 = new Rental(p3, daftarKendaraan.get(5), 1); // NMax

        daftarRental.add(r1);
        daftarRental.add(r2);
        daftarRental.add(r3);

        // 5. Tampilkan daftar kendaraan yang masih tersedia
        System.out.println("=== Daftar Kendaraan Tersedia ===");
        for (Kendaraan k : daftarKendaraan) {
            if (k.isTersedia()) {
                System.out.println("- " + k.getInfo());
            }
        }

        System.out.println();

        // 6. Tampilkan daftar penyewa dan kendaraan yang disewa
        System.out.println("=== Daftar Transaksi Rental ===");
        for (Rental r : daftarRental) {
            System.out.println(r.getInfo());
            System.out.println("--------------------------------");
        }
    }
}

4. Contoh Output Program

Kurang lebih output di console akan seperti ini:


Terlihat bahwa:

  • Kendaraan yang sudah disewa tidak muncul lagi di daftar tersedia, karena setTersedia(false) dipanggil saat Rental dibuat.

  • Method yang dipanggil sama (getInfo() dan hitungBiaya()), tapi hasilnya berbeda tergantung jenis kendaraan → contoh polimorfisme.

PBO A - Pertemuan 9 - Game World of Zuul

Nama    : Erlangga Rizqi Dwi Raswanto

NRP      : 5025241179

Kelas     : PBO A

Halo, di pertemuan ini saya membuat sebuah game petualangan bernama Game World of Zuul




Dalam project ini terdapat 5 kelas utama, yaitu:

  1. CommandWords
    Menyimpan semua kata perintah (command) yang valid dalam game, misalnya: go, help, quit, look.
    Kelas ini menggunakan array String untuk menampung daftar perintah, dan menyediakan method untuk mengecek apakah sebuah kata termasuk perintah yang valid.

  2. Parser
    Kelas yang bertugas membaca input dari user lewat terminal, memecah input menjadi kata-kata, lalu mengubahnya menjadi objek Command.
    Parser akan mengecek apakah kata pertama termasuk perintah yang dikenal di CommandWords. Jika tidak, command tersebut dianggap tidak dikenal (unknown).

  3. Command
    Objek Command merepresentasikan satu perintah yang diketik user.
    Sebuah perintah terdiri dari:

    • kata pertama (command utama, misalnya: go)

    • kata kedua (argumen, misalnya: north)
      Kelas ini punya method untuk:

    • mengecek apakah command valid atau tidak,

    • mengambil kata pertama,

    • mengambil kata kedua (jika ada).

  4. Room
    Objek Room merepresentasikan lokasi di dalam game.
    Setiap ruangan punya:

    • deskripsi (misalnya "in a lecture theater")

    • pintu keluar (exit) ke arah north, east, south, west yang bisa mengarah ke ruangan lain.

  5. Game
    Ini adalah kelas utama yang menjalankan game.
    Tugasnya:

    • Membuat semua ruangan dan menghubungkan pintu keluarnya.

    • Membuat objek Parser.

    • Menyimpan lokasi ruangan saat ini (currentRoom).

    • Menjalankan game loop (menerima command dari user dan mengeksekusinya).

    • Mengimplementasikan perintah-perintah seperti go, help, dan quit.


Gambaran Cara Kerja Program

Secara umum alur game-nya seperti ini:

  1. Program dijalankan → objek Game dibuat.

  2. Konstruktor Game:

    • memanggil createRooms() untuk membuat ruangan dan menghubungkannya,

    • membuat objek Parser.

  3. User memulai permainan dengan memanggil method play().

  4. Di dalam play(), program:

    • menampilkan pesan sambutan (printWelcome()),

    • masuk ke loop utama:

      • mengambil command dari Parser,

      • memproses command dengan processCommand(),

      • jika user mengetik quit, loop berhenti.

  5. Program berakhir dengan menampilkan pesan perpisahan.

Source Code Game:


Source Code Room:


Source Code Parser:


Source Code Command:

Source Code CommandWords:


Cara Mengimplementasikan dan Menjalankan

Berikut langkah-langkah

  1. Buat objek Game:

    • Klik kanan pada kelas Game → pilih new Game() → beri nama objek, misal game1.

  2. Jalankan game:

    • Klik kanan pada objek game1 → pilih method void play().

  3. Di jendela terminal, masukkan perintah seperti:

    • help

    • go east

    • go south

    • quit


Output :


Senin, 13 Oktober 2025

PBO A - Pertemuan 8 - Evaluasi Tengah Semester

 Nama    : Erlangga Rizqi Dwi Raswanto

NRP      : 5025241179

Kelas     : PBO A

Soal Evaluasi Tengah Semester

Demo              https://youtu.be/ucMWWojthF8

Link Github    https://github.com/erlanggardr/ETS_PBO_5025241179


Mengimplementasikan Vending Coffee Machine 


Adapun fitur-fitur yang ada didalam program ini adalah :

  • Menu Kopi dengan harga dasar ukuran S
  • Pemilihan Ukuran Gelas, terdapat 3 pilihan yaitu S/M/L (Small/Medium/Large)
    • Multiplier harga per ukuran (M=1.2, L=1.4)
    • Multiplier penggunaan resource per ukuran (M=1.1, L=1.4)
  • Pemilihan addon
    • Gula : gratis
    • Susu : +Rp2000
  • Pembayaran & Kembalian
  • Manajemen stok & Peringatan Refill saat mau habis
  • Menu Admin:
    • Laporan stok 
    • Log Transaksi dengan keterangan tanggal/jam, id transaksi, nama produk, total harga, beserta keterangan kembalian jika ada

Diagram pada BlueJ



Rancangan Kelas :

 Coffee  
  ├─ name:String  
  ├─ price:int  
  └─ +getName():String, +getPrice():int  
   
 Size  
  ├─ SMALL=1, MEDIUM=2, LARGE=3  
  ├─ +sizeCup(int):String  
  ├─ +multiplierPrice(int):double  // S=1.0, M=1.2, L=1.4  
  └─ +multiplierResource(int):double// S=1.0, M=1.1, L=1.2  
   
 Inventory  
  ├─ coffeeGram:int, waterML:int, sugarGram:int, milkML:int  
  ├─ BASE_COFFEE=5g, BASE_WATER=150ml, MILK_PER_CUP=50ml, SUGAR_PER_CUP=10g  
  ├─ +enoughFor(addSugar, addMilk, size):boolean  
  ├─ +lackingMessage(addSugar, addMilk, size):String  
  ├─ +consume(addSugar, addMilk, size):void  
  ├─ +quickRefill():void  
  ├─ +lowStockWarning():String  
  └─ +toString():String  
   
 Transaction  
  ├─ id:int, time:String (yyyy-MM-dd HH:mm:ss)  
  ├─ coffeeName:String, size:int, sugar:boolean, milk:boolean  
  ├─ total:int, paid:int, change:int  
  └─ +toString():String  
   
 CoffeeMachine (program utama)  
  ├─ menu: Coffee[]  
  ├─ inv: Inventory  
  ├─ log: ArrayList<Transaction>  
  ├─ +main(String[]):void  
  ├─ +showMenu():void  
  ├─ +showReport():void  
  ├─ +readInt(Scanner,String):int  
  └─ +readYesNo(Scanner,String):boolean  
   

Rancangan objek :

 CoffeeMachine  
  ├─ menu[4] → Coffee("Espresso",13000), ("Americano",10000),  
  │       ("Cappuccino",15000), ("Latte",18000)  
  ├─ inv → Inventory(coffeeGram=300, waterML=2000, sugarGram=100, milkML=500)  
  └─ log → [] 

Penjelasan masing-masing class/file :



Coffee.java



Kelas Coffee merepresentasikan satu item menu kopi. Kelas ini hanya menyimpan dua informasi: name (nama kopi) dan price.

Atribut:

  • name: String  - nama kopi yang ditampilkan di menu dan dicetak pada transaksi.

  • price: int  - harga dasar  (ukuran Small).

Konstruktor:

  • Menerima name dan price lalu menyimpannya ke field. 

Metode:

  • getName() -  mengembalikan nama kopi.

  • getPrice()  -  mengembalikan harga dasar.


Size.java


Kelas Size menyediakan konstanta untuk ukuran cup (SMALL=1, MEDIUM=2, LARGE=3) dan fungsi bantu untuk:

  • menampilkan label ukuran S/M/L,

  • mengembalikan multiplier harga (untuk menghitung total rupiah),

  • mengembalikan multiplier resource (untuk menghitung pemakaian bahan).

Konstanta:

  • SMALL=1, MEDIUM=2, LARGE=3 

Fungsi Bantu:

  • sizeCup(int s)"S", "M", atau "L" sesuai kode ukuran. Jika kode tidak cocok, mengembalikan "Not Valid".

  • multiplierPrice(int s) → pengali harga:

    • M → 1.2 (naik 20%), L → 1.4 (naik 40%), S → 1.0 (tidak berubah).

  • multiplierResource(int s) → pengali pemakaian bahan (kopi & air):

    • M → 1.1 (naik 10%), L → 1.4 (naik 40%), S → 1.0.


Transaction.java



Kelas Transaction merekam satu transaksi yang berhasil: identitas, waktu, pilihan user, dan angka-angka uangnya. Dengan menyimpan jejak transaksi, aplikasi bisa menampilkan laporan di menu admin.

Field:

  • id: int - nomor urut transaksi. 

  • time: Stringtimestamp transaksi dalam format yyyy-MM-dd HH:mm:ss.

  • coffeeName: String - nama kopi yang dibeli.

  • size: int - kode ukuran (1/2/3)

  • sugar: boolean - apakah menambah gula.

  • milk: boolean - apakah menambah susu.

  • total: int - total harga yang dihitung.

  • paid: int - jumlah uang yang dibayarkan pengguna.

  • change: int - kembalian (paid − total).

Konstruktor:

  • Mengisi semua field berdasarkan parameter.

  • Membentuk waktu sekarang dengan LocalDateTime.now() dan memformatnya secara manual menggunakan String.format agar menjadi YYYY-MM-DD HH:MM:SS.

Representasi Teks:

  • toString() menghasilkan satu baris ringkas untuk laporan, misalnya:
    [2025-10-15 10:07:02] #002 Espresso ukuran M + susu. total Rp14000, bayar Rp20000, kembali Rp6000
    Label ukuran didapat dari Size.sizeCup(size)S/M/L. Add-on (gula/susu) hanya dituliskan jika true.

Inventory.java



Kelas Inventory menyimpan stok bahan untuk:

  • Menghitung kebutuhan bahan per 1 gelas berdasarkan ukuran + add-on,

  • Memeriksa apakah stok cukup,

  • Menghasilkan pesan komponen mana yang kurang,

  • Mengurangi stok setelah transaksi sukses,

  • Memberi peringatan stok rendah,

  • Menampilkan ringkasan stok sebagai teks.

Field Stok (state mesin):

  • coffeeGram: int - stok kopi dalam gram.

  • waterML: int  - stok air dalam mililiter.

  • sugarGram: int  - stok gula dalam gram.

  • milkML: int -  stok susu dalam mililiter.

Konstanta konsumsi (ukuran Small):

  • BASE_COFFEE = 5 gram kopi per cup ukuran S.

  • BASE_WATER = 150 ml air per cup ukuran S.

  • MILK_PER_CUP = 50 ml susu jika pengguna pilih susu.

  • SUGAR_PER_CUP = 10 gram gula jika pengguna pilih gula.

Konstruktor:

  • Menerima nilai awal stok untuk keempat bahan. Nilai ini menjadi state awal mesin saat aplikasi mulai.

Perhitungan Kebutuhan Bahan (per 1 gelas):

  • Kopi: ceil(BASE_COFFEE × multiplierResource(size)).

  • Air: ceil(BASE_WATER × multiplierResource(size)).

  • Susu: tetap 50 ml jika dipilih.

  • Gula: tetap 10 g jika dipilih.

Pemeriksaan & Konsumsi:

  • enoughFor(addSugar, addMilk, size)
    Menghitung kebutuhan bahan untuk kombinasi tersebut lalu membandingkan dengan stok. Mengembalikan true jika semua bahan mencukupi.

  • lackingMessage(addSugar, addMilk, size)
    Jika ada yang kurang, membangun string “Stok tidak cukup: …” yang menyebutkan bahan mana saja yang kurang (kopi/air/gula/susu).

  • consume(addSugar, addMilk, size)
    Mengurangi stok sesuai kebutuhan yang telah dihitung. Dipanggil hanya setelah pembayaran cukup (transaksi sukses).

Informasi & Peringatan:

  • lowStockWarning()
    Mengembalikan teks peringatan jika ada bahan di bawah ambang (kopi < 30g, air < 200ml, gula < 20g, susu < 50ml). Jika aman, mengembalikan “Stok aman.”.

  • toString()
    Mengembalikan ringkasan stok: “Stok: Kopi Xg, Air Yml, Gula Zg, Susu Wml”.


CoffeeMachine.java



Kelas CoffeeMachine adalah program utama yaitu: membaca input, menampilkan menu, memanggil perhitungan harga & stok, memverifikasi pembayaran, mengurangi stok, mencatat transaksi, dan menyediakan laporan admin.

Data statis:

  • menu: Coffee[] - daftar item kopi 

  • MILK_PRICE: int - biaya tambahan

  • inven: Inventory - stok awal bahan

  • log: ArrayList<Transaction> - daftar transaksi yang berhasil, untuk laporan.

Metode main :

  1. Membuat Scanner untuk baca input dari konsol.

  2. While loop sebagai representasi mesin yang selalu aktif sampai user keluar:

    • Tampilkan menu dengan showMenu():
      a) nomor 1..N untuk kopi,
      b) 8 untuk Laporan admin,
      c) 0 untuk Keluar.

    • Baca pilihan pengguna dengan readInt(...). Metode ini memaksa input angka non-negatif; jika bukan angka, token dibuang dan diminta ulang.

    • Kondisi khusus pilihan:

      • 0 → ucapkan terima kasih dan break (keluar dari program).

      • 8 → panggil showReport() untuk menampilkan stok & semua transaksi; kemudian continue (kembali ke menu).

    • Validasi pilihan kopi: jika tidak di rentang 1..menu.length, tampilkan error dan kembali ke menu.

    • Ambil kopi terpilih: Coffee pesanan = menu[pilih - 1].

    • Pilih ukuran: tampilkan opsi 1) S 2) M 3) L

    • Pilih add-on: tanya Tambah gula? (y/n) dan Tambah susu? (y/n).

    • Cek stok: panggil inven.enoughFor(addSugar, addMilk, size).

      • Jika tidak cukup, tampilkan inven.lackingMessage(...) (menyebut bahan apa yang kurang), cetak ringkasan stok (inven.toString()) dan peringatan (inven.lowStockWarning()), lalu kembali ke menu.

    • Hitung total:

      • priceMult = Size.multiplierPrice(size)

      • total = round(pesanan.getPrice() × priceMult) + (addMilk ? MILK_PRICE : 0)

    • Pembayaran:

      • Cetak total dan minta Bayar (Rp):.

      • Jika bayar kurang dari total → tolak transaksi (“Kurang Rp …”) dan kembali ke menu.

      • Jika cukup → lanjut.

    • Seduh & konsumsi stok:

      • Cetak “Menyeduh … (S/M/L) + gula/susu …” sebagai feedback proses.

      • Kurangi stok dengan inven.consume(addSugar, addMilk, size).

    • Kembalian & log:

      • Hitung kembalian = bayar − total dan cetak.

      • Buat ID transaksi baru = log.size() + 1.

      • Tambahkan objek Transaction baru ke log (berisi waktu, item, ukuran, add-on, total, bayar, kembalian).

    • Info stok:

      • Cetak inven.lowStockWarning() untuk memberi tau kalau bahan ada yang mulai menipis.

  3. Menutup Scanner saat keluar loop.

Metode helper:

  • showMenu() - Mencetak daftar menu + opsi admin/keluar.

  • showReport() - Mencetak ringkasan stok (inven.toString()), lalu setiap transaksi (t.toString()), atau pesan “Belum ada transaksi.” jika log kosong.

  • readInt(Scanner, String): int -  Membaca input bilangan bulat non-negatif. Jika input bukan angka, menolak dan meminta ulang

  • readYesNo(Scanner, String): boolean - Membaca jawaban "y" atau "n"


Flowchart / Alur Sistem Program



PBO A - Pertemuan 15 - Pong!

Nama : Erlangga Rizqi Dwi Raswanto NRP : 5025241179 Kelas : PBO A 1. Pendahuluan Pada pertemuan 15, kita mempraktikkan pembuatan game...