我目前正在研究一个简化国际象棋游戏的OO表示。大部分都很简单,但有一个设计决定我有点不确定,那就是棋盘和棋子之间的关系最重要的是,我正在努力找出表示棋盘/棋子关系的最佳方式,这样我就可以轻松高效地查询"给定一个位置,这里有一块吗;以及";给定一个片段,它在什么位置
目前,Piece类存储它们自己的位置(使用Spot类)和对Board类的引用。board类将板表示为Dictionary<点,片>(简化游戏只有几个棋子,所以感觉没有必要像我看到的其他例子一样存储一个N乘M的空位阵列)来跟踪棋盘上的哪些棋子。这是有用的,对我来说似乎是直观的,因为当试图移动和询问";这个地方有人吗"并且它可以返回位于给定位置的工件(如果有的话)。类似地,为了移动工件,我需要知道它当前在哪里,并且将位置存储在工件本身似乎是一种很好的OO方法。
我遇到一些问题/麻烦的部分是如何保持这些值的同步,这样如果我调用(公共方法)Board。MovePiece()或(private setter)Piece。CurrentPosition=new spot()both Piece。CurrentPosition和董事会词典<点,片>正确更新。尽可能地保持所有内容的私有性,同时确保在板或块上调用一个方法,同时保持另一个类的同步,即使不是不可能,也是非常棘手的。如果C#有像C++这样的友元类,我可以让Board成为Piece的友元,那么他们可以毫无问题地设置彼此的私有变量(我知道内部变量可以改善情况,但我不认为它会阻止项目中的其他代码从理论上调用它不应该调用的东西)。现在,我的解决方案是在工件上进行自定义设置。CurrentPosition属性,这样每次更改它的调用都会导致对公共董事会成员的正确调用(这绝对有效),但我觉得我可以做得更好。最大的风险是板方法是公共的,可以在工件类之外调用,因此不会更新工件位置,但我不太喜欢代码目前的冗余/轻微复杂性。
以下是我的代码的简化视图:
public class Board : IBoard
{
private uint _width;
private uint _height;
private Dictionary<Spot, Piece> _pieceLocations;
public Board(uint width, uint height)
{
_width = width;
_height = height;
_pieceLocations = new Dictionary<Spot, Piece>();
}
// heavily used by piece when determining legal moves, what pieces and free spaces are in range etc.
public Piece CheckSpot(Spot spot)
{
Piece piece;
if (_pieceLocations.TryGetValue(transformSpot(spot), out piece))
{
return piece;
}
return null;
}
/// remove instead of null to potentially optimize space a bit
public bool RemovePiece(Spot spot)
{
if (spot != null)
{
return _pieceLocations.Remove(transformSpot(spot));
}
return false;
}
/// This function will simply attempt to just move the piece to the specified destination.
/// It's up to the caller to make sure the move is a valid chess move, etc
public Spot MovePiece(Spot destination, Piece pieceToBeMoved)
{
// remove piece from current position
// note the need to have current position here
RemovePiece(pieceToBeMoved.CurrentPosition);
// attempt to place at and return new position
return PlacePiece(destination, pieceToBeMoved);
}
/// Simply places piece at specified destination if not occupied by another piece
private Spot PlacePiece(Spot destination, Piece pieceToPlace)
{
var transformedDestination = transformSpot(destination);
//business logic to check for stuff like a piece already at destination
_pieceLocations.Add(transformedDestination, pieceToPlace);
return transformedDestination;
}
注意transformSpot只是确保坐标不越界;将它们包裹在";如果需要,在板尺寸内。
public abstract class Piece : IPiece
{
protected IBoard _board;
public ColorType Color { get; protected set; }
private Spot _currentPosition;
public Piece(ColorType color, Spot currentPosition, IBoard board)
{
_board = board;
Color = color;
CurrentPosition = currentPosition ?? throw new ArgumentNullException(nameof(currentPosition), "When creating a piece you must specify a location");
}
public Spot CurrentPosition {
get { return _currentPosition; }
// I wanted to make sure that the piece's current position was always in sync with the board.
// Calling <Board> functinos here seemed like a reasonable approach.
protected set {
// if position is being set to null remove from board
if (value == null)
{
_board.RemovePiece(_currentPosition);
_currentPosition = null;
}
else
{
_currentPosition = _board.MovePiece(value, this);
}
}
}
public void Move()
{
// note that I now need the current position to figure out where I can go etc.
// insert logic to determine where we can move using chess rules etc.
var destination = new Spot(x,y);
// if spot is occupied remove piece
var occupyingPiece = _board.CheckSpot(destination);
if (occupyingPiece != null)
{
occupyingPiece.RemovePiece();
}
// call custom setter which in turn updates board to move piece from current spot to destination
CurrentPosition = destination;
}
public void RemovePiece()
{
// call custom setter which in turn updates board to remove piece
CurrentPosition = null;
}
以及一些用于主驱动的超粗糙伪代码
List<Piece> whitePieces = generateWhitePieces();
List<Piece> blackPieces = generateBlackPieces();
while (gameActive)
{
//somehow determine which piece to move
var pieceToMove = whitePieces.PickPiece();
// could pass and store CurrentPosition at this level but if it's not stored on the piece or easily
// accessible from the board but feels slightly icky
pieceToMove.Move();
pieceToMove = blackPieces.PickPiece();
pieceToMove.Move();
// etc.
}
同样,在一些可能不必要的复杂性之上的主要缺陷似乎是董事会。MovePiece()需要是公共的,Piece才能调用它,但这意味着任何人都可以调用MovePieces(),在不更新Piece的情况下移动板上的东西。
我考虑过的可能解决方案:
我想得太多了,这是一个相当合理的方法。
- 让作品拥有自己的位置感觉很好,只是希望我能更好地访问修饰符来保护敏感值,但允许朋友更改它们(我知道内部问题,但似乎不能完全解决我的问题)
我可以删除currentPos和:
- 让board类维护Dictionary<单件,现货>
这感觉就像是简单地转移问题,让董事会以不同的顺序维护两个基本上相同信息的字典,这感觉很愚蠢,但至少它将数据紧密地耦合在一起,并允许董事会成为;权威;一切都在哪里。倾向于这一点,但有一种感觉,这可以通过正确的数据结构进行优化
- 让主驱动程序/游戏类维护一个工件及其当前位置的字典/元组列表,并拥有工件。Move()返回董事会告诉他们现在在哪里的信息
再一次,这感觉就像我们在转移问题,但更重要的是。另一方面,我可以理解游戏想要跟踪碎片的理由,但这仍然让人觉得很反OO,我可能更喜欢如上所述将其保留在棋盘上。
理论上也可以
- 只需将当前位置设置器公开,这样Board就可以直接更改它,并在Board函数和Piece中添加一些复杂的检查。CurrentPosition setter,以确保其中一个是否更改,另一个也更改
感觉比我认为的要糟糕得多,增加了更多的复杂性/风险。如果不是因为这个问题,CurrentPosition应该有一个私有的setter。
无论如何,非常感谢任何反馈/见解!
EDIT:需要明确的是,我假设piece类在如何移动时拥有自己的逻辑,并使用板的状态来确定它可以移动到什么空间。类似piece.DermineMove()。具体来说,piece需要知道它在哪里,以及它可以移动的空间上有什么碎片(如果有的话),威胁它的碎片,等等。
如果这是一个糟糕的设计,我会洗耳恭听,但我很难相信Piece类在OOP设计中不应该拥有自己的运动逻辑。
关于这一点的意见会很浓,实际上没有绝对正确的答案。每种方法都有优缺点,其中一些可能要到开发的后期才能发现。话虽如此,这是我的。:)
你真正想做的是确保在你的应用程序中只有一种做任何操作的方法,并且只有一个圣经数据存储在应用程序中的地方(抛开宗教含义不谈,圣经含义是"真")。
您可以从逻辑对象(在您的域中)开始,并尝试将这一方法放入有意义的对象中。如果这没有意义,那么你可能需要一个不同的[心理]模型。
您已经从Board
、Piece
和Spot
开始,到目前为止我很喜欢。由于我们希望将中某个片段的位置数据保存在一个位置,我认为我们希望在板级上这样做,因为这将使移动更容易(一个片段不必"向上"到板上,然后"向下"返回到另一个片段)。
这意味着Piece
将没有Spot
属性,只有一个类型(knight、bishop等)和ColorType(白色/黑色)。棋盘上会有以Spot
为键的棋子字典(这是一把好钥匙——一个正方形上只能有一个棋子)。这使得很多逻辑更加简单。移动一个片段意味着将其从dict中取出并放回目的地:
// Move attempt method might look like this:
Piece thisPiece = ... ;
Spot old = (1, 1); // origin ..
Spot new = (2, 2); // .. destination
// do some validation
if (pieces.Contains(new) && pieces[new].color == thisPiece.color)
throw new SpotOccupiedByFriendlyPieceAndThisIsntACastleException();
// capture a piece
if (pieces.Contains(new) && pieces[new].color != thisPiece.color)
{
pieces.Remove(new);
}
pieces.Remove(old); // move the old piece from "old"...
pieces.Add(new, thisPiece); //... to "new"
通过将所有的块放在板级的Dictionary<Spot, Piece>
中,只有一个集合保存块到点的关系(板拥有它),板可以执行它需要的所有逻辑。