Desain Berorientasi Objek untuk permainan Catur [tertutup]


88

Saya mencoba untuk merasakan bagaimana merancang dan berpikir dengan cara Berorientasi Objek dan ingin mendapatkan umpan balik dari komunitas tentang topik ini. Berikut contoh game catur yang ingin saya desain secara OO. Ini adalah desain yang sangat luas dan fokus saya pada tahap ini hanya untuk mengidentifikasi siapa yang bertanggung jawab atas pesan apa dan bagaimana objek berinteraksi satu sama lain untuk mensimulasikan permainan. Harap tunjukkan jika ada elemen dengan desain yang buruk (kopling tinggi, kohesi buruk, dll.) Dan cara memperbaikinya.

Permainan Catur memiliki kelas-kelas berikut

  • Naik
  • Pemain
  • Bagian
  • Kotak
  • Permainan catur

Papan terdiri dari bujur sangkar sehingga Papan dapat diberi tanggung jawab untuk membuat dan mengelola objek Persegi. Tiap bidak juga ada di bujur sangkar sehingga setiap bidak juga memiliki referensi ke bujur sangkar tempatnya berada. (Apakah ini masuk akal?). Masing-masing bagian kemudian bertanggung jawab untuk berpindah dari satu kotak ke kotak lainnya. Kelas pemain memegang referensi ke semua bidak yang dimilikinya dan juga bertanggung jawab atas pembuatannya (Haruskah pemain membuat Bidak?). Pemain memiliki metode takeTurn yang pada gilirannya memanggil metode movePiece yang termasuk dalam Kelas bidak yang mengubah lokasi bidak dari lokasi saat ini ke lokasi lain. Sekarang saya bingung tentang apa sebenarnya kelas Dewan harus bertanggung jawab. Saya berasumsi bahwa hal itu diperlukan untuk menentukan status game saat ini dan mengetahui kapan game tersebut selesai. Tapi ketika sepotong mengubahnya ' Lokasi bagaimana seharusnya papan diperbarui? haruskah itu mempertahankan array terpisah dari kotak di mana potongan ada dan yang mendapat pembaruan saat potongan bergerak?

Selain itu, ChessGame pada awalnya membuat objek Papan dan pemain yang pada gilirannya membuat kotak dan bidak masing-masing dan memulai simulasi. Singkatnya, mungkin seperti inilah tampilan kode di ChessGame

Player p1 =new Player();
Player p2 = new Player();

Board b = new Board();

while(b.isGameOver())
{
  p1.takeTurn(); // calls movePiece on the Piece object
  p2.takeTurn();

}

Saya tidak jelas tentang bagaimana status papan akan diperbarui. Haruskah sepotong memiliki referensi ke papan? Dimana seharusnya tanggung jawab itu berada? Siapa yang memegang referensi apa? Tolong bantu saya dengan masukan Anda dan tunjukkan masalah dalam desain ini. Saya sengaja tidak fokus pada algoritme apa pun atau detail lebih lanjut dari permainan game karena saya hanya tertarik pada aspek desain. Semoga komunitas ini bisa memberikan wawasan yang berharga.


3
Komentar nitpicky: p2 seharusnya tidak dipanggil takeTurn()jika langkah p1 mengakhiri permainan. Kurang komentar rewel: Saya merasa lebih alami untuk memanggil para pemain whitedan black.
Kristopher Johnson

Sepakat. Tapi seperti yang saya katakan, saya lebih tertarik pada aspek desain dan Objek apa yang harus bertanggung jawab atas tindakan apa dan siapa yang memegang referensi apa.
Sid

Saya suka seperti yang Anda uraikan di atas dalam cuplikan Anda. Dalam implementasinya, setiap bagian memiliki salinan dalam dari posisi lengkap karena akan digunakan dalam fungsinya sendiri canMove(). Dan ketika pemindahan selesai, semua bagian lainnya memperbarui salinan papan dalamnya sendiri. Saya tahu ini tidak optimal, tapi menarik saat itu belajar C ++. Belakangan, seorang teman non-pemain catur memberi tahu saya bahwa dia akan memiliki classesuntuk setiap kotak, bukan setiap bidak. Dan komentar itu menurut saya sangat menarik.
eigenfield

Jawaban:


54

Saya sebenarnya baru saja menulis implementasi C # lengkap dari papan catur, bidak, aturan, dll. Berikut ini kira-kira bagaimana saya memodelkannya (implementasi sebenarnya dihapus karena saya tidak ingin menghilangkan semua kesenangan dari pengkodean Anda):

public enum PieceType {
    None, Pawn, Knight, Bishop, Rook, Queen, King
}

public enum PieceColor {
    White, Black
}

public struct Piece {
    public PieceType Type { get; set; }
    public PieceColor Color { get; set; }
}

public struct Square {
    public int X { get; set; }
    public int Y { get; set; }

    public static implicit operator Square(string str) {
        // Parses strings like "a1" so you can write "a1" in code instead
        // of new Square(0, 0)
    }
}

public class Board {
    private Piece[,] board;

    public Piece this[Square square] { get; set; }

    public Board Clone() { ... }
}

public class Move {
    public Square From { get; }
    public Square To { get; }
    public Piece PieceMoved { get; }
    public Piece PieceCaptured { get; }
    public PieceType Promotion { get; }
    public string AlgebraicNotation { get; }
}

public class Game {
    public Board Board { get; }
    public IList<Move> Movelist { get; }
    public PieceType Turn { get; set; }
    public Square? DoublePawnPush { get; set; } // Used for tracking valid en passant captures
    public int Halfmoves { get; set; }

    public bool CanWhiteCastleA { get; set; }
    public bool CanWhiteCastleH { get; set; }
    public bool CanBlackCastleA { get; set; }
    public bool CanBlackCastleH { get; set; }
}

public interface IGameRules {
    // ....
}

Ide dasarnya adalah Game / Board / dll hanya menyimpan status game. Anda dapat memanipulasinya untuk misalnya mengatur posisi, jika itu yang Anda inginkan. Saya memiliki kelas yang mengimplementasikan antarmuka IGameRules saya yang bertanggung jawab untuk:

  • Menentukan jurus apa yang sah, termasuk castling dan en passant.
  • Menentukan apakah langkah tertentu valid.
  • Menentukan kapan pemain dalam check / skakmat / jalan buntu.
  • Melaksanakan gerakan.

Memisahkan aturan dari kelas game / papan juga berarti Anda dapat menerapkan varian dengan relatif mudah. Semua metode antarmuka aturan mengambil Gameobjek yang dapat diperiksa untuk menentukan gerakan mana yang valid.

Perhatikan bahwa saya tidak menyimpan informasi pemain di Game. Saya memiliki kelas terpisah Tableyang bertanggung jawab untuk menyimpan metadata permainan seperti siapa yang bermain, kapan permainan berlangsung, dll.

EDIT: Perhatikan bahwa tujuan jawaban ini tidak benar-benar memberi Anda kode template yang dapat Anda isi - kode saya sebenarnya memiliki lebih banyak informasi yang disimpan pada setiap item, lebih banyak metode, dll. Tujuannya adalah untuk memandu Anda menuju tujuan yang ingin Anda capai.


1
Terima kasih atas jawaban mendetailnya. Namun, saya punya beberapa pertanyaan tentang desainnya. Misalnya, tidak segera jelas mengapa Move harus menjadi sebuah kelas. Fokus saya satu-satunya adalah memberikan tanggung jawab dan memutuskan interaksi antara kelas dengan cara yang paling bersih. Saya ingin tahu "mengapa" di balik keputusan desain apa pun. Saya tidak jelas tentang bagaimana Anda sampai pada keputusan desain yang Anda lakukan dan mengapa itu adalah pilihan yang baik.
Sid

Moveadalah kelas sehingga Anda dapat menyimpan seluruh riwayat gerakan dalam daftar langkah, dengan notasi dan informasi tambahan seperti bidak apa yang ditangkap, bidak apa yang mungkin dipromosikan, dll.
cdhowie

@cdhowie Apakah Gamedelgasi ke pelaksana IGameRulesatau Anda menegakkan aturan di luar objek? Yang terakhir tampaknya tidak pantas karena game tidak dapat melindungi negaranya sendiri, bukan?
plalx

1
Ini mungkin bodoh, tapi bukankah seharusnya Turn in the Game class menjadi tipe PieceColor daripada PieceType?
Dennis van Gils

1
@nikhil Mereka menunjukkan arah mana kedua pemain masih dapat melakukan kastil (menuju file A dan H). Nilai-nilai ini dimulai dengan benar. Jika benteng A putih bergerak, CanWhiteCastleA dibuat salah, dan begitu juga untuk benteng H. Jika raja kulit putih bergerak, keduanya dibuat palsu. Dan proses yang sama untuk hitam.
cdhowie

6

Inilah ide saya, untuk permainan catur yang cukup mendasar:

class GameBoard {
 IPiece config[8][8];  

 init {
  createAndPlacePieces("Black");
  createAndPlacePieces("White");
  setTurn("Black");

 }

 createAndPlacePieces(color) {
   //generate pieces using a factory method
   //for e.g. config[1][0] = PieceFactory("Pawn",color);
 }

 setTurn(color) {
   turn = color;
 }

 move(fromPt,toPt) {
  if(getPcAt(fromPt).color == turn) {
    toPtHasOppositeColorPiece = getPcAt(toPt) != null && getPcAt(toPt).color != turn;
    possiblePath = getPcAt(fromPt).generatePossiblePath(fromPt,toPt,toPtHasOppositeColorPiece);
   if(possiblePath != NULL) {
      traversePath();
      changeTurn();
   }
  }
 } 

}

Interface IPiece {
  function generatePossiblePath(fromPt,toPt,toPtHasEnemy);
}

class PawnPiece implements IPiece{
  function generatePossiblePath(fromPt,toPt,toPtHasEnemy) {
    return an array of points if such a path is possible
    else return null;
  }
}

class ElephantPiece implements IPiece {....}

0

Saya baru-baru ini membuat program catur dalam PHP ( klik situs web di sini , klik sumber di sini ) dan saya membuatnya berorientasi objek. Ini kelas yang saya gunakan.

  • ChessRulebook (statis) - Saya memasukkan semua generate_legal_moves()kode saya di sini. Metode itu diberi papan, yang gilirannya, dan beberapa variabel untuk mengatur tingkat detail output, dan itu menghasilkan semua langkah hukum untuk posisi itu. Ini mengembalikan daftar ChessMoves.
  • ChessMove - Menyimpan semua yang diperlukan untuk membuat notasi aljabar , termasuk kotak awal, kotak akhir, warna, jenis bidak, tangkap, centang, skakmat, jenis bidak promosi, dan kata sandi. Variabel tambahan opsional termasuk disambiguasi (untuk gerakan seperti Rae4), rokade, dan papan.
  • ChessBoard - Menyimpan informasi yang sama seperti Catur FEN , termasuk array 8x8 yang mewakili kotak dan menyimpan ChessPieces, yang giliran itu, en passant target square, castling rights, halfmove clock, dan fullmove clock.
  • ChessPiece - Menyimpan jenis bidak, warna, bujur sangkar, dan nilai bidak (misalnya, pion = 1, ksatria = 3, benteng = 5, dll.)
  • ChessSquare - Menyimpan peringkat dan file, sebagai ints.

Saat ini saya mencoba mengubah kode ini menjadi AI catur, jadi harus CEPAT. Saya telah mengoptimalkan generate_legal_moves()fungsi dari 1500ms menjadi 8ms, dan saya masih mengerjakannya. Pelajaran yang saya pelajari dari itu adalah ...

  • Jangan menyimpan seluruh ChessBoard di setiap ChessMove secara default. Hanya simpan papan saat bergerak saat dibutuhkan.
  • Gunakan tipe primitif seperti intjika memungkinkan. Itulah mengapa ChessSquaremenyimpan peringkat dan file sebagai int, daripada juga menyimpan alfanumerik stringdengan notasi kotak catur yang dapat dibaca manusia seperti "a4".
  • Program ini membuat puluhan ribu ChessSquares saat mencari pohon bergerak. Saya mungkin akan merefaktor program untuk tidak menggunakan ChessSquares, yang seharusnya memberikan peningkatan kecepatan.
  • Jangan menghitung variabel yang tidak perlu di kelas Anda. Awalnya, menghitung FEN di setiap Papan Catur saya benar-benar mematikan kecepatan program. Saya harus mencari tahu ini dengan seorang profiler .

Saya tahu ini sudah tua, tapi semoga ini membantu seseorang. Semoga berhasil!

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.