Senin, 08 Desember 2025

Pertemuan 15 : Aplikasi CRUD JAVA

Pertemuan 15 - Java CRUD Database

Tanggal: 9 Desember 2025
Nama: Hosea Felix Sanjaya
NRP: 5025241177


a. Setup & Persiapan Environment

Sebelum menjalankan program, terdapat beberapa langkah konfigurasi yang harus dilakukan agar Java dapat berkomunikasi dengan database MySQL.

1. Download Library MySQL Connector

  • Kunjungi: MySQL Connector/J Download
  • Pilih menu Platform Independent.
  • Download file format .zip.
  • Ekstrak file zip tersebut dan ambil file mysql-connector-java-xx.jar.

2. Integrasi ke BlueJ

  • Buka BlueJ, kemudian navigasi ke menu: ToolsPreferencesLibraries.
  • Klik tombol Add dan pilih file .jar yang sudah diekstrak tadi.
  • Restart BlueJ agar library terbaca dengan sempurna.

3. Setup Database (XAMPP)

  • Jalankan aplikasi XAMPP.
  • Start module Apache dan MySQL.
  • Buka browser dan akses: localhost/phpmyadmin/.
  • Jalankan query SQL (dari file prep.sql) untuk membuat database bernama perpustakaan dan tabel buku.


Cuplikan Kode Login.java


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

public class Login {
    private final String USERNAME = "james3302";
    private final String PASSWORD = "pass";

    private JTextField txtUsername;
    private JPasswordField txtPassword;

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

    private void createAndShowGUI() {
        JFrame frame = new JFrame("Login");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel panel = new JPanel(new GridBagLayout());
        GridBagConstraints c = new GridBagConstraints();
        c.insets = new Insets(6, 6, 6, 6);

        c.gridx = 0;
        c.gridy = 0;
        c.anchor = GridBagConstraints.EAST;
        panel.add(new JLabel("Username:"), c);

        c.gridx = 1;
        c.anchor = GridBagConstraints.WEST;
        txtUsername = new JTextField(15);
        panel.add(txtUsername, c);

        c.gridx = 0;
        c.gridy = 1;
        c.anchor = GridBagConstraints.EAST;
        panel.add(new JLabel("Password:"), c);

        c.gridx = 1;
        c.anchor = GridBagConstraints.WEST;
        txtPassword = new JPasswordField(15); 
        panel.add(txtPassword, c);

        c.gridx = 0;
        c.gridy = 2;
        c.gridwidth = 2;
        c.anchor = GridBagConstraints.CENTER;
        JPanel btnPanel = new JPanel();
        JButton btnLogin = new JButton("Login");
        btnLogin.addActionListener(new LoginListener());
        JButton btnCancel = new JButton("Cancel");
        btnCancel.addActionListener(new CancelListener());
        btnPanel.add(btnLogin);
        btnPanel.add(btnCancel);
        panel.add(btnPanel, c);

        frame.getContentPane().add(panel);
        frame.pack();
        frame.setLocationRelativeTo(null); 
        frame.setVisible(true);
    }

    private class LoginListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            String enteredUser = txtUsername.getText();
            String enteredPass = new String(txtPassword.getPassword()); 

            String msg;
            if (USERNAME.equals(enteredUser) && PASSWORD.equals(enteredPass)) {
                msg = "Login Granted!";
            } else {
                msg = "Login Denied";
            }
            JOptionPane.showMessageDialog(null, msg);
        }
    }

    private class CancelListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            txtUsername.setText("");
            txtPassword.setText("");
            txtUsername.requestFocusInWindow();
        }
    }
}

Cuplikan Kode ImageViewer.java


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

public class ImageViewer
{
    private static final String VERSION = "Version 1.0";
    private static final JFileChooser fileChooser = new JFileChooser(System.getProperty("user.dir"));

    private JFrame frame;
    private ImagePanel imagePanel;
    private JLabel filenameLabel;
    private JLabel statusLabel;
    private OFImage currentImage;

    public ImageViewer() { }

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

    private void createAndShowGUI() {
        frame = new JFrame("ImageViewer");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        makeMenuBar(frame);

        Container contentPane = frame.getContentPane();
        contentPane.setLayout(new BorderLayout(6, 6));

        filenameLabel = new JLabel();
        contentPane.add(filenameLabel, BorderLayout.NORTH);

        imagePanel = new ImagePanel();
        contentPane.add(imagePanel, BorderLayout.CENTER);

        statusLabel = new JLabel(VERSION);
        contentPane.add(statusLabel, BorderLayout.SOUTH);

        showFilename(null);
        frame.pack();

        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
        frame.setLocation(d.width/2 - frame.getWidth()/2, d.height/2 - frame.getHeight()/2);
        frame.setVisible(true);
    }

    private void openFile() {
        int returnVal = fileChooser.showOpenDialog(frame);
        if (returnVal != JFileChooser.APPROVE_OPTION) {
            return; 
        }
        File selectedFile = fileChooser.getSelectedFile();
        OFImage loaded = ImageFileManager.loadImage(selectedFile);
        if (loaded == null) {
            JOptionPane.showMessageDialog(frame,
                    "The file was not in a recognized image file format.",
                    "Image Load Error",
                    JOptionPane.ERROR_MESSAGE);
            return;
        }
        currentImage = loaded;
        imagePanel.setImage(currentImage);
        imagePanel.revalidate();
        showFilename(selectedFile.getPath());
        showStatus("File loaded.");
        frame.pack();
    }

    private void close() {
        currentImage = null;
        imagePanel.clearImage();
        imagePanel.revalidate();
        showFilename(null);
    }

    private void quit() {
        System.exit(0);
    }

    private void makeDarker() {
        if (currentImage != null) {
            currentImage.darker();
            imagePanel.repaint();
            showStatus("Applied: darker");
        } else {
            showStatus("No image loaded.");
        }
    }

    private void makeLighter() {
        if (currentImage != null) {
            currentImage.lighter();
            imagePanel.repaint();
            showStatus("Applied: lighter");
        } else {
            showStatus("No image loaded.");
        }
    }

    private void threshold() {
        if (currentImage != null) {
            currentImage.threshold();
            imagePanel.repaint();
            showStatus("Applied: threshold");
        } else {
            showStatus("No image loaded.");
        }
    }

    private void showAbout() {
        JOptionPane.showMessageDialog(frame,
                "ImageViewer\n" + VERSION,
                "About ImageViewer",
                JOptionPane.INFORMATION_MESSAGE);
    }

    private void showFilename(String filename) {
        if (filenameLabel == null) return;
        if (filename == null) {
            filenameLabel.setText("No file displayed.");
        } else {
            filenameLabel.setText("File: " + filename);
        }
    }

    private void showStatus(String text) {
        if (statusLabel == null) return;
        statusLabel.setText(text);
    }

    private void makeMenuBar(JFrame frame) {
        final int SHORTCUT_MASK = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();

        JMenuBar menubar = new JMenuBar();
        frame.setJMenuBar(menubar);

        JMenu menu;
        JMenuItem item;

        menu = new JMenu("File");
        menubar.add(menu);

        item = new JMenuItem("Open");
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, SHORTCUT_MASK));
        item.addActionListener(e -> openFile());
        menu.add(item);

        item = new JMenuItem("Close");
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, SHORTCUT_MASK));
        item.addActionListener(e -> close());
        menu.add(item);
        menu.addSeparator();

        item = new JMenuItem("Quit");
        item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, SHORTCUT_MASK));
        item.addActionListener(e -> quit());
        menu.add(item);

        menu = new JMenu("Filter");
        menubar.add(menu);

        item = new JMenuItem("Darker");
        item.addActionListener(e -> makeDarker());
        menu.add(item);

        item = new JMenuItem("Lighter");
        item.addActionListener(e -> makeLighter());
        menu.add(item);

        item = new JMenuItem("Threshold");
        item.addActionListener(e -> threshold());
        menu.add(item);

        menu = new JMenu("Help");
        menubar.add(menu);

        item = new JMenuItem("About ImageViewer...");
        item.addActionListener(e -> showAbout());
        menu.add(item);
    }
}

Cuplikan Kode ImagePanel.java


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

public class ImagePanel extends JComponent
{
    private int width, height;
    private OFImage panelImage;

    private static final int DEFAULT_WIDTH = 360;
    private static final int DEFAULT_HEIGHT = 240;

    public ImagePanel() {
        width = DEFAULT_WIDTH;
        height = DEFAULT_HEIGHT;
        panelImage = null;
        setPreferredSize(new Dimension(width, height));
    }

    public void setImage(OFImage image) {
        if (image != null) {
            width = image.getWidth();
            height = image.getHeight();
            panelImage = image;
            setPreferredSize(new Dimension(width, height));
            revalidate();
            repaint();
        }
    }

    public void clearImage() {
        panelImage = null;
        width = DEFAULT_WIDTH;
        height = DEFAULT_HEIGHT;
        setPreferredSize(new Dimension(width, height));
        revalidate();
        repaint();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(width, height);
    }

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

        if (panelImage != null) {
            g.drawImage(panelImage, 0, 0, this);
        } else {
            g.setColor(Color.LIGHT_GRAY);
            g.fillRect(0, 0, getWidth(), getHeight());
        }
    }
} 

Cuplikan Kode ImageFileManager.java


import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;

public class ImageFileManager
{
    private static final String IMAGE_FORMAT = "jpg"; 

    public static OFImage loadImage(File imageFile) {
        try {
            BufferedImage img = ImageIO.read(imageFile);
            if (img == null) {
                return null;
            }
            BufferedImage copy = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
            copy.getGraphics().drawImage(img, 0, 0, null);
            return new OFImage(copy);
        } catch (IOException e) {
            return null;
        }
    }

    public static void saveImage(OFImage image, File file) {
        try {
            ImageIO.write(image, IMAGE_FORMAT, file);
        } catch (IOException e) {
            // silently return as original design
        }
    }
}

Cuplikan Kode OFImage.java


import java.awt.*;
import java.awt.image.*;

public class OFImage extends BufferedImage
{
    public OFImage(int width, int height) {
        super(width, height, BufferedImage.TYPE_INT_RGB);
    }

    public OFImage(BufferedImage image) {
        super(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
        Graphics2D g = this.createGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();
    }

    public void setPixel(int x, int y, Color col) {
        int pixel = col.getRGB();
        setRGB(x, y, pixel);
    }

    public Color getPixel(int x, int y) {
        int pixel = getRGB(x, y);
        return new Color(pixel, false);
    }

    public void darker() {
        int h = getHeight();
        int w = getWidth();
        for (int y = 0; y < h; y++) {
            for (int x = 0; x < w; x++) {
                setPixel(x, y, getPixel(x, y).darker());
            }
        }
    }

    public void lighter() {
        int h = getHeight();
        int w = getWidth();
        for (int y = 0; y < h; y++) {
            for (int x = 0; x < w; x++) {
                setPixel(x, y, getPixel(x, y).brighter());
            }
        }
    }

    public void threshold() {
        int h = getHeight();
        int w = getWidth();
        for (int y = 0; y < h; y++) {
            for (int x = 0; x < w; x++) {
                Color c = getPixel(x, y);
                int brightness = (c.getRed() + c.getGreen() + c.getBlue()) / 3;
                if (brightness <= 85) {
                    setPixel(x, y, Color.BLACK);
                } else if (brightness <= 170) {
                    setPixel(x, y, Color.GRAY);
                } else {
                    setPixel(x, y, Color.WHITE);
                }
            }
        }
    }
}

b. Penjelasan Alur Program

Program ini merupakan aplikasi Console sederhana yang mengimplementasikan operasi CRUD (Create, Read, Update, Delete) menggunakan Java JDBC.

Alur Utama (Main Logic)

  1. Load Driver: Program pertama kali memuat driver JDBC agar bisa mengenali MySQL.
  2. Connection: Membuat objek koneksi yang menghubungkan aplikasi Java dengan database perpustakaan di localhost.
  3. Statement: Membuat objek Statement yang berfungsi sebagai pengirim perintah SQL.
  4. Menu Loop: Program akan terus berjalan menampilkan menu interaktif:
    • 1 → Insert Data
    • 2 → Show Data
    • 3 → Update Data
    • 4 → Delete Data
    • 0 → Keluar
  5. Termination: Saat user memilih keluar, koneksi database dan statement ditutup untuk membersihkan resource.

Fungsi Operasi CRUD

  • insertBuku(): Meminta input judul dan pengarang dari user, lalu menyimpannya ke tabel menggunakan query INSERT.
  • showData(): Mengambil seluruh data buku menggunakan query SELECT * FROM buku dan menampilkannya dalam format daftar.
  • updateBuku(): Memperbarui informasi buku (Judul/Pengarang) berdasarkan id_buku yang dipilih user.
  • deleteBuku(): Menghapus baris data buku secara permanen berdasarkan id_buku.

c. Dokumentasi Output

1. Input Data (Insert)


2. Update Data


3. Delete Data


Kesimpulan

  • Aplikasi ini mendemonstrasikan penggunaan JDBC (Java Database Connectivity) sebagai jembatan antara bahasa pemrograman Java dan database relasional (MySQL).
  • Penggunaan library eksternal (mysql-connector.jar) sangat krusial karena berisi driver yang diperlukan untuk protokol komunikasi database.
  • Pemisahan logika program ke dalam fungsi-fungsi spesifik (insert, update, delete) membuat kode lebih rapi dan mudah untuk di-maintenance.

Minggu, 30 November 2025

Pertemuan 14 : Pong Game

Pertemuan 14 - Pong Game!

Tanggal: 30 November 2025
Nama: Hosea Felix Sanjaya
NRP: 5025241177


a. Visualisasi Alur di BlueJ

Penjelasan Alur

Diagram di atas menunjukkan alur kerja dan hubungan antar kelas dalam program Pong Game:

  • Pong (Controller): Berfungsi sebagai pengendali utama yang mengatur logika permainan, pergerakan bola/paddle, pengecekan tabrakan, dan skor.
  • Ball & Paddle (Model): Merupakan objek permainan. Posisi mereka terus diperbarui oleh kelas Pong berdasarkan logika fisika dan input user.
  • Renderer (View): Bertugas menampilkan seluruh objek permainan (visual) ke layar.
  • Dependensi: Panah menunjukkan bahwa Pong mengontrol Ball dan Paddle, kemudian data posisi dikirim ke Renderer untuk digambar. Pemisahan ini membuat struktur aplikasi lebih modular.

b. Code dan Penjelasan

1. Class Pong.java

Berperan sebagai pusat pengendali permainan (Game Controller).


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) { }
}

Penjelasan Singkat

  • Inisialisasi: Membuat JFrame, Timer (game loop 20ms), dan Renderer.
  • Method update(): Menangani logika inti seperti pergerakan paddle (manual & bot), fisika bola, dan kondisi menang/kalah.
  • Method render(): Menggambar UI menu, skor, garis lapangan, dan memanggil fungsi render objek lain.
  • Input Handling: Menggunakan KeyListener untuk mendeteksi input keyboard (W/S, Arrow Keys, Space, Shift).

2. Class Ball.java

Menangani logika fisik bola, pergerakan, dan pantulan.


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; //bounce
        }
        else if ((paddle.x > x && paddle.paddleNumber == 1) || (paddle.x < x - width && paddle.paddleNumber == 2))
        {
            return 2; //score
        }
        return 0; //nothing
    }

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

Penjelasan Singkat

  • Fisika: Mengatur posisi (x,y) dan arah gerak (motionX, motionY).
  • Pantulan: Bola memantul jika mengenai batas atas/bawah layar atau mengenai paddle.
  • Skoring: Jika bola melewati paddle, skor lawan bertambah dan posisi bola di-reset (spawn).

3. Class Paddle.java dan Renderer.java

Mengatur objek pemain dan proses penggambaran ke layar.


// PADDLE CLASS
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 CLASS
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);
    }
}

Penjelasan Singkat

  • Paddle: Merepresentasikan pemain kiri (1) atau kanan (2). Method move() membatasi agar paddle tidak keluar layar.
  • Renderer: Kelas turunan JPanel yang memanggil method render utama dari kelas Pong setiap kali layar perlu digambar ulang (repaint).

c. Dokumentasi Output

Tampilan Awal Permainan (Menu)

Tampilan Permainan Selama Berlangsung

Tampilan Saat Salah Satu Pemain Menang


Kesimpulan

  • Aplikasi Pong ini menerapkan konsep Object Oriented Programming (OOP) dengan memisahkan logika permainan (Pong), objek (Ball, Paddle), dan tampilan (Renderer).
  • Penggunaan Timer memungkinkan terciptanya game loop yang konsisten untuk memperbarui logika fisika dan animasi.
  • Interaksi pengguna ditangani melalui KeyListener, memberikan respons real-time untuk pergerakan paddle.

Senin, 24 November 2025

Pertemuan 13 : Pemrograman GUI

Pertemuan 13 – Pemrograman GUI

Tanggal: 17 November 2025
Nama: Hosea Felix Sanjaya
NRP: 5025241177



1. Aplikasi User Login ("LoginFrame")

Kode Program

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

public class LoginFrame extends JFrame implements ActionListener { private JTextField userField; private JPasswordField passField; private JButton loginButton;

public LoginFrame() {
    setTitle("Login");
    setSize(300, 150);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setLayout(new GridLayout(3, 2));

    // Komponen Input Username
    add(new JLabel("Username:"));
    userField = new JTextField();
    add(userField);

    // Komponen Input Password
    add(new JLabel("Password:"));
    passField = new JPasswordField();
    add(passField);

    // Tombol Login
    loginButton = new JButton("Login");
    loginButton.addActionListener(this);
    add(new JLabel()); // Placeholder kosong untuk grid
    add(loginButton);

    setLocationRelativeTo(null); // Posisi di tengah layar
    setVisible(true);
}

@Override
public void actionPerformed(ActionEvent e) {
    String username = userField.getText();
    String password = new String(passField.getPassword());

    // Validasi sederhana
    if (username.equals("admin") && password.equals("12345")) {
        JOptionPane.showMessageDialog(this, "Login berhasil!");
    } else {
        JOptionPane.showMessageDialog(this, "Username atau password salah!");
    }
}

public static void main(String[] args) {
    new LoginFrame();
}
} 

Penjelasan Singkat

  • extends JFrame → Kelas ini berfungsi sebagai jendela utama aplikasi.
  • implements ActionListener → Digunakan agar kelas dapat merespons aksi (klik tombol) pengguna.
  • GridLayout(3, 2) → Mengatur tata letak komponen menjadi 3 baris dan 2 kolom agar rapi.
  • JPasswordField → Komponen input khusus password (teks disembunyikan).
  • actionPerformed() → Method ini memvalidasi apakah username adalah "admin" dan password "12345".

Dokumentasi

Login and Password

Login Berhasil

Login Gagal


2. Aplikasi Image Viewer

Kode Program

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

public class ImageViewer extends JFrame implements ActionListener { private JLabel imageLabel; private JButton openButton;

public ImageViewer() {
    setTitle("Image Viewer");
    setSize(500, 500);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setLayout(new BorderLayout());

    // Area untuk menampilkan gambar (Tengah)
    imageLabel = new JLabel("", SwingConstants.CENTER);
    add(imageLabel, BorderLayout.CENTER);

    // Tombol untuk membuka file (Bawah)
    openButton = new JButton("Open Image");
    openButton.addActionListener(this);
    add(openButton, BorderLayout.SOUTH);

    setLocationRelativeTo(null);
    setVisible(true);
}

@Override
public void actionPerformed(ActionEvent e) {
    JFileChooser fileChooser = new JFileChooser();
    int option = fileChooser.showOpenDialog(this);

    if(option == JFileChooser.APPROVE_OPTION) {
        File file = fileChooser.getSelectedFile();
        ImageIcon imageIcon = new ImageIcon(file.getAbsolutePath());
        imageLabel.setIcon(imageIcon);
    }
}

public static void main(String[] args) {
    new ImageViewer();
}
} 

Penjelasan Singkat

  • BorderLayout → Membagi tampilan menjadi 5 area (Center, North, South, East, West). Gambar diletakkan di Center dan tombol di South.
  • JFileChooser → Membuka dialog jendela sistem operasi untuk memilih file gambar dari komputer.
  • ImageIcon → Mengubah file gambar yang dipilih menjadi ikon yang bisa ditampilkan pada JLabel.
  • Event-Driven → Gambar hanya akan muncul setelah pengguna menekan tombol dan memilih file (aksi memicu perubahan).

Kesimpulan

  • Java Swing menyediakan komponen GUI (seperti JFrame, JButton, JTextField) untuk membuat aplikasi desktop interaktif.
  • Interface ActionListener sangat penting untuk menangani interaksi pengguna (Event Handling).
  • Penggunaan Layout Manager (seperti GridLayout dan BorderLayout) membantu menyusun komponen secara terstruktur.

Image Viewer

Jumat, 14 November 2025

Pertemuan 12 : Abstract Class

Pertemuan 12 – Abstract Class

Tanggal: 10 November 2025
Nama: Hosea Felix Sanjaya
NRP: 5025241177


1. Contoh Abstrak Class: “Makhluk Hidup”

Kode Program


public abstract class MakhlukHidup {
    
    protected String nama;
    protected String jenisMakhluk;
    
    public MakhlukHidup(String nama, String jenisMakhluk) {
        this.nama = nama;
        this.jenisMakhluk = jenisMakhluk;
    }
    
    public void bernapas() {
        System.out.println(this.nama + " (" + this.jenisMakhluk + ") sedang bernapas.");
    }
    
    public abstract void bergerak();
    public abstract void berkembangBiak();
    
    public String getNama() {
        return nama;
    }
    public String getJenisMakhluk() {
        return jenisMakhluk;
    }
}




public class Manusia extends MakhlukHidup {
    
    public Manusia(String nama) {
        super(nama, "Manusia");
    }

    @Override
    public void bergerak() {
        System.out.println(this.nama + " bergerak dengan berjalan atau berlari.");
    }

    @Override
    public void berkembangBiak() {
        System.out.println(this.nama + " berkembang biak dengan melahirkan.");
    }
    
    public void berpikir() {
        System.out.println(this.nama + " sedang menggunakan akal untuk berpikir.");
    }
}




public class Hewan extends MakhlukHidup {
    
    private String caraGerak;
    
    public Hewan(String nama, String caraGerak) {
        super(nama, "Hewan");
        this.caraGerak = caraGerak;
    }

    @Override
    public void bergerak() {
        System.out.println(this.nama + " bergerak dengan cara " + this.caraGerak + ".");
    }

    @Override
    public void berkembangBiak() {
        System.out.println(this.nama + " berkembang biak dengan bertelur atau melahirkan.");
    }
}




public class Tumbuhan extends MakhlukHidup {
    
    public Tumbuhan(String nama) {
        super(nama, "Tumbuhan");
    }

    @Override
    public void bergerak() {
        System.out.println(this.nama + " bergerak secara pasif (tumbuh mengikuti cahaya/air).");
    }

    @Override
    public void berkembangBiak() {
        System.out.println(this.nama + " berkembang biak dengan biji atau tunas.");
    }
    
    public void berfotosintesis() {
        System.out.println(this.nama + " sedang melakukan fotosintesis.");
    }
}




public class Main {
    public static void main(String[] args) {
        System.out.println("--- DEMO ABSTRACT CLASS DAN INHERITANCE ---");
        
        Manusia andi = new Manusia("Andi");
        System.out.println("\n** Objek 1: " + andi.getNama() + " **");
        andi.bernapas();         
        andi.bergerak();         
        andi.berkembangBiak();   
        andi.berpikir();         
        
        Hewan kucing = new Hewan("Kucing", "merayap dan melompat");
        System.out.println("\n** Objek 2: " + kucing.getNama() + " **");
        kucing.bernapas();
        kucing.bergerak();
        kucing.berkembangBiak();
        
        Tumbuhan mawar = new Tumbuhan("Mawar");
        System.out.println("\n** Objek 3: " + mawar.getNama() + " **");
        mawar.bernapas();
        mawar.bergerak();
        mawar.berkembangBiak();
        mawar.berfotosintesis(); 

        System.out.println("\n--- DEMO POLIMORFISME ---");
        MakhlukHidup[] daftarMakhluk = new MakhlukHidup[3];
        daftarMakhluk[0] = andi;
        daftarMakhluk[1] = kucing;
        daftarMakhluk[2] = mawar;

        for (MakhlukHidup m : daftarMakhluk) {
            System.out.print(m.getNama() + " bertindak: ");
            m.bergerak();
        }
    }
}

Penjelasan Singkat

  • abstract class MakhlukHidup → berisi metode abstrak bernapas() yang wajib diimplementasikan kelas turunannya.
  • Class Manusia, Hewan, Tumbuhan → masing-masing memberikan implementasi berbeda.
  • Class Main → menggunakan polimorfisme untuk memanggil metode bernapas() pada objek berbeda.

2. Class Abstrak “Animal” + Program Simulasi Hewan

Kode Program


import java.util.List;
import java.util.Random;

public abstract class Animal {
    protected int age;
    protected boolean alive;
    protected Field field;
    protected Location location;
    protected static final Random rand = new Random();

    // Konstruktor
    public Animal(boolean randomAge, Field field, Location location) {
        this.alive = true;
        this.field = field;
        setLocation(location);

        if (randomAge) {
            this.age = rand.nextInt(getMaxAge());
        } else {
            this.age = 0;
        }
    }

    // Getter dan utilitas umum
    public int getAge() { return age; }
    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);
    }

    // Umur dan kelahiran
    protected void incrementAge() {
        age++;
        if (age > getMaxAge()) {
            setDead();
        }
    }

    protected void giveBirth(List newAnimals) {
        List free = field.allFreeAdjacentLocations(location);
        int births = breed();

        for (int b = 0; b < births && free.size() > 0; b++) {
            Location loc = free.remove(0);
            Animal young = createOffspring(false, field, loc);
            newAnimals.add(young);
        }
    }

    protected int breed() {
        int births = 0;
        if (canBreed() && rand.nextDouble() <= getBreedingProbability()) {
            births = rand.nextInt(getMaxLitterSize()) + 1;
        }
        return births;
    }

    public boolean canBreed() {
        return age >= getBreedingAge();
    }

    // Method abstrak — diimplementasi oleh subclass
    public abstract void act(List newAnimals);
    protected abstract int getBreedingAge();
    protected abstract int getMaxAge();
    protected abstract double getBreedingProbability();
    protected abstract int getMaxLitterSize();
    protected abstract Animal createOffspring(boolean randomAge, Field field, Location location);
}




import java.util.List;

public class Rabbit extends Animal {
    private static final int BREEDING_AGE = 5;
    private static final int MAX_AGE = 50;
    private static final double BREEDING_PROBABILITY = 0.15;
    private static final int MAX_LITTER_SIZE = 5;

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

    @Override
    public void act(List newRabbits) {
        incrementAge();
        if (isAlive()) {
            giveBirth(newRabbits);
            Location newLocation = field.freeAdjacentLocation(location);
            if (newLocation != null) {
                setLocation(newLocation);
            } else {
                setDead();
            }
        }
    }

    @Override
    protected int getBreedingAge() { return BREEDING_AGE; }

    @Override
    protected int getMaxAge() { return MAX_AGE; }

    @Override
    protected double getBreedingProbability() { return BREEDING_PROBABILITY; }

    @Override
    protected int getMaxLitterSize() { return MAX_LITTER_SIZE; }

    @Override
    protected Animal createOffspring(boolean randomAge, Field field, Location location) {
        return new Rabbit(randomAge, field, location);
    }
}




import java.util.List;
import java.util.Iterator;

public class Fox extends Animal {
    private static final int RABBIT_ENERGY_VALUE = 10;
    private static final int BREEDING_AGE = 10;
    private static final int MAX_AGE = 70;
    private static final double BREEDING_PROBABILITY = 0.15;
    private static final int MAX_LITTER_SIZE = 3;
    private static final int INITIAL_ENERGY = 20;

    private int energyLevel;

    public Fox(boolean randomAge, Field field, Location location) {
        super(randomAge, field, location);
        energyLevel = INITIAL_ENERGY;
    }

    @Override
    public void act(List newFoxes) {
        incrementAge();
        incrementHunger();

        if (isAlive()) {
            giveBirth(newFoxes);
            Location newLocation = findFood();

            if (newLocation == null) {
                newLocation = field.freeAdjacentLocation(location);
            }

            if (newLocation != null) {
                setLocation(newLocation);
            } else {
                setDead();
            }
        }
    }

    private void incrementHunger() {
        energyLevel--;
        if (energyLevel <= 0) {
            setDead();
        }
    }

    private Location findFood() {
        List adjacent = field.adjacentLocations(location);
        for (Location where : adjacent) {
            Object animal = field.getObjectAt(where);
            if (animal instanceof Rabbit) {
                Rabbit rabbit = (Rabbit) animal;
                if (rabbit.isAlive()) {
                    rabbit.setDead();
                    energyLevel += RABBIT_ENERGY_VALUE;
                    return where;
                }
            }
        }
        return null;
    }

    @Override
    protected int getBreedingAge() { return BREEDING_AGE; }

    @Override
    protected int getMaxAge() { return MAX_AGE; }

    @Override
    protected double getBreedingProbability() { return BREEDING_PROBABILITY; }

    @Override
    protected int getMaxLitterSize() { return MAX_LITTER_SIZE; }

    @Override
    protected Animal createOffspring(boolean randomAge, Field field, Location location) {
        return new Fox(randomAge, field, location);
    }
}




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

public class Field {
    private Object[][] field;

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

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

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

    public Object getObjectAt(Location location) {
        if (location.getRow() >= 0 && location.getRow() < getRow() &&
            location.getCol() >= 0 && location.getCol() < getCol()) {
            return field[location.getRow()][location.getCol()];
        }
        return null;
    }

    public List adjacentLocations(Location location) {
        List locations = new ArrayList<>();

        int row = location.getRow();
        int col = location.getCol();

        if (row > 0) locations.add(new Location(row - 1, col));
        if (row < getRow() - 1) locations.add(new Location(row + 1, col));
        if (col > 0) locations.add(new Location(row, col - 1));
        if (col < getCol() - 1) locations.add(new Location(row, col + 1));

        return locations;
    }

    public List allFreeAdjacentLocations(Location location) {
        List free = new ArrayList<>();
        for (Location loc : adjacentLocations(location)) {
            if (getObjectAt(loc) == null) free.add(loc);
        }
        return free;
    }

    public Location freeAdjacentLocation(Location location) {
        List free = allFreeAdjacentLocations(location);
        return free.isEmpty() ? null : free.get(0);
    }

    public int getRow() { return field.length; }
    public int getCol() { return field[0].length; }

    public void clearAll() {
        for (int i = 0; i < field.length; i++) {
            for (int j = 0; j < field[i].length; j++) {
                field[i][j] = null;
            }
        }
    }
}




import java.util.*;

public class Simulator {
    private Field field;
    private List animals;
    private int step;
    private SimulatorView view;

    private static final int DEFAULT_ROW = 10;
    private static final int DEFAULT_COL = 10;

    public Simulator(int depth, int width) {
        if (width <= 0 || depth <= 0) {
            depth = DEFAULT_ROW;
            width = DEFAULT_COL;
        }

        animals = new ArrayList<>();
        field = new Field(depth, width);
        view = new SimulatorView(depth, width);
        view.setSymbol(Rabbit.class, 'R');
        view.setSymbol(Fox.class, 'F');

        populate();
    }

    public void simulate(int steps) {
        for (int i = 0; i < steps; i++) {
            simulateOneStep();
        }
    }

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

        for (Iterator it = animals.iterator(); it.hasNext();) {
            Animal animal = it.next();
            animal.act(newAnimals);
            if (!animal.isAlive()) it.remove();
        }

        animals.addAll(newAnimals);
        view.show(step, field);
    }

    private void populate() {
        field.clearAll();
        Random rand = new Random();
        double foxProb = 0.2;
        double rabbitProb = 0.3;

        for (int row = 0; row < field.getRow(); row++) {
            for (int col = 0; col < field.getCol(); col++) {
                Location loc = new Location(row, col);
                if (rand.nextDouble() <= foxProb) {
                    Fox fox = new Fox(false, field, loc);
                    animals.add(fox);
                    field.place(fox, loc);
                } else if (rand.nextDouble() <= rabbitProb) {
                    Rabbit rabbit = new Rabbit(true, field, loc);
                    animals.add(rabbit);
                    field.place(rabbit, loc);
                }
            }
        }
    }
}




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; }
}




import java.util.HashMap;
import java.util.Map;

public class SimulatorView {
    private int row;
    private int col;
    private Map, Character> symbols;

    public SimulatorView(int row, int col) {
        this.row = row;
        this.col = col;
        this.symbols = new HashMap<>();
    }

    public void setSymbol(Class /*isi ini dengan "< ? >" tanpa spasi, di mode html error soalnya*/ animal, char symbol) {
        symbols.put(animal, symbol);
    }

    public void show(int step, Field field) {
        System.out.println("Simulation step " + step + ": ");
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                Object animal = field.getObjectAt(new Location(i, j));
                if (animal == null) {
                    System.out.print(". ");
                } else {
                    Character symbol = symbols.get(animal.getClass());
                    System.out.print((symbol != null ? symbol : '?') + " ");
                }
            }
            System.out.println();
        }
        System.out.println();
    }
}




public class Main {
    public static void main(String[] args) {
        Simulator simulator = new Simulator(10, 10);
        simulator.simulate(5);  // simulasi 5 langkah
    }
}


Penjelasan Singkat

  • Abstract class Animal → memiliki atribut dasar dan metode abstrak move() & eat().
  • Rabbit → melompat dan makan rumput.
  • Fox → berlari cepat dan berburu kelinci.
  • Simulation → menunjukkan penerapan polimorfisme dengan array objek.

Kesimpulan

  • Abstract class digunakan sebagai kerangka dasar bagi subclass.
  • Polimorfisme memungkinkan pemanggilan metode yang berbeda melalui referensi yang sama.
  • Contoh di atas memperlihatkan penerapan OOP dalam kehidupan nyata.

Kamis, 06 November 2025

Pertemuan 11 : Inheritance pada Rental kendaraan

Testing Inheritance PBO - Pertemuan 11

Pemrograman Berbasis Objek (PBO)
Nama: Hosea Felix Sanjaya
NRP: 5025241177
Kelas: B
Tahun: 2025


Penerapan Inheritance pada Sistem Rental Kendaraan

Halo semuanya! 👋
Pada pertemuan kali ini, saya akan membahas tentang penerapan konsep inheritance dalam Java dengan studi kasus sistem rental kendaraan.
Konsep ini memperlihatkan bagaimana kelas induk dapat mewariskan atribut dan metode umum ke beberapa kelas turunan seperti Mobil, Motor, dan Sepeda. Selain itu, juga ditambahkan kelas Penyewa dan MainApp sebagai pengelola interaksi pengguna.

Tujuan

Tujuan dari program ini adalah untuk memahami konsep pewarisan (inheritance) serta penerapannya dalam pembuatan sistem sederhana berbasis OOP menggunakan Java. Setiap jenis kendaraan akan mewarisi atribut dasar dari kelas Kendaraan, dan memiliki perilaku khusus masing-masing.

Kode Program

1 Class Kendaraan

public class Kendaraan {
    private String merk;
    private String model;
    private int tahunProduksi;

    public Kendaraan(String merk, String model, int tahunProduksi) {
        this.merk = merk;
        this.model = model;
        this.tahunProduksi = tahunProduksi;
    }

    public String getMerk() {
        return merk;
    }

    public String getModel() {
        return model;
    }

    public int getTahunProduksi() {
        return tahunProduksi;
    }

    public String getInfo() {
        return merk + " " + model + " (" + tahunProduksi + ")";
    }
}

2 Class Mobil

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;
    }

    @Override
    public String getInfo() {
        return "Mobil: " + super.getInfo() + ", Roda: " + jumlahRoda;
    }
}

3 Class Motor

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;
    }

    @Override
    public String getInfo() {
        return "Motor: " + super.getInfo() + ", Roda: " + jumlahRoda;
    }
}

4 Class Sepeda

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;
    }

    @Override
    public String getInfo() {
        return "Sepeda: " + super.getInfo() + ", Jenis: " + jenisSepeda;
    }
}

5 Class Penyewa

public class Penyewa {
    private String nama;
    private Kendaraan kendaraanDisewa;

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

    public String getNama() {
        return nama;
    }

    public Kendaraan getKendaraanDisewa() {
        return kendaraanDisewa;
    }

    public String getInfo() {
        return "Nama: " + nama + "\nKendaraan: " + kendaraanDisewa.getInfo();
    }
}

6 Class MainApp

import java.util.ArrayList;
import java.util.Scanner;

public class MainApp {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        // Daftar kendaraan tersedia
        ArrayList daftarKendaraan = new ArrayList();
        daftarKendaraan.add(new Mobil("Toyota", "Avanza", 2020, 4));
        daftarKendaraan.add(new Motor("Honda", "Beat", 2022, 2));
        daftarKendaraan.add(new Sepeda("Polygon", "Helios", 2021, "Balap"));
        daftarKendaraan.add(new Mobil("Daihatsu", "Xenia", 2019, 4));
        daftarKendaraan.add(new Sepeda("BMX", "Street 3000", 2018, "BMX"));
        daftarKendaraan.add(new Motor("Yamaha", "NMax", 2023, 2));
        daftarKendaraan.add(new Mobil("Honda", "Civic", 2018, 4));
        daftarKendaraan.add(new Sepeda("United", "Trifold", 2020, "Lipat"));
        daftarKendaraan.add(new Motor("Suzuki", "Satria FU", 2017, 2));
        daftarKendaraan.add(new Mobil("Mitsubishi", "Pajero Sport", 2021, 4));

        // Daftar penyewa
        ArrayList daftarPenyewa = new ArrayList();
        daftarPenyewa.add(new Penyewa("Andi", daftarKendaraan.get(0)));
        daftarPenyewa.add(new Penyewa("Budi", daftarKendaraan.get(2)));
        daftarPenyewa.add(new Penyewa("Citra", daftarKendaraan.get(1)));

        int pilihan;

        do {
            System.out.println("\n===== MENU RENTAL KENDARAAN =====");
            System.out.println("1. Tampilkan daftar kendaraan");
            System.out.println("2. Tampilkan daftar penyewa");
            System.out.println("3. Tambah penyewa baru");
            System.out.println("4. Hapus penyewa");
            System.out.println("5. Keluar");
            System.out.print("Pilih menu: ");
            pilihan = input.nextInt();
            input.nextLine(); 
            
            switch(pilihan) {
                case 1:
                    System.out.println("\n=== DAFTAR KENDARAAN TERSEDIA ===");
                    int index = 1;
                    for (Kendaraan k : daftarKendaraan) {
                        System.out.println(index + ". " + k.getInfo());
                        index++;
                    }
                    break;

                case 2:
                    System.out.println("\n=== DAFTAR PENYEWA ===");
                    if (daftarPenyewa.isEmpty()) {
                        System.out.println("Belum ada penyewa.");
                    } else {
                        for (Penyewa p : daftarPenyewa) {
                            System.out.println(p.getInfo());
                            System.out.println("---------------------");
                        }
                    }
                    break;

                case 3:
                    System.out.println("\nTambahkan Penyewa Baru");
                    System.out.print("Nama penyewa: ");
                    String nama = input.nextLine();

                    System.out.println("\nPilih kendaraan yang ingin disewa:");
                    for (int i = 0; i < daftarKendaraan.size(); i++) {
                        System.out.println((i + 1) + ". " + daftarKendaraan.get(i).getInfo());
                    }
                    System.out.print("Masukkan nomor kendaraan: ");
                    int pilihKendaraan = input.nextInt();
                    input.nextLine();

                    if (pilihKendaraan < 1 || pilihKendaraan > daftarKendaraan.size()) {
                        System.out.println("Pilihan tidak valid!");
                    } else {
                        Kendaraan dipilih = daftarKendaraan.get(pilihKendaraan - 1);
                        daftarPenyewa.add(new Penyewa(nama, dipilih));
                        System.out.println("Penyewa berhasil ditambahkan!");
                    }
                    break;

                case 4:
                    System.out.println("\nMasukkan nama penyewa yang akan dihapus: ");
                    String hapusNama = input.nextLine();
                    
                    boolean ditemukan = false;
                    for (int i = 0; i < daftarPenyewa.size(); i++) {
                        if (daftarPenyewa.get(i).getNama().equalsIgnoreCase(hapusNama)) {
                            daftarPenyewa.remove(i);
                            System.out.println("Penyewa berhasil dihapus!");
                            ditemukan = true;
                            break;
                        }
                    }
                    if (!ditemukan) {
                        System.out.println("Penyewa tidak ditemukan.");
                    }
                    break;

                case 5:
                    System.out.println("Terima kasih telah menggunakan sistem rental!");
                    break;

                default:
                    System.out.println("Pilihan tidak valid!");
            }
        } while(pilihan != 5);

        input.close();
    }
}

📸 Dokumentasi

Kamis, 30 Oktober 2025

Pertemuan 10 : Testing dan Debugging

Testing Sales Item PBO (Pertemuan 10)

Pemrograman Berbasis Objek (PBO)
Nama: Hosea Felix Sanjaya
NRP: 5025241177
Kelas: B
Tahun: 2025


Testing Class Comment di Java

Halo semuanya! 👋
Pada kesempatan kali ini, saya mau membahas tentang pengujian (testing) untuk sebuah class sederhana bernama Comment.
Class ini dirancang untuk merepresentasikan sebuah komentar pengguna, misalnya dalam sistem ulasan (review system) pada aplikasi e-commerce atau forum diskusi.

Tujuan Class

Class Comment memiliki fungsi utama untuk menyimpan informasi berikut:

  • Author (Penulis komentar)
  • Text (Isi komentar)
  • Rating (Penilaian dalam skala 1–5)
  • Votes (Jumlah suara atau dukungan)

Selain menyimpan data, class ini juga mendukung operasi sederhana seperti:

  • upvote() → menambah satu suara positif.
  • downvote() → mengurangi satu suara.

Kode Program Class Comment

/**
 * Write a description of class Comment here.
 *
 * @author Hosea Felix Sanjaya
 * @version 30 Oct 2025
 */
public class Comment {
    private String author;
    private String text;
    private int rating;
    private int votes;
    
    public Comment(String author, String text, int rating) {
        this.author = author;
        this.text = text;
        this.rating = rating;
        this.votes = 0;
    }
    
    public String getAuthor() {
        return author;
    }

    public String getText() {
        return text;
    }

    public int getRating() {
        return rating;
    }

    public int getVoteCount() {
        return votes;
    }
    
    public void upvote() {
        votes++;
    }

    public void downvote() {
        votes--;
    }
    
    public String getFullDetails() {
        return "Author: " + author + "\n" +
               "Rating: " + rating + "/5\n" +
               "Votes: " + votes + "\n" +
               "Comment: " + text;
    }
}

Class Testing: CommentTester

Untuk menguji fungsionalitas class Comment, kita buat class lain bernama CommentTester.
Class ini akan membuat objek Comment, menampilkan detailnya, dan mencoba melakukan voting.

/**
 * Class untuk mengetes Comment class.
 *
 * @author Hose
 * @version 30 Oct 2025
 */
public class CommentTester {
    public static void main(String[] args) {
        // Membuat objek comment
        Comment comment1 = new Comment("Hosea", "Produk ini sangat bagus!", 5);
        
        // Menampilkan detail awal
        System.out.println("=== Detail Awal ===");
        System.out.println(comment1.getFullDetails());
        
        // Melakukan voting
        comment1.upvote();
        comment1.upvote();
        comment1.downvote();
        
        // Menampilkan hasil setelah voting
        System.out.println("\n=== Setelah Voting ===");
        System.out.println(comment1.getFullDetails());
    }
}

Hasil Output

=== Detail Awal ===
Author: Hosea
Rating: 5/5
Votes: 0
Comment: Produk ini sangat bagus!

=== Setelah Voting ===
Author: Hosea
Rating: 5/5
Votes: 1
Comment: Produk ini sangat bagus!

Dokumentasi







Selasa, 21 Oktober 2025

Pertemuan 9 - Implementasi World of Zuul

Analisis Desain Kelas PBO (Pertemuan 9) - Studi Kasus: World of Zuul

Pemrograman Berbasis Objek (PBO)

Nama   : Hosea Felix Sanjaya
NRP     : 5025241177
Kelas  : B
Tahun : 2025


Pendahuluan

Dalam materi perkuliahan Pemrograman Berbasis Objek (PBO) pertemuan ke-9, kita membahas dua konsep fundamental dalam desain perangkat lunak yang berkualitas: Cohesion (Kohesi) dan Coupling (Kopling). Prinsip utamanya adalah untuk selalu mengusahakan "High Cohesion, Loose Coupling".

  • High Cohesion (Kohesi Tinggi): Menandakan bahwa sebuah kelas memiliki satu tanggung jawab yang jelas dan terfokus. Semua metode dan atribut di dalam kelas tersebut bekerja bersama untuk mencapai satu tujuan tunggal.
  • Loose Coupling (Kopling Longgar): Menandakan bahwa antar kelas memiliki tingkat ketergantungan yang rendah. Perubahan pada satu kelas (misalnya, perbaikan internal) sebisa mungkin tidak mengharuskan perubahan pada kelas lain yang menggunakannya.

Untuk memahami penerapan konsep ini, kita akan melakukan analisis terhadap "World of Zuul", sebuah studi kasus game petualangan berbasis teks sederhana yang sangat populer digunakan dalam pengajaran OOP.

Deskripsi Sistem (World of Zuul)

World of Zuul adalah sebuah game petualangan berbasis teks yang sangat sederhana. Pemain dapat "berjalan-jalan" di dalam sebuah lingkungan kampus fiksi dengan mengetikkan perintah teks sederhana.

Tujuan dari game ini (dari sisi akademis) adalah untuk mendemonstrasikan bagaimana sebuah program dapat dipecah menjadi beberapa kelas yang saling berinteraksi, masing-masing dengan tanggung jawabnya sendiri.

Alur Sistem (Game Flow)

  1. Game dimulai dan menampilkan pesan selamat datang serta lokasi awal pemain.
  2. Game mencetak pintu keluar (exits) yang tersedia dari lokasi saat ini.
  3. Game menunggu pemain mengetikkan perintah (misalnya, "go south" atau "help").
  4. Parser akan membaca input dan mengubahnya menjadi objek Command.
  5. Game akan memproses objek Command tersebut:
    • Jika perintah "go", pemain akan dipindahkan ke Room lain (jika ada pintu).
    • Jika perintah "help", game akan menampilkan daftar perintah yang valid.
    • Jika perintah "quit", game akan berakhir.
  6. Loop berlanjut kembali ke langkah 2 sampai pemain mengetik "quit".

Kode Program Lengkap

Sistem ini dibangun dari 5 kelas utama, ditambah 1 kelas `Main` untuk menjalankannya.

1. Kelas Game.java

Ini adalah kelas utama yang menginisialisasi semua bagian game, membuat ruangan, dan menjalankan game loop utama. Kelas ini juga yang memproses perintah dari pengguna.


/**
 * This class is the main class of the "World of Zuul" application. 
 * "World of Zuul" is a very simple, text based adventure game.  Users 
 * can walk around some scenery. That's all. It should really be extended 
 * to make it more interesting!
 *
 * To play this game, create an instance of this class and call the "play"
 * method.
 *
 * This main class creates and initialises all the others: it creates all
 * rooms, creates the parser and starts the game.  It also evaluates and
 * executes the commands that the parser returns.
 *
 * @author  Michael Kolling and David J. Barnes
 * @version 1.0 (February 2002)
 */

class Game 
{
    private Parser parser;
    private Room currentRoom;
        
    /**
     * Create the game and initialise its internal map.
     */
    public Game() 
    {
        createRooms();
        parser = new Parser();
    }

    /**
     * Create all the rooms and link their exits together.
     */
    private void createRooms()
    {
        Room outside, theatre, pub, lab, office;
      
        // create the rooms
        outside = new Room("outside the main entrance of the university");
        theatre = new Room("in a lecture theatre");
        pub = new Room("in the campus pub");
        lab = new Room("in a computing lab");
        office = new Room("in the computing admin office");
        
        // initialise room exits
        outside.setExits(null, theatre, lab, pub);
        theatre.setExits(null, null, null, outside);
        pub.setExits(null, outside, null, null);
        lab.setExits(outside, office, null, null);
        office.setExits(null, null, null, lab);

        currentRoom = outside;  // start game outside
    }

    /**
     * Main play routine.  Loops until end of play.
     */
    public void play() 
    {            
        printWelcome();

        // Enter the main command loop.  Here we repeatedly read commands and
        // execute them until the game is over.
                
        boolean finished = false;
        while (! finished) {
            Command command = parser.getCommand();
            finished = processCommand(command);
        }
        System.out.println("Thank you for playing.  Good bye.");
    }

    /**
     * Print out the opening message for the player.
     */
    private void printWelcome()
    {
        System.out.println();
        System.out.println("Welcome to Adventure!");
        System.out.println("Adventure is a new, incredibly boring adventure game.");
        System.out.println("Type 'help' if you need help.");
        System.out.println();
        System.out.println("You are " + currentRoom.getDescription());
        System.out.print("Exits: ");
        if(currentRoom.northExit != null)
            System.out.print("north ");
        if(currentRoom.eastExit != null)
            System.out.print("east ");
        if(currentRoom.southExit != null)
            System.out.print("south ");
        if(currentRoom.westExit != null)
            System.out.print("west ");
        System.out.println();
    }

    /**
     * Given a command, process (that is: execute) the command.
     * If this command ends the game, true is returned, otherwise false is
     * returned.
     */
    private boolean processCommand(Command command) 
    {
        boolean wantToQuit = false;

        if(command.isUnknown()) {
            System.out.println("I don't know what you mean...");
            return false;
        }

        String commandWord = command.getCommandWord();
        if (commandWord.equals("help"))
            printHelp();
        else if (commandWord.equals("go"))
            goRoom(command);
        else if (commandWord.equals("quit"))
            wantToQuit = quit(command);

        return wantToQuit;
    }

    // implementations of user commands:

    /**
     * Print out some help information.
     * Here we print some stupid, cryptic message and a list of the 
     * command words.
     */
    private void printHelp() 
    {
        System.out.println("You are lost. You are alone. You wander");
        System.out.println("around at the university.");
        System.out.println();
        System.out.println("Your command words are:");
        System.out.println("   go quit help");
    }

    /** * Try to go to one direction. If there is an exit, enter
     * the new room, otherwise print an error message.
     */
    private void goRoom(Command command) 
    {
        if(!command.hasSecondWord()) {
            // if there is no second word, we don't know where to go...
            System.out.println("Go where?");
            return;
        }

        String direction = command.getSecondWord();

        // Try to leave current room.
        Room nextRoom = null;
        if(direction.equals("north"))
            nextRoom = currentRoom.northExit;
        if(direction.equals("east"))
            nextRoom = currentRoom.eastExit;
        if(direction.equals("south"))
            nextRoom = currentRoom.southExit;
        if(direction.equals("west"))
            nextRoom = currentRoom.westExit;

        if (nextRoom == null)
            System.out.println("There is no door!");
        else {
            currentRoom = nextRoom;
            System.out.println("You are " + currentRoom.getDescription());
            System.out.print("Exits: ");
            if(currentRoom.northExit != null)
                System.out.print("north ");
            if(currentRoom.eastExit != null)
                System.out.print("east ");
            if(currentRoom.southExit != null)
                System.out.print("south ");
            if(currentRoom.westExit != null)
                System.out.print("west ");
            System.out.println();
        }
    }

    /** * "Quit" was entered. Check the rest of the command to see
     * whether we really quit the game. Return true, if this command
     * quits the game, false otherwise.
     */
    private boolean quit(Command command) 
    {
        if(command.hasSecondWord()) {
            System.out.println("Quit what?");
            return false;
        }
        else
            return true;  // signal that we want to quit
    }
}

2. Kelas Room.java

Kelas ini merepresentasikan satu lokasi atau ruangan dalam game. Setiap ruangan memiliki deskripsi dan referensi ke ruangan lain melalui pintu keluar (exit).


/**
 * Class Room - a room in an adventure game.
 *
 * This class is the main class of the "World of Zuul"
 * application. 
 * "World of Zuul" is a very simple, text based adventure game.
 *
 * A "Room" represents one location in the scenery of the game.  It is 
 * connected to other rooms via exits.  The exits are labelled north, 
 * east, south, west.  For each direction, the room stores a reference
 * to the neighboring room, or null if there is no exit
 * in that direction.
 *
 * @author  Michael Kolling and David J. Barnes
 * @version 1.0 (February 2002)
 */

class Room 
{
    public String description;
    public Room northExit;
    public Room southExit;
    public Room eastExit;
    public Room westExit;

    /**
     * Create a room described "description". Initially, it has
     * no exits. "description" is something like "a kitchen" or
     * "an open court yard".
     */
    public Room(String description) 
    {
        this.description = description;
    }

    /**
     * Define the exits of this room.  Every direction either leads
     * to another room or is null (no exit there).
     */
    public void setExits(Room north, Room east, Room south, Room west) 
    {
        if(north != null)
            northExit = north;
        if(east != null)
            eastExit = east;
        if(south != null)
            southExit = south;
        if(west != null)
            westExit = west;
    }

    /**
     * Return the description of the room (the one that was defined
     * in the constructor).
     */
    public String getDescription()
    {
        return description;
    }
}

3. Kelas Parser.java

Kelas ini bertanggung jawab membaca input dari pengguna dan mengubahnya menjadi objek Command yang dapat dimengerti oleh game.


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

/**
 * This class is the main class of the "World of Zuul"
 * application. 
 * "World of Zuul" is a very simple, text based adventure game.
 *
 * This parser reads user input and tries to interpret it as an "Adventure"
 * command. Every time it is called it reads a line from the terminal and
 * tries to interpret the line as a two word command. It returns the command
 * as an object of class Command.
 *
 * The parser has a set of known command words. It checks user input against
 * the known commands, and if the input is not one of the known commands, it
 * returns a command object that is marked as an unknown command.
 *
 * @author  Michael Kolling and David J. Barnes
 * @version 1.0 (February 2002)
 */

class Parser 
{

    private CommandWords commands;  // holds all valid command words

    public Parser() 
    {
        commands = new CommandWords();
    }

    public Command getCommand() 
    {
        String inputLine = "";   // will hold the full input line
        String word1;
        String word2;

        System.out.print("> ");     // print prompt

        BufferedReader reader = 
            new BufferedReader(new InputStreamReader(System.in));
        try {
            inputLine = reader.readLine();
        }
        catch(java.io.IOException exc) {
            System.out.println ("There was an error during reading: "
                                + exc.getMessage());
        }

        StringTokenizer tokenizer = new StringTokenizer(inputLine);

        if(tokenizer.hasMoreTokens())
            word1 = tokenizer.nextToken();      // get first word
        else
            word1 = null;
        if(tokenizer.hasMoreTokens())
            word2 = tokenizer.nextToken();      // get second word
        else
            word2 = null;

        // note: we just ignore the rest of the input line.

        // Now check whether this word is known. If so, create a command
        // with it. If not, create a "null" command (for unknown command).

        if(commands.isCommand(word1))
            return new Command(word1, word2);
        else
            return new Command(null, word2);
    }
}

4. Kelas Command.java

Kelas ini adalah kelas data sederhana yang menyimpan informasi tentang perintah yang diketik pengguna. Ini memisahkan kata perintah (kata pertama) dan kata kedua.


/**
 * This class is the main class of the "World of Zuul" application. 
 * "World of Zuul" is a very simple, text based adventure game.
 *
 * This class holds information about a command that was issued by the user.
 * A command currently consists of two strings: a command word and a second
 * word (for example, if the command was "take map", then the two strings
 * are "take" and "map").
 *
 * The way this is used is: Commands are already checked for being valid
 * command words. If the user entered an invalid command (a word that is not
 * known) then the command word is <null>.
 *
 * If the command has only one word, then the second word is <null>.
 *
 * @author  Michael Kolling and David J. Barnes
 * @version 1.0 (February 2002)
 */

class Command
{
    private String commandWord;
    private String secondWord;

    /**
     * Create a command object. First and second word must be supplied, but
     * either one (or both) can be null. The command word should be null to
     * indicate that this was a command that was not recognised by this game.
     */
    public Command(String firstWord, String secondWord)
    {
        commandWord = firstWord;
        this.secondWord = secondWord;
    }

    /**
     * Return the command word (the first word) of this command. If the
     * command was not understood, the result is null.
     */
    public String getCommandWord()
    {
        return commandWord;
    }

    /**
     * Return the second word of this command. Returns null if there was no
     * second word.
     */
    public String getSecondWord()
    {
        return secondWord;
    }

    /**
     * Return true if this command was not understood.
     */
    public boolean isUnknown()
    {
        return (commandWord == null);
    }

    /**
     * Return true if the command has a second word.
     */
    public boolean hasSecondWord()
    {
        return (secondWord != null);
    }
}

5. Kelas CommandWords.java

Kelas ini memiliki satu tanggung jawab: menyimpan dan memvalidasi daftar semua kata perintah yang sah dalam game.


/**
 * This class is the main class of the "World of Zuul"
 * application. 
 * "World of Zuul" is a very simple, text based
 * adventure game.
 *
 * This class holds an enumeration of all command words
 * known to the game.
 * It is used to recognise commands as they are typed
 * in.
 *
 * @author  Michael Kolling and David J. Barnes
 * @version 1.0 (February 2002)
 */

class CommandWords
{
    // a constant array that holds all valid command
    // words
    private static final String validCommands[] = {
        "go", "quit", "help", "look"
    };

    /**
     * Constructor - initialise the command words.
     */
    public CommandWords()
    {
        // nothing to do at the moment...
    }

    /**
     * Check whether a given String is a valid command
     * word. 
     * Return true if it is, false if it isn't.
     */
    public boolean isCommand(String aString)
    {
        for(int i = 0; i < validCommands.length; i++) {
            if(validCommands[i].equals(aString))
                return true;
        }
        // if we get here, the string was not found in
        // the commands
        return false;
    }
}

6. Kelas Main.java (Untuk Menjalankan)

Kelas ini tidak ada di gambar, namun diperlukan untuk membuat objek `Game` dan memulai permainan.


public class Main {
    public static void main(String[] args) {
        // Buat objek Game baru
        Game game = new Game();
        
        // Panggil metode play() untuk memulai permainan
        game.play();
    }
}

Analisis Desain (Kohesi & Kopling)

Sekarang, mari kita evaluasi desain kelas "World of Zuul" menggunakan konsep yang telah kita pelajari.

Analisis Kohesi (Prinsip: High Cohesion)

Kohesi mengukur seberapa fokus sebuah kelas terhadap satu tanggung jawab. Desain yang baik memiliki Kohesi Tinggi.

Contoh Kohesi Tinggi (Desain Baik)

  • Room: Sangat kohesif. Tanggung jawabnya hanya satu: merepresentasikan satu lokasi. Semua data (description, northExit, dll.) dan metode (setExits, getDescription) hanya berfokus pada properti ruangan itu saja.
  • Command: Sangat kohesif. Tanggung jawabnya hanya menyimpan dua kata dari perintah pengguna (commandWord dan secondWord).
  • CommandWords: Sangat kohesif. Tanggung jawab tunggalnya adalah mengetahui dan memvalidasi semua kata perintah yang sah dalam game.
  • Parser: Kohesif. Tanggung jawabnya jelas, yaitu membaca input mentah dari pengguna dan mengubahnya menjadi objek Command.

Contoh Kohesi Rendah (Desain Kurang Baik)

  • Game: Kelas ini adalah contoh utama dari Kohesi Rendah. Perhatikan semua tanggung jawabnya yang berbeda:
    1. Membuat dan menginisialisasi semua ruangan (createRooms).
    2. Menjalankan game loop utama (play).
    3. Memproses setiap jenis perintah (processCommand).
    4. Menjalankan logika spesifik untuk setiap perintah (printHelp, goRoom, quit).

Kelas Game melakukan terlalu banyak hal. Ini membuatnya sulit dipahami, di-debug, dan dirawat. Jika kita ingin menambah 10 perintah baru, kita harus mengubah kelas Game di banyak tempat.

Analisis Kopling (Prinsip: Loose Coupling)

Kopling mengukur seberapa besar ketergantungan satu kelas terhadap kelas lain. Desain yang baik memiliki Kopling Longgar (Loose Coupling).

Contoh Kopling Longgar (Desain Baik)

  • Parser dan Game: Kopling di antara keduanya cukup longgar.
    • Kelas Game hanya perlu memanggil parser.getCommand().
    • Game tidak peduli bagaimana Parser mendapatkan perintah itu (apakah dari terminal, file, atau internet).
    • Parser juga tidak tahu apa-apa tentang Game atau Room. Ia hanya mengembalikan objek Command.
    • Ini adalah desain yang bagus. Kita bisa mengganti seluruh implementasi Parser tanpa merusak kelas Game.

Contoh Kopling Erat (Desain Kurang Baik)

  • Game dan Command: Di dalam metode processCommand, terdapat blok if-else yang besar. Ini adalah kopling yang erat. Jika kita ingin menambah perintah baru (misal, "look" atau "take"), kita harus memodifikasi kode internal kelas Game dengan menambah else if baru. Ini membuat Game sangat bergantung pada setiap detail perintah yang ada.
  • Game dan Room: Ini adalah contoh terburuk dalam kode ini. Kelas Game (dalam goRoom dan printWelcome) mengakses fields dari Room secara langsung:
    nextRoom = currentRoom.northExit;
    if(currentRoom.northExit != null) ...
    Ini terjadi karena kelas Room mendeklarasikan variabelnya sebagai public (public Room northExit;), yang merupakan pelanggaran berat terhadap prinsip Enkapsulasi.

    Ini adalah kopling yang sangat erat. Game bergantung pada struktur internal Room. Jika kita ingin mengubah cara Room menyimpan exit (misalnya, menjadi HashMap), maka kelas Game akan rusak total dan harus ditulis ulang.

Kesimpulan

Studi kasus "World of Zuul" adalah contoh yang sangat baik untuk belajar. Desainnya berhasil memisahkan beberapa tanggung jawab dengan baik (seperti Parser dan Command) yang menunjukkan Kohesi Tinggi dan Kopling Longgar.

Namun, desain ini juga secara sengaja menunjukkan praktik yang buruk (Kohesi Rendah di kelas Game dan Kopling Erat antara Game dan Room) sebagai bahan pelajaran. Langkah selanjutnya dari materi ini biasanya adalah melakukan Refactoring: yaitu memperbaiki struktur kode ini untuk meningkatkan kohesi dan mengurangi kopling, tanpa mengubah fungsionalitas game.

Dokumentasi

Pertemuan 15 : Aplikasi CRUD JAVA

Pertemuan 15 - Java CRUD Database Tanggal: 9 Desember 2025 Nama: Hosea Felix Sanjaya NRP: 5025241177 a. Setup & Persiapan Envi...