基于C++的变体国际象棋引擎设计问题

Gou*_*ham 5 c++ templates chess

我有一个国际象棋变种引擎,与正常的国际象棋一起玩自杀国际象棋和输家国际象棋.随着时间的推移,我可能会为我的引擎添加更多变种.该引擎完全用C++实现,并正确使用OOP.我的问题与这种变型引擎的设计有关.

最初这个项目最初只是一个自杀式引擎,而且随着时间的推移,我添加了其他风味.为了添加新的变体,我首先尝试在C++中使用多态.例如,MoveGenerator抽象类有两个子类SuicideMoveGenerator,NormalMoveGenerator并且根据用户选择的游戏类型,工厂将实例化正确的子类.但我发现这要慢得多 - 显然是因为实例化包含虚函数的类并在紧密循环中调用虚函数都是非常低效的.

但后来我发现使用带有模板特化的C++模板来分离不同变体的逻辑,并最大限度地重用代码.这似乎也很合乎逻辑,因为动态链接在上下文中并不是必需的,因为一旦你选择了游戏类型,你基本上坚持它直到游戏结束.C++模板专业化提供了这种 - 静态多态性.模板参数是SUICIDELOSERSNORMAL.

enum GameType { LOSERS, NORMAL, SUICIDE };
Run Code Online (Sandbox Code Playgroud)

因此,一旦用户选择游戏类型,就会实例化适当的游戏对象,并且从那里调用的所有内容都将被适当地模板化.例如,如果用户选择自杀象棋,让我们说:

ComputerPlayer<SUICIDE>
Run Code Online (Sandbox Code Playgroud)

对象被实例化,并且该实例化基本上静态地链接到整个控制流.函数in ComputerPlayer<SUICIDE>将使用MoveGenerator<SUICIDE>,Board<SUICIDE>等等,而相应的函数NORMAL将适当地工作.

总的来说,这让我在开始时实例化正确的模板化专业课程,在任何if地方都没有任何其他条件,整个过程完美无瑕.最好的是没有性能损失!

然而,这种方法的主要缺点是使用模板会使您的代码更难阅读.如果处理不当,模板专业化也会导致严重错误.

我想知道其他变种引擎作者通常做什么来分离逻辑(好的代码重用)?我发现C++模板编程非常合适,但如果有更好的东西,我会很高兴拥抱.特别是,我检查了Dr. HG Muller的Fairymax引擎,但它使用配置文件来定义游戏规则.我不想那样做,因为我的许多变种都有不同的扩展,并且通过使它成为配置文件级别的通用,引擎可能不会变强.另一个受欢迎的引擎Sjeng到处乱七八糟,if我个人觉得这不是一个好的设计.

任何新的设计见解都非常有用.

Mat*_* M. 6

"在紧密循环中调用虚函数是低效的"

我会非常惊讶,如果这导致任何真正的膨胀,如果循环的所有变量具有相同的动态类型,那么我希望编译器从其L1缓存中获取相应的指令,因此不会受到太大影响.

然而,有一部分让我担心:

"很明显,因为实例化包含虚函数的类[效率很低"

现在......我真的很惊讶.

使用虚函数实例化类的成本几乎与实例化没有任何虚函数的类的成本无法区分:它是另一个指针,而且都是(在流行的编译器上,与之对应_vptr).

我猜测你的问题出在其他地方.所以我要猜测一下:

  • 你有没有机会进行大量的动态实例化?(打电话new)

如果是这种情况,你可以通过删除它们获得更多.

有一种设计模式被称为Strategy非常适合您的精确情况.事实上,这种模式的概念类似于虚函数的使用,但它实际上将这些函数外化.

这是一个简单的例子:

class StrategyInterface
{
public:
  Move GenerateMove(Player const& player) const;
private:
  virtual Move GenerateMoveImpl(Player const& player) const = 0;
};

class SuicideChessStrategy: public StrategyInterface
{
  virtual Move GenerateMoveImpl(Player const& player) const = 0;
};

// Others
Run Code Online (Sandbox Code Playgroud)

实施后,您需要一个功能来获得正确的策略:

StrategyInterface& GetStrategy(GameType gt)
{
  static std::array<StrategyInterface*,3> strategies
    = { new SuicideChessStrategy(), .... };
  return *(strategies[gt]);
}
Run Code Online (Sandbox Code Playgroud)

最后,您可以在不使用其他结构的继承的情况下委派工作:

class Player
{
public:
  Move GenerateMove() const { return GetStrategy(gt).GenerateMove(*this); }

private:
  GameType gt;
};
Run Code Online (Sandbox Code Playgroud)

成本与使用虚拟函数非常相似,但是您不再需要为游戏的基本对象动态分配内存,并且堆栈分配速度更快.


ski*_*ear 0

我不太确定这是否合适,但您也许可以通过 CRTP 实现静态多态性,并对原始设计进行一些细微的修改。