Objektorientiertes Design für ein Schachspiel [geschlossen]


88

Ich versuche ein Gefühl dafür zu bekommen, wie man objektorientiert gestaltet und denkt, und möchte ein Feedback von der Community zu diesem Thema erhalten. Das Folgende ist ein Beispiel für ein Schachspiel, das ich auf OO-Weise entwerfen möchte. Dies ist ein sehr umfassendes Design, und ich konzentriere mich in dieser Phase nur darauf, herauszufinden, wer für welche Nachrichten verantwortlich ist und wie die Objekte miteinander interagieren, um das Spiel zu simulieren. Bitte weisen Sie darauf hin, ob es Elemente mit schlechtem Design gibt (hohe Kopplung, schlechte Kohäsion usw.) und wie Sie diese verbessern können.

Das Schachspiel hat die folgenden Klassen

  • Tafel
  • Spieler
  • Stück
  • Quadrat
  • Schachspiel

Das Board besteht aus Quadraten, sodass das Board für die Erstellung und Verwaltung von Square-Objekten verantwortlich gemacht werden kann. Jedes Stück befindet sich auch auf einem Quadrat, sodass jedes Stück auch einen Verweis auf das Quadrat hat, auf dem es sich befindet. (Macht das Sinn?). Jedes Stück ist dann dafür verantwortlich, sich von einem Feld zum anderen zu bewegen. Die Spielerklasse enthält Verweise auf alle Teile, die er besitzt, und ist auch für deren Erstellung verantwortlich (Sollte der Spieler Teile erstellen?). Der Spieler hat eine Methode takeTurn, die wiederum eine Methode movePiece aufruft, die zur Stückklasse gehört, die die Position der Figur von ihrer aktuellen Position an eine andere Position ändert. Jetzt bin ich verwirrt darüber, wofür genau die Board-Klasse verantwortlich sein muss. Ich nahm an, dass es notwendig war, um den aktuellen Status des Spiels zu bestimmen und zu wissen, wann das Spiel vorbei ist. Aber wenn ein Stück es ändert ' s Ort wie soll das Board aktualisiert werden? Sollte es eine separate Anordnung von Quadraten beibehalten, auf denen Teile vorhanden sind und die aktualisiert werden, wenn sich Teile bewegen?

Außerdem erstellt ChessGame zunächst die Brett- und Spielerobjekte, die wiederum Quadrate bzw. Steine ​​erstellen und die Simulation starten. Kurz gesagt, so könnte der Code in ChessGame aussehen

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

}

Ich bin mir nicht sicher, wie der Status des Boards aktualisiert wird. Sollte das Stück einen Verweis auf die Tafel haben? Wo soll die Verantwortung liegen? Wer hat welche Referenzen? Bitte helfen Sie mir bei Ihren Eingaben und weisen Sie auf Probleme bei diesem Entwurf hin. Ich konzentriere mich bewusst nicht auf Algorithmen oder weitere Details des Spiels, da ich mich nur für den Designaspekt interessiere. Ich hoffe, diese Community kann wertvolle Erkenntnisse liefern.


3
Nitpicky Kommentar: p2 sollte nicht callen, takeTurn()wenn der Zug von p1 das Spiel beendet. Weniger pingeliger Kommentar: Ich finde es natürlicher, die Spieler anzurufen whiteund black.
Kristopher Johnson

Einverstanden. Aber wie gesagt, ich interessiere mich mehr für die Designaspekte und welche Objekte für welche Aktionen verantwortlich sein sollten und wer welche Referenzen hat.
Sid

Es hat mir gefallen, wie Sie oben in Ihrem Ausschnitt beschrieben haben. In meiner Implementierung hat jedes Stück eine innere Kopie der vollständigen Position, da es diese in ihrer eigenen canMove()Funktion verwendet. Und wenn der Umzug abgeschlossen ist, aktualisieren alle anderen Teile ihre eigene innere Kopie des Bretts. Ich weiß, dass es nicht optimal ist, aber es war zu dieser Zeit interessant, C ++ zu lernen. Später sagte mir ein Freund eines Nicht-Schachspielers, dass er classesfür jedes Feld statt für jede Figur haben würde. Und dieser Kommentar kam mir sehr interessant vor.
Truthadjustr

Antworten:


54

Ich habe gerade eine vollständige C # -Implementierung eines Schachbretts, von Figuren, Regeln usw. geschrieben. Hier ist ungefähr, wie ich es modelliert habe (die tatsächliche Implementierung wurde entfernt, da ich nicht den ganzen Spaß aus Ihrer Codierung herausholen möchte ):

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

Die Grundidee ist, dass Game / Board / etc einfach den Status des Spiels speichert. Sie können sie manipulieren, um beispielsweise eine Position einzurichten, wenn Sie dies möchten. Ich habe eine Klasse, die meine IGameRules-Schnittstelle implementiert, die verantwortlich ist für:

  • Feststellen, welche Züge gültig sind, einschließlich Rochade und en passant.
  • Feststellen, ob ein bestimmter Zug gültig ist.
  • Feststellen, wann sich Spieler in Schach / Schachmatt / Patt befinden.
  • Bewegungen ausführen.

Wenn Sie die Regeln von den Spiel- / Brettklassen trennen, können Sie Varianten auch relativ einfach implementieren. Alle Methoden der Regelschnittstelle verwenden ein GameObjekt, das sie überprüfen können, um festzustellen, welche Bewegungen gültig sind.

Beachten Sie, dass ich keine Spielerinformationen auf speichere Game. Ich habe eine separate Klasse Table, die für das Speichern von Spielmetadaten zuständig ist, z. B. wer gespielt hat, wann das Spiel stattgefunden hat usw.

BEARBEITEN: Beachten Sie, dass der Zweck dieser Antwort nicht darin besteht, Ihnen einen Vorlagencode zu geben, den Sie ausfüllen können. In meinem Code sind tatsächlich ein bisschen mehr Informationen zu jedem Element, mehr Methoden usw. gespeichert. Der Zweck besteht darin, Sie zum Thema zu führen Ziel, das Sie erreichen wollen.


1
Vielen Dank für die ausführliche Antwort. Ich habe jedoch einige Fragen zum Design. Zum Beispiel ist nicht sofort klar, warum Move eine Klasse sein sollte. Mein einziger Fokus liegt darauf, Verantwortlichkeiten zuzuweisen und die Interaktionen zwischen den Klassen auf möglichst saubere Weise zu entscheiden. Ich möchte das "Warum" hinter jeder Designentscheidung wissen. Mir ist nicht klar, wie Sie zu den Designentscheidungen gekommen sind, die Sie getroffen haben, und warum sie eine gute Wahl sind.
Sid

Moveist eine Klasse, mit der Sie den gesamten
Zugverlauf in einer Zugliste

@cdhowie Ist das GameDelgieren an einen Implementierer von IGameRulesoder erzwingen Sie Regeln außerhalb des Objekts? Letzteres scheint unangemessen, da das Spiel seinen eigenen Zustand nicht schützen kann, nicht wahr?
Plalx

1
Das mag dumm sein, aber sollte Turn in the Game nicht vom Typ PieceColor anstelle von PieceType sein?
Dennis van Gils

1
@nikhil Sie geben an, in welche Richtung beide Spieler noch burgieren dürfen (in Richtung der A- und H-Dateien). Diese Werte beginnen wahr. Wenn sich der A-Turm von Weiß bewegt, wird CanWhiteCastleA falsch gemacht, ebenso für den H-Turm. Wenn sich der weiße König bewegt, werden beide falsch gemacht. Und das gleiche Verfahren für Schwarz.
CDhowie

6

Hier ist meine Idee für ein ziemlich einfaches Schachspiel:

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

Ich habe kürzlich ein Schachprogramm in PHP erstellt ( Website hier klicken , Quelle hier klicken ) und es objektorientiert gemacht. Hier sind die Klassen, die ich verwendet habe.

  • ChessRulebook (statisch) - Ich habe meinen gesamten generate_legal_moves()Code hier eingegeben . Diese Methode erhält eine Karte, die an der Reihe ist, und einige Variablen, um den Detaillierungsgrad der Ausgabe festzulegen, und generiert alle rechtlichen Schritte für diese Position. Es wird eine Liste von ChessMoves zurückgegeben.
  • ChessMove - Speichert alles, was zum Erstellen einer algebraischen Notation benötigt wird , einschließlich Startquadrat, Endquadrat , Farbe, Stücktyp , Erfassung, Scheck, Schachmatt, Werbestückentyp und en passant. Optionale zusätzliche Variablen sind Disambiguierung (für Bewegungen wie Rae4), Rochade und Brett.
  • ChessBoard - Speichert die gleichen Informationen wie ein Chess FEN , einschließlich eines 8x8-Arrays, das die Quadrate darstellt und die ChessPieces speichert, deren Zug es ist, en passant Zielquadrat, Burgenrechte, Halbbewegungsuhr und Vollbewegungsuhr.
  • ChessPiece - Speichert Stückart, Farbe, Quadrat und Stückwert (z. B. Bauer = 1, Ritter = 3, Turm = 5 usw.)
  • ChessSquare - Speichert die Basis als ints.

Ich versuche gerade, diesen Code in eine Schach-KI umzuwandeln, daher muss er SCHNELL sein. Ich habe die generate_legal_moves()Funktion von 1500 ms auf 8 ms optimiert und arbeite noch daran. Die Lehren, die ich daraus gezogen habe, sind ...

  • Speichern Sie standardmäßig nicht in jedem ChessMove ein ganzes ChessBoard. Bewahren Sie das Board nur bei Bedarf in Bewegung auf.
  • Verwenden Sie nach intMöglichkeit primitive Typen . Aus diesem Grund ChessSquarespeichert Rank and File als int, anstatt auch eine alphanumerische Zahl stringmit lesbarer Schachquadratnotation wie "a4" zu speichern .
  • Das Programm erstellt beim Durchsuchen des Verschiebungsbaums Zehntausende von ChessSquares. Ich werde das Programm wahrscheinlich umgestalten, um ChessSquares nicht zu verwenden, was einen Geschwindigkeitsschub geben sollte.
  • Berechnen Sie keine unnötigen Variablen in Ihren Klassen. Ursprünglich hat die Berechnung des FEN in jedem meiner ChessBoards die Geschwindigkeit des Programms wirklich beeinträchtigt. Ich musste das mit einem Profiler herausfinden .

Ich weiß, dass das alt ist, aber hoffentlich hilft es jemandem. Viel Glück!

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.