Общий альфа-бета-поиск с C ++ - PullRequest
3 голосов
/ 24 января 2010

Я пытаюсь разработать шаблон функции, который ищет лучший ход для любой игры - конечно, пользователь этого шаблона функции должен реализовать некоторые игровые функции. То, что я пытаюсь сделать, это обобщить Алфа бета алгоритм поиска с шаблоном функции.

Объявление этого шаблона функции выглядит следующим образом:

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 с членами которые оценивают состояние игры или решают, закончилась ли игра. Но альфа-бета Дерево поиска содержит множество экземпляров Gamestate, которые могут привести к ненужные требования к памяти, поэтому я хочу, чтобы Gamestate был маленьким. В общем, является ли шаблон функции правильным?

Ответы [ 6 ]

5 голосов
/ 24 января 2010

Вы можете определить абстрактный интерфейс, скажем, 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);
  ...
}

См. Char_traits в стандартной библиотеке C ++, как он там используется.

В качестве альтернативы, вы можете сделать их просто методами классов 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: (

2 голосов
/ 24 января 2010

Добавление методов в класс не увеличивает объекты этого класса. Методы хранятся один раз для всего класса и используются при вызовах для любых экземпляров. Поэтому добавление функций в класс 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 к вашему алгоритму, не требуя специальных методов для этих объектов. Чтобы не загрязнять глобальное пространство имен этими новыми функциями, вы можете поместить их в свое собственное пространство имен или класс свойств.

Таким образом, вы можете просто пропустить дополнительные параметры шаблона, если функции правильных имен и типов будут определены после создания шаблона.

2 голосов
/ 24 января 2010

Насколько я понимаю идею, я бы немного суммировал:

  • GameState, EndGame, GetMoves, Evaluate - перенос с единичными чертами типа GameStateTraits
  • MakeMove является ответственностью отдельного алгоритма, поэтому GameMovePolicy

Я намеренно различаю черты и политику как отдельный тип. Как объясняется в Общих методах программирования Boost , черты обычно несут информацию о типе, описание свойств типа. Эта идея хорошо подходит для переноса статической информации, игрового состояния. Политика обеспечивает поведение - MakeMove является частью динамического, поведенческого алгоритма игры.

template<typename GameStateTraits, typename GameMovePolicy>
int alphaBetaMax(GameStateTraits const& state, int alpha, int beta, int depthleft);
1 голос
/ 24 января 2010

Слишком много? Почему это вообще шаблон? Это именно тот тип «удара по гвоздю бесконечными молотами», с которым нужно быть осторожным с шаблонами. Особенно что-то вроде ИИ, где большинство проблем в значительной степени трудноразрешимы, а производительность имеет решающее значение.

1 голос
/ 24 января 2010

Лично я бы написал это как:

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);

Функция сама удалит каждый указатель, кроме игрового состояния (я предполагаю, что именно здесь ваша функция возвращает результат, все остальное временно?).

Теперь еще лучший способ сделать все это - пропустить только один класс, который может делать все, поскольку «сделать ход», «получить ход», «оценить», они все являются частью одной и той же логики. Нет смысла делать 5 разных классов, C ++ позволяет переопределять более одной виртуальной функции в классе.

0 голосов
/ 24 января 2010

Наивный вопрос: не должны ли оценка состояния игры и «игра закончилась» быть дополнительными методами (вы написали членов ) в классе игрового состояния и, таким образом, не занимают дополнительных инстанс памяти? Просто спрашиваю ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...