大一新手小白跟黑马学习的第一个图形化项目:拼图小游戏(java)

本项目使用Java Swing完成。由idea编程。

该游戏由三个窗口组成:分别为1、游戏主界面 2、注册界面 3、登录界面

一、游戏主界面

1、设置长宽高(setSize:603、680)(由于不太熟悉JFame的方法,所以记录一下。)

2、设置可见(setVisible)(建议放在最下面)(设置true)(不设置看不到窗口)

3、设置标题(setTitle)

4、设置置顶(setAlwaysOnTop):在任何应用的上方

5、设置界面居中(setLocationRelativeTo)(null)

6、设置关闭模式 this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);(写3也行)。关闭一个窗口,所有窗口关闭,虚拟机结束。

7、菜单制作JMenuBar------------Jmenu------------------JmenuItem

创建三个Jmenu对象

用add把下一级的对象添加到上一级

最后设置菜单

8、添加图片

把分割好的图片复制粘贴到模块中

简单的添加单张图片 创建ImageIcon对象(后写图片路径)和JLabel对象(后写ImageIcon对象)

图片默认位置 在中间

指定图片位置(在jLabel对象中使用setBounds)

在初始化方法中取消默认的居中放置,只有取消了才会按照xy轴的形式添加组件(使用setLayout)

图片在左上角了

利用两个for循环把所有图片添加上去,文件名用变量

还要把把管理容器添加到界面中

this.getContentPane().add(jLabel);

添加完成之后

9.打乱图片

首先创建一个一维数组保存数据(1-15)

再利用random打乱里面的数据,添加到一个二维数组中(二维数组创建要在最上面,因为在另一个方法中也要用上),

最后用一个变量接收二维数组的数据,把变量放在图片名称那。

ini 复制代码
private void initDate() {

 //定义一个一维数组
 int[] tempArr = {0,1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
 //打乱数组中的数据的顺序
 //遍历数组,得到每一个元素,拿着每一个元素跟随机索引上的数据进行交换
 Random r = new Random();
 for (int i = 0; i < tempArr.length; i++) {
  int index = r.nextInt(tempArr.length);
  int temp = tempArr[i];
  tempArr[i] = tempArr[index];
  tempArr[index] = temp;
 }

 for (int i = 0; i < tempArr.length; i++) {
  if (tempArr[i] ==0){
   x = i / 4;
   y = i % 4;
  }
   data[i / 4][i % 4] = tempArr[i];
 }
}

10、美化界面

把图片的位置调整在合适的位置上

把背景图片放上去(背景图片比拼图图片后添加)(先添加的在上面)

给图片添加边框

arduino 复制代码
//给图片添加边框
//0:表示让图片凸起来
//1:表示让图片凹下去
jLabel.setBorder(new BevelBorder(0));

11、事件(重要)

给图片添加上下移动的事件监听(目前添加键盘监听)

首先在GameJFame实现KeyListener 接口并重写所有方法

在添加游戏开始窗口中设置键盘监听

kotlin 复制代码
//给整个界面添加键盘监听事件
this.addKeyListener(this);

让图片上下左右移动,就是找到空白的图片的位置,让它跟旁边的图片交换位置。

首先找到空白图片的位置(在添加数据这)(把一维数据添加到二维数组中时进行if判断)(定义x ,y记录空白图片坐标)

ini 复制代码
for (int i = 0; i < tempArr.length; i++) {
 if (tempArr[i] ==0){
  x = i / 4;
  y = i % 4;
 }
  data[i / 4][i % 4] = tempArr[i];
}

在重写的public void keyReleased(KeyEvent e)方法中添加键盘上下左右的监听

以下为做好的代码

ini 复制代码
@Override
public void keyReleased(KeyEvent e) {
 //对上下左右进行判断
 //左:37; 上:38; 右:39; 下:40;
 int code = e.getKeyCode();
 if (code == 37) {
  System.out.println("向左移动");
  if(x==3){
   return;
  }

  data[x][y] = data[x+1][y];
  data[x+1][y] = 0;
  x++;
  //调用方法按照最新的数字加载
  initImage();
 } else if (code == 38) {

  System.out.println("向上移动");
  if(y==3){
   return;
  }
  //逻辑:
  //把空白方块下方的数字往上移动
  //x,y  表示空白方块
  //x+1,y  表示空白方块下方的数字

  //把空白方块下方的数字赋值给空白方块
  data[x][y] = data[x][y+1];
  data[x][y+1] = 0;
  y++;
  //调用方法按照最新的数字加载
  initImage();


 } else if (code == 39) {
  System.out.println("向右移动");
  if(x==0){
   return;
  }
  data[x][y] = data[x-1][y];
  data[x-1][y] = 0;
  x--;
  //调用方法按照最新的数字加载
  initImage();
 } else if (code == 40) {
  System.out.println("向下移动");
  if(y==0){
   return;
  }
  data[x][y] = data[x][y-1];
  data[x][y-1] = 0;
  y--;
  //调用方法按照最新的数字加载
  initImage();
 }else if(code == 65){
  initImage();
 }
}

设置return是为了程序不出现索引越界异常

12、添加作弊码(按A不松可查看完整图片)(一键通关)

在keyPressed方法中

代码为:

scss 复制代码
//键盘按下不松时会调用这个方法
@Override
public void keyPressed(KeyEvent e) {
 int code = e.getKeyCode();
 if(code == 65){
  //把界面中所有的图片全部删除
  this.getContentPane().removeAll();
  //加载第一张完整的图片
  JLabel all = new JLabel(new ImageIcon("D:\Java\daima\pingtuyouxi\image\animal\animal3\all.jpg"));
  all.setBounds(83,140,420,420);
  this.getContentPane().add(all);
  //加载背景图片
  JLabel backeground = new JLabel(new ImageIcon("image\background.png"));
  backeground.setBounds(40,40,508,560);
  this.getContentPane().add(backeground);

  //刷新界面
  this.getContentPane().repaint();

 }
}

注意要在按下松开方法添加A键的键盘监听(即调用initImage()方法,重新加载图片)

设置一个变量赋值为图片路径,方便修改(因为每个图片的后缀名都一样)

vbnet 复制代码
String path = "image\animal\animal3\";

设置一键通关:

在按下松开设置w键一键通关(即加载完整的图片) 需要把二维数组排列成正确的顺序,而不是打乱的顺序,然后再重新加载一次图片

ini 复制代码
else if(code == 87){
 data = new int[][]{
         {1,5,9,13},
         {2,6,10,14},
         {3,7,11,15},
         {4,8,12,0}
         };
 initImage();
}

我的顺序是第一列是1234 一列一列排序(尝试过,但黑马是一行一行)

13、设置胜利条件

首先设置一个正确的二位数组,与date打乱(移动)的数组进行比较,如果两个数组完全相同,即胜利

ini 复制代码
int[][] win = {
        {1,5,9,13},
        {2,6,10,14},
        {3,7,11,15},
        {4,8,12,0},
};

设置一个victory方法(比较两个二位数组,如果有一个不同就返回false,否则返回true)

ini 复制代码
public 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]){
       return false;
         }
       }
     }
      return true;
   }

然后再在initImage方法中添加如果胜利则加载胜利图片

scss 复制代码
if(victory()){
 JLabel winJLable = new JLabel(new ImageIcon("D:\Java\daima\pingtuyouxi\image\win.png"));
 winJLable.setBounds(203, 283, 197, 73);
 this.getContentPane().add(winJLable);
}

这样设置完还有一个弊端 就是胜利之后还能上下移动

要在按下不松键盘监听中设置如果胜利就结束方法

kotlin 复制代码
if(victory()){
 return;
}

14、设置计步

首先设置一个全局变量step记录步数,在initImage方法里设置一个JLable对象:步数+step

最后在上下移动中添加step++

效果展示

15、重新开始

(1)、给游戏重新绑定点击事件

kotlin 复制代码
//给条目绑定事件
replayItem.addActionListener(this);
reLoginItem.addActionListener(this);
closeItem.addActionListener(this);
accountItem.addActionListener(this);

实现ActionListener接口 重写方法

(2)、重新打乱二维数组中的数字

这时候出现一个问题,重新开始后加载图片后空白图片消失

(3)、加载图片

(4)、计步器清零 计步器清零一定要放在事件的最上面,要不然卡在加载图片这,步数不会清零

这是因为在原先的代码中

ini 复制代码
for (int i = 0; i < tempArr.length; i++) {
 if (tempArr[i] ==0){
  x = i / 4;
  y = i % 4;
 }
 else{
  data[i / 4][i % 4] = tempArr[i];
 }
}

如果出现空白图片0则会慧姐跳过,而不会赋值给二维数组

所以我们只要把else去掉即可

ini 复制代码
for (int i = 0; i < tempArr.length; i++) {
 if (tempArr[i] ==0){
  x = i / 4;
  y = i % 4;
 }
  data[i / 4][i % 4] = tempArr[i];
}

16、重新登录

less 复制代码
else if(obj == reLoginI
 System.out.println("重新
 //返回登录界面

 //关闭当前的游戏界面
 this.setVisible( false
 //打开登录界面
 new LoginFrame();

}

还没有设置登录的方法

17、关闭游戏

直接结束虚拟机

arduino 复制代码
System.exit(0);

18、设置公众号 新的对象:弹框对象 JDialog

java 复制代码
else if(obj == accountItem
System.out.println("公众号")

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

19、更新菜单,添加更换图片选项并完善

首先再根据JMenuBar------------Jmenu------------------JmenuItem添加更换图片窗口

再调换它们的顺序,更换图片在最上面(注意创建JLable对象时要创建在全局变量中,因为所有方法都可能用到)

添加完效果为

对美女,动物,运动添加点击事件

kotlin 复制代码
girl.addActionListener(this);
animal.addActionListener(this);
sport.addActionListener(this);

在actionPerformed中添加点击事件 首先生成对应图片数量的随机数 然后修改path变量 打乱数据 重新加载图片

注意:更换后计步器一定要清零

代码为:

scss 复制代码
else if(obj == girl){
 step = 0;
 Random random = new Random();
 int index = random.nextInt(13) + 1;
 path = "image\girl\girl"+index+"\";
 //再次打乱二维数组中的数据
 initDate();
 //重新加载图片
 initImage();
}else if (obj == animal){
 step = 0;
 Random random = new Random();
 int index = random.nextInt(8) + 1;
 path = "image\animal\animal"+index+"\";
 //再次打乱二维数组中的数据
 initDate();
 //重新加载图片
 initImage();
}else if(obj == sport){
 step = 0;
 Random random = new Random();
 int index = random.nextInt(10) + 1;
 path = "image\sport\sport"+index+"\";
 //再次打乱二维数组中的数据
 initDate();
 //重新加载图片
 initImage();
}

二、登录界面

这里复制黑马 第一排:用户名文字其实是一张图片,还是用JLabel去管理ImageIcon

​ 输入框:JTextField(明文显示的输入框)

第二排:密码文字其实是一张图片,还是用JLabel去管理ImageIcon

​ 输入框:JPasswordField(密文显示的输入框)

第三排:验证码文字其实是一张图片,还是用JLabel去管理ImageIcon

​ 输入框:JTextField(明文显示的输入框)

​ 验证码wyS7i:用JLabel去管理文字,需要自己写一个生成验证码的工具类。

第四排:两个都是按钮,绿色跟红色是按钮的背景图

​ 当点击按钮不松的时候,按钮变灰,其实就是换一个深色的背景图。

复制黑马的代码

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

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

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

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

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

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

    String codeStr = CodeUtil.getCode();
    JLabel rightCode = new JLabel();
    //设置内容
    rightCode.setText(codeStr);
    //位置和宽高
    rightCode.setBounds(300, 256, 50, 30);
    //添加到界面
    this.getContentPane().add(rightCode);

    //5.添加登录按钮
    JButton login = new JButton();
    login.setBounds(123, 310, 128, 47);
    login.setIcon(new ImageIcon("image\login\登录按钮.png"));
    //去除按钮的默认边框
    login.setBorderPainted(false);
    //去除按钮的默认背景
    login.setContentAreaFilled(false);
    this.getContentPane().add(login);

    //6.添加注册按钮
    JButton register = new JButton();
    register.setBounds(256, 310, 128, 47);
    register.setIcon(new ImageIcon("image\login\注册按钮.png"));
    //去除按钮的默认边框
    register.setBorderPainted(false);
    //去除按钮的默认背景
    register.setContentAreaFilled(false);
    this.getContentPane().add(register);

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

弹窗代码

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

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

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

弹窗是为了输入验证码,用户名,密码错误跳出提示用户重新输入

后面注册的代码根据黑马的业务分析写完

目前的效果为

先说明一下里面设置的验证码是一个工具类,需要自己写

ini 复制代码
public class CodeUtil {
    // 生成4位随机验证码(包含数字和大小写字母)
    public static String getCode() {
        String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuilder code = new StringBuilder();
        for (int i = 0; i < 4; i++) {
            int index = random.nextInt(chars.length());
            code.append(chars.charAt(index));
        }
        return code.toString();
    }
}

需完成:

给登录按钮绑定鼠标事件

当按下不松的时候:利用setIcon方法,修改登录按钮的背景色为蓝色(即放上image文件中的图片)

当松开的时候:利用setIcon方法,将按钮的背景色修改为红色

当点击的时候:校验用户输入的用户名和密码是否正确 (注意重写mouseClicked方法的校验时,要把initView()中的要用到的变量放在全局变量的位置)

  • trim() :清理字符串首尾的空白字符,避免因多余空格导致验证失败。
  • codeInput :在你的项目中是接收用户验证码的输入框,通过getText()获取输入内容。

验证码刷新功能:给验证码标签添加点击事件,允许用户刷新验证码

刷新界面

this.repaint();

登录代码:

scss 复制代码
@Override
public void mouseClicked(MouseEvent e) {
    Object obj = e.getSource();
    if (obj == login) {
        String user = username.getText().trim();
        String pwd = new String(password.getPassword()).trim();

        String inputCode = code.getText().trim();

        if (user.isEmpty() || pwd.isEmpty()) {
            showJDialog("用户名或密码不能为空!");
        } else if (!inputCode.equalsIgnoreCase(codeStr)) {
            showJDialog("验证码错误!");
        } else if ("admin".equals(user) && "123456".equals(pwd)) {
            showJDialog("登录成功!");
            this.dispose();
            new GameJframe();
        } else {
            showJDialog("用户名或密码错误!");
        }

重新生成验证码(先再次调用CodeUtil方法重新生成一个验证码,赋值给rigtCode,再刷新一下界面)

scss 复制代码
}else if(obj == rightCode){
    // 1. 重新生成验证码
    codeStr = CodeUtil.getCode();

    // 2. 更新标签文本(直接修改现有标签,不创建新对象)
    rightCode.setText(codeStr);

    // 3. 可选:刷新界面(确保UI更新)
    this.repaint();

}

(记得要把rightCode添加鼠标监听,要不然点不动)

优化代码: 为了更好的存储用户信息 于是创建了一个user对象

typescript 复制代码
public class User {

        private String username;
        private String password;


        public User() {
        }

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

        /**
         * 获取
         * @return username
         */
        public String getUsername() {
            return username;
        }

        /**
         * 设置
         * @param username
         */
        public void setUsername(String username) {
            this.username = username;
        }

        /**
         * 获取
         * @return password
         */
        public String getPassword() {
            return password;
        }

        /**
         * 设置
         * @param password
         */
        public void setPassword(String password) {
            this.password = password;
        }
    }

创建一个readUserInfo()用来读取文件中的用户名信息(用户名和密码)

保存格式为username=zhangsan&password=1234

用BufferedReader读取文件中的用户信息,然后用正则表达式把用户名和密码提取出来保存到集合中

scss 复制代码
public void readUserInfo() {
    // 1. 定义文件路径(项目根目录下的userinfo文件,无后缀)
    File file = new File("userinfo");

    // 2. 若文件不存在,创建空文件
    if (!file.exists()) {
        try {
            file.createNewFile(); // 直接创建userinfo文件
        } catch (IOException e) {
            showJDialog("用户信息文件创建失败!");
            e.printStackTrace();
            return;
        }
    }

    // 3. 读取文件内容并解析用户信息
    try (BufferedReader br = new BufferedReader(new FileReader(file))) {
        String line;
        while ((line = br.readLine()) != null) { // 按行读取
            // 解析格式:"username=xxx&password=xxx"
            String[] parts = line.split("&");
            if (parts.length != 2) continue; // 跳过格式错误的行

            String username = parts[0].split("=")[1]; // 提取用户名
            String password = parts[1].split("=")[1]; // 提取密码

            allUsers.add(new User(username, password)); // 添加到集合
        }
    } catch (IOException e) {
        showJDialog("读取用户信息失败!");
        e.printStackTrace();
    }
}

接着在鼠标点击事件中的登录按钮中把校验用户名密码加入进去(和集合中的数据一一比较)

scss 复制代码
if (obj == login) {
    String user = username.getText().trim();
    String pwd = new String(password.getPassword()).trim();

    String inputCode = code.getText().trim();

    if (user.isEmpty() || pwd.isEmpty()) {
        showJDialog("用户名或密码不能为空!");
    } else if (!inputCode.equalsIgnoreCase(codeStr)) {
        showJDialog("验证码错误!");
    }

    boolean loginSuccess = false;

    for (User user1 : allUsers) {
        if (user1.getUsername().equals(user) && user1.getPassword().equals(pwd)) {
            loginSuccess = true;
            break;
        }
    }

    // 4. 根据校验结果处理
    if (loginSuccess) {
        showJDialog("登录成功!");
        this.dispose();
        new GameJframe();
    } else {
        showJDialog("用户名或密码错误!");
    }
}

现在来设置三个窗口的出现顺序(跳转顺序)

在App类中只设置new LoginFrame()

然后用户点击登录后校验成功再跳转游戏界面

用户点击注册则跳转注册界面

三、注册界面

设置窗口大小,需要自调

代码放在下面

scss 复制代码
import javax.swing.*;
import java.awt.event.*;
import java.util.ArrayList;

public class RegisterFrame extends JFrame implements MouseListener {

    ArrayList<User> allUsers;
    //提升三个输入框的变量的作用范围,让这三个变量可以在本类中所有方法里面可以使用。
    JTextField username = new JTextField();
    JTextField password = new JTextField();
    JTextField rePassword = new JTextField();


    //只创建一个弹框对象
    JDialog jDialog = new JDialog();

    //提升两个按钮变量的作用范围,让这两个变量可以在本类中所有方法里面可以使用。
    JButton submit = new JButton();
    JButton reset = new JButton();


    public RegisterFrame(ArrayList<User> allUsers) {
        this.allUsers = allUsers;
        initFrame();
        initView();
        setVisible(true);
    }


    private void initView() {
        //添加注册用户名的文本
        JLabel usernameText = new JLabel(new ImageIcon("image\register\注册用户名.png"));
        usernameText.setBounds(85, 135, 80, 20);

        //添加注册用户名的输入框
        username.setBounds(195, 134, 200, 30);

        //添加注册密码的文本
        JLabel passwordText = new JLabel(new ImageIcon("image\register\注册密码.png"));
        passwordText.setBounds(97, 193, 70, 20);

        //添加密码输入框
        password.setBounds(195, 195, 200, 30);

        //添加再次输入密码的文本
        JLabel rePasswordText = new JLabel(new ImageIcon("image\register\再次输入密码.png"));
        rePasswordText.setBounds(64, 255, 95, 20);

        //添加再次输入密码的输入框
        rePassword.setBounds(195, 255, 200, 30);

        //注册的按钮
        submit.setIcon(new ImageIcon("image\register\注册按钮.png"));
        submit.setBounds(123, 310, 128, 47);
        submit.setBorderPainted(false);
        submit.setContentAreaFilled(false);
        submit.addMouseListener(this);

        //重置的按钮
        reset.setIcon(new ImageIcon("image\register\重置按钮.png"));
        reset.setBounds(256, 310, 128, 47);
        reset.setBorderPainted(false);
        reset.setContentAreaFilled(false);
        reset.addMouseListener(this);

        //背景图片
        JLabel background = new JLabel(new ImageIcon("image\register\background.png"));
        background.setBounds(0, 0, 470, 390);

        this.getContentPane().add(usernameText);
        this.getContentPane().add(passwordText);
        this.getContentPane().add(rePasswordText);
        this.getContentPane().add(username);
        this.getContentPane().add(password);
        this.getContentPane().add(rePassword);
        this.getContentPane().add(submit);
        this.getContentPane().add(reset);
        this.getContentPane().add(background);
    }

    private void initFrame() {
        //对自己的界面做一些设置。
        //设置宽高
        setSize(488, 430);
        //设置标题
        setTitle("拼图游戏 V1.0注册");
        //取消内部默认布局
        setLayout(null);
        //设置关闭模式
        setDefaultCloseOperation(3);
        //设置居中
        setLocationRelativeTo(null);
        //设置置顶
        setAlwaysOnTop(true);
    }



    //因为展示弹框的代码,会被运行多次
    //所以,我们把展示弹框的代码,抽取到一个方法中。以后用到的时候,就不需要写了
    //直接调用就可以了。
    public void showDialog(String content){
        if(!jDialog.isVisible()){
            //把弹框中原来的文字给清空掉。
            jDialog.getContentPane().removeAll();
            JLabel jLabel = new JLabel(content);
            jLabel.setBounds(0,0,200,150);
            jDialog.add(jLabel);
            //给弹框设置大小
            jDialog.setSize(200, 150);
            //要把弹框在设置为顶层 -- 置顶效果
            jDialog.setAlwaysOnTop(true);
            //要让jDialog居中
            jDialog.setLocationRelativeTo(null);
            //让弹框
            jDialog.setModal(true);
            //让jDialog显示出来
            jDialog.setVisible(true);
        }
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        

    }

    @Override
    public void mousePressed(MouseEvent e) {
        if (e.getSource() == submit) {
            submit.setIcon(new ImageIcon("image\register\注册按下.png"));
        } else if (e.getSource() == reset) {
            reset.setIcon(new ImageIcon("image\register\重置按下.png"));
        }
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        if (e.getSource() == submit) {
            submit.setIcon(new ImageIcon("image\register\注册按钮.png"));
        } else if (e.getSource() == reset) {
            reset.setIcon(new ImageIcon("image\register\重置按钮.png"));
        }
    }

    @Override
    public void mouseEntered(MouseEvent e) {

    }

    @Override
    public void mouseExited(MouseEvent e) {

    }
}

效果为

接下来设置鼠标点击事件

点击重置时 三个输入框全部清空

scss 复制代码
lse if(e.getSource() == reset){
    //点击了重置按钮
    //清空三个输入框
    username.setText("");
    password.setText("");
    rePassword.setText("");
}

点击注册时 需要完成7个步骤

首先用户名和密码不能为空

scss 复制代码
 //1、用户名,密码不能为空
if(username.getText().length() == 0 || password.getText().length() == 0 || rePassword.getText().length() == 0){
    showDialog("用户名或密码不能为空");
    return;
}

两次密码输入必须一致

scss 复制代码
 //2、判断两次密码输入是否一致
if(!password.getText().equals(rePassword.getText())){
    showDialog("两次密码输入不一致");
    return;
}

用户名和密码格式需正确

scss 复制代码
//3 、判断用户名和密码的格式是否正确
if(!username.getText().matches("[a-zA-Z0-9]{4,16}")){
    showDialog("用户名格式不正确,包含大小写字母,数字,长度为4-16位");
    return;
}
if(!password.getText().matches("\S*(?=\S{6,})(?=\S*\d)(?=\S*[a-z])\S*")){
    showDialog("密码格式不正确,至少包含1个小写字母,1个数字,长度至少6位");
    return;
}

用户名不能重复

scss 复制代码
//4、判断用户名是否已经重复
if(containsUsername(username.getText())){
    showDialog("用户名已存在,请重新输入");
    return;
}

添加信息到集合中

less 复制代码
 //5、添加用户
allUsers.add(new User(username.getText(),password.getText()));

把信息写入文件中 这里写一个方法专门用来保存信息到文件中

typescript 复制代码
private void saveUserToFile(String username, String password) {
    File file = new File("userinfo"); // 定位userinfo文件

    try (BufferedWriter writer = new BufferedWriter(new FileWriter(file, true))) {
        // 按格式写入新用户信息(追加模式,true表示不覆盖原有内容)
        writer.write("username=" + username + "&password=" + password);
        writer.newLine(); // 换行,确保每条记录占一行
    } catch (IOException e) {
        e.printStackTrace();
        showDialog("注册失败,文件写入错误!");
    }
}
scss 复制代码
//6、写入文件
saveUserToFile(username.getText(), password.getText());

弹框显示注册成功

kotlin 复制代码
//7、弹框提示注册成功
showDialog("注册成功!");
this.dispose(); // 关闭注册窗口

这里因为用户名密码格式不正确显示的文字太多了窗口显示不完全,所以更换弹窗

scss 复制代码
public void showDialog(String content){
        if (!jDialog.isVisible()) {
            // 清空原有内容
            jDialog.getContentPane().removeAll();

            // 1. 创建支持自动换行的文本区域
            JTextArea textArea = new JTextArea(content);
            textArea.setLineWrap(true); // 自动换行(关键)
            textArea.setWrapStyleWord(true); // 按单词边界换行(更美观)
            textArea.setEditable(false); // 禁止编辑
            textArea.setBorder(null); // 去除边框
            textArea.setBackground(jDialog.getBackground()); // 背景色与弹窗一致
            textArea.setAlignmentX(Component.CENTER_ALIGNMENT); // 水平居中

            // 2. 添加到弹窗(使用FlowLayout居中)
            jDialog.setLayout(new FlowLayout(FlowLayout.CENTER));
            jDialog.add(textArea);

            // 3. 设置弹窗大小(足够容纳换行内容)
            jDialog.setSize(200, 150);
            jDialog.setAlwaysOnTop(true);
            jDialog.setLocationRelativeTo(null);
            jDialog.setModal(true);

            // 4. 刷新布局
            jDialog.revalidate();
            jDialog.setVisible(true);
        }
    }

现在开始设置存档和读档 重新设置的窗体代码

ini 复制代码
JMenu saveJMenu = new JMenu("存档");
JMenu loadJMenu = new JMenu("读档");

JMenuItem saveItem0 = new JMenuItem("存档0(空)");
JMenuItem saveItem1 = new JMenuItem("存档1(空)");
JMenuItem saveItem2 = new JMenuItem("存档2(空)");
JMenuItem saveItem3 = new JMenuItem("存档3(空)");
JMenuItem saveItem4 = new JMenuItem("存档4(空)");

JMenuItem loadItem0 = new JMenuItem("读档0(空)");
JMenuItem loadItem1 = new JMenuItem("读档1(空)");
JMenuItem loadItem2 = new JMenuItem("读档2(空)");
JMenuItem loadItem3 = new JMenuItem("读档3(空)");
JMenuItem loadItem4 = new JMenuItem("读档4(空)");

存档时为了便捷的保存游戏数据 于是创建一个GameInfo对象

arduino 复制代码
public class GameInfo implements Serializable {

    private static final long serialVersionUID = 1763912527206709060L;
    private int[][] data;
    private int x = 0;
    private int y = 0;
    private String path;
    private int step;


    public GameInfo() {
    }

    public GameInfo(int[][] data, int x, int y, String path, int step) {
        this.data = data;
        this.x = x;
        this.y = y;
        this.path = path;
        this.step = step;
    }

    /**
     * 获取
     * @return data
     */
    public int[][] getData() {
        return data;
    }

    /**
     * 设置
     * @param data
     */
    public void setData(int[][] data) {
        this.data = data;
    }

    /**
     * 获取
     * @return x
     */
    public int getX() {
        return x;
    }

    /**
     * 设置
     * @param x
     */
    public void setX(int x) {
        this.x = x;
    }

    /**
     * 获取
     * @return y
     */
    public int getY() {
        return y;
    }

    /**
     * 设置
     * @param y
     */
    public void setY(int y) {
        this.y = y;
    }

    /**
     * 获取
     * @return path
     */
    public String getPath() {
        return path;
    }

    /**
     * 设置
     * @param path
     */
    public void setPath(String path) {
        this.path = path;
    }

    /**
     * 获取
     * @return step
     */
    public int getStep() {
        return step;
    }

    /**
     * 设置
     * @param step
     */
    public void setStep(int step) {
        this.step = step;
    }

    public String toString() {
        return "GameInfo{data = " + data + ", x = " + x + ", y = " + y + ", path = " + path + ", step = " + step + "}";
    }
}

存档到哪个文件夹中首先我们要获取用户点击了哪个存档

我们要获取存档1234...中的数字

所以用以下方法

ini 复制代码
//获取当前是哪个存档被点击了,获取期中的序号
JMenuItem item = (JMenuItem) obj;
String str = item.getText();
int index = str.charAt(2) - '0';

然后保存到本地文件中

java 复制代码
//直接把游戏的数据写到本地文件中
   try {
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("save//save"+ index +".data"));
    GameInfo gi = new GameInfo(data,x,y,path,step);
    oos.writeObject(gi);
    oos.close();
   } catch (IOException ex) {
    throw new RuntimeException(ex);
   }

然后要修改一下菜单中的展示信息

arduino 复制代码
//修改一下存档item上的展示信息
//存档0(xx步)
item.setText("存档"+index+"("+step+"步)");
//修改一下读档item上的展示信息
loadJMenu.getItem(index).setText("读档0("+step+"步)");

读档也是类似的

ini 复制代码
GameInfo gi = null;
 try {
  ObjectInputStream ois = new ObjectInputStream(new FileInputStream("save\save"+ index +".data"));
  gi = (GameInfo) ois.readObject();
  ois.close();
 } catch (IOException ex) {
  ex.printStackTrace();
 } catch (ClassNotFoundException ex) {
  ex.printStackTrace();
 }

 data = gi.getData();
 path = gi.getPath();
 step = gi.getStep();
 x = gi.getX();
 y = gi.getY();

 //刷新重新加载图片
 initImage();
}

这时候有个bug,当存档完再次打开游戏时,存档和读档都为0,这是因为游戏一开始没有读取文件信息

所以创建一个方法读取文件中的信息

arduino 复制代码
public void getGameInfo(){
  //1、创建File对象表示所有存档所在的文件夹
  File file = new File("save");
 //2、进入文件夹获取到里面所有的存档文件
 File[] files = file.listFiles();
 //遍历数组,得到每一个存档
 for (File f : files) {
  GameInfo gi = null;
  try {
   ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));
   gi = (GameInfo) ois.readObject();
   ois.close();
  } catch (IOException e) {
   throw new RuntimeException(e);
  } catch (ClassNotFoundException e) {
   throw new RuntimeException(e);
  }
  //获取步数
  int step = gi.getStep();

  //获取存档的文件名中的数字
  String name = f.getName();
  //获取存档的序号(索引)
  int index = name.charAt(4) - '0';
  //修改菜单上所表示的文字信息
  saveJMenu.getItem(index).setText("存档"+index+"("+step+")");
  loadJMenu.getItem(index).setText("存档"+index+"("+step+")");
 }
 
}

调用这个方法即可

最后打包成exe文件

打包过程中遇到一个问题登录成功后无法跳转游戏界面 这是因为没有创建save文件夹所以报错空指针异常

所以修改方法

scss 复制代码
public void readUserInfo() {
    // 1. 定义文件路径(项目根目录下的userinfo文件,无后缀)
    File file = new File("userinfo");

    // 2. 若文件不存在,创建空文件
    if (!file.exists()) {
        try {
            file.createNewFile(); // 直接创建userinfo文件
        } catch (IOException e) {
            showJDialog("用户信息文件创建失败!");
            e.printStackTrace();
            return;
        }
    }

    // 3. 读取文件内容并解析用户信息
    try (BufferedReader br = new BufferedReader(new FileReader(file))) {
        String line;
        while ((line = br.readLine()) != null) { // 按行读取
            // 解析格式:"username=xxx&password=xxx"
            String[] parts = line.split("&");
            if (parts.length != 2) continue; // 跳过格式错误的行

            String username = parts[0].split("=")[1]; // 提取用户名
            String password = parts[1].split("=")[1]; // 提取密码

            allUsers.add(new User(username, password)); // 添加到集合
        }
    } catch (IOException e) {
        showJDialog("读取用户信息失败!");
        e.printStackTrace();
    }
}

做完之后就可以发给朋友玩了,自己也可以下载。

相关推荐
小屁孩大帅-杨一凡1 小时前
如何解决ThreadLocal内存泄漏问题?
java·开发语言·jvm·算法
学习3人组1 小时前
在 IntelliJ IDEA 系列中phpstorm2025设置中文界面
java·ide·intellij-idea
cainiao0806053 小时前
Java 大视界:基于 Java 的大数据可视化在智慧城市能源消耗动态监测与优化决策中的应用(2025 实战全景)
java
长风破浪会有时呀3 小时前
记一次接口优化历程 CountDownLatch
java
云朵大王4 小时前
SQL 视图与事务知识点详解及练习题
java·大数据·数据库
我爱Jack4 小时前
深入解析 LinkedList
java·开发语言
27669582925 小时前
tiktok 弹幕 逆向分析
java·python·tiktok·tiktok弹幕·tiktok弹幕逆向分析·a-bogus·x-gnarly
用户40315986396636 小时前
多窗口事件分发系统
java·算法
用户40315986396636 小时前
ARP 缓存与报文转发模拟
java·算法