1つのゲームティックで可能なすべての状態を生成できると思いますが、4人のプレーヤーと5つの基本アクション(4移動と爆弾配置)を使用すると、ゲームツリーの最初のレベルで5 ^ 4状態になります。
正しい!ゲームティックごとに、すべての5 ^ 4(または4方向に歩き、停止して「爆弾を置く」ことができるので6 ^ 4)アクションを検索する必要があります。ただし、プレイヤーがすでに移動することを決定している場合、移動が実行されるまでには時間がかかります(たとえば、10ゲームティック)。この期間中、可能性の数は減少します。
その値は、次のレベルごとに指数関数的に上昇します。何か不足していますか?それを実装する方法はありますか、またはまったく異なるアルゴリズムを使用する必要がありますか?
ハッシュテーブルを使用して、同じゲーム状態の「サブツリー」を1回だけ計算できます。プレーヤーAが上下に歩き、他のすべてのプレーヤーが「待機」している間、あなたは同じゲーム状態になります。「left-right」や「right-left」と同じです。また、「左上」と「左上」を移動すると、同じ状態になります。ハッシュテーブルを使用すると、すでに評価されているゲーム状態の計算スコアを「再利用」できます。これにより、成長速度が大幅に低下します。数学的には、それはあなたの指数関数的成長関数のベースを減らします。どれだけ複雑さを軽減するかを理解するために、プレーヤーが上/下/左/右/停止するだけの場合の、マップ上の到達可能な位置(=異なるゲーム状態)と比較した、1人のプレーヤーのみの可能な移動を見てみましょう。
深さ1:5つの移動、5つの異なる状態、この再帰のための5つの追加状態
深さ2:25の移動、13の異なる状態、この再帰のための8つの追加の状態
深さ3:6125移動、25の異なる状態、この再帰のための12の追加状態
それを視覚化するには、自分に答えてください。マップ上のどのフィールドに1回の移動、2つの移動、3つの移動で到達できますか。答えは、開始位置からの最大距離が1、2、または3のすべてのフィールドです。
HashTableを使用する場合、到達可能な各ゲーム状態(この例では深さ3の25)を1回評価するだけで済みます。一方、HashTableがない場合、それらを複数回評価する必要があります。つまり、深さレベル3では25ではなく6125評価になります。最適:HashTableエントリを計算したら、後のタイムステップで再利用できます...
さらに深く調査する価値のない、インクリメンタルディープニングおよびアルファベータプルーニングの「カット」サブツリーを使用することもできます。チェスの場合、これは検索されるノードの数を約1%に減らします。アルファベータ剪定の簡単な紹介は、ビデオとしてここにあります:http://www.teachingtree.co/cs/watch? concept_name = Alpha-beta + Pruning
さらなる研究の良いスタートはhttp://chessprogramming.wikispaces.com/Searchです。このページはチェスに関連していますが、検索と最適化のアルゴリズムはまったく同じです。
ゲームに適した別の(しかし複雑な)AIアルゴリズムは、「時間差学習」です。
よろしく
ステファン
PS:可能なゲームステートの数を減らすと(たとえば、マップのサイズが非常に小さい、プレイヤーごとに爆弾が1つだけで、他には何もない)、すべてのゲームステートの評価を事前に計算する機会があります。
-編集-
ミニマックス計算のオフライン計算結果を使用して、神経ネットワークをトレーニングすることもできます。または、それらを使用して、手動で実装した戦略を評価/比較することもできます。たとえば、提案された「パーソナリティ」のいくつかと、どの戦略が適切であるかを検出するいくつかのヒューリスティックを実装できます。したがって、状況(ゲームの状態など)を「分類」する必要があります。これはニューラルネットワークでも処理できます。ニューラルネットワークをトレーニングして、現在コーディングされている戦略のうちどれが現在の状況で最適に機能しているかを予測し、実行します。これにより、実際のゲームで非常に優れたリアルタイムの決定が得られます。オフラインでの計算にかかる時間はそれほど重要ではないため(ゲームの前にあるため)、それ以外の場合に実現できる低深度の検索よりもはるかに優れています。
-編集#2-
1秒ごとにベストムーブのみを再計算する場合は、より高いレベルのプレーニングを試みることもできます。それはどういう意味ですか?あなたはあなたが1秒間に何回動くことができるか知っています。そのため、到達可能な位置のリストを作成できます(たとえば、これが1秒間に3移動である場合、到達可能な位置は25になります)。次に、「位置xに移動して爆弾を配置する」のように計画できます。他の人が提案したように、ルーティングアルゴリズムに使用される「危険」マップを作成できます(位置xに移動する方法?どのパスを優先する必要があります[ほとんどの場合、可能なバリエーションがいくつかあります])。これは、巨大なHashTableと比較してメモリ消費は少ないですが、最適な結果は得られません。ただし、メモリ使用量が少ないため、キャッシング効果のために高速になる可能性があります(L1 / L2メモリキャッシュのより適切な使用)。
さらに:失うことになるバリエーションを選別するために、1人のプレーヤーの動きのみを含む事前検索を行うことができます。したがって、他のすべてのプレーヤーをゲームから除外します...各プレーヤーが失うことなく選択できる組み合わせを保存します。失う動きのみがある場合は、プレーヤーが最も長く生き続ける動きの組み合わせを探します。この種のツリー構造を格納/処理するには、次のようなインデックスポインターを含む配列を使用する必要があります。
class Gamestate {
int value;
int bestmove;
int moves[5];
};
#define MAX 1000000
Gamestate[MAX] tree;
int rootindex = 0;
int nextfree = 1;
各状態には評価「値」があり、移動時に「ツリー」内の配列インデックスを格納することにより、移動時に次のゲーム状態にリンクします(0 =停止、1 =上、2 =右、3 =下、4 =左)。 ]からmoves [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;
}
動的にメモリを割り当てるのは本当に遅いので、この種のツリー構造ははるかに高速です!しかし、検索ツリーの保存も非常に遅くなります...したがって、これはよりインスピレーションになります。