Я думаю, що я міг генерувати всі можливі стани для однієї ігрової галочки, але з чотирма гравцями та 5 основними діями (4 ходи та місце бомби) це дає 5 ^ 4 стани на першому рівні ігрового дерева.
Правильно! Вам потрібно шукати всі 5 ^ 4 (або навіть 6 ^ 4, оскільки ви можете ходити в 4-х напрямках, зупинятися і «ставити бомбу»?) Для кожної ігрової галочки. АЛЕ, коли гравець уже вирішив рухатись, потрібен певний час, поки хід не буде виконаний (наприклад, 10 ігрових тиків). У цей період кількість можливостей зменшується.
Це значення буде зростати експоненціально з кожним наступним рівнем. Я щось пропускаю? Чи є якісь способи її реалізувати або я повинен використовувати зовсім інший алгоритм?
Ви можете використовувати таблицю Hash-таблиць, щоб обчислити одне і те ж стан «піддерева» гри. Уявіть, що гравець А ходить вгору і вниз, а всі інші гравці "чекають", ви опинитеся в тому ж ігровому стані. Це те саме, що і для "ліво-право" або "право-ліво". Також переміщення "вгору-вліво" і "вліво-потім-вгору" призводить до того ж стану. Використовуючи хеш-таблицю, ви можете "повторно використовувати" обчислений бал для стану гри, який вже був оцінений. Це досить скорочує швидкість росту. Математично це зменшує основу вашої функції експоненціального росту. Щоб отримати уявлення про те, наскільки це зменшує складність, давайте подивимось на можливі рухи лише для одного гравця порівняно з доступними позиціями на карті (= різні стани гри), якщо гравець може просто рухатися вгору / вниз / вліво / вправо / стоп .
глибина 1: 5 рухів, 5 різних станів, 5 додаткових станів для цієї рекурсії
глибина 2: 25 рухів, 13 різних станів, 8 додаткових станів для цієї рекурсії
глибина 3: 6125 рухів, 25 різних станів, 12 додаткових станів для цієї рекурсії
Щоб візуалізувати це, відповідайте собі: до яких полів на карті можна дійти одним рухом, двома рухами, трьома рухами. Відповідь: Усі поля з максимальною відстані = 1, 2 або 3 від стартового положення.
При використанні HashTable вам потрібно лише один раз оцінити кожен доступний стан гри (у нашому прикладі 25 на глибині 3). Тоді як без HashTable вам потрібно оцінити їх кілька разів, що означало б 6125 оцінок замість 25 на рівні глибини 3. Найкраще. Після того, як ви обчислили запис HashTable, ви можете його повторно використовувати в наступних етапах часу ...
Ви також можете використовувати поглиблене поглиблення та обрізання альфа-бета-обрізки підрізів, які не варто шукати більш глибоко. Для шахів це зменшує кількість шуканих вузлів приблизно до 1%. Короткий вступ до обрізки альфа-бета можна знайти як відео тут: http://www.teachingtree.co/cs/watch?concept_name=Alpha-beta+Pruning
Гарним початком для подальших досліджень є http://chessprogramming.wikispaces.com/Search . Сторінка пов'язана з шахами, але алгоритми пошуку та оптимізації абсолютно однакові.
Інший (але складний) алгоритм ШІ - який би більше підходив до гри - це "Навчання часовій різниці".
З повагою
Стефан
PS: Якщо ви зменшите кількість можливих ігрових станів (наприклад, дуже малий розмір карти, лише одна бомба на гравця, нічого іншого), є шанс попередньо обчислити оцінку для всіх ігрових станів.
--edit--
Ви також можете використовувати розраховані в автономному режимі результати розрахунків мінімаксу для тренування нейронної мережі. Або ви можете використовувати їх для оцінки / порівняння стратегій, що реалізуються вручну. Наприклад, ви можете реалізувати деякі запропоновані "особистості" та деякі евристики, які виявляють, в яких ситуаціях стратегія є хорошою. Тому слід "класифікувати" ситуації (наприклад, стани гри). З цим може впоратися і нейрональна мережа: Навчіть нейронну мережу, щоб передбачити, яка із стратегій, кодованих рукою, найкраще грає в поточній ситуації та виконайте її. Це повинно дати надзвичайно хороші рішення в реальному часі для реальної гри. Набагато краще, ніж пошук з низькою глибиною, який можна досягти інакше, оскільки не має значення стільки часу, як тривати офлайн-обчислення (вони до гри).
- редагування №2 -
Якщо ви перераховуєте лише свої найкращі рухи кожні 1 секунду, ви також можете спробувати зробити більше планування вищого рівня. Що я маю на увазі під цим? Ви знаєте, скільки рухів ви можете зробити за 1 секунду. Таким чином, ви можете скласти список доступних позицій (наприклад, якщо це було б 3 ходи за 1 секунду, у вас було б 25 доступних позицій). Тоді ви могли б планувати так: перейдіть до "положення х і поставте бомбу". Як деякі інші запропонували, ви можете створити карту "небезпеки", яка використовується для алгоритму маршрутизації (як перейти до позиції x? Якому шляху слід віддати перевагу [можливі варіанти в більшості випадків]). Це менше споживає пам'ять у порівнянні з величезним HashTable, але дає менш оптимальні результати. Але оскільки він використовує менше пам'яті, він може бути швидшим через кешування ефектів (краще використовувати кеш пам'яті L1 / L2).
ДОПОМОГО: Ви можете робити попередній пошук, який містить лише ходи для одного гравця, щоб розібрати варіанти, які призводять до втрати. Тому вийміть із гри всіх інших гравців ... Зберігайте, які комбінації кожен гравець може вибрати, не програючи. Якщо є лише втрачені рухи, шукайте комбінації рухів, де гравець залишається живим найдовше. Щоб зберігати / обробляти такі структури дерев, ви повинні використовувати масив з такими покажчиками:
class Gamestate {
int value;
int bestmove;
int moves[5];
};
#define MAX 1000000
Gamestate[MAX] tree;
int rootindex = 0;
int nextfree = 1;
Кожен стан має оцінювальне "значення" та посилається на наступні Gamestates під час руху (0 = стоп, 1 = вгору, 2 = праворуч, 3 = вниз, 4 = ліворуч), зберігаючи індекс масиву в межах "дерева" в ходах [0 ] рухатися [4]. Для того щоб будувати дерево рекурсивно, це може виглядати приблизно так:
const int dx[5] = { 0, 0, 1, 0, -1 };
const int dy[5] = { 0, -1, 0, 1, 0 };
int search(int x, int y, int current_state, int depth_left) {
// TODO: simulate bombs here...
if (died) return RESULT_DEAD;
if (depth_left == 0) {
return estimate_result();
}
int bestresult = RESULT_DEAD;
for(int m=0; m<5; ++m) {
int nx = x + dx[m];
int ny = y + dy[m];
if (m == 0 || is_map_free(nx,ny)) {
int newstateindex = nextfree;
tree[current_state].move[m] = newstateindex ;
++nextfree;
if (newstateindex >= MAX) {
// ERROR-MESSAGE!!!
}
do_move(m, &undodata);
int result = search(nx, ny, newstateindex, depth_left-1);
undo_move(undodata);
if (result == RESULT_DEAD) {
tree[current_state].move[m] = -1; // cut subtree...
}
if (result > bestresult) {
bestresult = result;
tree[current_state].bestmove = m;
}
}
}
return bestresult;
}
Така структура дерева набагато швидша, оскільки динамічне розподілення пам’яті дійсно дуже повільне! Але зберігання дерева пошуку відбувається досить повільно ... Тож це більше натхнення.