实现的基本思想就是在游戏开始的时候先生成状态图,并且找到每个状态图到成功状态的路径,然后在游戏过程中只要先找到目前的状态,再前往下一个成功路径上的状态就行了。
以下是状态的类,要实现IEquatable<T>中的Equals接口,方便使用list中的内置方法而不至于出错。状态只是记录一边岸上的数据。
public class status : IEquatable<status>
{
public status(int p, int d, bool b)
{
pnum = p;
dnum = d;
boat = b;
children = new List<status>();
}
public bool isLegal()
{
if (dnum > pnum && pnum != 0)
return false;
if (3 - dnum > 3 - pnum && pnum != 3)
return false;
return true;
}
public bool equals(status other)
{
if (other.pnum == pnum && other.dnum == dnum && other.boat == boat)
return true;
else
return false;
}
public bool Equals(status other)
{
return equals(other);
}
public int pnum;
public int dnum;
public bool boat;
public List<status> children;
}
在firstController中新声明的一些变量如下
Graph是保存图的邻接表,appendGraph是初始化图的时候用到的,rootStatus是游戏刚开始的状态,path是保存每个状态对应的最短路径的邻接表,visited是迪杰斯特拉算法找最短路径时要用到的,cnext标志UI上的next按钮能否出现,避免多次点击的问题。
public List<status> Graph = new List<status>();
public List<status> appendGraph = new List<status>();
public status rootstatus = new status(3, 3, true);
public List<List<status>> path = new List<List<status>>();
List<status> visited = new List<status>();
bool cnext = true;
自动生成状态图
图所用的数据结构是邻接链表,基本思路就是先初始化一开始的状态,然后状态分成有船和无船两种,有船可以走两个牧师,两个魔鬼,一个牧师,一个魔鬼,一个牧师和一个魔鬼,而无船则是相反,是来几个牧师和魔鬼,据此就可以生成下一个状态,生成下一个状态之后再判断状态是否合法(即是否已经输了),合法后就假如到该原状态的儿子列表(即连接的节点列表),然后再检查新状态是否已经存在图中,不存在再加入到图的节点链表中。代码如下
void initGraph()
{
List<status> graph = new List<status>(Graph);
do
{
appendGraph.Clear();
foreach (status s in graph)
{
if (s.boat)
{
if (s.pnum >= 2)
{
status newStatus = new status(s.pnum - 2, s.dnum, false);
if (newStatus.isLegal())
{
s.children.Add(newStatus);
afterLegal(newStatus);
}
}
if (s.dnum >= 2)
{
status newStatus = new status(s.pnum, s.dnum - 2, false);
if (newStatus.isLegal())
{
s.children.Add(newStatus);
afterLegal(newStatus);
}
}
if (s.pnum >= 1)
{
status newStatus = new status(s.pnum - 1, s.dnum, false);
if (newStatus.isLegal())
{
s.children.Add(newStatus);
afterLegal(newStatus);
}
}
if (s.dnum >= 1)
{
status newStatus = new status(s.pnum, s.dnum - 1, false);
if (newStatus.isLegal())
{
s.children.Add(newStatus);
afterLegal(newStatus);
}
}
if (s.pnum >= 1 && s.dnum >= 1)
{
status newStatus = new status(s.pnum - 1, s.dnum - 1, false);
if (newStatus.isLegal())
{
s.children.Add(newStatus);
afterLegal(newStatus);
}
}
}
else
{
if(s.pnum <= 1)
{
status newStatus = new status(s.pnum + 2, s.dnum, true);
if (newStatus.isLegal())
{
s.children.Add(newStatus);
afterLegal(newStatus);
}
}
if(s.dnum <= 1)
{
status newStatus = new status(s.pnum, s.dnum + 2, true);
if (newStatus.isLegal())
{
s.children.Add(newStatus);
afterLegal(newStatus);
}
}
if(s.pnum <= 2)
{
status newStatus = new status(s.pnum + 1, s.dnum, true);
if (newStatus.isLegal())
{
s.children.Add(newStatus);
afterLegal(newStatus);
}
}
if(s.dnum <= 2)
{
if(s.pnum == 0 && s.dnum == 0)
{
}
else
{
status newStatus = new status(s.pnum, s.dnum + 1, true);
if (newStatus.isLegal())
{
s.children.Add(newStatus);
afterLegal(newStatus);
}
}
}
if(s.pnum <= 2 && s.dnum <= 2)
{
status newStatus = new status(s.pnum + 1, s.dnum + 1, true);
if (newStatus.isLegal())
{
s.children.Add(newStatus);
afterLegal(newStatus);
}
}
}
}
graph = new List<status>(appendGraph);
Graph.AddRange(appendGraph);
} while (appendGraph.Count != 0);
/*
Debug.Log(Graph.Count);
foreach (status s in Graph)
{
Debug.Log(s.pnum);
Debug.Log(s.dnum);
Debug.Log(s.boat);
}
*/
}
public void afterLegal(status newStatus)
{
bool index = true;
foreach (status s2 in Graph)
{
if (s2.equals(newStatus))
{
index = false;
break;
}
}
foreach(status s2 in appendGraph)
{
if(s2.equals(newStatus))
{
index = false;
break;
}
}
if (index)
appendGraph.Add(newStatus);
}
寻找最短路径到达最终状态
思路就是用迪杰斯特拉算法就行,然后把每个状态对应的最短路径保存到新的邻接表中
public void initShortestPath()
{
int[] dis = new int[Graph.Count];
status src = Graph[Graph.Count - 1];
for(int i = 0; i < dis.Length; i++)
{
dis[i] = 9999999;
List<status> pa = new List<status>();
pa.Add(src);
path.Add(pa);
}
dis[Graph.IndexOf(src)] = 0;
foreach(status s in src.children)
{
dis[Graph.IndexOf(s)] = 1;
}
visited.Add(src);
while(visited.Count != Graph.Count)
{
status shortest = rootstatus;
int shortdis = 99999999;
for(int i = 0; i < dis.Length; i++)
{
if(shortdis > dis[i] && notVisit(Graph[i]))
{
shortest = Graph[i];
shortdis = dis[i];
}
}
visited.Add(shortest);
foreach(status s in shortest.children)
{
if(notVisit(s) && dis[Graph.IndexOf(s)] > dis[Graph.IndexOf(shortest)] + 1)
{
dis[Graph.IndexOf(s)] = dis[Graph.IndexOf(shortest)] + 1;
path[Graph.IndexOf(s)].Clear();
path[Graph.IndexOf(s)].AddRange(path[Graph.IndexOf(shortest)]);
path[Graph.IndexOf(s)].Add(shortest);
}
}
}
/*
for (int i = 0; i < dis.Length; i++)
{
string str = "";
str += Graph[i].pnum.ToString() + "P" + Graph[i].dnum.ToString() + "D" + Graph[i].boat.ToString() + " " + dis[i].ToString();
Debug.Log(str);
}
*/
foreach(List<status> ls in path)
{
ls.Reverse();
}
/*
foreach(List<status> ls in path)
{
string str = "";
foreach (status s in ls)
{
str += s.pnum.ToString() + "P" + s.dnum.ToString() + "D" + s.boat.ToString() + "b ->";
}
Debug.Log(str);
}
*/
}
public bool notVisit(status other)
{
foreach(status s in visited)
{
if (s.equals(other))
return false;
}
return true;
}
状态移动
代码如下,next是提供给IUserAction的接口函数,在移动的过程中,为了避免上船下船动作和船移动的同时执行,还有避免一个过程还没结束再次点击Next下一个状态的情况,使用了协程来等待。
public void next()
{
status current = findCurrentStatus();
status nextStatus = path[Graph.IndexOf(current)][0];
StartCoroutine(moveFromStatus(current, nextStatus));
cnext = false;
}
public bool canNext()
{
return cnext;
}
private int frame = 70;
IEnumerator moveFromStatus(status first, status second)
{
if(boat.get_pcount() > 0 || boat.get_dcount() > 0)
{
int Num = boat.get_pcount() + boat.get_dcount();
for (int i = 0; i < Num; i++)
{
for (int j = 0; j < frame; j++)
{
yield return null;
}
movePD(boat.getPD());
}
for (int j = 0; j < frame; j++)
{
yield return null;
}
}
if(first.boat)
{
int pnum = first.pnum - second.pnum;
int dnum = first.dnum - second.dnum;
if(pnum > 0)
{
for (int i = 0; i < pnum; i++)
{
movePD(right_coast.getOneP());
}
}
if(dnum > 0)
{
for (int i = 0; i < dnum; i++)
{
movePD(right_coast.getOneD());
}
}
}
else
{
int pnum = second.pnum - first.pnum;
int dnum = second.dnum - first.dnum;
if(pnum > 0)
{
for (int i = 0; i < pnum; i++)
{
movePD(left_coast.getOneP());
}
}
if (dnum > 0)
{
for (int i = 0; i < dnum; i++)
{
movePD(left_coast.getOneD());
}
}
}
for (int i = 0; i < frame; i++)
{
yield return null;
}
moveBoat();
int boatPDNum = boat.get_pcount() + boat.get_dcount();
for (int i = 0; i < boatPDNum; i++)
{
for (int j = 0; j < frame; j++)
{
yield return null;
}
movePD(boat.getPD());
}
for (int j = 0; j < frame; j++)
{
yield return null;
}
cnext = true;
}