88

我正在尝试了解如何以面向对象的方式进行设计和思考,并希望从社区中获得有关此主题的一些反馈。下面是一个我希望以OO方式设计的象棋游戏的例子。这是一个非常广泛的设计,我在这个阶段的重点只是确定谁负责哪些消息以及对象如何相互交互以模拟游戏。请指出是否存在不良设计元素(高耦合、不良内聚等)以及如何改进它们。

国际象棋游戏有以下类

  • 木板
  • 播放器
  • 正方形
  • 国际象棋游戏

Board 由正方形组成,因此可以让 Board 负责创建和管理 Square 对象。每一块也都在一个正方形上,所以每块也有一个指向它所在的正方形的参考。(这有意义吗?)。然后每一块负责将自己从一个方格移动到另一个方格。玩家类拥有对他拥有的所有棋子的引用,并且还负责它们的创建(玩家应该创建棋子吗?)。Player 有一个方法 takeTurn,该方法又调用一个方法 movePiece,该方法属于piece 类,该方法将一块的位置从其当前位置更改为另一个位置。现在我对 Board 类必须负责的具体内容感到困惑。我认为需要确定游戏的当前状态并知道游戏何时结束。但是当一块改变它' s location 板子应该如何更新?它是否应该维护一个单独的正方形数组,其中存在碎片并随着碎片的移动而更新?

此外,ChessGame 最初创建 Board 和 player 对象,它们依次分别创建正方形和棋子并开始模拟。简而言之,这可能是 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();

}

我不清楚董事会的状态将如何更新。应该片有参考板?责任应该在哪里?谁持有什么参考资料?请帮助我输入您的意见并指出此设计中的问题。我故意不关注任何算法或游戏的更多细节,因为我只对设计方面感兴趣。我希望这个社区可以提供有价值的见解。

4

3 回答 3

54

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.

于 2010-11-12T18:59:27.943 回答
6

这是我的想法,对于一个相当基本的国际象棋游戏:

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 {....}
于 2010-11-30T17:15:40.377 回答
0

我最近在 PHP 中创建了一个国际象棋程序(网站点击这里源代码点击这里),我使它面向对象。这是我使用的类。

  • ChessRulebook (static) - 我把我所有的generate_legal_moves()代码都放在这里。该方法给出了一个棋盘,轮到它了,以及一些变量来设置输出的详细程度,它会生成该位置的所有合法移动。它返回 ChessMove 列表。
  • ChessMove - 存储创建代数符号所需的所有内容,包括起始方格、结束方格、颜色、棋子类型、捕获、检查、将死、提升棋子类型和过路。可选的附加变量包括消歧(对于像 Rae4 这样的移动)、castling 和 board。
  • ChessBoard - 存储与Chess FEN相同的信息,包括表示方格的 8x8 数组并存储轮到棋子的棋子、过路目标方格、易位权、半步时钟和全步时钟。
  • ChessPiece - 存储棋子类型、颜色、方格和棋子值(例如,pawn = 1、knight = 3、rook = 5 等)
  • ChessSquare - 将等级和文件存储为ints。

我目前正试图把这段代码变成一个国际象棋人工智能,所以它需要很快。我已经将generate_legal_moves()功能从 1500 毫秒优化到 8 毫秒,并且仍在努力。我从中吸取的教训是……

  • 默认情况下,不要在每个 ChessMove 中存储整个 ChessBoard。仅在需要时将电路板存放在移动中。
  • 尽可能使用原始类型int。这就是为什么ChessSquare将 rank 和 file 存储为,而不是使用人类可读的国际象棋方格符号(例如“a4”)int存储字母数字。string
  • 该程序在搜索移动树时会创建数万个 ChessSquares。我可能会重构程序以不使用 ChessSquares,这应该会提高速度。
  • 不要在你的类中计算任何不必要的变量。最初,计算我每个 ChessBoards 中的 FEN 确实会降低程序的速度。我不得不用profiler找出来。

我知道这是旧的,但希望它可以帮助某人。祝你好运!

于 2018-09-24T07:22:38.517 回答