【发布时间】:2022-01-03 09:51:33
【问题描述】:
我目前正在研究简化国际象棋游戏的面向对象表示。其中大部分都很简单,但我有点不确定的一个设计决定是棋盘和棋子之间的关系。 底线是我试图找出表示棋盘/棋子关系的最佳方式,以便我可以轻松有效地查询“给定一个位置,这里有一个棋子”和“给定一个棋子,什么现场是不是”?
目前,Piece 类存储它们自己的位置(使用 Spot 类)和对 Board 类的引用。棋盘类将棋盘表示为 Dictionary
我遇到一些问题/麻烦的部分是如何使这些值保持同步这样,如果我调用(公共方法) Board.MovePiece() 或(私人二传手) Piece .CurrentPosition = new spot() Piece.CurrentPosition 和 Board's Dictionary
下面是我的代码的简化视图:
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.
}
再一次,除了一些可能不需要的复杂性之外,主要缺陷似乎是 Board.MovePiece() 需要公开以便 Piece 调用它,但这意味着任何人也可以调用 MovePiece() 并在板上移动某些东西而无需更新这件作品。
我考虑过的可能解决方案:
我完全想多了,这是一个相当合理的方法。
- 拥有自己的位置感觉不错,只是希望我有更好的访问修饰符来保护敏感值但允许朋友更改它们(我知道内部但似乎并没有完全解决我的问题)李>
我可以删除 currentPos 并且:
- 让 board 类维护一个 Dictionary
这感觉就像只是简单地转移问题并让董事会以不同的顺序维护两个基本相同信息的字典感觉很愚蠢,但至少它将数据紧密耦合在一起并允许董事会成为一切的“权威” .倾向于这一点,但感觉这可以通过正确的数据结构以某种方式进行优化
- 让主驱动程序/游戏类维护一个包含棋子及其当前位置的字典/元组列表,并让 Piece.Move() 传回棋盘告诉它们它们现在所在位置的信息。
再一次,这感觉就像我们在转移问题,但更是如此。另一方面,我可以看出游戏想要跟踪棋子的理由,但这仍然感觉很反 OO,我可能更愿意将其保留给董事会,如上所述。
理论上也可以
- 只需公开当前位置设置器,以便 Board 可以直接更改它,并在 Board 函数和 Piece.CurrentPosition 设置器中添加一些复杂的检查,以确保是否更改了另一个。
感觉比我认为的解决方案更糟糕,并且增加了更多的复杂性/风险。如果不是因为这个问题,CurrentPosition 应该有一个私有的 setter。
无论如何,非常感谢任何反馈/见解!
编辑:为了清楚起见,我假设片断类在它如何移动时拥有它的逻辑,并使用板的状态来确定它可以移动到哪些空间。像 Piece.DetermineMove() 这样的东西。具体来说,这件作品需要知道它在哪里,以及它可以移动到的空间上有哪些碎片,威胁它的碎片等等。
如果这是一个糟糕的设计,我会全力以赴,但我很难相信 Piece 类不应该在 OOP 设计中拥有自己的运动逻辑。
【问题讨论】:
-
首先,我会问自己是否真的需要额外的冗余/复杂性,或者简单(线性)搜索是否足以满足两个可能的查询之一。
-
这是一个非常有效的问题,但我认为这并不是一个很好的可扩展/长期设计决策。为了清楚起见,您是在建议我抓住 Dictionary
并遍历条目直到找到一个片段?我明白你的意思,但我试图展示良好的设计实践,这感觉很幼稚。 -
是的,这是我的第一个想法。如果只有很少的部分,这将很难被更复杂的算法所击败。
-
假设我忽略被翻倍的空间并添加倒置 Dictionary
你认为维护第二个字典所需的额外添加/删除步骤仍然会使 O (N) 每次在列表中搜索块位置的复杂性?这感觉就像它进入了实现细节和编译器优化的杂草,但我的直觉说,与仅维护第二个字典相比,任何大于 2-3 块的东西都会开始拖累。 -
也就是说,我猜想降低复杂性和空间使用是有价值的(尽管我们再次假设字典包含很少的条目)。在纸上,虽然检索从时间 O(1) 和空间 O(n) 与两个 dicts 到时间 O(n) 和仍然空间 O(n) 如果我们只搜索一个。也许时钟时间有利于搜索方法,但在纸面上,我不相信我们在简单性和更好的所有权之外得到了太多。
标签: c# dictionary oop design-patterns chess