拼图小游戏

注册界面 

游戏界面 

登录界面

在本次游戏的GUI开发中,我们将使用Swing包 

这些东西统一称为组件,JFrame是一个组件、JMenuBar也是一个组件、等等

AWT包会有些兼容问题,不支持某些中文

界面搭建

 

创建对象的同时调用空参构造,也就是说构造方法就是创建对象的时候进行初始化的

this就表示new出来的对象,也就是调用者的地址值,也就是给调用者设置一些列参数,说白了就是我自己,就是调用这个构造方法的调用者

游戏窗口 

 游戏窗口我们主要用到Java给我提供的JFrame类,这个类可以给我们提供一个窗口,我们可以对这个窗口设置大小,位置,以及是否显示等等…,下面是界面的样子

我们后面所有的业务逻辑都要在这种窗口里面实现,下面是实现的代码: 

JFrame jf = new JFrame();
jf.setSize(400, 400);
jf.setVisible(true);

JFrame是Java给我提供的一个类,所以我们可以直接用JFrame来创建对象,里面的setSize()方法就是给窗口设置大小,单位都是像素; 这里需要注意的是,不是设置完大小执行就可以出现,JFrame默认隐藏了界面窗口,我们需要用setVisible()方法将他展示出来,true就是展示,相反false就是隐藏句号

所以我们需要创建三个JFrame对象,这里我们需要想一个问题:我们新建一个类文件,然后在main方法里创建三个JFrame对象,然后把所有业务逻辑全部写在main方法里吗? 显而易见结果是NO,到时候一千多行代码全写在一个main方法里吗,到时候出BUG了,在哪都不知道。所以我建议分三个类,即游戏界面, 登录界面, 注册界面这三个类,然后我们在新建一个类用main方法来执行我们的游戏,在main方法里创建三个窗口类的对象用来调用方法,比如设置窗口大小等等。这样我们就可以需要在那个窗口做业务逻辑就直接去相应的类里去做就可以了。大致思路如下:

练习一:创建主界面1

游戏界面:

public class GameJFrame extends JFrame {
    //JFrame表示界面
    //子类也表示界面
    //规定GameFrame表示游戏的主界面
    //以后跟游戏相关的所有逻辑都写在这个类中
    public GameJFrame() {
        //设置界面的宽高
        this.setSize(603, 680);
        //让显示出来,建议写在最后
        this.setVisible(true);
    }
}

 上面是GameFrame(游戏界面)类
这里我用了一个空参构造来进行窗口大小以及展示界面的设置,方便在main方法里我们创建GameJFrame对象时,就会调用GameJFrame空参构造来设置界面,这样就不用我们在main方法里去设置

登录界面:

public class LoginJFrame extends JFrame {
    //LoginJFrame表示登录界面
    //以后所有跟登录有关的代码,都写在这里
 
    public LoginJFrame(){
        //在创建登录界面的时候同时给这个界面去设置一些信息
        //比如 宽高,直接展示出来
        
        this.setSize(488,430);
        this.setVisible(true);
    }
}

注册界面:

public class RegisterJFrame extends JFrame {
    //跟注册相关的代码,都写在这个界面中
 
    public RegisterJFrame(){
        this.setSize(488,500);
        this.setVisible(true);
    }
 
}

主入口:

public class App {
    public static void main(String[] args) {
        //表示程序程序启动的入口
        //如果我们想要开启一个界面,就创建谁的对象就可以了
        //new LoginJFrame();
        //new RegisterJFrame();
        new GameJFrame();
    }
}

大致框架搭建好之后,这样我们只用在对应的类中进行相应的业务逻辑的搭建就好了,我们后面的所有业务逻辑的实现几乎都在这三个窗口中。

注意: this代表这个所在的类,由于前面我们将三个窗口类都继承了JFrame,所以我们可以直接用this调用父类方法,这个父类的方法就是JFrame所有拥有的方法,

 菜单搭建

‘        

1.游戏标题


游戏标题就是我们游戏的名字,一般写在游戏界面的左上方,效果如下图

标题设置用到了JFrame的方法,代码如下:

//设置界面的标题
this.setTitle("拼图游戏单机版 V1.0");

2.游戏主界面置顶

置顶功能想必大家应该很了解了,就是当我们打开其他软件的时候,置顶能够让我们在点击其他应用的时候依然能够显示在最上层,这里我们会用到JFrame里的一个方法

//设置界面置顶
//盖住其他界面
this.setAlwaysOnTop(true);

3.游戏主界面居中

游戏界面居中就是当我们打开游戏的时候,界面始终是在我们电脑屏幕的正中间出现,方法如下

//设置界面居中
this.setLocationRelativeTo(null);

由于这个方法需要传递一个component参数,这里我们只用填一个null就可以了 

4.设置游戏关闭模式

游戏关闭模式,JFrame给我们提供了关闭方法

常用的六个界面设置:

设置界面关闭模式:

0:什么都不做

1:默认模式

2:需要所有界面全部设置才会有效,即最后一个界面关闭时,关闭虚拟机

3:只要关闭其中一个界面就会关闭虚拟机

//设置游戏的关闭模式
this.setDefaultCloseOperation(3);

JAVA中的菜单
java中的菜单,可以通过引入 java.swing来实现。
在java中菜单有如下三大组件:JMenuBar,JMenu,JMenuItem。

JMenuBar是相关的菜单栏,一般一个窗体中有一个就可以了;也就是整个框架栏
JMenu有两种功能,一是在菜单栏中显示,二是当它被加入到另一个JMenu中时,会产生引出子菜单的效果;
JMenuItem是JMenu目录下的菜单。

在我们创建这样的菜单之前,我们必须先了解Java给我们提供的JMenuBar类,JMenu类和JMenuItem类,下面我会用几张图让你了解这三种类有什么关系

了解完这三种类之后,我们需要将这三种类联系起来然后放进GameJFrame,下面是实现的步骤:
1.先创建JMenuBar
2.再创建JMenu
3.再创建JMenuItem
4.把JMenuItem放到JMenu里面
5.把JMenu放到JMenuBar
6.最后把JMenuBar添加到整个JFrame界面中就可以了

 private void initJmenuBar() {
        //创建整个的菜单对象
        JMenuBar jmenuBar = new JMenuBar();
        //创建菜单上面2个选项对象(功能,关于我们)
        JMenu funJmenu = new JMenu("功能");
        JMenu aboutJmenu = new JMenu("关于我们");
        //创建选项下面的条目对象
        JMenuItem replyItem = new JMenuItem("重新游戏");
        JMenuItem reLoginItem = new JMenuItem("重新登录");
        JMenuItem closeItem = new JMenuItem("关闭游戏");

        JMenuItem accountItem = new JMenuItem("我的微信号");

        //将每一个选项下面的条目添加在这个选项中
        funJmenu.add(replyItem);
        funJmenu.add(reLoginItem);
        funJmenu.add(closeItem);

        aboutJmenu.add(accountItem);

        //将菜单下的2个选项添加在菜单中
        jmenuBar.add(funJmenu);
        jmenuBar.add(aboutJmenu);

        //给整个界面设置菜单 也就是把JMenuBar添加在整个Jframe界面当中
        this.setJMenuBar(jmenuBar);
    }

菜单的使用

以下的代码,将在窗体中添加一个菜单栏。同时有“新建”与“哪里”两项内容,在“哪里”中,又有两个子目录

import java.awt.*;
import javax.swing.*;
 
public class abc {
 
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		JFrame frm=new JFrame("This is a test!");    //实例化一个窗体
		frm.setSize(200, 200);                      
		frm.setLocation(150, 150);                   
		
		JMenuBar mb=new JMenuBar();                 //实例菜单栏
		JMenu newj=new JMenu("新建");                //实例一个菜单项
		JMenu oldj=new JMenu("哪里");
		JMenuItem mi1=new JMenuItem("一个");         //实例子目录
		JMenuItem mi2=new JMenuItem("例子");
		
		frm.setJMenuBar(mb);                        //设置菜单栏
		mb.add(newj);                               //添加菜单项
		mb.add(oldj);
		oldj.add(mi1);                              //添加子目录
		oldj.add(mi2);
		
 
		frm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
		frm.setVisible(true);                                
	}
 
}

 

添加图片 

imagelcon是Java中用于表示图像图标的类

 imagelcon传递文件的地址即可

一张图片对应一个imagelcon的对象,我们不能直接把imagelcon对象直接添加在initJFrame对象中,因为我们还要给图片进行大小设置.我们要先把图片放在一个JLabel管理区域当中

JLabel相当于管理者,管理图片和文字

首先 创建imagelcon的对象,并指定图片在电脑中的位置,然后创建一个JLabel的对象,把图片交给JLabel,在把这个整体放在界面当中

 为什么加了限定图片的位置代码,图片还是在中间呢(默认在中间)

其实有Frame有一个隐藏的容器,只是个大的架子

隐藏的容器不需要我们自己创建对象,因为是JFrame里面的东西,继承了JFrame的对象的时候,这个容器就可以提供get方法获取

添加图片

窗口和菜单大致搭建完之后,我们现在可以把图片试着添加到主窗口中了,由于游戏窗口都是GameFrame窗口内,所以后面的业务逻辑也都是在GameFrame类中完成的
添加图片之前,我们重新了了解一下游戏窗口

这里的游戏窗口实际上是分为三部分的
1.标题部分
2.菜单部分
3.隐藏容器部分
这里的隐藏容器部分是当我们继承了JFrame对象的时候,已经存在的,所以我们不需要重新创建对象,我们可以直接用this.getContentPane()方法调用就可以了,再用它去调用add()方法添加图片进去就可以了,如果没有特殊要求,它会默认的放置在正中央,如果我们想要放置其他位置,就必须要把其默认开关给关闭,关闭我们可以用下面的方法

//取消默认的居中放置,只有取消了才会按照XY轴的形式添加组件
this.setLayout(null);

由于隐藏容器是在继承了JFrame对象的时候产生的,所以我们可以在initFrame()即初始化界面里使用这个setLayout方法,后面我们就可以将图片以XY轴的方式放置在隐藏容器中,而不会默认放置在正中间

当我们取消了隐藏容器默认居中的方式之后,接下来我们就可以添加图片了

在JFrame中添加图片,不是简单的将图片地址直接放入JFrame中,而是要用到ImageIcon和JLabel类
下面是实现步骤
1.我们要先创建一个ImageIcon对象,就相当于我们要放置的图片,传递的参数可以是图片地址
2.创建一个JLabel类来存储我们的ImageIcon对象也就是我们的图片,这个JLabel是一个管理容器容器,可以存放图片和文字等
3.用JLabel类创建的对象来指定图片的位置,也就是XY轴,这里我们会使用JLabel的setBounds方法,这里我们传递的参数是(x, y, width, height)也就是设置了图片的XY轴和宽高,这里的XY轴不是直角坐标系那个,在JFrame窗口里原点在左上角,下面的图可以让你更好理解

4.最后再把JLabel创建的对象添加到JFrame的隐藏容器中 

下面我们就用代码来实现上面的功能 

  //初始化图片
    /*
    首先 创建ImageIcon的对象,
    并指定图片在电脑中的位置,然后创建一个JLabel的对象,
    把图片交给JLabel,在把这个整体放在界面当中
     */
    private void initImage() {
        //创建一个ImageIcon的对象  imagelcon是Java中用于表示图像图标的类
        ImageIcon i = new ImageIcon("D:\\Java学习\\java-learning\\day25\\image\\girl\\girl1\\2.jpg");
        //创建一个JLabel的对象(管理容器) 然后ImageIcon对象放在管理容器里面
        JLabel j = new JLabel(i);
        //指定图片的位置
        j.setBounds(0, 0, 105,105);
        //把管理容器添加在界面当中
         this.getContentPane().add(j);//先获取隐藏的容器在添加add方法,把管理容器添加在界面当中
        //this.add(j);

    }

然后运行结果

这里我是在GameFrame类里构建了一个方法,然后在GameFrame类的空参构造里调用

这样写完之后,得到的效果应该是这样的

由于我们设置的setBounds方法里面的参数x = 0 , y = 0; 也就是放置在原点位置,这里需要注意我们是看图片左上角对应的坐标,也就是图片的坐标是图片左上角对应的坐标,很显然结果是对的

当我们知道了如何添加图片到JFrame中后,我们接下来添加其他图片

这里我图方便,我直接把创建的ImageIcon对象放入了创建JLabel对象的参数中

这里需要我们仔细观察这三段代码,告诉我有没有发现什么规律?
我们可以发现每次添加图片就只有图片位置发生了改变和图片地址发生了改变,尤其是图片位置改变的时候还满足一个规律,就是每行图片的x轴都是逐张加105,也就是说第一行第一张x是0,第二张就是105,第三张就是210…以此类推,第一行的第n张的x就是105 * (n - 1), y始终都是0; 那第二行呢,和第一行一样,就是y变成了105, 第三行的y就是210…以此类推,第n行的y就是105 * (n - 1);

有了上面的思路我们现在就应该要想到循环了,因为我们是四行四列,我们就可以用行循环四次,再用列循环四次,这样我们可以用到循环嵌套,一个外循环一个内循环来添加图片,下面是代码演示:

    public void initImage(){

        int number = 1;
        //外循环 --- 四行
        for (int i = 0; i < 4; i++) {
            //内循环 --- 一行的四张图片
            for (int j = 0; j < 4; j++) {
                //创建一个图片ImageIcon对象
                //创建一个JLabel的对象(管理容器)放入ImageIcon对象
                JLabel jLabel = new JLabel(new ImageIcon(path + number + ".jpg"));
                //指定图片位置
                jLabel.setBounds(105 * j, 105 * i, 105, 105);
                //把管理容器添加到界面中
                this.getContentPane().add(jLabel);

                //number自增表示图片变化
                number++;
            }
        }
    }

这个功能我写在了GameFrame类里的initImage()方法里,然后在空参构造里调用

这个功能我写在了GameFrame类里的initImage()方法里,然后在空参构造里调用

在上面的代码可以看到,外循环我用来表示行数,内循环表示列数,这个嵌套循环大致意思就是当我开始循环是i = 0表示第一行,然后 j 表示列数,j 逐渐增加, 表示列数逐渐增加, 表现在setBounds方法里的x逐渐增加,并且是逐级增加105, y又恰好是0, 刚好满足我们的图片放置规律, 第一行图片添加完之后, i 自增表示第二行,然后又执行内循环, 添加第二行的4张图片,然后继续循环知道图片所有图片添加完成

有人就会问了那个number是什么意思,这里我又要提醒一下了,图片的名字我们为了添加方便尽量设置成下面这种形式
注意:这里我将一个图片分成了16份来放入JFrame中,不能将一整个图片放入

我们在创建ImageIcon对象传递地址的时候就可以用一个数字逐渐递增来表示所有图片,并且将所有图片添加到容器中
这样就可以理解了number的含义了,就是作为地址值,添加对应的图片到容器中,number写进地址的时候,要记得写 “+number+”,不要直接将number写进去了,这样我们就可以得到下面的结果了

 方法2

 // 定义图片的基础路径
        String basePath = "D:\\Java学习\\java-learning\\day25\\image\\girl\\girl4\\";

        // 循环添加16张图片
        for (int j = 1; j <= 16; j++) {
            // 创建一个ImageIcon对象
            ImageIcon icon = new ImageIcon(basePath + j + ".jpg");

            // 创建一个JLabel对象(管理容器)
            JLabel label = new JLabel(icon);

            // 计算图片的位置(假设每行4张图片)
            int row = (j - 1) / 4; // 行号  行号从第0行开始的 然后按列进行拷贝
            int col = (j - 1) % 4; // 列号

            // 指定图片的位置
            label.setBounds(col * 105, row * 105, 105, 105);

            // 把管理容器添加在界面当中
            this.getContentPane().add(label);
        }

打乱图片的方法一

  String basePath = "D:\\Java学习\\java-learning\\day25\\image\\girl\\girl1\\";
        // 创建一个存储图片文件名的列表
        List<String> imagePaths = new ArrayList<>();
        for (int i = 1; i <= 16; i++) {
            imagePaths.add(basePath + i + ".jpg");//因为图片是从1开始的比如1.jpg
        }
        // 打乱图片顺序
        Collections.shuffle(imagePaths);
        // 循环添加16张打乱顺序的图片
        for (int j = 0; j < 16; j++) {
            // 创建一个ImageIcon对象
            ImageIcon icon = new ImageIcon(imagePaths.get(j));


            // 创建一个JLabel对象(管理容器)
            JLabel label = new JLabel(icon);

            // 计算图片的位置(假设每行4张图片)
            int row = j / 4; // 行号  行号从第0行开始的 然后按列进行拷贝
            int col = j % 4; // 列号

            // 指定图片的位置
            label.setBounds(col * 105, row * 105, 105, 105);

            // 把管理容器添加在界面当中
            this.getContentPane().add(label);
        }

打乱图片

既然我们已经弄懂了如何在JFrame添加图片,接下来我们要做的事就是打乱图片顺序,毕竟是拼图游戏,本身就是一个打乱顺序的图片让我们去移动恢复的
要想打乱图片顺序,我们前面用了一个int类型的number变量来不断自增,我们的图片就依次放进去了

此时的图片是有规律的,要想打乱1-16的顺序,最简单的办法是设置一个长度为16的一维数组,然后进行索引的交换来打断顺序,下面就是实现的代码

import java.util.Random;

public class Test {
    public static void main(String[] args) {
        //需求
        //把一个二维数组中的数据:0~15打乱顺序
        //然后再按照4个一组的方式添加到二维数组当中

        //1.定义一个一维数组
        int[] tempArray = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
        //2.打乱数组中的顺序
        //遍历数组,得到每一个元素,拿到每一个元素跟随机索引上的数据进行数据交换
        Random r = new Random();
        for (int i = 0; i < tempArray.length; i++) {
            int index = r.nextInt(tempArray.length);
            int temp = tempArray[i];
            tempArray[i] = tempArray[index];
            tempArray[index] = temp;
        }
        //3.打印先看看
        for (int i = 0; i < tempArray.length; i++) {
            System.out.print(tempArray[i] + " ");
        }
        System.out.println();
        //4.创建一个二维数组
        int[][] date = new int[4][4];
        //5.解法一:
        //遍历一维数组tempArr得到每一个元素,把每个元素一次添加在二维数组当中
//        for (int i = 0; i < tempArray.length; i++) {
//            date[i / 4][i % 4] = tempArray[i];
//        }


        //解法二:
        //遍历二维数组给里面的每一个数据赋值
        int index=0;
        for (int i = 0; i < date.length; i++) {
            for (int j = 0; j < date[i].length; j++) {
              date[i][j] = tempArray[index++];
            }
           
        }

        //遍历二维数组
        for (int i = 0; i < date.length; i++) {
            for (int j = 0; j < date[i].length; j++) {
                System.out.print(date[i][j] + " ");
            }
            System.out.println();
        }
    }

}

 要想打乱1-16的顺序,最简单的办法是设置一个长度为16的一维数组,然后进行索引的交换来打断顺序,下面就是实现的代码

		//创建一个一维数组
        int[] Arr = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};

        Random r = new Random();
        //遍历数组,将索引进行交换
        for (int i = 0; i < Arr.length; i++) {
            int index = r.nextInt(Arr.length);

            int temp = Arr[i];
            Arr[i] = Arr[index];
            Arr[index] = temp;
        }

        //遍历一维数组
        for (int i = 0; i < Arr.length; i++) {
            System.out.print(Arr[i] + " ");
        }

 这里需要注意的是,一维数组我为什么不按1-16,而是按0-15,而多的那个0,也就是当识别到0的时候,在素材里找不到会自动空着,也就是空白图片,就是我们移动图片时候的白框

很明显,顺序被完全打乱了,打乱完顺序之后,是不是就可以直接放入初始化图片方法里了呢,不不不,我们还要把图片索引弄的更优雅一点,既然拼图是按4 × 4 来显示,那我们的索引为什么不也按4 × 4 来显示呢

4 × 4 的索引排列第一个想到的应该是二维数组,那么现在我们只需要将一维数组转换成二维数组就可以了,我们可以用循环嵌套来表示,并逐渐将一维数组内的元素添加进去,下面是实现的代码:

		//将一维数组添加到二维数组中
        int[][] newArr = new int[4][4];

        int index = 0;
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                newArr[i][j] = Arr[index];
                index++;
            }
        }

        //遍历二维数组
        for (int i = 0; i < newArr.length; i++) {
            for (int j = 0; j < newArr[i].length; j++) {
                System.out.print(newArr[i][j] + " ");
            }
            System.out.println();
        }

最后我们只用把这个二维数组的数据代入到初始化图片方法里就可以了
注意:二维数组的创建最好写在GameFrame类的成员位置,这里我为了方便展示就写在了初始化数据方法里,因为我们在初始化图片方法里需要用的这个二维数组,如果写在成员位置,我们就可以直接使用了,所以这个初始化数据方法相当于给二维数组赋值,初始化图片方法直接使用二维数组里的数据就可以了

下面是如何在初始化图片方法里使用二维数组来打乱图片的顺序

 打乱图片的程序---------//初始化图片(根据打乱之后的结果去加载图片)

   private void initImage() {
        //初始化图片
        //直接用循环解决
        //方法2
        //添加图片的时候,就需要按照二维数组中管理的数据添加图片了
        // 定义图片的基础路径
       //外循环是把内循环执行4次
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                //内循环----表示在一行添加了4张图片
                //获取当前要加载的图片的序号
                int num = date[i][j];
                // 创建一个ImageIcon对象
                ImageIcon icon = new ImageIcon("D:\\Java学习\\java-learning\\day25\\image\\girl\\girl1\\" + num + ".jpg");
                // 创建一个JLabel对象(管理容器)
                JLabel label = new JLabel(icon);

                // 指定图片的位置
                label.setBounds(j * 105, i * 105, 105, 105);

                // 把管理容器添加在界面当中
                this.getContentPane().add(label);
            }

        }
}

我们之前用的是number来调用图片地址,而现在就是用二维数组里被打乱的值来调用,恰好这里有个循环嵌套,我们就可以直接用num来取出二维数组的值,这也就是为什么我前面要把一维数组转换成二维数组
最后的结果就是这样 

事件

事件监听简单点理解就是java识别你在鼠标和键盘上做出了各种操作来做出回应, 就像这个游戏,我们按↑键,空白下面的图片就会往上移,像这样的,而java识别你按下了↑键然后来做出空白下面的图片上移就叫事件监听
关于事件监听,java给了我们三个接口,可以直接使用,分别是ActionListener(动作监听), MouseListener(鼠标监听), KeyListener(键盘监听), 其中动作监听是其他两个的简化版只能识别鼠标的左键点击和空格的键盘, 鼠标监听能识别单击, 按住不松, 松开, 划入, 划出, 键盘监听能识别按住不松, 键入, 松开.

动作监听在监听鼠标的时候只能点左击,键盘的时候只能监听空格

JButton

创建一个带图标的按钮。

参数

icon - 要在按钮上显示的图标图像

这个this就是本类的对象,也就是GameJFrame类的对象,监听事件本身就是一个接口,如果
一个方法(动作监听事件方法)的参数是一个接口,那么接口的所有实现类都可以作为这个参数,.

 这个this就是表示动作事件的实现类对象, 这个实现类对象就是GameJFrame ,为什么呢 这个GameJFrame继承了JFrame,这个对象间接调用了重写的方法,

也可以理解为各种事件的参数就是实现类对象,就可以用本类实现这个事件 ,当给按钮添加各种事件的时候,参数就写本类的对象即可,本类的对象就会间接调用重写的方法

这个this就会执行本类对应的代码,这个代码就是下面的

package itheima.test;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

public class MJFrame extends JFrame implements ActionListener {

    //1.创建一个按钮对象
    JButton jtb1 = new JButton("点我啊");
    //2.创建一个按钮对象
    JButton jtb2 = new JButton("再点我啊");

   public  MJFrame() {
        //设置界面的宽高
        this.setSize(603, 680);
        //设置界面的标题
        this.setTitle("拼图单机版 v1.0");
        //设置界面置顶
        this.setAlwaysOnTop(true);
        //设置界面居中
        this.setLocationRelativeTo(null);
        //设置游戏的关闭模式
        this.setDefaultCloseOperation(3);//3就是关掉其中一个窗口就关闭了虚拟机
        //取消默认的居中放置,只有取消了才会按照XY轴的形式添加组件
        this.setLayout(null);



        jtb1.setBounds(0, 0, 100, 50);
        //给按钮添加事件
        jtb1.addActionListener(this);



        //设置位置和宽高
        jtb2.setBounds(100, 0, 100, 50);
        jtb2.addActionListener(this);

        //把按钮添加在整个界面当中
        this.getContentPane().add(jtb1);
        this.getContentPane().add(jtb2);
        //给按钮添加动作监听
        //jtb表示组件对象,表示你要给哪个组件添加事件
        //addActionListener:表示我要给组件添加哪个事件监听(动作监听鼠标左键点击,空格)

       //让他显示出来,建议最后
       this.setVisible(true);

    }

    public MJFrame(JButton jtb1, JButton jtb2) {
        this.jtb1 = jtb1;
        this.jtb2 = jtb2;
    }


    @Override
    public void actionPerformed(ActionEvent e) {
        //对当前的按钮进行判断
        //获取当前被操作的那个按钮对象
        Object source = e.getSource();
        if(source == jtb1) {
            jtb1.setSize(200,200);
        }else if(source == jtb2) {
            Random r= new Random();

            jtb2.setSize(r.nextInt(500),r.nextInt(500));
        }

    }

}

 上面是视频里的讲解点

下面我们就开始实践
由于事件监听是接口,我们直接在GameFrame类调用接口

public class GameFrame extends JFrame implements KeyListener

调用接口后,要重写接口所有的方法,一共有三个方法:

    @Override
    public void keyTyped(KeyEvent e) {

    }

    @Override
    public void keyPressed(KeyEvent e) {
        
    }

    @Override
    public void keyReleased(KeyEvent e) {
        
    }

 这个this就是表示鼠标事件的实现类对象, 这个实现类对象就是GameJFrame ,为什么呢

这个GameJFrame继承了JFrame,这个对象间接调用了重写的方法,下面就是重写的方法

 

package itheima.test;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Random;

public class MJFrame2 extends JFrame implements MouseListener {

    //1.创建一个按钮对象
    JButton jtb1 = new JButton("点我啊");
    //2.创建一个按钮对象
    JButton jtb2 = new JButton("再点我啊");

    public MJFrame2() {
        //设置界面的宽高
        this.setSize(603, 680);
        //设置界面的标题
        this.setTitle("拼图单机版 v1.0");
        //设置界面置顶
        this.setAlwaysOnTop(true);
        //设置界面居中
        this.setLocationRelativeTo(null);
        //设置游戏的关闭模式
        this.setDefaultCloseOperation(3);//3就是关掉其中一个窗口就关闭了虚拟机
        //取消默认的居中放置,只有取消了才会按照XY轴的形式添加组件
        this.setLayout(null);

        //设置位置和宽高
        jtb1.setBounds(0, 0, 100, 50);
        // 给按钮绑定鼠标事件
        jtb1.addMouseListener(this);//这个this就是指的是本类的对象
        //当MouseListener这个事件被触发,我要执行本类的this,也就是重写的本类的这些代码
        //传递的是MouseListener这个实现类的对象
        jtb2.addMouseListener(this);
        this.getContentPane().add(jtb1);
        //要把按钮添加在整个界面当中

        //设置位置和宽高
        jtb2.setBounds(100, 0, 100, 50);


        //把按钮添加在整个界面当中
        this.getContentPane().add(jtb1);
        this.getContentPane().add(jtb2);
        //给按钮添加动作监听
        //jtb表示组件对象,表示你要给哪个组件添加事件
        //addActionListener:表示我要给组件添加哪个事件监听(动作监听鼠标左键点击,空格)

        //让他显示出来,建议最后
        this.setVisible(true);

    }


    @Override
    public void mouseClicked(MouseEvent e) {
        System.out.println("单机");
    }

    @Override
    public void mousePressed(MouseEvent e) {
        System.out.println("按下不松");
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        System.out.println("松开");   
    }

    @Override
    public void mouseEntered(MouseEvent e) {
        System.out.println("划入");
    }

    @Override
    public void mouseExited(MouseEvent e) {
        System.out.println("划出");
    }
}

 

 

键盘监听的用法

本类对应的代码就是下面图片的代码 

顾名思义就是提供键盘按键来实现

美化界面

在我们打乱图片顺序之后,我们可以来着手美化界面了,我们主要美化游戏界面也就是添加一个游戏背景图,再给图片添加边跨,以及把图片和背景图移到中下的位置(多次尝试,中下的位置最好看) 

1.移动图片的位置 

2.添加游戏背景图

先添加的图片在上方,后添加的图片在下方,可能会与其他语言相反,这里要注意一下,所以添加背景图片的代码要写在添加图片的循环之后,并且在initImage()方法之内,下面是代码实现:

3.添加图片边框

添加图片边框不用我们自己找边框添加进去,JLabel也就是存放图片,文字的容器给我们提供了setBorder()方法,我们直接拿来用就好了
下面的代码是写在添加图片的嵌套循环内的

Borders设置边框的

从项目的名称开始,保留后面的,然后再项目的名字前面加上..即可

把上面的美化代码完成后,结果就是下面图片的样子 

 

 移动图片

这里我们主要用到keyReleased方法,也就是松开, 也就是当我按下松开后才会识别, 我按住不松是不会识别的
我们移动图片的原理简单一点理解其实就是空白图片和四周图片进行移动,如何移动呢?
我们之间添加图片的时候,是按照随机索引来添加图片的,那我们移动图片其实也是根据索引来移动的,只要我们把移动之后的数据给初始化图片方法就可以实现移动图片, 所以我们大致实现的逻辑是

1.获取空白图片的位置

这里我们主要就是判断当索引为0的时候,也就是空白图片的时候,就将它的索引获取出来,我在成员位置处设置了一个x 和 y来记录空白图片位置(由于键盘监听重写的方法里也要用到x和y),这里的x和y其实就是我们之前设置二维数组的 i 和 j ,如果你把它列成一个4 × 4 的表,它其实就是行和列

    ///遍历一维数组tempArr得到每一个元素,把每个元素一次添加在二维数组当中
        for (int i = 0; i < tempArray.length; i++) {
            if (tempArray[i] == 0) {
                x = i / 4;
                y = i % 4;
            }
            date[i / 4][i % 4] = tempArray[i];
        }
    //记录空白方块(0)在二维数组中的位置
    int x = 0;
    int y = 0;

2.在keyReleased方法里实现移动逻辑(按下松开的时候调用)

图片移动的原理其实就是空白图片与四周图片进行交换,前面我们知道了空白图片的位置,现在我们主要就是实现交换

int code = e.getKeyCode() 这个code就是我们键盘按下键的密码,说白了就是数字,每个键对应一个数字,所以后面我们直接判断这个code,然后进行相应的交换就行,交换就是索引的交换, 比如说向下移,就是空白的图片与上面的图片交换, 空白图片向上移,空白图片的x不变, 空白图片的y就要 - 1, 被交换的图片向下移, 也就是被交换的图片x不变, y要+ 1; 被交换的图片位置由于和空白图片交换了,所以直接给它的索引设置为0就可以了, 最后不要忘了, 空白图片移动之后,位置也改变了, 比如向下移动,就是空白图片向上移动, 所以要x-- , x和y要始终表示空白图片的位置

 @Override
    public void keyReleased(KeyEvent e) {
        //对上,下,左,右进行判断
        //左上右下 分别是37,38,30,40
        int code = e.getKeyCode();
        if (code == 37) {
            System.out.println("向左移动");
            //边界问题
            if(y==3){
                return;
            }
            //逻辑
            把空白块右方的数字向左移动
            //x,y表示空白方快
            //x,y-1表示空白左方的数字
            //把空白方块左方x的数字赋值给空白方快
            date[x][y] = date[x][y + 1];
            date[x][y + 1] = 0;//就是留出空白方快的位置
            y++;
            //调用方法按照最新的数字加载图片
            initImage();

        } else if (code == 38) {
            System.out.println("向上移动");
            //边界问题
            if(x==3){
                return;
            }
            //逻辑
            //把空白块下方的数字向上移动
            //x,y表示空白方快
            //x+1,y表示空白下方的数字
            //把空白方块下方的数字赋值给空白方快
            date[x][y] = date[x + 1][y];
            date[x + 1][y] = 0;//就是留出空白方快的位置
            x++;
            //调用方法按照最新的数字加载图片
            initImage();


        } else if (code == 39) {
            System.out.println("向右移动");
            //边界问题
            if(y==0){
                return;
            }
            //逻辑
            //把空白块左方的数字向右移动
            //x,y表示空白方快
            //x,y-1表示空白右方的数字
            //把空白方块右方的数字赋值给空白方快
            date[x][y] = date[x][y -1];
            date[x][y -1] = 0;//就是留出空白方快的位置
            y--;
            //调用方法按照最新的数字加载图片
            initImage();
        } else if (code == 40) {
            System.out.println("向下移动");
            //边界问题
            if(x==0){
                return;
            }
            //逻辑
            //把空白块上方的数字向下移动
            //x,y表示空白方快
            //x+1,y表示空白下方的数字
            //把空白方块下方的数字赋值给空白方快
            date[x][y] = date[x - 1][y];
            date[x - 1][y] = 0;//就是留出空白方快的位置
            x--;
            //调用方法按照最新的数字加载图片
            initImage();


        }

3.移动之后的效果显示及刷新

在我们完成上面的代码后,执行会发现并不会移动,难道是写错了,其实不是,我之前说过了,我们在修改索引之后,调用了initImage方法,此时的移动之后的效果会重新放在主界面上,但是,它会被第一次初始化图片给覆盖,也就是先添加的图片在上方,后添加的图片在下方,千万不要忘记了,所以我们要在initImage方法的前面实现一个清空之前主界面的内容,这样每次移动就会显示移动后的内容

 

4.BUG修复(索引越界问题)

只用在keyReleased方法里的四个按键判断语句中加上判断x和y是否在边界,如果在就不执行移动代码,直接退出就可以了,实现代码在2.在keyReleased方法里实现移动逻辑中 

显示完整的图片

在我们玩游戏的时候,有时候会不知道游戏的完整图片是什么而不知道怎么移动拼图,所以下面我们就要实现按下快捷键出现完整图片的功能,我们拿按下A键举例,当我们按下A键不松的时候,游戏主界面会出现当前拼图的完整图片,送开就会重新显示我们游玩的界面。下面是大致思路:
1.键盘监听
既然是按下A键显示,那么我们肯定要用到键盘监听,之前我们实现移动键盘的逻辑的时候,已经重写了键盘监听的所有方法,所以我们可以直接用到重写的方法
2.按下不松
我们需要按下A键不松来显示完整图片,松开就会返回游戏界面,这里我们需要在按下keyPressed()和keyReleased()方法里书写代码,当我们按下就会执行keyPressed()方法里的内容,当我们松开就会执行keyReleased()方法里的内容
3.清除界面和显示图片
我们实现按下显示完整图片的原理就是,当我们按下的时候,我们游戏的图片就会全部清楚,并且将完整图片显示在主界面里,当我们松开后直接调用initImage()方法就可以显示我们之前的游戏内容

下面是代码实现

@Override //键盘按下不松的时候调用这个方法
    public void keyPressed(KeyEvent e) {
        int code = e.getKeyCode();
        if (code == 65) {
            //把界面所有的图片删除
            this.getContentPane().removeAll();
            //加载第一张完整的图片,
            JLabel all = new JLabel(new ImageIcon(path+"all.jpg"));
            all.setBounds(83, 134, 420, 420);
            //界面显示加载图片
            this.getContentPane().add(all);
            //加载第二张背景图片
            //添加背景图片
            //创建一个ImageIcon的对象
            ImageIcon bg = new ImageIcon("..\\day25\\image\\background.png");
            //创建一个JLabel的对象(管理容器)
            JLabel label = new JLabel(bg);
            label.setBounds(40, 40, 508, 560);
            //把背景界面添加在界面当中
            this.getContentPane().add(label);
            this.getContentPane().repaint();
        }

上面这段是识别我们按下A键的时候,清除所有图片的代码,并且刷新

else if(code == 65){
      initImage();

 上面这段是在keyReleased()方法内的else if() 因为之前判断按键移动图片的时候加上了判断,所以现在直接在后面写else if()就可以了, 然后直接调用initImage()方法, 这个方法会清除之前的所有内容并重新显示游戏图片和背景图.

作弊码

    initImage();
        } else if (code == 87) {
               //作弊码
            //重写给二维数组赋值,初始化二维数组 //w键
            date = new int[][]{
                    {1, 2, 3, 4},
                    {5, 6, 7, 8},
                    {9, 10, 11, 12},
                    {13, 14, 15, 0},
            };
//调用上面的二维数组进行初始化图片,直接通关
            initImage();

依旧是在keyReleased()方法里使用else if(), 这里我设置的是W键(对应87), 我们直接初始化二维数组,也就是按照通关的顺序设置二维数组, 然后直接调用initImage()方法就可以显示通关后的样子,如下图

判断胜利

就是判断正确数据的二维数组win和date数组的数据是否一样,一样就展示胜利图片,不一样就不展示

date的数组的数据我按下W键的时候,这个数组的数据就会被我初始化为正确的数据,所以和作弊码很像,然后我定义的win就是正确的数据,所以当我按下W键的时候,就会显示正确的图片并且带有胜利的图标

//判断date数组中的数据是否和win数组中是否相同
    //如果相同返回true,不同false
    public boolean victory(){
        for (int i = 0; i < date.length; i++) {
            for (int j = 0; j < date[i].length; j++) {
                 if(date[i][j]!=wim[i][j]){
                     return false;
                 }
            }
        }
        return true;
    }

}

1.定义一个正确的二维数组win,并设置在成员位置处,因为我们需要在多个方法里用到这个数据 

//定义一个二维数组,存储正确的数据
    int[][] win = {
            {1,2,3,4},
            {5,6,7,8},
            {9,10,11,12},
            {13,14,15,0}
    };

2.在加载图片之前,先判断一下二维数组中的数字跟win数组中是否相同, 因为加载图片,显示游戏界面的功能都在initImage()方法里, 我们要想继续游戏,也就是继续显示游戏界面, 就要先识别有没有胜利 

if (victory()){
            //显示胜利图片
            JLabel winjLabel=new JLabel(new ImageIcon("D:\\Java学习\\java-learning\\day25\\image\\win.png"));
            //设置位置
            winjLabel.setBounds(203,283,197,73);
            //显示图片 把管理容器添加在界面当中
            this.getContentPane().add(winjLabel);
        }

 3.如果胜利了,就直接展示正确的图标,反之展示不正确的图标, 没有胜利会继续执行initImage()方法内的代码        就是显示游戏拼图

4.BUG修复。当我们全部写好后,我们发现当我们胜利后,显示了正确的图标,但是图片仍可以移动,我想当我成功后,游戏就不能动了,除非我们退出或下一局。
这个就需要我们在keyReleased()方法里进行判断,如果我们胜利了,就直接退出该方法,退出后就表示我们之前判断按键的代码都不能执行,也就是图片不会再进行移动

上面这段代码要写在keyReleased()方法的最上面, 因为此方法下面就是我们判断上下左右按键的代码, 想要胜利后, 这些按键都不能使用, 就要在它们上面进行判断,如果胜利就直接结束方法, 不再执行下面的判断按键, 如果没有胜利就会接着执行下面的判断,直至胜利

计步功能

为了增加游戏的趣味性, 在游戏的旁边,我们可以加上一个计数器来显示步数,每移动一次就加一, 这样我们就可以来比较谁的步数低,还是非常有意思的
统计步数逻辑的实现比较简单,我们可以在成员位置处定义一个计数器变量,然后在键盘监听的keyReleased()方法里的判断移动键的时候加上计数器变量自增,最后在initImage()方法里显示计数器内容就可以了,下面是代码实现

在成员位置处定义计算器变量

//定义变量用来统计步数
int step = 0;
//添加计数器到主界面中
JLabel stepCount = new JLabel("步数:" + step);
stepCount.setBounds(50, 30, 100, 20);
this.getContentPane().add(stepCount);

在initImage()方法里实现显示计数器的功能
最后在判断上下左右键的代码最下面加上step++就可以实现每移动一步,游戏界面的左上角显示移动的步数, 如下图 

重新游戏和关闭游戏        

先把Imagelcon放在JLabel当中,再把JLabel放在JDialog当中

重新游戏需要在菜单栏中点击重新游戏后,会重新打乱图片顺序,重新开始
既然是点击菜单栏中的数据, 那我们需要给这些菜单中的数据加上监听事件,最简单的就是给它们加上ActionListener,只有鼠标点击和键盘空格, 所以我们要在initJMenuBar()方法里给这些条目绑定事件监听
在调用ActionListener之前,我们要先调用ActionListene接口

public class GameFrame extends JFrame implements KeyListener, ActionListener

然后给条目加上事件

  replyItem这个就是你要给那个组件添加事件,addActionListener表示我要给那个组件添加动作事件
        参数,里面的this就是表示本类的对象,我们之前学习接口的时候,当一个方法的参数是接口的时,  可以传递这个接口的所有实现类对象,本类是JFrame的子类GameJFrame,这个类实现了接口,说白了这个参数就是实现类对象,实现类是GameJFrame实现的

参数:就是事件被触发之后要执行的代码


        //给条目绑定动作事件
        replyItem.addActionListener(this);
        //replyItem这个就是你要给那个组件添加事件,addActionListener表示我要给那个组件添加动作事件
        //参数,里面的this就是表示本类的对象,我们之前学习接口的时候,当一个方法的参数是接口的时,
        //可以传递这个接口的所有实现类对象,本类是JFrame的子类GameJFrame,这个类实现了接口
        //这个this就是GameJFrame的对象
 //参数:就是事件被触发之后要执行的代码
        reLoginItem.addActionListener(this);
        closeItem.addActionListener(this);
        wxItem.addActionListener(this);
        qqItem.addActionListener(this);

重新游戏 

注意在我们重写ActionListener接口方法之前,我们要先把之前定义的条目对象即JMenuItem移动到成员位置处,因为我们需要在ActionListener接口重写方法内调用条目对象
下面是ActionListener接口重写方法内内容
这里判断当我们点击重新游戏的条目对象replayItem时,执行的代码

    @Override
    public void actionPerformed(ActionEvent e) {
        //获取当前被点击的条目对象
        Object obj = e.getSource();
        //判断
        if(obj == replayItem){
            System.out.println("重新游戏");

            //计数器清零
            step = 0;
            //再次打乱二维数组中的数据
            initData();
            //重新加载图片
            initImage();

重新登录

重新登录是我们点击菜单栏中的重新登录即可,由于我们前面已经给重新登录添加了事件监听, 所以我们只用在ActionListener接口重写的方法内再进行判断就可以了,下面是代码实现

else if(obj == reLoginItem){
            System.out.println("重新登录");
            //关闭当前的游戏界面
            this.setVisible(false);
            //打开登录界面
            new LoginJFrame();

关闭游戏

关闭游戏很简单,在重写方法内判断是否点击关闭游戏, 如果点击了直接退出虚拟机就可以了

else if(obj == closeItem){
            System.out.println("关闭游戏");
            System.exit(0);

关于我们 

关于我们,我们可以显示我们的二维码,就是当玩家点击关于我们的时候, 会弹出一个弹框显示我们的二维码图片, 弹框是一个新的界面,要重新对其设置大小, 居中, 置顶, 是否显示等等, 和JFrame差不多的性质。 下面是代码实现

else if(obj == accountItem){
            System.out.println("公众号");

            //创建一个弹框对象
            JDialog jDialog = new JDialog();
            //创建一个管理图片的容器对象JLabel
            JLabel jLabel = new JLabel(new ImageIcon("image\\about.png"));
            //设置位置和宽高
            jLabel.setBounds(0,0,258,258);
            //把图片添加到弹框当中
            jDialog.getContentPane().add(jLabel);
            //给弹框设置大小
            jDialog.setSize(344, 344);
            //让弹框置顶
            jDialog.setAlwaysOnTop(true);
            //让弹框居中
            jDialog.setLocationRelativeTo(null);
            //弹框不关闭则无法操作下面的界面
            jDialog.setModal(true);
            //让弹框显示出来
            jDialog.setVisible(true);

        }

游戏优化

CodeUtil
package ui;

import java.util.ArrayList;
import java.util.Random;

public class CodeUtil {

    public static String getCode() {
        //1.创建一个集合
        ArrayList<Character> list = new ArrayList<>();//52  索引的范围:0 ~ 51
        //2.添加字母 a - z  A - Z
        for (int i = 0; i < 26; i++) {
            list.add((char) ('a' + i));//a - z
            list.add((char) ('A' + i));//A - Z
        }
        //3.打印集合
        //System.out.println(list);
        //4.生成4个随机字母
        String result = "";
        Random r = new Random();
        for (int i = 0; i < 4; i++) {
            //获取随机索引
            int randomIndex = r.nextInt(list.size());
            char c = list.get(randomIndex);
            result = result + c;
        }
        //System.out.println(result);//长度为4的随机字符串

        //5.在后面拼接数字 0~9
        int number = r.nextInt(10);
        //6.把随机数字拼接到result的后面
        result = result + number;
        //System.out.println(result);//ABCD5
        //7.把字符串变成字符数组
        char[] chars = result.toCharArray();//[A,B,C,D,5]
        //8.在字符数组中生成一个随机索引
        int index = r.nextInt(chars.length);
        //9.拿着4索引上的数字,跟随机索引上的数字进行交换
        char temp = chars[index];
        chars[index] = chars[chars.length - 1];
        chars[chars.length - 1] = temp;

        //10.把字符数组再变回字符串
        String code = new String(chars);
        //System.out.println(code);
        return code;
    }
}

GameJFrame
package ui;

import javax.swing.*;
import javax.swing.border.BevelBorder;
import java.awt.event.*;
import java.util.Random;

//游戏界面
public class GameJFrame extends JFrame implements KeyListener, ActionListener {
    //图片的路径
    String path = "image\\girl\\girl10\\";
    //创建数组
    //目的:用来管理数据,加载图片的时候,根据二维数组的数据进行加载
    int[][] date = new int[4][4];
    //记录空白方块(0)在二维数组中的位置
    int x = 0;
    int y = 0;
    //定义一个二维数组win和date数组进行比较,win存储的是图片正确的顺序
    int[][] win = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 0}};
    //用于统计计步功能的变量
    int step = 0;
    //创建选项下的条目对象(重新游戏,重新登录,关闭游戏,我的QQ号,我的微信号)
    JMenuItem replyItem = new JMenuItem("重新游戏");
    JMenuItem reLoginItem = new JMenuItem("重新登录");
    JMenuItem closeItem = new JMenuItem("关闭游戏");
    JMenuItem wxItem = new JMenuItem("我的微信号");
    JMenuItem qqItem = new JMenuItem("我的QQ号");
    //3.创建JMenuItem的对象 ---更换图片的条目
    JMenuItem girlItem = new JMenuItem("美女");
    JMenuItem animalItem = new JMenuItem("动物");
    JMenuItem sportItem = new JMenuItem("运动");


    //JFrame表示界面
    //子类也表示界面
    //规定GameFrame表示游戏的主界面
    //以后跟游戏相关的所有逻辑都写在这个类中
    public GameJFrame() {
        //初始化界面
        initJFrame();
        //初始化菜单
        initJMenuBar();
        //初始化打乱数据
        initdate();
        //初始化图片(加载打乱之后的图片)
        initImage();
        //让显示出来,建议写在最后
        this.setVisible(true);
    }

    private void initdate() {
        int[] tempArray = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
        Random r = new Random();
        for (int i = 0; i < tempArray.length; i++) {
            int index = r.nextInt(tempArray.length);
            int temp = tempArray[i];
            tempArray[i] = tempArray[index];
            tempArray[index] = temp;

        }
        ///遍历一维数组tempArr得到每一个元素,把每个元素一次添加在二维数组当中
        for (int i = 0; i < tempArray.length; i++) {
            if (tempArray[i] == 0) {
                x = i / 4;
                y = i % 4;
            }
            date[i / 4][i % 4] = tempArray[i];
        }

    }

    //初始化图片
     /*
    首先 创建ImageIcon的对象,
    并指定图片在电脑中的位置,然后创建一个JLabel的对象,
    把图片交给JLabel,在把这个整体放在界面当中
     */
    private void initImage() {
        清空原本已经出现的所有图片
        清空以后才会出现移动后的图片,不然被覆盖在下面了
        this.getContentPane().removeAll();
        //JLabel用于管理容器或者文字
        JLabel stepCount = new JLabel("步数:" + step);
        stepCount.setBounds(50, 30, 100, 20);
        this.getContentPane().add(stepCount);


        //根据victory的结果显示不同的结果,如果是true显示胜利,否则不展示图片
        if (victory()) {
            //显示胜利的图标
            ImageIcon icon = new ImageIcon("image\\win.png");
            //创建一个JLable的对象(管理容器),然后ImageIcon对象放在管理容器里面
            JLabel label = new JLabel(icon);
            //指定图片的位置
            label.setBounds(203, 283, 197, 73);
            //把管理容器添加到这个界面当中 (最后再把JLabel创建的对象添加到JFrame的隐藏容器中)
            this.getContentPane().add(label);//先获取隐藏的容器在添加add方法,把管理容器添加在界面当中
        }
        int num = 0;
        for (int i = 0; i < 4; i++) {
            //循环四次,就代表循环四行
            for (int j = 0; j < 4; j++) {
                //每一行添加4张图片
                //获取当前要加载的图片的序号
                num = date[i][j];
                //创建一个ImageIcon(用于管理图片的类)的对象
                ImageIcon icon = new ImageIcon(path + num + ".jpg");
                //创建一个JLable的对象(管理容器),然后ImageIcon对象放在管理容器里面
                JLabel label = new JLabel(icon);
                //指定图片的位置
                label.setBounds(105 * j + 83, 105 * i + 134, 105, 105);
                //设置图片边框(0:让图片凸起来  1:让图片凹下去)
                label.setBorder(new BevelBorder(0));
                //把管理容器添加到这个界面当中 (最后再把JLabel创建的对象添加到JFrame的隐藏容器中)
                this.getContentPane().add(label);//先获取隐藏的容器在添加add方法,把管理容器添加在界面当中

            }
        }
        //添加背景图片  背景图片放在16张图片后面
        JLabel backgound = new JLabel(new ImageIcon("image\\background.png"));
        backgound.setBounds(40, 40, 508, 560);
        //把背景图片添加到界面当中
        this.getContentPane().add(backgound);
        //刷新一下界面
        this.getContentPane().repaint();

    }


    //初始化菜单
    private void initJMenuBar() {
        //创建整个菜单对象
        JMenuBar jmenuBar = new JMenuBar();
        //创建菜单上的2个选项对象(功能,关于我们)
        JMenu funtionJMenu = new JMenu("功能");
        JMenu aboutJMenu = new JMenu("关于我们");
        //创建更换图片
        JMenu changeImage = new JMenu("更换图片 ");

        //把每选项下的条目对象添加在功能和关于我们上面
        funtionJMenu.add(replyItem);//重新游戏 等添加在功能里面
        funtionJMenu.add(reLoginItem);//重新登陆
        funtionJMenu.add(closeItem);//关闭游戏
        funtionJMenu.add(changeImage);//把更换图片添加在功能里面
        aboutJMenu.add(wxItem);//把微信登添加在关于我们里面
        aboutJMenu.add(qqItem);

        //把更换图片的下的条目对象添加到更换图片下面
        changeImage.add(girlItem);//把美女等添加在更换图片里面
        changeImage.add(animalItem);
        changeImage.add(sportItem);


        //给条目绑定动作事件
        replyItem.addActionListener(this);
        //replyItem这个就是你要给那个组件添加事件,addActionListener表示我要给那个组件添加动作事件
        //参数,里面的this就是表示本类的对象,我们之前学习接口的时候,当一个方法的参数是接口的时,
        //可以传递这个接口的所有实现类对象,本类是JFrame的子类GameJFrame,这个类实现了接口
        //这个this就是GameJFrame的对象
        //参数:就是事件被触发之后要执行的代码
        reLoginItem.addActionListener(this);
        closeItem.addActionListener(this);
        wxItem.addActionListener(this);
        qqItem.addActionListener(this);
        girlItem.addActionListener(this);
        animalItem.addActionListener(this);
        sportItem.addActionListener(this);


        //将菜单下的2个选项添加在菜单当中
        jmenuBar.add(funtionJMenu);
        jmenuBar.add(aboutJMenu);

        //最后把 JmenuBar添加在整个JFrame当中
        this.setJMenuBar(jmenuBar);
    }

    //初始化界面
    private void initJFrame() {
        //设置宽高
        this.setSize(603, 680);
        //设置标题
        this.setTitle("拼图单机版 V1.0");
        //设置界面置顶
        this.setAlwaysOnTop(true);
        //设置界面居中
        this.setLocationRelativeTo(null);
        //设置游戏的关闭模式
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //取消默认的居中放置,只有取消了才会按照XY轴的形式添加组件
        this.setLayout(null);
        //设置键盘监听事件 给整个界面添加
        this.addKeyListener(this);


    }


    @Override
    public void keyTyped(KeyEvent e) {

    }

    //按下时不松开调用
    @Override
    public void keyPressed(KeyEvent e) {
        int code = e.getKeyCode();
        //按下A
        if (code == 65) {
            //把界面图片中的所有图片删除
            this.getContentPane().removeAll();
            //加载一张完整的图片
            JLabel all = new JLabel(new ImageIcon(path + "all.jpg"));
            all.setBounds(83, 134, 420, 420);
            this.getContentPane().add(all);
            //添加背景图片  背景图片放在16张图片后面
            JLabel backgound = new JLabel(new ImageIcon("image\\background.png"));
            backgound.setBounds(40, 40, 508, 560);
            //把背景图片添加到界面当中
            this.getContentPane().add(backgound);
            //刷新一下界面
            this.getContentPane().repaint();

        }
    }

    //按下松开的时候调用
    @Override
    public void keyReleased(KeyEvent e) {
        //判断游戏是否胜利,如果胜利,此方法结束游戏,不能执行键盘移动的代码了
        if (victory()) {
            return;
        }
        //对上,下,左,右进行判断
        //左上右下 分别是37,38,30,40
        int code = e.getKeyCode();
        if (code == 37) {
            System.out.println("向左移动");
            //边界问题
            if (y == 3) return;
            //逻辑
            把空白块右方的数字向左移动
            //x,y表示空白方快
            //x,y-1表示空白左方的数字
            //把空白方块左方x的数字赋值给空白方快
            date[x][y] = date[x][y + 1];
            date[x][y + 1] = 0;
            y++;
            //用于添加步数,每移动一次,计数器++
            step++;
            //调用方法按照最新的数字加载图片
            initImage();
        } else if (code == 38) {
            System.out.println("向上移动");
            //边界问题
            if (x == 3) return;
            //逻辑
            把空白块下方的数字向上移动
            //x,y表示空白方快
            //x+1,y表示空白下方的数字
            //把空白方块下方x的数字赋值给空白方快
            date[x][y] = date[x + 1][y];
            date[x + 1][y] = 0;
            x++;
            step++;

            //调用方法按照最新的数字加载图片
            initImage();
        } else if (code == 39) {
            System.out.println("向右移动");
            //边界问题
            if (y == 0) return;
            //逻辑
            把空白块左方的数字向右移动
            //x,y表示空白方快
            //x,y-1表示空白左方的数字
            //把空白方块左方x的数字赋值给右边的空白方快
            date[x][y] = date[x][y - 1];
            date[x][y - 1] = 0;
            y--;
            step++;

            //调用方法按照最新的数字加载图片
            initImage();
        } else if (code == 40) {
            System.out.println("向下移动");
            //边界问题
            if (x == 0) return;
            //逻辑
            把空白块上方的数字向下移动
            //x,y表示空白方快
            //x-1,y表示空白上方的数字
            //把空白方块上方x的数字赋值给下方空白方快
            date[x][y] = date[x - 1][y];
            date[x - 1][y] = 0;
            x--;
            step++;

            //调用方法按照最新的数字加载图片
            initImage();
        } else if (code == 65) {//按下A键松开的时候执行代码(恢复原来的打乱模型)
            initImage();
        } else if (code == 87) {//按下W键松开的的时候执行代码(作弊码)
            //作弊码
            //重写给二维数组赋值,初始化二维数组 //w键
            date = new int[][]{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}, {13, 14, 15, 0},};
            //调用上面的二维数组进行初始化图片,直接通关
            initImage();
        }

    }

    //判断date数组中的数据是否和win数组中是否相同
    //如果相同返回true,不同false
    public boolean victory() {
        for (int i = 0; i < date.length; i++) {
            for (int j = 0; j < date[i].length; j++) {
                if (date[i][j] != win[i][j]) {
                    return false;
                }
            }

        }
        return true;
    }

    //简化版的--ActionListener,只有鼠标点击和键盘空格
    @Override
    public void actionPerformed(ActionEvent e) {
        //获取当前被点击的条目对象
        Object obj = e.getSource();
        if (obj == replyItem) {
            System.out.println("重新游戏");
            //计数器清0
            step = 0;
            //再次打乱二维数组中的数据date
            initdate();
            //加载图片
            initImage();

        } else if (obj == reLoginItem) {
            System.out.println("重新登录");
            //关闭当前窗口
            this.setVisible(false);
            //打开登录界面
            new LoginJFrame();
        } else if (obj == closeItem) {
            System.out.println("关闭游戏");
            System.exit(0);
        } else if (obj == wxItem) {
            System.out.println("我的微信号");
            //创建一个弹框对象
            JDialog dialog = new JDialog();
            //创建一个管理容器
            JLabel label = new JLabel(new ImageIcon("Snipaste_2024-06-23_15-59-16.png"));
            //设置位置和宽高
            label.setBounds(0, 0, 477, 581);
            //  //把图片添加到弹框里面
            dialog.getContentPane().add(label);
            //给弹框设置大小
            dialog.setSize(700, 700);
            //让弹框置顶
            dialog.setAlwaysOnTop(true);
            //让弹框居中
            dialog.setLocationRelativeTo(null);
            //让弹框不关闭则无法操作下面的界面
            dialog.setVisible(true);
            //显示图片
            dialog.setVisible(true);


        } else if (obj == qqItem) {
            System.out.println("我的qq号");
            //创建一个弹框对象
            JDialog jd = new JDialog();
            //创建一个管理图片的容器对象JLabel
            JLabel jLabel = new JLabel(new ImageIcon("Snipaste_2024-06-23_16-14-15.png"));
            //设置位置和宽高
            jLabel.setBounds(0, 0, 418, 551);
            //把图片添加到弹框里面
            jd.getContentPane().add(jLabel);
            //给弹框设置大小
            jd.setSize(600, 600);
            //让弹框置顶
            jd.setAlwaysOnTop(true);
            //让弹框居中
            jd.setLocationRelativeTo(null);
            //让弹框不关闭则无法操作下面的界面
            jd.setModal(true);
            //显示图片
            jd.setVisible(true);
        } else if (obj == girlItem) {
            System.out.println("美女图片");
            //随机获取图片
            Random r = new Random();
            int num = r.nextInt(15);//包左不包右

            path = "image\\girl\\" + "girl" + num + "\\";
            //初始化步数
            step = 0;
            //初始化数据
            initdate();
            //初始化图片
            initImage();

        } else if (obj == animalItem) {

            System.out.println("动物图片");
            //随机获取图片
            Random r = new Random();
            int num = r.nextInt(15);//包左不包右

            path = "image\\animal\\" + "animal" + num + "\\";
            //初始化步数
            step = 0;
            //初始化数据
            initdate();
            //初始化图片
            initImage();
        } else if (obj == sportItem) {
            System.out.println("运动图片");
            //随机获取图片
            Random r = new Random();
            int num = r.nextInt(15);//包左不包右
            path = "image\\sport\\" + "sport" + num + "\\";
            //初始化步数
            step = 0;
            //初始化数据
            initdate();
            //初始化图片
            initImage();
        }


    }
}
LoginJFrame
package ui;

import javax.swing.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;

public class LoginJFrame extends JFrame implements MouseListener {
    static ArrayList<User> allUsers = new ArrayList<>();

    static {
        allUsers.add(new User("zhangsan", "123"));
        allUsers.add(new User("lisi", "1234"));
    }


    JButton login = new JButton();
    JButton register = new JButton();

    JTextField username = new JTextField();
    JPasswordField password = new JPasswordField();
    JTextField code = new JTextField();

    //正确的验证码
    JLabel rightCode = new JLabel();


    public LoginJFrame() {
        //初始化界面
        initJFrame();

        //在这个界面中添加内容
        initView();


        //让当前界面显示出来
        this.setVisible(true);
    }

    public void initView() {
        //1. 添加用户名文字
        JLabel usernameText = new JLabel(new ImageIcon("image\\login\\用户名.png"));
        usernameText.setBounds(116, 135, 47, 17);
        this.getContentPane().add(usernameText);

        //2.添加用户名输入框
        username.setBounds(195, 134, 200, 30);
        this.getContentPane().add(username);

        //3.添加密码文字
        JLabel passwordText = new JLabel(new ImageIcon("image\\login\\密码.png"));
        passwordText.setBounds(130, 195, 32, 16);
        this.getContentPane().add(passwordText);

        //4.密码输入框
        password.setBounds(195, 195, 200, 30);
        this.getContentPane().add(password);


        //验证码提示
        JLabel codeText = new JLabel(new ImageIcon("image\\login\\验证码.png"));
        codeText.setBounds(133, 256, 50, 30);
        this.getContentPane().add(codeText);

        //验证码的输入框
        code.setBounds(195, 256, 100, 30);
        this.getContentPane().add(code);


        String codeStr = CodeUtil.getCode();
        //设置内容
        rightCode.setText(codeStr);
        //绑定鼠标事件
        rightCode.addMouseListener(this);
        //位置和宽高
        rightCode.setBounds(300, 256, 50, 30);
        //添加到界面
        this.getContentPane().add(rightCode);

        //5.添加登录按钮
        login.setBounds(123, 310, 128, 47);
        login.setIcon(new ImageIcon("image\\login\\登录按钮.png"));
        //去除按钮的边框
        login.setBorderPainted(false);
        //去除按钮的背景
        login.setContentAreaFilled(false);
        //给登录按钮绑定鼠标事件
        login.addMouseListener(this);
        this.getContentPane().add(login);

        //6.添加注册按钮
        register.setBounds(256, 310, 128, 47);
        register.setIcon(new ImageIcon("image\\login\\注册按钮.png"));
        //去除按钮的边框
        register.setBorderPainted(false);
        //去除按钮的背景
        register.setContentAreaFilled(false);
        //给注册按钮绑定鼠标事件
        register.addMouseListener(this);
        this.getContentPane().add(register);


        //7.添加背景图片
        JLabel background = new JLabel(new ImageIcon("image\\login\\background.png"));
        background.setBounds(0, 0, 470, 390);
        this.getContentPane().add(background);

    }


    public void initJFrame() {
        this.setSize(488, 430);//设置宽高
        this.setTitle("拼图游戏 V1.0登录");//设置标题
        this.setDefaultCloseOperation(3);//设置关闭模式
        this.setLocationRelativeTo(null);//居中
        this.setAlwaysOnTop(true);//置顶
        this.setLayout(null);//取消内部默认布局
    }


    //点击
    @Override
    public void mouseClicked(MouseEvent e) {
        if (e.getSource() == login) {
            System.out.println("点击了登录按钮");
            //获取两个文本输入框中的内容
            String usernameInput = username.getText();
            String passwordInput = password.getText();
            //获取用户输入的验证码
            String codeInput = code.getText();

            //创建一个User对象
            User userInfo = new User(usernameInput, passwordInput);
            System.out.println("用户输入的用户名为" + usernameInput);
            System.out.println("用户输入的密码为" + passwordInput);

            if (codeInput.length() == 0) {
                showJDialog("验证码不能为空");
            } else if (usernameInput.length() == 0 || passwordInput.length() == 0) {
                //校验用户名和密码是否为空
                System.out.println("用户名或者密码为空");

                //调用showJDialog方法并展示弹框
                showJDialog("用户名或者密码为空");


            } else if (!codeInput.equalsIgnoreCase(rightCode.getText())) {
                showJDialog("验证码输入错误");
            } else if (contains(userInfo)) {
                System.out.println("用户名和密码正确可以开始玩游戏了");
                //关闭当前登录界面
                this.setVisible(false);
                //打开游戏的主界面
                //需要把当前登录的用户名传递给游戏界面
                new GameJFrame();
            } else {
                System.out.println("用户名或密码错误");
                showJDialog("用户名或密码错误");
            }
        } else if (e.getSource() == register) {
            System.out.println("点击了注册按钮");
        } else if (e.getSource() == rightCode) {
            System.out.println("更换验证码");
            //获取一个新的验证码
            String code = CodeUtil.getCode();
            rightCode.setText(code);
        }
    }


    public void showJDialog(String content) {
        //创建一个弹框对象
        JDialog jDialog = new JDialog();
        //给弹框设置大小
        jDialog.setSize(200, 150);
        //让弹框置顶
        jDialog.setAlwaysOnTop(true);
        //让弹框居中
        jDialog.setLocationRelativeTo(null);
        //弹框不关闭永远无法操作下面的界面
        jDialog.setModal(true);

        //创建Jlabel对象管理文字并添加到弹框当中
        JLabel warning = new JLabel(content);
        warning.setBounds(0, 0, 200, 150);
        jDialog.getContentPane().add(warning);

        //让弹框展示出来
        jDialog.setVisible(true);
    }

    //按下不松
    @Override
    public void mousePressed(MouseEvent e) {
        if (e.getSource() == login) {
            login.setIcon(new ImageIcon("image\\login\\登录按下.png"));
        } else if (e.getSource() == register) {
            register.setIcon(new ImageIcon("image\\login\\注册按下.png"));
        }
    }


    //松开按钮
    @Override
    public void mouseReleased(MouseEvent e) {
        if (e.getSource() == login) {
            login.setIcon(new ImageIcon("image\\login\\登录按钮.png"));
        } else if (e.getSource() == register) {
            register.setIcon(new ImageIcon("image\\login\\注册按钮.png"));
        }
    }

    //鼠标划入
    @Override
    public void mouseEntered(MouseEvent e) {

    }

    //鼠标划出
    @Override
    public void mouseExited(MouseEvent e) {

    }

    //判断用户在集合中是否存在
    public boolean contains(User userInput) {
        for (int i = 0; i < allUsers.size(); i++) {
            User rightUser = allUsers.get(i);
            if (userInput.getName().equals(rightUser.getName()) && userInput.getPassword().equals(rightUser.getPassword())) {
                //有相同的代表存在,返回true,后面的不需要再比了
                return true;
            }
        }
        //循环结束之后还没有找到就表示不存在
        return false;
    }
}
RegisterJFrame
package ui;

import javax.swing.*;

public class RegisterJFrame extends JFrame {
    //跟注册相关的代码,都写在这个界面中
    public RegisterJFrame() {
        //设置大小
        this.setSize(488, 500);
        //设置标题
        this.setTitle("拼图 注册");
        //设置界面置顶
        this.setAlwaysOnTop(true);
        //设置界面居中
        this.setLocationRelativeTo(null);
        //设置游戏的关闭模式
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //让显示出来,建议写在最后
        this.setVisible(true);
        //让显示出来,建议写在最后
        this.setVisible(true);
    }
}

User

 

package ui;

public class User {
    private String name;
    private String password;

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public User() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

APP

import ui.GameJFrame;
import ui.LoginJFrame;

public class APP {
    public static void main(String[] args) {
        //表示程序程序启动的入口
        //如果我们想要开启一个界面,就创建谁的对象就可以了
        new LoginJFrame();
    }
}

  • 14
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值