拼圖小遊戲
采用主技術
GUI Java多線程 IO流
效果展示:
啟動界面:

主界面:(遊戲界面内容功能見名知意)
閱前須知
源碼分為兩部分
1、圖檔image
2、代碼實作
在導入之前需要修改IO檔案路徑
如需修改拼圖照片内容,需要将主圖像素大小調至450 x 600
然後拆分為6行5列作為拼圖區
注意圖檔命名格式一定要一緻
附上一個拆分圖檔網站:https://www.qtool.net/piccutting
創作不易,如果能幫到您,請點贊支援,感謝!
源碼部分
ResourceUtil
package jinhuan.pink;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class ResourceUtil {
public static BufferedImage getImage(String imageName){
BufferedImage bufferedImage = null;
try {
// 這裡的路徑需要修改為自己的檔案路徑
bufferedImage = ImageIO.read(new File("Xingle\\src\\image\\"+imageName));
} catch (IOException e) {
e.printStackTrace();
}
return bufferedImage;
}
}
MyTime
package jinhuan.pink;
/**
* 自定義計時器
* 實作時間的遞增
* */
public class MyTime implements Runnable {
static String myTime;
static int myMinute = 0;
static int mySecond = 0;
static int myHour = 0;
@Override
public void run() {
while (true){
try {
Thread.currentThread().sleep(1000);
mySecond++;
if(mySecond >= 60){
mySecond = 0;
myMinute++;
}
if(myMinute >= 60){
myMinute++;
}
if(myHour >= 24){
myMinute++;
}
myTime = myHour+":"+myMinute+":"+mySecond;
MainJFrame.buttonAreaJPanel.yourtime.setText("時間:"+myTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
GameStart
package jinhuan.pink;
//主啟動類
public class GameStart {
public static void main(String[] args) {
new BeginJFrame();
}
}
BeginJFrame
package jinhuan.pink;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
/**
* 遊戲的開始界面
* */
public class BeginJFrame extends JFrame {
private BeginJFrame beginJFrame = this;
private BufferedImage titleIcon = ResourceUtil.getImage("icon.png");
private BufferedImage startBu = ResourceUtil.getImage("startBu.png");
private JButton start = new JButton(new ImageIcon(startBu));
private Container contentPane = getContentPane();
/**
* 定義主視窗的元件
*/
private JLabel title = new JLabel("拼圖小遊戲",JLabel.CENTER );
private JLabel hhh = new JLabel("開始遊戲",JLabel.CENTER );
/**
* 定義主窗體的子面闆
* */
private JPanel mainJpanel = new JPanel(null);
// 擷取本窗體的自帶面闆并将其屬化
public BeginJFrame(){
addComponent();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);
setTitle("拼圖小遊戲");
setSize(600,300);
setIconImage(titleIcon);
setLocationRelativeTo(null);
setVisible(true);
}
public void addComponent(){
contentPane.setBackground(Color.getHSBColor(12,12,12));
title.setFont(new Font("宋體",Font.BOLD,60));
contentPane.add(title,BorderLayout.NORTH);
/**
* 給兩個按鈕設定屬性并添加監聽事件
* */
start.setBounds(200,100,200,76);
hhh.setFont(new Font("幼圓",Font.BOLD,20));
start.setFocusPainted(false);
start.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new MainJFrame();
beginJFrame.dispose();
}
});
hhh.setBounds(200,50,200,76);
mainJpanel.add(hhh);
mainJpanel.add(start);
mainJpanel.setBackground(Color.WHITE);
contentPane.add(mainJpanel,BorderLayout.CENTER);
}
}
MainJFrame
package jinhuan.pink;
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
public class MainJFrame extends JFrame {
/**
* 建立整個面闆的屬性變量
* 窗體的圖示:
* iconImage-->建立工具類,利用IO進行批量的讀入
* 兩個子面闆:
* buttonAreaJPanel-->代表按鈕面闆
* imageAreaJPanel-->代表下部圖檔區域
*
* */
BufferedImage iconImage = ResourceUtil.getImage("icon.png");// 代表整個遊戲的圖示
static ButtonAreaJPanel buttonAreaJPanel = new ButtonAreaJPanel();//按鈕區域對象
static ImageAreaJPanel imageAreaJPanel = new ImageAreaJPanel();// 建立圖檔區域對象
/**
* 提供無參的構造方法:
* 添加事件監聽
* 設定視窗屬性(可見設定在最後)
* */
public MainJFrame(){
//調用監聽的方法
this.setIconImage(iconImage);
this.setTitle("拼圖小遊戲");
this.setSize(1200, 720);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);// 設定窗體居中
this.setResizable(false);// 設定窗體的大小不可以改變
this.add(buttonAreaJPanel, BorderLayout.NORTH);
this.add(imageAreaJPanel, BorderLayout.CENTER);
this.setVisible(true);
}
}
ButtonAreaJPanel
package jinhuan.pink;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.TitledBorder;
public class ButtonAreaJPanel extends JPanel{
/**
* 為目前面闆添加兩個子面闆:
* 1、左面闆--leftJPanel
* 2、右面闆--rightJPanel
* */
JPanel leftJPanel = new JPanel();
JPanel rightJPanel = new JPanel();
/**
* 構造方法來初始化面闆
* 1、設定此面闆的屬性
* 2、将左邊的面闆和右邊的面闆添加到按鈕區域面闆中
* */
public ButtonAreaJPanel() {
this.setLayout(new GridLayout(1, 2));
addLeftJPanel();
addRightJPanel();
}
/**
* 1、設定左邊面闆的屬性
* 2、定義左邊面闆的元件:
* a、imageName代表左邊的圖檔名稱
* b、step代表計步區域
* */
static JTextField imageName;
static JTextField step;
static JTextField yourtime;
public void addLeftJPanel() {
leftJPanel.setBorder(new TitledBorder("遊戲資料區"));
leftJPanel.setLayout(new GridLayout(1,2));
/**
* 圖檔顯示的文本框
* */
imageName = new JTextField("圖檔名稱:"+pictures[0]);
imageName.setBackground(Color.CYAN);
imageName.setEditable(false);
/**
* 步數的文本顯示框
* */
step = new JTextField("步數:"+0);//
step.setBackground(Color.magenta);
step.setEditable(false);
/**
* yourtime
* */
yourtime = new JTextField("花費時間:"+0);//
yourtime.setBackground(Color.PINK);
yourtime.setEditable(false);
// 添加至左面闆
leftJPanel.add(imageName,BorderLayout.WEST);
leftJPanel.add(yourtime,BorderLayout.CENTER);
leftJPanel.add(step, BorderLayout.EAST);
// 将leftJPanel添加到ButtonAreaJPanel
this.add(leftJPanel, BorderLayout.WEST);
}
/**
* 定義右邊面闆的元件:
* 1、單選按鈕:——>分組
* a、showNumber代表顯示序号
* b、hideNumber代表隐藏序号-->預設被選中
* 2、下拉清單——>來選擇圖檔
* 3、開始按鈕
* */
static String[] pictures = {"波妞和宗介_1","波妞和宗介_2","宗介"};//作為下拉清單的選項
static String[] models = {"簡單","中等","困難"};
static JRadioButton showNum;
static JRadioButton hideNum;
static JComboBox<String> changePic;
static JComboBox<String> changeMod;
static JButton start;
static int modIndex;
static int indexBegin;
static long beginTime;
MyTime myTime = new MyTime();
Thread thread = new Thread(myTime);
public void addRightJPanel() {
rightJPanel.setBorder(new TitledBorder("功能區"));//設定 面闆的邊框的标題
/**
* 完成 兩個單選框的初始化
* */
showNum = new JRadioButton("顯示序号",false);
showNum.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
ImageAreaJPanel.myPuzzle.showNum();
}
});
hideNum = new JRadioButton("隐藏序号",true);
hideNum.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
ImageAreaJPanel.myPuzzle.hideNum();
}
});
// 将兩個按鈕綁定為一組
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(showNum);
buttonGroup.add(hideNum);
// 添加到右邊的面闆上
rightJPanel.add(showNum);
rightJPanel.add(hideNum);
// 對下拉清單進行設定
changePic = new JComboBox<String>(pictures);//将圖檔名稱的數組進行傳入
changeMod = new JComboBox<String>(models);//設定難度
changePic.addItemListener(new ItemListener(){
@Override
public void itemStateChanged(ItemEvent e) {
if(e.getStateChange() == ItemEvent.SELECTED) {
hideNum.setSelected(false);// 切換圖檔預設不顯示序号
int index = changePic.getSelectedIndex();//擷取選中的圖檔的索引 index 第一個選項 0
PreviewAreaJPanel.picId = index + 1;// 通過擷取的索引 确定圖檔的id ----> 修改圖檔的id
ImageAreaJPanel.myPreview.repaint(); //重新繪制
imageName.setText("圖檔名稱:"+changePic.getSelectedItem());//設定功能區顯示的圖檔名稱
PreviewAreaJPanel.stepCount = 0;//當遊戲切換圖檔之後,步數清空,重新指派為0
//将總共用了多少步數 指派給文本框
step.setText("目前步數:"+PreviewAreaJPanel.stepCount);
MainJFrame.buttonAreaJPanel.yourtime.setText("時間:"+MyTime.myTime);
// thread.interrupt();
ImageAreaJPanel.myPuzzle.reload();
}
}
});
changeMod.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
modIndex = changeMod.getSelectedIndex();
}
});
changePic.setBackground(Color.WHITE);
changeMod.setBackground(Color.WHITE);
//将下拉清單添加到右邊的面闆上
rightJPanel.add(new JLabel("選擇圖檔:"));
rightJPanel.add(changePic);
rightJPanel.add(new JLabel("選擇難度:"));
rightJPanel.add(changeMod);
/**
* 完成開始按鈕的初始化
* 并給開始按鈕添加監聽事件
* */
start = new JButton("開始遊戲");
start.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
beginTime = System.currentTimeMillis();
PreviewAreaJPanel.stepCount = 0;
PreviewAreaJPanel.wasteTime = "";
MainJFrame.buttonAreaJPanel.step.setText("步數:"+PreviewAreaJPanel.stepCount);
myTime.mySecond = 0;
myTime.myMinute = 0;
myTime.myHour = 0;
if(indexBegin == 0){
thread.start();
}
indexBegin++;
ImageAreaJPanel.myPuzzle.upset();
}
});
start.setFocusPainted(false);//去除擷取焦點時的邊框線
// 将開始按鈕添加到右邊的面闆中
rightJPanel.add(start);
// 将整個右邊的面闆添加到按鈕區
this.add(rightJPanel, BorderLayout.EAST);
}
}
ImageAreaJPanel
package jinhuan.pink;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import javax.swing.JPanel;
import javax.swing.border.TitledBorder;
public class ImageAreaJPanel extends JPanel{
/**
* 下面闆的兩個子面闆
* 一個是原圖區
* 一個是拼圖區
* */
static PreviewAreaJPanel myPreview = new PreviewAreaJPanel();
static PuzzleAreaJPanel myPuzzle = new PuzzleAreaJPanel();
/**
* 構造方法完成圖檔區域的初始化
* */
public ImageAreaJPanel() {
this.setLayout(new GridLayout(1, 2));
myPreview.setBorder(new TitledBorder("圖檔預覽區"));
myPuzzle.setBorder(new TitledBorder("拼圖區"));
this.add(myPreview, BorderLayout.WEST);
this.add(myPuzzle, BorderLayout.EAST);
}
}
PreviewAreaJPanel
package jinhuan.pink;
//圖檔預覽區
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
public class PreviewAreaJPanel extends JPanel{
//定義兩個變量
//圖檔的數字 ----> static 修飾的成員 可以直接通過類名.通路 靜态變量:類變量 随着類的加載而 初始化的 存在于 方法區的靜态區
static int picId = 1;
//總共用了多少步
static int stepCount = 0;
static String wasteTime = "";
//預覽區的圖檔
BufferedImage previewImage ;
//繪制圖檔
@Override
public void paint(Graphics g) {
super.paint(g);
previewImage = ResourceUtil.getImage(picId + ".jpg");
g.drawImage(previewImage, 70, 20, 450, 600, null);
}
}
PuzzleAreaJPanel
package jinhuan.pink;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
public class PuzzleAreaJPanel extends JPanel implements MouseListener {
static class PictureButton extends JButton {
// 構造方法完成按鈕的初始化
public PictureButton(Icon icon) {
super(icon);
this.setSize(90, 100);
this.setHorizontalTextPosition(CENTER);
this.setVerticalTextPosition(CENTER);
}
/**
* 定義小方格按鈕的運動的方法
* */
public void movePictureButton(String dir) {
switch (dir) {
case "RIGHT":
this.setLocation(this.getBounds().x + 90, this.getBounds().y);
break;
case "LEFT":
this.setLocation(this.getBounds().x - 90, this.getBounds().y);
break;
case "UP":
this.setLocation(this.getBounds().x, this.getBounds().y - 100);
break;
case "DOWN":
this.setLocation(this.getBounds().x , this.getBounds().y + 100);
break;
}
}
}
/**
* 設定拼圖區域
* 1、設定29個按鈕
* a、圖檔來添加到按鈕上
* b、for循環建立按鈕(記得減去一個按鈕)
* 2、一個為空白按鈕
* */
static PictureButton[] picButtons;
static Rectangle rectangle;
static BufferedImage showImage;
/**
* 構造方法完成屬性的初始化
* */
public PuzzleAreaJPanel() {
picButtons = new PictureButton[6 * 5];
this.setLayout(null);
for (int i = 0; i < 6; i++) {
for (int j = 0; j < 5; j++) {
showImage = ResourceUtil.getImage(PreviewAreaJPanel.picId + "_" + (5 * i + j + 1) + ".jpg");
picButtons[5 * i + j] = new PictureButton(new ImageIcon(showImage));
picButtons[5 * i + j].setLocation(70 + j * 90, 20 + i * 100);
this.add(picButtons[5 * i + j]);
}
}
// 删除最後一個小方格
this.remove(picButtons[picButtons.length - 1]);
// 設定最後的空白格的坐标來将其初始化
rectangle = new Rectangle(70 + 360, 20 + 500, 90, 100);
}
/**
* 顯示序号的方法
* */
public void showNum() {
// 循環為每個按鈕添加文本
for (int i = 0; i < picButtons.length - 1; i++) {
picButtons[i].setText("" + (i + 1));
}
}
/**
* 隐藏序号的方法
* */
public void hideNum() {
// 循環為每個按鈕添加文本
for (int i = 0; i < picButtons.length - 1; i++) {
picButtons[i].setText("");
}
}
/**
* 完成拼圖區的圖檔重新加載
* 擷取目前picId 重新設定
* */
public void reload() {
for (int i = 0; i < 6; i++) {
for (int j = 0; j < 5; j++) {
showImage = ResourceUtil.getImage(PreviewAreaJPanel.picId + "_" + (i * 5 + j + 1) + ".jpg");
// 重新設定小方格的圖檔
picButtons[i * 5 + j].setIcon(new ImageIcon(showImage));
picButtons[5 * i + j].setLocation(70 + j * 90, 20 + i * 100);
// 小方格的文本内容 清空
picButtons[i * 5 + j].setText("");
}
}
}
/**
* 完成 空白格 和 小方格 進行位置的交換
* a b 空白格的坐标位置
* */
public void movePictureButton(int rx, int ry, String dir) {
for (int i = 0; i < picButtons.length - 1; i++) {
if (picButtons[i].getBounds().x == rx && picButtons[i].getBounds().y == ry) {
picButtons[i].movePictureButton(dir);
rectangle.setLocation(rx, ry);
break;
}
}
}
/**
* 完成圖示的打亂
* */
boolean isListener = false;
public void upset() {// 打亂小方格
// 判斷目前的小方格有沒有被監聽
if (!isListener) {
// 為每一個小方格添加監聽
for (int i = 0; i < picButtons.length - 1; i++) {
picButtons[i].addMouseListener(this);
}
isListener = true;
}
//以第一個小方格的位置來判定,隻要第一個小方格的位置沒有發生改變就一直打亂小方格的位置
// for(int i=0;i<MainJFrame.buttonAreaJPanel.modIndex;i++) {
while (picButtons[0].getBounds().x <= 70 + MainJFrame.buttonAreaJPanel.modIndex * 90 || picButtons[0].getBounds().y <= 20 + MainJFrame.buttonAreaJPanel.modIndex * 100 ) {
//擷取空白格的坐标
int rx = rectangle.getBounds().x;
int ry = rectangle.getBounds().y;
//定義一個 0 - 3 之間的随機整數,用來表示空白格上下左右移動的方向
int random = (int) (Math.random() * 4);
switch (random) {
case 0:
//當随機數為0 的時候 空白格水準向左移動一格
rx -= 90;
//和空白個重合的小方格向右水準移動一格
movePictureButton(rx,ry,"RIGHT");
break;
case 1:
//随機數為1時,空白格水準向右移動一格
rx += 90;
//和空白格重合的小方格向左水準移動一格
movePictureButton(rx,ry,"LEFT");
break;
case 2:
//随機數為2的時候 空白格垂直向下移動一格
ry += 100;
//和空白格重合的小方格垂直向上移動一格
movePictureButton(rx,ry,"UP");
break;
case 3:
//當随機數為3時,空白格垂直向下移動一格
ry -= 100;
movePictureButton(rx,ry,"DOWN");
break;
}
}
}
public void moveSwitch(int random,int rx,int ry) {
switch (random) {
case 0://随機數為0 --> 空白格 向左移動一格
//改變空白格的坐标
rx -= 90;
//和空白格重合的小方格進行向右移動一格
movePictureButton(rx, ry, "RIGHT");
break;
case 1: // ---> 空白格向右移動一格
//改變空格格的坐标
rx+=90;
//和空白格重合的小方格進行向左移動一格
movePictureButton(rx, ry, "LEFT");
break;
case 2:// ---> 向下
//改變空格格的坐标
ry+=100;
//和空白格重合的小方格進行向上移動一格
movePictureButton(rx, ry, "UP");
break;
case 3://向上
//改變空格格的坐标
ry-=100;
//和空白格重合的小方格進行向左移動一格
movePictureButton(rx, ry, "DOWN");
break;
}
}
@Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mousePressed(MouseEvent e) {
//當滑鼠按下時候 發生一些操作
// 1 擷取被點選的對象 ---> 小方格
PictureButton p = (PictureButton) e.getSource();
// 2 擷取目前被點選的小方格的 坐标
int px = p.getBounds().x;
int py = p.getBounds().y;
//3 .擷取目前空方格的坐标
int rx = rectangle.getBounds().x;
int ry = rectangle.getBounds().y;
//4 判斷 目前被點選的小方格 周圍有沒有空白格
if (px == rx && py - ry == 100) {//上邊有空白格
//小方格 移動到上邊
p.movePictureButton("UP");
}else if (px == rx && py - ry == -100) {//下邊有空白格
//小方格移動到下邊
p.movePictureButton("DOWN");
}else if (py == ry && px - rx == 90) {//左邊有空白格
//小方格 左移
p.movePictureButton("LEFT");
}else if (py == ry && px - rx == -90) {//右邊有空白格
// 小方格 右邊移動
p.movePictureButton("RIGHT");
}else {
return;
}
// 5 當小方格移動之後 空白格跟着移動
rectangle.setLocation(px, py);// 将空白格的坐标設定為原來小方格的坐标
// 6 重繪
this.repaint();
// 7 當移動了一次 步數進行 + 1
PreviewAreaJPanel.stepCount++;
// 8更新 文本框中的步數的内容
ButtonAreaJPanel.step.setText("步數:"+PreviewAreaJPanel.stepCount);
// 9 當遊戲完成的時候 彈出一格框框 顯示 用了多少步
if (win()) {
//彈出框 顯示資訊
MainJFrame.buttonAreaJPanel.thread.stop();
long endTime = System.currentTimeMillis();
long cha = (endTime - MainJFrame.buttonAreaJPanel.beginTime)/1000;
JOptionPane.showMessageDialog(this, "勝利! 共用了:"+PreviewAreaJPanel.stepCount+"步,"+ cha+"秒");
// 複原遊戲 取消 每個小方格的監聽
for (int i = 0; i < picButtons.length-1; i++) {
picButtons[i].removeMouseListener(this);
}
isListener = false;
}
}
/**
* 判斷是否遊戲成功
* */
public boolean win() {
//周遊所有的方格 判斷每個方格是不是在它應該在的位置
for (int i = 0; i < picButtons.length-1; i++) {
//擷取方格的位置
int x = picButtons[i].getBounds().x;
int y = picButtons[i].getBounds().y;
//隻要有一個方格不在自己的位置上就傳回false
if ((y-20)/100*5 + (x-20)/90 != i) {
return false;
}
}
return true;
}
@Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
@Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
}
原檔案以及源碼下載下傳連結:
連結:https://pan.baidu.com/s/19oe0AavtxNyCQfs8rbpK9A
提取碼:3598
感謝支援!謝謝!