锻炼逻辑思维能力,让我们知道前面学习的知识点在实际开发中的应用场景。
1、为了学习一个新知识:GUI
2、为什么图形化用户界面这个知识点很多Java课程中都不讲呢?
服务器
通过网络
传递给客户端(电脑、手机等)
的。
3、难道图形化用户界面这个知识点是真的一点用都没有了吗?
4、那我们之前做的学生管理系统、文字版格斗游戏、评委打分等等那些难道不是项目吗?
拆分成三部分:
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方法的返回结果是否为true
if (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++) {
// 判断当前数据是否为0
if (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方法的返回结果是否为true
if (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:鼠标监听
ActionListener:动作监听
package cn.edu.gxufe.test;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
// 创建动作监听ActionListener接口的实现类,用于实现接口里的单击按钮方法
public class MyActionListenerImpl implements ActionListener{
// 实现ActionListener接口中的单击按钮方法
@Override
public 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() {
@Override
public 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 {
// 创建按钮对象1
JButton jbt1 = new JButton("点我啊");
// 创建按钮对象2
JButton 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接口的单击按钮
*/
@Override
public void actionPerformed(ActionEvent e) {
// 获取当前的按钮对象
Object source = e.getSource();
// 判断当前的按钮对象是否为jbt1
if (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的鼠标单击事件方法
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("鼠标单击了一下");
}
// 实现鼠标监听接口MouseListener的按下鼠标事件方法
@Override
public void mousePressed(MouseEvent e) {
System.out.println("被鼠标按了一下");
}
// 实现鼠标监听接口MouseListener的松开鼠标事件方法
@Override
public void mouseReleased(MouseEvent e) {
System.out.println("鼠标松开了");
}
// 实现鼠标监听接口MouseListener的鼠标划入事件方法
@Override
public void mouseEntered(MouseEvent e) {
System.out.println("鼠标划进来了");
}
// 实现鼠标监听接口MouseListener的鼠标划出事件方法
@Override
public 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);
}
// 这个可以不用管
@Override
public void keyTyped(KeyEvent e) {
}
// 实现键盘监听接口KeyListener的按下键盘方法
/*
细节1:
如果我们按下键盘的一个键没有松开,那么会重复的去调用keyPressed方法
细节2:
键盘里面那么多按键,如果进行区分?
答:每一个按键都有一个编号与之对应的
*/
@Override
public void keyPressed(KeyEvent e) {
// 获取键盘每个按键的编号
int keyCode = e.getKeyCode();
// 判断当前按下的按键的编号是否为65
if (keyCode == 65) {
// 是,则说明按下的按键是A
System.out.println("按下按键A");
}else if (keyCode == 66){ // 否,则判断当前按下的按键的编号是否为66
// 是,则说明按下的按键是B
System.out.println("按下按键B");
}else {
// 否,则说明按下的是其他按键
System.out.println("按下的是其他按键");
}
}
// 实现键盘监听接口KeyListener的释放(松开)键盘方法
@Override
public 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++) {
// 判断当前数据是否为0
if (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方法的返回结果是否为true
if (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++) {
// 判断当前数据是否为0
if (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方法的返回结果是否为true
if (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();
}
// 这个可以不用管
@Override
public void keyTyped(KeyEvent e) {
}
/*
监听按下键盘按键的事件
细节:当按下按键不松开,会不断移动图片
*/
@Override
public void keyPressed(KeyEvent e) {
// 获取键盘每个按键的编号
int keyCode = e.getKeyCode();
// 判断按下的按键是否为A
if (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();
}
}
/*
监听松开键盘按键的事件:
当按下按键松开后,会调用该方法
*/
@Override
public void keyReleased(KeyEvent e) {
// 获取键盘每个按键的编号
int keyCode = e.getKeyCode();
// 判断玩家是否已经胜利!
if (victory()) {
// 是,则说明已经胜利!则直接结束当前方法!不让玩家继续进行拼图操作!
return;
}
/*
上下左右按键的编号:
左:37、上:38、右:39、下:40
*/
// 判断松开的按键是否为上
if (keyCode == 38) {
// 判断空白方块的X轴是否为3
if (x == 3) {
// 是,说明空白方块下方已没有图片可以向上移动了,则提示!
System.out.println("下方已没有图片可以向上移动了");
return; // 结束方法!
}
// 否,则图片向上移动
System.out.println("图片向上移动");
/*
图片向上移动逻辑:
把空白方块下方的数字向上移动
x, y: 表示空白方块
x + 1, y: 表示空白方块下方的数字
*/
// 将空白方块下方图片的数据 赋值给 空白方块处
data[x][y] = data[x + 1][y];
// 空白方块下方图片的数据赋值为0
data[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];
// 空白下方图片的数据赋值为0
data[x - 1][y] = 0;
// 将空白方块往上移动
x--;
// 移动后统计步数
step++;
// 重新加载一下所有图片
initImages(path);
} else if (keyCode == 37) { // 否,则判断松开的按键是否为左
// 是,则判断空白方块的Y轴是否为3
if (y == 3) {
// 是,说明空白方块的右方已没有图片可以向左移动了,则提示!
System.out.println("右方已没有图片可以向左移动了");
return; // 结束方法!
}
// 否,则图片向左移动
System.out.println("图片向左移动");
/*
图片向左移动逻辑:
把空白方块右方的数字向左移动
x, y: 表示空白方块
x, y + 1: 表示空白方块右方的数字
*/
// 将空表方块右方图片的数据 赋值给 空白方块处
data[x][y] = data[x][y + 1];
// 空白右方图片的数据赋值为0
data[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];
// 空白左方图片的数据赋值为0
data[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++) {
// 判断当前数据是否为0
if (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方法的返回结果是否为true
if (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();
}
// 这个可以不用管
@Override
public void keyTyped(KeyEvent e) {
}
/*
监听按下键盘按键的事件
细节:当按下按键不松开,会不断移动图片
*/
@Override
public void keyPressed(KeyEvent e) {
// 获取键盘每个按键的编号
int keyCode = e.getKeyCode();
// 判断按下的按键是否为A
if (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();
}
}
/*
监听松开键盘按键的事件:
当按下按键松开后,会调用该方法
*/
@Override
public void keyReleased(KeyEvent e) {
// 获取键盘每个按键的编号
int keyCode = e.getKeyCode();
// 判断玩家是否已经胜利!
if (victory()) {
// 是,则说明已经胜利!则直接结束当前方法!不让玩家继续进行拼图操作!
return;
}
/*
上下左右按键的编号:
左:37、上:38、右:39、下:40
*/
// 判断松开的按键是否为上
if (keyCode == 38) {
// 判断空白方块的X轴是否为3
if (x == 3) {
// 是,说明空白方块下方已没有图片可以向上移动了,则提示!
System.out.println("下方已没有图片可以向上移动了");
return; // 结束方法!
}
// 否,则图片向上移动
System.out.println("图片向上移动");
/*
图片向上移动逻辑:
把空白方块下方的数字向上移动
x, y: 表示空白方块
x + 1, y: 表示空白方块下方的数字
*/
// 将空白方块下方图片的数据 赋值给 空白方块处
data[x][y] = data[x + 1][y];
// 空白方块下方图片的数据赋值为0
data[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];
// 空白下方图片的数据赋值为0
data[x - 1][y] = 0;
// 将空白方块往上移动
x--;
// 移动后统计步数
step++;
// 重新加载一下所有图片
initImages(path);
} else if (keyCode == 37) { // 否,则判断松开的按键是否为左
// 是,则判断空白方块的Y轴是否为3
if (y == 3) {
// 是,说明空白方块的右方已没有图片可以向左移动了,则提示!
System.out.println("右方已没有图片可以向左移动了");
return; // 结束方法!
}
// 否,则图片向左移动
System.out.println("图片向左移动");
/*
图片向左移动逻辑:
把空白方块右方的数字向左移动
x, y: 表示空白方块
x, y + 1: 表示空白方块右方的数字
*/
// 将空表方块右方图片的数据 赋值给 空白方块处
data[x][y] = data[x][y + 1];
// 空白右方图片的数据赋值为0
data[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];
// 空白左方图片的数据赋值为0
data[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++) {
// 判断当前数据是否为0
if (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方法的返回结果是否为true
if (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();
}
// 这个可以不用管
@Override
public void keyTyped(KeyEvent e) {
}
/*
监听按下键盘按键的事件
细节:当按下按键不松开,会不断移动图片
*/
@Override
public void keyPressed(KeyEvent e) {
// 获取键盘每个按键的编号
int keyCode = e.getKeyCode();
// 判断按下的按键是否为A
if (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();
}
}
/*
监听松开键盘按键的事件:
当按下按键松开后,会调用该方法
*/
@Override
public void keyReleased(KeyEvent e) {
// 获取键盘每个按键的编号
int keyCode = e.getKeyCode();
// 判断玩家是否已经胜利!
if (victory()) {
// 是,则说明已经胜利!则直接结束当前方法!不让玩家继续进行拼图操作!
return;
}
/*
上下左右按键的编号:
左:37、上:38、右:39、下:40
*/
// 判断松开的按键是否为上
if (keyCode == 38) {
// 判断空白方块的X轴是否为3
if (x == 3) {
// 是,说明空白方块下方已没有图片可以向上移动了,则提示!
System.out.println("下方已没有图片可以向上移动了");
return; // 结束方法!
}
// 否,则图片向上移动
System.out.println("图片向上移动");
/*
图片向上移动逻辑:
把空白方块下方的数字向上移动
x, y: 表示空白方块
x + 1, y: 表示空白方块下方的数字
*/
// 将空白方块下方图片的数据 赋值给 空白方块处
data[x][y] = data[x + 1][y];
// 空白方块下方图片的数据赋值为0
data[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];
// 空白下方图片的数据赋值为0
data[x - 1][y] = 0;
// 将空白方块往上移动
x--;
// 移动后统计步数
step++;
// 重新加载一下所有图片
initImages(path);
} else if (keyCode == 37) { // 否,则判断松开的按键是否为左
// 是,则判断空白方块的Y轴是否为3
if (y == 3) {
// 是,说明空白方块的右方已没有图片可以向左移动了,则提示!
System.out.println("右方已没有图片可以向左移动了");
return; // 结束方法!
}
// 否,则图片向左移动
System.out.println("图片向左移动");
/*
图片向左移动逻辑:
把空白方块右方的数字向左移动
x, y: 表示空白方块
x, y + 1: 表示空白方块右方的数字
*/
// 将空表方块右方图片的数据 赋值给 空白方块处
data[x][y] = data[x][y + 1];
// 空白右方图片的数据赋值为0
data[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];
// 空白左方图片的数据赋值为0
data[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++) {
// 判断当前数据是否为0
if (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方法的返回结果是否为true
if (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();
}
// 这个可以不用管
@Override
public void keyTyped(KeyEvent e) {
}
/*
监听按下键盘按键的事件
细节:当按下按键不松开,会不断移动图片
*/
@Override
public void keyPressed(KeyEvent e) {
// 获取键盘每个按键的编号
int keyCode = e.getKeyCode();
// 判断按下的按键是否为A
if (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();
}
}
/*
监听松开键盘按键的事件:
当按下按键松开后,会调用该方法
*/
@Override
public void keyReleased(KeyEvent e) {
// 获取键盘每个按键的编号
int keyCode = e.getKeyCode();
// 判断玩家是否已经胜利!
if (victory()) {
// 是,则说明已经胜利!则直接结束当前方法!不让玩家继续进行拼图操作!
return;
}
/*
上下左右按键的编号:
左:37、上:38、右:39、下:40
*/
// 判断松开的按键是否为上
if (keyCode == 38) {
// 判断空白方块的X轴是否为3
if (x == 3) {
// 是,说明空白方块下方已没有图片可以向上移动了,则提示!
System.out.println("下方已没有图片可以向上移动了");
return; // 结束方法!
}
// 否,则图片向上移动
System.out.println("图片向上移动");
/*
图片向上移动逻辑:
把空白方块下方的数字向上移动
x, y: 表示空白方块
x + 1, y: 表示空白方块下方的数字
*/
// 将空白方块下方图片的数据 赋值给 空白方块处
data[x][y] = data[x + 1][y];
// 空白方块下方图片的数据赋值为0
data[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];
// 空白下方图片的数据赋值为0
data[x - 1][y] = 0;
// 将空白方块往上移动
x--;
// 移动后统计步数
step++;
// 重新加载一下所有图片
initImages(path);
} else if (keyCode == 37) { // 否,则判断松开的按键是否为左
// 是,则判断空白方块的Y轴是否为3
if (y == 3) {
// 是,说明空白方块的右方已没有图片可以向左移动了,则提示!
System.out.println("右方已没有图片可以向左移动了");
return; // 结束方法!
}
// 否,则图片向左移动
System.out.println("图片向左移动");
/*
图片向左移动逻辑:
把空白方块右方的数字向左移动
x, y: 表示空白方块
x, y + 1: 表示空白方块右方的数字
*/
// 将空表方块右方图片的数据 赋值给 空白方块处
data[x][y] = data[x][y + 1];
// 空白右方图片的数据赋值为0
data[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];
// 空白左方图片的数据赋值为0
data[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++) {
// 判断当前数据是否为0
if (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方法的返回结果是否为true
if (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();
}
// 这个可以不用管
@Override
public void keyTyped(KeyEvent e) {
}
/*
监听按下键盘按键的事件
细节:当按下按键不松开,会不断移动图片
*/
@Override
public void keyPressed(KeyEvent e) {
// 获取键盘每个按键的编号
int keyCode = e.getKeyCode();
// 判断按下的按键是否为A
if (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();
}
}
/*
监听松开键盘按键的事件:
当按下按键松开后,会调用该方法
*/
@Override
public void keyReleased(KeyEvent e) {
// 获取键盘每个按键的编号
int keyCode = e.getKeyCode();
// 判断玩家是否已经胜利!
if (victory()) {
// 是,则说明已经胜利!则直接结束当前方法!不让玩家继续进行拼图操作!
return;
}
/*
上下左右按键的编号:
左:37、上:38、右:39、下:40
*/
// 判断松开的按键是否为上
if (keyCode == 38) {
// 判断空白方块的X轴是否为3
if (x == 3) {
// 是,说明空白方块下方已没有图片可以向上移动了,则提示!
System.out.println("下方已没有图片可以向上移动了");
return; // 结束方法!
}
// 否,则图片向上移动
System.out.println("图片向上移动");
/*
图片向上移动逻辑:
把空白方块下方的数字向上移动
x, y: 表示空白方块
x + 1, y: 表示空白方块下方的数字
*/
// 将空白方块下方图片的数据 赋值给 空白方块处
data[x][y] = data[x + 1][y];
// 空白方块下方图片的数据赋值为0
data[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];
// 空白下方图片的数据赋值为0
data[x - 1][y] = 0;
// 将空白方块往上移动
x--;
// 移动后统计步数
step++;
// 重新加载一下所有图片
initImages(path);
} else if (keyCode == 37) { // 否,则判断松开的按键是否为左
// 是,则判断空白方块的Y轴是否为3
if (y == 3) {
// 是,说明空白方块的右方已没有图片可以向左移动了,则提示!
System.out.println("右方已没有图片可以向左移动了");
return; // 结束方法!
}
// 否,则图片向左移动
System.out.println("图片向左移动");
/*
图片向左移动逻辑:
把空白方块右方的数字向左移动
x, y: 表示空白方块
x, y + 1: 表示空白方块右方的数字
*/
// 将空表方块右方图片的数据 赋值给 空白方块处
data[x][y] = data[x][y + 1];
// 空白右方图片的数据赋值为0
data[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];
// 空白左方图片的数据赋值为0
data[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++) {
// 判断当前数据是否为0
if (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方法的返回结果是否为true
if (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();
}
// 这个可以不用管
@Override
public void keyTyped(KeyEvent e) {
}
/*
监听按下键盘按键的事件
细节:当按下按键不松开,会不断移动图片
*/
@Override
public void keyPressed(KeyEvent e) {
// 获取键盘每个按键的编号
int keyCode = e.getKeyCode();
// 判断按下的按键是否为A
if (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();
}
}
/*
监听松开键盘按键的事件:
当按下按键松开后,会调用该方法
*/
@Override
public void keyReleased(KeyEvent e) {
// 获取键盘每个按键的编号
int keyCode = e.getKeyCode();
// 判断玩家是否已经胜利!
if (victory()) {
// 是,则说明已经胜利!则直接结束当前方法!不让玩家继续进行拼图操作!
return;
}
/*
上下左右按键的编号:
左:37、上:38、右:39、下:40
*/
// 判断松开的按键是否为上
if (keyCode == 38) {
// 判断空白方块的X轴是否为3
if (x == 3) {
// 是,说明空白方块下方已没有图片可以向上移动了,则提示!
System.out.println("下方已没有图片可以向上移动了");
return; // 结束方法!
}
// 否,则图片向上移动
System.out.println("图片向上移动");
/*
图片向上移动逻辑:
把空白方块下方的数字向上移动
x, y: 表示空白方块
x + 1, y: 表示空白方块下方的数字
*/
// 将空白方块下方图片的数据 赋值给 空白方块处
data[x][y] = data[x + 1][y];
// 空白方块下方图片的数据赋值为0
data[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];
// 空白下方图片的数据赋值为0
data[x - 1][y] = 0;
// 将空白方块往上移动
x--;
// 移动后统计步数
step++;
// 重新加载一下所有图片
initImages(path);
} else if (keyCode == 37) { // 否,则判断松开的按键是否为左
// 是,则判断空白方块的Y轴是否为3
if (y == 3) {
// 是,说明空白方块的右方已没有图片可以向左移动了,则提示!
System.out.println("右方已没有图片可以向左移动了");
return; // 结束方法!
}
// 否,则图片向左移动
System.out.println("图片向左移动");
/*
图片向左移动逻辑:
把空白方块右方的数字向左移动
x, y: 表示空白方块
x, y + 1: 表示空白方块右方的数字
*/
// 将空表方块右方图片的数据 赋值给 空白方块处
data[x][y] = data[x][y + 1];
// 空白右方图片的数据赋值为0
data[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];
// 空白左方图片的数据赋值为0
data[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);
}
}
// 动作监听事件:监听鼠标左键单击、键盘空格操作
@Override
public 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];
}*/
// 判断当前数据是否为0
if (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方法的返回结果是否为true
if (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;
}
}
}
// 循环都结束了!说明两个二维数组的数据都相等!则返回true
return 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);
}
// 这个可以不用管
@Override
public void keyTyped(KeyEvent e) {
}
/*
监听按下键盘按键的事件
细节:当按下按键不松开,会不断移动图片
*/
@Override
public void keyPressed(KeyEvent e) {
// 获取键盘每个按键的编号
int keyCode = e.getKeyCode();
// 判断按下的按键是否为A
if (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();
}
}
/*
监听松开键盘按键的事件:
当按下按键松开后,会调用该方法
*/
@Override
public void keyReleased(KeyEvent e) {
// 获取键盘每个按键的编号
int keyCode = e.getKeyCode();
// 判断玩家是否已经胜利!
if (victory()) {
// 是,则说明已经胜利!则直接结束当前方法!不让玩家继续进行拼图操作!
return;
}
/*
上下左右按键的编号:
左:37、上:38、右:39、下:40
*/
// 判断松开的按键是否为上
if (keyCode == 38) {
// 判断空白方块的X轴是否为3
if (x == 3) {
// 是,说明空白方块下方已没有图片可以向上移动了,则提示!
System.out.println("下方已没有图片可以向上移动了");
return; // 结束方法!
}
// 否,则图片向上移动
System.out.println("图片向上移动");
/*
图片向上移动逻辑:
把空白方块下方的数字向上移动
x, y: 表示空白方块
x + 1, y: 表示空白方块下方的数字
*/
// 将空白方块下方图片的数据 赋值给 空白方块处
data[x][y] = data[x + 1][y];
// 空白方块下方图片的数据赋值为0
data[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];
// 空白下方图片的数据赋值为0
data[x - 1][y] = 0;
// 将空白方块往上移动
x--;
// 移动后统计步数
step++;
// 重新加载一下所有图片
initImages(path);
} else if (keyCode == 37) { // 否,则判断松开的按键是否为左
// 是,则判断空白方块的Y轴是否为3
if (y == 3) {
// 是,说明空白方块的右方已没有图片可以向左移动了,则提示!
System.out.println("右方已没有图片可以向左移动了");
return; // 结束方法!
}
// 否,则图片向左移动
System.out.println("图片向左移动");
/*
图片向左移动逻辑:
把空白方块右方的数字向左移动
x, y: 表示空白方块
x, y + 1: 表示空白方块右方的数字
*/
// 将空表方块右方图片的数据 赋值给 空白方块处
data[x][y] = data[x][y + 1];
// 空白右方图片的数据赋值为0
data[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];
// 空白左方图片的数据赋值为0
data[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);
}
}
// 动作监听事件:监听鼠标左键单击、键盘空格操作
@Override
public 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(); // 注册界面
}
}
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<User> 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);
}
// 鼠标单击事件
@Override
public void mouseClicked(MouseEvent e) {}
// 按下鼠标事件
@Override
public 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("");
}
}
// 鼠标释放事件
@Override
public 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"));
}
}
// 鼠标划入事件
@Override
public void mouseEntered(MouseEvent e) {
}
// 鼠标划出事件
@Override
public 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'))) {
// 有一个是中文,就返回false
return false;
}
}
// 循环结束,说明都不是中文,返回true
return true;
}
/**
* 校验用户名
*
* @return 用户名不存在返回true,否则返回false
*/
private boolean checkUsername() {
// 2、根据注册的用户名得到该用户对象在集合中的索引
int index = getUserIndex(nameJText.getText());
// 3、判断索引是否小于0
if (index < 0) {
// 该用户不存在!返回null
return 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、循环结束,仍然找不到与注册的用户名 匹配的用户对象,返回-1
return -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<User> 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方法,设置显示界面