2010-11-12 194 views
81

我想了解如何以面向對象的方式進行設計和思考,並希望從社區獲得關於此主題的一些反饋。以下是我希望以面向對象方式設計的國際象棋遊戲的一個例子。這是一個非常廣泛的設計,我在這個階段的重點只是確定誰負責什麼消息以及對象如何相互交互來模擬遊戲。請指出是否存在設計不好(高耦合,不良內聚等)的元素以及如何改進它們。面向對象的國際象棋遊戲設計

棋局有以下類

  • 球員
  • 廣場
  • ChessGame

董事會由正方形等局可以負責創建和管理Square ob jects。每件作品也放在一個正方形上,因此每件作品也有一個參考它所在的正方形。 (這有道理嗎?)。每件作品都有責任將自己從一個廣場移到另一個廣場。 Player類擁有對他擁有的所有作品的引用,並且也負責其創作(玩家是否應創建作品?)。玩家有一個方法takeTurn,它依次調用一個方法movePiece,該方法屬於片段Class,它將片段的位置從其當前位置改變到另一個位置。現在我很困惑董事會的職責究竟是什麼。我認爲需要確定遊戲的當前狀態,並知道遊戲何時結束。但是,當一塊改變它的位置板應該如何更新?它是否應該維持一個單獨的存在棋子的方塊陣列,並且隨着棋子的移動而更新?

另外,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

吹毛求疵的評論:P2不應該叫'takeTurn()如果P1的舉動結束了比賽'。不太挑剔的評論:我覺得把玩家稱爲「白色」和「黑色」更自然。 – 2010-11-12 19:01:14

+0

同意。但正如我所說,我對設計方面更感興趣,以及對於什麼操作以及誰擁有什麼參考,對象應該負責。 – Sid 2010-11-12 19:39:58

回答

48

其實我只是寫了一個完整的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 { 
    // .... 
} 

基本的想法是,遊戲/委員會/ etc只是存儲遊戲的狀態。你可以操縱它們到例如設置一個位置,如果這就是你想要的。我有一個實現我的IGameRules接口的類,它負責:

  • 確定哪些移動是有效的,包括castling和en passant。
  • 確定特定移動是否有效。
  • 確定玩家在入住/死亡/僵持狀態。
  • 執行移動。

分離遊戲/板類的規則,也意味着你可以相對容易地實現變型。規則界面的所有方法都需要一個可以檢查的對象,以確定哪些移動是有效的。

請注意,我不會在Game上存儲玩家信息。我有一個單獨的類Table,它負責存儲遊戲元數據,比如誰在彈鋼琴,當比賽發生了,等

編輯:注意,這個答案的目的是不是真的給你模板你可以填寫的代碼 - 我的代碼實際上有更多的信息存儲在每個項目,更多的方法等。目的是引導你實現你想要實現的目標。

+1

謝謝你的詳細回覆。不過,我有幾個有關設計的問題。例如,爲什麼Move應該是一個班級,這並不是很明顯。我唯一的關注點是分配責任,並以最清晰的方式決定班級之間的相互作用。我想知道任何設計決定背後的「原因」。我不清楚你是如何到達你所做的設計決策以及他們爲什麼是好的選擇。 – Sid 2010-11-12 21:23:15

+0

'移動'是一類,因此您可以將整個移動歷史記錄存儲在移動列表中,並且可以使用記錄和輔助信息,例如捕獲什麼棋子,可以提升哪些棋子等。 – cdhowie 2010-11-12 21:25:43

+0

@cdhowie是「遊戲'分配給'IGameRules'的實現者還是強制對象之外的規則?後者似乎是無足輕重的,因爲比賽無法保護自己的國家沒有? – plalx 2014-04-30 02:13:34

4

這裏是我的想法,有一個比較基本的國際象棋比賽:

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