チェスゲームのオブジェクト指向設計[クローズ]


88

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

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

  • ボード
  • プレーヤー
  • ピース
  • 平方
  • ChessGame

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

また、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();

}

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


3
気の利いたコメント:takeTurn()p1の動きがゲームを終了した場合、p2は呼び出すべきではありません。あまり気の利いたコメント:プレイヤーwhiteとを呼び出す方が自然だと思いますblack
クリストファージョンソン

同意しました。しかし、私が言ったように、私は設計の側面と、どのオブジェクトがどのアクションに責任を持ち、誰がどの参照を保持するかについてより興味があります。
シド

上記のスニペットで概説したように、私は気に入りました。私の実装では、各ピースはそれ自体のcanMove()機能で使用するため、完全な位置の内部コピーを持っています。そして、移動が完了すると、他のすべてのピースがボードの内部コピーを更新します。最適ではないことはわかっていますが、当時C ++を学ぶのは面白かったです。後で、チェスをしていない友達の友達がclasses、各ピースではなく、各スクエアに持っていると言った。そして、そのコメントは私に非常に興味深いものとして印象づけました。
eigenfield 2018

回答:


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

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

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

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

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

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


1
詳細な返信ありがとうございます。ただし、デザインに関していくつか質問があります。たとえば、Moveがクラスである理由はすぐにはわかりません。私の唯一の焦点は、責任を割り当て、可能な限りクリーンな方法でクラス間の相互作用を決定することです。デザイン決定の背後にある「理由」を知りたい。あなたが行った設計上の決定にどのように到達したのか、そしてなぜそれらが良い選択であるのかについてははっきりしていません。
2010年

Moveあなたが表記となど、ポーンがに昇格されているかもしれないもの、捕獲されたものを作品のよう補助情報と、移動リストに全体の移動履歴を保存することができるようにクラスがある
cdhowie

@cdhowieのGame実装者への委任ですか、それともIGameRulesオブジェクトの外部でルールを適用しますか?ゲームはそれ自体の状態を保護できないため、後者は不適切に見えますか?
plalx 2014

1
これはばかげているかもしれませんが、GameクラスをPieceTypeではなくPieceColor型にするべきではありませんか?
デニス・ヴァン・ギルス2017

1
@nikhilこれらは、両方のプレイヤーがまだキャッスルできる方向を示します(AファイルとHファイルに向かって)。これらの値は最初は真です。白のAルークが移動すると、CanWhiteCastleAはfalseになり、Hルークも同様になります。白の王が動くと、両方とも偽りになります。そして、黒についても同じプロセスです。
cdhowie

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

0

最近、PHPでチェスプログラムを作成し(Webサイトはここをクリックソースはここをクリック)、オブジェクト指向にしました。これが私が使ったクラスです。

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

私は現在、このコードをチェスAIに変換しようとしているので、高速である必要があります。generate_legal_moves()機能を1500msから8msに最適化し、現在も作業中です。それから学んだ教訓は...

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

私はこれが古いことを知っています、しかしうまくいけばそれは誰かを助けるでしょう。幸運を!

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.