2010-11-12 7 views
81

próbuję poczuć, jak projektować i myśleć w sposób Object Oriented i chcą uzyskać pewne informacje zwrotne od społeczeństwa na ten temat. Poniżej znajduje się przykład gry w szachy, którą chcę zaprojektować w stylu OO. Jest to bardzo szeroki projekt i na tym etapie skupiam się tylko na tym, aby określić, kto jest odpowiedzialny za komunikaty i sposób, w jaki obiekty współdziałają ze sobą, aby symulować grę. Proszę wskazać, czy istnieją elementy złego projektu (wysokie sprzężenie, słaba spójność itp.) I jak je poprawić.Object Oriented Design for gry w szachy

Gra Chess ma następujących klas

  • Board
  • gracza
  • Kawałek
  • Plac
  • ChessGame

Zarząd składa się z kwadratów i tak Nadzorczej może być odpowiedzialny za tworzenie i zarządzanie Square ob jekty. Każdy kawałek jest również na kwadracie, więc każdy kawałek ma również odniesienie do kwadratu, na którym się znajduje. (Czy to ma sens?). Każdy kawałek jest odpowiedzialny za przesunięcie się z jednego kwadratu do drugiego. Klasa gracza zawiera odniesienia do wszystkich utworów, które posiada, a także jest odpowiedzialna za ich tworzenie (czy gracz powinien tworzyć kawałki?). Gracz ma metodę takeTurn, która z kolei wywołuje metodę movePiece, która należy do utworu Class, która zmienia położenie fragmentu z bieżącej lokalizacji na inną lokalizację. Teraz jestem zdezorientowany, za co dokładnie odpowiedzialna jest klasa Board. Zakładałem, że było to konieczne, aby określić aktualny stan gry i wiedzieć, kiedy gra się skończyła. Ale kiedy utwór zmienia jego położenie, w jaki sposób płyta powinna zostać zaktualizowana? czy powinien on utrzymywać oddzielną tablicę kwadratów, na których znajdują się części i która otrzymuje aktualizacje w miarę przesuwania się kawałków?

Również ChessGame intially tworzy planszy i odtwarzacz obiektów, które z kolei tworzą kwadraty i kawałki odpowiednio i uruchomić symulację. W skrócie, to może być to, co w ChessGame kod może wyglądać

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

} 

jestem jasne, w jaki sposób stan planszy będą aktualizowane. Czy element powinien mieć odniesienie do tablicy? Gdzie powinna leżeć odpowiedzialność? Kto posiada jakie referencje? Proszę, pomóż mi swoimi uwagami i wskaż problemy w tym projekcie. Rozmyślnie nie skupiam się na żadnych algorytmach ani innych szczegółach gry, ponieważ interesuję się tylko aspektem projektowania. Mam nadzieję, że ta społeczność może dostarczyć cennych informacji.

+3

nitpicky komentarz: p2 nie powinno nazywać 'takeTurn()' jeśli ruch P1 kończy grę. Mniej nijaki komentarz: bardziej naturalne jest nazywanie graczy "białymi" i "czarnymi". –

+0

Uzgodnione. Ale jak powiedziałem, bardziej interesują mnie aspekty projektowe i to, co Obiekty powinny być odpowiedzialne za to, jakie działania i kto posiada jakie referencje. – Sid

Odpowiedz

48

I rzeczywiście tylko napisał pełną C# realizacji szachownicy, kawałki, zasady itp oto z grubsza jak ja wzorowany go (faktyczna realizacja usunięty, ponieważ nie chcą podjąć wszystkie zabawy z twoje kodowanie):

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

Podstawową ideą jest to, że Game/Board/etc po prostu przechowuje stan gry. Możesz nimi manipulować np. ustaw pozycję, jeśli tego chcesz. Mam klasę, która implementuje mój interfejs IGameRules, który jest odpowiedzialny za:

  • Określanie, które ruchy są ważne, w tym roszady i en passant.
  • Ustalenie, czy określony ruch jest ważny.
  • Określanie, kiedy gracze są w czeku/checkmate/pat.
  • Wykonywanie ruchów.

Oddzielenie reguł od klas gry/planszy oznacza także, że możesz stosunkowo łatwo wdrożyć warianty. Wszystkie metody interfejsu reguł pobierają obiekt, który mogą sprawdzić, aby określić, które ruchy są poprawne.

Należy pamiętać, że nie przechowuję informacji o graczu na Game. Mam osobną klasę Table który jest odpowiedzialny za przechowywanie metadanych, takich jak gry, który grał, gdy gra odbyła itd

EDIT: pamiętać, że celem tej odpowiedzi nie jest naprawdę daje szablon kod, który możesz wypełnić - mój kod zawiera trochę więcej informacji przechowywanych na każdym przedmiocie, więcej metod itd. Celem jest poprowadzenie Cię do celu, który chcesz osiągnąć.

+1

Dziękuję za szczegółową odpowiedź. Mam jednak kilka pytań dotyczących projektu. Na przykład nie jest od razu oczywiste, dlaczego Move powinien być klasą. Moim jedynym celem jest wyznaczanie odpowiedzialności i decydowanie o interakcjach między klasami w najczystszy możliwy sposób. Chcę wiedzieć, "dlaczego" kryje się za decyzją projektową. Nie jestem pewien, w jaki sposób dotarłeś do decyzji projektowych, które podjąłeś i dlaczego są one dobrym wyborem. – Sid

+0

'Przenieś' jest klasą, dzięki czemu możesz przechowywać całą historię ruchów na liście ruchów, z notacją i informacjami pomocniczymi, takimi jak to, co zostało przechwycone, jaki pion mógł zostać awansowany, itp. – cdhowie

+0

@cdhowie Jest grą 'delgowanie do implementatora' IGameRules' lub wymuszanie reguł poza obiektem? Ta ostatnia wydaje się niewystarczająca, ponieważ gra nie może ochronić jej własnego stanu nie? – plalx

4

Oto mój pomysł na dość podstawowej gry w szachy:

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