分類
答えは簡単ではありません。ゲーム理論にはゲームの分類がいくつかありますが、そのゲームと特別な理論との明確な1対1の一致はないようです。これは、組み合わせ問題の特別な形式です。
これは巡回セールスマンではありません。これは、「ノード」にアクセスして、最後のノードから次のノードに到達するためにある程度のコストがかかる注文を決定することになります。キューを並べ替えることも、マップ上のすべてのフィールドを使用する必要もありません。
一部の項目を「ナップザック」に入れると一部のフィールドが空になるため、ナップザックは一致しません。したがって、これは拡張された形式の可能性がありますが、おそらくアルゴリズムが適用されない可能性があります。
ウィキペディアはここに分類に関するいくつかのヒントを与えます:http : //en.wikipedia.org/wiki/Game_theory#Types_of_games
私はそれを「離散時間最適制御問題」(http://en.wikipedia.org/wiki/Optimal_control)として分類しますが、これが役立つとは思いません。
アルゴリズム
完全なキューが本当にわかっている場合は、ツリー検索アルゴリズムを適用できます。あなたが言ったように、問題の複雑さはキューの長さとともに非常に速く成長します。メモリをあまり必要としない「深さ優先検索(DFS)」のようなアルゴリズムを使用することをお勧めします。スコアは重要ではないので、最初の解決策を見つけた後に停止することができます。最初に検索するサブブランチを決定するには、順序付けにヒューリスティックを適用する必要があります。つまり、評価関数(たとえば、空のフィールドの数。これはより高度なものであるほど優れています)を記述し、次の移動のどれが最も有望かを比較するスコアを与える必要があることを意味します。
次に必要なのは次の部分のみです。
- ゲームのすべての情報を格納するゲーム状態のモデル(例:ボードステータス/マップ、キュー、移動番号/キュー内の位置)
- 与えられたゲーム状態のすべての有効な動きを提供する動きジェネレーター
- 「移動」および「移動を元に戻す」機能。与えられた(有効な)動きをゲーム状態に適用/元に戻します。一方、「移動」機能は「元に戻す」機能の「元に戻す」情報を保存する必要があります。ゲームの状態をコピーして、反復ごとに変更すると、検索が大幅に遅くなります。少なくともスタックに状態を保存するようにしてください(=ローカル変数、 "new"を使用した動的割り当てなし)。
- 各ゲームの状態に匹敵するスコアを与える評価関数
- 検索機能
以下は、深さ優先検索の不完全な参照実装です。
public class Item
{
// TODO... represents queue items (FLOWER, SHOVEL, BUTTERFLY)
}
public class Field
{
// TODO... represents field on the board (EMPTY or FLOWER)
}
public class Modification {
int x, y;
Field originalValue, newValue;
public Modification(int x, int y, Field originalValue, newValue) {
this.x = x;
this.y = y;
this.originalValue = originalValue;
this.newValue = newValue;
}
public void Do(GameState state) {
state.board[x,y] = newValue;
}
public void Undo(GameState state) {
state.board[x,y] = originalValue;
}
}
class Move : ICompareable {
// score; from evaluation function
public int score;
// List of modifications to do/undo to execute the move or to undo it
Modification[] modifications;
// Information for later knowing, what "control" action has been chosen
public int x, y; // target field chosen
public int x2, y2; // secondary target field chosen (e.g. if moving a field)
public Move(GameState state, Modification[] modifications, int score, int x, int y, int x2 = -1, int y2 = -1) {
this.modifications = modifications;
this.score = score;
this.x = x;
this.y = y;
this.x2 = x2;
this.y2 = y2;
}
public int CompareTo(Move other)
{
return other.score - this.score; // less than 0, if "this" precededs "other"...
}
public virtual void Do(GameState state)
{
foreach(Modification m in modifications) m.Do(state);
state.queueindex++;
}
public virtual void Undo(GameState state)
{
--state.queueindex;
for (int i = m.length - 1; i >= 0; --i) m.Undo(state); // undo modification in reversed order
}
}
class GameState {
public Item[] queue;
public Field[][] board;
public int queueindex;
public GameState(Field[][] board, Item[] queue) {
this.board = board;
this.queue = queue;
this.queueindex = 0;
}
private int Evaluate()
{
int value = 0;
// TODO: Calculate some reasonable value for the game state...
return value;
}
private List<Modification> SimulateAutomaticChanges(ref int score) {
List<Modification> modifications = new List<Modification>();
// TODO: estimate all "remove" flowers or recoler them according to game rules
// and store all changes into modifications...
if (modifications.Count() > 0) {
foreach(Modification modification in modifications) modification.Do(this);
// Recursively call this function, for cases of chain reactions...
List<Modification> moreModifications = SimulateAutomaticChanges();
foreach(Modification modification in modifications) modification.Undo(this);
// Add recursively generated moves...
modifications.AddRange(moreModifications);
} else {
score = Evaluate();
}
return modifications;
}
// Helper function for move generator...
private void MoveListAdd(List<Move> movelist, List<Modifications> modifications, int x, int y, int x2 = -1, int y2 = -1) {
foreach(Modification modification in modifications) modification.Do(this);
int score;
List<Modification> autoChanges = SimulateAutomaticChanges(score);
foreach(Modification modification in modifications) modification.Undo(this);
modifications.AddRange(autoChanges);
movelist.Add(new Move(this, modifications, score, x, y, x2, y2));
}
private List<Move> getValidMoves() {
List<Move> movelist = new List<Move>();
Item nextItem = queue[queueindex];
const int MAX = board.length * board[0].length + 2;
if (nextItem.ItemType == Item.SHOVEL)
{
for (int x = 0; x < board.length; ++x)
{
for (int y = 0; y < board[x].length; ++y)
{
// TODO: Check if valid, else "continue;"
for (int x2 = 0; x2 < board.length; ++x2)
{
for(int y2 = 0; y2 < board[x].length; ++y2) {
List<Modifications> modifications = new List<Modifications>();
Item fromItem = board[x][y];
Item toItem = board[x2][y2];
modifications.Add(new Modification(x, y, fromItem, Item.NONE));
modifications.Add(new Modification(x2, y2, toItem, fromItem));
MoveListAdd(movelist, modifications, x, y, x2, y2);
}
}
}
}
} else {
for (int x = 0; x < board.length; ++x)
{
for (int y = 0; y < board[x].length; ++y)
{
// TODO: check if nextItem may be applied here... if not "continue;"
List<Modifications> modifications = new List<Modifications>();
if (nextItem.ItemType == Item.FLOWER) {
// TODO: generate modifications for putting flower at x,y
} else {
// TODO: generate modifications for putting butterfly "nextItem" at x,y
}
MoveListAdd(movelist, modifications, x, y);
}
}
}
// Sort movelist...
movelist.Sort();
return movelist;
}
public List<Move> Search()
{
List<Move> validmoves = getValidMoves();
foreach(Move move in validmoves) {
move.Do(this);
List<Move> solution = Search();
if (solution != null)
{
solution.Prepend(move);
return solution;
}
move.Undo(this);
}
// return "null" as no solution was found in this branch...
// this will also happen if validmoves == empty (e.g. lost game)
return null;
}
}
このコードは動作することが確認されておらず、コンパイル可能でも完全でもありません。しかし、それはあなたにそれを行う方法のアイデアを与えるはずです。最も重要な作業は評価関数です。より高度なものであるほど、後でアルゴリズムが試行する(そして元に戻す必要がある)間違った「試行」が行われます。これにより、複雑さが大幅に軽減されます。
これが遅すぎる場合は、2人ゲームのいくつかのメソッドをHashTablesとして適用することもできます。そのためには、評価する各ゲーム状態の(反復)ハッシュキーを計算し、ソリューションにつながらない状態にマークを付ける必要があります。たとえば、Search()メソッドが「null」を返す前にHashTableエントリを作成する必要があり、Search()に入るときに、この状態がすでに肯定的な結果なしに到達しているかどうかを確認し、そうである場合は「null」を返します。さらなる調査。これには巨大なハッシュテーブルが必要であり、「ハッシュ衝突」を受け入れる必要があります。これにより、おそらく既存のソリューションが見つからない可能性がありますが、ハッシュ関数が十分であり、テーブルが十分に大きい(計算可能なリスクのリスク)。
あなたの評価関数が最適であると仮定して、この問題を(あなたが説明したように)より効率的に解決する他のアルゴリズムはないと思います...