88

オブジェクト指向の方法で設計し、考える方法の感触を得ようとしており、このトピックに関するコミュニティからのフィードバックを得たいと考えています。以下は、オブジェクト指向でデザインしたいチェス ゲームの例です。これは非常に広い設計であり、この段階での私の焦点は、誰がどのメッセージを担当しているか、オブジェクトがどのように相互に作用してゲームをシミュレートしているかを特定することだけです。悪い設計要素 (高結合、悪い結束など) がある場合は指摘し、それらを改善する方法を教えてください。

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

  • ボード
  • プレーヤー
  • ピース
  • 四角
  • チェスゲーム

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

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

}

ボードの状態がどのように更新されるかは不明です。ピースにはボードへの参照が必要ですか? 責任の所在はどこにある?誰がどの参照を保持していますか? あなたの意見を参考にして、このデザインの問題点を指摘してください。私は意図的にアルゴリズムやゲーム プレイの詳細に焦点を当てていません。私はデザインの側面にのみ関心があるからです。このコミュニティが貴重な洞察を提供できることを願っています。

4

3 に答える 3

54

私は実際に、チェス盤、駒、ルールなどの完全な C# 実装を作成しました。これを大まかにモデル化した方法を次に示します (コーディングからすべての楽しみを奪いたくないため、実際の実装は削除されました)。

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

基本的な考え方は、ゲーム/ボード/etc は単にゲームの状態を保存するというものです。必要に応じて、位置を設定するなどの操作を行うことができます。以下を担当する IGameRules インターフェイスを実装するクラスがあります。

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

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

にプレイヤー情報を保存しないことに注意してくださいGameTable誰がプレイしていたか、いつゲームが行われたかなど、ゲームのメタデータを格納する責任を負う別のクラスがあります。

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

于 2010-11-12T18:59:27.943 に答える
6

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

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 {....}
于 2010-11-30T17:15:40.377 に答える