本项目使用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();
}
}
做完之后就可以发给朋友玩了,自己也可以下载。