我正在尝试设计一个搜索最佳动作的函数模板 对于任何游戏 - 当然这个功能模板的用户必须实现 一些游戏特定的功能。我想要做的是概括 具有功能模板的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小。一般来说,功能模板实际上是正确的方式吗?
答案 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)
据我所知,我会稍微总结一下:
我故意将特征和政策区分为单独的类型。正如在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);
// ...
}
此处endGame
和getMoves
是相关名称,它们取决于模板参数(因为它们将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)
天真的问题:游戏状态评估和“游戏已经结束”的决定不应该是游戏状态类中的其他方法(你写了成员),因此不占用额外的每个实例内存?只是问......