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
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.
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:
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.
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 {....}
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.
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.int
s.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 ...
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".Je sais que c'est vieux, mais j'espère que ça aidera quelqu'un. Bonne chance!