8数码实现

版权声明:本文为博主原创文章,转载注出处! https://blog.csdn.net/qq_25944759/article/details/70159715

采用了 A* 算法(不在位的将牌的距离和,和深度 的和做 函数值进行优先搜索)这是第一个纯自己码的分享一下 欢迎批评指正!

package com.zyl;
import java.util.*;
/**
 * 
 * @author zhaoYuLong
 * :QQ 406244471
 * @version 1.0
 * 
 */
public class Shuma {
final static int N = 3 ; //保存数组大小
int kk = 0;//保存 遍历路径表的个数 避免数组越界
int y = 0 ;//保存 解的成功与否
ArrayList<Integer> s ; // 初始状态
ArrayList<Integer> g ; // 目标状态
ArrayList<Xmap> open ;  // 待扩展节点
ArrayList<Xmap> close ;  // 已扩展节点
ArrayList<Xmap> luJing ;  // 最优路径节点
ArrayList<Integer> csz ; // 初始节点的 father

int yu = 0 ;


int [][] g1 = {{1,2,3},{8,0,4},{7,6,5}};// 奇偶 逆序和 对应的两个二维数组(0代表空格)
int [][] g2 = {{1,2,3},{4,5,6},{7,8,0}};
int [][] g0 = {{0,0,0},{0,0,0},{0,0,0}}; ; // 确认之后的目标数组
int [][] s0 = {{0,0,0},{0,0,0},{0,0,0}}; // 生成的起始随机数组


public Shuma() {
// 完成open表等值的初始化
this.init();
}

// 初始化 开始状态 、open表初始化、 起始数组  和  目标状态、目标数组
public void init (){
csz = new ArrayList<Integer>();
s = new ArrayList<>();
g = new ArrayList<Integer>();
open = new ArrayList<Xmap>();
close = new ArrayList<Xmap>();
int yuShu = 0;

change(csz,s0);

// 初始状态
for(int i = 0 ; i < N*N ; i++) {
s.add(i);
}
Collections.shuffle(s);// 打乱
changeA(s0,s);


// 目标状态
yuShu = niXu(s);
yu = yuShu ;
// 对不同初始状态设定目标值 同时初始化open表
//奇
if(yuShu == 1) {
change(g,g1); 
g0 = g1;
Xmap map = new Xmap();
map.arr.addAll(s);
map.p = compDis(g1,s0);
map.deep = 0;
map.f = map.p + map.deep ;
map.dir = "初始图";
map.father = csz ;
open.add(map);
// 求逆序和
}
//偶
else
{
change(g,g2);
g0 = g2;
Xmap map = new Xmap();
map.arr.addAll(s);
map.p = compDis(g1,s0);
map.deep = 0;
map.f = map.p + map.deep ;
map.father = csz ;
open.add(map);
}
}
// 求逆序和
public int niXu(ArrayList<Integer> as) {
int count = 0 ;
int yuShu = 0 ;
for(int i = 0 ; i < s.size() ; i++) {
for(int j = 0 ; j < i ; j++) {
if(((int)as.get(j) > (int)as.get(i))&&(int)as.get(i)!=0)  {
count++;
}
}
}
yuShu = count % 2 ;
return yuShu;
}
// 将ArrayList 变成数组
public void changeA (int[][] a , ArrayList<Integer> arr) {
int num = 0 ;
for(int i = 0 ; i < N ; i++) {
for(int j = 0 ; j < N ; j++) {
a[i][j] =  (int)arr.get(num) ;
num++ ;
}

}
}

// 将数组变成ArrayList 
public void change ( ArrayList<Integer> arr,int[][] a ) {
for(int i = 0 ; i < N ; i++) {
for(int j = 0 ; j < N ; j++) {
arr.add(a[i][j]) ;
}

}
}
// 计算不在位将的距离和
public int compDis ( int[][] a,int[][] b ) {
int count = 0 ;
for(int i = 0 ; i < N ; i++) {
for(int j = 0 ; j < N ; j++) {
if(a[i][j] != b[i][j] && a[i][j] != 0) {
count += Math.abs(getX(a[i][j],b) - getX(a[i][j],a)) + Math.abs(getY(a[i][j],b) - getY(a[i][j],a)) ;
}
}
}
return count ;
}
// 返回某数的坐标的X值
public int getX ( int c,int[][] d ) {
int x = 0 ;
for(int i = 0 ; i < N ; i++) {
for(int j = 0 ; j < N ; j++) {
if(d[i][j] == c) {
x = i ;
}
}
} 
return x ;
}
// 返回某数的坐标的Y值
public int getY ( int c,int[][] d ) {
int y = 0 ;
for(int i = 0 ; i < N ; i++) {
for(int j = 0 ; j < N ; j++) {
if(d[i][j] == c) {
y = j ;
}
}
} 
return y ;
}


// 完成空格满足规则的移动 并且之后将扩展的节点初始化 然后加入 open表中
public void move(Xmap abc) {
// 将传入的参数的 list 转化为数组 便于后面的比较使用
int [][] a = new int[3][3];
changeA(a,abc.arr);


// 获得 0(空格)所在的坐标
int x,y ;
x = getX(0,a);
y = getY(0,a);


// 定义4个 list 来完成移动后的数据保存(由于修改一次后后面在移动就不能用了所以定义4个)
ArrayList<Integer> list0 = new ArrayList <>() ;
ArrayList<Integer> list1 = new ArrayList <>() ;
ArrayList<Integer> list2 = new ArrayList <>() ;
ArrayList<Integer> list3 = new ArrayList <>() ;
list0.addAll(abc.arr);
list1.addAll(abc.arr);
list2.addAll(abc.arr);
list3.addAll(abc.arr);

// 移动:利用坐标的范围来过滤不合法的移动规则
// 左移
if( (y-1) > -1) {
// 定义一个二维数组用于保存移动后的数据图
int [][] r0 = new int[3][3];
Xmap map0 = new Xmap();

// 利用图形的排列规律 将0所在序号和序号减一的交换即可完成左移 (以下同理)
Collections.swap(list0, list0.indexOf(0), list0.indexOf(0)-1);

// 把list转换成数组用于比较
changeA(r0,list0);

// 遍历open表和close表 保证没有重复的状态加入
boolean bool1 = true ;
for(Xmap m : close){
int [][] ar = new int [3][3];
changeA(ar, m.arr);
if(equals(ar,r0)) {
bool1 = false;
} 
}
for(Xmap m : open){
int [][] ar = new int [3][3];
changeA(ar, m.arr);
if(equals(ar,r0)) {
bool1 = false;
} 
}
// 如果没有相同状态的则将其加入 并完成初始化
if(bool1) {
map0.arr.addAll(list0) ; // 状态图赋值保存(利用了addAll()来拷贝)
map0.deep = abc.deep+1; // 利用父节点的节点深度加1即可得扩展节点的深度值(开始定义初始节点的深度为0)
map0.p = compDis(r0,g0); // 距离和计算并赋值保存
map0.f = map0.p + map0.deep ;// f值为所有不在位的将牌的距离和加节点所处深度的和
map0.dir = "左移"; // 保存父节点到他的移动方向
map0.father = abc.arr ; //保存父节点的图状态信息  用于后面找父节点
open.add(map0); // 加入open 表完成单方向扩展

}
}
// 右移
   if((y+1) < 3 ) {
    int [][] r1 = new int[3][3];
    Xmap map1 = new Xmap();
    Collections.swap(list1, list1.indexOf(0), list1.indexOf(0)+1);
    changeA(r1,list1);

boolean bool1 = true ;
for(Xmap m : open){
int [][] ar = new int [3][3];
changeA(ar, m.arr);
if(equals(ar,r1)) {
bool1 = false;
} 
}
for(Xmap m : close){
int [][] ar = new int [3][3];
changeA(ar, m.arr);
if(equals(ar,r1)) {
bool1 = false;
} 
}
if(bool1) {
map1.arr.addAll(list1) ;
map1.deep = abc.deep+1;
map1.p = compDis(r1,g0);
map1.f = map1.p + map1.deep ;
map1.dir = "右移";
map1.father = abc.arr;
open.add(map1);
}
}
    // 上移
   if((x-1) > -1) {
    int [][] r2 = new int[3][3];
    Xmap map2 = new Xmap();
    Collections.swap(list2, list2.indexOf(0), list2.indexOf(0)-3);
    changeA(r2,list2);
boolean bool1 = true ;
for(Xmap m : open){
int [][] ar = new int [3][3];
changeA(ar, m.arr);
if(equals(ar,r2)) {
bool1 = false;
} 
}
for(Xmap m : close){
int [][] ar = new int [3][3];
changeA(ar, m.arr);
if(equals(ar,r2)) {
bool1 = false;
} 
}
if(bool1) {
map2.arr.addAll(list2) ;
map2.deep = abc.deep+1;
map2.p = compDis(r2,g0);
map2.f = map2.p + map2.deep ;
map2.dir = "上移";
map2.father = abc.arr ;
open.add(map2);
}
}
    // 下移
   if((x+1) < 3 ) {
    int [][] r3 = new int[3][3];
    Xmap map3 = new Xmap();
    Collections.swap(list3, list3.indexOf(0), list3.indexOf(0)+3);
    changeA(r3,list3);

boolean bool1 = true ;
for(Xmap m : open){
int [][] ar = new int [3][3];
changeA(ar, m.arr);
if(equals(ar,r3)) {
bool1 = false;
} 
}
for(Xmap m : close){
int [][] ar = new int [3][3];
changeA(ar, m.arr);
if(equals(ar,r3)) {
bool1 = false;
} 
}
if(bool1) {
map3.arr.addAll(list3) ;
map3.deep = abc.deep+1;
map3.p = compDis(r3,g0);
map3.f = map3.p + map3.deep ;
map3.dir = "下移";
map3.father = abc.arr ;
open.add(map3);
}
    }

}

// 游戏的开始方法
public boolean play() {
boolean bl = true ;
while(bl) {
// 1.判断open表是否为空? 为空则输出“fail”  否 则继续
if(open.isEmpty()) {
y = -1 ;
Xframe.text.append("fail\n");
break;
}

// 2.将open表中的第一个状态对象赋值给map1
Xmap map1 = new Xmap();
map1 = open.get(0);
// 2.1将第一个的状态图也就是list 转化为二维数组( 后面要调用方法比较)
int [][] temp = new int[3][3];
changeA(temp,map1.arr);

// 3.判断该状态图是否为目标值? 是则输出“success”  否 则继续 
if(equals(temp,g0)){
y = 1 ;
break;
}

// 4.将第一个节点从open表中删除
open.remove(0);

// 5.将第一个节点加入至close表中
close.add(map1);

// 6.扩展节点
move(map1);

// 7.对open表根据f值升序排序(f=p+deep (所有不在位的将牌的距离和+节点所处深度))
   // 并且f值相同的节点 先加入的排在前面
open.sort((o1,o2)->((Xmap)o1).f - ((Xmap)o2).f);

// 重绘数据显示的9宫格
Xframe.paint(this);
}
//while的一个循环 只有当 open表为空也就是遍历了所有结果没找到目标值 和成功
//找到目标值时 才退出循环

return true;
}




// 用于比较两个数组是否相同坐标所对应的值相同
public boolean equals(int[][] aa , int [][] bb) {
boolean bool = true ;
for(int i = 0 ; i < N ; i++) {
for(int j = 0 ; j < N ; j++) {
if(aa[i][j] != bb[i][j]) {
bool = false ;
}
}
} 
return bool;
}

// 输出初始信息 
public void infoFirst() {
 Xframe.text.append("    -------------------------------------- 8数码游戏  ---------------------------------------  \n\n");
   
   // 输出初始状态(仅用于检测使用)
 Xframe.text.append("  初始状态:          (0代表空格)");
   if(yu == 1) {
    Xframe.text.append("           (逆序和为奇)\n");
   }
   else
   {
    Xframe.text.append("           (逆序和为偶)\n");
   }
for(int i = 0 ; i < N ; i++) {
Xframe.text.append("   "+Arrays.toString(s0[i])+"\n");
}
// 完成初始状态的 list 表转化为二维数组
       int[][] aa = new int[3][3]; 
       changeA(aa, g);
       Xframe.text.append("\n");
       
       // 输出目标状态(仅用于检测使用)
       Xframe.text.append(" 目标状态:\n");
       for(int i = 0 ; i < N ; i++) {
        Xframe.text.append("  "+Arrays.toString(aa[i])+"\n");
}
       Xframe.text.append("\n");
       Xframe.text.append("初始状态到目标状态的不在位将牌距离和:");
       // 输出初始状态到目标状态的不在位将牌距离和(仅用于检测使用)
       
       Xframe.text.append(compDis(s0, aa)+"\n");
       Xframe.text.append("\n");
}

// 用于输出扩展节点个数和存储结点个数(结束信息)
public void infoLast() {
Xframe.text.append(" 扩展节点个数:"+close.size()+"\n");
Xframe.text.append(" 存储节点个数:"+(open.size()+close.size())+"\n");
Xframe.text.append(" 最短路径:"+open.get(0).deep+"\n");
Xframe.text.append("\n");
        path(close);
}


// 输出初始状态到目标状态的路径图
public void path (ArrayList<Xmap> app ) {
luJing = new ArrayList<Xmap>();
luJing.add(open.get(0));
int[][] a = new int[N][N] ;
int[][] b = new int[N][N] ;
boolean bool = true ;
while(bool) {
int[][] tem = {{0,0,0},{0,0,0},{0,0,0}}; 
    for(int i = 0 ; i < app.size() ;i++) {
changeA(a,app.get(i).arr);
changeA(b,luJing.get(luJing.size()-1).father);
if(equals(a,b)) {
luJing.add(app.get(i));
}

if(equals(tem,b)) {
bool = false ;
}
}
}
Collections.reverse(luJing);

Xframe.text.append("    --------------------------------------- 路径图   -----------------------------------------        (0 代表空格)\n");
for(int i = 0 ; i < luJing.size() ; i++) {
Xframe.text.append(" "+i+": "+luJing.get(i).dir+"\n");
Xframe.text.append("");
for(int j = 0 ; j < N*N ; j++) {
Xframe.text.append(luJing.get(i).arr.get(j)+"  ");
if((j+1)%3 == 0 && j!=(N*N-1)) {
Xframe.text.append("\n");
Xframe.text.append("");
}
}
Xframe.text.append("\n");

}
Xframe.text.append("\n");
Xframe.text.append("    --------------------------------------- 游戏结束  -----------------------------------------  \n");
}


// 输出详细步骤
public  void mq () {
this.infoFirst();
Xframe.text.append("    ------------------------------------- 开始游戏  -----------------------------------------  \n");
Xframe.text.append("\n");
Xframe.text.append(" 遍历结果统计:");
        // 开始游戏
    this.infoLast();
} 
}







package com.zyl;


import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;


import javax.swing.*;


public class Xframe {
// 定义所需变量
JFrame frame ; //窗体
JButton start ; //开始按钮
JButton reset ; //重置按钮
JButton hand; //最优路径展示按钮
JLabel zhuangTai ; //状态标签
JLabel author ; //作者标签
JPanel card1 ; //游戏面板
JPanel card2 ; //详细步骤面板
    JPanel panel9 ; //9宫格
JTabbedPane tab; //分页面板
static JTextArea text ; //输出详细步骤文本
String str1 = "游戏界面" ; //
String str2 = "详细步骤" ; //
    Shuma mm ; //定义一个 数码对象
static ArrayList <JLabel> lab ; //保存状态图信息的 标签集合
static ArrayList<JPanel> pan ; //用于填充9宫格的面板

// 构造方法
public Xframe() {
// 调用初始界面方法
this.init();
}

// 初始化界面
public void init () {
// 创建所需对象
mm = new Shuma() ;
hand = new JButton("最优路径展示");
zhuangTai  = new JLabel("状态:初始状态");
lab = new ArrayList<JLabel>();
pan = new ArrayList<JPanel>();



// 创建窗体
frame = new JFrame("8数码--启发式宽度优先搜索") ;
frame.setLocation(400,100);// 大小
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 

// 游戏界面
card1 = new JPanel(); //游戏面板
card1.setPreferredSize(new Dimension(500,500));//大小
JPanel a = new JPanel();//填充面板
a.setPreferredSize(new Dimension(1500,70));//大小
card1.add(a);//添加
panel9 = new JPanel();//9 宫格
panel9.setPreferredSize(new Dimension(300,300));//大小
panel9.setLayout(new GridLayout(3,3));//3*3布局
panel9.setBorder(BorderFactory.createBevelBorder(1));//边框


csh();//完成9宫格初始化
card1.add(panel9) ;//添加

JPanel a1 = new JPanel();//填充面板  用于排版
a1.setPreferredSize(new Dimension(1500,40));//大小
card1.add(a1);//添加

JPanel a2 = new JPanel();//填充面板  用于排版
a2.setPreferredSize(new Dimension(1300,20));//大小
card1.add(a2);//添加

start = new JButton("开始(自动)");//开始按钮
start.addActionListener(new ActionListener() {// 监听


@Override
public void actionPerformed(ActionEvent e) {
// TODO 自动生成的方法存根
zhuangTai.setText("状态:拼命运算中");  // 状态改变
mm.play();                     // 开始运算
if(mm.y == 1) {
mm.mq();        // 将详细步骤输出到文本域
text.setCaretPosition(0);
zhuangTai.setText("状态:成功");   // 状态改变
}
if(mm.y == -1)
{
zhuangTai.setText("状态:失败");
}

};

});


reset = new JButton("重置");   // 重置按钮
reset.addActionListener(new ActionListener() {   // 监听


@Override
public void actionPerformed(ActionEvent e) {
// TODO 自动生成的方法存根
text.setText(null);
mm = new Shuma();
paint(mm);
zhuangTai.setText("状态:已重置");

};

});

card1.add(start);  // 添加
JPanel a5 = new JPanel();  // 排版面板
a5.setPreferredSize(new Dimension(30,20));
card1.add(a5);
hand.addActionListener(new ActionListener() {  // 最优路径 按钮监听


@Override
public void actionPerformed(ActionEvent e) {
if(mm.kk == 0 && mm.luJing.size() != 0) {           // 判断是否为第一个
zhuangTai.setText("状态:已恢复至初始位置");
}

else  // 判断是否为中间节点
if(mm.kk < mm.luJing.size()-1) {
zhuangTai.setText("状态:第"+mm.kk+"次移动("+mm.luJing.get(mm.kk).dir+")");
}
else   // 判断是否为目标节点
if(mm.kk == mm.luJing.size()-1) {
zhuangTai.setText("状态:成功(共移动"+mm.kk+"次)");
}
// TODO 自动生成的方法存根
if(mm.y == 1 && mm.kk < mm.luJing.size()) {  // 完成每一步移动的显示
for(int i = 0 ; i < 9 ; i++ ) {
if((int)mm.luJing.get(mm.kk).arr.get(i) == 0) {
Xframe.lab.get(i).setText(null);
}
else
{
Xframe.lab.get(i).setText(Integer.toString(mm.luJing.get(mm.kk).arr.get(i)));
}

}
mm.kk++;

}
};

});
card1.add(hand);

JPanel a3 = new JPanel();  // 排版面板
a3.setPreferredSize(new Dimension(70,20));
card1.add(a3);

card1.add(reset);

JPanel a4 = new JPanel();  // 排版面板
a4.setPreferredSize(new Dimension(1500,30));
card1.add(a4);
card1.add(zhuangTai);

JPanel a6 = new JPanel();  // 排版面板
a6.setPreferredSize(new Dimension(3000,50));
card1.add(a6);
JPanel a7 = new JPanel();  // 排版面板
a7.setPreferredSize(new Dimension(600,5));
card1.add(a7);
author = new JLabel("Author:zhaoYuLong");
card1.add(author);



// 详细步骤界面
card2 = new JPanel();  // 详细步骤面板
card2.setPreferredSize(new Dimension(500,500));  // 大小
text = new JTextArea(37,50);  // 文本域大小
text.setEditable(false);  // 设置为不可编辑
text.setLineWrap(true);  // 自动换行
text.setWrapStyleWord(true);   // 已完整单词换行
JScrollPane p = new JScrollPane(text);  // 加入滚动条面板
card2.add(p) ;  // 添加


// 将两个界面 加入 JTabbedPane
tab = new JTabbedPane(JTabbedPane.TOP,JTabbedPane.WRAP_TAB_LAYOUT);
   tab.addTab(str1,card1);
   tab.addTab(str2,card2);

   // 将JTabbedPane加入窗体
frame.add(tab) ;

frame.setExtendedState(Frame.MAXIMIZED_BOTH);   // 最大化显示
frame.setVisible(true);// 显示

}

 //完成图信息保存至JLabel 集合
public static void labC (Shuma mm ,ArrayList<JLabel> lab ) {
for(int i = 0 ; i < 9 ; i++) {
if((int)mm.open.get(0).arr.get(i) == 0) {
lab.add(new JLabel());
}
else
{
lab.add(new JLabel(Integer.toString(mm.open.get(0).arr.get(i))));
}

}
}

// 将9宫格中添加 9 个3*1的面板 
public static void panC (ArrayList<JPanel> p ) {
for(int i = 0 ; i < 9 ; i++) {
JPanel pp = new JPanel();
pp.setLayout(new GridLayout(3, 1));;
pp.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1)); 
p.add(pp);
}
}

// 将保存图信息的 JLabel 利用 排版面板 显示在格子中央
public static void pan (ArrayList<JLabel> lab ,ArrayList<JPanel> p ,JPanel panel) {
for(int i = 0 ; i < 9 ; i++) {
for(int j = 0 ; j < 3 ; j++) {
JPanel pp = new JPanel();
if(j == 1) {
pp.add(lab.get(i));
}
p.get(i).add(pp);
}
panel.add(p.get(i));
}
}

// 将9宫格带数据完成初始化
public  void csh () {
labC(mm,lab);
panC(pan);
pan(lab,pan,panel9);
}

// 重置9宫格数据
public static void paint(Shuma mm) {
for(int i = 0 ;i < 9 ; i++) {
if((int)mm.open.get(0).arr.get(i) == 0) {
lab.get(i).setText(null);
}
else
{
lab.get(i).setText(Integer.toString(mm.open.get(0).arr.get(i)));
}

}
}

}







package com.zyl;


import java.util.*;
// 为了方便保存状态图 和 启发函数值  定义了单独 的一个类
public class Xmap {
public ArrayList<Integer> arr = new ArrayList<>(); // 状态图用一个 ArrayList 集合表示
//(由于他方便遍历和可以使用Collections 工具类来洗牌)
public ArrayList<Integer> father = new ArrayList<>(); // 保存父节点的 图
public int deep; // 保存节点深度
public int p;  // 保存不在位将牌的距离和
public int f; // 保存启发函数值
public String dir = new String();// 保存父节点到该节点空格移动方向
} 





package com.zyl;


public class Main {
    // 主方法
public static void main(String[] args) {
// TODO 自动生成的方法存根
// 使用无参构造函数生成Xframe匿名对象
new Xframe();


}


}

工程文件 :链接:http://pan.baidu.com/s/1kVdqZxd 密码:a74r

猜你喜欢

转载自blog.csdn.net/qq_25944759/article/details/70159715