锻炼逻辑思维能力,让我们知道前面学习的知识点在实际开发中的应用场景。
1、为了学习一个新知识:GUI
2、为什么图形化用户界面这个知识点很多Java课程中都不讲呢?
服务器
通过网络
传递给客户端(电脑、手机等)
的。 3、难道图形化用户界面这个知识点是真的一点用都没有了吗?
4、那我们之前做的学生管理系统、文字版格斗游戏、评委打分等等那些难道不是项目吗?
拆分成三部分:
1、最外层的窗体:
2、最上层的菜单:
3、管理文字和图片的容器:
JLabel:
JFrame
的子类,所以需要将每个界面都独立成一个界面类,继承父类:JFrame
。package cn.edu.gxufe.ui;import javax.swing.*;/*** 游戏主界面类:* 继承父类:JFrame*/
public class GameJFrame extends JFrame{// 表示游戏相关的逻辑代码都写在这!/*提供无参数的构造器需求:初始化一个宽603像素,高680像素的游戏主界面*/public GameJFrame() {// 注:如果当前类没有setSize、setVisible方法,就会自动调用父类JFrame的// 调用setSize方法,初始化一个宽603像素,高680像素大小的界面this.setSize(603, 680);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用setVisible方法,设置显示界面this.setVisible(true);}
}
package cn.edu.gxufe.ui;import javax.swing.*;/*** 登录界面类:* 继承父类:JFrame*/
public class LoginJFrame extends JFrame {// 表示登录相关的逻辑代码都写在这!/*提供无参数的构造器需求:初始化一个宽488像素,高430像素的登录界面*/public LoginJFrame() {// 注:如果当前类没有setSize、setVisible方法,就会自动调用父类JFrame的// 设置界面宽高// 调用setSize方法,初始化一个宽501像素,高437像素大小的界面this.setSize(501, 437);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用setVisible方法,设置显示界面this.setVisible(true);}
}
package cn.edu.gxufe.ui;import javax.swing.*;/*** 注册界面类:* 继承父类:JFrame*/
public class RegisterJFrame extends JFrame{// 表示注册相关的逻辑代码都写在这!/*提供无参数的构造器需求:初始化一个宽488像素,高500像素的登录界面*/public RegisterJFrame() {// 注:如果当前类没有setSize、setVisible方法,就会自动调用父类JFrame的// 调用setSize方法,初始化一个宽501像素,高437像素大小的界面this.setSize(501, 437);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用setVisible方法,设置显示界面this.setVisible(true);}
}
import cn.edu.gxufe.ui.GameJFrame;
import cn.edu.gxufe.ui.LoginJFrame;
import cn.edu.gxufe.ui.RegisterJFrame;public class App {public static void main(String[] args) {// 表示程序启动的入口// 如果我们想要开启一个界面,就创建对应界面的对象即可!new GameJFrame(); // 游戏主界面new LoginJFrame(); // 登录界面new RegisterJFrame(); // 注册界面}
}
游戏主界面类
package cn.edu.gxufe.ui;import javax.swing.*;/*** 游戏主界面类:* 继承父类:JFrame*/
public class GameJFrame extends JFrame{// 表示游戏相关的逻辑代码都写在这!/*提供无参数的构造器需求:初始化一个宽603像素,高680像素的游戏主界面*/public GameJFrame() {// 注:如果当前类没有以下这些方法,就会自动调用父类JFrame的// 1、初始化游戏主界面initJFrame();// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用当前GameJFrame类的setVisible方法,设置显示界面this.setVisible(true);}// 初始化游戏主界面private void initJFrame() {// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽603像素,高680像素大小的界面this.setSize(603, 680);// 设置界面标题this.setTitle("奥利gei拼图单机版 v1.0");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 注:取消默认的居中位置,只有取消了才会按照X、Y轴的形式添加组件// 因为只有这样,添加图片的时候才不会一直处于居中位置this.setLayout(null);}
}
登录界面类
package cn.edu.gxufe.ui;import javax.swing.*;/*** 登录界面类:* 继承父类:JFrame*/
public class LoginJFrame extends JFrame {// 表示登录相关的逻辑代码都写在这!/*提供无参数的构造器需求:初始化一个宽501像素,高437像素的登录界面*/public LoginJFrame() {// 初始化界面initJFrame();// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用setVisible方法,设置显示界面this.setVisible(true);}// 初始化登录界面private void initJFrame() {// 注:如果当前类没有以下这些方法,就会自动调用父类JFrame的// 设置界面宽高// 调用setSize方法,初始化一个宽501像素,高437像素大小的界面this.setSize(501, 437);// 设置界面标题this.setTitle("奥利gei拼图-登录");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行this.setDefaultCloseOperation(3);// 取消默认布局,这样子才可以让布局按照x和y轴的形式添加组件this.setLayout(null);}
}
注册界面类
package cn.edu.gxufe.ui;import javax.swing.*;/*** 注册界面类:* 继承父类:JFrame*/
public class RegisterJFrame extends JFrame{// 表示注册相关的逻辑代码都写在这!/*提供无参数的构造器需求:初始化一个宽501像素,高437像素的登录界面*/public RegisterJFrame() {// 注:如果当前类没有以下这些方法,就会自动调用父类JFrame的// 初始化注册界面initJFrame();// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用setVisible方法,设置显示界面this.setVisible(true);}// 初始化注册界面private void initJFrame() {// 设置界面宽高// 调用setSize方法,初始化一个宽501像素,高437像素大小的界面this.setSize(501, 437);// 设置界面标题this.setTitle("奥利gei拼图-注册");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 取消默认布局,这样子才可以让布局按照x和y轴的形式添加组件this.setLayout(null);}
}
程序的启动入口类
import cn.edu.gxufe.ui.GameJFrame;
import cn.edu.gxufe.ui.LoginJFrame;
import cn.edu.gxufe.ui.RegisterJFrame;public class App {public static void main(String[] args) {// 表示程序启动的入口// 如果我们想要开启一个界面,就创建对应界面的对象即可!new GameJFrame(); // 游戏主界面new LoginJFrame(); // 登录界面new RegisterJFrame(); // 注册界面}
}
测试结果
JMenuBar
菜单栏类: JMenuItem
放到JMenu
里面JMenu
放到JMenuBar
里面JMenuBar
放到GameJFrame
里面package cn.edu.gxufe.ui;import javax.swing.*;/*** 游戏主界面类:* 继承父类:JFrame*/
public class GameJFrame extends JFrame{// 表示游戏相关的逻辑代码都写在这!/*创建二维数组:目的:管理数据加载图片的时候,会根据二位数组中的数据进行加载*/int[][] data = new int[4][4];// 定义x、y变量,用于记录空白方块所在的XY的坐标位置int x = 0;int y = 0;// 创建一个成员的随机数对象,用于生成一个随机数Random rd = new Random();// 定义path变量,用于记录图片的相对路径,方便以后做更换图片、重新游戏等功能的实现String path = "puzzle_game\puzzleimages\girl\girl1\";// 定义一个正确顺序数据的二维数组int[][] win = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 定义一个计数器变量,用于记录玩家拼图移动的步数int step = 0;// c.创建功能菜单下的三个栏目:重新游戏、重新登录、关闭游戏// 更换图片栏目放到后面再写,因为比较复杂JMenuItem replayGameItem = new JMenuItem("重新游戏");JMenuItem replayLoginItem = new JMenuItem("重新登录");JMenuItem closeGameItem = new JMenuItem("关闭游戏");// d.创建关于我们菜单下的一个栏目:公众号JMenuItem accountItem = new JMenuItem("公众号");// 创建更换图片菜单下的两个栏目:美女、动物JMenuItem belleItem = new JMenuItem("美女");JMenuItem animalItem = new JMenuItem("动物");/*提供无参数的构造器需求:初始化一个宽603像素,高680像素的游戏主界面*/public GameJFrame() {// 注:如果当前类没有以下这些方法,就会自动调用父类JFrame的// 1、初始化游戏主界面initJFrame();// 2、初始化游戏主界面里的菜单栏initJMenu();// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用当前GameJFrame类的setVisible方法,设置显示界面this.setVisible(true);}// 初始化游戏主界面private void initJFrame() {// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽603像素,高680像素大小的界面this.setSize(603, 680);// 设置界面标题this.setTitle("奥利gei拼图单机版 v1.0");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 注:取消默认的居中位置,只有取消了才会按照X、Y轴的形式添加组件// 因为只有这样,添加图片的时候才不会一直处于居中位置this.setLayout(null);}// 初始化游戏主界面里的菜单栏private void initJMenu() {// a.创建一个菜单栏对象JMenuBar jMenuBar = new JMenuBar();// b.创建两个菜单栏下的菜单:功能、关于我们JMenu functionJMenu = new JMenu("功能");JMenu aboutUSJMenu = new JMenu("关于我们");// 创建菜单栏下的菜单:更换图片JMenu changeJMenu = new JMenu("更换图片");// 将更换图片的菜单嵌套进功能菜单中functionJMenu.add(changeJMenu);// e.将重新游戏、重新登录、关闭游戏这些栏目放到功能菜单下functionJMenu.add(replayGameItem);functionJMenu.add(replayLoginItem);functionJMenu.add(closeGameItem);// 将美女、动物这两个栏目放到更换图片菜单下changeJMenu.add(belleItem);changeJMenu.add(animalItem);// f.将公众号这个栏目放到关于我们菜单下aboutUSJMenu.add(accountItem);// g.将功能、关于我们这两个菜单放到菜单栏下jMenuBar.add(functionJMenu);jMenuBar.add(aboutUSJMenu);// h.最后将菜单栏放到设置到游戏界面中this.setJMenuBar(jMenuBar);}
}
import cn.edu.gxufe.ui.GameJFrame;
import cn.edu.gxufe.ui.LoginJFrame;
import cn.edu.gxufe.ui.RegisterJFrame;public class App {public static void main(String[] args) {// 表示程序启动的入口// 如果我们想要开启一个界面,就创建对应界面的对象即可!new GameJFrame(); // 游戏主界面
// new LoginJFrame(); // 登录界面
// new RegisterJFrame(); // 注册界面}
}
package cn.edu.gxufe.ui;import javax.swing.*;/*** 游戏主界面类:* 继承父类:JFrame*/
public class GameJFrame extends JFrame{// 表示游戏相关的逻辑代码都写在这!/*创建二维数组:目的:管理数据加载图片的时候,会根据二位数组中的数据进行加载*/int[][] data = new int[4][4];// 定义x、y变量,用于记录空白方块所在的XY的坐标位置int x = 0;int y = 0;// 创建一个成员的随机数对象,用于生成一个随机数Random rd = new Random();// 定义path变量,用于记录图片的相对路径,方便以后做更换图片、重新游戏等功能的实现String path = "puzzle_game\puzzleimages\girl\girl1\";// 定义一个正确顺序数据的二维数组int[][] win = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 定义一个计数器变量,用于记录玩家拼图移动的步数int step = 0;// c.创建功能菜单下的三个栏目:重新游戏、重新登录、关闭游戏// 更换图片栏目放到后面再写,因为比较复杂JMenuItem replayGameItem = new JMenuItem("重新游戏");JMenuItem replayLoginItem = new JMenuItem("重新登录");JMenuItem closeGameItem = new JMenuItem("关闭游戏");// d.创建关于我们菜单下的一个栏目:公众号JMenuItem accountItem = new JMenuItem("公众号");// 创建更换图片菜单下的两个栏目:美女、动物JMenuItem belleItem = new JMenuItem("美女");JMenuItem animalItem = new JMenuItem("动物");/*提供无参数的构造器需求:初始化一个宽603像素,高680像素的游戏主界面*/public GameJFrame() {// 注:如果当前类没有以下这些方法,就会自动调用父类JFrame的// 初始化游戏主界面initJFrame();// 初始化游戏主界面里的菜单栏initJMenu();// 初始化图片到游戏主界面中initImages(path);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用当前GameJFrame类的setVisible方法,设置显示界面this.setVisible(true);}// 初始化游戏主界面private void initJFrame() {// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽603像素,高680像素大小的界面this.setSize(603, 680);// 设置界面标题this.setTitle("奥利gei拼图单机版 v1.0");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 注:取消默认的居中位置,只有取消了才会按照X、Y轴的形式添加组件// 因为只有这样,添加图片的时候才不会一直处于居中位置this.setLayout(null);}// 初始化游戏主界面里的菜单栏private void initJMenu() {// a.创建一个菜单栏对象JMenuBar jMenuBar = new JMenuBar();// b.创建两个菜单栏下的菜单:功能、关于我们JMenu functionJMenu = new JMenu("功能");JMenu aboutUSJMenu = new JMenu("关于我们");// 创建菜单栏下的菜单:更换图片JMenu changeJMenu = new JMenu("更换图片");// 将更换图片的菜单嵌套进功能菜单中functionJMenu.add(changeJMenu);// e.将重新游戏、重新登录、关闭游戏这些栏目放到功能菜单下functionJMenu.add(replayGameItem);functionJMenu.add(replayLoginItem);functionJMenu.add(closeGameItem);// 将美女、动物这两个栏目放到更换图片菜单下changeJMenu.add(belleItem);changeJMenu.add(animalItem);// f.将公众号这个栏目放到关于我们菜单下aboutUSJMenu.add(accountItem);// g.将功能、关于我们这两个菜单放到菜单栏下jMenuBar.add(functionJMenu);jMenuBar.add(aboutUSJMenu);// h.最后将菜单栏放到设置到游戏界面中this.setJMenuBar(jMenuBar);}/*初始化图片到游戏主界面中细节:先加载的图片在上方,后加载的图片会在下方*/private void initImages(String path) {// 先清空原本出现的所有图片this.getContentPane().removeAll();// 判断victory方法的返回结果是否为trueif (victory()) {// 是,则说明玩家胜利了!显示胜利图标!// 根据指定图片的相对路径创建图片对象,并添加到管理容器中JLabel winJLabel = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\win.jpg"));// 设置胜利图片的坐标位置以及宽高winJLabel.setBounds(193, 300, 194, 75);// 将管理容器添加到游戏主界面中this.getContentPane().add(winJLabel);}// 统计步数// 创建一个管理容器对象,用于管理文字(步数: 0)JLabel stepJLabel = new JLabel("步数:" + step);// 设置管理容器的坐标位置stepJLabel.setBounds(40, 20, 400, 50);// 将容器添加到游戏主界面中this.getContentPane().add(stepJLabel);// 再加载图片/*外循环和内循环执行流程解析:外循环第一次执行:当 i = 0 时:i<4,为true,表示添加第一行的四张图片:当 j = 0 时:j<4,为true,此时number=1,表示添加第一行的第一张图片当 j = 1 时:j<4,为true,此时number=2,表示添加第一行的第二张图片当 j = 2 时:j<4,为true,此时number=3,表示添加第一行的第三张图片当 j = 3 时:j<4,为true,此时number=4,表示添加第一行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第二次执行:当 i = 1 时:i<4,为true,表示添加第二行的四张图片:当 j = 0 时:j<4,为true,此时number=5,表示添加第二行的第一张图片当 j = 1 时:j<4,为true,此时number=6,表示添加第二行的第二张图片当 j = 2 时:j<4,为true,此时number=7,表示添加第二行的第三张图片当 j = 3 时:j<4,为true,此时number=8,表示添加第二行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第三次执行:当 i = 2 时:i<4,为true,表示添加第三行的四张图片:当 j = 0 时:j<4,为true,此时number=9,表示添加第三行的第一张图片当 j = 1 时:j<4,为true,此时number=10,表示添加第三行的第二张图片当 j = 2 时:j<4,为true,此时number=11,表示添加第三行的第三张图片当 j = 3 时:j<4,为true,此时number=12,表示添加第三行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第四次执行:当 i = 3 时:i<4,为true,表示添加第四行的四张图片:当 j = 0 时:j<4,为true,此时number=13,表示添加第四行的第一张图片当 j = 1 时:j<4,为true,此时number=14,表示添加第四行的第二张图片当 j = 2 时:j<4,为true,此时number=15,表示添加第四行的第三张图片当 j = 3 时:j<4,为true,此时number=16,表示添加第四行的第四张图片(由于文件中没有16这个命名的图片,因此会添加一个空白)当 j = 4 时:j<4,为false,内循环结束!外循环第五次执行:当 i = 4 时:i<4,为false,外循环结束!*/// 外循环:控制添加4行图片for (int i = 0; i < data.length; i++) { // Y轴:纵向// 内循环:控制每行添加4张图片for (int j = 0; j < data[i].length; j++) { // X轴:横向/*解析:比如二维数组中的数据:[{3, 9, 7, 5} {8, 10, 14, 11} {4, 2, 12, 13} {0, 15, 1, 6} ]二维数组中的一维数组的索引: 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3二维数组的索引: 0 1 2 3(1) 外循环执行第一次:i=0, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(2) 外循环执行第二次:i=1, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(3) 外循环执行第三次:i=2, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(4) 外循环执行第四次:i=3, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(5) 外循环执行第五次:i=0, i<二维数组长度(4), 为false, 外循环结束!!*/// 接收二维数组i索引的一维数组j索引的数据int number = data[i][j];// 1、根据指定的文件相对路径创建一个ImageIcon图片对象ImageIcon imageIcon = new ImageIcon(path + number + ".jpg");// JLabel:管理容器,用于管理图片、文字// 2、创建一个管理容器JLabel对象,用于管理图片对象ImageIcon,// 并将图片对象imageIcon放到管理容器jLabel中。JLabel jLabel = new JLabel(imageIcon);// 3、指定图片位置:XY轴jLabel.setBounds(105 * j + 80, 105 * i + 130, 105, 105);// 4、每添加完一张小图片,给这张小图片添加边框/*0: 表示让图片凸起来1: 表示让图片凹下去*/jLabel.setBorder(new BevelBorder(1));// 5、将管理容器JLabel对象放到游戏主界面中// getContentPane:获取隐藏容器this.getContentPane().add(jLabel);}}// 在所有小图片都添加到界面之后,开始添加背景图片// 根据指定的相对路径创建背景图片对象,并添加到管理容器中JLabel background = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\background.jpg"));// 设置背景图片的坐标位置以及宽高background.setBounds(36, 36, 508, 560);// 将背景图片管理容器添加到界面中this.getContentPane().add(background);// 最后刷新一下游戏界面this.getContentPane().repaint();}
}
import cn.edu.gxufe.ui.GameJFrame;
import cn.edu.gxufe.ui.LoginJFrame;
import cn.edu.gxufe.ui.RegisterJFrame;public class App {public static void main(String[] args) {// 表示程序启动的入口// 如果我们想要开启一个界面,就创建对应界面的对象即可!new GameJFrame(); // 游戏主界面
// new LoginJFrame(); // 登录界面
// new RegisterJFrame(); // 注册界面}
}
打乱图片顺序
1、可以用0~15(包含15)来代表每张小的图片
//定义一个一维数组,将0~15的数据先存储起来
int arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
2、然后将这些数据随机打乱顺序
// 打乱后
int arr = {10, 5, 14, 15, 6, 12, 3, 11, 0, 8, 4, 13, 1, 9, 7, 2};
3、将这些随机打乱后的数据按照每4个作为一个一维数组存储到二维数组中
// 二维数组
int[][] data = { {10, 5, 14, 15}, {6, 12, 3, 11}, {0, 8, 4, 13}, {1, 9, 7, 2} };
package cn.edu.gxufe.ui;import javax.swing.*;/*** 游戏主界面类:* 继承父类:JFrame*/
public class GameJFrame extends JFrame{// 表示游戏相关的逻辑代码都写在这!/*创建二维数组:目的:管理数据加载图片的时候,会根据二位数组中的数据进行加载*/int[][] data = new int[4][4];// 定义x、y变量,用于记录空白方块所在的XY的坐标位置int x = 0;int y = 0;// 创建一个成员的随机数对象,用于生成一个随机数Random rd = new Random();// 定义path变量,用于记录图片的相对路径,方便以后做更换图片、重新游戏等功能的实现String path = "puzzle_game\puzzleimages\girl\girl1\";// 定义一个正确顺序数据的二维数组int[][] win = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 定义一个计数器变量,用于记录玩家拼图移动的步数int step = 0;// c.创建功能菜单下的三个栏目:重新游戏、重新登录、关闭游戏// 更换图片栏目放到后面再写,因为比较复杂JMenuItem replayGameItem = new JMenuItem("重新游戏");JMenuItem replayLoginItem = new JMenuItem("重新登录");JMenuItem closeGameItem = new JMenuItem("关闭游戏");// d.创建关于我们菜单下的一个栏目:公众号JMenuItem accountItem = new JMenuItem("公众号");// 创建更换图片菜单下的两个栏目:美女、动物JMenuItem belleItem = new JMenuItem("美女");JMenuItem animalItem = new JMenuItem("动物");/*提供无参数的构造器需求:初始化一个宽603像素,高680像素的游戏主界面*/public GameJFrame() {// 注:如果当前类没有以下这些方法,就会自动调用父类JFrame的// 初始化游戏主界面initJFrame();// 初始化游戏主界面里的菜单栏initJMenu();// 初始化数据:打乱initData();// 初始化图片到游戏主界面中initImages(path);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用当前GameJFrame类的setVisible方法,设置显示界面this.setVisible(true);}// 初始化数据:打乱private void initData() {// 1、创建一个一维数组,用于存储一些数据int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};// 2、开始打乱// 遍历数组,依次获取到数组中的每个数据for (int i = 0; i < arr.length; i++) {// 循环每执行一次,就生成一个随机索引int rdIndex = rd.nextInt(arr.length);// 每遍历到一个数据,就用临时变量存储一下int temp = arr[i];// 开始交换// 将随机索引位置的数据 放到 当前遍历到的数据的位置arr[i] = arr[rdIndex];// 将当前遍历到的数据 放到 随机索引位置arr[rdIndex] = temp;}/*解析:比如打乱后的一维数组的数据:[0, 14, 8, 3, 13, 15, 6, 4, 10, 12, 7, 1, 9, 5, 11, 2]索引: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15循环第一次执行:i=0,i<数组长度(16),为true,将0索引的数据:0 添加到二维数组的 i/4=0索引的一维数组的 i%4=0索引,i++循环第二次执行:i=1,i<数组长度(16),为true,将1索引的数据:14 添加到二维数组的 i/4=0索引的一维数组的 i%4=1索引,i++循环第三次执行:i=2,i<数组长度(16),为true,将2索引的数据:8 添加到二维数组的 i/4=0索引的一维数组的 i%4=2索引,i++后面的都是以此类推了!直到i<16,为false,循环结束!*/// 3、遍历arr一维数组,依次得到打乱后的每个数据for (int i = 0; i < arr.length; i++) {// 判断当前数据是否为0if (arr[i] == 0) {// 是,则记录空白方块XY的坐标位置x = i / 4;y = i % 4;}// 否,则依次将打乱后的数据添加到二维数组中data[i / 4][i % 4] = arr[i];}}// 初始化游戏主界面private void initJFrame() {// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽603像素,高680像素大小的界面this.setSize(603, 680);// 设置界面标题this.setTitle("奥利gei拼图单机版 v1.0");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 注:取消默认的居中位置,只有取消了才会按照X、Y轴的形式添加组件// 因为只有这样,添加图片的时候才不会一直处于居中位置this.setLayout(null);}// 初始化游戏主界面里的菜单栏private void initJMenu() {// a.创建一个菜单栏对象JMenuBar jMenuBar = new JMenuBar();// b.创建两个菜单栏下的菜单:功能、关于我们JMenu functionJMenu = new JMenu("功能");JMenu aboutUSJMenu = new JMenu("关于我们");// 创建菜单栏下的菜单:更换图片JMenu changeJMenu = new JMenu("更换图片");// 将更换图片的菜单嵌套进功能菜单中functionJMenu.add(changeJMenu);// e.将重新游戏、重新登录、关闭游戏这些栏目放到功能菜单下functionJMenu.add(replayGameItem);functionJMenu.add(replayLoginItem);functionJMenu.add(closeGameItem);// 将美女、动物这两个栏目放到更换图片菜单下changeJMenu.add(belleItem);changeJMenu.add(animalItem);// f.将公众号这个栏目放到关于我们菜单下aboutUSJMenu.add(accountItem);// g.将功能、关于我们这两个菜单放到菜单栏下jMenuBar.add(functionJMenu);jMenuBar.add(aboutUSJMenu);// h.最后将菜单栏放到设置到游戏界面中this.setJMenuBar(jMenuBar);}/*初始化图片到游戏主界面中细节:先加载的图片在上方,后加载的图片会在下方*/private void initImages(String path) {// 先清空原本出现的所有图片this.getContentPane().removeAll();// 判断victory方法的返回结果是否为trueif (victory()) {// 是,则说明玩家胜利了!显示胜利图标!// 根据指定图片的相对路径创建图片对象,并添加到管理容器中JLabel winJLabel = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\win.jpg"));// 设置胜利图片的坐标位置以及宽高winJLabel.setBounds(193, 300, 194, 75);// 将管理容器添加到游戏主界面中this.getContentPane().add(winJLabel);}// 统计步数// 创建一个管理容器对象,用于管理文字(步数: 0)JLabel stepJLabel = new JLabel("步数:" + step);// 设置管理容器的坐标位置stepJLabel.setBounds(40, 20, 400, 50);// 将容器添加到游戏主界面中this.getContentPane().add(stepJLabel);// 再加载图片/*外循环和内循环执行流程解析:外循环第一次执行:当 i = 0 时:i<4,为true,表示添加第一行的四张图片:当 j = 0 时:j<4,为true,此时number=1,表示添加第一行的第一张图片当 j = 1 时:j<4,为true,此时number=2,表示添加第一行的第二张图片当 j = 2 时:j<4,为true,此时number=3,表示添加第一行的第三张图片当 j = 3 时:j<4,为true,此时number=4,表示添加第一行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第二次执行:当 i = 1 时:i<4,为true,表示添加第二行的四张图片:当 j = 0 时:j<4,为true,此时number=5,表示添加第二行的第一张图片当 j = 1 时:j<4,为true,此时number=6,表示添加第二行的第二张图片当 j = 2 时:j<4,为true,此时number=7,表示添加第二行的第三张图片当 j = 3 时:j<4,为true,此时number=8,表示添加第二行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第三次执行:当 i = 2 时:i<4,为true,表示添加第三行的四张图片:当 j = 0 时:j<4,为true,此时number=9,表示添加第三行的第一张图片当 j = 1 时:j<4,为true,此时number=10,表示添加第三行的第二张图片当 j = 2 时:j<4,为true,此时number=11,表示添加第三行的第三张图片当 j = 3 时:j<4,为true,此时number=12,表示添加第三行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第四次执行:当 i = 3 时:i<4,为true,表示添加第四行的四张图片:当 j = 0 时:j<4,为true,此时number=13,表示添加第四行的第一张图片当 j = 1 时:j<4,为true,此时number=14,表示添加第四行的第二张图片当 j = 2 时:j<4,为true,此时number=15,表示添加第四行的第三张图片当 j = 3 时:j<4,为true,此时number=16,表示添加第四行的第四张图片(由于文件中没有16这个命名的图片,因此会添加一个空白)当 j = 4 时:j<4,为false,内循环结束!外循环第五次执行:当 i = 4 时:i<4,为false,外循环结束!*/// 外循环:控制添加4行图片for (int i = 0; i < data.length; i++) { // Y轴:纵向// 内循环:控制每行添加4张图片for (int j = 0; j < data[i].length; j++) { // X轴:横向/*解析:比如二维数组中的数据:[{3, 9, 7, 5} {8, 10, 14, 11} {4, 2, 12, 13} {0, 15, 1, 6} ]二维数组中的一维数组的索引: 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3二维数组的索引: 0 1 2 3(1) 外循环执行第一次:i=0, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(2) 外循环执行第二次:i=1, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(3) 外循环执行第三次:i=2, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(4) 外循环执行第四次:i=3, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(5) 外循环执行第五次:i=0, i<二维数组长度(4), 为false, 外循环结束!!*/// 接收二维数组i索引的一维数组j索引的数据int number = data[i][j];// 1、根据指定的文件相对路径创建一个ImageIcon图片对象ImageIcon imageIcon = new ImageIcon(path + number + ".jpg");// JLabel:管理容器,用于管理图片、文字// 2、创建一个管理容器JLabel对象,用于管理图片对象ImageIcon,// 并将图片对象imageIcon放到管理容器jLabel中。JLabel jLabel = new JLabel(imageIcon);// 3、指定图片位置:XY轴jLabel.setBounds(105 * j + 80, 105 * i + 130, 105, 105);// 4、每添加完一张小图片,给这张小图片添加边框/*0: 表示让图片凸起来1: 表示让图片凹下去*/jLabel.setBorder(new BevelBorder(1));// 5、将管理容器JLabel对象放到游戏主界面中// getContentPane:获取隐藏容器this.getContentPane().add(jLabel);}}// 在所有小图片都添加到界面之后,开始添加背景图片// 根据指定的相对路径创建背景图片对象,并添加到管理容器中JLabel background = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\background.jpg"));// 设置背景图片的坐标位置以及宽高background.setBounds(36, 36, 508, 560);// 将背景图片管理容器添加到界面中this.getContentPane().add(background);// 最后刷新一下游戏界面this.getContentPane().repaint();}
}
import cn.edu.gxufe.ui.GameJFrame;
import cn.edu.gxufe.ui.LoginJFrame;
import cn.edu.gxufe.ui.RegisterJFrame;public class App {public static void main(String[] args) {// 表示程序启动的入口// 如果我们想要开启一个界面,就创建对应界面的对象即可!new GameJFrame(); // 游戏主界面
// new LoginJFrame(); // 登录界面
// new RegisterJFrame(); // 注册界面}
}
多执行几次,看看是不是随机的
KeyListener:键盘监听
MouseListener:鼠标监听
比如lol英雄联盟,当你用鼠标点击界面时,会出现很多问号!并且还会出现叮叮叮的声音!!
ActionListener:动作监听
package cn.edu.gxufe.test;import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;// 创建动作监听ActionListener接口的实现类,用于实现接口里的单击按钮方法
public class MyActionListenerImpl implements ActionListener{// 实现ActionListener接口中的单击按钮方法@Overridepublic void actionPerformed(ActionEvent e) {System.out.println("我是ActionListener动作监听接口的单击按钮实现方法,我被你点击了!");}
}
package cn.edu.gxufe.test;import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;// 测试类
public class Test2 extends JFrame {public static void main(String[] args) {// 初始化界面JFrame jFrame = new JFrame();// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽603像素,高680像素大小的界面jFrame.setSize(603, 680);// 设置界面标题jFrame.setTitle("奥利gei拼图单机版 v1.0");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的jFrame.setAlwaysOnTop(true);// 设置界面打开时自动居中jFrame.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 注:取消默认的居中位置,只有取消了才会按照X、Y轴的形式添加组件// 因为只有这样,添加图片的时候才不会一直处于居中位置jFrame.setLayout(null);// 重点:// 创建一个按钮对象JButton jbt1 = new JButton("点我呗");// 设置位置和宽高jbt1.setBounds(0, 0, 100, 50);// 给按钮添加动作监听// jbt1: 组件对象,表示你要给哪个组件添加事件// addActionListener: 表示我要给组件添加哪个事件监听(动作监听包含鼠标左键点击,空格)// 参数: 表示事件被触发之后要执行的代码// 方式一:实现类实现接口的单击按钮方法jbt1.addActionListener(new MyActionListenerImpl());// 把按钮添加到界面当中jFrame.getContentPane().add(jbt1);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用当前GameJFrame类的setVisible方法,设置显示界面jFrame.setVisible(true);}
}
package cn.edu.gxufe.test;import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;// 测试类
public class Test2 extends JFrame {public static void main(String[] args) {// 初始化界面JFrame jFrame = new JFrame();// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽603像素,高680像素大小的界面jFrame.setSize(603, 680);// 设置界面标题jFrame.setTitle("奥利gei拼图单机版 v1.0");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的jFrame.setAlwaysOnTop(true);// 设置界面打开时自动居中jFrame.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 注:取消默认的居中位置,只有取消了才会按照X、Y轴的形式添加组件// 因为只有这样,添加图片的时候才不会一直处于居中位置jFrame.setLayout(null);// 重点:// 创建一个按钮对象JButton jbt1 = new JButton("点我呗");// 设置位置和宽高jbt1.setBounds(0, 0, 100, 50);// 给按钮添加动作监听// jbt1: 组件对象,表示你要给哪个组件添加事件// addActionListener: 表示我要给组件添加哪个事件监听(动作监听包含鼠标左键点击,空格)// 参数: 表示事件被触发之后要执行的代码// 方式一:实现类实现接口的单击按钮方法
// jbt1.addActionListener(new MyActionListenerImpl());// 方式二:匿名内部类直接实现接口的单击按钮方法jbt1.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {System.out.println("达咩!不要点我喔~ 会疼喔!!");}});// 把按钮添加到界面当中jFrame.getContentPane().add(jbt1);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用当前GameJFrame类的setVisible方法,设置显示界面jFrame.setVisible(true);}
}
package cn.edu.gxufe.test;import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;public class MyFrame extends JFrame implements ActionListener {// 创建按钮对象1JButton jbt1 = new JButton("点我啊");// 创建按钮对象2JButton jbt2 = new JButton("再点我啊~");public MyFrame() {// 初始化界面initJFrame();// 设置按钮的位置和宽高jbt1.setBounds(0, 0, 100, 50);// 给按钮添加动作监听事件// jbt1: 组件对象,表示你要给哪个组件添加事件// addActionListener: 表示我要给组件添加哪个事件监听(动作监听包含鼠标左键点击,空格)// 参数: 表示事件被触发之后要执行的代码jbt1.addActionListener(this);// 设置按钮的位置和宽高jbt2.setBounds(100, 0, 100, 50);// 给按钮添加动作监听事件jbt2.addActionListener(this);// 添加两个按钮到当前界面中this.getContentPane().add(jbt1);this.getContentPane().add(jbt2);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用当前GameJFrame类的setVisible方法,设置显示界面this.setVisible(true);}private void initJFrame() {// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽603像素,高680像素大小的界面this.setSize(603, 680);// 设置界面标题this.setTitle("奥利gei拼图单机版 v1.0");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 注:取消默认的居中位置,只有取消了才会按照X、Y轴的形式添加组件// 因为只有这样,添加图片的时候才不会一直处于居中位置this.setLayout(null);}/*直接在本类实现ActionListener接口的单击按钮*/@Overridepublic void actionPerformed(ActionEvent e) {// 获取当前的按钮对象Object source = e.getSource();// 判断当前的按钮对象是否为jbt1if (source == jbt1) {// 是,则将jbt1按钮的宽高都设置为150像素jbt1.setSize(150, 150);}else if (source == jbt2) { // 判断当前的按钮对象是否为jbt2// 是,则当按下jbt2按钮后,该按钮的坐标随机到500内的范围Random rd = new Random();jbt2.setLocation(rd.nextInt(500), rd.nextInt(500));}}
}
package cn.edu.gxufe.test;public class Test3 {public static void main(String[] args) {// 构造界面new MyFrame();}
}
鼠标监听:
思考:
返回类型 | 方法名称 | |
---|---|---|
void | mouseClicked(MouseEvent e) | 在组件上单击(按下并释放)鼠标按钮时调用 |
void | mouseEntered(MouseEvent e) | 当鼠标进入组件时调用 |
void | mouseExited(MouseEvent e) | 当鼠标退出组件时调用 |
void | mousePressed(MouseEvent e) | 在组件上按下鼠标按钮时调用 |
void | mouseReleased(MouseEvent e) | 在组件上释放鼠标按钮时调用 |
package cn.edu.gxufe.test;import javax.swing.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;// 自定义界面类:需要继承父类(JFrame)
public class MyJFrame2 extends JFrame implements MouseListener {// 创建按钮对象JButton jbt = new JButton("点我啊");public MyJFrame2() {// 初始化界面initJFrame();// 设置按钮在界面中的坐标位置jbt.setBounds(0, 0, 100, 50);// 给按钮添加鼠标监听事件jbt.addMouseListener(this);// 将按钮添加到界面中this.getContentPane().add(jbt);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用当前界面类的setVisible方法,设置显示界面this.setVisible(true);}// 初始化界面private void initJFrame() {// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽603像素,高680像素大小的界面this.setSize(603, 680);// 设置界面标题this.setTitle("奥利gei拼图单机版 v1.0");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 注:取消默认的居中位置,只有取消了才会按照X、Y轴的形式添加组件// 因为只有这样,添加图片的时候才不会一直处于居中位置this.setLayout(null);}// 实现鼠标监听接口MouseListener的鼠标单击事件方法@Overridepublic void mouseClicked(MouseEvent e) {System.out.println("鼠标单击了一下");}// 实现鼠标监听接口MouseListener的按下鼠标事件方法@Overridepublic void mousePressed(MouseEvent e) {System.out.println("被鼠标按了一下");}// 实现鼠标监听接口MouseListener的松开鼠标事件方法@Overridepublic void mouseReleased(MouseEvent e) {System.out.println("鼠标松开了");}// 实现鼠标监听接口MouseListener的鼠标划入事件方法@Overridepublic void mouseEntered(MouseEvent e) {System.out.println("鼠标划进来了");}// 实现鼠标监听接口MouseListener的鼠标划出事件方法@Overridepublic void mouseExited(MouseEvent e) {System.out.println("鼠标划走了");}
}
package cn.edu.gxufe.test;public class Test4 {public static void main(String[] args) {// 构造界面new MyJFrame2();}
}
返回类型 | 方法名称 | 描述 |
---|---|---|
void | keyPressed(KeyEvent e) | 按下键时调用 |
void | keyReleased(KeyEvent e) | 当键已被释放时调用 |
void | keyTyped(KeyEvent e) | 键入键时调用 |
package cn.edu.gxufe.test;import javax.swing.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;// 自定义界面类:继承父类JFrame
public class MyJFrame3 extends JFrame implements KeyListener{// 提供无参数的构造器public MyJFrame3(){// 初始化界面initJFrame();// 给整个界面添加键盘监听事件// 调用者this:本类对象,当前的界面对象,表示我要给整个界面添加监听// addKeyListener:表示要给本界面添加键盘监听// 参数this:当事件被触发之后,会执行本类中的对应代码this.addKeyListener(this);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用当前界面类的setVisible方法,设置显示界面this.setVisible(true);}// 初始化界面private void initJFrame() {// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽603像素,高680像素大小的界面this.setSize(603, 680);// 设置界面标题this.setTitle("奥利gei拼图单机版 v1.0");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 注:取消默认的居中位置,只有取消了才会按照X、Y轴的形式添加组件// 因为只有这样,添加图片的时候才不会一直处于居中位置this.setLayout(null);}// 这个可以不用管@Overridepublic void keyTyped(KeyEvent e) {}// 实现键盘监听接口KeyListener的按下键盘方法/*细节1:如果我们按下键盘的一个键没有松开,那么会重复的去调用keyPressed方法细节2:键盘里面那么多按键,如果进行区分?答:每一个按键都有一个编号与之对应的*/@Overridepublic void keyPressed(KeyEvent e) {// 获取键盘每个按键的编号int keyCode = e.getKeyCode();// 判断当前按下的按键的编号是否为65if (keyCode == 65) {// 是,则说明按下的按键是ASystem.out.println("按下按键A");}else if (keyCode == 66){ // 否,则判断当前按下的按键的编号是否为66// 是,则说明按下的按键是BSystem.out.println("按下按键B");}else {// 否,则说明按下的是其他按键System.out.println("按下的是其他按键");}}// 实现键盘监听接口KeyListener的释放(松开)键盘方法@Overridepublic void keyReleased(KeyEvent e) {System.out.println("按键已松开");}
}
package cn.edu.gxufe.test;public class Test5 {public static void main(String[] args) {// 构造界面new MyJFrame3();}
}
按照以下美化后的要求,优化游戏界面
美化之前
美化之后
package cn.edu.gxufe.ui;import javax.swing.*;/*** 游戏主界面类:* 继承父类:JFrame*/
public class GameJFrame extends JFrame{// 表示游戏相关的逻辑代码都写在这!/*创建二维数组:目的:管理数据加载图片的时候,会根据二位数组中的数据进行加载*/int[][] data = new int[4][4];// 定义x、y变量,用于记录空白方块所在的XY的坐标位置int x = 0;int y = 0;// 创建一个成员的随机数对象,用于生成一个随机数Random rd = new Random();// 定义path变量,用于记录图片的相对路径,方便以后做更换图片、重新游戏等功能的实现String path = "puzzle_game\puzzleimages\girl\girl1\";// 定义一个正确顺序数据的二维数组int[][] win = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 定义一个计数器变量,用于记录玩家拼图移动的步数int step = 0;// c.创建功能菜单下的三个栏目:重新游戏、重新登录、关闭游戏// 更换图片栏目放到后面再写,因为比较复杂JMenuItem replayGameItem = new JMenuItem("重新游戏");JMenuItem replayLoginItem = new JMenuItem("重新登录");JMenuItem closeGameItem = new JMenuItem("关闭游戏");// d.创建关于我们菜单下的一个栏目:公众号JMenuItem accountItem = new JMenuItem("公众号");// 创建更换图片菜单下的两个栏目:美女、动物JMenuItem belleItem = new JMenuItem("美女");JMenuItem animalItem = new JMenuItem("动物");/*提供无参数的构造器需求:初始化一个宽603像素,高680像素的游戏主界面*/public GameJFrame() {// 注:如果当前类没有以下这些方法,就会自动调用父类JFrame的// 初始化游戏主界面initJFrame();// 初始化游戏主界面里的菜单栏initJMenu();// 初始化数据:打乱initData();// 初始化图片到游戏主界面中initImages(path);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用当前GameJFrame类的setVisible方法,设置显示界面this.setVisible(true);}// 初始化数据:打乱private void initData() {// 1、创建一个一维数组,用于存储一些数据int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};// 2、开始打乱// 遍历数组,依次获取到数组中的每个数据for (int i = 0; i < arr.length; i++) {// 循环每执行一次,就生成一个随机索引int rdIndex = rd.nextInt(arr.length);// 每遍历到一个数据,就用临时变量存储一下int temp = arr[i];// 开始交换// 将随机索引位置的数据 放到 当前遍历到的数据的位置arr[i] = arr[rdIndex];// 将当前遍历到的数据 放到 随机索引位置arr[rdIndex] = temp;}/*解析:比如打乱后的一维数组的数据:[0, 14, 8, 3, 13, 15, 6, 4, 10, 12, 7, 1, 9, 5, 11, 2]索引: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15循环第一次执行:i=0,i<数组长度(16),为true,将0索引的数据:0 添加到二维数组的 i/4=0索引的一维数组的 i%4=0索引,i++循环第二次执行:i=1,i<数组长度(16),为true,将1索引的数据:14 添加到二维数组的 i/4=0索引的一维数组的 i%4=1索引,i++循环第三次执行:i=2,i<数组长度(16),为true,将2索引的数据:8 添加到二维数组的 i/4=0索引的一维数组的 i%4=2索引,i++后面的都是以此类推了!直到i<16,为false,循环结束!*/// 3、遍历arr一维数组,依次得到打乱后的每个数据for (int i = 0; i < arr.length; i++) {// 判断当前数据是否为0if (arr[i] == 0) {// 是,则记录空白方块XY的坐标位置x = i / 4;y = i % 4;}// 否,则依次将打乱后的数据添加到二维数组中data[i / 4][i % 4] = arr[i];}}// 初始化游戏主界面private void initJFrame() {// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽603像素,高680像素大小的界面this.setSize(603, 680);// 设置界面标题this.setTitle("奥利gei拼图单机版 v1.0");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 注:取消默认的居中位置,只有取消了才会按照X、Y轴的形式添加组件// 因为只有这样,添加图片的时候才不会一直处于居中位置this.setLayout(null);}// 初始化游戏主界面里的菜单栏private void initJMenu() {// a.创建一个菜单栏对象JMenuBar jMenuBar = new JMenuBar();// b.创建两个菜单栏下的菜单:功能、关于我们JMenu functionJMenu = new JMenu("功能");JMenu aboutUSJMenu = new JMenu("关于我们");// 创建菜单栏下的菜单:更换图片JMenu changeJMenu = new JMenu("更换图片");// 将更换图片的菜单嵌套进功能菜单中functionJMenu.add(changeJMenu);// e.将重新游戏、重新登录、关闭游戏这些栏目放到功能菜单下functionJMenu.add(replayGameItem);functionJMenu.add(replayLoginItem);functionJMenu.add(closeGameItem);// 将美女、动物这两个栏目放到更换图片菜单下changeJMenu.add(belleItem);changeJMenu.add(animalItem);// f.将公众号这个栏目放到关于我们菜单下aboutUSJMenu.add(accountItem);// g.将功能、关于我们这两个菜单放到菜单栏下jMenuBar.add(functionJMenu);jMenuBar.add(aboutUSJMenu);// h.最后将菜单栏放到设置到游戏界面中this.setJMenuBar(jMenuBar);}/*初始化图片到游戏主界面中细节:先加载的图片在上方,后加载的图片会在下方*/private void initImages(String path) {// 先清空原本出现的所有图片this.getContentPane().removeAll();// 判断victory方法的返回结果是否为trueif (victory()) {// 是,则说明玩家胜利了!显示胜利图标!// 根据指定图片的相对路径创建图片对象,并添加到管理容器中JLabel winJLabel = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\win.jpg"));// 设置胜利图片的坐标位置以及宽高winJLabel.setBounds(193, 300, 194, 75);// 将管理容器添加到游戏主界面中this.getContentPane().add(winJLabel);}// 统计步数// 创建一个管理容器对象,用于管理文字(步数: 0)JLabel stepJLabel = new JLabel("步数:" + step);// 设置管理容器的坐标位置stepJLabel.setBounds(40, 20, 400, 50);// 将容器添加到游戏主界面中this.getContentPane().add(stepJLabel);// 再加载图片/*外循环和内循环执行流程解析:外循环第一次执行:当 i = 0 时:i<4,为true,表示添加第一行的四张图片:当 j = 0 时:j<4,为true,此时number=1,表示添加第一行的第一张图片当 j = 1 时:j<4,为true,此时number=2,表示添加第一行的第二张图片当 j = 2 时:j<4,为true,此时number=3,表示添加第一行的第三张图片当 j = 3 时:j<4,为true,此时number=4,表示添加第一行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第二次执行:当 i = 1 时:i<4,为true,表示添加第二行的四张图片:当 j = 0 时:j<4,为true,此时number=5,表示添加第二行的第一张图片当 j = 1 时:j<4,为true,此时number=6,表示添加第二行的第二张图片当 j = 2 时:j<4,为true,此时number=7,表示添加第二行的第三张图片当 j = 3 时:j<4,为true,此时number=8,表示添加第二行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第三次执行:当 i = 2 时:i<4,为true,表示添加第三行的四张图片:当 j = 0 时:j<4,为true,此时number=9,表示添加第三行的第一张图片当 j = 1 时:j<4,为true,此时number=10,表示添加第三行的第二张图片当 j = 2 时:j<4,为true,此时number=11,表示添加第三行的第三张图片当 j = 3 时:j<4,为true,此时number=12,表示添加第三行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第四次执行:当 i = 3 时:i<4,为true,表示添加第四行的四张图片:当 j = 0 时:j<4,为true,此时number=13,表示添加第四行的第一张图片当 j = 1 时:j<4,为true,此时number=14,表示添加第四行的第二张图片当 j = 2 时:j<4,为true,此时number=15,表示添加第四行的第三张图片当 j = 3 时:j<4,为true,此时number=16,表示添加第四行的第四张图片(由于文件中没有16这个命名的图片,因此会添加一个空白)当 j = 4 时:j<4,为false,内循环结束!外循环第五次执行:当 i = 4 时:i<4,为false,外循环结束!*/// 外循环:控制添加4行图片for (int i = 0; i < data.length; i++) { // Y轴:纵向// 内循环:控制每行添加4张图片for (int j = 0; j < data[i].length; j++) { // X轴:横向/*解析:比如二维数组中的数据:[{3, 9, 7, 5} {8, 10, 14, 11} {4, 2, 12, 13} {0, 15, 1, 6} ]二维数组中的一维数组的索引: 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3二维数组的索引: 0 1 2 3(1) 外循环执行第一次:i=0, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(2) 外循环执行第二次:i=1, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(3) 外循环执行第三次:i=2, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(4) 外循环执行第四次:i=3, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(5) 外循环执行第五次:i=0, i<二维数组长度(4), 为false, 外循环结束!!*/// 接收二维数组i索引的一维数组j索引的数据int number = data[i][j];// 1、根据指定的文件相对路径创建一个ImageIcon图片对象ImageIcon imageIcon = new ImageIcon(path + number + ".jpg");// JLabel:管理容器,用于管理图片、文字// 2、创建一个管理容器JLabel对象,用于管理图片对象ImageIcon,// 并将图片对象imageIcon放到管理容器jLabel中。JLabel jLabel = new JLabel(imageIcon);// 3、指定图片位置:XY轴jLabel.setBounds(105 * j + 80, 105 * i + 130, 105, 105);// 4、每添加完一张小图片,给这张小图片添加边框/*0: 表示让图片凸起来1: 表示让图片凹下去*/jLabel.setBorder(new BevelBorder(1));// 5、将管理容器JLabel对象放到游戏主界面中// getContentPane:获取隐藏容器this.getContentPane().add(jLabel);}}// 在所有小图片都添加到界面之后,开始添加背景图片// 根据指定的相对路径创建背景图片对象,并添加到管理容器中JLabel background = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\background.jpg"));// 设置背景图片的坐标位置以及宽高background.setBounds(36, 36, 508, 560);// 将背景图片管理容器添加到界面中this.getContentPane().add(background);// 最后刷新一下游戏界面this.getContentPane().repaint();}
}
import cn.edu.gxufe.ui.GameJFrame;
import cn.edu.gxufe.ui.LoginJFrame;
import cn.edu.gxufe.ui.RegisterJFrame;public class App {public static void main(String[] args) {// 表示程序启动的入口// 如果我们想要开启一个界面,就创建对应界面的对象即可!new GameJFrame(); // 游戏主界面
// new LoginJFrame(); // 登录界面
// new RegisterJFrame(); // 注册界面}
}
我们都知道,每张小图片,都有对应的数据存放在二维数组中
1、我们需要先给整个界面添加键盘监听事件,监听键盘的上下左右键就可以了!
2、需要定义两个变量:x、y,用于记录空白方块所在的索引位置,然后记录空白方块的所在索引。
3、需要获取每个按键的编号,不需要牢记,只需要在按下键盘按键的时候,输出一下编号就可以知道上下左右按键的编号了!
4、要先清空原本已经出现的所有图片,然后加载完所有图片后,再刷新一下界面就好了!
5、向上移动:
空白方块下方的图片
的索引3,1的数据 赋值到 空白方块的
索引2,1中。6、向下移动:
空白方块上方的图片
的索引1,1的数据 赋值到 空白方块
的索引2,1中。7、向左移动:
空白方块右方的图片
的索引2,2的数据 赋值到 空白方块
的索引2,1中。8、向右移动:
空白方块左方的图片
的索引2,0的数据 赋值到 空白方块
的索引2,1中。package cn.edu.gxufe.ui;import javax.swing.*;/*** 游戏主界面类:* 继承父类:JFrame*/
public class GameJFrame extends JFrame implements KeyListener{// 表示游戏相关的逻辑代码都写在这!/*创建二维数组:目的:管理数据加载图片的时候,会根据二位数组中的数据进行加载*/int[][] data = new int[4][4];// 定义x、y变量,用于记录空白方块所在的XY的坐标位置int x = 0;int y = 0;// 创建一个成员的随机数对象,用于生成一个随机数Random rd = new Random();// 定义path变量,用于记录图片的相对路径,方便以后做更换图片、重新游戏等功能的实现String path = "puzzle_game\puzzleimages\girl\girl1\";// 定义一个正确顺序数据的二维数组int[][] win = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 定义一个计数器变量,用于记录玩家拼图移动的步数int step = 0;// c.创建功能菜单下的三个栏目:重新游戏、重新登录、关闭游戏// 更换图片栏目放到后面再写,因为比较复杂JMenuItem replayGameItem = new JMenuItem("重新游戏");JMenuItem replayLoginItem = new JMenuItem("重新登录");JMenuItem closeGameItem = new JMenuItem("关闭游戏");// d.创建关于我们菜单下的一个栏目:公众号JMenuItem accountItem = new JMenuItem("公众号");// 创建更换图片菜单下的两个栏目:美女、动物JMenuItem belleItem = new JMenuItem("美女");JMenuItem animalItem = new JMenuItem("动物");/*提供无参数的构造器需求:初始化一个宽603像素,高680像素的游戏主界面*/public GameJFrame() {// 注:如果当前类没有以下这些方法,就会自动调用父类JFrame的// 初始化游戏主界面initJFrame();// 初始化游戏主界面里的菜单栏initJMenu();// 初始化数据:打乱initData();// 初始化图片到游戏主界面中initImages(path);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用当前GameJFrame类的setVisible方法,设置显示界面this.setVisible(true);}// 初始化数据:打乱private void initData() {// 1、创建一个一维数组,用于存储一些数据int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};// 2、开始打乱// 遍历数组,依次获取到数组中的每个数据for (int i = 0; i < arr.length; i++) {// 循环每执行一次,就生成一个随机索引int rdIndex = rd.nextInt(arr.length);// 每遍历到一个数据,就用临时变量存储一下int temp = arr[i];// 开始交换// 将随机索引位置的数据 放到 当前遍历到的数据的位置arr[i] = arr[rdIndex];// 将当前遍历到的数据 放到 随机索引位置arr[rdIndex] = temp;}/*解析:比如打乱后的一维数组的数据:[0, 14, 8, 3, 13, 15, 6, 4, 10, 12, 7, 1, 9, 5, 11, 2]索引: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15循环第一次执行:i=0,i<数组长度(16),为true,将0索引的数据:0 添加到二维数组的 i/4=0索引的一维数组的 i%4=0索引,i++循环第二次执行:i=1,i<数组长度(16),为true,将1索引的数据:14 添加到二维数组的 i/4=0索引的一维数组的 i%4=1索引,i++循环第三次执行:i=2,i<数组长度(16),为true,将2索引的数据:8 添加到二维数组的 i/4=0索引的一维数组的 i%4=2索引,i++后面的都是以此类推了!直到i<16,为false,循环结束!*/// 3、遍历arr一维数组,依次得到打乱后的每个数据for (int i = 0; i < arr.length; i++) {// 判断当前数据是否为0if (arr[i] == 0) {// 是,则记录空白方块XY的坐标位置x = i / 4;y = i % 4;}// 否,则依次将打乱后的数据添加到二维数组中data[i / 4][i % 4] = arr[i];}}// 初始化游戏主界面private void initJFrame() {// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽603像素,高680像素大小的界面this.setSize(603, 680);// 设置界面标题this.setTitle("奥利gei拼图单机版 v1.0");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 注:取消默认的居中位置,只有取消了才会按照X、Y轴的形式添加组件// 因为只有这样,添加图片的时候才不会一直处于居中位置this.setLayout(null);// 给整个游戏界面添加键盘监听事件this.addKeyListener(this);}// 初始化游戏主界面里的菜单栏private void initJMenu() {// a.创建一个菜单栏对象JMenuBar jMenuBar = new JMenuBar();// b.创建两个菜单栏下的菜单:功能、关于我们JMenu functionJMenu = new JMenu("功能");JMenu aboutUSJMenu = new JMenu("关于我们");// 创建菜单栏下的菜单:更换图片JMenu changeJMenu = new JMenu("更换图片");// 将更换图片的菜单嵌套进功能菜单中functionJMenu.add(changeJMenu);// e.将重新游戏、重新登录、关闭游戏这些栏目放到功能菜单下functionJMenu.add(replayGameItem);functionJMenu.add(replayLoginItem);functionJMenu.add(closeGameItem);// 将美女、动物这两个栏目放到更换图片菜单下changeJMenu.add(belleItem);changeJMenu.add(animalItem);// f.将公众号这个栏目放到关于我们菜单下aboutUSJMenu.add(accountItem);// g.将功能、关于我们这两个菜单放到菜单栏下jMenuBar.add(functionJMenu);jMenuBar.add(aboutUSJMenu);// h.最后将菜单栏放到设置到游戏界面中this.setJMenuBar(jMenuBar);}/*初始化图片到游戏主界面中细节:先加载的图片在上方,后加载的图片会在下方*/private void initImages(String path) {// 先清空原本出现的所有图片this.getContentPane().removeAll();// 判断victory方法的返回结果是否为trueif (victory()) {// 是,则说明玩家胜利了!显示胜利图标!// 根据指定图片的相对路径创建图片对象,并添加到管理容器中JLabel winJLabel = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\win.jpg"));// 设置胜利图片的坐标位置以及宽高winJLabel.setBounds(193, 300, 194, 75);// 将管理容器添加到游戏主界面中this.getContentPane().add(winJLabel);}// 统计步数// 创建一个管理容器对象,用于管理文字(步数: 0)JLabel stepJLabel = new JLabel("步数:" + step);// 设置管理容器的坐标位置stepJLabel.setBounds(40, 20, 400, 50);// 将容器添加到游戏主界面中this.getContentPane().add(stepJLabel);// 再加载图片/*外循环和内循环执行流程解析:外循环第一次执行:当 i = 0 时:i<4,为true,表示添加第一行的四张图片:当 j = 0 时:j<4,为true,此时number=1,表示添加第一行的第一张图片当 j = 1 时:j<4,为true,此时number=2,表示添加第一行的第二张图片当 j = 2 时:j<4,为true,此时number=3,表示添加第一行的第三张图片当 j = 3 时:j<4,为true,此时number=4,表示添加第一行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第二次执行:当 i = 1 时:i<4,为true,表示添加第二行的四张图片:当 j = 0 时:j<4,为true,此时number=5,表示添加第二行的第一张图片当 j = 1 时:j<4,为true,此时number=6,表示添加第二行的第二张图片当 j = 2 时:j<4,为true,此时number=7,表示添加第二行的第三张图片当 j = 3 时:j<4,为true,此时number=8,表示添加第二行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第三次执行:当 i = 2 时:i<4,为true,表示添加第三行的四张图片:当 j = 0 时:j<4,为true,此时number=9,表示添加第三行的第一张图片当 j = 1 时:j<4,为true,此时number=10,表示添加第三行的第二张图片当 j = 2 时:j<4,为true,此时number=11,表示添加第三行的第三张图片当 j = 3 时:j<4,为true,此时number=12,表示添加第三行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第四次执行:当 i = 3 时:i<4,为true,表示添加第四行的四张图片:当 j = 0 时:j<4,为true,此时number=13,表示添加第四行的第一张图片当 j = 1 时:j<4,为true,此时number=14,表示添加第四行的第二张图片当 j = 2 时:j<4,为true,此时number=15,表示添加第四行的第三张图片当 j = 3 时:j<4,为true,此时number=16,表示添加第四行的第四张图片(由于文件中没有16这个命名的图片,因此会添加一个空白)当 j = 4 时:j<4,为false,内循环结束!外循环第五次执行:当 i = 4 时:i<4,为false,外循环结束!*/// 外循环:控制添加4行图片for (int i = 0; i < data.length; i++) { // Y轴:纵向// 内循环:控制每行添加4张图片for (int j = 0; j < data[i].length; j++) { // X轴:横向/*解析:比如二维数组中的数据:[{3, 9, 7, 5} {8, 10, 14, 11} {4, 2, 12, 13} {0, 15, 1, 6} ]二维数组中的一维数组的索引: 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3二维数组的索引: 0 1 2 3(1) 外循环执行第一次:i=0, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(2) 外循环执行第二次:i=1, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(3) 外循环执行第三次:i=2, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(4) 外循环执行第四次:i=3, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(5) 外循环执行第五次:i=0, i<二维数组长度(4), 为false, 外循环结束!!*/// 接收二维数组i索引的一维数组j索引的数据int number = data[i][j];// 1、根据指定的文件相对路径创建一个ImageIcon图片对象ImageIcon imageIcon = new ImageIcon(path + number + ".jpg");// JLabel:管理容器,用于管理图片、文字// 2、创建一个管理容器JLabel对象,用于管理图片对象ImageIcon,// 并将图片对象imageIcon放到管理容器jLabel中。JLabel jLabel = new JLabel(imageIcon);// 3、指定图片位置:XY轴jLabel.setBounds(105 * j + 80, 105 * i + 130, 105, 105);// 4、每添加完一张小图片,给这张小图片添加边框/*0: 表示让图片凸起来1: 表示让图片凹下去*/jLabel.setBorder(new BevelBorder(1));// 5、将管理容器JLabel对象放到游戏主界面中// getContentPane:获取隐藏容器this.getContentPane().add(jLabel);}}// 在所有小图片都添加到界面之后,开始添加背景图片// 根据指定的相对路径创建背景图片对象,并添加到管理容器中JLabel background = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\background.jpg"));// 设置背景图片的坐标位置以及宽高background.setBounds(36, 36, 508, 560);// 将背景图片管理容器添加到界面中this.getContentPane().add(background);// 最后刷新一下游戏界面this.getContentPane().repaint();}// 这个可以不用管@Overridepublic void keyTyped(KeyEvent e) {}/*监听按下键盘按键的事件细节:当按下按键不松开,会不断移动图片*/@Overridepublic void keyPressed(KeyEvent e) {// 获取键盘每个按键的编号int keyCode = e.getKeyCode();// 判断按下的按键是否为Aif (keyCode == 65) {// 需要先清空原本的所有图片this.getContentPane().removeAll();// 加载完整图片// 根据指定图片的相对路径创建一个图片对象,并添加到管理容器中JLabel masterMap = new JLabel(new ImageIcon(path + "all.jpg"));// 设置图片的坐标位置以及宽高masterMap.setBounds(80, 130, 420, 420);// 将管理容器添加到游戏界面中this.getContentPane().add(masterMap);// 在完整图片下面加载背景图片// 根据指定图片的相对路径创建图片对象,并添加到管理容器中JLabel background = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\background.jpg"));// 设置背景图片的坐标位置以及宽高background.setBounds(36, 36, 508, 560);// 将管理容器添加到游戏界面中this.getContentPane().add(background);// 最后刷新一下游戏界面this.getContentPane().repaint();}}/*监听松开键盘按键的事件:当按下按键松开后,会调用该方法*/@Overridepublic void keyReleased(KeyEvent e) {// 获取键盘每个按键的编号int keyCode = e.getKeyCode();// 判断玩家是否已经胜利!if (victory()) {// 是,则说明已经胜利!则直接结束当前方法!不让玩家继续进行拼图操作!return;}/*上下左右按键的编号:左:37、上:38、右:39、下:40*/// 判断松开的按键是否为上if (keyCode == 38) {// 判断空白方块的X轴是否为3if (x == 3) {// 是,说明空白方块下方已没有图片可以向上移动了,则提示!System.out.println("下方已没有图片可以向上移动了");return; // 结束方法!}// 否,则图片向上移动System.out.println("图片向上移动");/*图片向上移动逻辑:把空白方块下方的数字向上移动x, y: 表示空白方块x + 1, y: 表示空白方块下方的数字*/// 将空白方块下方图片的数据 赋值给 空白方块处data[x][y] = data[x + 1][y];// 空白方块下方图片的数据赋值为0data[x + 1][y] = 0;// 将空白方块往下移动x++;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 40) { // 否,则判断松开的按键是否为下// 是,则判断空白方块的X轴是否在0位置if (x == 0) {// 是,说明空白方块的上方已没有图片可以向下移动了,则提示!System.out.println("上方已没有图片可以向下移动了");return; // 结束方法!}// 否,则图片向下移动System.out.println("图片向下移动");/*图片向下移动逻辑:把空白方块上方的数字向下移动x, y: 表示空白方块x - 1, y: 表示空白方块上方的数字*/// 将空白方块上方图片的数据 赋值给 空白方块处data[x][y] = data[x - 1][y];// 空白下方图片的数据赋值为0data[x - 1][y] = 0;// 将空白方块往上移动x--;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 37) { // 否,则判断松开的按键是否为左// 是,则判断空白方块的Y轴是否为3if (y == 3) {// 是,说明空白方块的右方已没有图片可以向左移动了,则提示!System.out.println("右方已没有图片可以向左移动了");return; // 结束方法!}// 否,则图片向左移动System.out.println("图片向左移动");/*图片向左移动逻辑:把空白方块右方的数字向左移动x, y: 表示空白方块x, y + 1: 表示空白方块右方的数字*/// 将空表方块右方图片的数据 赋值给 空白方块处data[x][y] = data[x][y + 1];// 空白右方图片的数据赋值为0data[x][y + 1] = 0;// 将空白方块往右移动y++;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 39) { // 否,则判断松开的按键是否为右// 是,则判断空白方块的Y轴是否在0位置if (y == 0) {// 是,说明空白方块的左方已没有图片可以向右移动了,则提示!System.out.println("左方已没有图片可以向右移动了");return; // 结束方法!}// 否,则图片向右移动System.out.println("图片向右移动");/*图片向右移动逻辑:把空白方块左方的数字向右移动x, y: 表示空白方块x, y - 1: 表示空白方块左方的数字*/// 将空表方块左方图片的数据 赋值给 空白方块处data[x][y] = data[x][y - 1];// 空白左方图片的数据赋值为0data[x][y - 1] = 0;// 将空白方块往左移动y--;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 65) { // 否,则判断松开的按键是否为A// 显示随机打乱的图片initImages(path);} else if (keyCode == 87) { // 否,则判断松开的按键是否为W// 是,则显示拼图完成后的效果!// 其实就是直接把正确顺序数据的新二维数组 赋值给 乱序数据的旧二维数组data = new int[][]{{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 按照二维数组的最新数据重新加载所有小图片initImages(path);}}
}
import cn.edu.gxufe.ui.GameJFrame;
import cn.edu.gxufe.ui.LoginJFrame;
import cn.edu.gxufe.ui.RegisterJFrame;public class App {public static void main(String[] args) {// 表示程序启动的入口// 如果我们想要开启一个界面,就创建对应界面的对象即可!new GameJFrame(); // 游戏主界面
// new LoginJFrame(); // 登录界面
// new RegisterJFrame(); // 注册界面}
}
package cn.edu.gxufe.ui;import javax.swing.*;/*** 游戏主界面类:* 继承父类:JFrame*/
public class GameJFrame extends JFrame implements KeyListener{// 表示游戏相关的逻辑代码都写在这!/*创建二维数组:目的:管理数据加载图片的时候,会根据二位数组中的数据进行加载*/int[][] data = new int[4][4];// 定义x、y变量,用于记录空白方块所在的XY的坐标位置int x = 0;int y = 0;// 创建一个成员的随机数对象,用于生成一个随机数Random rd = new Random();// 定义path变量,用于记录图片的相对路径,方便以后做更换图片、重新游戏等功能的实现String path = "puzzle_game\puzzleimages\girl\girl1\";// 定义一个正确顺序数据的二维数组int[][] win = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 定义一个计数器变量,用于记录玩家拼图移动的步数int step = 0;// c.创建功能菜单下的三个栏目:重新游戏、重新登录、关闭游戏// 更换图片栏目放到后面再写,因为比较复杂JMenuItem replayGameItem = new JMenuItem("重新游戏");JMenuItem replayLoginItem = new JMenuItem("重新登录");JMenuItem closeGameItem = new JMenuItem("关闭游戏");// d.创建关于我们菜单下的一个栏目:公众号JMenuItem accountItem = new JMenuItem("公众号");// 创建更换图片菜单下的两个栏目:美女、动物JMenuItem belleItem = new JMenuItem("美女");JMenuItem animalItem = new JMenuItem("动物");/*提供无参数的构造器需求:初始化一个宽603像素,高680像素的游戏主界面*/public GameJFrame() {// 注:如果当前类没有以下这些方法,就会自动调用父类JFrame的// 初始化游戏主界面initJFrame();// 初始化游戏主界面里的菜单栏initJMenu();// 初始化数据:打乱initData();// 初始化图片到游戏主界面中initImages(path);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用当前GameJFrame类的setVisible方法,设置显示界面this.setVisible(true);}// 初始化数据:打乱private void initData() {// 1、创建一个一维数组,用于存储一些数据int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};// 2、开始打乱// 遍历数组,依次获取到数组中的每个数据for (int i = 0; i < arr.length; i++) {// 循环每执行一次,就生成一个随机索引int rdIndex = rd.nextInt(arr.length);// 每遍历到一个数据,就用临时变量存储一下int temp = arr[i];// 开始交换// 将随机索引位置的数据 放到 当前遍历到的数据的位置arr[i] = arr[rdIndex];// 将当前遍历到的数据 放到 随机索引位置arr[rdIndex] = temp;}/*解析:比如打乱后的一维数组的数据:[0, 14, 8, 3, 13, 15, 6, 4, 10, 12, 7, 1, 9, 5, 11, 2]索引: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15循环第一次执行:i=0,i<数组长度(16),为true,将0索引的数据:0 添加到二维数组的 i/4=0索引的一维数组的 i%4=0索引,i++循环第二次执行:i=1,i<数组长度(16),为true,将1索引的数据:14 添加到二维数组的 i/4=0索引的一维数组的 i%4=1索引,i++循环第三次执行:i=2,i<数组长度(16),为true,将2索引的数据:8 添加到二维数组的 i/4=0索引的一维数组的 i%4=2索引,i++后面的都是以此类推了!直到i<16,为false,循环结束!*/// 3、遍历arr一维数组,依次得到打乱后的每个数据for (int i = 0; i < arr.length; i++) {// 判断当前数据是否为0if (arr[i] == 0) {// 是,则记录空白方块XY的坐标位置x = i / 4;y = i % 4;}// 否,则依次将打乱后的数据添加到二维数组中data[i / 4][i % 4] = arr[i];}}// 初始化游戏主界面private void initJFrame() {// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽603像素,高680像素大小的界面this.setSize(603, 680);// 设置界面标题this.setTitle("奥利gei拼图单机版 v1.0");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 注:取消默认的居中位置,只有取消了才会按照X、Y轴的形式添加组件// 因为只有这样,添加图片的时候才不会一直处于居中位置this.setLayout(null);// 给整个游戏界面添加键盘监听事件this.addKeyListener(this);}// 初始化游戏主界面里的菜单栏private void initJMenu() {// a.创建一个菜单栏对象JMenuBar jMenuBar = new JMenuBar();// b.创建两个菜单栏下的菜单:功能、关于我们JMenu functionJMenu = new JMenu("功能");JMenu aboutUSJMenu = new JMenu("关于我们");// 创建菜单栏下的菜单:更换图片JMenu changeJMenu = new JMenu("更换图片");// 将更换图片的菜单嵌套进功能菜单中functionJMenu.add(changeJMenu);// e.将重新游戏、重新登录、关闭游戏这些栏目放到功能菜单下functionJMenu.add(replayGameItem);functionJMenu.add(replayLoginItem);functionJMenu.add(closeGameItem);// 将美女、动物这两个栏目放到更换图片菜单下changeJMenu.add(belleItem);changeJMenu.add(animalItem);// f.将公众号这个栏目放到关于我们菜单下aboutUSJMenu.add(accountItem);// g.将功能、关于我们这两个菜单放到菜单栏下jMenuBar.add(functionJMenu);jMenuBar.add(aboutUSJMenu);// h.最后将菜单栏放到设置到游戏界面中this.setJMenuBar(jMenuBar);}/*初始化图片到游戏主界面中细节:先加载的图片在上方,后加载的图片会在下方*/private void initImages(String path) {// 先清空原本出现的所有图片this.getContentPane().removeAll();// 判断victory方法的返回结果是否为trueif (victory()) {// 是,则说明玩家胜利了!显示胜利图标!// 根据指定图片的相对路径创建图片对象,并添加到管理容器中JLabel winJLabel = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\win.jpg"));// 设置胜利图片的坐标位置以及宽高winJLabel.setBounds(193, 300, 194, 75);// 将管理容器添加到游戏主界面中this.getContentPane().add(winJLabel);}// 统计步数// 创建一个管理容器对象,用于管理文字(步数: 0)JLabel stepJLabel = new JLabel("步数:" + step);// 设置管理容器的坐标位置stepJLabel.setBounds(40, 20, 400, 50);// 将容器添加到游戏主界面中this.getContentPane().add(stepJLabel);// 再加载图片/*外循环和内循环执行流程解析:外循环第一次执行:当 i = 0 时:i<4,为true,表示添加第一行的四张图片:当 j = 0 时:j<4,为true,此时number=1,表示添加第一行的第一张图片当 j = 1 时:j<4,为true,此时number=2,表示添加第一行的第二张图片当 j = 2 时:j<4,为true,此时number=3,表示添加第一行的第三张图片当 j = 3 时:j<4,为true,此时number=4,表示添加第一行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第二次执行:当 i = 1 时:i<4,为true,表示添加第二行的四张图片:当 j = 0 时:j<4,为true,此时number=5,表示添加第二行的第一张图片当 j = 1 时:j<4,为true,此时number=6,表示添加第二行的第二张图片当 j = 2 时:j<4,为true,此时number=7,表示添加第二行的第三张图片当 j = 3 时:j<4,为true,此时number=8,表示添加第二行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第三次执行:当 i = 2 时:i<4,为true,表示添加第三行的四张图片:当 j = 0 时:j<4,为true,此时number=9,表示添加第三行的第一张图片当 j = 1 时:j<4,为true,此时number=10,表示添加第三行的第二张图片当 j = 2 时:j<4,为true,此时number=11,表示添加第三行的第三张图片当 j = 3 时:j<4,为true,此时number=12,表示添加第三行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第四次执行:当 i = 3 时:i<4,为true,表示添加第四行的四张图片:当 j = 0 时:j<4,为true,此时number=13,表示添加第四行的第一张图片当 j = 1 时:j<4,为true,此时number=14,表示添加第四行的第二张图片当 j = 2 时:j<4,为true,此时number=15,表示添加第四行的第三张图片当 j = 3 时:j<4,为true,此时number=16,表示添加第四行的第四张图片(由于文件中没有16这个命名的图片,因此会添加一个空白)当 j = 4 时:j<4,为false,内循环结束!外循环第五次执行:当 i = 4 时:i<4,为false,外循环结束!*/// 外循环:控制添加4行图片for (int i = 0; i < data.length; i++) { // Y轴:纵向// 内循环:控制每行添加4张图片for (int j = 0; j < data[i].length; j++) { // X轴:横向/*解析:比如二维数组中的数据:[{3, 9, 7, 5} {8, 10, 14, 11} {4, 2, 12, 13} {0, 15, 1, 6} ]二维数组中的一维数组的索引: 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3二维数组的索引: 0 1 2 3(1) 外循环执行第一次:i=0, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(2) 外循环执行第二次:i=1, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(3) 外循环执行第三次:i=2, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(4) 外循环执行第四次:i=3, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(5) 外循环执行第五次:i=0, i<二维数组长度(4), 为false, 外循环结束!!*/// 接收二维数组i索引的一维数组j索引的数据int number = data[i][j];// 1、根据指定的文件相对路径创建一个ImageIcon图片对象ImageIcon imageIcon = new ImageIcon(path + number + ".jpg");// JLabel:管理容器,用于管理图片、文字// 2、创建一个管理容器JLabel对象,用于管理图片对象ImageIcon,// 并将图片对象imageIcon放到管理容器jLabel中。JLabel jLabel = new JLabel(imageIcon);// 3、指定图片位置:XY轴jLabel.setBounds(105 * j + 80, 105 * i + 130, 105, 105);// 4、每添加完一张小图片,给这张小图片添加边框/*0: 表示让图片凸起来1: 表示让图片凹下去*/jLabel.setBorder(new BevelBorder(1));// 5、将管理容器JLabel对象放到游戏主界面中// getContentPane:获取隐藏容器this.getContentPane().add(jLabel);}}// 在所有小图片都添加到界面之后,开始添加背景图片// 根据指定的相对路径创建背景图片对象,并添加到管理容器中JLabel background = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\background.jpg"));// 设置背景图片的坐标位置以及宽高background.setBounds(36, 36, 508, 560);// 将背景图片管理容器添加到界面中this.getContentPane().add(background);// 最后刷新一下游戏界面this.getContentPane().repaint();}// 这个可以不用管@Overridepublic void keyTyped(KeyEvent e) {}/*监听按下键盘按键的事件细节:当按下按键不松开,会不断移动图片*/@Overridepublic void keyPressed(KeyEvent e) {// 获取键盘每个按键的编号int keyCode = e.getKeyCode();// 判断按下的按键是否为Aif (keyCode == 65) {// 需要先清空原本的所有图片this.getContentPane().removeAll();// 加载完整图片// 根据指定图片的相对路径创建一个图片对象,并添加到管理容器中JLabel masterMap = new JLabel(new ImageIcon(path + "all.jpg"));// 设置图片的坐标位置以及宽高masterMap.setBounds(80, 130, 420, 420);// 将管理容器添加到游戏界面中this.getContentPane().add(masterMap);// 在完整图片下面加载背景图片// 根据指定图片的相对路径创建图片对象,并添加到管理容器中JLabel background = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\background.jpg"));// 设置背景图片的坐标位置以及宽高background.setBounds(36, 36, 508, 560);// 将管理容器添加到游戏界面中this.getContentPane().add(background);// 最后刷新一下游戏界面this.getContentPane().repaint();}}/*监听松开键盘按键的事件:当按下按键松开后,会调用该方法*/@Overridepublic void keyReleased(KeyEvent e) {// 获取键盘每个按键的编号int keyCode = e.getKeyCode();// 判断玩家是否已经胜利!if (victory()) {// 是,则说明已经胜利!则直接结束当前方法!不让玩家继续进行拼图操作!return;}/*上下左右按键的编号:左:37、上:38、右:39、下:40*/// 判断松开的按键是否为上if (keyCode == 38) {// 判断空白方块的X轴是否为3if (x == 3) {// 是,说明空白方块下方已没有图片可以向上移动了,则提示!System.out.println("下方已没有图片可以向上移动了");return; // 结束方法!}// 否,则图片向上移动System.out.println("图片向上移动");/*图片向上移动逻辑:把空白方块下方的数字向上移动x, y: 表示空白方块x + 1, y: 表示空白方块下方的数字*/// 将空白方块下方图片的数据 赋值给 空白方块处data[x][y] = data[x + 1][y];// 空白方块下方图片的数据赋值为0data[x + 1][y] = 0;// 将空白方块往下移动x++;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 40) { // 否,则判断松开的按键是否为下// 是,则判断空白方块的X轴是否在0位置if (x == 0) {// 是,说明空白方块的上方已没有图片可以向下移动了,则提示!System.out.println("上方已没有图片可以向下移动了");return; // 结束方法!}// 否,则图片向下移动System.out.println("图片向下移动");/*图片向下移动逻辑:把空白方块上方的数字向下移动x, y: 表示空白方块x - 1, y: 表示空白方块上方的数字*/// 将空白方块上方图片的数据 赋值给 空白方块处data[x][y] = data[x - 1][y];// 空白下方图片的数据赋值为0data[x - 1][y] = 0;// 将空白方块往上移动x--;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 37) { // 否,则判断松开的按键是否为左// 是,则判断空白方块的Y轴是否为3if (y == 3) {// 是,说明空白方块的右方已没有图片可以向左移动了,则提示!System.out.println("右方已没有图片可以向左移动了");return; // 结束方法!}// 否,则图片向左移动System.out.println("图片向左移动");/*图片向左移动逻辑:把空白方块右方的数字向左移动x, y: 表示空白方块x, y + 1: 表示空白方块右方的数字*/// 将空表方块右方图片的数据 赋值给 空白方块处data[x][y] = data[x][y + 1];// 空白右方图片的数据赋值为0data[x][y + 1] = 0;// 将空白方块往右移动y++;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 39) { // 否,则判断松开的按键是否为右// 是,则判断空白方块的Y轴是否在0位置if (y == 0) {// 是,说明空白方块的左方已没有图片可以向右移动了,则提示!System.out.println("左方已没有图片可以向右移动了");return; // 结束方法!}// 否,则图片向右移动System.out.println("图片向右移动");/*图片向右移动逻辑:把空白方块左方的数字向右移动x, y: 表示空白方块x, y - 1: 表示空白方块左方的数字*/// 将空表方块左方图片的数据 赋值给 空白方块处data[x][y] = data[x][y - 1];// 空白左方图片的数据赋值为0data[x][y - 1] = 0;// 将空白方块往左移动y--;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 65) { // 否,则判断松开的按键是否为A// 显示随机打乱的图片initImages(path);} else if (keyCode == 87) { // 否,则判断松开的按键是否为W// 是,则显示拼图完成后的效果!// 其实就是直接把正确顺序数据的新二维数组 赋值给 乱序数据的旧二维数组data = new int[][]{{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 按照二维数组的最新数据重新加载所有小图片initImages(path);}}
}
import cn.edu.gxufe.ui.GameJFrame;
import cn.edu.gxufe.ui.LoginJFrame;
import cn.edu.gxufe.ui.RegisterJFrame;public class App {public static void main(String[] args) {// 表示程序启动的入口// 如果我们想要开启一个界面,就创建对应界面的对象即可!new GameJFrame(); // 游戏主界面
// new LoginJFrame(); // 登录界面
// new RegisterJFrame(); // 注册界面}
}
package cn.edu.gxufe.ui;import javax.swing.*;/*** 游戏主界面类:* 继承父类:JFrame*/
public class GameJFrame extends JFrame implements KeyListener{// 表示游戏相关的逻辑代码都写在这!/*创建二维数组:目的:管理数据加载图片的时候,会根据二位数组中的数据进行加载*/int[][] data = new int[4][4];// 定义x、y变量,用于记录空白方块所在的XY的坐标位置int x = 0;int y = 0;// 创建一个成员的随机数对象,用于生成一个随机数Random rd = new Random();// 定义path变量,用于记录图片的相对路径,方便以后做更换图片、重新游戏等功能的实现String path = "puzzle_game\puzzleimages\girl\girl1\";// 定义一个正确顺序数据的二维数组int[][] win = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 定义一个计数器变量,用于记录玩家拼图移动的步数int step = 0;// c.创建功能菜单下的三个栏目:重新游戏、重新登录、关闭游戏// 更换图片栏目放到后面再写,因为比较复杂JMenuItem replayGameItem = new JMenuItem("重新游戏");JMenuItem replayLoginItem = new JMenuItem("重新登录");JMenuItem closeGameItem = new JMenuItem("关闭游戏");// d.创建关于我们菜单下的一个栏目:公众号JMenuItem accountItem = new JMenuItem("公众号");// 创建更换图片菜单下的两个栏目:美女、动物JMenuItem belleItem = new JMenuItem("美女");JMenuItem animalItem = new JMenuItem("动物");/*提供无参数的构造器需求:初始化一个宽603像素,高680像素的游戏主界面*/public GameJFrame() {// 注:如果当前类没有以下这些方法,就会自动调用父类JFrame的// 初始化游戏主界面initJFrame();// 初始化游戏主界面里的菜单栏initJMenu();// 初始化数据:打乱initData();// 初始化图片到游戏主界面中initImages(path);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用当前GameJFrame类的setVisible方法,设置显示界面this.setVisible(true);}// 初始化数据:打乱private void initData() {// 1、创建一个一维数组,用于存储一些数据int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};// 2、开始打乱// 遍历数组,依次获取到数组中的每个数据for (int i = 0; i < arr.length; i++) {// 循环每执行一次,就生成一个随机索引int rdIndex = rd.nextInt(arr.length);// 每遍历到一个数据,就用临时变量存储一下int temp = arr[i];// 开始交换// 将随机索引位置的数据 放到 当前遍历到的数据的位置arr[i] = arr[rdIndex];// 将当前遍历到的数据 放到 随机索引位置arr[rdIndex] = temp;}/*解析:比如打乱后的一维数组的数据:[0, 14, 8, 3, 13, 15, 6, 4, 10, 12, 7, 1, 9, 5, 11, 2]索引: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15循环第一次执行:i=0,i<数组长度(16),为true,将0索引的数据:0 添加到二维数组的 i/4=0索引的一维数组的 i%4=0索引,i++循环第二次执行:i=1,i<数组长度(16),为true,将1索引的数据:14 添加到二维数组的 i/4=0索引的一维数组的 i%4=1索引,i++循环第三次执行:i=2,i<数组长度(16),为true,将2索引的数据:8 添加到二维数组的 i/4=0索引的一维数组的 i%4=2索引,i++后面的都是以此类推了!直到i<16,为false,循环结束!*/// 3、遍历arr一维数组,依次得到打乱后的每个数据for (int i = 0; i < arr.length; i++) {// 判断当前数据是否为0if (arr[i] == 0) {// 是,则记录空白方块XY的坐标位置x = i / 4;y = i % 4;}// 否,则依次将打乱后的数据添加到二维数组中data[i / 4][i % 4] = arr[i];}}// 初始化游戏主界面private void initJFrame() {// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽603像素,高680像素大小的界面this.setSize(603, 680);// 设置界面标题this.setTitle("奥利gei拼图单机版 v1.0");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 注:取消默认的居中位置,只有取消了才会按照X、Y轴的形式添加组件// 因为只有这样,添加图片的时候才不会一直处于居中位置this.setLayout(null);// 给整个游戏界面添加键盘监听事件this.addKeyListener(this);}// 初始化游戏主界面里的菜单栏private void initJMenu() {// a.创建一个菜单栏对象JMenuBar jMenuBar = new JMenuBar();// b.创建两个菜单栏下的菜单:功能、关于我们JMenu functionJMenu = new JMenu("功能");JMenu aboutUSJMenu = new JMenu("关于我们");// 创建菜单栏下的菜单:更换图片JMenu changeJMenu = new JMenu("更换图片");// 将更换图片的菜单嵌套进功能菜单中functionJMenu.add(changeJMenu);// e.将重新游戏、重新登录、关闭游戏这些栏目放到功能菜单下functionJMenu.add(replayGameItem);functionJMenu.add(replayLoginItem);functionJMenu.add(closeGameItem);// 将美女、动物这两个栏目放到更换图片菜单下changeJMenu.add(belleItem);changeJMenu.add(animalItem);// f.将公众号这个栏目放到关于我们菜单下aboutUSJMenu.add(accountItem);// g.将功能、关于我们这两个菜单放到菜单栏下jMenuBar.add(functionJMenu);jMenuBar.add(aboutUSJMenu);// h.最后将菜单栏放到设置到游戏界面中this.setJMenuBar(jMenuBar);}/*初始化图片到游戏主界面中细节:先加载的图片在上方,后加载的图片会在下方*/private void initImages(String path) {// 先清空原本出现的所有图片this.getContentPane().removeAll();// 判断victory方法的返回结果是否为trueif (victory()) {// 是,则说明玩家胜利了!显示胜利图标!// 根据指定图片的相对路径创建图片对象,并添加到管理容器中JLabel winJLabel = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\win.jpg"));// 设置胜利图片的坐标位置以及宽高winJLabel.setBounds(193, 300, 194, 75);// 将管理容器添加到游戏主界面中this.getContentPane().add(winJLabel);}// 统计步数// 创建一个管理容器对象,用于管理文字(步数: 0)JLabel stepJLabel = new JLabel("步数:" + step);// 设置管理容器的坐标位置stepJLabel.setBounds(40, 20, 400, 50);// 将容器添加到游戏主界面中this.getContentPane().add(stepJLabel);// 再加载图片/*外循环和内循环执行流程解析:外循环第一次执行:当 i = 0 时:i<4,为true,表示添加第一行的四张图片:当 j = 0 时:j<4,为true,此时number=1,表示添加第一行的第一张图片当 j = 1 时:j<4,为true,此时number=2,表示添加第一行的第二张图片当 j = 2 时:j<4,为true,此时number=3,表示添加第一行的第三张图片当 j = 3 时:j<4,为true,此时number=4,表示添加第一行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第二次执行:当 i = 1 时:i<4,为true,表示添加第二行的四张图片:当 j = 0 时:j<4,为true,此时number=5,表示添加第二行的第一张图片当 j = 1 时:j<4,为true,此时number=6,表示添加第二行的第二张图片当 j = 2 时:j<4,为true,此时number=7,表示添加第二行的第三张图片当 j = 3 时:j<4,为true,此时number=8,表示添加第二行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第三次执行:当 i = 2 时:i<4,为true,表示添加第三行的四张图片:当 j = 0 时:j<4,为true,此时number=9,表示添加第三行的第一张图片当 j = 1 时:j<4,为true,此时number=10,表示添加第三行的第二张图片当 j = 2 时:j<4,为true,此时number=11,表示添加第三行的第三张图片当 j = 3 时:j<4,为true,此时number=12,表示添加第三行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第四次执行:当 i = 3 时:i<4,为true,表示添加第四行的四张图片:当 j = 0 时:j<4,为true,此时number=13,表示添加第四行的第一张图片当 j = 1 时:j<4,为true,此时number=14,表示添加第四行的第二张图片当 j = 2 时:j<4,为true,此时number=15,表示添加第四行的第三张图片当 j = 3 时:j<4,为true,此时number=16,表示添加第四行的第四张图片(由于文件中没有16这个命名的图片,因此会添加一个空白)当 j = 4 时:j<4,为false,内循环结束!外循环第五次执行:当 i = 4 时:i<4,为false,外循环结束!*/// 外循环:控制添加4行图片for (int i = 0; i < data.length; i++) { // Y轴:纵向// 内循环:控制每行添加4张图片for (int j = 0; j < data[i].length; j++) { // X轴:横向/*解析:比如二维数组中的数据:[{3, 9, 7, 5} {8, 10, 14, 11} {4, 2, 12, 13} {0, 15, 1, 6} ]二维数组中的一维数组的索引: 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3二维数组的索引: 0 1 2 3(1) 外循环执行第一次:i=0, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(2) 外循环执行第二次:i=1, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(3) 外循环执行第三次:i=2, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(4) 外循环执行第四次:i=3, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(5) 外循环执行第五次:i=0, i<二维数组长度(4), 为false, 外循环结束!!*/// 接收二维数组i索引的一维数组j索引的数据int number = data[i][j];// 1、根据指定的文件相对路径创建一个ImageIcon图片对象ImageIcon imageIcon = new ImageIcon(path + number + ".jpg");// JLabel:管理容器,用于管理图片、文字// 2、创建一个管理容器JLabel对象,用于管理图片对象ImageIcon,// 并将图片对象imageIcon放到管理容器jLabel中。JLabel jLabel = new JLabel(imageIcon);// 3、指定图片位置:XY轴jLabel.setBounds(105 * j + 80, 105 * i + 130, 105, 105);// 4、每添加完一张小图片,给这张小图片添加边框/*0: 表示让图片凸起来1: 表示让图片凹下去*/jLabel.setBorder(new BevelBorder(1));// 5、将管理容器JLabel对象放到游戏主界面中// getContentPane:获取隐藏容器this.getContentPane().add(jLabel);}}// 在所有小图片都添加到界面之后,开始添加背景图片// 根据指定的相对路径创建背景图片对象,并添加到管理容器中JLabel background = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\background.jpg"));// 设置背景图片的坐标位置以及宽高background.setBounds(36, 36, 508, 560);// 将背景图片管理容器添加到界面中this.getContentPane().add(background);// 最后刷新一下游戏界面this.getContentPane().repaint();}// 这个可以不用管@Overridepublic void keyTyped(KeyEvent e) {}/*监听按下键盘按键的事件细节:当按下按键不松开,会不断移动图片*/@Overridepublic void keyPressed(KeyEvent e) {// 获取键盘每个按键的编号int keyCode = e.getKeyCode();// 判断按下的按键是否为Aif (keyCode == 65) {// 需要先清空原本的所有图片this.getContentPane().removeAll();// 加载完整图片// 根据指定图片的相对路径创建一个图片对象,并添加到管理容器中JLabel masterMap = new JLabel(new ImageIcon(path + "all.jpg"));// 设置图片的坐标位置以及宽高masterMap.setBounds(80, 130, 420, 420);// 将管理容器添加到游戏界面中this.getContentPane().add(masterMap);// 在完整图片下面加载背景图片// 根据指定图片的相对路径创建图片对象,并添加到管理容器中JLabel background = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\background.jpg"));// 设置背景图片的坐标位置以及宽高background.setBounds(36, 36, 508, 560);// 将管理容器添加到游戏界面中this.getContentPane().add(background);// 最后刷新一下游戏界面this.getContentPane().repaint();}}/*监听松开键盘按键的事件:当按下按键松开后,会调用该方法*/@Overridepublic void keyReleased(KeyEvent e) {// 获取键盘每个按键的编号int keyCode = e.getKeyCode();// 判断玩家是否已经胜利!if (victory()) {// 是,则说明已经胜利!则直接结束当前方法!不让玩家继续进行拼图操作!return;}/*上下左右按键的编号:左:37、上:38、右:39、下:40*/// 判断松开的按键是否为上if (keyCode == 38) {// 判断空白方块的X轴是否为3if (x == 3) {// 是,说明空白方块下方已没有图片可以向上移动了,则提示!System.out.println("下方已没有图片可以向上移动了");return; // 结束方法!}// 否,则图片向上移动System.out.println("图片向上移动");/*图片向上移动逻辑:把空白方块下方的数字向上移动x, y: 表示空白方块x + 1, y: 表示空白方块下方的数字*/// 将空白方块下方图片的数据 赋值给 空白方块处data[x][y] = data[x + 1][y];// 空白方块下方图片的数据赋值为0data[x + 1][y] = 0;// 将空白方块往下移动x++;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 40) { // 否,则判断松开的按键是否为下// 是,则判断空白方块的X轴是否在0位置if (x == 0) {// 是,说明空白方块的上方已没有图片可以向下移动了,则提示!System.out.println("上方已没有图片可以向下移动了");return; // 结束方法!}// 否,则图片向下移动System.out.println("图片向下移动");/*图片向下移动逻辑:把空白方块上方的数字向下移动x, y: 表示空白方块x - 1, y: 表示空白方块上方的数字*/// 将空白方块上方图片的数据 赋值给 空白方块处data[x][y] = data[x - 1][y];// 空白下方图片的数据赋值为0data[x - 1][y] = 0;// 将空白方块往上移动x--;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 37) { // 否,则判断松开的按键是否为左// 是,则判断空白方块的Y轴是否为3if (y == 3) {// 是,说明空白方块的右方已没有图片可以向左移动了,则提示!System.out.println("右方已没有图片可以向左移动了");return; // 结束方法!}// 否,则图片向左移动System.out.println("图片向左移动");/*图片向左移动逻辑:把空白方块右方的数字向左移动x, y: 表示空白方块x, y + 1: 表示空白方块右方的数字*/// 将空表方块右方图片的数据 赋值给 空白方块处data[x][y] = data[x][y + 1];// 空白右方图片的数据赋值为0data[x][y + 1] = 0;// 将空白方块往右移动y++;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 39) { // 否,则判断松开的按键是否为右// 是,则判断空白方块的Y轴是否在0位置if (y == 0) {// 是,说明空白方块的左方已没有图片可以向右移动了,则提示!System.out.println("左方已没有图片可以向右移动了");return; // 结束方法!}// 否,则图片向右移动System.out.println("图片向右移动");/*图片向右移动逻辑:把空白方块左方的数字向右移动x, y: 表示空白方块x, y - 1: 表示空白方块左方的数字*/// 将空表方块左方图片的数据 赋值给 空白方块处data[x][y] = data[x][y - 1];// 空白左方图片的数据赋值为0data[x][y - 1] = 0;// 将空白方块往左移动y--;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 65) { // 否,则判断松开的按键是否为A// 显示随机打乱的图片initImages(path);} else if (keyCode == 87) { // 否,则判断松开的按键是否为W// 是,则显示拼图完成后的效果!// 其实就是直接把正确顺序数据的新二维数组 赋值给 乱序数据的旧二维数组data = new int[][]{{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 按照二维数组的最新数据重新加载所有小图片initImages(path);}}
}
import cn.edu.gxufe.ui.GameJFrame;
import cn.edu.gxufe.ui.LoginJFrame;
import cn.edu.gxufe.ui.RegisterJFrame;public class App {public static void main(String[] args) {// 表示程序启动的入口// 如果我们想要开启一个界面,就创建对应界面的对象即可!new GameJFrame(); // 游戏主界面
// new LoginJFrame(); // 登录界面
// new RegisterJFrame(); // 注册界面}
}
先玩一下
不想玩了!直接作弊通关吧!
当玩家拼图完成了!则显示一个胜利图标!
例如:
package cn.edu.gxufe.ui;import javax.swing.*;/*** 游戏主界面类:* 继承父类:JFrame*/
public class GameJFrame extends JFrame implements KeyListener{// 表示游戏相关的逻辑代码都写在这!/*创建二维数组:目的:管理数据加载图片的时候,会根据二位数组中的数据进行加载*/int[][] data = new int[4][4];// 定义x、y变量,用于记录空白方块所在的XY的坐标位置int x = 0;int y = 0;// 创建一个成员的随机数对象,用于生成一个随机数Random rd = new Random();// 定义path变量,用于记录图片的相对路径,方便以后做更换图片、重新游戏等功能的实现String path = "puzzle_game\puzzleimages\girl\girl1\";// 定义一个正确顺序数据的二维数组int[][] win = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 定义一个计数器变量,用于记录玩家拼图移动的步数int step = 0;// c.创建功能菜单下的三个栏目:重新游戏、重新登录、关闭游戏// 更换图片栏目放到后面再写,因为比较复杂JMenuItem replayGameItem = new JMenuItem("重新游戏");JMenuItem replayLoginItem = new JMenuItem("重新登录");JMenuItem closeGameItem = new JMenuItem("关闭游戏");// d.创建关于我们菜单下的一个栏目:公众号JMenuItem accountItem = new JMenuItem("公众号");// 创建更换图片菜单下的两个栏目:美女、动物JMenuItem belleItem = new JMenuItem("美女");JMenuItem animalItem = new JMenuItem("动物");/*提供无参数的构造器需求:初始化一个宽603像素,高680像素的游戏主界面*/public GameJFrame() {// 注:如果当前类没有以下这些方法,就会自动调用父类JFrame的// 初始化游戏主界面initJFrame();// 初始化游戏主界面里的菜单栏initJMenu();// 初始化数据:打乱initData();// 初始化图片到游戏主界面中initImages(path);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用当前GameJFrame类的setVisible方法,设置显示界面this.setVisible(true);}// 初始化数据:打乱private void initData() {// 1、创建一个一维数组,用于存储一些数据int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};// 2、开始打乱// 遍历数组,依次获取到数组中的每个数据for (int i = 0; i < arr.length; i++) {// 循环每执行一次,就生成一个随机索引int rdIndex = rd.nextInt(arr.length);// 每遍历到一个数据,就用临时变量存储一下int temp = arr[i];// 开始交换// 将随机索引位置的数据 放到 当前遍历到的数据的位置arr[i] = arr[rdIndex];// 将当前遍历到的数据 放到 随机索引位置arr[rdIndex] = temp;}/*解析:比如打乱后的一维数组的数据:[0, 14, 8, 3, 13, 15, 6, 4, 10, 12, 7, 1, 9, 5, 11, 2]索引: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15循环第一次执行:i=0,i<数组长度(16),为true,将0索引的数据:0 添加到二维数组的 i/4=0索引的一维数组的 i%4=0索引,i++循环第二次执行:i=1,i<数组长度(16),为true,将1索引的数据:14 添加到二维数组的 i/4=0索引的一维数组的 i%4=1索引,i++循环第三次执行:i=2,i<数组长度(16),为true,将2索引的数据:8 添加到二维数组的 i/4=0索引的一维数组的 i%4=2索引,i++后面的都是以此类推了!直到i<16,为false,循环结束!*/// 3、遍历arr一维数组,依次得到打乱后的每个数据for (int i = 0; i < arr.length; i++) {// 判断当前数据是否为0if (arr[i] == 0) {// 是,则记录空白方块XY的坐标位置x = i / 4;y = i % 4;}// 否,则依次将打乱后的数据添加到二维数组中data[i / 4][i % 4] = arr[i];}}// 初始化游戏主界面private void initJFrame() {// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽603像素,高680像素大小的界面this.setSize(603, 680);// 设置界面标题this.setTitle("奥利gei拼图单机版 v1.0");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 注:取消默认的居中位置,只有取消了才会按照X、Y轴的形式添加组件// 因为只有这样,添加图片的时候才不会一直处于居中位置this.setLayout(null);// 给整个游戏界面添加键盘监听事件this.addKeyListener(this);}// 初始化游戏主界面里的菜单栏private void initJMenu() {// a.创建一个菜单栏对象JMenuBar jMenuBar = new JMenuBar();// b.创建两个菜单栏下的菜单:功能、关于我们JMenu functionJMenu = new JMenu("功能");JMenu aboutUSJMenu = new JMenu("关于我们");// 创建菜单栏下的菜单:更换图片JMenu changeJMenu = new JMenu("更换图片");// 将更换图片的菜单嵌套进功能菜单中functionJMenu.add(changeJMenu);// e.将重新游戏、重新登录、关闭游戏这些栏目放到功能菜单下functionJMenu.add(replayGameItem);functionJMenu.add(replayLoginItem);functionJMenu.add(closeGameItem);// 将美女、动物这两个栏目放到更换图片菜单下changeJMenu.add(belleItem);changeJMenu.add(animalItem);// f.将公众号这个栏目放到关于我们菜单下aboutUSJMenu.add(accountItem);// g.将功能、关于我们这两个菜单放到菜单栏下jMenuBar.add(functionJMenu);jMenuBar.add(aboutUSJMenu);// h.最后将菜单栏放到设置到游戏界面中this.setJMenuBar(jMenuBar);}/*初始化图片到游戏主界面中细节:先加载的图片在上方,后加载的图片会在下方*/private void initImages(String path) {// 先清空原本出现的所有图片this.getContentPane().removeAll();// 判断victory方法的返回结果是否为trueif (victory()) {// 是,则说明玩家胜利了!显示胜利图标!// 根据指定图片的相对路径创建图片对象,并添加到管理容器中JLabel winJLabel = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\win.jpg"));// 设置胜利图片的坐标位置以及宽高winJLabel.setBounds(193, 300, 194, 75);// 将管理容器添加到游戏主界面中this.getContentPane().add(winJLabel);}// 统计步数// 创建一个管理容器对象,用于管理文字(步数: 0)JLabel stepJLabel = new JLabel("步数:" + step);// 设置管理容器的坐标位置stepJLabel.setBounds(40, 20, 400, 50);// 将容器添加到游戏主界面中this.getContentPane().add(stepJLabel);// 再加载图片/*外循环和内循环执行流程解析:外循环第一次执行:当 i = 0 时:i<4,为true,表示添加第一行的四张图片:当 j = 0 时:j<4,为true,此时number=1,表示添加第一行的第一张图片当 j = 1 时:j<4,为true,此时number=2,表示添加第一行的第二张图片当 j = 2 时:j<4,为true,此时number=3,表示添加第一行的第三张图片当 j = 3 时:j<4,为true,此时number=4,表示添加第一行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第二次执行:当 i = 1 时:i<4,为true,表示添加第二行的四张图片:当 j = 0 时:j<4,为true,此时number=5,表示添加第二行的第一张图片当 j = 1 时:j<4,为true,此时number=6,表示添加第二行的第二张图片当 j = 2 时:j<4,为true,此时number=7,表示添加第二行的第三张图片当 j = 3 时:j<4,为true,此时number=8,表示添加第二行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第三次执行:当 i = 2 时:i<4,为true,表示添加第三行的四张图片:当 j = 0 时:j<4,为true,此时number=9,表示添加第三行的第一张图片当 j = 1 时:j<4,为true,此时number=10,表示添加第三行的第二张图片当 j = 2 时:j<4,为true,此时number=11,表示添加第三行的第三张图片当 j = 3 时:j<4,为true,此时number=12,表示添加第三行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第四次执行:当 i = 3 时:i<4,为true,表示添加第四行的四张图片:当 j = 0 时:j<4,为true,此时number=13,表示添加第四行的第一张图片当 j = 1 时:j<4,为true,此时number=14,表示添加第四行的第二张图片当 j = 2 时:j<4,为true,此时number=15,表示添加第四行的第三张图片当 j = 3 时:j<4,为true,此时number=16,表示添加第四行的第四张图片(由于文件中没有16这个命名的图片,因此会添加一个空白)当 j = 4 时:j<4,为false,内循环结束!外循环第五次执行:当 i = 4 时:i<4,为false,外循环结束!*/// 外循环:控制添加4行图片for (int i = 0; i < data.length; i++) { // Y轴:纵向// 内循环:控制每行添加4张图片for (int j = 0; j < data[i].length; j++) { // X轴:横向/*解析:比如二维数组中的数据:[{3, 9, 7, 5} {8, 10, 14, 11} {4, 2, 12, 13} {0, 15, 1, 6} ]二维数组中的一维数组的索引: 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3二维数组的索引: 0 1 2 3(1) 外循环执行第一次:i=0, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(2) 外循环执行第二次:i=1, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(3) 外循环执行第三次:i=2, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(4) 外循环执行第四次:i=3, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(5) 外循环执行第五次:i=0, i<二维数组长度(4), 为false, 外循环结束!!*/// 接收二维数组i索引的一维数组j索引的数据int number = data[i][j];// 1、根据指定的文件相对路径创建一个ImageIcon图片对象ImageIcon imageIcon = new ImageIcon(path + number + ".jpg");// JLabel:管理容器,用于管理图片、文字// 2、创建一个管理容器JLabel对象,用于管理图片对象ImageIcon,// 并将图片对象imageIcon放到管理容器jLabel中。JLabel jLabel = new JLabel(imageIcon);// 3、指定图片位置:XY轴jLabel.setBounds(105 * j + 80, 105 * i + 130, 105, 105);// 4、每添加完一张小图片,给这张小图片添加边框/*0: 表示让图片凸起来1: 表示让图片凹下去*/jLabel.setBorder(new BevelBorder(1));// 5、将管理容器JLabel对象放到游戏主界面中// getContentPane:获取隐藏容器this.getContentPane().add(jLabel);}}// 在所有小图片都添加到界面之后,开始添加背景图片// 根据指定的相对路径创建背景图片对象,并添加到管理容器中JLabel background = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\background.jpg"));// 设置背景图片的坐标位置以及宽高background.setBounds(36, 36, 508, 560);// 将背景图片管理容器添加到界面中this.getContentPane().add(background);// 最后刷新一下游戏界面this.getContentPane().repaint();}// 这个可以不用管@Overridepublic void keyTyped(KeyEvent e) {}/*监听按下键盘按键的事件细节:当按下按键不松开,会不断移动图片*/@Overridepublic void keyPressed(KeyEvent e) {// 获取键盘每个按键的编号int keyCode = e.getKeyCode();// 判断按下的按键是否为Aif (keyCode == 65) {// 需要先清空原本的所有图片this.getContentPane().removeAll();// 加载完整图片// 根据指定图片的相对路径创建一个图片对象,并添加到管理容器中JLabel masterMap = new JLabel(new ImageIcon(path + "all.jpg"));// 设置图片的坐标位置以及宽高masterMap.setBounds(80, 130, 420, 420);// 将管理容器添加到游戏界面中this.getContentPane().add(masterMap);// 在完整图片下面加载背景图片// 根据指定图片的相对路径创建图片对象,并添加到管理容器中JLabel background = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\background.jpg"));// 设置背景图片的坐标位置以及宽高background.setBounds(36, 36, 508, 560);// 将管理容器添加到游戏界面中this.getContentPane().add(background);// 最后刷新一下游戏界面this.getContentPane().repaint();}}/*监听松开键盘按键的事件:当按下按键松开后,会调用该方法*/@Overridepublic void keyReleased(KeyEvent e) {// 获取键盘每个按键的编号int keyCode = e.getKeyCode();// 判断玩家是否已经胜利!if (victory()) {// 是,则说明已经胜利!则直接结束当前方法!不让玩家继续进行拼图操作!return;}/*上下左右按键的编号:左:37、上:38、右:39、下:40*/// 判断松开的按键是否为上if (keyCode == 38) {// 判断空白方块的X轴是否为3if (x == 3) {// 是,说明空白方块下方已没有图片可以向上移动了,则提示!System.out.println("下方已没有图片可以向上移动了");return; // 结束方法!}// 否,则图片向上移动System.out.println("图片向上移动");/*图片向上移动逻辑:把空白方块下方的数字向上移动x, y: 表示空白方块x + 1, y: 表示空白方块下方的数字*/// 将空白方块下方图片的数据 赋值给 空白方块处data[x][y] = data[x + 1][y];// 空白方块下方图片的数据赋值为0data[x + 1][y] = 0;// 将空白方块往下移动x++;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 40) { // 否,则判断松开的按键是否为下// 是,则判断空白方块的X轴是否在0位置if (x == 0) {// 是,说明空白方块的上方已没有图片可以向下移动了,则提示!System.out.println("上方已没有图片可以向下移动了");return; // 结束方法!}// 否,则图片向下移动System.out.println("图片向下移动");/*图片向下移动逻辑:把空白方块上方的数字向下移动x, y: 表示空白方块x - 1, y: 表示空白方块上方的数字*/// 将空白方块上方图片的数据 赋值给 空白方块处data[x][y] = data[x - 1][y];// 空白下方图片的数据赋值为0data[x - 1][y] = 0;// 将空白方块往上移动x--;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 37) { // 否,则判断松开的按键是否为左// 是,则判断空白方块的Y轴是否为3if (y == 3) {// 是,说明空白方块的右方已没有图片可以向左移动了,则提示!System.out.println("右方已没有图片可以向左移动了");return; // 结束方法!}// 否,则图片向左移动System.out.println("图片向左移动");/*图片向左移动逻辑:把空白方块右方的数字向左移动x, y: 表示空白方块x, y + 1: 表示空白方块右方的数字*/// 将空表方块右方图片的数据 赋值给 空白方块处data[x][y] = data[x][y + 1];// 空白右方图片的数据赋值为0data[x][y + 1] = 0;// 将空白方块往右移动y++;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 39) { // 否,则判断松开的按键是否为右// 是,则判断空白方块的Y轴是否在0位置if (y == 0) {// 是,说明空白方块的左方已没有图片可以向右移动了,则提示!System.out.println("左方已没有图片可以向右移动了");return; // 结束方法!}// 否,则图片向右移动System.out.println("图片向右移动");/*图片向右移动逻辑:把空白方块左方的数字向右移动x, y: 表示空白方块x, y - 1: 表示空白方块左方的数字*/// 将空表方块左方图片的数据 赋值给 空白方块处data[x][y] = data[x][y - 1];// 空白左方图片的数据赋值为0data[x][y - 1] = 0;// 将空白方块往左移动y--;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 65) { // 否,则判断松开的按键是否为A// 显示随机打乱的图片initImages(path);} else if (keyCode == 87) { // 否,则判断松开的按键是否为W// 是,则显示拼图完成后的效果!// 其实就是直接把正确顺序数据的新二维数组 赋值给 乱序数据的旧二维数组data = new int[][]{{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 按照二维数组的最新数据重新加载所有小图片initImages(path);}}
}
import cn.edu.gxufe.ui.GameJFrame;
import cn.edu.gxufe.ui.LoginJFrame;
import cn.edu.gxufe.ui.RegisterJFrame;public class App {public static void main(String[] args) {// 表示程序启动的入口// 如果我们想要开启一个界面,就创建对应界面的对象即可!new GameJFrame(); // 游戏主界面
// new LoginJFrame(); // 登录界面
// new RegisterJFrame(); // 注册界面}
}
需要统计玩家一共移动了多少步,并且让数据显示在游戏主界面的左上方
package cn.edu.gxufe.ui;import javax.swing.*;/*** 游戏主界面类:* 继承父类:JFrame*/
public class GameJFrame extends JFrame implements KeyListener{// 表示游戏相关的逻辑代码都写在这!/*创建二维数组:目的:管理数据加载图片的时候,会根据二位数组中的数据进行加载*/int[][] data = new int[4][4];// 定义x、y变量,用于记录空白方块所在的XY的坐标位置int x = 0;int y = 0;// 创建一个成员的随机数对象,用于生成一个随机数Random rd = new Random();// 定义path变量,用于记录图片的相对路径,方便以后做更换图片、重新游戏等功能的实现String path = "puzzle_game\puzzleimages\girl\girl1\";// 定义一个正确顺序数据的二维数组int[][] win = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 定义一个计数器变量,用于记录玩家拼图移动的步数int step = 0;// c.创建功能菜单下的三个栏目:重新游戏、重新登录、关闭游戏// 更换图片栏目放到后面再写,因为比较复杂JMenuItem replayGameItem = new JMenuItem("重新游戏");JMenuItem replayLoginItem = new JMenuItem("重新登录");JMenuItem closeGameItem = new JMenuItem("关闭游戏");// d.创建关于我们菜单下的一个栏目:公众号JMenuItem accountItem = new JMenuItem("公众号");// 创建更换图片菜单下的两个栏目:美女、动物JMenuItem belleItem = new JMenuItem("美女");JMenuItem animalItem = new JMenuItem("动物");/*提供无参数的构造器需求:初始化一个宽603像素,高680像素的游戏主界面*/public GameJFrame() {// 注:如果当前类没有以下这些方法,就会自动调用父类JFrame的// 初始化游戏主界面initJFrame();// 初始化游戏主界面里的菜单栏initJMenu();// 初始化数据:打乱initData();// 初始化图片到游戏主界面中initImages(path);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用当前GameJFrame类的setVisible方法,设置显示界面this.setVisible(true);}// 初始化数据:打乱private void initData() {// 1、创建一个一维数组,用于存储一些数据int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};// 2、开始打乱// 遍历数组,依次获取到数组中的每个数据for (int i = 0; i < arr.length; i++) {// 循环每执行一次,就生成一个随机索引int rdIndex = rd.nextInt(arr.length);// 每遍历到一个数据,就用临时变量存储一下int temp = arr[i];// 开始交换// 将随机索引位置的数据 放到 当前遍历到的数据的位置arr[i] = arr[rdIndex];// 将当前遍历到的数据 放到 随机索引位置arr[rdIndex] = temp;}/*解析:比如打乱后的一维数组的数据:[0, 14, 8, 3, 13, 15, 6, 4, 10, 12, 7, 1, 9, 5, 11, 2]索引: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15循环第一次执行:i=0,i<数组长度(16),为true,将0索引的数据:0 添加到二维数组的 i/4=0索引的一维数组的 i%4=0索引,i++循环第二次执行:i=1,i<数组长度(16),为true,将1索引的数据:14 添加到二维数组的 i/4=0索引的一维数组的 i%4=1索引,i++循环第三次执行:i=2,i<数组长度(16),为true,将2索引的数据:8 添加到二维数组的 i/4=0索引的一维数组的 i%4=2索引,i++后面的都是以此类推了!直到i<16,为false,循环结束!*/// 3、遍历arr一维数组,依次得到打乱后的每个数据for (int i = 0; i < arr.length; i++) {// 判断当前数据是否为0if (arr[i] == 0) {// 是,则记录空白方块XY的坐标位置x = i / 4;y = i % 4;}// 否,则依次将打乱后的数据添加到二维数组中data[i / 4][i % 4] = arr[i];}}// 初始化游戏主界面private void initJFrame() {// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽603像素,高680像素大小的界面this.setSize(603, 680);// 设置界面标题this.setTitle("奥利gei拼图单机版 v1.0");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 注:取消默认的居中位置,只有取消了才会按照X、Y轴的形式添加组件// 因为只有这样,添加图片的时候才不会一直处于居中位置this.setLayout(null);// 给整个游戏界面添加键盘监听事件this.addKeyListener(this);}// 初始化游戏主界面里的菜单栏private void initJMenu() {// a.创建一个菜单栏对象JMenuBar jMenuBar = new JMenuBar();// b.创建两个菜单栏下的菜单:功能、关于我们JMenu functionJMenu = new JMenu("功能");JMenu aboutUSJMenu = new JMenu("关于我们");// 创建菜单栏下的菜单:更换图片JMenu changeJMenu = new JMenu("更换图片");// 将更换图片的菜单嵌套进功能菜单中functionJMenu.add(changeJMenu);// e.将重新游戏、重新登录、关闭游戏这些栏目放到功能菜单下functionJMenu.add(replayGameItem);functionJMenu.add(replayLoginItem);functionJMenu.add(closeGameItem);// 将美女、动物这两个栏目放到更换图片菜单下changeJMenu.add(belleItem);changeJMenu.add(animalItem);// f.将公众号这个栏目放到关于我们菜单下aboutUSJMenu.add(accountItem);// g.将功能、关于我们这两个菜单放到菜单栏下jMenuBar.add(functionJMenu);jMenuBar.add(aboutUSJMenu);// h.最后将菜单栏放到设置到游戏界面中this.setJMenuBar(jMenuBar);}/*初始化图片到游戏主界面中细节:先加载的图片在上方,后加载的图片会在下方*/private void initImages(String path) {// 先清空原本出现的所有图片this.getContentPane().removeAll();// 判断victory方法的返回结果是否为trueif (victory()) {// 是,则说明玩家胜利了!显示胜利图标!// 根据指定图片的相对路径创建图片对象,并添加到管理容器中JLabel winJLabel = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\win.jpg"));// 设置胜利图片的坐标位置以及宽高winJLabel.setBounds(193, 300, 194, 75);// 将管理容器添加到游戏主界面中this.getContentPane().add(winJLabel);}// 统计步数// 创建一个管理容器对象,用于管理文字(步数: 0)JLabel stepJLabel = new JLabel("步数:" + step);// 设置管理容器的坐标位置stepJLabel.setBounds(40, 20, 400, 50);// 将容器添加到游戏主界面中this.getContentPane().add(stepJLabel);// 再加载图片/*外循环和内循环执行流程解析:外循环第一次执行:当 i = 0 时:i<4,为true,表示添加第一行的四张图片:当 j = 0 时:j<4,为true,此时number=1,表示添加第一行的第一张图片当 j = 1 时:j<4,为true,此时number=2,表示添加第一行的第二张图片当 j = 2 时:j<4,为true,此时number=3,表示添加第一行的第三张图片当 j = 3 时:j<4,为true,此时number=4,表示添加第一行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第二次执行:当 i = 1 时:i<4,为true,表示添加第二行的四张图片:当 j = 0 时:j<4,为true,此时number=5,表示添加第二行的第一张图片当 j = 1 时:j<4,为true,此时number=6,表示添加第二行的第二张图片当 j = 2 时:j<4,为true,此时number=7,表示添加第二行的第三张图片当 j = 3 时:j<4,为true,此时number=8,表示添加第二行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第三次执行:当 i = 2 时:i<4,为true,表示添加第三行的四张图片:当 j = 0 时:j<4,为true,此时number=9,表示添加第三行的第一张图片当 j = 1 时:j<4,为true,此时number=10,表示添加第三行的第二张图片当 j = 2 时:j<4,为true,此时number=11,表示添加第三行的第三张图片当 j = 3 时:j<4,为true,此时number=12,表示添加第三行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第四次执行:当 i = 3 时:i<4,为true,表示添加第四行的四张图片:当 j = 0 时:j<4,为true,此时number=13,表示添加第四行的第一张图片当 j = 1 时:j<4,为true,此时number=14,表示添加第四行的第二张图片当 j = 2 时:j<4,为true,此时number=15,表示添加第四行的第三张图片当 j = 3 时:j<4,为true,此时number=16,表示添加第四行的第四张图片(由于文件中没有16这个命名的图片,因此会添加一个空白)当 j = 4 时:j<4,为false,内循环结束!外循环第五次执行:当 i = 4 时:i<4,为false,外循环结束!*/// 外循环:控制添加4行图片for (int i = 0; i < data.length; i++) { // Y轴:纵向// 内循环:控制每行添加4张图片for (int j = 0; j < data[i].length; j++) { // X轴:横向/*解析:比如二维数组中的数据:[{3, 9, 7, 5} {8, 10, 14, 11} {4, 2, 12, 13} {0, 15, 1, 6} ]二维数组中的一维数组的索引: 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3二维数组的索引: 0 1 2 3(1) 外循环执行第一次:i=0, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(2) 外循环执行第二次:i=1, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(3) 外循环执行第三次:i=2, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(4) 外循环执行第四次:i=3, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(5) 外循环执行第五次:i=0, i<二维数组长度(4), 为false, 外循环结束!!*/// 接收二维数组i索引的一维数组j索引的数据int number = data[i][j];// 1、根据指定的文件相对路径创建一个ImageIcon图片对象ImageIcon imageIcon = new ImageIcon(path + number + ".jpg");// JLabel:管理容器,用于管理图片、文字// 2、创建一个管理容器JLabel对象,用于管理图片对象ImageIcon,// 并将图片对象imageIcon放到管理容器jLabel中。JLabel jLabel = new JLabel(imageIcon);// 3、指定图片位置:XY轴jLabel.setBounds(105 * j + 80, 105 * i + 130, 105, 105);// 4、每添加完一张小图片,给这张小图片添加边框/*0: 表示让图片凸起来1: 表示让图片凹下去*/jLabel.setBorder(new BevelBorder(1));// 5、将管理容器JLabel对象放到游戏主界面中// getContentPane:获取隐藏容器this.getContentPane().add(jLabel);}}// 在所有小图片都添加到界面之后,开始添加背景图片// 根据指定的相对路径创建背景图片对象,并添加到管理容器中JLabel background = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\background.jpg"));// 设置背景图片的坐标位置以及宽高background.setBounds(36, 36, 508, 560);// 将背景图片管理容器添加到界面中this.getContentPane().add(background);// 最后刷新一下游戏界面this.getContentPane().repaint();}// 这个可以不用管@Overridepublic void keyTyped(KeyEvent e) {}/*监听按下键盘按键的事件细节:当按下按键不松开,会不断移动图片*/@Overridepublic void keyPressed(KeyEvent e) {// 获取键盘每个按键的编号int keyCode = e.getKeyCode();// 判断按下的按键是否为Aif (keyCode == 65) {// 需要先清空原本的所有图片this.getContentPane().removeAll();// 加载完整图片// 根据指定图片的相对路径创建一个图片对象,并添加到管理容器中JLabel masterMap = new JLabel(new ImageIcon(path + "all.jpg"));// 设置图片的坐标位置以及宽高masterMap.setBounds(80, 130, 420, 420);// 将管理容器添加到游戏界面中this.getContentPane().add(masterMap);// 在完整图片下面加载背景图片// 根据指定图片的相对路径创建图片对象,并添加到管理容器中JLabel background = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\background.jpg"));// 设置背景图片的坐标位置以及宽高background.setBounds(36, 36, 508, 560);// 将管理容器添加到游戏界面中this.getContentPane().add(background);// 最后刷新一下游戏界面this.getContentPane().repaint();}}/*监听松开键盘按键的事件:当按下按键松开后,会调用该方法*/@Overridepublic void keyReleased(KeyEvent e) {// 获取键盘每个按键的编号int keyCode = e.getKeyCode();// 判断玩家是否已经胜利!if (victory()) {// 是,则说明已经胜利!则直接结束当前方法!不让玩家继续进行拼图操作!return;}/*上下左右按键的编号:左:37、上:38、右:39、下:40*/// 判断松开的按键是否为上if (keyCode == 38) {// 判断空白方块的X轴是否为3if (x == 3) {// 是,说明空白方块下方已没有图片可以向上移动了,则提示!System.out.println("下方已没有图片可以向上移动了");return; // 结束方法!}// 否,则图片向上移动System.out.println("图片向上移动");/*图片向上移动逻辑:把空白方块下方的数字向上移动x, y: 表示空白方块x + 1, y: 表示空白方块下方的数字*/// 将空白方块下方图片的数据 赋值给 空白方块处data[x][y] = data[x + 1][y];// 空白方块下方图片的数据赋值为0data[x + 1][y] = 0;// 将空白方块往下移动x++;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 40) { // 否,则判断松开的按键是否为下// 是,则判断空白方块的X轴是否在0位置if (x == 0) {// 是,说明空白方块的上方已没有图片可以向下移动了,则提示!System.out.println("上方已没有图片可以向下移动了");return; // 结束方法!}// 否,则图片向下移动System.out.println("图片向下移动");/*图片向下移动逻辑:把空白方块上方的数字向下移动x, y: 表示空白方块x - 1, y: 表示空白方块上方的数字*/// 将空白方块上方图片的数据 赋值给 空白方块处data[x][y] = data[x - 1][y];// 空白下方图片的数据赋值为0data[x - 1][y] = 0;// 将空白方块往上移动x--;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 37) { // 否,则判断松开的按键是否为左// 是,则判断空白方块的Y轴是否为3if (y == 3) {// 是,说明空白方块的右方已没有图片可以向左移动了,则提示!System.out.println("右方已没有图片可以向左移动了");return; // 结束方法!}// 否,则图片向左移动System.out.println("图片向左移动");/*图片向左移动逻辑:把空白方块右方的数字向左移动x, y: 表示空白方块x, y + 1: 表示空白方块右方的数字*/// 将空表方块右方图片的数据 赋值给 空白方块处data[x][y] = data[x][y + 1];// 空白右方图片的数据赋值为0data[x][y + 1] = 0;// 将空白方块往右移动y++;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 39) { // 否,则判断松开的按键是否为右// 是,则判断空白方块的Y轴是否在0位置if (y == 0) {// 是,说明空白方块的左方已没有图片可以向右移动了,则提示!System.out.println("左方已没有图片可以向右移动了");return; // 结束方法!}// 否,则图片向右移动System.out.println("图片向右移动");/*图片向右移动逻辑:把空白方块左方的数字向右移动x, y: 表示空白方块x, y - 1: 表示空白方块左方的数字*/// 将空表方块左方图片的数据 赋值给 空白方块处data[x][y] = data[x][y - 1];// 空白左方图片的数据赋值为0data[x][y - 1] = 0;// 将空白方块往左移动y--;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 65) { // 否,则判断松开的按键是否为A// 显示随机打乱的图片initImages(path);} else if (keyCode == 87) { // 否,则判断松开的按键是否为W// 是,则显示拼图完成后的效果!// 其实就是直接把正确顺序数据的新二维数组 赋值给 乱序数据的旧二维数组data = new int[][]{{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 按照二维数组的最新数据重新加载所有小图片initImages(path);}}
}
import cn.edu.gxufe.ui.GameJFrame;
import cn.edu.gxufe.ui.LoginJFrame;
import cn.edu.gxufe.ui.RegisterJFrame;public class App {public static void main(String[] args) {// 表示程序启动的入口// 如果我们想要开启一个界面,就创建对应界面的对象即可!new GameJFrame(); // 游戏主界面
// new LoginJFrame(); // 登录界面
// new RegisterJFrame(); // 注册界面}
}
package cn.edu.gxufe.ui;import javax.swing.*;/*** 游戏主界面类:* 继承父类:JFrame*/
public class GameJFrame extends JFrame implements KeyListener, ActionListener{// 表示游戏相关的逻辑代码都写在这!/*创建二维数组:目的:管理数据加载图片的时候,会根据二位数组中的数据进行加载*/int[][] data = new int[4][4];// 定义x、y变量,用于记录空白方块所在的XY的坐标位置int x = 0;int y = 0;// 创建一个成员的随机数对象,用于生成一个随机数Random rd = new Random();// 定义path变量,用于记录图片的相对路径,方便以后做更换图片、重新游戏等功能的实现String path = "puzzle_game\puzzleimages\girl\girl1\";// 定义一个正确顺序数据的二维数组int[][] win = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 定义一个计数器变量,用于记录玩家拼图移动的步数int step = 0;// c.创建功能菜单下的三个栏目:重新游戏、重新登录、关闭游戏// 更换图片栏目放到后面再写,因为比较复杂JMenuItem replayGameItem = new JMenuItem("重新游戏");JMenuItem replayLoginItem = new JMenuItem("重新登录");JMenuItem closeGameItem = new JMenuItem("关闭游戏");// d.创建关于我们菜单下的一个栏目:公众号JMenuItem accountItem = new JMenuItem("公众号");// 创建更换图片菜单下的两个栏目:美女、动物JMenuItem belleItem = new JMenuItem("美女");JMenuItem animalItem = new JMenuItem("动物");/*提供无参数的构造器需求:初始化一个宽603像素,高680像素的游戏主界面*/public GameJFrame() {// 注:如果当前类没有以下这些方法,就会自动调用父类JFrame的// 初始化游戏主界面initJFrame();// 初始化游戏主界面里的菜单栏initJMenu();// 初始化数据:打乱initData();// 初始化图片到游戏主界面中initImages(path);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用当前GameJFrame类的setVisible方法,设置显示界面this.setVisible(true);}// 初始化数据:打乱private void initData() {// 1、创建一个一维数组,用于存储一些数据int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};// 2、开始打乱// 遍历数组,依次获取到数组中的每个数据for (int i = 0; i < arr.length; i++) {// 循环每执行一次,就生成一个随机索引int rdIndex = rd.nextInt(arr.length);// 每遍历到一个数据,就用临时变量存储一下int temp = arr[i];// 开始交换// 将随机索引位置的数据 放到 当前遍历到的数据的位置arr[i] = arr[rdIndex];// 将当前遍历到的数据 放到 随机索引位置arr[rdIndex] = temp;}/*解析:比如打乱后的一维数组的数据:[0, 14, 8, 3, 13, 15, 6, 4, 10, 12, 7, 1, 9, 5, 11, 2]索引: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15循环第一次执行:i=0,i<数组长度(16),为true,将0索引的数据:0 添加到二维数组的 i/4=0索引的一维数组的 i%4=0索引,i++循环第二次执行:i=1,i<数组长度(16),为true,将1索引的数据:14 添加到二维数组的 i/4=0索引的一维数组的 i%4=1索引,i++循环第三次执行:i=2,i<数组长度(16),为true,将2索引的数据:8 添加到二维数组的 i/4=0索引的一维数组的 i%4=2索引,i++后面的都是以此类推了!直到i<16,为false,循环结束!*/// 3、遍历arr一维数组,依次得到打乱后的每个数据for (int i = 0; i < arr.length; i++) {// 判断当前数据是否为0if (arr[i] == 0) {// 是,则记录空白方块XY的坐标位置x = i / 4;y = i % 4;}// 否,则依次将打乱后的数据添加到二维数组中data[i / 4][i % 4] = arr[i];}}// 初始化游戏主界面private void initJFrame() {// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽603像素,高680像素大小的界面this.setSize(603, 680);// 设置界面标题this.setTitle("奥利gei拼图单机版 v1.0");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 注:取消默认的居中位置,只有取消了才会按照X、Y轴的形式添加组件// 因为只有这样,添加图片的时候才不会一直处于居中位置this.setLayout(null);// 给整个游戏界面添加键盘监听事件this.addKeyListener(this);}// 初始化游戏主界面里的菜单栏private void initJMenu() {// a.创建一个菜单栏对象JMenuBar jMenuBar = new JMenuBar();// b.创建两个菜单栏下的菜单:功能、关于我们JMenu functionJMenu = new JMenu("功能");JMenu aboutUSJMenu = new JMenu("关于我们");// 创建菜单栏下的菜单:更换图片JMenu changeJMenu = new JMenu("更换图片");// 将更换图片的菜单嵌套进功能菜单中functionJMenu.add(changeJMenu);// 给各个栏目绑定动作监听事件replayGameItem.addActionListener(this); // 重新游戏replayLoginItem.addActionListener(this); // 重新登录closeGameItem.addActionListener(this); // 关闭游戏accountItem.addActionListener(this); // 公众号belleItem.addActionListener(this); // 美女animalItem.addActionListener(this); // 动物// e.将重新游戏、重新登录、关闭游戏这些栏目放到功能菜单下functionJMenu.add(replayGameItem);functionJMenu.add(replayLoginItem);functionJMenu.add(closeGameItem);// 将美女、动物这两个栏目放到更换图片菜单下changeJMenu.add(belleItem);changeJMenu.add(animalItem);// f.将公众号这个栏目放到关于我们菜单下aboutUSJMenu.add(accountItem);// g.将功能、关于我们这两个菜单放到菜单栏下jMenuBar.add(functionJMenu);jMenuBar.add(aboutUSJMenu);// h.最后将菜单栏放到设置到游戏界面中this.setJMenuBar(jMenuBar);}/*初始化图片到游戏主界面中细节:先加载的图片在上方,后加载的图片会在下方*/private void initImages(String path) {// 先清空原本出现的所有图片this.getContentPane().removeAll();// 判断victory方法的返回结果是否为trueif (victory()) {// 是,则说明玩家胜利了!显示胜利图标!// 根据指定图片的相对路径创建图片对象,并添加到管理容器中JLabel winJLabel = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\win.jpg"));// 设置胜利图片的坐标位置以及宽高winJLabel.setBounds(193, 300, 194, 75);// 将管理容器添加到游戏主界面中this.getContentPane().add(winJLabel);}// 统计步数// 创建一个管理容器对象,用于管理文字(步数: 0)JLabel stepJLabel = new JLabel("步数:" + step);// 设置管理容器的坐标位置stepJLabel.setBounds(40, 20, 400, 50);// 将容器添加到游戏主界面中this.getContentPane().add(stepJLabel);// 再加载图片/*外循环和内循环执行流程解析:外循环第一次执行:当 i = 0 时:i<4,为true,表示添加第一行的四张图片:当 j = 0 时:j<4,为true,此时number=1,表示添加第一行的第一张图片当 j = 1 时:j<4,为true,此时number=2,表示添加第一行的第二张图片当 j = 2 时:j<4,为true,此时number=3,表示添加第一行的第三张图片当 j = 3 时:j<4,为true,此时number=4,表示添加第一行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第二次执行:当 i = 1 时:i<4,为true,表示添加第二行的四张图片:当 j = 0 时:j<4,为true,此时number=5,表示添加第二行的第一张图片当 j = 1 时:j<4,为true,此时number=6,表示添加第二行的第二张图片当 j = 2 时:j<4,为true,此时number=7,表示添加第二行的第三张图片当 j = 3 时:j<4,为true,此时number=8,表示添加第二行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第三次执行:当 i = 2 时:i<4,为true,表示添加第三行的四张图片:当 j = 0 时:j<4,为true,此时number=9,表示添加第三行的第一张图片当 j = 1 时:j<4,为true,此时number=10,表示添加第三行的第二张图片当 j = 2 时:j<4,为true,此时number=11,表示添加第三行的第三张图片当 j = 3 时:j<4,为true,此时number=12,表示添加第三行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第四次执行:当 i = 3 时:i<4,为true,表示添加第四行的四张图片:当 j = 0 时:j<4,为true,此时number=13,表示添加第四行的第一张图片当 j = 1 时:j<4,为true,此时number=14,表示添加第四行的第二张图片当 j = 2 时:j<4,为true,此时number=15,表示添加第四行的第三张图片当 j = 3 时:j<4,为true,此时number=16,表示添加第四行的第四张图片(由于文件中没有16这个命名的图片,因此会添加一个空白)当 j = 4 时:j<4,为false,内循环结束!外循环第五次执行:当 i = 4 时:i<4,为false,外循环结束!*/// 外循环:控制添加4行图片for (int i = 0; i < data.length; i++) { // Y轴:纵向// 内循环:控制每行添加4张图片for (int j = 0; j < data[i].length; j++) { // X轴:横向/*解析:比如二维数组中的数据:[{3, 9, 7, 5} {8, 10, 14, 11} {4, 2, 12, 13} {0, 15, 1, 6} ]二维数组中的一维数组的索引: 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3二维数组的索引: 0 1 2 3(1) 外循环执行第一次:i=0, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(2) 外循环执行第二次:i=1, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(3) 外循环执行第三次:i=2, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(4) 外循环执行第四次:i=3, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(5) 外循环执行第五次:i=0, i<二维数组长度(4), 为false, 外循环结束!!*/// 接收二维数组i索引的一维数组j索引的数据int number = data[i][j];// 1、根据指定的文件相对路径创建一个ImageIcon图片对象ImageIcon imageIcon = new ImageIcon(path + number + ".jpg");// JLabel:管理容器,用于管理图片、文字// 2、创建一个管理容器JLabel对象,用于管理图片对象ImageIcon,// 并将图片对象imageIcon放到管理容器jLabel中。JLabel jLabel = new JLabel(imageIcon);// 3、指定图片位置:XY轴jLabel.setBounds(105 * j + 80, 105 * i + 130, 105, 105);// 4、每添加完一张小图片,给这张小图片添加边框/*0: 表示让图片凸起来1: 表示让图片凹下去*/jLabel.setBorder(new BevelBorder(1));// 5、将管理容器JLabel对象放到游戏主界面中// getContentPane:获取隐藏容器this.getContentPane().add(jLabel);}}// 在所有小图片都添加到界面之后,开始添加背景图片// 根据指定的相对路径创建背景图片对象,并添加到管理容器中JLabel background = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\background.jpg"));// 设置背景图片的坐标位置以及宽高background.setBounds(36, 36, 508, 560);// 将背景图片管理容器添加到界面中this.getContentPane().add(background);// 最后刷新一下游戏界面this.getContentPane().repaint();}// 这个可以不用管@Overridepublic void keyTyped(KeyEvent e) {}/*监听按下键盘按键的事件细节:当按下按键不松开,会不断移动图片*/@Overridepublic void keyPressed(KeyEvent e) {// 获取键盘每个按键的编号int keyCode = e.getKeyCode();// 判断按下的按键是否为Aif (keyCode == 65) {// 需要先清空原本的所有图片this.getContentPane().removeAll();// 加载完整图片// 根据指定图片的相对路径创建一个图片对象,并添加到管理容器中JLabel masterMap = new JLabel(new ImageIcon(path + "all.jpg"));// 设置图片的坐标位置以及宽高masterMap.setBounds(80, 130, 420, 420);// 将管理容器添加到游戏界面中this.getContentPane().add(masterMap);// 在完整图片下面加载背景图片// 根据指定图片的相对路径创建图片对象,并添加到管理容器中JLabel background = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\background.jpg"));// 设置背景图片的坐标位置以及宽高background.setBounds(36, 36, 508, 560);// 将管理容器添加到游戏界面中this.getContentPane().add(background);// 最后刷新一下游戏界面this.getContentPane().repaint();}}/*监听松开键盘按键的事件:当按下按键松开后,会调用该方法*/@Overridepublic void keyReleased(KeyEvent e) {// 获取键盘每个按键的编号int keyCode = e.getKeyCode();// 判断玩家是否已经胜利!if (victory()) {// 是,则说明已经胜利!则直接结束当前方法!不让玩家继续进行拼图操作!return;}/*上下左右按键的编号:左:37、上:38、右:39、下:40*/// 判断松开的按键是否为上if (keyCode == 38) {// 判断空白方块的X轴是否为3if (x == 3) {// 是,说明空白方块下方已没有图片可以向上移动了,则提示!System.out.println("下方已没有图片可以向上移动了");return; // 结束方法!}// 否,则图片向上移动System.out.println("图片向上移动");/*图片向上移动逻辑:把空白方块下方的数字向上移动x, y: 表示空白方块x + 1, y: 表示空白方块下方的数字*/// 将空白方块下方图片的数据 赋值给 空白方块处data[x][y] = data[x + 1][y];// 空白方块下方图片的数据赋值为0data[x + 1][y] = 0;// 将空白方块往下移动x++;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 40) { // 否,则判断松开的按键是否为下// 是,则判断空白方块的X轴是否在0位置if (x == 0) {// 是,说明空白方块的上方已没有图片可以向下移动了,则提示!System.out.println("上方已没有图片可以向下移动了");return; // 结束方法!}// 否,则图片向下移动System.out.println("图片向下移动");/*图片向下移动逻辑:把空白方块上方的数字向下移动x, y: 表示空白方块x - 1, y: 表示空白方块上方的数字*/// 将空白方块上方图片的数据 赋值给 空白方块处data[x][y] = data[x - 1][y];// 空白下方图片的数据赋值为0data[x - 1][y] = 0;// 将空白方块往上移动x--;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 37) { // 否,则判断松开的按键是否为左// 是,则判断空白方块的Y轴是否为3if (y == 3) {// 是,说明空白方块的右方已没有图片可以向左移动了,则提示!System.out.println("右方已没有图片可以向左移动了");return; // 结束方法!}// 否,则图片向左移动System.out.println("图片向左移动");/*图片向左移动逻辑:把空白方块右方的数字向左移动x, y: 表示空白方块x, y + 1: 表示空白方块右方的数字*/// 将空表方块右方图片的数据 赋值给 空白方块处data[x][y] = data[x][y + 1];// 空白右方图片的数据赋值为0data[x][y + 1] = 0;// 将空白方块往右移动y++;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 39) { // 否,则判断松开的按键是否为右// 是,则判断空白方块的Y轴是否在0位置if (y == 0) {// 是,说明空白方块的左方已没有图片可以向右移动了,则提示!System.out.println("左方已没有图片可以向右移动了");return; // 结束方法!}// 否,则图片向右移动System.out.println("图片向右移动");/*图片向右移动逻辑:把空白方块左方的数字向右移动x, y: 表示空白方块x, y - 1: 表示空白方块左方的数字*/// 将空表方块左方图片的数据 赋值给 空白方块处data[x][y] = data[x][y - 1];// 空白左方图片的数据赋值为0data[x][y - 1] = 0;// 将空白方块往左移动y--;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 65) { // 否,则判断松开的按键是否为A// 显示随机打乱的图片initImages(path);} else if (keyCode == 87) { // 否,则判断松开的按键是否为W// 是,则显示拼图完成后的效果!// 其实就是直接把正确顺序数据的新二维数组 赋值给 乱序数据的旧二维数组data = new int[][]{{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 按照二维数组的最新数据重新加载所有小图片initImages(path);}}// 动作监听事件:监听鼠标左键单击、键盘空格操作@Overridepublic void actionPerformed(ActionEvent e) {// 获取当前被点击的栏目对象Object obj = e.getSource();// 判断当前被点击的是否为重新游戏栏目if (obj == replayGameItem) {System.out.println("重新游戏");// 1、先将步数清零step = 0;// 2、重新打乱二维数组的数据initData();// 3、重新按照打乱后的数据加载所有小图片initImages(path);} else if (obj == replayLoginItem) {// 否,则判断当前被点击的是否为重新登录栏目System.out.println("重新登录");// 1、先关闭当前游戏主界面this.setVisible(false); // 隐藏起来// 2、构造登录界面new LoginJFrame();} else if (obj == closeGameItem) {// 否,则判断当前被点击的是否为关闭游戏栏目System.out.println("关闭游戏");// 直接结束JVM虚拟机运行System.exit(0);} else if (obj == accountItem) {// 否,则判断当前被点击的是否为公众号栏目System.out.println("公众号");// 1、创建对话窗对象JDialog jDialog = new JDialog();// 2、根据相对路径创建图片对象,并添加到管理容器中,并设置图片的坐标和宽高JLabel aboutJLabel = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\about.jpg"));aboutJLabel.setBounds(0, 0, 220, 220);// 3、将管理容器添加到对话窗中jDialog.getContentPane().add(aboutJLabel);// 4、设置对话窗的大小jDialog.setSize(300, 300);// 5、将对话窗置顶jDialog.setAlwaysOnTop(true);// 6、让对话窗居中jDialog.setLocationRelativeTo(null);// 7、设置对话窗不关闭则无法操作下面的界面jDialog.setModal(true);// 8、让对话窗显示出来,因为默认是隐藏的jDialog.setVisible(true);} else if (obj == belleItem) { // 否,则判断当前被点击的是否为美女栏目// 是,则随机更换美女图片中的一张System.out.println("更换美女图片");// 随机一个1~2之间的数int rdNumber = rd.nextInt(2) + 1;// 将path的值改为美女图片所在的相对路径path = "puzzle_game\puzzleimages\girl\girl" + rdNumber + "\";// 重新打乱二维数组中的数据initData();// 根据美女图片的相对路径,随机初始化一张美女图片initImages(path);} else if (obj == animalItem) { // 否,则判断当前被点击的是否为动物栏目// 是,则随机更换动物图片中的一张System.out.println("更换动物图片");// 随机一个1~2之间的数int rdNumber = rd.nextInt(2) + 1;// 将path的值改为美女图片所在的相对路径path = "puzzle_game\puzzleimages\animal\animal" + rdNumber + "\";// 重新打乱二维数组中的数据initData();// 根据动物图片的相对路径,随机初始化一张动物图片initImages(path);}}
}
import cn.edu.gxufe.ui.GameJFrame;
import cn.edu.gxufe.ui.LoginJFrame;
import cn.edu.gxufe.ui.RegisterJFrame;public class App {public static void main(String[] args) {// 表示程序启动的入口// 如果我们想要开启一个界面,就创建对应界面的对象即可!new GameJFrame(); // 游戏主界面
// new LoginJFrame(); // 登录界面
// new RegisterJFrame(); // 注册界面}
}
重新游戏:
重新登录
关闭游戏
公众号
package cn.edu.gxufe.ui;import javax.swing.*;
import javax.swing.border.BevelBorder;
import java.awt.event.*;
import java.util.Random;/*** 游戏主界面类:* 继承父类:JFrame*/
public class GameJFrame extends JFrame implements KeyListener, ActionListener {// 表示游戏相关的逻辑代码都写在这!/*创建二维数组:目的:管理数据加载图片的时候,会根据二位数组中的数据进行加载*/int[][] data = new int[4][4];// 定义x、y变量,用于记录空白方块所在的XY的坐标位置int x = 0;int y = 0;// 创建一个成员的随机数对象,用于生成一个随机数Random rd = new Random();// 定义path变量,用于记录图片的相对路径,方便以后做更换图片、重新游戏等功能的实现String path = "puzzle_game\puzzleimages\girl\girl1\";// 定义一个正确顺序数据的二维数组int[][] win = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 定义一个计数器变量,用于记录玩家拼图移动的步数int step = 0;// c.创建功能菜单下的三个栏目:重新游戏、重新登录、关闭游戏// 更换图片栏目放到后面再写,因为比较复杂JMenuItem replayGameItem = new JMenuItem("重新游戏");JMenuItem replayLoginItem = new JMenuItem("重新登录");JMenuItem closeGameItem = new JMenuItem("关闭游戏");// d.创建关于我们菜单下的一个栏目:公众号JMenuItem accountItem = new JMenuItem("公众号");// 创建更换图片菜单下的两个栏目:美女、动物JMenuItem belleItem = new JMenuItem("美女");JMenuItem animalItem = new JMenuItem("动物");/*提供无参数的构造器需求:初始化一个宽603像素,高680像素的游戏主界面*/public GameJFrame() {// 注:如果当前类没有以下这些方法,就会自动调用父类JFrame的// 1、初始化游戏主界面initJFrame();// 2、初始化游戏主界面里的菜单栏initJMenu();// 3、初始化数据:打乱initData();// 4、利用打乱后的数据初始化图片到游戏主界面中initImages(path);// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用当前GameJFrame类的setVisible方法,设置显示界面this.setVisible(true);}// 初始化数据:打乱private void initData() {// 1、创建一个一维数组,用于存储一些数据int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};// 2、开始打乱// 遍历数组,依次获取到数组中的每个数据for (int i = 0; i < arr.length; i++) {// 循环每执行一次,就生成一个随机索引int rdIndex = rd.nextInt(arr.length);// 每遍历到一个数据,就用临时变量存储一下int temp = arr[i];// 开始交换// 将随机索引位置的数据 放到 当前遍历到的数据的位置arr[i] = arr[rdIndex];// 将当前遍历到的数据 放到 随机索引位置arr[rdIndex] = temp;}/*解析:比如打乱后的一维数组的数据:[0, 14, 8, 3, 13, 15, 6, 4, 10, 12, 7, 1, 9, 5, 11, 2]索引: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15循环第一次执行:i=0,i<数组长度(16),为true,将0索引的数据:0 添加到二维数组的 i/4=0索引的一维数组的 i%4=0索引,i++循环第二次执行:i=1,i<数组长度(16),为true,将1索引的数据:14 添加到二维数组的 i/4=0索引的一维数组的 i%4=1索引,i++循环第三次执行:i=2,i<数组长度(16),为true,将2索引的数据:8 添加到二维数组的 i/4=0索引的一维数组的 i%4=2索引,i++后面的都是以此类推了!直到i<16,为false,循环结束!*/// 3、遍历arr一维数组,依次得到打乱后的每个数据for (int i = 0; i < arr.length; i++) {// 判断当前数据是否为0/*if (arr[i] == 0) {// 是,则记录空白方块XY的坐标位置x = i / 4;y = i % 4;} else {// 否,则依次将打乱后的数据添加到二维数组中data[i / 4][i % 4] = arr[i];}*/// 判断当前数据是否为0if (arr[i] == 0) {// 是,则记录空白方块XY的坐标位置x = i / 4;y = i % 4;}// 否,则依次将打乱后的数据添加到二维数组中data[i / 4][i % 4] = arr[i];}}/*初始化图片到游戏主界面中细节:先加载的图片在上方,后加载的图片会在下方*/private void initImages(String path) {// 先清空原本出现的所有图片this.getContentPane().removeAll();// 判断victory方法的返回结果是否为trueif (victory()) {// 是,则说明玩家胜利了!显示胜利图标!// 根据指定图片的相对路径创建图片对象,并添加到管理容器中JLabel winJLabel = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\win.jpg"));// 设置胜利图片的坐标位置以及宽高winJLabel.setBounds(193, 300, 194, 75);// 将管理容器添加到游戏主界面中this.getContentPane().add(winJLabel);}// 统计步数// 创建一个管理容器对象,用于管理文字(步数: 0)JLabel stepJLabel = new JLabel("步数:" + step);// 设置管理容器的坐标位置stepJLabel.setBounds(40, 20, 400, 50);// 将容器添加到游戏主界面中this.getContentPane().add(stepJLabel);// 再加载图片/*外循环和内循环执行流程解析:外循环第一次执行:当 i = 0 时:i<4,为true,表示添加第一行的四张图片:当 j = 0 时:j<4,为true,此时number=1,表示添加第一行的第一张图片当 j = 1 时:j<4,为true,此时number=2,表示添加第一行的第二张图片当 j = 2 时:j<4,为true,此时number=3,表示添加第一行的第三张图片当 j = 3 时:j<4,为true,此时number=4,表示添加第一行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第二次执行:当 i = 1 时:i<4,为true,表示添加第二行的四张图片:当 j = 0 时:j<4,为true,此时number=5,表示添加第二行的第一张图片当 j = 1 时:j<4,为true,此时number=6,表示添加第二行的第二张图片当 j = 2 时:j<4,为true,此时number=7,表示添加第二行的第三张图片当 j = 3 时:j<4,为true,此时number=8,表示添加第二行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第三次执行:当 i = 2 时:i<4,为true,表示添加第三行的四张图片:当 j = 0 时:j<4,为true,此时number=9,表示添加第三行的第一张图片当 j = 1 时:j<4,为true,此时number=10,表示添加第三行的第二张图片当 j = 2 时:j<4,为true,此时number=11,表示添加第三行的第三张图片当 j = 3 时:j<4,为true,此时number=12,表示添加第三行的第四张图片当 j = 4 时:j<4,为false,内循环结束!外循环第四次执行:当 i = 3 时:i<4,为true,表示添加第四行的四张图片:当 j = 0 时:j<4,为true,此时number=13,表示添加第四行的第一张图片当 j = 1 时:j<4,为true,此时number=14,表示添加第四行的第二张图片当 j = 2 时:j<4,为true,此时number=15,表示添加第四行的第三张图片当 j = 3 时:j<4,为true,此时number=16,表示添加第四行的第四张图片(由于文件中没有16这个命名的图片,因此会添加一个空白)当 j = 4 时:j<4,为false,内循环结束!外循环第五次执行:当 i = 4 时:i<4,为false,外循环结束!*/// 外循环:控制添加4行图片for (int i = 0; i < data.length; i++) { // Y轴:纵向// 内循环:控制每行添加4张图片for (int j = 0; j < data[i].length; j++) { // X轴:横向/*解析:比如二维数组中的数据:[{3, 9, 7, 5} {8, 10, 14, 11} {4, 2, 12, 13} {0, 15, 1, 6} ]二维数组中的一维数组的索引: 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3二维数组的索引: 0 1 2 3(1) 外循环执行第一次:i=0, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(2) 外循环执行第二次:i=1, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(3) 外循环执行第三次:i=2, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(4) 外循环执行第四次:i=3, i<二维数组长度(4), 为true, 进入内循环:a.内循环执行第一次j=0, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][0], j++b.内循环执行第二次j=1, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][1], j++c.内循环执行第三次j=2, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][2], j++d.内循环执行第四次j=3, j<一维数组长度(4), 为true, 此时data[i][j] == data[0][3], j++e.内循环执行第五次j=4, j<一维数组长度(4), 为false,内循环结束(5) 外循环执行第五次:i=0, i<二维数组长度(4), 为false, 外循环结束!!*/// 接收二维数组i索引的一维数组j索引的数据int number = data[i][j];// 1、根据指定的文件相对路径创建一个ImageIcon图片对象ImageIcon imageIcon = new ImageIcon(path + number + ".jpg");// JLabel:管理容器,用于管理图片、文字// 2、创建一个管理容器JLabel对象,用于管理图片对象ImageIcon,// 并将图片对象imageIcon放到管理容器jLabel中。JLabel jLabel = new JLabel(imageIcon);// 3、指定图片位置:XY轴jLabel.setBounds(105 * j + 80, 105 * i + 130, 105, 105);// 4、每添加完一张小图片,给这张小图片添加边框/*0: 表示让图片凸起来1: 表示让图片凹下去*/jLabel.setBorder(new BevelBorder(1));// 5、将管理容器JLabel对象放到游戏主界面中// getContentPane:获取隐藏容器this.getContentPane().add(jLabel);}}// 在所有小图片都添加到界面之后,开始添加背景图片// 根据指定的相对路径创建背景图片对象,并添加到管理容器中JLabel background = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\background.jpg"));// 设置背景图片的坐标位置以及宽高background.setBounds(36, 36, 508, 560);// 将背景图片管理容器添加到界面中this.getContentPane().add(background);// 最后刷新一下游戏界面this.getContentPane().repaint();}/*** 判断玩家是否胜利!** @return 都等于返回true,否则返回false*/private boolean victory() {// 外循环:遍历二维数组,依次得到二维数组中的每个一维数组for (int i = 0; i < data.length; i++) {// 内循环:遍历每个一维数组,依次得到每个一维数组中的每个数据for (int j = 0; j < data[i].length; j++) {// 判断当前二维数组中的数据 是否不等于 正确二维数组中的数据if (data[i][j] != win[i][j]) {// 只要有一个数据不相等,则直接返回false,后面的就没必要判断了!return false;}}}// 循环都结束了!说明两个二维数组的数据都相等!则返回truereturn true;}// 初始化游戏主界面里的菜单栏private void initJMenu() {// a.创建一个菜单栏对象JMenuBar jMenuBar = new JMenuBar();// b.创建两个菜单栏下的菜单:功能、关于我们JMenu functionJMenu = new JMenu("功能");JMenu aboutUSJMenu = new JMenu("关于我们");// 创建菜单栏下的菜单:更换图片JMenu changeJMenu = new JMenu("更换图片");// 将更换图片的菜单嵌套进功能菜单中functionJMenu.add(changeJMenu);// 给各个栏目绑定动作监听事件replayGameItem.addActionListener(this); // 重新游戏replayLoginItem.addActionListener(this); // 重新登录closeGameItem.addActionListener(this); // 关闭游戏accountItem.addActionListener(this); // 公众号belleItem.addActionListener(this); // 美女animalItem.addActionListener(this); // 动物// e.将重新游戏、重新登录、关闭游戏这些栏目放到功能菜单下functionJMenu.add(replayGameItem);functionJMenu.add(replayLoginItem);functionJMenu.add(closeGameItem);// 将美女、动物这两个栏目放到更换图片菜单下changeJMenu.add(belleItem);changeJMenu.add(animalItem);// f.将公众号这个栏目放到关于我们菜单下aboutUSJMenu.add(accountItem);// g.将功能、关于我们这两个菜单放到菜单栏下jMenuBar.add(functionJMenu);jMenuBar.add(aboutUSJMenu);// h.最后将菜单栏放到设置到游戏界面中this.setJMenuBar(jMenuBar);}// 初始化游戏主界面private void initJFrame() {// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽603像素,高680像素大小的界面this.setSize(603, 680);// 设置界面标题this.setTitle("奥利gei拼图单机版 v1.0");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 注:取消默认的居中位置,只有取消了才会按照X、Y轴的形式添加组件// 因为只有这样,添加图片的时候才不会一直处于居中位置this.setLayout(null);// 给整个游戏界面添加键盘监听事件this.addKeyListener(this);}// 这个可以不用管@Overridepublic void keyTyped(KeyEvent e) {}/*监听按下键盘按键的事件细节:当按下按键不松开,会不断移动图片*/@Overridepublic void keyPressed(KeyEvent e) {// 获取键盘每个按键的编号int keyCode = e.getKeyCode();// 判断按下的按键是否为Aif (keyCode == 65) {// 需要先清空原本的所有图片this.getContentPane().removeAll();// 加载完整图片// 根据指定图片的相对路径创建一个图片对象,并添加到管理容器中JLabel masterMap = new JLabel(new ImageIcon(path + "all.jpg"));// 设置图片的坐标位置以及宽高masterMap.setBounds(80, 130, 420, 420);// 将管理容器添加到游戏界面中this.getContentPane().add(masterMap);// 在完整图片下面加载背景图片// 根据指定图片的相对路径创建图片对象,并添加到管理容器中JLabel background = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\background.jpg"));// 设置背景图片的坐标位置以及宽高background.setBounds(36, 36, 508, 560);// 将管理容器添加到游戏界面中this.getContentPane().add(background);// 最后刷新一下游戏界面this.getContentPane().repaint();}}/*监听松开键盘按键的事件:当按下按键松开后,会调用该方法*/@Overridepublic void keyReleased(KeyEvent e) {// 获取键盘每个按键的编号int keyCode = e.getKeyCode();// 判断玩家是否已经胜利!if (victory()) {// 是,则说明已经胜利!则直接结束当前方法!不让玩家继续进行拼图操作!return;}/*上下左右按键的编号:左:37、上:38、右:39、下:40*/// 判断松开的按键是否为上if (keyCode == 38) {// 判断空白方块的X轴是否为3if (x == 3) {// 是,说明空白方块下方已没有图片可以向上移动了,则提示!System.out.println("下方已没有图片可以向上移动了");return; // 结束方法!}// 否,则图片向上移动System.out.println("图片向上移动");/*图片向上移动逻辑:把空白方块下方的数字向上移动x, y: 表示空白方块x + 1, y: 表示空白方块下方的数字*/// 将空白方块下方图片的数据 赋值给 空白方块处data[x][y] = data[x + 1][y];// 空白方块下方图片的数据赋值为0data[x + 1][y] = 0;// 将空白方块往下移动x++;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 40) { // 否,则判断松开的按键是否为下// 是,则判断空白方块的X轴是否在0位置if (x == 0) {// 是,说明空白方块的上方已没有图片可以向下移动了,则提示!System.out.println("上方已没有图片可以向下移动了");return; // 结束方法!}// 否,则图片向下移动System.out.println("图片向下移动");/*图片向下移动逻辑:把空白方块上方的数字向下移动x, y: 表示空白方块x - 1, y: 表示空白方块上方的数字*/// 将空白方块上方图片的数据 赋值给 空白方块处data[x][y] = data[x - 1][y];// 空白下方图片的数据赋值为0data[x - 1][y] = 0;// 将空白方块往上移动x--;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 37) { // 否,则判断松开的按键是否为左// 是,则判断空白方块的Y轴是否为3if (y == 3) {// 是,说明空白方块的右方已没有图片可以向左移动了,则提示!System.out.println("右方已没有图片可以向左移动了");return; // 结束方法!}// 否,则图片向左移动System.out.println("图片向左移动");/*图片向左移动逻辑:把空白方块右方的数字向左移动x, y: 表示空白方块x, y + 1: 表示空白方块右方的数字*/// 将空表方块右方图片的数据 赋值给 空白方块处data[x][y] = data[x][y + 1];// 空白右方图片的数据赋值为0data[x][y + 1] = 0;// 将空白方块往右移动y++;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 39) { // 否,则判断松开的按键是否为右// 是,则判断空白方块的Y轴是否在0位置if (y == 0) {// 是,说明空白方块的左方已没有图片可以向右移动了,则提示!System.out.println("左方已没有图片可以向右移动了");return; // 结束方法!}// 否,则图片向右移动System.out.println("图片向右移动");/*图片向右移动逻辑:把空白方块左方的数字向右移动x, y: 表示空白方块x, y - 1: 表示空白方块左方的数字*/// 将空表方块左方图片的数据 赋值给 空白方块处data[x][y] = data[x][y - 1];// 空白左方图片的数据赋值为0data[x][y - 1] = 0;// 将空白方块往左移动y--;// 移动后统计步数step++;// 重新加载一下所有图片initImages(path);} else if (keyCode == 65) { // 否,则判断松开的按键是否为A// 显示随机打乱的图片initImages(path);} else if (keyCode == 87) { // 否,则判断松开的按键是否为W// 是,则显示拼图完成后的效果!// 其实就是直接把正确顺序数据的新二维数组 赋值给 乱序数据的旧二维数组data = new int[][]{{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12},{13, 14, 15, 0}};// 按照二维数组的最新数据重新加载所有小图片initImages(path);}}// 动作监听事件:监听鼠标左键单击、键盘空格操作@Overridepublic void actionPerformed(ActionEvent e) {// 获取当前被点击的栏目对象Object obj = e.getSource();// 判断当前被点击的是否为重新游戏栏目if (obj == replayGameItem) {System.out.println("重新游戏");// 1、先将步数清零step = 0;// 2、重新打乱二维数组的数据initData();// 3、重新按照打乱后的数据加载所有小图片initImages(path);} else if (obj == replayLoginItem) {// 否,则判断当前被点击的是否为重新登录栏目System.out.println("重新登录");// 1、先关闭当前游戏主界面this.setVisible(false); // 隐藏起来// 2、构造登录界面new LoginJFrame();} else if (obj == closeGameItem) {// 否,则判断当前被点击的是否为关闭游戏栏目System.out.println("关闭游戏");// 直接结束JVM虚拟机运行System.exit(0);} else if (obj == accountItem) {// 否,则判断当前被点击的是否为公众号栏目System.out.println("公众号");// 1、创建对话窗对象JDialog jDialog = new JDialog();// 2、根据相对路径创建图片对象,并添加到管理容器中,并设置图片的坐标和宽高JLabel aboutJLabel = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\about.jpg"));aboutJLabel.setBounds(0, 0, 220, 220);// 3、将管理容器添加到对话窗中jDialog.getContentPane().add(aboutJLabel);// 4、设置对话窗的大小jDialog.setSize(300, 300);// 5、将对话窗置顶jDialog.setAlwaysOnTop(true);// 6、让对话窗居中jDialog.setLocationRelativeTo(null);// 7、设置对话窗不关闭则无法操作下面的界面jDialog.setModal(true);// 8、让对话窗显示出来,因为默认是隐藏的jDialog.setVisible(true);} else if (obj == belleItem) { // 否,则判断当前被点击的是否为美女栏目// 是,则随机更换美女图片中的一张System.out.println("更换美女图片");// 随机一个1~2之间的数int rdNumber = rd.nextInt(2) + 1;// 将path的值改为美女图片所在的相对路径path = "puzzle_game\puzzleimages\girl\girl" + rdNumber + "\";// 重新打乱二维数组中的数据initData();// 根据美女图片的相对路径,随机初始化一张美女图片initImages(path);} else if (obj == animalItem) { // 否,则判断当前被点击的是否为动物栏目// 是,则随机更换动物图片中的一张System.out.println("更换动物图片");// 随机一个1~2之间的数int rdNumber = rd.nextInt(2) + 1;// 将path的值改为美女图片所在的相对路径path = "puzzle_game\puzzleimages\animal\animal" + rdNumber + "\";// 重新打乱二维数组中的数据initData();// 根据动物图片的相对路径,随机初始化一张动物图片initImages(path);}}
}
import cn.edu.gxufe.ui.GameJFrame;
import cn.edu.gxufe.ui.LoginJFrame;
import cn.edu.gxufe.ui.RegisterJFrame;public class App {public static void main(String[] args) {// 表示程序启动的入口// 如果我们想要开启一个界面,就创建对应界面的对象即可!new GameJFrame(); // 游戏主界面
// new LoginJFrame(); // 登录界面
// new RegisterJFrame(); // 注册界面}
}
未更换之前:
更换图片为动物
按A的时候
点击重新游戏的时候
1、先定义用户类,用于封装用户信息为一个用户对象
2、定义一个集合,用于存储用户对象
3、当用户输入注册的用户名时:
4、当用户输入注册的密码时:
5、当用户确认注册的密码时:
6、当用户按下注册按钮时:
7、当用户按下重置按钮时:
package cn.edu.gxufe.entity;/*** 用户类*/
public class User {// 定义用户属性:用户名、密码private String username;private String password;// 提供无参、有参构造器public User(){}public User(String username, String password) {this.username = username;this.password = password;}// 提供成员变量全套的get和set方法,方便赋值和取值public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}
package cn.edu.gxufe.ui;import cn.edu.gxufe.entity.User;import javax.swing.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;/*** 注册界面类:* 继承父类:JFrame*/
public class RegisterJFrame extends JFrame implements MouseListener {// 表示注册相关的逻辑代码都写在这!// 1、创建管理容器:管理用户名输入框JLabel nameJLabel = new JLabel("注册用户名");// 2、创建管理容器:管理用户密码输入框JLabel passwordJLabel = new JLabel("注册密码");// 3、创建管理容器:管理用户确认密码输入框JLabel okPasswordJLabel = new JLabel("再次确认密码");// 4、根据指定注册图片的相对路径创建图片对象,并添加到按钮对象中JButton registerJButton = new JButton(new ImageIcon("puzzle_game\puzzleimages\register\register_button.jpg"));// 5、根据指定重置图片的相对路径创建图片对象,并添加到按钮对象中JButton resetJButton = new JButton(new ImageIcon("puzzle_game\puzzleimages\register\reset_button.jpg"));// 定义一个静态的List集合对象常量,用于存储用户对象public static final ArrayList userList = new ArrayList<>();// b.创建文本输入框:用于用户输入用户名JTextField nameJText = new JTextField();// b.创建文本输入框:用于用户输入密码JTextField passwordJText = new JTextField();// b.创建文本输入框:用于用户输入密码JTextField okPasswordJText = new JTextField();/*提供无参数的构造器需求:初始化一个宽501像素,高437像素的登录界面*/public RegisterJFrame() {// 注:如果当前类没有以下这些方法,就会自动调用父类JFrame的// 初始化注册界面initJFrame();// 初始化注册信息框initRegisterMess();// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用setVisible方法,设置显示界面this.setVisible(true);}// 初始化注册界面private void initJFrame() {// 设置界面宽高// 调用当前GameJFrame类的setSize方法,初始化一个宽501像素,高437像素大小的界面this.setSize(501, 437);// 设置界面标题this.setTitle("奥利gei拼图-注册");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行// this.setDefaultCloseOperation(3); // 与下一行代码是一样的功能this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);// 取消默认布局,这样子才可以让布局按照x和y轴的形式添加组件this.setLayout(null);}// 初始化注册信息框private void initRegisterMess() {// a.设置管理容器的坐标位置及宽高nameJLabel.setBounds(90, 100, 350, 100);// c.设置输入框的坐标位置及宽高nameJText.setBounds(80, 35, 200, 30);nameJLabel.add(nameJText);// 将管理容器对象添加到当前注册界面中this.getContentPane().add(nameJLabel);// a.设置管理容器的坐标位置及宽高passwordJLabel.setBounds(90, 150, 350, 100);// c.设置输入框的坐标位置及宽高passwordJText.setBounds(80, 35, 200, 30);passwordJLabel.add(passwordJText);// 将管理容器对象添加到当前注册界面中this.getContentPane().add(passwordJLabel);// a.设置管理容器的坐标位置及宽高okPasswordJLabel.setBounds(90, 200, 350, 100);// c.设置输入框的坐标位置及宽高okPasswordJText.setBounds(80, 35, 200, 30);okPasswordJLabel.add(okPasswordJText);// 将管理容器对象添加到当前注册界面中this.getContentPane().add(okPasswordJLabel);// a.设置按钮的坐标位置及宽高registerJButton.setBounds(90, 300, 115, 35);// 给按钮对象绑定鼠标监听registerJButton.addMouseListener(this);// 将按钮对象添加到当前注册界面中this.getContentPane().add(registerJButton);// a.设置按钮的坐标位置及宽高resetJButton.setBounds(256, 300, 115, 35);// 给按钮对象绑定鼠标监听resetJButton.addMouseListener(this);// 将按钮对象添加到当前注册界面中this.getContentPane().add(resetJButton);// 最后// a.根据指定背景图片的相对路径创建图片对象,并添加到管理容器中JLabel bgJLabel = new JLabel(new ImageIcon("puzzle_game\puzzleimages\sport\register_login_background.jpg"));// b.设置背景图管理容器的坐标位置及宽高bgJLabel.setBounds(0, 0, 488, 400);// c.将背景图管理容器对象添加到当前注册界面中this.getContentPane().add(bgJLabel);}// 鼠标单击事件@Overridepublic void mouseClicked(MouseEvent e) {}// 按下鼠标事件@Overridepublic void mousePressed(MouseEvent e) {// 获取当前按下的按钮对象Object obj = e.getSource();// 判断当前按下的按钮是否为注册按钮if (obj == registerJButton) {// 是System.out.println("按下注册按钮");// 设置注册按钮图片registerJButton.setIcon(new ImageIcon("puzzle_game\puzzleimages\register\register_down.jpg"));// 注册用户registerUser();} else if (obj == resetJButton) { // 否,则判断当前按下的按钮是否为重置按钮// 是System.out.println("按下重置按钮");// 设置重置按钮图片resetJButton.setIcon(new ImageIcon("puzzle_game\puzzleimages\register\reset_down.jpg"));// 将用户名、密码、确认密码的输入框的内容都清空nameJText.setText("");passwordJText.setText("");okPasswordJText.setText("");}}// 鼠标释放事件@Overridepublic void mouseReleased(MouseEvent e) {// 获取当前按下的按钮对象Object obj = e.getSource();// 判断当前按下的按钮是否为重置按钮if (obj == resetJButton) {// 是System.out.println("松开重置按钮");resetJButton.setIcon(new ImageIcon("puzzle_game\puzzleimages\register\reset_button.jpg"));}}// 鼠标划入事件@Overridepublic void mouseEntered(MouseEvent e) {}// 鼠标划出事件@Overridepublic void mouseExited(MouseEvent e) {}/*** 注册用户功能*/private void registerUser() {// 1、校验注册的用户名不能为空字符串if (nameJText.getText().equals("")) {// 是空字符,创建一个对话窗,提示一下!createJDialog(new JLabel("注册的用户名不能为空!"));}// 2、校验用户名的唯一性if (!checkUsername()) {// 用户不存在// 校验密码不能是空字符串if (passwordJText.getText().equals("")) {// 是空字符,创建一个对话窗,提示一下!createJDialog(new JLabel("注册的密码不能为空!"));} else {// 否,则校验密码是否不为中文if (checkPassword()) {// 是,则说明都不是中文// 判断两次输入的密码是否一致if (passwordJText.getText().equals(okPasswordJText.getText())) {// 一致!封装用户信息为一个用户对象,并添加进集合中userList.add(new User(nameJText.getText(), passwordJText.getText()));// 注册成功!创建一个对话窗,提示一下!createJDialog(new JLabel("注册成功!"));// 注册成功后!关闭注册界面,返回登录界面this.setVisible(false);new LoginJFrame();} else {// 两次输入的密码不一致,创建一个对话窗,提示一下!createJDialog(new JLabel("两次输入的密码不一致!"));}} else {// 注册的密码为中文,创建一个对话窗,提示一下!createJDialog(new JLabel("注册的密码不能为中文!"));}}} else {// 用户名已存在,创建一个对话窗,提示一下!createJDialog(new JLabel("用户名:" + nameJText.getText() + "已注册!请您换一个名字~"));}}/*** 创建一个对话窗** @param jLabel 管理容器对象*/private void createJDialog(JLabel jLabel) {// 1、创建对话窗对象JDialog jDialog = new JDialog();// 2、设置管理容器的坐标及宽高jLabel.setBounds(0, 0, 100, 100);// 3、将管理容器添加到对话窗中jDialog.getContentPane().add(jLabel);// 设置对话窗的大小jDialog.setSize(300, 100);// 4、将对话窗置顶jDialog.setAlwaysOnTop(true);// 5、让对话窗居中jDialog.setLocationRelativeTo(null);// 6、设置对话窗不关闭则无法操作下面的界面jDialog.setModal(true);// 7、让对话窗显示出来,因为默认是隐藏的jDialog.setVisible(true);// 8、当用户关闭对话窗时,注册按钮恢复原样!registerJButton.setIcon(new ImageIcon("puzzle_game\puzzleimages\register\register_button.jpg"));}/*** 校验密码** @return 校验通过返回true,否则返回false*/private boolean checkPassword() {// 1、遍历密码字符串,依次得到每个字符for (int i = 0; i < passwordJText.getText().length(); i++) {// 判断密码的每个字符是否为中文char c = passwordJText.getText().charAt(i);if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))) {// 有一个是中文,就返回falsereturn false;}}// 循环结束,说明都不是中文,返回truereturn true;}/*** 校验用户名** @return 用户名不存在返回true,否则返回false*/private boolean checkUsername() {// 2、根据注册的用户名得到该用户对象在集合中的索引int index = getUserIndex(nameJText.getText());// 3、判断索引是否小于0if (index < 0) {// 该用户不存在!返回nullreturn false;}// 用户存在return true;}/*** 根据用户名得到该用户对象在集合中的索引** @param username 注册的用户名* @return 存在就返回索引,否则返回-1*/public static int getUserIndex(String username) {// 1、遍历用户对象集合,依次得到集合中的每个用户对象for (int i = 0; i < userList.size(); i++) {// 判断当前遍历到的用户对象的用户名 是否匹配 注册的用户名if (userList.get(i).getUsername().equals(username)) {// 是,则说明匹配成功!返回该用户对象在集合中的索引return i;}}// 2、循环结束,仍然找不到与注册的用户名 匹配的用户对象,返回-1return -1;}
}
import cn.edu.gxufe.ui.GameJFrame;
import cn.edu.gxufe.ui.LoginJFrame;
import cn.edu.gxufe.ui.RegisterJFrame;public class App {public static void main(String[] args) {// 表示程序启动的入口// 如果我们想要开启一个界面,就创建对应界面的对象即可!
// new GameJFrame(); // 游戏主界面new RegisterJFrame(); // 注册界面
// new LoginJFrame(); // 登录界面}
}
直接按下注册按钮:
注册一个用户:
当用户关掉注册成功提示框时:
1、需要得到注册界面类中的List集合
2、需要给显示验证码的管理容器绑定鼠标监听:
3、需要给登录按钮、注册按钮、眼睛按钮都绑定鼠标监听:
package cn.edu.gxufe.ui;import cn.edu.gxufe.entity.User;
import cn.edu.gxufe.util.VerifyCodeUtil;import javax.swing.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;/*** 登录界面类:* 继承父类:JFrame*/
public class LoginJFrame extends JFrame implements MouseListener {// 表示登录相关的逻辑代码都写在这!// 需要得到注册界面类中的List集合public static ArrayList userList = RegisterJFrame.userList;// 2、创建一个JTextField明文输入框,用于输入用户名JTextField usernameText = new JTextField();// 4、创建一个JPasswordField密文输入框,用于输入密码JPasswordField passwordText = new JPasswordField();// 7、创建一个JTextField明文输入框,用于输入验证码JTextField verifyCodeText = new JTextField();// 8、创建一个JLabel管理容器,用于管理显示的验证码JLabel displayVerifyCodeJLabel = new JLabel(VerifyCodeUtil.getVerifyCode());// 9、创建一个JButton按钮对象,用于管理登录按钮图片JButton loginButton = new JButton();// 10、创建一个JButton按钮对象,用于管理注册按钮图片JButton registerButton = new JButton();/*提供无参数的构造器需求:初始化一个宽501像素,高437像素的登录界面*/public LoginJFrame() {// 初始化界面initJFrame();// 初始化登录信息框initLoginMess();// 最后设置界面为显示的:建议写在最后,因为所有设置都弄好了之后,才显示出完整的界面// 由于界面默认是隐藏起来的,因此需要调用setVisible方法,设置显示界面this.setVisible(true);}// 初始化登录信息框private void initLoginMess() {// 1、创建一个JLabel管理容器,用于管理用户名输入框JLabel usernameJLabel = new JLabel("用户名");// 设置管理容器的坐标及宽高usernameJLabel.setBounds(90, 100, 350, 100);// 设置输入框的坐标位置及宽高usernameText.setBounds(80, 35, 200, 30);// 将用户名输入框放进管理容器中usernameJLabel.add(usernameText);// 然后将管理容器放进界面中this.getContentPane().add(usernameJLabel);// 3、创建一个JLabel管理容器,用于管理密码输入框JLabel passwordJLabel = new JLabel("密码");// 设置管理容器的坐标及宽高passwordJLabel.setBounds(90, 150, 350, 100);// 设置输入框的坐标位置及宽高passwordText.setBounds(80, 35, 200, 30);// 将密码输入框放进管理容器中passwordJLabel.add(passwordText);// 然后将管理容器放进界面中this.getContentPane().add(passwordJLabel);// 6、创建一个JLabel管理容器,用于管理验证码输入框JLabel verifyCodeJLabel = new JLabel("验证码");// 设置管理容器的坐标及宽高verifyCodeJLabel.setBounds(90, 200, 350, 100);// 设置输入框的坐标位置及宽高verifyCodeText.setBounds(80, 35, 200, 30);// 将验证码输入框放进管理容器中verifyCodeJLabel.add(verifyCodeText);// 然后将管理容器放进界面中this.getContentPane().add(verifyCodeJLabel);// 设置显示验证码管理容器的坐标及宽高displayVerifyCodeJLabel.setBounds(380, 232, 200, 30);// 需要给显示验证码的管理容器绑定鼠标监听displayVerifyCodeJLabel.addMouseListener(this);// 然后将管理容器放进界面中this.getContentPane().add(displayVerifyCodeJLabel);// 根据指定登录图片的相对路径创建图片对象,并设置到登录按钮中loginButton.setIcon(new ImageIcon("puzzle_game\\puzzleimages\\login\\login_button.jpg"));// 设置登录按钮的坐标及宽高loginButton.setBounds(90, 300, 115, 35);// 需要给登录按钮绑定鼠标监听loginButton.addMouseListener(this);// 然后将登录按钮对象放进界面中this.getContentPane().add(loginButton);// 根据指定注册图片的相对路径创建图片对象,并设置到注册按钮中registerButton.setIcon(new ImageIcon("puzzle_game\\puzzleimages\\register\\register_button.jpg"));// 设置注册按钮的坐标位置及宽高registerButton.setBounds(256, 300, 115, 35);// 需要给注册按钮绑定鼠标监听registerButton.addMouseListener(this);// 然后将注册按钮对象放进界面中this.getContentPane().add(registerButton);// 最后,创建一个JLabel管理容器,用于管理背景图JLabel bgJLabel = new JLabel(new ImageIcon("puzzle_game\\puzzleimages\\sport\\register_login_background.jpg"));// 设置背景图管理容器的坐标位置及宽高bgJLabel.setBounds(0, 0, 488, 400);// 然后将管理容器放进界面中this.getContentPane().add(bgJLabel);}// 初始化登录界面private void initJFrame() {// 注:如果当前类没有以下这些方法,就会自动调用父类JFrame的// 设置界面宽高// 调用setSize方法,初始化一个宽501像素,高437像素大小的界面this.setSize(501, 437);// 设置界面标题this.setTitle("奥利gei拼图-登录");// 设置界面为置顶:用户在点击其他界面时,该界面一直是置顶的this.setAlwaysOnTop(true);// 设置界面打开时自动居中this.setLocationRelativeTo(null);// 设置界面的关闭模式:用户只需要关闭一个界面,其他界面会自动关闭,并且结束JVM虚拟机的运行this.setDefaultCloseOperation(3);// 取消默认布局,这样子才可以让布局按照x和y轴的形式添加组件this.setLayout(null);}// 鼠标单击事件@Overridepublic void mouseClicked(MouseEvent e) {// 1、获取当前鼠标单击的事件Object obj = e.getSource();// 2、判断当前鼠标单击的事件是否为验证码管理容器:if (obj == displayVerifyCodeJLabel) {System.out.println("鼠标点击验证码");// 是,则重新生成新的验证码displayVerifyCodeJLabel.setText(VerifyCodeUtil.getVerifyCode());}}// 按下鼠标事件@Overridepublic void mousePressed(MouseEvent e) {// 1、获取当前按下的事件Object obj = e.getSource();// 2、判断当前按下的事件是否为登录按钮:if (obj == loginButton) {// 是System.out.println("按下登录按钮");// (1)说明当前按下的是登录按钮,则让按钮变暗loginButton.setIcon(new ImageIcon("puzzle_game\\puzzleimages\\login\\login_down.jpg"));// 用户登录userLogin();} else if (obj == registerButton) { // 否,则判断当前按下的事件是否为注册按钮:// 是System.out.println("按下注册按钮");// (1)说明当前按下的是注册按钮,则让按钮变暗registerButton.setIcon(new ImageIcon("puzzle_game\\puzzleimages\\register\\register_down.jpg"));// (2)关闭登录界面后,跳转到注册界面中this.setVisible(false);new RegisterJFrame();}}/*** 用户登录功能*/private void userLogin() {// 获取输入框的用户名、密码、验证码String inputUsername = usernameText.getText();String inputPassword = passwordText.getText();String inputVerifyCode = verifyCodeText.getText();// 先校验用户名是否为空字符串if (inputUsername.equals("")) {// 是空字符串,则创建一个弹窗提示:“用户名不能为空!”createJDialog(new JLabel("用户名不能为空!"));} else {// 否,说明用户名不是空字符串,则校验密码是否为空字符串if (inputPassword.equals("")) {// 是空字符串,则创建一个弹窗提示:“密码不能为空!”createJDialog(new JLabel("密码不能为空!"));} else {// 否,说明密码不是空字符串,则校验验证码是否为空字符if (inputVerifyCode.equals("")) {// 是,则创建一个弹窗提示:“验证码不能为空!”createJDialog(new JLabel("验证码不能为空!"));}else {// 否,说明验证码不为空字符串,校验用户名输入框的用户名是否未注册:if (!checkUsername(inputUsername)) {// 是,则说明该用户名未注册,弹窗提示:“用户名未注册!请先注册!”createJDialog(new JLabel("用户名:" + usernameText.getText() + "未注册!请先注册!"));} else {// 否,则说明该用户名已注册,则校验用户名和密码是否正确:if (checkUsernameAndPassword(inputUsername, inputPassword)) {// 是,说明用户名和密码正确,则校验验证码是否正确:if (inputVerifyCode.equalsIgnoreCase(displayVerifyCodeJLabel.getText())) {// 是,则说明验证码正确,弹窗提示:“登录成功!”createJDialog(new JLabel("登录成功!"));// 关闭弹窗后跳转到游戏界面!this.setVisible(false);new GameJFrame();} else {// 否,则说明验证码不正确,弹窗提示:“验证码错误!”createJDialog(new JLabel("验证码错误!"));}} else {// 否,则说明不正确,则创建一个弹窗提示:“您的用户名或密码有误!”createJDialog(new JLabel("您的用户名或密码有误!"));}}}}}}/*** 校验用户名和密码是否正确** @param inputUsername 输入框的用户名* @param inputPassword 输入框的密码* @return 正确返回true,否则返回false*/private boolean checkUsernameAndPassword(String inputUsername, String inputPassword) {// 1、遍历userList集合,依次得到每个用户对象for (User user : userList) {// 判断输入框的用户名和密码 是否匹配 当前用户对象的用户名和密码if ((inputUsername.equals(user.getUsername())&& (inputPassword.equals(user.getPassword())))) {// 匹配,返回truereturn true;}// 不匹配,则继续与集合中的下一个用户对象做比较}// 2、循环结束,仍然找不到匹配的用户名和密码,说明用户名或密码错误!返回falsereturn false;}/*** 校验用户是否已注册!** @param inputUsername 输入框的用户名* @return 已注册返回true,未注册返回true*/private boolean checkUsername(String inputUsername) {// 1、根据用户名获取该用户对象在集合中的索引int index = RegisterJFrame.getUserIndex(inputUsername);// 2、判断索引是否大于等于0if (index >= 0) {// 是,说明该用户存在,返回truereturn true;}// 否,说明该用户不存在,返回falsereturn false;}// 释放鼠标事件@Overridepublic void mouseReleased(MouseEvent e) {}// 鼠标划入事件@Overridepublic void mouseEntered(MouseEvent e) {}// 鼠标划出事件@Overridepublic void mouseExited(MouseEvent e) {}/*** 创建一个对话窗** @param jLabel 管理容器对象*/private void createJDialog(JLabel jLabel) {// 1、创建对话窗对象JDialog jDialog = new JDialog();// 2、设置管理容器的坐标及宽高jLabel.setBounds(0, 0, 100, 100);// 3、将管理容器添加到对话窗中jDialog.getContentPane().add(jLabel);// 设置对话窗的大小jDialog.setSize(300, 100);// 4、将对话窗置顶jDialog.setAlwaysOnTop(true);// 5、让对话窗居中jDialog.setLocationRelativeTo(null);// 6、设置对话窗不关闭则无法操作下面的界面jDialog.setModal(true);// 7、让对话窗显示出来,因为默认是隐藏的jDialog.setVisible(true);// 8、当用户关闭对话窗时,登录按钮恢复原样!loginButton.setIcon(new ImageIcon("puzzle_game\\puzzleimages\\login\\login_button.jpg"));}
}
import cn.edu.gxufe.ui.GameJFrame;
import cn.edu.gxufe.ui.LoginJFrame;
import cn.edu.gxufe.ui.RegisterJFrame;public class App {public static void main(String[] args) {// 表示程序启动的入口// 如果我们想要开启一个界面,就创建对应界面的对象即可!new LoginJFrame(); // 登录界面}
}
直接点击登录:
点击注册按钮:
直接点击注册按钮:
当关闭弹窗后:
当关闭弹窗后:
当点击重新登录后:
(1)一定要包含图形化界面
(2)代码要打包起来
(3)游戏用到的图片素材也要打包起来
(4)JDK也要打包
(1)把所有代码打包成一个压缩包,jar后缀的压缩包。
(2)把jar包转换成exe安装包
(3)把第二步的exe,图片,JDK整合在一起,变成最终的exe安装包
登录界面测试:
注册界面测试:
关闭弹窗后会自动回到登录界面,再次点击注册:
关闭弹窗后会自动回到登录界面
关闭弹窗后会自动跳转到拼图游戏主界面:
测试更换图片功能(更换图片时不重置步数):
测试重新游戏功能(重新游戏后的图片是更换后的图片):
测试关于我们菜单下的公众号功能:
测试重新登录功能:
关闭弹窗后会自动跳转到拼图游戏主界面:
测试关闭游戏功能: