繁体   English   中英

棋子层次结构设计:继承与类型字段

[英]Chess piece hierarchy design: inheritance vs type fields

我有一个作品的基类

 class piece;

和一个包含派生对象的数组

piece* board[8][8];

通过虚拟功能的优势,简洁的设计。 缺点,如果我必须在板上找到一块或比较一块,我必须恢复到动态转换(或 typeid)。 这很丑陋,并且在发出数百万个请求时可能会影响性能。

另一方面,如果我制作一个单片类的数组,它有一个用于识别片的类型字段,我没有这个问题(它应该更快)但我必须制作超级丑陋的 switch 语句。 我想,由于件数是有限的,而且我认为自己不会制作那么多开关,这最终可能是一个更好的选择,你怎么看?

这是为了好玩(所以没有位板)。

阅读一些答案,我认为仅将类型字段用于运算符重载( == , != , ...)可以带来两个词的最佳效果。

boost::variant看起来也很有趣。

或者,如果您的课程集有限 - 即您知道数量,请使用变体和访问者。 例如boost::variant<king, queen, bishop, knight ...>而棋盘就是由这种类型的二维数组组成的。 现在要审问,你可以使用访问者...

我会使用类层次结构。

为了找到一件作品,您可以为每种作品类型保留一个单独的列表。 所以你知道在哪里寻找每种类型的作品。

为了进行比较,您也可以依赖虚拟方法。

另一种方法是使用组件架构(如此处所述: http : //cowboyprogramming.com/2007/01/05/evolve-your-heirachy/ ),但我认为这对于您清楚知道的国际象棋游戏来说太过分了类型并知道这些类型不会很快改变:)。

我会使用层次结构,如果我想知道类型(为什么?)有一个识别类型的虚拟方法。

我从来没有写过国际象棋程序,但我猜最常见的操作是这样的:

  • 显示/打印板
  • 获得每件可能的动作集
  • 总结一块棋盘上所有棋子的值,也许总结出某种取决于棋子的“位置值”(车在空旷的线上,类似的东西)

此外,有些棋子有“状态”(一个国王只有在它之前没有移动过的情况下才能城堡,如果另一个棋子移动了两个格子,一个棋子可以通过攻击)只适用于一种棋子。

这一切都在向我尖叫阶级层次结构。 (假设您不需要位板性能)

另一方面,您不太可能需要添加新的工件类型,也不太可能在分离时重新使用其中一种工件类型。 即可扩展性和模块化并不是真正的问题。 因此,如果您发现算法中应该真正放在一个地方的某些重要部分分散在多个部分类中 - 使用 switch 语句。 只需添加一个抽象方法 tp 返回PieceType枚举的Piece类并打开它。

你不能同时担心性能和代码的乐趣:)

考虑使用“nibbleboard”(或至少是byteboard)而不是bitboard,其中每个nibble 代表一种类型。 每个半字节也是对该片类型进行操作的单例对象表中的索引。

class Empty : public Piece {};
class Rook : public Piece {};
...

const int wrook = 1;
...
const int bpawn = 12;

Piece* Operator[13] = {new Empty(), new Rook(), ..., new Pawn()};

byte table[64] = {
    wrook, wbishop, wknight, wking, wqueen, wknight, wbishop, wrook,
    wpawn, wpawn, wpawn, wpawn, wpawn, wpawn, wpawn, wpawn, 
    0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 0, 0, 
    bpawn, bpawn, bpawn, bpawn, bpawn, bpawn, bpawn, bpawn, 
    brook, bbishop, bknight, bking, bqueen, bknight, bbishop, brook};

// Given some position and some operation DoSomething we would have this:
Operator[table[position]]->DoSomething(table, position, <other parameters>);

// Possible return value of DoSomething might be new table

“超级丑陋的 switch 语句”是正确的技术 它不丑。 这叫做函数式编程。

继承是完全错误的技术。 每个部分都以不同的方式移动,具有不同的图形和其他属性。 没有什么共同点。 棋子不是抽象的。 它们是离散对象的具体集合。

您必须通过统一来使某些东西变得通用:创建所谓的 sum 类型。 在 Ocaml 中:

type shape = Pawn | Rook | Knight | Bishop | Queen | King
type color = Black | White
type piece = shape * color
type pos = { row:int;  col:int }

let check_white_move piece board from to = match piece with
| Pawn -> on_board to && (from.row = 2 && to.row = 4 or to.row = from.row + 1)
| ....

在 C++ 中没有正确的 sum 类型,您可以使用:

enum shape { pawn, rook, knight, bishop, queen, king};
..
bool check_white_move (..) { switch piece {
 case pawn: ...

它更笨拙。 向 C 和 C++ 委员会投诉。 但是使用正确的概念。 点心类(可识别联合,变体)来统一一组离散的具体类型方式。 类和继承用于表示抽象并提供其实现。

国际象棋没有什么抽象的。 这都是关于组合的。 这不是不同技术的优缺点的问题:而是关于使用正确的技术。

[顺便说一句:是的,您可以尝试 boost 变体,尽管我不能为这个应用程序推荐它,因为这些部分没有关联数据,枚举是完美的]

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM