web-dev-qa-db-ja.com

チェスゲームのオブジェクト指向デザイン

私は、オブジェクト指向の方法で設計および思考する方法の感覚を得ようとしています。このトピックについてコミュニティからフィードバックをもらいたいです。以下は、私がOOのように設計したいチェスゲームの例です。これは非常に広範な設計であり、この段階での焦点は、誰がどのメッセージに責任があるのか​​を特定することですそして、オブジェクトがゲームをシミュレートするために相互にどのように相互作用するか悪いデザインの要素(高いカップリング、悪い凝集など)があるかどうか、そしてそれらを改善する方法を指摘してください。

Chessゲームには次のクラスがあります

  • ボード
  • プレーヤー
  • ピース
  • 平方
  • チェスゲーム

ボードは正方形で構成されているため、ボードは正方形オブジェクトの作成と管理を担当できます。また、各ピースは正方形上にあるため、各ピースにはそれが置かれている正方形への参照もあります。 (これは理にかなっていますか?)。次に、各ピースは、ある正方形から別の正方形に移動する責任があります。プレーヤークラスは、所有するすべてのピースへの参照を保持し、それらの作成にも責任があります(プレーヤーはピースを作成する必要がありますか?)。 PlayerにはtakeTurnメソッドがあり、このメソッドは、ピースの位置を現在の位置から別の位置に変更するピースクラスに属するmovePieceメソッドを呼び出します。今、私は理事会のクラスが正確に責任を負わなければならないことについて混乱しています。ゲームの現在の状態を判断し、ゲームがいつ終了するかを知る必要があると思いました。しかし、ピースが変更された場合、その場所はボードをどのように更新する必要がありますか?ピースが存在し、ピースの移動に応じて更新される正方形の個別の配列を維持する必要がありますか?

また、ChessGameは最初にボードとプレーヤーのオブジェクトを作成し、それらはそれぞれ正方形とピースを作成し、シミュレーションを開始します。簡単に言えば、これは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();

}

取締役会の状態がどのように更新されるかは不明です。作品にはボードへの参照が必要ですか?責任はどこにあるべきですか?誰がどのような参照を保持していますか?ご意見をお寄せください。この設計の問題点を指摘してください。設計の側面にのみ興味があるため、ゲームプレイのアルゴリズムや詳細には意図的に焦点を合わせていません。このコミュニティが貴重な洞察を提供してくれることを願っています。

87
Sid

私は実際にjustチェス盤、ピース、ルールなどの完全なC#実装を作成しました。 '(allコーディングの楽しさを取りたくない):

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

基本的な考え方は、ゲーム/ボードなどが単にゲームの状態を保存するというものです。それらを操作して、たとえば希望する場合は、ポジションを設定します。 IGameRulesインターフェースを実装するクラスがあります。

  • キャスティングや通行人など、どの動きが有効かを判断します。
  • 特定の移動が有効かどうかを判断します。
  • プレイヤーがいつチェック/チェックメイト/膠着状態にあるかの判断。
  • 移動を実行します。

ルールをゲーム/ボードクラスから分離することは、バリアントを比較的簡単に実装できることも意味します。ルールインターフェイスのすべてのメソッドは、Gameオブジェクトを受け取ります。これらのオブジェクトを検査して、どの移動が有効かを判断できます。

Gameにプレーヤー情報を保存しないことに注意してください。私は別のクラスTableを持っています。このクラスは、誰がプレイしていたのか、ゲームがいつ行われたのかなど、ゲームのメタデータを保存します。

編集:この回答の目的は、記入できるテンプレートコードを提供することではないことに注意してください-私のコードには、実際には各項目に保存されている情報やメソッドなどが少しあります。目的は、達成しようとしている目標に向かって導くことです。

54
cdhowie

かなり基本的なチェスゲームの私の考えは次のとおりです。

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 {....}
5
dharm0us

最近、PHP( website click heresource click here )でチェスプログラムを作成し、オブジェクト指向にしました。私が使用したクラス。

  • ChessRulebook(静的)-ここにgenerate_legal_moves()コードをすべて入れます。このメソッドには、順番が変わるボードと、出力の詳細レベルを設定するいくつかの変数が与えられ、その位置のすべての正当な動きが生成されます。 ChessMovesのリストを返します。
  • ChessMove-開始正方形、終了正方形、色、ピースタイプ、キャプチャ、チェック、チェックメイト、プロモーションピースタイプ、およびパッサンを含む、 代数表記 の作成に必要なすべてを格納します。オプションの追加変数には、曖昧性除去(Rae4などの移動の場合)、キャスティング、およびボードが含まれます。
  • ChessBoard-正方形を表す8x8配列を含む Chess FEN と同じ情報を格納します。ChesPiecesを格納します。
  • ChessPiece-ピースの種類、色、四角、ピースの値を保存します(たとえば、ポーン= 1、ナイト= 3、ルーク= 5など)
  • ChessSquare-ランクとファイルをintsとして保存します。

現在、このコードをチェスA.I.に変換しようとしているので、高速である必要があります。 generate_legal_moves()関数を1500ミリ秒から8ミリ秒に最適化しましたが、まだ作業中です。それから学んだ教訓は...

  • デフォルトでは、すべてのChessMoveにChessBoard全体を保存しないでください。必要な場合にのみ、移動中にボードを保管してください。
  • 可能な場合、intなどのプリミティブ型を使用します。 ChessSquareがランクとファイルをintとして保存する理由は、「a4」のような人間が読めるチェスの正方形表記で英数字stringも保存する理由です。
  • このプログラムは、移動ツリーを検索するときに何万ものChessSquaresを作成します。おそらく、プログラムをリファクタリングして、ChessSquaresを使用しないようにします。これにより、速度が向上します。
  • クラス内の不要な変数を計算しないでください。もともと、私のチェスボードのそれぞれでFENを計算すると、プログラムの速度が本当に低下しました。 profiler でこれを見つけなければなりませんでした。

私はこれが古いことを知っていますが、うまくいけば誰かの助けになります。幸運を!

0
AdmiralAdama