2010-11-12 26 views
81

Estoy tratando de tener una idea de cómo diseñar y pensar de una manera orientada a objetos y quiero obtener algunos comentarios de la comunidad sobre este tema. El siguiente es un ejemplo de un juego de ajedrez que deseo diseñar de manera OO. Este es un diseño muy amplio y mi enfoque en esta etapa es solo identificar quién es responsable de qué mensajes y cómo los objetos interactúan entre sí para simular el juego. Por favor señale si hay elementos de mal diseño (alto acoplamiento, mala cohesión, etc.) y cómo mejorarlos.Diseño orientado a objetos para un juego de ajedrez

El juego de ajedrez tiene las siguientes clases

  • Junta
  • jugador
  • pieza
  • Plaza
  • Chessgame

La Junta se compone de cuadrados y así Junta se puede hacer responsable de crear y administrar Square ob objetos. Cada pieza también está en un cuadrado, por lo que cada pieza también tiene una referencia al cuadrado en el que se encuentra. (¿Esto tiene sentido?). Cada pieza es responsable de moverse de una casilla a otra. La clase de jugador contiene referencias a todas las piezas que posee y también es responsable de su creación (¿Debería el jugador crear piezas?). El jugador tiene un método takeTurn que a su vez llama a un método movePiece que pertenece a la pieza Class que cambia la ubicación de la pieza desde su ubicación actual a otra ubicación. Ahora estoy confundido sobre de qué exactamente es responsable la clase de la Junta. Supuse que era necesario determinar el estado actual del juego y saber cuándo terminó el juego. Pero cuando una pieza cambia su ubicación, ¿cómo debe actualizarse la placa? ¿debería mantener una matriz separada de cuadrados en los que existen piezas y que recibe actualizaciones a medida que las piezas se mueven?

Además, ChessGame crea inicialmente el tablero y los objetos del jugador que a su vez crean cuadrados y piezas respectivamente y comienzan la simulación. En pocas palabras, esto podría ser lo que el código en Chessgame puede parecer

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

} 

no tengo muy claro de cómo se actualizará el estado de la junta. ¿Debería la pieza tener una referencia al tablero? ¿Dónde debería estar la mentira de la responsabilidad? ¿Quién tiene qué referencias? Por favor ayúdenme con sus entradas y señale los problemas en este diseño. Deliberadamente, no me estoy centrando en ningún algoritmo ni en otros detalles del juego, ya que solo me interesa el aspecto del diseño. Espero que esta comunidad pueda proporcionar información valiosa.

+3

comentario nitpicky: p2 no debe llamar 'takeTurn()' si el movimiento de P1 termina el juego. Comentario menos quisquilloso: me parece más natural llamar a los jugadores 'blanco' y' negro'. –

+0

De acuerdo. Pero como dije, estoy más interesado en los aspectos de diseño y en qué objetos deberían ser responsables de qué acciones y quién tiene qué referencias. – Sid

Respuesta

48

En realidad simplemente escribió un C# aplicación plena de un tablero de ajedrez, pedazos, normas, etc. Aquí es más o menos la forma en que lo modeló (implementación real eliminado ya que no quiero tomar todo la diversión de su codificación):

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

La idea básica es que Game/Board/etc simplemente almacena el estado del juego. Puede manipularlos, p. Ej. establecer una posición, si eso es lo que quieres. Tengo una clase que implementa la interfaz de mi IGameRules que es responsable de:

  • La determinación de lo que se mueve son válidas, incluyendo el enroque y de pasada.
  • Determinando si un movimiento específico es válido.
  • Determinando cuando los jugadores están en jaque/jaque mate/estancado.
  • Ejecutando movimientos.

Separar las reglas de las clases de juego/tablero también significa que puede implementar variantes con relativa facilidad. Todos los métodos de la interfaz de reglas toman un objeto Game que pueden inspeccionar para determinar qué movimientos son válidos.

Tenga en cuenta que no guardo información del jugador en Game. Tengo una clase separada Table que se encarga de almacenar los metadatos juego como el que estaba jugando, cuando el juego se llevó a cabo, etc.

EDIT: Tenga en cuenta que el propósito de esta respuesta no es realmente para darle Plantilla código que puede completar: mi código en realidad tiene un poco más de información almacenada en cada artículo, más métodos, etc. El propósito es guiarlo hacia el objetivo que está tratando de lograr.

+1

Gracias por la respuesta detallada. Sin embargo, tengo algunas preguntas con respecto al diseño. Por ejemplo, no es inmediatamente obvio por qué Move debería ser una clase. Mi único objetivo es asignar responsabilidades y decidir las interacciones entre las clases de la manera más limpia posible. Quiero saber el "por qué" detrás de cualquier decisión de diseño. No tengo claro cómo llegó a las decisiones de diseño que hizo y por qué son buenas opciones. – Sid

+0

'Move' es una clase para que pueda almacenar todo el historial de movimientos en una lista de movimientos, con notación e información auxiliar como qué pieza fue capturada, a qué se ha promocionado un peón, etc. – cdhowie

+0

@cdhowie Is the' Game '¿Delgating a un implementador de' IGameRules' o usted aplica reglas fuera del objeto? El último parece innapropiado ya que el juego no puede proteger su propio estado, ¿no? – plalx

4

Aquí es mi idea, para un juego de ajedrez bastante básico:

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