使用C ++进行通用Alpha Beta搜索

时间:2010-01-23 23:28:57

标签: c++ algorithm

我正在尝试设计一个搜索最佳动作的函数模板 对于任何游戏 - 当然这个功能模板的用户必须实现 一些游戏特定的功能。我想要做的是概括 具有功能模板的alpha beta搜索算法。

此功能模板的声明如下所示:

template<class GameState, class Move,
         class EndGame, class Evaluate, class GetMoves, class MakeMove)
int alphaBetaMax(GameState g, int alpha, int beta, int depthleft);

该功能必须包括:

  • 确定游戏是否已结束:bool EndGame(g)
  • 评估游戏状态:int Evaluate(g)
  • 获取可能的动作:std::vector<Move> moves = GetMoves(g)
  • 采取行动:Gamestate gnew = MakeMove(g, moves[i])

你认为该函数有多个模板参数吗?有办法吗? 减少参数的数量?一个想法是使用成员扩展GameState类 评估游戏状态或决定游戏是否结束。但是alpha beta 搜索树包含许多可能导致的Gamestate实例 不必要的内存要求,因此我想保持Gamestate小。一般来说,功能模板实际上是正确的方式吗?

6 个答案:

答案 0 :(得分:5)

您可以定义一个抽象界面,例如game_traits,并为每个游戏设置专门的game_traits实现:

template<typename Game>
class game_traits {
  ...
};

class Chess {
  ...
};

template<>
class game_traits<Chess> {
  static bool endGame(Chess game);
  ...
};

template <typename Game, typename traits = game_traits<Game> >
int alphaBetaMax(Game game, int alpha, int beta, int depthleft) {
  ended = traits::endGame(game);
  ...
}

请参阅C ++标准库中的char_traits如何在那里使用它。

或者,您可以将它们作为Game类的方法,因为您将它作为模板参数提供,所以您不需要从某个抽象类继承。当你的模板函数试图访问时,你会发现一个,也许不那么透明的编译错误,例如game.has_ended(),当没有这样的方法时。这种机制在标准模板库中也经常使用。

顺便说一句,有一个新功能计划用于此;概念:

auto concept GameType<typename Game>
{
  bool has_ended(Game&);
  ...
};

template<typename Game> requires GameType<Game>
int alphaBetaMax(Game game, int alpha, int beta, int depthleft) {
  bool ended = game.has_ended();
  ...
}

不幸的是,概念被推迟到标准的未来版本,并且还没有出现在c ++ 0x中:(

答案 1 :(得分:2)

据我所知,我会稍微总结一下:

  • GameState,EndGame,GetMoves,Evaluate - 使用单一特征包装类型GameStateTraits
  • MakeMove是单独算法的责任,因此GameMovePolicy

我故意将特征和政策区分为单独的类型。正如在Boost的Generic Programming Techniques中所解释的那样,特征通常包含类型信息,类型属性的描述。这个想法非常适合携带静态信息,即游戏状态。策略提供行为 - MakeMove是游戏动态,行为算法的一部分。

template<typename GameStateTraits, typename GameMovePolicy>
int alphaBetaMax(GameStateTraits const& state, int alpha, int beta, int depthleft);

答案 2 :(得分:2)

向类中添加方法不会使该类的对象变大。这些方法为整个类存储一次,并由任何实例上的调用使用。因此,将函数添加到GameState类不会导致算法需要更多内存。

然后,函数模板只需要单个参数GameState,并且需要用作此参数的类来实现正确的方法。

更灵活的方法是在算法中简单地使用自由函数:

template<class GameState>
int alphaBetaMax(GameState g, int alpha, int beta, int depthleft) {
   if (endGame(g)) {
     return 1;
   }
   std::vector<Move> moves = getMoves(g);
   // ...   
}

此处endGamegetMoves是相关名称,它们取决于模板参数(因为它们将g作为参数)。因此,在声明模板时,编译器不会搜索这些名称的实际定义(它还不知道这些函数应该具有什么类型,因为尚未指定GameState)。

只有在实例化模板时,才需要提供符合模板中使用方式的这些函数的重载:

struct MyGameState {};

bool endGame(const MyGameState &st) {
  return false;
}

std::vector<Move> getMoves(const MyGameState &st) {
  // ...
}

void tst() {
  MyGameState s;
  alphaBetaMax(s, 1, 1, 1); // uses the new functions
}

通过这种方式,您可以将任何GameState对象调整为算法,而无需在这些对象上使用特殊方法。为了避免使用这些新函数污染全局命名空间,您可以将它们放入自己的命名空间或特征类中。

所以基本上你可以省略额外的模板参数,只要在实例化模板后定义正确名称和类型的函数。

答案 3 :(得分:1)

我个人认为这是:

int alphaBetaMax(GameState *g, Move *move, EndGame *endgame, 
    Evaluate *evaluate, GetMoves* getmoves, MakeMove* makemove, 
    int alpha, int beta, int depthleft);

你称之为:

GameState gs;
alphaBetaMax(&gs, new ChessMove(), new ChessEndGame(), new ChessEvaluate(),
    new ChessGetMoves(), new ChessMakeMove(), a, b, 40);

函数本身会删除每个指针但游戏状态(我假设你的函数返回结果,其他一切都是临时的?)。

现在更好的方法是通过一个可以完成所有任务的类,因为“make move”,“get move”,“evaluate”,它们都是相同逻辑的一部分。没有理由创建5个不同的类,C ++允许您覆盖类中的多个虚函数。

答案 4 :(得分:1)

太多了?为什么它是一个模板?这就是“用无限锤击打钉子”,你需要小心使用模板。尤其是人工智能,其中大多数问题在很大程度上是棘手的,性能至关重要。

答案 5 :(得分:0)

天真的问题:游戏状态评估和“游戏已经结束”的决定不应该是游戏状态类中的其他方法(你写了成员),因此不占用额外的每个实例内存?只是问......