PERBAIKAN DAN OPTIMALISASI KODE PROGRAM PADA GAME CATUR (Part-3)

 PERBAIKAN DAN OPTIMALISASI KODE PROGRAM PADA GAME CATUR

(Part-3)


Berikut adalah implementasi lengkap game catur berbasis Object-Oriented Programming (OOP) dalam Python. Untuk memudahkan struktur kode, game ini terdiri dari beberapa file:

  1. piece.py: Mengelola logika terkait bidak catur.
  2. board.py: Mengelola logika papan dan validasi gerakan.
  3. game.py: Mengelola logika permainan, giliran pemain, dan status permainan (skak, skakmat, atau pat).
  4. main.py: Menjalankan permainan, menggambar papan, dan menangani input pemain.

File 1: piece.py

class Piece:
    def __init__(self, color):
        self.color = color
        self.has_moved = False

    def is_valid_move(self, board, start, end):
        raise NotImplementedError("This method should be implemented in subclasses")


class Pawn(Piece):
    def is_valid_move(self, board, start, end):
        direction = 1 if self.color == "white" else -1
        start_row, start_col = start
        end_row, end_col = end

        # Move forward
        if end_col == start_col:
            if end_row == start_row + direction and board[end_row][end_col] is None:
                return True
            if (
                end_row == start_row + 2 * direction
                and not self.has_moved
                and board[start_row + direction][start_col] is None
                and board[end_row][end_col] is None
            ):
                return True

        # Capture diagonally
        if abs(end_col - start_col) == 1 and end_row == start_row + direction:
            if board[end_row][end_col] and board[end_row][end_col].color != self.color:
                return True

        # En passant
        if abs(end_col - start_col) == 1 and end_row == start_row + direction:
            if isinstance(board[start_row][end_col], Pawn) and board[start_row][end_col].color != self.color:
                return True

        return False


class Knight(Piece):
    def is_valid_move(self, board, start, end):
        start_row, start_col = start
        end_row, end_col = end
        return (abs(start_row - end_row), abs(start_col - end_col)) in [(2, 1), (1, 2)]


class Bishop(Piece):
    def is_valid_move(self, board, start, end):
        start_row, start_col = start
        end_row, end_col = end
        return abs(start_row - end_row) == abs(start_col - end_col)


class Rook(Piece):
    def is_valid_move(self, board, start, end):
        start_row, start_col = start
        end_row, end_col = end
        return start_row == end_row or start_col == end_col


class Queen(Piece):
    def is_valid_move(self, board, start, end):
        return Rook(self.color).is_valid_move(board, start, end) or Bishop(self.color).is_valid_move(board, start, end)


class King(Piece):
    def is_valid_move(self, board, start, end):
        start_row, start_col = start
        end_row, end_col = end
        if abs(start_row - end_row) <= 1 and abs(start_col - end_col) <= 1:
            return True
        return False

File 2: board.py

from piece import Pawn, Knight, Bishop, Rook, Queen, King

class Board:
    def __init__(self):
        self.board = self.setup_board()

    def setup_board(self):
        # Create an 8x8 chessboard with pieces
        board = [[None] * 8 for _ in range(8)]

        # Place pawns
        for i in range(8):
            board[1][i] = Pawn("black")
            board[6][i] = Pawn("white")

        # Place other pieces
        pieces = [Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook]
        for i, piece in enumerate(pieces):
            board[0][i] = piece("black")
            board[7][i] = piece("white")

        return board

    def move_piece(self, start, end):
        piece = self.board[start[0]][start[1]]
        if piece and piece.is_valid_move(self.board, start, end):
            self.board[end[0]][end[1]] = piece
            self.board[start[0]][start[1]] = None
            piece.has_moved = True
            return True
        return False

    def is_in_check(self, color):
        # Determine if a player is in check
        pass

    def is_checkmate(self, color):
        # Determine if a player is in checkmate
        pass

    def draw(self):
        for row in self.board:
            print(" ".join([str(piece) if piece else "." for piece in row]))

File 3: game.py

from board import Board

class Game:
    def __init__(self):
        self.board = Board()
        self.turn = "white"

    def play_turn(self, start, end):
        if self.board.move_piece(start, end):
            if self.board.is_checkmate(self.turn):
                print(f"{self.turn} is in checkmate. Game over!")
                return False
            self.turn = "black" if self.turn == "white" else "white"
            return True
        else:
            print("Invalid move. Try again.")
            return False

    def is_game_over(self):
        return self.board.is_checkmate(self.turn)

File 4: main.py

from game import Game

def parse_input(user_input):
    try:
        start, end = user_input.split()
        start = (8 - int(start[1]), ord(start[0].lower()) - ord("a"))
        end = (8 - int(end[1]), ord(end[0].lower()) - ord("a"))
        return start, end
    except Exception:
        print("Invalid input format. Use 'e2 e4'")
        return None, None

def main():
    game = Game()
    while not game.is_game_over():
        game.board.draw()
        print(f"{game.turn}'s turn")
        user_input = input("Enter your move (e.g., e2 e4): ")
        start, end = parse_input(user_input)
        if start and end:
            game.play_turn(start, end)

if __name__ == "__main__":
    main()



Untuk mengeksekusi keempat file ini agar game catur dapat berjalan dengan tampilan grafis dan menggunakan gambar buah catur yang ada di folder images, (file gambar buah catur putih dan hitam semuanya ada folder images, misalnya pada punya saya ada di  direktori/folder berikut: /storage/emulated/0/AppJadi/Catur/Catur Berbasis OOP/images . Gambar buah catur bisa di download di sini: GAMBAR TRANSPARAN LENGKAP BUAH CATUR PUTIH DAN HITAM.

Selanjutnya kita dapat memanfaatkan Tkinter untuk membuat GUI (Graphical User Interface).

Berikut adalah langkah-langkahnya:


1. Tambahkan Gambar ke Papan Catur

Gunakan pustaka Tkinter untuk menampilkan papan catur dengan gambar bidak dari direktori yang Anda tentukan.


2. Buat File main_with_gui.py

File ini akan menggunakan gambar dari folder /storage/emulated/0/AppJadi/Catur/Catur Berbasis OOP/images dan menggambar papan catur dengan buah catur.

Berikut adalah kode lengkapnya:

import tkinter as tk
from game import Game
from PIL import Image, ImageTk

class ChessGUI:
    def __init__(self, root, game, image_path):
        self.root = root
        self.game = game
        self.image_path = image_path
        self.board_frame = tk.Frame(root)
        self.board_frame.pack()
        self.tiles = {}
        self.images = {}
        self.create_board()
        self.update_board()

    def create_board(self):
        # Create an 8x8 grid for the chessboard
        for row in range(8):
            for col in range(8):
                color = "white" if (row + col) % 2 == 0 else "gray"
                frame = tk.Frame(self.board_frame, width=80, height=80, bg=color)
                frame.grid(row=row, column=col)
                frame.pack_propagate(False)
                label = tk.Label(frame, bg=color)
                label.pack(fill=tk.BOTH, expand=True)
                label.bind("<Button-1>", lambda e, r=row, c=col: self.on_click(r, c))
                self.tiles[(row, col)] = label

    def load_images(self):
        # Load images for each piece type
        piece_names = ["pawn", "rook", "knight", "bishop", "queen", "king"]
        for color in ["white", "black"]:
            for piece in piece_names:
                img_path = f"{self.image_path}/{color}_{piece}.png"
                image = Image.open(img_path).resize((80, 80))
                self.images[f"{color}_{piece}"] = ImageTk.PhotoImage(image)

    def update_board(self):
        self.load_images()
        for row in range(8):
            for col in range(8):
                piece = self.game.board.board[row][col]
                if piece:
                    piece_name = piece.__class__.__name__.lower()
                    color = piece.color
                    image = self.images[f"{color}_{piece_name}"]
                    self.tiles[(row, col)].config(image=image)
                    self.tiles[(row, col)].image = image
                else:
                    self.tiles[(row, col)].config(image=None)

    def on_click(self, row, col):
        if hasattr(self, "selected"):
            start = self.selected
            end = (row, col)
            if self.game.play_turn(start, end):
                self.update_board()
            del self.selected
        else:
            self.selected = (row, col)

def main():
    root = tk.Tk()
    root.title("Catur Berbasis OOP")
    image_path = "/storage/emulated/0/AppJadi/Catur/Catur Berbasis OOP/images"
    game = Game()
    gui = ChessGUI(root, game, image_path)
    root.mainloop()

if __name__ == "__main__":
    main()

3. Struktur Folder Gambar

Pastikan Anda menyimpan gambar-gambar buah catur di direktori images dengan nama file berikut:

  • Putih:

    • white_pawn.png
    • white_rook.png
    • white_knight.png
    • white_bishop.png
    • white_queen.png
    • white_king.png
  • Hitam:

    • black_pawn.png
    • black_rook.png
    • black_knight.png
    • black_bishop.png
    • black_queen.png
    • black_king.png

4. Eksekusi Game

  1. Simpan semua file kode (piece.py, board.py, game.py, main_with_gui.py) di dalam satu folder kerja.

  2. Pastikan folder images dengan semua gambar bidak catur sudah lengkap sesuai struktur.

  3. Jalankan file main_with_gui.py menggunakan Python:

    python main_with_gui.py
    

5. Catatan Penting

  • Library yang Dibutuhkan: Pastikan Anda menginstal pustaka Pillow untuk memproses gambar.
    • Instal Pillow dengan perintah: pip install pillow.
  • Jika direktori images berada di lokasi lain, ubah variabel image_path dalam kode main_with_gui.py sesuai lokasi sebenarnya.

Gmbar-gambar buah catur tidak harus berada dalam folder terpisah seperti white atau black. Anda cukup menyimpan semua file gambar langsung di dalam folder images dengan nama file seperti ini:

  • Untuk bidak putih:

    • white_pawn.png
    • white_rook.png
    • white_knight.png
    • white_bishop.png
    • white_queen.png
    • white_king.png
  • Untuk bidak hitam:

    • black_pawn.png
    • black_rook.png
    • black_knight.png
    • black_bishop.png
    • black_queen.png
    • black_king.png

Struktur Folder

Berikut adalah struktur folder yang benar:

/storage/emulated/0/AppJadi/Catur/Catur Berbasis OOP/
├── images/
│   ├── white_pawn.png
│   ├── white_rook.png
│   ├── white_knight.png
│   ├── white_bishop.png
│   ├── white_queen.png
│   ├── white_king.png
│   ├── black_pawn.png
│   ├── black_rook.png
│   ├── black_knight.png
│   ├── black_bishop.png
│   ├── black_queen.png
│   ├── black_king.png
├── piece.py
├── board.py
├── game.py
├── main_with_gui.py

Kode main_with_gui.py sudah diatur untuk langsung membaca file gambar dari folder images tanpa perlu subfolder seperti white atau black. Anda hanya perlu memastikan nama file-nya sesuai.

Jika Anda ingin memindahkan file gambar ke subfolder, seperti white atau black, maka Anda harus mengubah jalur file di kode, misalnya:

img_path = f"{self.image_path}/{color}/{piece}.png"

Namun, untuk kemudahan, sebaiknya simpan semua gambar dalam satu folder images.

Dengan langkah-langkah ini, Anda dapat memainkan game catur berbasis GUI lengkap dengan gambar buah catur. 

Bila terjadi Error yang menunjukkan bahwa program tidak menemukan file gambar misalnya white_rook.png di direktori yang ditentukan. Berikut adalah langkah-langkah untuk mengatasi masalah ini:

1. Pastikan File Gambar Tersedia

  • Periksa apakah semua file gambar berikut ada di dalam folder:
    /storage/emulated/0/AppJadi/Catur/Catur Berbasis OOP/images/

    File yang harus ada:

    • white_pawn.png
    • white_rook.png
    • white_knight.png
    • white_bishop.png
    • white_queen.png
    • white_king.png
    • black_pawn.png
    • black_rook.png
    • black_knight.png
    • black_bishop.png
    • black_queen.png
    • black_king.png

2. Pastikan Nama File Gambar Benar

  • Pastikan nama file gambar sesuai dengan yang ada di kode, termasuk huruf kecil dan besar (case-sensitive). Contoh:
    • white_rook.png (huruf kecil semua, bukan White_Rook.png atau white_Rook.png).

3. Pastikan Folder images Benar

  • Pastikan folder images berada di dalam direktori berikut:
    /storage/emulated/0/AppJadi/Catur/Catur Berbasis OOP/
    
  • Struktur direktori harus seperti ini:
    /storage/emulated/0/AppJadi/Catur/Catur Berbasis OOP/
    ├── images/
    │   ├── white_pawn.png
    │   ├── white_rook.png
    │   ├── white_knight.png
    │   ├── white_bishop.png
    │   ├── white_queen.png
    │   ├── white_king.png
    │   ├── black_pawn.png
    │   ├── black_rook.png
    │   ├── black_knight.png
    │   ├── black_bishop.png
    │   ├── black_queen.png
    │   ├── black_king.png
    ├── piece.py
    ├── board.py
    ├── game.py
    ├── main_with_gui.py
    

4. Cek Jalur di Kode

  • Dalam file main_with_gui.py, pastikan jalur folder gambar sudah benar:
    image_path = "/storage/emulated/0/AppJadi/Catur/Catur Berbasis OOP/images"
    

5. Debugging Manual

  • Untuk memastikan jalur benar, tambahkan kode berikut di file main_with_gui.py sebelum memuat gambar:
    import os
    print(f"Image path: {image_path}")
    for file in os.listdir(image_path):
        print(file)
    
    Jalankan program dan periksa apakah semua file gambar terdaftar.

6. Jika Masih Error

Jika error tetap muncul, kemungkinan ada masalah pada izin akses folder. Pastikan aplikasi Python Anda (seperti Pydroid 3) memiliki izin untuk membaca file dari direktori /storage/emulated/0/AppJadi/.

  • Coba pindahkan direktori ke lokasi default Pydroid, seperti:
    /data/user/0/ru.iiec.pydroid3/files/Catur/images
    
    Lalu ubah image_path di kode:
    image_path = "/data/user/0/ru.iiec.pydroid3/files/Catur/images"
    

Setelah semua langkah ini diikuti, game seharusnya dapat berjalan tanpa error.

Jika papan catur dan bidak sudah tampil dengan benar tetapi bidak tidak bisa digerakkan, kemungkinan besar ada masalah dengan implementasi event handler untuk klik dan logika validasi gerakan. Berikut langkah-langkah untuk memperbaikinya:


1. Periksa Logika Klik pada GUI

Pastikan di file main_with_gui.py, event handler seperti on_square_click sudah diimplementasikan dengan benar untuk menangkap klik pengguna pada papan catur.

Contoh kode untuk menangani klik:

def on_square_click(self, row, col):
    if self.selected_piece is None:  # Jika belum ada bidak yang dipilih
        piece = self.game.board.get_piece_at(row, col)  # Ambil bidak di posisi yang diklik
        if piece and piece.color == self.game.current_turn:  # Pastikan bidak sesuai giliran pemain
            self.selected_piece = (row, col)
            self.highlight_selected_piece(row, col)  # Highlight petak yang dipilih
    else:  # Jika sudah ada bidak yang dipilih
        from_row, from_col = self.selected_piece
        if self.game.move_piece(from_row, from_col, row, col):  # Coba pindahkan bidak
            self.update_board()  # Update tampilan papan
        self.selected_piece = None  # Reset seleksi bidak

2. Cek Fungsi move_piece di Class Game

Di file game.py, pastikan fungsi move_piece mengatur logika untuk memindahkan bidak.

Contoh kode yang benar untuk move_piece:

def move_piece(self, from_row, from_col, to_row, to_col):
    piece = self.board.get_piece_at(from_row, from_col)
    if piece and piece.is_valid_move(from_row, from_col, to_row, to_col, self.board):
        self.board.move_piece(from_row, from_col, to_row, to_col)
        self.switch_turn()  # Ganti giliran pemain
        return True
    return False

3. Validasi Gerakan Bidak

Pastikan validasi gerakan bidak (misalnya pion, kuda, raja, dll.) sudah diimplementasikan di class Piece dan subclass-nya di file piece.py.

Contoh validasi pion:

class Pawn(Piece):
    def is_valid_move(self, from_row, from_col, to_row, to_col, board):
        direction = 1 if self.color == 'white' else -1
        # Gerakan maju 1 langkah
        if from_col == to_col and to_row == from_row + direction:
            return board.get_piece_at(to_row, to_col) is None
        # Gerakan menyerang secara diagonal
        if abs(from_col - to_col) == 1 and to_row == from_row + direction:
            target = board.get_piece_at(to_row, to_col)
            return target is not None and target.color != self.color
        return False

4. Update Tampilan Papan Setelah Gerakan

Setelah bidak digerakkan, pastikan GUI diperbarui. Tambahkan panggilan ke self.update_board() di dalam event handler untuk klik, seperti di poin pertama.


5. Debugging

Untuk memeriksa apakah klik bekerja dan event handler dijalankan, tambahkan debug log di on_square_click. Contoh:

def on_square_click(self, row, col):
    print(f"Clicked on square: ({row}, {col})")

Jalankan program dan cek di konsol apakah koordinat petak diklik muncul.


6. Cek Giliran Pemain

Pastikan giliran pemain (self.game.current_turn) sudah diperbarui setiap kali bidak berhasil dipindahkan.


7. Contoh Fungsi Highlight

Agar pengguna tahu bidak telah dipilih, tambahkan highlight pada petak yang sedang dipilih:

def highlight_selected_piece(self, row, col):
    self.canvas.create_rectangle(
        col * 80, row * 80, (col + 1) * 80, (row + 1) * 80,
        outline="yellow", width=3
    )

Panggil fungsi ini saat bidak dipilih.


8. Pastikan Semua Subclass Piece Memiliki is_valid_move

Setiap bidak (pawn, rook, knight, bishop, queen, king) harus memiliki implementasi metode is_valid_move yang sesuai.


Setelah semua langkah di atas dilakukan, bidak seharusnya bisa digerakkan sesuai aturan catur. 


Kode pada file main_with_gui.py, kita modifikasi menjadi seperti berikut ini:

import tkinter as tk

from game import Game

from PIL import Image, ImageTk


class ChessGUI:


    def __init__(self, root, game, image_path):

        self.root = root

        self.game = game

        self.image_path = image_path

        self.board_frame = tk.Frame(root)

        self.board_frame.pack()

        self.tiles = {}

        self.images = {}

        self.create_board()

        self.update_board()


    def create_board(self):

        # Create an 8x8 grid for the chessboard

        for row in range(8):

            for col in range(8):

                color = "white" if (row + col) % 2 == 0 else "gray"

                frame = tk.Frame(self.board_frame, width=80, height=80, bg=color)

                frame.grid(row=row, column=col)

                frame.pack_propagate(False)

                label = tk.Label(frame, bg=color)

                label.pack(fill=tk.BOTH, expand=True)

                label.bind("<Button-1>", lambda e, r=row, c=col: self.on_click(r, c))

                self.tiles[(row, col)] = label


    def load_images(self):

        # Load images for each piece type

        piece_names = ["pawn", "rook", "knight", "bishop", "queen", "king"]

        for color in ["white", "black"]:

            for piece in piece_names:

                img_path = f"{self.image_path}/{color}_{piece}.png"

                image = Image.open(img_path).resize((80, 80))

                self.images[f"{color}_{piece}"] = ImageTk.PhotoImage(image)

                     

    def update_board(self):

        self.load_images()

        for row in range(8):

            for col in range(8):

                piece = self.game.board.board[row][col]

                if piece:

                    piece_name = piece.__class__.__name__.lower()

                    color = piece.color

                    image = self.images[f"{color}_{piece_name}"]

                    self.tiles[(row, col)].config(image=image)

                    self.tiles[(row, col)].image = image

                else:

                    self.tiles[(row, col)].config(image=None)                    

                    

    def highlight_selected_piece(self, row, col):

    # Sorot petak yang dipilih

        self.canvas.create_rectangle(

        col * 80, row * 80, (col + 1) * 80, (row + 1) * 80,

        outline="yellow", width=3

    )                    

                    

    def on_click(self, row, col):

    # Tangani klik pada papan catur

        if self.selected_piece is None:  # Jika belum ada bidak yang dipilih

            piece = self.game.board.get_piece_at(row, col)

            if piece and piece.color == self.game.current_turn:  # Hanya bidak sesuai giliran pemain

                self.selected_piece = (row, col)

                self.highlight_selected_piece(row, col)  # Highlight petak yang dipilih

        else:

            from_row, from_col = self.selected_piece

            if self.game.move_piece(from_row, from_col, row, col):  # Coba pindahkan bidak

                self.update_board()  # Update tampilan papan

            self.selected_piece = None  # Reset seleksi bidak                                  

                    

    def on_square_click(self, row, col):

        if self.selected_piece is None:  # Jika belum ada bidak yang dipilih

            piece = self.game.board.get_piece_at(row, col)  # Ambil bidak di posisi yang diklik

            if piece and piece.color == self.game.current_turn:  # Pastikan bidak sesuai giliran pemain

                self.selected_piece = (row, col)

                self.highlight_selected_piece(row, col)  # Highlight petak yang dipilih

        else:  # Jika sudah ada bidak yang dipilih

            from_row, from_col = self.selected_piece

            if self.game.move_piece(from_row, from_col, row, col):  # Coba pindahkan bidak

                self.update_board()  # Update tampilan papan

            self.selected_piece = None  # Reset seleksi bidak                      

        print(f"Clicked on square: ({row}, {col})")

        

def main():

    root = tk.Tk()

    root.title("Catur Berbasis OOP")

    image_path = "/storage/emulated/0/AppJadi/Catur/Catur Berbasis OOP/images"

    game = Game()

    gui = ChessGUI(root, game, image_path)

    root.mainloop()


if __name__ == "__main__":

    main()


Lalu pastikan Bind Event Sudah ditambahkan pada bagian kode berikut di main_with_gui.py, pilihannya ada 2 (dua) yaitu:

label.bind("<Button-1>", lambda e, r=row, c=col: self.on_click(r, c))

Atau

self.canvas.bind("<Button-1>", self.handle_click)

Pastikan ini benar dan sudah sesuai pilihan Anda dengan metode on_click.

Kode Bind Event berikut:

label.bind("<Button-1>", lambda e, r=row, c=col: self.on_click(r, c))

Harus diletakkan dalam proses pembuatan papan catur pada ChessGUI, tepatnya di bagian yang membuat label untuk setiap petak papan catur di metode update_board atau metode lainnya yang bertugas menggambar papan catur.

Contoh lengkap penempatannya:

  1. Tempatkan di dalam loop for di metode update_board

    Di dalam ChessGUI pada file main_with_gui.py, temukan metode update_board. Tambahkan kode label.bind di bagian yang membuat label untuk setiap petak. Berikut contohnya:

    def update_board(self):
        """Update tampilan papan catur di GUI."""
        self.canvas.delete("all")  # Bersihkan papan
        self.load_images()
    
        for row in range(8):
            for col in range(8):
                # Gambarkan petak papan catur
                color = "white" if (row + col) % 2 == 0 else "gray"
                x1, y1 = col * 80, row * 80
                x2, y2 = x1 + 80, y1 + 80
                self.canvas.create_rectangle(x1, y1, x2, y2, fill=color)
    
                # Gambarkan bidak jika ada
                piece = self.game.board.get_piece_at(row, col)
                if piece:
                    img_path = self.get_image_path(piece)
                    image = Image.open(img_path).resize((80, 80))
                    tk_image = ImageTk.PhotoImage(image)
                    label = tk.Label(self.canvas, image=tk_image, bg=color)
                    label.image = tk_image
                    label.place(x=x1, y=y1)
    
                    # Bind klik untuk memilih/memindahkan bidak
                    label.bind("<Button-1>", lambda e, r=row, c=col: self.on_click(r, c))
    
  2. Penyesuaian jika tidak ada label

    Jika Anda tidak menggunakan label tetapi hanya menggunakan canvas, Anda perlu memodifikasi metode on_click untuk menangani klik pada petak papan catur langsung dari canvas.

    Tambahkan bind pada canvas dengan kode seperti ini:

    self.canvas.bind("<Button-1>", self.handle_click)
    

    Kemudian tambahkan metode handle_click untuk menghitung petak yang diklik:

    def handle_click(self, event):
        col = event.x // 80
        row = event.y // 80
        self.on_click(row, col)
    

Jika menggunakan pendekatan label, pastikan bind diletakkan dalam loop pembuatan petak (seperti di langkah 1). Jika menggunakan canvas, gunakan metode handle_click seperti di langkah 2. Pilih yang sesuai dengan implementasi Anda.

Pilihan antara label.bind dan canvas.bind tergantung pada desain dan kebutuhan aplikasi Anda. Berikut adalah perbandingan kedua pendekatan tersebut:


1. label.bind

Menggunakan label.bind berarti Anda membuat tk.Label untuk setiap petak pada papan catur dan mengikat peristiwa klik langsung ke label tersebut.

Kelebihan:

  • Mudah digunakan: Anda bisa langsung menghubungkan setiap petak dengan klik mouse tanpa perlu memproses koordinat klik secara manual.
  • Visual yang terpisah: Setiap petak di papan catur memiliki objek label terpisah, sehingga lebih fleksibel untuk penyesuaian visual (misalnya, mengganti warna petak tertentu dengan mudah).
  • Penggunaan gambar lebih sederhana: Anda dapat menempelkan gambar langsung ke label.

Kekurangan:

  • Kinerja lebih lambat untuk papan besar: Karena setiap petak membutuhkan satu objek Label, ini bisa lebih berat untuk di-render, terutama jika papan catur sering diperbarui.
  • Lebih banyak objek GUI: Anda membuat 64 objek Label untuk papan catur (8x8), yang bisa menjadi kurang efisien dibandingkan menggambar langsung di canvas.

Cocok digunakan jika:

  • Anda ingin setiap petak memiliki properti individual (misalnya, warna atau gambar).
  • Anda memerlukan fitur yang lebih interaktif untuk setiap petak secara terpisah.

2. canvas.bind

Menggunakan canvas.bind berarti Anda menggambar papan catur secara langsung di objek Canvas dan menangkap peristiwa klik dengan menghitung koordinat klik (misalnya, baris dan kolom dari posisi mouse).

Kelebihan:

  • Lebih ringan dan efisien: Tidak ada objek GUI terpisah untuk setiap petak. Semua petak hanya berupa bentuk yang digambar di dalam satu objek Canvas.
  • Kinerja lebih baik: Menggambar di canvas lebih efisien daripada membuat banyak Label, terutama jika papan sering diperbarui.
  • Kontrol penuh atas rendering: Anda dapat menggambar elemen grafis kompleks seperti highlight, jalur, atau animasi langsung di canvas.

Kekurangan:

  • Mengelola klik lebih rumit: Anda harus menghitung petak mana yang diklik berdasarkan koordinat klik mouse (event.x dan event.y).
  • Visual lebih sederhana: Mengganti tampilan per petak mungkin memerlukan logika tambahan, karena Anda tidak memiliki objek GUI individual untuk setiap petak.

Cocok digunakan jika:

  • Anda ingin performa lebih baik (lebih ringan).
  • Anda lebih banyak menggambar grafis atau elemen dinamis pada papan catur.

Rekomendasi

  1. Gunakan canvas.bind jika Anda:

    • Ingin performa lebih baik.
    • Membutuhkan kontrol penuh atas papan (misalnya, animasi atau highlight jalur).
    • Membuat aplikasi yang kompleks atau sering diperbarui.
  2. Gunakan label.bind jika Anda:

    • Ingin implementasi lebih sederhana.
    • Membutuhkan interaktivitas atau properti berbeda untuk setiap petak secara individual.
    • Tidak keberatan dengan penggunaan sumber daya lebih besar (karena jumlah objek GUI lebih banyak).

Kesimpulan

Untuk aplikasi papan catur, canvas.bind lebih disarankan, terutama untuk performa yang lebih baik dan fleksibilitas dalam menggambar elemen grafis seperti highlight jalur bidak atau efek visual lainnya. Anda bisa membuat papan sepenuhnya di dalam Canvas tanpa perlu membuat banyak objek GUI.

Pertama Kita pakai yang mudah saja yaitu memakai label.bind, berikut adalah modifikasi kode dengan pendekatan tersebut. Ini akan menghindari penggunaan canvas.bind yang mungkin membuat implementasi lebih rumit. Berikut adalah versi kode yang diperbaiki:

Perbaikan main_with_gui.py

import tkinter as tk
from game import Game
from PIL import Image, ImageTk

class ChessGUI:
    def __init__(self, root, game, image_path):
        self.root = root
        self.game = game
        self.image_path = image_path
        self.board_frame = tk.Frame(root)
        self.board_frame.pack()
        self.tiles = {}
        self.images = {}
        self.selected_piece = None
        self.create_board()
        self.update_board()

    def create_board(self):
        # Create an 8x8 grid for the chessboard
        for row in range(8):
            for col in range(8):
                color = "white" if (row + col) % 2 == 0 else "gray"
                frame = tk.Frame(self.board_frame, width=80, height=80, bg=color)
                frame.grid(row=row, column=col)
                frame.pack_propagate(False)
                label = tk.Label(frame, bg=color)
                label.pack(fill=tk.BOTH, expand=True)
                label.bind("<Button-1>", lambda e, r=row, c=col: self.on_click(r, c))
                self.tiles[(row, col)] = label

    def load_images(self):
        # Load images for each piece type
        piece_names = ["pawn", "rook", "knight", "bishop", "queen", "king"]
        for color in ["white", "black"]:
            for piece in piece_names:
                img_path = f"{self.image_path}/{color}_{piece}.png"
                image = Image.open(img_path).resize((80, 80))
                self.images[f"{color}_{piece}"] = ImageTk.PhotoImage(image)

    def update_board(self):
        self.load_images()
        for row in range(8):
            for col in range(8):
                piece = self.game.board.get_piece_at(row, col)
                if piece:
                    piece_name = piece.__class__.__name__.lower()
                    color = piece.color
                    image = self.images[f"{color}_{piece_name}"]
                    self.tiles[(row, col)].config(image=image)
                    self.tiles[(row, col)].image = image
                else:
                    self.tiles[(row, col)].config(image=None)

    def highlight_selected_piece(self, row, col):
        # Sorot petak yang dipilih dengan mengubah warna latar belakang
        self.tiles[(row, col)].config(bg="yellow")

    def reset_highlight(self):
        # Reset warna latar belakang petak
        for (row, col), label in self.tiles.items():
            color = "white" if (row + col) % 2 == 0 else "gray"
            label.config(bg=color)

    def on_click(self, row, col):
        if self.selected_piece is None:  # Jika belum ada bidak yang dipilih
            piece = self.game.board.get_piece_at(row, col)
            if piece and piece.color == self.game.current_turn:  # Hanya bidak sesuai giliran pemain
                self.selected_piece = (row, col)
                self.highlight_selected_piece(row, col)  # Highlight petak yang dipilih
        else:
            from_row, from_col = self.selected_piece
            if self.game.move_piece(from_row, from_col, row, col):  # Coba pindahkan bidak
                self.update_board()  # Update tampilan papan
            self.selected_piece = None  # Reset seleksi bidak
            self.reset_highlight()  # Reset highlight

def main():
    root = tk.Tk()
    root.title("Catur Berbasis OOP")
    image_path = "/storage/emulated/0/AppJadi/Catur/Catur Berbasis OOP/images"
    game = Game()
    gui = ChessGUI(root, game, image_path)
    root.mainloop()

if __name__ == "__main__":
    main()

Penjelasan Perbaikan

  1. Penggunaan label.bind:

    • Setiap petak papan catur (label) sekarang menangani klik melalui label.bind. Tidak ada lagi referensi ke canvas, sehingga lebih sederhana.
  2. Sorotan Petak yang Dipilih:

    • Saat sebuah petak dipilih, warnanya diubah menjadi kuning melalui metode highlight_selected_piece.
    • Setelah bidak dipindahkan atau klik kedua dilakukan, sorotan direset ke warna awal dengan reset_highlight.
  3. Pemeriksaan Giliran Pemain:

    • Bidak hanya bisa dipilih jika sesuai dengan giliran pemain (self.game.current_turn).
  4. Kesederhanaan dan Fleksibilitas:

    • Dengan label.bind, setiap petak memiliki event handler sendiri, membuat kode lebih modular.

Sedangkan perbaikan pada file board.py adalah berikut ini, 

Berikut adalah modifikasi yang disesuaikan pada file board.py, yang bekerja dengan pendekatan label.bind pada file main_with_gui.py.

Modifikasi ini memastikan bahwa setiap fungsi yang dibutuhkan, seperti get_piece_at, berjalan dengan benar.

Perbaikan board.py

class Board:
    def __init__(self):
        self.board = self.create_initial_board()

    def create_initial_board(self):
        # Buat papan 8x8 dengan bidak dalam posisi awal
        board = [[None for _ in range(8)] for _ in range(8)]

        # Barisan pion
        for col in range(8):
            board[1][col] = Pawn("black")
            board[6][col] = Pawn("white")

        # Barisan bidak utama
        main_pieces = [Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook]
        for col, piece_type in enumerate(main_pieces):
            board[0][col] = piece_type("black")
            board[7][col] = piece_type("white")

        return board

    def get_piece_at(self, row, col):
        # Ambil bidak pada posisi tertentu
        if 0 <= row < 8 and 0 <= col < 8:
            return self.board[row][col]
        return None

    def move_piece(self, from_row, from_col, to_row, to_col):
        # Pindahkan bidak dari satu posisi ke posisi lain
        piece = self.get_piece_at(from_row, from_col)
        if piece and piece.is_valid_move(from_row, from_col, to_row, to_col, self):
            self.board[to_row][to_col] = piece
            self.board[from_row][from_col] = None
            return True
        return False

    def is_valid_position(self, row, col):
        # Periksa apakah posisi valid di papan
        return 0 <= row < 8 and 0 <= col < 8

    def is_empty_at(self, row, col):
        # Periksa apakah posisi kosong
        return self.is_valid_position(row, col) and self.board[row][col] is None

Penjelasan Modifikasi

  1. Fungsi get_piece_at:

    • Digunakan untuk mendapatkan bidak di posisi tertentu (baris dan kolom) pada papan.
    • Memastikan indeks tidak keluar dari batas array (0-7).
  2. Fungsi move_piece:

    • Memindahkan bidak dari satu posisi ke posisi lain dengan memeriksa validitas langkah menggunakan metode is_valid_move dari bidak.
    • Jika langkah valid, bidak dipindahkan, dan posisi awal diatur menjadi None.
  3. Validasi Posisi:

    • Fungsi is_valid_position memastikan bahwa koordinat berada dalam batas papan (0-7).
    • Fungsi is_empty_at memeriksa apakah posisi tertentu kosong.
  4. Inisialisasi Papan:

    • Fungsi create_initial_board mengisi papan dengan bidak-bidak dalam posisi awal, termasuk pion dan bidak utama.

Perlu Diperhatikan

Untuk mendukung logika validasi langkah (is_valid_move), pastikan semua kelas bidak (seperti Pawn, Rook, dll.) memiliki metode ini yang mendefinisikan langkah valid mereka. Jika Anda memerlukan detail implementasi dari kelas bidak, saya bisa membantu menuliskannya.

Sedangkan perbaikan pada file piece.py dan game.py adalah nerikit ini:


Perbaikan pada file piece.py

Tambahkan argumen color pada kelas Piece dan pastikan semua subclass-nya, seperti Pawn, Rook, dll., menggunakannya. Berikut adalah contoh implementasinya:

class Piece:
    def __init__(self, color):
        self.color = color  # Warna bidak, bisa "black" atau "white"

    def is_valid_move(self, from_row, from_col, to_row, to_col, board):
        # Logika langkah valid (akan diimplementasikan pada subclass)
        raise NotImplementedError("Method is_valid_move harus diimplementasikan oleh subclass")


class Pawn(Piece):
    def is_valid_move(self, from_row, from_col, to_row, to_col, board):
        # Logika langkah untuk pion
        direction = 1 if self.color == "white" else -1
        if from_col == to_col:  # Langkah maju
            if (to_row - from_row) == direction and board.is_empty_at(to_row, to_col):
                return True
        return False


class Rook(Piece):
    def is_valid_move(self, from_row, from_col, to_row, to_col, board):
        # Logika langkah untuk benteng
        if from_row == to_row or from_col == to_col:
            # Periksa apakah jalur kosong
            return True
        return False


class Knight(Piece):
    def is_valid_move(self, from_row, from_col, to_row, to_col, board):
        # Logika langkah untuk kuda
        if abs(from_row - to_row) == 2 and abs(from_col - to_col) == 1:
            return True
        if abs(from_row - to_row) == 1 and abs(from_col - to_col) == 2:
            return True
        return False


class Bishop(Piece):
    def is_valid_move(self, from_row, from_col, to_row, to_col, board):
        # Logika langkah untuk gajah
        if abs(from_row - to_row) == abs(from_col - to_col):
            return True
        return False


class Queen(Piece):
    def is_valid_move(self, from_row, from_col, to_row, to_col, board):
        # Logika langkah untuk ratu
        if from_row == to_row or from_col == to_col or abs(from_row - to_row) == abs(from_col - to_col):
            return True
        return False


class King(Piece):
    def is_valid_move(self, from_row, from_col, to_row, to_col, board):
        # Logika langkah untuk raja
        if max(abs(from_row - to_row), abs(from_col - to_col)) == 1:
            return True
        return False

Perbaikan pada file game.py

Tambahkan logika untuk mengatur papan permainan (Board) dan menghubungkan bidak dengan GUI. Berikut adalah contoh implementasinya:

from board import Board

class Game:
    def __init__(self):
        self.board = Board()  # Inisialisasi papan
        self.current_turn = "white"  # Giliran awal selalu putih

    def switch_turn(self):
        # Ganti giliran pemain
        self.current_turn = "black" if self.current_turn == "white" else "white"

    def is_valid_turn(self, piece):
        # Periksa apakah giliran sesuai dengan warna bidak
        return piece.color == self.current_turn

    def move(self, from_row, from_col, to_row, to_col):
        piece = self.board.get_piece_at(from_row, from_col)
        if piece and self.is_valid_turn(piece):
            if self.board.move_piece(from_row, from_col, to_row, to_col):
                self.switch_turn()
                return True
        return False

Penjelasan Solusi

  1. File piece.py:

    • Konstruktor Piece menerima color sebagai argumen untuk menentukan warna bidak.
    • Setiap subclass (seperti Pawn, Rook, dll.) mengimplementasikan logika langkahnya sendiri pada metode is_valid_move.
  2. File game.py:

    • Kelas Game mengelola giliran pemain dan pergerakan bidak.
    • Metode is_valid_turn memeriksa apakah bidak yang dipilih sesuai dengan giliran pemain.
    • Metode move mengatur validasi langkah, menggerakkan bidak, dan mengganti giliran.

Setelah melakukan perubahan ini, jalankan kembali program, dan pastikan semua bagian berjalan dengan baik. 

Ketika program dijalankan dari file main_with_gui.py, maka papan catur sudah tampil, buah-buah catur putih dan hitam sudah di petaknya masing-masing dan warna petak berubah warna kuning ketika bidak hendak dijalankan ke petak lain, namun buah catur satupun tidak dapat digerakkan. Dan muncul error:  Exception in Tkinter callback Traceback (most recent call last): File "/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/lib/python3.11/tkinter/__init__.py", line 1948, in __call__ return self.func(*args) ^^^^^^^^^^^^^^^^ File "/storage/emulated/0/AppJadi/Catur/Catur Berbasis OOP/main_with_gui.py", line 28, in label.pack(fill=tk.BOTH, expand=True) File "/storage/emulated/0/AppJadi/Catur/Catur Berbasis OOP/main_with_gui.py", line 72, in on_click piece = self.game.board.get_piece_at(row, col) ^^^^^^^^^^^^^^^^^^^^ AttributeError: 'Game' object has no attribute 'move_piece' 

Error tersebut muncul karena metode move_piece tidak ditemukan pada kelas Game. Masalah ini dapat disebabkan oleh ketidaksesuaian antara implementasi kelas Game dan pengaturan papan pada kelas Board. Kita akan memperbaiki dan menambahkan metode yang diperlukan untuk memastikan bidak catur dapat digerakkan.


Solusi Perbaikan

Berikut adalah langkah-langkah perbaikan pada file board.py, game.py, dan main_with_gui.py.


Perbaikan pada file board.py

Tambahkan metode move_piece pada kelas Board. Metode ini bertugas memindahkan bidak dari satu petak ke petak lain, serta memeriksa validitas pergerakan.

class Board:
    def __init__(self):
        self.board = self.create_initial_board()

    def create_initial_board(self):
        board = [[None for _ in range(8)] for _ in range(8)]
        # Inisialisasi bidak putih
        for col in range(8):
            board[1][col] = Pawn("white")
        board[0][0] = board[0][7] = Rook("white")
        board[0][1] = board[0][6] = Knight("white")
        board[0][2] = board[0][5] = Bishop("white")
        board[0][3] = Queen("white")
        board[0][4] = King("white")
        # Inisialisasi bidak hitam
        for col in range(8):
            board[6][col] = Pawn("black")
        board[7][0] = board[7][7] = Rook("black")
        board[7][1] = board[7][6] = Knight("black")
        board[7][2] = board[7][5] = Bishop("black")
        board[7][3] = Queen("black")
        board[7][4] = King("black")
        return board

    def get_piece_at(self, row, col):
        return self.board[row][col]

    def move_piece(self, from_row, from_col, to_row, to_col):
        piece = self.get_piece_at(from_row, from_col)
        if piece and piece.is_valid_move(from_row, from_col, to_row, to_col, self):
            self.board[to_row][to_col] = piece
            self.board[from_row][from_col] = None
            return True
        return False

    def is_empty_at(self, row, col):
        return self.get_piece_at(row, col) is None

Perbaikan pada file game.py

Tambahkan metode move_piece ke kelas Game agar dapat memindahkan bidak di papan dan menangani giliran pemain.

from board import Board

class Game:
    def __init__(self):
        self.board = Board()
        self.current_turn = "white"

    def switch_turn(self):
        self.current_turn = "black" if self.current_turn == "white" else "white"

    def is_valid_turn(self, piece):
        return piece and piece.color == self.current_turn

    def move_piece(self, from_row, from_col, to_row, to_col):
        piece = self.board.get_piece_at(from_row, from_col)
        if piece and self.is_valid_turn(piece):
            if self.board.move_piece(from_row, from_col, to_row, to_col):
                self.switch_turn()
                return True
        return False

Perbaikan pada file main_with_gui.py

Pastikan logika di dalam metode on_click menangani pemilihan dan pemindahan bidak dengan benar.

import tkinter as tk
from game import Game

class ChessGUI:
    def __init__(self, root):
        self.root = root
        self.game = Game()
        self.selected_piece = None
        self.selected_pos = None
        self.labels = [[None for _ in range(8)] for _ in range(8)]
        self.create_board()

    def create_board(self):
        for row in range(8):
            for col in range(8):
                color = "white" if (row + col) % 2 == 0 else "gray"
                label = tk.Label(self.root, bg=color, width=8, height=4)
                label.grid(row=row, column=col)
                label.bind("<Button-1>", lambda e, r=row, c=col: self.on_click(r, c))
                self.labels[row][col] = label
        self.update_board()

    def update_board(self):
        for row in range(8):
            for col in range(8):
                piece = self.game.board.get_piece_at(row, col)
                if piece:
                    text = piece.__class__.__name__[0]  # Nama bidak (huruf pertama)
                    text += "B" if piece.color == "black" else "W"  # Warna bidak
                else:
                    text = ""
                self.labels[row][col].config(text=text)

    def on_click(self, row, col):
        if self.selected_piece is None:  # Pemilihan bidak
            piece = self.game.board.get_piece_at(row, col)
            if piece and piece.color == self.game.current_turn:
                self.selected_piece = piece
                self.selected_pos = (row, col)
                self.labels[row][col].config(bg="yellow")  # Warna petak terpilih
        else:  # Pemindahan bidak
            from_row, from_col = self.selected_pos
            if self.game.move_piece(from_row, from_col, row, col):
                self.update_board()
            self.labels[from_row][from_col].config(bg="white" if (from_row + from_col) % 2 == 0 else "gray")
            self.selected_piece = None
            self.selected_pos = None

def main():
    root = tk.Tk()
    root.title("Catur Berbasis OOP")
    ChessGUI(root)
    root.mainloop()

if __name__ == "__main__":
    main()

Penjelasan Solusi

  1. File board.py:

    • Menambahkan metode move_piece untuk memindahkan bidak jika langkah valid.
    • Menambahkan metode is_empty_at untuk memeriksa apakah suatu petak kosong.
  2. File game.py:

    • Metode move_piece menangani validasi giliran pemain dan memindahkan bidak.
  3. File main_with_gui.py:

    • Pemilihan dan pemindahan bidak diatur dengan logika dalam on_click.
    • Petak yang dipilih diberi warna kuning.

Setelah perubahan ini, bidak catur seharusnya bisa digerakkan sesuai aturan dasar. 

Comments

Popular posts from this blog

MOVE (LANGKAH) CATUR TERBAIK SAYA DI TURNAMEN KEJUARAAN CATUR HARIAN CHESS.COM 2025

π”»π•š π•‹π”Έβ„π•Œβ„• π•Šπ•€π•Šπ”Έ 9, β„‚β„π”Όπ•Šπ•Š.ℂ𝕆𝕄 𝕄𝔼ℕ𝔾𝔸𝔻𝔸𝕂𝔸ℕℕ π•‹π•Œβ„β„•π”Έπ•„π”Όβ„• π•‚π”Όπ•π•Œπ”Έβ„π”Έπ”Έβ„• β„‚π”Έπ•‹π•Œβ„ ℍ𝔸ℝ𝕀𝔸ℕ β„‚β„π”Όπ•Šπ•Š.ℂ𝕆𝕄 2025

Kecerdasan Manusia Dan Kecerdasan Buatan/Ai (Artificial Intelligence)