Satranç oyunu için Nesneye Dayalı Tasarım [kapalı]


88

Nesneye Dayalı bir şekilde nasıl tasarlayıp düşüneceğime dair bir fikir edinmeye çalışıyorum ve bu konu hakkında topluluktan biraz geri bildirim almak istiyorum. Aşağıdaki, OO tarzında tasarlamak istediğim bir satranç oyunu örneğidir. Bu çok geniş bir tasarım ve bu aşamadaki odak noktam sadece kimin hangi mesajlardan sorumlu olduğunu ve nesnelerin oyunu simüle etmek için birbirleriyle nasıl etkileşime girdiğini belirlemektir. Lütfen kötü tasarım unsurları (yüksek bağlantı, kötü uyum vb.) Olup olmadığını ve bunların nasıl iyileştirilebileceğini belirtin.

Satranç oyunu aşağıdaki sınıflara sahiptir

  • Yazı tahtası
  • oyuncu
  • Parça
  • Meydan
  • Satranç oyunu

Pano, karelerden oluşur ve bu nedenle, Square nesnelerini oluşturmaktan ve yönetmekle sorumlu hale getirilebilir. Her bir parça aynı zamanda bir karenin üzerindedir, bu nedenle her bir parça üzerinde bulunduğu kareye referans verir. (Bu mantıklı mı?). Her parça daha sonra kendisini bir kareden diğerine taşımaktan sorumludur. Oyuncu sınıfı, sahip olduğu tüm parçaların referanslarını tutar ve bunların oluşturulmasından da sorumludur (Oyuncu Taşlar oluşturmalı mı?). Oyuncu takeTurn yöntemine sahiptir, bu da parçanın sınıfına ait olan ve parçanın konumunu mevcut konumundan başka bir konuma değiştiren bir movePiece yöntemini çağırır. Şimdi, Board sınıfının tam olarak neden sorumlu olması gerektiği konusunda kafam karıştı. Oyunun mevcut durumunu belirlemenin ve oyunun ne zaman bittiğini bilmenin gerekli olduğunu varsaydım. Ama bir parça onu değiştirdiğinde yeri yönetim kurulu nasıl güncellenmelidir? hangi parçaların var olduğu ve parçalar hareket ettikçe güncellemeler alan ayrı bir kare dizisi tutmalı mı?

Ayrıca, ChessGame başlangıçta sırasıyla kareler ve parçalar oluşturan ve simülasyonu başlatan Tahta ve oyuncu nesnelerini oluşturur. Kısaca, ChessGame'deki kod böyle görünebilir

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();

}

Yönetim kurulunun durumunun nasıl güncelleneceği konusunda emin değilim. Parçanın tahtaya bir referansı olmalı mı? Sorumluluk nerede olmalı? Kim hangi referansları tutuyor? Lütfen girdilerinizde bana yardımcı olun ve bu tasarımdaki sorunları belirtin. Sadece tasarım yönüyle ilgilendiğim için kasıtlı olarak herhangi bir algoritmaya veya oyunun daha fazla detayına odaklanmıyorum. Umarım bu topluluk değerli bilgiler sağlayabilir.


3
Nitpicky yorum: p1'in takeTurn()hamlesi oyunu bitirirse p2 çağırmamalıdır . Az nitpicky comment: Ben daha doğal oyuncu aramaya bulmak whiteve black.
Kristopher Johnson

Kabul. Ama dediğim gibi, tasarım yönleriyle ve hangi nesnelerin hangi eylemlerden sorumlu olması gerektiği ve kimin hangi referansları tutması gerektiği ile daha çok ilgileniyorum.
Sid

Snippet'inizde yukarıda özetlediğiniz gibi yaptım. Benim uygulamamda, her parça kendi canMove()işlevinde kullanacağı için tam konumun iç kopyasına sahip . Ve taşıma tamamlandığında, diğer tüm parçalar panonun kendi iç kopyasını günceller. Bunun optimal olmadığını biliyorum ama o sırada C ++ öğrenmek ilginçti. Daha sonra, satranç oyuncusu olmayan bir arkadaşım bana classesher taş yerine her kare için sahip olacağını söyledi . Ve bu yorum bana çok ilginç geldi.
eigenfield

Yanıtlar:


54

Aslında sadece ben (ben almak istiyorum olmadığı gerçek uygulama kaldırıldı modelledik nasıl İşte kabaca var vs. bir satranç tahtası, parçalara, kurallar, tam bir C # uygulaması yazdım tüm senin kodlama eğlenceli dışarı):

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 {
    // ....
}

Temel fikir, Oyun / Tahta / vb'nin oyunun durumunu basitçe kaydetmesidir. Onları, örneğin bir pozisyon oluşturmak için manipüle edebilirsiniz, eğer istediğiniz buysa. IGameRules arabirimimi uygulayan ve şunlardan sorumlu bir sınıfım var:

  • Rok atma ve geçerken dahil olmak üzere hangi hareketlerin geçerli olduğunun belirlenmesi.
  • Belirli bir hareketin geçerli olup olmadığını belirleme.
  • Oyuncuların ne zaman şah / şah mat / çıkmazda olduğunu belirleme.
  • Yürütme hamleleri.

Kuralları oyun / tahta sınıflarından ayırmak, varyantları nispeten kolay bir şekilde uygulayabileceğiniz anlamına da gelir. Kural arayüzünün tüm yöntemleri, Gamehangi hareketlerin geçerli olduğunu belirlemek için inceleyebilecekleri bir nesneyi alır .

Oyuncu bilgilerini kaydetmediğimi unutmayın Game. TableKimin oynadığı, oyunun ne zaman oynandığı vb. Gibi oyun meta verilerini depolamaktan sorumlu ayrı bir sınıfım var.

DÜZENLEME: Bu cevabın amacının gerçekten size doldurabileceğiniz şablon kodu vermek olmadığını unutmayın - benim kodum aslında her öğede depolanan biraz daha fazla bilgi, daha fazla yöntem, vb. ulaşmaya çalıştığınız hedef.


1
Ayrıntılı cevap için teşekkür ederim. Ancak tasarımla ilgili birkaç sorum var. Örneğin, Move'un neden bir sınıf olması gerektiği hemen belli değil. Tek odak noktam sorumlulukları atamak ve sınıflar arasındaki etkileşimlere mümkün olan en temiz şekilde karar vermektir. Herhangi bir tasarım kararının arkasındaki "neden" i bilmek istiyorum. Yaptığınız tasarım kararlarına nasıl vardığınız ve neden iyi seçimler oldukları konusunda net değilim.
Sid

Movetüm hareket geçmişini, hangi parçanın ele geçirildiği, bir piyonun neye yükseltilmiş olabileceği gibi notasyon ve yardımcı bilgilerle birlikte bir hareket listesinde saklayabileceğiniz bir sınıftır
cdhowie

@cdhowie GameDelgating bir uygulayıcıya IGameRulesmı yoksa nesnenin dışındaki kuralları uyguluyorsunuz? İkincisi uygunsuz görünüyor çünkü oyun kendi durumunu koruyamıyor değil mi?
plalx

1
Bu aptalca olabilir, ancak Turn in the Game sınıfının PieceType yerine PieceColor türünde olması gerekmez mi?
Dennis van Gils

1
@nikhil Her iki oyuncunun da hangi yöne rok atabileceğini belirtirler (A ve H dosyalarına doğru). Bu değerler doğru olarak başlar. Beyazın A kalesi hareket ederse, CanWhiteCastleA yanlış yapılır ve aynı şekilde H kalesi için de geçerlidir. Beyazın şahı hareket ederse, ikisi de yanlış yapılır. Siyah için de aynı süreç.
cdhowie

6

Oldukça basit bir satranç oyunu için fikrim şu:

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

Yakın zamanda PHP'de bir satranç programı oluşturdum ( web sitesi buraya tıklayın , kaynak buraya tıklayın ) ve bunu nesne odaklı yaptım. İşte kullandığım sınıflar.

  • ChessRulebook (statik) - Tüm kodlarımı generate_legal_moves()buraya koyuyorum . Bu yönteme, sırası olan bir tahta ve çıktının ayrıntı düzeyini ayarlamak için bazı değişkenler verilir ve bu konum için tüm yasal hamleleri üretir. Bir ChessMoves listesi döndürür.
  • ChessMove - Başlangıç ​​karesi, bitiş karesi, renk, parça türü, yakalama, kontrol, şah mat, promosyon parçası türü ve geçerken dahil olmak üzere cebirsel gösterim oluşturmak için gereken her şeyi saklar . İsteğe bağlı ek değişkenler arasında belirsizliği giderme (Rae4 gibi hareketler için), rok atma ve tahta bulunur.
  • ChessBoard - Kareleri temsil eden ve sırası gelen ChessPieces'i depolayan 8x8'lik bir dizi dahil olmak üzere, bir Satranç FEN'iyle aynı bilgileri depolar, geçerken hedef kare, rok atma hakları, yarım hareket saati ve tam hareket saati.
  • ChessPiece - Taş türünü, rengini, karesini ve taş değerini kaydeder (örneğin, piyon = 1, at = 3, kale = 5 vb.)
  • ChessSquare - Sıralamayı ve dosyayı ints olarak kaydeder .

Şu anda bu kodu bir satranç yapay zekasına dönüştürmeye çalışıyorum, bu yüzden HIZLI olması gerekiyor. generate_legal_moves()İşlevi 1500 ms'den 8 ms'ye optimize ettim ve hala üzerinde çalışıyorum. Bundan öğrendiğim dersler ...

  • Varsayılan olarak her ChessMove'da bir ChessBoard'un tamamını saklamayın. Tahtayı yalnızca gerektiğinde hareket halindeyken saklayın.
  • intMümkün olduğunda olduğu gibi ilkel türleri kullanın . Bu nedenle , "a4" gibi insan tarafından okunabilir satranç kare gösterimi ile bir alfanümerik depolamak yerine, ChessSquaresıralama ve dosya olarak depolar .intstring
  • Program, hareket ağacını ararken on binlerce ChessSquares oluşturur. Muhtemelen programı ChessSquares'i kullanmayacak şekilde yeniden düzenleyeceğim, bu da hız artışı sağlayacaktır.
  • Sınıflarınızda gereksiz değişkenleri hesaplamayın. Başlangıçta, Satranç Tahtalarımın her birinde FEN'i hesaplamak programın hızını gerçekten öldürüyordu. Bunu bir profilciyle bulmalıydım .

Bunun eski olduğunu biliyorum, ama umarım birine yardımcı olur. İyi şanslar!

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.