-
需求分析
-
軟件範圍陳述
這是一個八數碼遊戲,用戶可以隨機產生一組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
}
}