分类
答案并不容易。博弈论对游戏进行了一些分类,但是对于该博弈而言,似乎没有与特定理论明确的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;
}
}
该代码未经验证可工作,也不可编译或完整。但是它应该给您一个想法如何做。最重要的工作是评估功能。它越复杂,算法将在以后尝试(必须撤消)错误的“重试”。这极大地降低了复杂性。
如果这太慢,您还可以尝试将某些两人游戏方法用作HashTables。为此,您必须为您评估的每个游戏状态计算(迭代)哈希键,并标记不会导致解决方案的状态。例如,每次Search()方法返回“ null”之前,必须创建一个HashTable条目,并且在输入Search()时,您将检查到目前为止是否已经达到该状态且没有肯定结果,如果返回,则返回“ null”,而没有进一步的调查。为此,您将需要一个巨大的哈希表,并且必须接受“哈希冲突”,这可能会导致您可能找不到现有的解决方案,但是如果您的哈希函数足够好并且您的表是足够大(存在可计算风险的风险)。
我认为没有其他算法可以更有效地解决此问题(如您所述),前提是您的评估功能最佳。