-
需求分析
-
软件范围陈述
这是一个八数码游戏,用户可以随机产生一组1~8的数字显示在棋盘上,然后可以选择自己玩,也可以让计算机计算出最短路径,显示出移动步骤和访问节点个数并可以选择自动演示。用户可以选择让计算机用广度优先与A*算法计算最短路径。
-
软件功能描述
-
随机产生8个不同的数字
-
用户可以通过点击来移动棋盘数字
-
使用广度优先计算最短路径
-
使用A*算法计算最短路径
-
计算后显示最短路径移动步骤
-
自动演示移动步骤
-
可以手动选择观看最短路径步骤中的某个步骤
-
-
软硬件环境
硬件环境:
CPU:PII400MHZ以上
内存:128MB以上
分辨率:800*600以上
软件环境:
.NET Framework 2.0以上的平台
-
系统设计
-
系统总体设计:
总体UML图
-
系统详细设计
结点定义类:
枚举类型定义
产生随机数的类 和 A*算法用的自动排序OPEN表
核心算法类,包括广度优先和A*算法
扩展最大深度
总体结构与函数的作用在截图中的已经指明,现在分析一下核心算法
广度优先算法:
通过两个函数来完成,一个是扩展
扩展函数中先判断结点是否打到目标,达到目标则返回,否是则若位访问过结点就放入open表中。
broad函数汇总调用扩展函数,将每个结点扩展,直到它返回打到目标。
A*算法:
在广度优先的基础上,加入的估价函数,使用的是求出没个不同的数字距离目标的步数之和加上展开的层数作为估价值。
-
实现
-
随机数模块:
随机产生棋盘,并判断是否有解
-
求解模块
使用广度优先 与 A* 算法 求解,并将结果显示在右侧listBox中
-
手动游戏模块
可以通过点击棋盘,移动数字,来手动玩。
-
演示模块:
主要分为两种,一种是通过点击演示按钮来演示
另一种,通过点击listBox中的步骤,显示某步的棋盘
-
评价
该体统整体功能按要求实现了,较好的完成了老师要求的任务
缺点:界面不够美观,由于时间仓促,制作较为简陋,只实现了老师要求的功能。
-
参考文献
-
判断随机的棋盘是否可解
先复习线性代数中逆序数的概念,举例说明,线性代数书P5页例4,一组数中,如32514,
3排在首位,逆序数为0
2的前面比2打的数有(3),故逆序数为1
5是最大数,逆序数为0;
1的前面比1大的数有三个(3,2,5),故逆序数为3;
4的前面比4大的数有一个(5),故逆序数为1,于是这个排列的逆序数为t=0+1+0+3+1=5
2.通过上面的概念易证明两个状态如果可达,则他们的逆序数的奇偶性相同
- 附录
-
核心代码:
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace eightnum
{
#region 棋盘数据结构
///
/// 空格移动的方向
///
public enum Direction
{
None,
Up,
Left,
Right,
Down
}
///
/// 返回答案
///
public enum Answer
{
///
/// 不存在解答
///
NotExist,
///
/// 存在解答
///
Exist,
///
/// 在当前指定的搜索深度内不存在解答(需要扩大深度)
///
NotExistInDepth
}
///
/// 局面状态信息
///
public class StateMsg
{
///
/// 深度
///
public int depth;
///
/// 方向
///
public Direction dir;
///
/// 局面信息类构造函数
///
/// 深度
/// 方向
public StateMsg(int depth, Direction dir)
{
this.depth = depth;
this.dir = dir;
}
}
///
/// 局面状态
///
public class Node : StateMsg
{
///
/// 棋盘的编码
///
public int code;
///
/// 估计值
///
public int evalue;
///
/// 指向上一层结点
///
public Node parentNode;
///
/// 是否达到目标
///
public bool Goal;
///
/// 构造函数,初始化结点
///
/// 一个用整形表示的棋盘
/// 估价值
/// 深度
/// 方向
/// 父节点
public Node(int code,int evalue, int depth, Direction dir,Node n,bool goal)
: base(depth, dir)
{
this.code = code;
this.evalue = evalue;
parentNode = n;
Goal = goal;
}
}
public class SortedArrayList : ArrayList
{
///
/// 估价值
///
public const int EVALUE = 1;
///
/// 棋盘值
///
public const int CODE = 2;
///
/// 加入结点并排序
///
public bool AddSorted(Node n,int MODE)
{
if (MODE == EVALUE)
{
int i, j;
for (i = 0, j = Count - 1; i <=j; )
{
if (n.evalue == ((Node)this[(i + j) / 2]).evalue)
{
Insert((i + j) / 2, n);
return true ;
}
else if (n.evalue > ((Node)this[(i + j) / 2]).evalue) i = (i + j) / 2 + 1;
else j = (i + j) / 2 - 1;
}
Insert(i, n);
return true;
}
else
{
int i, j;
for (i = 0, j = Count - 1; i <=j; )
{
if (n.code == ((Node)this[(i + j) / 2]).code)
{
return false;
}
else if (n.code > ((Node)this[(i + j) / 2]).code) i = (i + j) / 2 + 1;
else j = (i + j) / 2 - 1;
}
Insert(i, n);
return true;
}
}
}
#endregion
class AIcores
{
#region 常量定义与非核心变量
///
/// 最大深度
///
private int MaxDepth;
///
/// 至多搜索的结点数=最大局面状态数量:(9!)=362880;
///
private const int maxNodes = 362880;
///
/// ten[i]代表10的i次方
///
private static readonly long[] tens = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
#endregion
///
/// 整个棋盘每个位置可能移动的方向
///
private Direction[][] dirs;
///
/// 开始棋盘
///
private int startBoard;
///
/// 结束棋盘
///
private static int endBoard;
///
/// 结束棋盘数组表示方式
///
private static int[] endBoardArray=new int[9];
///
/// 已访问的结点数
///
public int nodes;
///
/// 重复访问到的相同结点个数
///
private int same;
///
/// A*算法用的OPEN表,自动加入结点后进行排序
///
public SortedArrayList axOpenList = new SortedArrayList();
///
/// visited表,存放访问过的结点
///
public SortedArrayList visitedList = new SortedArrayList();
///
/// 广度优先搜索的OPEN表
///
public Queue broadOpenList=new Queue();
///
/// 储存最终的最短路径
///
public ArrayList result=new ArrayList();
///
/// 构造函数初始化dirs
///
public AIcores()
{
dirs = new Direction[9][];
dirs[0] = new Direction[] { Direction.Right, Direction.Down };
dirs[1] = new Direction[] { Direction.Left, Direction.Right, Direction.Down };
dirs[2] = new Direction[] { Direction.Left, Direction.Down };
dirs[3] = new Direction[] { Direction.Up, Direction.Right, Direction.Down };
dirs[4] = new Direction[] { Direction.Up, Direction.Left, Direction.Right, Direction.Down };
dirs[5] = new Direction[] { Direction.Up, Direction.Left, Direction.Down };
dirs[6] = new Direction[] { Direction.Up, Direction.Right };
dirs[7] = new Direction[] { Direction.Left, Direction.Right, Direction.Up };
dirs[8] = new Direction[] { Direction.Up, Direction.Left };
}
public static Node MoveBlank(Node node, Direction dir)
{
Node newNode=new Node(0,0,node.depth+1,dir,node,false);
int num;
int t0;
long t1;
long t2;
switch (dir)
{
case Direction.Left:
newNode.code = node.code - 1;
if (newNode.code == endBoard)
{
newNode.Goal = true;
return newNode;
}
newNode.evalue = evalueFun(newNode.code,newNode.depth)+node.depth;
return newNode;
case Direction.Right:
newNode.code = node.code + 1;
if (newNode.code == endBoard)
{
newNode.Goal = true;
return newNode;
}//得到目标了
newNode.evalue = evalueFun(newNode.code, newNode.depth) + node.depth;
return newNode;
case Direction.Up:
num = node.code;
t0 = 9 - num % 10 + 1;
t1 = num / tens[t0];
t2 = t1 % 1000;
t1 = t1 - t2 + (t2 % 100) * 10 + t2 / 100;
t1 *= tens[t0];
newNode .code = (int)(t1 + ((num % tens[t0]) - 3));
if (newNode.code == endBoard)
{
newNode.Goal = true;
return newNode;
}
newNode.evalue = evalueFun(newNode.code, newNode.depth) + node.depth;
return newNode;
case Direction.Down:
num = node.code;
t0 = 9 - num % 10 + 1 - 3;//跟Up不同的地方
t1 = num / tens[t0];
t2 = t1 % 1000;
t1 = t1 - t2 + (t2 % 10) * 100 + t2 / 10;//跟Up不同的地方
t1 *= tens[t0];
newNode.code = (int)(t1 + ((num % tens[t0]) + 3));//跟Up不同的地方
if (newNode.code == endBoard)
{
newNode.Goal = true;
return newNode;
}
newNode.evalue = evalueFun(newNode.code, newNode.depth) + node.depth;
return newNode;
default:
return null;
}
}
///
/// 恢复上一步的局面
///
///
///
public static Node MoveBack(Node node, Direction dir)
{
return MoveBlank(node, (Direction)(5 - dir));
}
///
/// 将结果放入resultNode集合中
///
/// 最中的目标结点
public void setResult(Node resultNode)
{
result.Clear();//清空原结果
Node tempNode = resultNode ;
while (tempNode.parentNode != null)
{
result.Add(tempNode);
tempNode = tempNode.parentNode;
}
}
//核心算法
#region A*算法
///
/// 距离
///
/// 要移动的结点
/// 当前位置
///
public static int distance(int t, int pos)
{
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
{
if (t != endBoardArray[i * 3 + j])
return (pos / 3 - i) + (pos % 3 - j);
}
return -1;
}
///
/// 估价函数
///
///
///
public static int evalueFun(int curboard,int dep)
{
int table = curboard;
int emptyPos = curboard % 10;
int ev = 0;
//写2个for是为了减少9个if
for (int i = 9; i > emptyPos; i--)
{
table /= 10;
if (table % 10 != endBoardArray[i - 1])
ev += distance(table % 10, i);
}
for (int i = emptyPos - 1; i >= 1; i--)
{
table /= 10;
if (table % 10 != endBoardArray[i - 1])
ev += distance(table % 10, i);
}
return ev+dep;
}
private int extendAxOpenList()
{
Node openNode = (Node)axOpenList[0];
axOpenList.RemoveAt(0);
int flag = 0;
int emptyPos = openNode.code % 10-1;
// if (!visitedList.AddSorted(openNode, SortedArrayList.CODE))
// return flag;
foreach (Direction dir in dirs[emptyPos])
{
Node tempNode = MoveBlank(openNode, dir);
if (tempNode.Goal == true)
{
setResult(tempNode);
return 2;//达到目标
}
if (tempNode == null) return flag;//结点不能展开
if (visitedList.AddSorted(tempNode, SortedArrayList.CODE))//未访问
{
axOpenList.AddSorted(tempNode, SortedArrayList.EVALUE);
flag = 1;
}
}
return flag;
}
///
/// A*算法
///
/// 初始棋盘状态
/// 目标棋盘状态
/// 扩展最大深度
///
public int Ax(int[,] sCode,int[,] dCode,int maxDepth )
{
nodes = 0;
#region 初始化startboard和endboard
string codeString1="";
string codeString2 = "";
int flag;
int sEmpty=0;
int dEmpty=0;
for(int i=0;i<3;i++)
for (int j = 0; j < 3; j++)
{
if (sCode[i, j] != 0)
{
codeString1 += sCode[i, j].ToString();
}
else
{
sEmpty = i * 3 + j;
}
if (dCode[i, j] != 0)
{
codeString2 += dCode[i, j].ToString();
}
else
{
dEmpty = i * 3 + j;
}
endBoardArray[i * 3 + j] = dCode[i, j];
}
startBoard = int.Parse(codeString1)*10+sEmpty+1;
endBoard = int.Parse(codeString2) * 10 + dEmpty+1;
if( startBoard==endBoard)return 2;
#endregion
MaxDepth = maxDepth;
Node newNode = new Node(startBoard,evalueFun(startBoard,0), 0, Direction.None, null,false);
axOpenList.AddSorted(newNode, SortedArrayList.EVALUE);
visitedList.AddSorted(newNode, SortedArrayList.CODE);
while (axOpenList.Count > 0)
{
if ((flag = extendAxOpenList()) == 1) nodes++;
else if (flag == 2) return 2;//返回成功
}
return -1;
}
#endregion
#region 广度优先算法
///
/// 扩展结点
///
/// 扩展结果
private int extendBroadOpenList()
{
Node openNode = (Node)broadOpenList.Dequeue();
int flag = 0;
int emptyPos = openNode.code % 10 - 1;
foreach (Direction dir in dirs[emptyPos])
{
Node tempNode = MoveBlank(openNode, dir);
if (tempNode.Goal == true)
{
setResult(tempNode);
return 2;//达到目标
}
if (tempNode == null) return flag;//结点不能展开
if (visitedList.AddSorted(tempNode, SortedArrayList.CODE))//未访问
{
broadOpenList.Enqueue(tempNode);
flag = 1;
}
}
return flag;
}
///
/// 广度优先算法
///
/// 初始棋盘状态
/// 目标棋盘状态
/// 扩展最大深度
///
public int broad(int[,] sCode, int[,] dCode, int maxDepth)
{
nodes = 0;
#region 初始化startboard和endboard
string codeString1 = "";
string codeString2 = "";
int flag=0;
int sEmpty = 0;
int dEmpty = 0;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
{
if (sCode[i, j] != 0)
{
codeString1 += sCode[i, j].ToString();
}
else
{
sEmpty = i * 3 + j;
}
if (dCode[i, j] != 0)
{
codeString2 += dCode[i, j].ToString();
}
else
{
dEmpty = i * 3 + j;
}
endBoardArray[i * 3 + j] = dCode[i, j];
}
startBoard = int.Parse(codeString1) * 10 + sEmpty + 1;
endBoard = int.Parse(codeString2) * 10 + dEmpty + 1;
if (startBoard == endBoard) return 2;
#endregion
MaxDepth = maxDepth;
Node newNode = new Node(startBoard, evalueFun(startBoard,0), 0, Direction.None, null, false);
broadOpenList.Enqueue(newNode);
visitedList.AddSorted(newNode, SortedArrayList.CODE);
while (broadOpenList.Count > 0)
{
if ((flag = extendBroadOpenList()) == 1) nodes++;
else if (flag == 2) return 2;//返回成功
}
return flag;
}
#endregion
}
}