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:
piece.py
: Mengelola logika terkait bidak catur.board.py
: Mengelola logika papan dan validasi gerakan.game.py
: Mengelola logika permainan, giliran pemain, dan status permainan (skak, skakmat, atau pat).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
-
Simpan semua file kode (
piece.py
,board.py
,game.py
,main_with_gui.py
) di dalam satu folder kerja. -
Pastikan folder
images
dengan semua gambar bidak catur sudah lengkap sesuai struktur. -
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
.
- Instal Pillow dengan perintah:
- Jika direktori
images
berada di lokasi lain, ubah variabelimage_path
dalam kodemain_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, bukanWhite_Rook.png
atauwhite_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:
Jalankan program dan periksa apakah semua file gambar terdaftar.import os print(f"Image path: {image_path}") for file in os.listdir(image_path): print(file)
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:
Lalu ubah/data/user/0/ru.iiec.pydroid3/files/Catur/images
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:
-
Tempatkan di dalam loop
for
di metodeupdate_board
Di dalam
ChessGUI
pada filemain_with_gui.py
, temukan metodeupdate_board
. Tambahkan kodelabel.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))
-
Penyesuaian jika tidak ada
label
Jika Anda tidak menggunakan
label
tetapi hanya menggunakancanvas
, Anda perlu memodifikasi metodeon_click
untuk menangani klik pada petak papan catur langsung daricanvas
.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 dicanvas
.
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 banyakLabel
, 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
danevent.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
-
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.
-
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
-
Penggunaan
label.bind
:- Setiap petak papan catur (label) sekarang menangani klik melalui
label.bind
. Tidak ada lagi referensi kecanvas
, sehingga lebih sederhana.
- Setiap petak papan catur (label) sekarang menangani klik melalui
-
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
.
- Saat sebuah petak dipilih, warnanya diubah menjadi kuning melalui metode
-
Pemeriksaan Giliran Pemain:
- Bidak hanya bisa dipilih jika sesuai dengan giliran pemain (
self.game.current_turn
).
- Bidak hanya bisa dipilih jika sesuai dengan giliran pemain (
-
Kesederhanaan dan Fleksibilitas:
- Dengan
label.bind
, setiap petak memiliki event handler sendiri, membuat kode lebih modular.
- Dengan
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
-
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).
-
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
.
- Memindahkan bidak dari satu posisi ke posisi lain dengan memeriksa validitas langkah menggunakan metode
-
Validasi Posisi:
- Fungsi
is_valid_position
memastikan bahwa koordinat berada dalam batas papan (0-7). - Fungsi
is_empty_at
memeriksa apakah posisi tertentu kosong.
- Fungsi
-
Inisialisasi Papan:
- Fungsi
create_initial_board
mengisi papan dengan bidak-bidak dalam posisi awal, termasuk pion dan bidak utama.
- Fungsi
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
-
File
piece.py
:- Konstruktor
Piece
menerimacolor
sebagai argumen untuk menentukan warna bidak. - Setiap subclass (seperti
Pawn
,Rook
, dll.) mengimplementasikan logika langkahnya sendiri pada metodeis_valid_move
.
- Konstruktor
-
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.
- Kelas
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
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
-
File
board.py
:- Menambahkan metode
move_piece
untuk memindahkan bidak jika langkah valid. - Menambahkan metode
is_empty_at
untuk memeriksa apakah suatu petak kosong.
- Menambahkan metode
-
File
game.py
:- Metode
move_piece
menangani validasi giliran pemain dan memindahkan bidak.
- Metode
-
File
main_with_gui.py
:- Pemilihan dan pemindahan bidak diatur dengan logika dalam
on_click
. - Petak yang dipilih diberi warna kuning.
- Pemilihan dan pemindahan bidak diatur dengan logika dalam
Setelah perubahan ini, bidak catur seharusnya bisa digerakkan sesuai aturan dasar.
Comments
Post a Comment