面向对象的象棋游戏设计

我试图了解如何以面向对象的方式进行设计和思考,并希望从社区得到一些关于这个主题的反馈。下面是我希望以面向对象方式设计的一个国际象棋游戏示例。这是一个非常广泛的设计,我在这个阶段的重点只是确定谁负责什么消息以及对象如何相互作用来模拟游戏。请指出是否存在糟糕的设计元素(高耦合、不良内聚等) ,以及如何改进它们。

国际象棋游戏有以下类别

  • 登机
  • 玩家
  • 一片
  • 正方形
  • 棋局

The Board is made up of squares and so Board can be made responsible for creating and managing Square objects. Each piece also is on a square so each piece also has a reference to the square it is on. (Does this make sense?). Each piece then is responsible to move itself from one square to another. Player class holds references to all pieces he owns and is also responsible for their creation (Should player create Pieces?) . Player has a method takeTurn which in turn calls a method movePiece which belongs to the piece Class which changes the location of the piece from its current location to another location. Now I am confused on what exactly the Board class must be responsible for. I assumed it was needed to determine the current state of the game and know when the game is over. But when a piece changes it's location how should the board get updated? should it maintain a seperate array of squares on which pieces exist and that gets updates as pieces move?

此外,棋盘游戏最初创建的董事会和玩家对象,谁反过来创建正方形和块分别开始模拟。简而言之,这可能就是象棋游戏中的代码的样子

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


}

我不清楚董事会的情况将如何更新。作品应该有一个参考板?责任在哪里?谁持有什么推荐信?请帮助我与您的投入和指出问题在这个设计。我故意不关注任何算法或游戏的进一步细节,因为我只对设计方面感兴趣。我希望这个社区能提供有价值的见解。

75066 次浏览

I actually just wrote a full C# implementation of a chess board, pieces, rules, etc. Here's roughly how I modeled it (actual implementation removed since I don't want to take all the fun out of your coding):

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

The basic idea is that Game/Board/etc simply store the state of the game. You can manipulate them to e.g. set up a position, if that's what you want. I have a class that implements my IGameRules interface that is responsible for:

  • Determining what moves are valid, including castling and en passant.
  • Determining if a specific move is valid.
  • Determining when players are in check/checkmate/stalemate.
  • Executing moves.

Separating the rules from the game/board classes also means you can implement variants relatively easily. All methods of the rules interface take a Game object which they can inspect to determine which moves are valid.

Note that I do not store player information on Game. I have a separate class Table that is responsible for storing game metadata such as who was playing, when the game took place, etc.

EDIT: Note that the purpose of this answer isn't really to give you template code you can fill out -- my code actually has a bit more information stored on each item, more methods, etc. The purpose is to guide you towards the goal you're trying to achieve.

Here is my idea, for a fairly basic chess game :

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

I recently created a chess program in PHP (website click here, source click here) and I made it object oriented. Here are the classes I used.

  • ChessRulebook (static) - I put all my generate_legal_moves() code in here. That method is given a board, whose turn it is, and some variables to set the level of detail of the output, and it generates all the legal moves for that position. It returns a list of ChessMoves.
  • ChessMove - Stores everything needed to create algebraic notation, including starting square, ending square, color, piece type, capture, check, checkmate, promotion piece type, and en passant. Optional additional variables include disambiguation (for moves like Rae4), castling, and board.
  • ChessBoard - Stores the same information as a Chess FEN, including an 8x8 array representing the squares and storing the ChessPieces, whose turn it is, en passant target square, castling rights, halfmove clock, and fullmove clock.
  • ChessPiece - Stores piece type, color, square, and piece value (for example, pawn = 1, knight = 3, rook = 5, etc.)
  • ChessSquare - Stores the rank and file, as ints.

I am currently trying to turn this code into a chess A.I., so it needs to be FAST. I've optimized the generate_legal_moves() function from 1500ms to 8ms, and am still working on it. Lessons I learned from that are...

  • Do not store an entire ChessBoard in every ChessMove by default. Only store the board in the move when needed.
  • Use primitive types such as int when possible. That is why ChessSquare stores rank and file as int, rather than also storing an alphanumeric string with human readable chess square notation such as "a4".
  • The program creates tens of thousands of ChessSquares when searching the move tree. I will probably refactor the program to not use ChessSquares, which should give a speed boost.
  • Do not calculate any unnecessary variables in your classes. Originally, calculating the FEN in each of my ChessBoards was really killing the program's speed. I had to find this out with a profiler.

I know this is old, but hopefully it helps somebody. Good luck!