需求文档
项目名称:拼图游戏
项目概述: 拼图游戏是一款基于Java基础语法开发的桌面游戏,通过拖动图片块来完成图片的拼图,游戏分为重新开始、重新登录、关闭游戏、更换图片等功能。
功能需求:
2.1 游戏界面:
- 游戏界面大小为603x680像素
- 界面标题为“拼图游戏单机版 V1.0
- 界面居中显示 - 界面关闭时退出程序
2.2 菜单功能:
- 功能菜单包括重新开始、重新登录、关闭游戏、更换图片功能 -
- 关于我们菜单包括赞助和版本号功能
2.3 图片功能:
- 可以更换不同类别的图片,包括美女、动物、运动等 - 每类图片包含多张图片可供选择
2.4 游戏操作:
- 键盘移动空白块完成拼图 - 计步器统计步数 - 提供作弊码快速通关
用户界面:
游戏界面包含图片拼图区域、计步器、胜利图片显示区域
数据需求:
二维数组存储图片数据
计数器记录步数
二维数据判断游戏胜利状态
非功能需求:
游戏界面美观、易操作
图片加载速度快
界面响应快速
其他需求:
提供作弊码快速通关
提供赞助和版本号信息
提供重新开始、重新登录、关闭游戏等功能
附加需求:
可以根据用户需求定制更多图片类别和图片数量
部分代码展示(提供详细解释)(完整代码在最下面)
创建初始窗口代码
//创建界面,初始化界面(单独一个方法)
public void init(){
//创建初始窗口
//Jframe类,界面,窗体(class类继承了JFrame父类)
//1.设置界面的宽和高
this.setSize(603,680);
//2.设置界面标题为“拼图游戏单机版 V1.0”
this.setTitle("拼图游戏单机版 V1.0");
//3.设置界面一直处于置顶状态
this.setAlwaysOnTop(true);//ps:true代表是一直置顶,如果是false就不是一直处于置顶状态
//4.界面居中显示
this.setLocationRelativeTo(null);
//5.设置关闭模式,只要关闭游戏窗口就直接关闭虚拟机,防止关闭游戏后虚拟机持续运行
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
/*ps:还需要添加一个功能,就是取消添加图片是居中放置,只有取消了以后添加图片才是可以自己设置XY值来添加组件,不然就一直是居中放置*/
this.setLayout(null);
//为了游戏的操作,我们还需要设置键盘监听
this.addKeyListener(this);
}
初始菜单(包括重新开始、重新登录、关闭游戏、更换图片功能 ,赞助和版本号功能)
public void initJMenuBar(){
//JMenuBar就是标题下方的一个长条方框,方框里面的就是对象,对象里面的东西叫做条目
//先创建一个大的JMenuBar对象
JMenuBar jMenuBar = new JMenuBar();
//再创建功能选项
JMenu functionJMenu = new JMenu("功能");
JMenu aboutJMenu = new JMenu("关于我们");
JMenu changeImage = new JMenu("更换图片");//因为更换图片是再功能选项里面,所以我们需要将这个添加到功能对象中
//创建JMenuItem对象
//创建选项下面的条目对象,包含《功能》和《关于我们》还有《更换图片》
//功能
JMenuItem regainGameItem = new JMenuItem("重新开始");
JMenuItem loginGameItem = new JMenuItem("重新登录");
JMenuItem closeGameItem = new JMenuItem("关闭游戏");
//关于我们
JMenuItem accountItem = new JMenuItem("赞助");
JMenuItem aboutGame = new JMenuItem("版本号");
//更换图片
JMenuItem girlP = new JMenuItem("换美女");
JMenuItem animalP = new JMenuItem("换动物");
JMenuItem sportP = new JMenuItem("换运动");
//需要将创建的JMenuItem对象添加到JMenu对象中,这样可视化界面才会显现出这些条目
//将JMenuItem条目归类到JMenu对象中
//ps:将条目归类到对象中,将对象归类到菜单中,将菜单归类到JMenuBar中
functionJMenu.add(changeImage);//更换图片
functionJMenu.add(regainGameItem);//重新开始
functionJMenu.add(loginGameItem);//重新登录
functionJMenu.add(closeGameItem);//关闭游戏
aboutJMenu.add(accountItem);//赞助
aboutJMenu.add(aboutGame);//版本号
changeImage.add(girlP);//换美女
changeImage.add(animalP);//换动物
changeImage.add(sportP);//换运动
//将JMenu对象归类到Bar类
jMenuBar.add(functionJMenu);
jMenuBar.add(aboutJMenu);
//给条目绑定动作监听(鼠标点击)
regainGameItem.addActionListener(this);//重新开始
loginGameItem.addActionListener(this);//重新登录
closeGameItem.addActionListener(this);//关闭游戏
accountItem.addActionListener(this);//关于我们
girlP.addActionListener(this);
animalP.addActionListener(this);
sportP.addActionListener(this);
//ps:现在已经将所有东西都归类到他的上一级,最后一步就是将可视化界面设置一下菜单
this.setJMenuBar(jMenuBar);
}
打乱二维数组,将照片碎片按照打乱后的二维数组进行输出
public void swap(){
Random rd = new Random();
//图片碎片编号一共是1~15,0代表可移动空白块
int[] arr = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
for(int i =0;i<arr.length;i++){
//创建一个随机数
int number = rd.nextInt(arr.length);
int temp = arr[i];
arr[i] = arr[number];
arr[number] = temp;
}
//给二维数组添加数据
for(int i =0;i<arr.length;i++){
data[i/4][i%4] = arr[i];
if(arr[i] == 0){
x = i/4;
y = i%4;
//ps:一个小方法,可以将一维数组里的数据存储到二维数组中,只用一层for循环
}
}
}
初始化图片(添加图片)
添加图片需要注意的小知识:先添加的图片显示在上层,后添加的图片显示在下层
private void initImage(){
//页面刷新
this.getContentPane().removeAll();//ps:每次空白图片的更换都需要进行刷新界面的操作,不然界面无法更新
//如果胜利则输出胜利的图标
if(victory()){
//创建一个对象胜利图片
JLabel win = new JLabel(new ImageIcon("C:\\Users\\31412\\IdeaProjects\\mystatic\\src\\puzzleGame\\image\\win.png"));
//设置图片的大小和位置
win.setBounds(203,283,193,73);
//将图片添加到界面中
this.getContentPane().add(win);
}
//计数器
JLabel stepCount = new JLabel("步数:" + count);//count已经创建好了但是不在这个方法中
stepCount.setBounds(50,30,100,20);//设置计步器大小和位置
this.getContentPane().add(stepCount);//将计步器添加到界面中
//添加图片到界面上
for (int i = 0; i < 4;i++) {
for (int i1 = 0; i1 < 4;) {
//创建一个图片类,将这个图片类添加到管理容器中
JLabel jlabel = new JLabel(new ImageIcon(path + photoClass + "\\" + photoNumber +"\\"+ data[i][i1] +".jpg"));//ps:其中path,photoclass等都已经创建好了
//添加边框
jlabel.setBorder(new BevelBorder(BevelBorder.LOWERED));//1
//指定图片位置
jlabel.setBounds(105 * i1 + 83,105 * i + 134,105,105);//按照打乱好的二维数组进行输出图片
i1++;
//将这个容器添加到界面中
this.getContentPane().add(jlabel);
//如果图片编号为0,那么这个图片就是空白图片
}
}
//添加背景图片(后添加,显示在下层)
JLabel background = new JLabel(new ImageIcon("C:\\Users\\31412\\IdeaProjects\\mystatic\\src\\puzzleGame\\image\\background.png"));
background.setBounds(40,40,508,560);
//把背景图片添加到界面中
this.getContentPane().add(background);
//刷新界面强制界面重现绘制
this.getContentPane().repaint();
}
完成代码展示
import javax.swing.*;
import javax.swing.border.BevelBorder;
import java.awt.event.*;
import java.util.Random;
public class GameJframe extends JFrame implements KeyListener, ActionListener {
//JFrame 界面,窗体
//子类继承父类,也表示窗体
//规定:GameJFrame也表示游戏的界面,以后所有的界面代码逻辑都写在这里
Random rd = new Random();
//创建选项下面的条目对象,包含《功能》和《关于我们》还有《更换图片》
JMenuItem regainGameItem = new JMenuItem("重新开始");
JMenuItem loginGameItem = new JMenuItem("重新登录");
JMenuItem closeGameItem = new JMenuItem("关闭游戏");
//关于我们
JMenuItem accountItem = new JMenuItem("赞助");
JMenuItem aboutGame = new JMenuItem("版本号");
//更换图片
JMenuItem girlP = new JMenuItem("换美女");
JMenuItem animalP = new JMenuItem("换动物");
JMenuItem sportP = new JMenuItem("换运动");
//创建二维数组来存储图片
int[][] data = new int[4][4];
//创建XY来确定空白图片的位置
int x,y;
//创建计数器,统计步数
int count = 0;
//创建一个二维数据存储数据来判断游戏是否结束
int[][] win = {
{1,2,3,4},
{5,6,7,8,},
{9,10,11,12},
{13,14,15,0},
};
String photoNumber = "";
String photoClass = changeClass();
String path = "C:\\Users\\31412\\IdeaProjects\\mystatic\\src\\puzzleGame\\image\\";
//界面主体
public GameJframe(){
//初始界面
init();
//初始菜单
initJMenuBar();
//打乱数组
swap();
//添加图片(初始化图片)
changePicture();
initImage();
//显示界面
this.setVisible(true);
}
//初始界面
private void init() {
//设置界面的宽高
this.setSize(603,680);//像素
//设置界面的标题
this.setTitle("拼图游戏单机版 VIP1.0");
//设置一直处于置顶状态
this.setAlwaysOnTop(true);
//设置界面居中
this.setLocationRelativeTo(null);
//取消添加图片是居中放置,只有取下了才会按照XY轴的形式添加组件
this.setLayout(null);
//设置关闭模式,关闭程序时,虚拟机自动停止
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
//添加键盘监听
this.addKeyListener(this);
}
//初始菜单
private void initJMenuBar(){
//创建整个菜单对象
JMenuBar jMenuBar = new JMenuBar();
//创建菜单上面的两个选项的对象(功能 关于我们)
JMenu functionJMenu = new JMenu("功能");
JMenu aboutJMenu = new JMenu("关于我们");
JMenu changeImage = new JMenu("更换图片");
//将JMenuItem对象归类到JMenu中
functionJMenu.add(changeImage);//更换图片
functionJMenu.add(regainGameItem);//重新开始
functionJMenu.add(loginGameItem);//重新登录
functionJMenu.add(closeGameItem);//关闭游戏
aboutJMenu.add(accountItem);//赞助
aboutJMenu.add(aboutGame);//版本号
changeImage.add(girlP);
changeImage.add(animalP);
changeImage.add(sportP);
//给条目绑定动作监听(鼠标点击)
regainGameItem.addActionListener(this);//重新开始
loginGameItem.addActionListener(this);//重新登录
closeGameItem.addActionListener(this);//关闭游戏
accountItem.addActionListener(this);//关于我们
girlP.addActionListener(this);
animalP.addActionListener(this);
sportP.addActionListener(this);
//将JMenu对象归类到Bar类
jMenuBar.add(functionJMenu);
jMenuBar.add(aboutJMenu);
//最后一步,给整个界面设置菜单也最容易漏掉的一步
this.setJMenuBar(jMenuBar);
}
//更换图片类别
public String changeClass(){
String[] arr = {"girl","animal","sport"};
int n = rd.nextInt(3);
return arr[n];
}
//选择美女图片编号
public String girlPicture(){
int n = rd.nextInt(13) + 1;
return "girl"+n;
}
//选择动物图片编号
public String animalPicture(){
int n = rd.nextInt(8) + 1;
return "animal"+n;
}
//选择运动图片编号
public String sportPicture(){
int n = rd.nextInt(10) + 1;
return "sport"+n;
}
public void changePicture(){
//显示图片类别和图片编号
switch (photoClass) {
case "girl":
photoNumber = girlPicture();
break;
case "animal":
photoNumber = animalPicture();
break;
case "sport":
photoNumber = sportPicture();
break;
}
}
//初始化图片(添加图片)
//细节:先添加的图片是在上层的,后添加的图片是在下层的
private void initImage(){
//页面刷新
this.getContentPane().removeAll();
//胜利则输出图标
if(victory()){
//输出胜利图片
JLabel win = new JLabel(new ImageIcon("C:\\Users\\31412\\IdeaProjects\\mystatic\\src\\puzzleGame\\image\\win.png"));
win.setBounds(203,283,193,73);
this.getContentPane().add(win);
}
//计数器来表示有多少步
JLabel stepCount = new JLabel("步数:" + count);
stepCount.setBounds(50,30,100,20);
this.getContentPane().add(stepCount);
//添加图片到界面上
for (int i = 0; i < 4;i++) {
for (int i1 = 0; i1 < 4;) {
//创建一个图片类,将这个图片类添加到管理容器中
JLabel jlabel = new JLabel(new ImageIcon(path + photoClass + "\\" + photoNumber +"\\"+ data[i][i1] +".jpg"));
//添加边框
jlabel.setBorder(new BevelBorder(BevelBorder.LOWERED));//1
//指定图片位置
jlabel.setBounds(105 * i1 + 83,105 * i + 134,105,105);
i1++;
//将这个容器添加到界面中
this.getContentPane().add(jlabel);
//如果图片编号为0,那么这个图片就是空白图片
}
}
//添加背景图片(后添加,显示在下层)
JLabel background = new JLabel(new ImageIcon("C:\\Users\\31412\\IdeaProjects\\mystatic\\src\\puzzleGame\\image\\background.png"));
background.setBounds(40,40,508,560);
//把背景图片添加到界面中
this.getContentPane().add(background);
//刷新界面强制界面重现绘制
this.getContentPane().repaint();
}
//打乱图片顺序
public void swap(){
int[] arr = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
for(int i =0;i<arr.length;i++){
int number = rd.nextInt(arr.length);
int temp = arr[i];
arr[i] = arr[number];
arr[number] = temp;
}
//给二维数组添加数据
for(int i =0;i<arr.length;i++){
data[i/4][i%4] = arr[i];
if(arr[i] == 0){
x = i/4;
y = i%4;
}
}
}
//判断游戏是否胜利
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;
}
//键盘监听(没用上)
@Override
public void keyTyped(KeyEvent e) {
//如果胜利就直接结束
if(victory()){
return;
}
}
//键盘监听, 显示全部图片
@Override
public void keyPressed(KeyEvent e) {
//如果胜利就直接结束
if(victory()){
return;
}
int code = e.getKeyCode();
if(code == 65){
//删除全部图片
this.getContentPane().removeAll();
//添加全部图片
String allPhoto = "C:\\Users\\31412\\IdeaProjects\\mystatic\\src\\puzzleGame/image/";
JLabel all = new JLabel(new ImageIcon(allPhoto + photoClass+"/"+photoNumber+"/all.jpg"));
all.setBounds(83,134,420,420);
this.getContentPane().add(all);
//添加背景图
JLabel background = new JLabel(new ImageIcon("C:\\Users\\31412\\IdeaProjects\\mystatic\\src\\puzzleGame\\image\\background.png"));
background.setBounds(40,40,508,560);
//把背景图片添加到界面中
this.getContentPane().add(background);
//刷新
this.repaint();
}
}
//键盘监听,操作白块
@Override
public void keyReleased(KeyEvent e) {
//如果胜利就直接结束
if(victory()){
return;
}
int code = e.getKeyCode();
//左方向键,空白方块向左移动
if(code == 37){
System.out.println("向左移动");
data[x][y] = data[x][y-1];
data[x][y-1] = 0;
y--;
count++;
initImage();
}
//上方向键,空白方块向上移动
else if(code == 38){
System.out.println("向上移动");
//逻辑:
//空白方块向上移动
//XY表示空白方块
//x-1,y表示空白方块上方的数字
data[x][y] = data[x-1][y];
data[x-1][y] = 0;
x--;
count++;
initImage();
}
//右方向键,空白方块向右移动
else if(code == 39){
System.out.println("向右移动");
data[x][y] = data[x][y+1];
data[x][y+1] = 0;
y++;
count++;
initImage();
}
//下方向键,空白方块向下移动
else if(code == 40){
System.out.println("向下移动");
data[x][y] = data[x+1][y];
data[x+1][y] = 0;
x++;
count++;
initImage();
}
else if(code == 65){
initImage();
}
//作弊码,按一下可以通关
if(code == 87){
data = new int[][] {
{1,2,3,4},
{5,6,7,8,},
{9,10,11,12},
{13,14,15,0},
};
initImage();
}
}
//动作监听,选择功能
@Override
public void actionPerformed(ActionEvent e) {
//获取当前被点击的条目对象
Object obj = e.getSource();
//判断
if(obj == regainGameItem){
System.out.println("重新游戏");
//再次打乱二维数组
swap();
//计步器清零
count = 0;
//加载图片
initImage();
}else if(obj == loginGameItem){
System.out.println("重新登录");
//关闭界面
this.setVisible(false);
//打开登录界面
new LoginJframe();
}else if(obj == closeGameItem){
System.out.println("关闭游戏");
//关闭虚拟机
System.exit(0);
}else if(obj == accountItem){
System.out.println("赞助");
//创建收款码对象
JLabel money = new JLabel(new ImageIcon("C:\\Users\\31412\\IdeaProjects\\mystatic\\src\\puzzleGame\\image\\微信图片_20240202174310.jpg"));
//设置位置和宽高
money.setBounds(0,0,423,389);
//创建弹窗对象
JDialog m = new JDialog();
//将对象添加到弹框中
m.add(money);
//设置弹框大小
m.setSize(500,450);
//设置弹框置顶
m.setAlwaysOnTop(true);
//设置弹框居中
m.setLocationRelativeTo(null);
//设置弹框不关闭就不能进行下一步操作
m.setModal(true);
//显示弹框
m.setVisible(true);
}
if(obj == girlP){
System.out.println("换美女");
//计步器清零
count = 0;
//打乱新的图片
swap();
//从13张女生图片选一张
photoClass = "girl";
photoNumber = girlPicture();
//添加新的图片
initImage();
}else if(obj == animalP){
System.out.println("换动物");
//计步器清零
count = 0;
//打乱新的图片
swap();
//从8张动物的照片换一张
photoClass = "animal";
photoNumber = animalPicture();
initImage();
}else if(obj == sportP){
System.out.println("换运动");
//计步器清零
count = 0;
//打乱新的图片
swap();
//从10张运动的照片换一张
photoClass = "sport";
photoNumber = sportPicture();
initImage();
}
}
}