web-dev-qa-db-fra.com

Jeu orienté objet pour un jeu d'échecs

J'essaie de comprendre comment concevoir et de penser de manière orientée objet et je souhaite obtenir des commentaires de la communauté sur ce sujet. Voici un exemple de jeu d’échecs que je souhaite concevoir de manière OO. Cette conception est très large et mon objectif à ce stade est simplement d'identifier qui est responsable de quels messages et comment les objets interagissent les uns avec les autres pour simuler le jeu. Veuillez indiquer s'il existe des éléments de mauvaise conception (couplage élevé, mauvaise cohésion, etc.) et comment les améliorer. 

Le jeu d'échecs a les classes suivantes

  • Planche
  • Joueur
  • Pièce
  • Carré
  • Jeu d'échecs

Le tableau étant composé de carrés, il peut être chargé de créer et de gérer des objets carrés. Chaque pièce est également sur un carré, de sorte que chaque pièce a également une référence au carré sur lequel elle se trouve (Est-ce que ça a du sens?). Chaque pièce est ensuite responsable de se déplacer d’une case à l’autre ... La classe de joueurs contient des références à toutes les pièces qu’elle possède et est également responsable de leur création (le joueur doit-il créer des pièces?). Le joueur a une méthode takeTurn qui appelle à son tour une méthode movePiece qui appartient à la classe Class et modifie l'emplacement de la pièce de son emplacement actuel à un autre emplacement. Maintenant, je suis confus quant à la responsabilité exacte de la classe du jury. J'ai supposé qu'il était nécessaire de déterminer l'état actuel du jeu et de savoir quand le jeu serait terminé. Mais quand une pièce change son emplacement, comment le conseil doit-il être mis à jour? Devrait-il conserver un tableau séparé de carrés sur lesquels les pièces existent et qui reçoit des mises à jour au fur et à mesure que les pièces bougent? 

En outre, ChessGame crée initialement les objets Plateau et Joueur qui créent à leur tour des carrés et des pièces et lancent la simulation. En bref, cela pourrait être à quoi le code dans ChessGame pourrait ressembler

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

}

Je ne vois pas comment la situation du conseil sera mise à jour. Le morceau devrait-il avoir une référence au conseil? Où devrait être la responsabilité? Qui détient quelles références? Aidez-moi s'il vous plaît avec vos entrées et soulignez les problèmes dans cette conception. Je ne me concentre pas délibérément sur des algorithmes ou des détails supplémentaires du jeu car je ne m'intéresse qu'à l'aspect design. J'espère que cette communauté pourra fournir des informations précieuses. 

84
Sid

En fait, j’ai juste écrit une implémentation complète en C # d’un échiquier, de pièces, de règles, etc. Voici à peu près comment je l’ai modélisé (cette implémentation a été supprimée car je ne veux pas prendre tout le plaisir de votre codage):

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

L'idée de base est que Game/Board/etc stocke simplement l'état du jeu. Vous pouvez les manipuler pour, par exemple, mettre en place une position, si c'est ce que vous voulez. J'ai une classe qui implémente mon interface IGameRules qui est responsable de:

  • Déterminer quels mouvements sont valides, y compris le castling et en passant.
  • Déterminer si un mouvement spécifique est valide.
  • Déterminer quand les joueurs sont en échec/mat check/impasse.
  • Exécution de mouvements.

Séparer les règles des classes jeu/tableau signifie également que vous pouvez implémenter des variantes relativement facilement. Toutes les méthodes de l'interface de règles prennent un objet Game qu'elles peuvent inspecter pour déterminer quels déplacements sont valides.

Notez que je ne stocke pas les informations du joueur sur Game. J'ai une classe distincte Table qui est chargée de stocker les métadonnées du jeu, telles que qui jouait, quand le jeu a eu lieu, etc.

EDIT: Notez que le but de cette réponse n'est pas vraiment de vous donner un code de gabarit que vous pouvez remplir - mon code contient en fait un peu plus d'informations stockées sur chaque élément, plus de méthodes, etc. vous guider vers le but que vous essayez d'atteindre.

52
cdhowie

Voici mon idée pour un jeu d'échecs assez basique: 

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

J'ai récemment créé un programme d'échecs dans PHP ( site Web cliquez ici , source cliquez ici ) et je l'ai orienté objet. Voici les cours que j'ai utilisés.

  • ChessRulebook (statique) - J'ai mis tout mon code generate_legal_moves() ici. Cette méthode reçoit un tableau, son tour, et quelques variables pour définir le niveau de détail de la sortie. Elle génère tous les mouvements légaux pour cette position. Il retourne une liste de ChessMoves.
  • ChessMove - Stocke tout le nécessaire pour créer notation algébrique , y compris le carré de départ, le carré de fin, la couleur, le type de pièce, la capture, le chèque, le compagnon, le type de pièce promotionnelle et en passant. Les variables supplémentaires facultatives incluent la non-ambiguïté (pour les mouvements comme Rae4), le castling et le tableau.
  • ChessBoard - Stocke les mêmes informations qu'un Chess FEN , y compris un tableau 8x8 représentant les carrés et stockant les pièces d’échecs, dont il est le tour, carré cible, droits de châtiment, horloge demi-tour et horloge complète.
  • Pièce d'échecs - Stocke le type, la couleur, le carré et la valeur de la pièce (par exemple, pion = 1, chevalier = 3, tour = 5, etc.)
  • ChessSquare - Stocke les rangs et fichiers sous la forme ints.

J'essaie actuellement de transformer ce code en un jeu d'échecs A.I., il doit donc être rapide. J'ai optimisé la fonction generate_legal_moves() de 1500 ms à 8 ms et j'y travaille encore. Les leçons que j'ai apprises qui sont ...

  • Ne stockez pas un ChessBoard entier dans chaque ChessMove par défaut. Ne stockez la planche que lorsque vous en avez besoin.
  • Utilisez des types primitifs tels que int lorsque cela est possible. C’est pourquoi ChessSquare stocke les rangs sous la forme int, plutôt que de stocker également une string alphanumérique avec une notation carrée lisible par les échecs, telle que "a4".
  • Le programme crée des dizaines de milliers de ChessSquares lors de la recherche dans l’arbre de déplacement. Je vais probablement refactoriser le programme pour ne pas utiliser ChessSquares, ce qui devrait donner un coup de pouce à la vitesse.
  • Ne calculez aucune variable inutile dans vos classes. À l'origine, calculer le FEN dans chacun de mes ChessBoards était en train de tuer le programme. Je devais le découvrir avec un profileur .

Je sais que c'est vieux, mais j'espère que ça aidera quelqu'un. Bonne chance!

0
AdmiralAdama