首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 實作簡易拼圖遊戲:從入門到完整原始碼解析

     編輯:Java 相關知識

專案簡介

本文提供一個基於 Java Swing 的經典拼圖遊戲完整實現。專案包含圖片載入、打亂邏輯、勝負判定、菜單操作(開始、重置、換圖、查看樣圖)等核心功能,適合初學者學習 GUI 程式設計與事件處理。

核心類別架構

  • J_Puzzle:主視窗類別,繼承 JFrame,實作 ActionListener,負責遊戲初始化、菜單欄建立、拼圖按鈕佈局、打亂演算法與勝負檢測。
  • J_JPanel:自定義繪圖面板,用於顯示完整參考圖片。

關鍵功能實現

1. 圖片切割與按鈕初始化

將一張 360x360 的圖片切割為 4x4 的 15 個小塊(最後一塊為空白),每塊 90x90 像素,存入 ImageIcon[][] 陣列。使用 GridBagLayout 將 16 個按鈕(含空白佔位)排列於容器中。

2. 打亂演算法

採用費雪-耶茨洗牌演算法生成不重複的隨機索引陣列 randomnumber(),將按鈕位置對應映射到預設的 16 個座標點,確保拼圖可解性(本範例未嚴格驗證逆序數)。

3. 滑塊移動邏輯

在 actionPerformed 中,檢測被點擊按鈕上下左右相鄰位置是否為空白區域(高度不等於 90),若是則交換位置。移動後呼叫 check() 判斷是否復位成功。

4. 菜單功能

  • 開始/重置:重新打亂拼圖。
  • 背景更換:切換兩組預設圖片(changetime 控制)。
  • 查看樣圖:彈出新視窗顯示完整圖片(使用 J_JPanel 繪製)。
  • 關於:顯示作者資訊。

完整原始碼

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.Toolkit;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.KeyStroke;

class J_JPanel extends JPanel {
    private static final long serialVersionUID = 1L;
    Image m_image;
    int showpicture = 0;

    @SuppressWarnings("static-access")
    public J_JPanel() throws IOException {
        J_Puzzle a = new J_Puzzle();
        if (a.changetime == -1) showpicture = 1;
        File f = new File("樣圖" + showpicture + ".jpg");
        m_image = ImageIO.read(f);
    }

    public void paintComponent(Graphics g) {
        g.drawImage(m_image, 0, 0, 360, 360, this);
    }
}

public class J_Puzzle extends JFrame implements ActionListener {
    private static final long serialVersionUID = 1L;
    int i, j;
    static int changetime = 0;
    Container c = getContentPane();
    JButton b[] = new JButton[16];
    ImageIcon ic[][] = new ImageIcon[2][15];

    public J_Puzzle() throws IOException {
        super("拼圖小遊戲");
        String pic_name[] = new String[15];
        for (i = 0; i < 2; i++)
            for (j = 0; j < 15; j++) {
                pic_name[j] = String.valueOf(j + 1 + i * 15) + ".jpg";
                ic[i][j] = new ImageIcon(pic_name[j]);
            }

        JMenuBar mBar = new JMenuBar();
        setJMenuBar(mBar);
        int k = 0;
        JMenu[] m = {new JMenu("菜單(M)"), new JMenu("贊助(H)")};
        char mC[][] = {{'M', 'H'}, {'S', 'X', 'C', 'Z'}, {'E', 'T'}};
        JMenuItem mItem[][] = {
            {new JMenuItem("開始(S)"), new JMenuItem("重置(X)"), new JMenuItem("背景更換(C)"), new JMenuItem("退出(Z)")},
            {new JMenuItem("查看樣圖(E)"), new JMenuItem("關於(T)")}
        };

        for (i = 0; i < 2; i++) {
            mBar.add(m[i]);
            m[i].setMnemonic(mC[0][i]);
            if (i == 0) k = 0;
            else k = 1;
            for (j = 0; j < 4 - i - k; j++) {
                m[i].add(mItem[i][j]);
                mItem[i][j].setMnemonic(mC[i + 1][j]);
                mItem[i][j].setAccelerator(KeyStroke.getKeyStroke("ctrl " + mC[i + 1][j]));
                mItem[i][j].addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        JMenuItem mItem = (JMenuItem) e.getSource();
                        if (mItem.getText().equalsIgnoreCase("重置(X)") || mItem.getText().equalsIgnoreCase("開始(S)")) {
                            int location[][] = {
                                {17, 13}, {17, 103}, {17, 193}, {17, 283},
                                {107, 13}, {107, 103}, {107, 193}, {107, 283},
                                {197, 13}, {197, 103}, {197, 193}, {197, 283},
                                {287, 13}, {287, 103}, {287, 193}, {287, 283}
                            };
                            int rd_number[] = new int[16];
                            rd_number = randomnumber();
                            for (i = 1; i < 16; i++)
                                b[i].setLocation(location[rd_number[i - 1] - 1][0], location[rd_number[i - 1] - 1][1]);
                        } else if (mItem.getText().equalsIgnoreCase("背景更換(C)")) {
                            changetime++;
                            for (i = 0; i < 15; i++) {
                                b[i + 1].setIcon(null);
                                b[i + 1].setIcon(ic[changetime][i]);
                            }
                            if (changetime == 1) changetime = -1;
                        } else if (mItem.getText().equalsIgnoreCase("退出(Z)")) {
                            int a = JOptionPane.showConfirmDialog(null, "您確定退出遊戲?");
                            if (a == 0) System.exit(0);
                        } else if (mItem.getText().equalsIgnoreCase("查看樣圖(E)")) {
                            JFrame jj = new JFrame("樣圖");
                            jj.setSize(360, 360);
                            jj.setLocation(Toolkit.getDefaultToolkit().getScreenSize().width / 3 - 360,
                                    Toolkit.getDefaultToolkit().getScreenSize().height / 4);
                            jj.setVisible(true);
                            Container c1 = jj.getContentPane();
                            try {
                                c1.add(new J_JPanel(), BorderLayout.CENTER);
                            } catch (IOException e1) {
                                e1.printStackTrace();
                            }
                        }
                        if (mItem.getText().equalsIgnoreCase("關於(T)")) {
                            JOptionPane.showMessageDialog(null, "簡易拼圖小遊戲\n製作人:菜鳥");
                        }
                    }
                });
            }
        }
        m[0].insertSeparator(1);
        m[1].insertSeparator(1);

        GridBagLayout gr = new GridBagLayout();
        c.setLayout(gr);
        int gx[] = {0, 1, 2, 3};
        int gy[] = {0, 1, 2, 3};
        int k1;
        Dimension d = new Dimension(90, 90);
        String s_number;
        GridBagConstraints gc = new GridBagConstraints();
        for (i = 1; i < 5; i++) {
            if (i == 4) k1 = 4;
            else k1 = 5;
            for (j = 1; j < k1; j++) {
                gc.gridx = gx[j - 1];
                gc.gridy = gy[i - 1];
                gc.fill = GridBagConstraints.NONE;
                s_number = String.valueOf(j + (i - 1) * 4);
                b[j + (i - 1) * 4] = new JButton(s_number, ic[0][j + (i - 1) * 4 - 1]);
                b[j + (i - 1) * 4].setPreferredSize(d);
                b[j + (i - 1) * 4].setFont(new Font("宋體", Font.PLAIN, 0));
                gr.setConstraints(b[j + (i - 1) * 4], gc);
                c.add(b[j + (i - 1) * 4]);
            }
        }
        for (i = 1; i < 16; i++)
            b[i].addActionListener(this);
    }

    public void actionPerformed(ActionEvent e) {
        int j;
        JButton b = (JButton) e.getSource();
        Point p = b.getLocation();
        Point p1 = null;
        for (j = -1; j < 2; j++) {
            if (p.y + j * 90 > 283 || p.y + j * 90 < 13) continue;
            else {
                Component a = c.getComponentAt(p.x, p.y + j * 90);
                if (a.getHeight() != 90) p1 = new Point(p.x, p.y + j * 90);
            }
        }
        for (j = -1; j < 2; j++) {
            if (p.x + j * 90 > 287 || p.x + j * 90 < 17) continue;
            else {
                Component a = c.getComponentAt(p.x + j * 90, p.y);
                if (a.getHeight() != 90) p1 = new Point(p.x + j * 90, p.y);
            }
        }
        if (p1 != null) b.setLocation(p1.x, p1.y);
        if (check() == true)
            JOptionPane.showMessageDialog(null, "恭喜您獲勝了");
    }

    public int[] randomnumber() {
        Random rd = new Random();
        int n[] = new int[15];
        for (int i = 0; i < 15; i++) {
            int temp = rd.nextInt(15) + 1;
            n[i] = temp;
            for (int j = 0; j < i; j++)
                if (n[j] == temp) {
                    i--;
                    break;
                }
        }
        return n;
    }

    public boolean check() {
        Point location[] = new Point[16];
        boolean bo = false;
        int count = 0;
        for (int i = 0; i < 4; i++)
            for (int j = 0; j < 4; j++)
                location[i * 4 + j] = new Point(17 + j * 90, 13 + i * 90);
        for (int i = 0; i < 15; i++) {
            if (b[i + 1].getLocation().x == location[i].x && b[i + 1].getLocation().y == location[i].y)
                count++;
            if (count == 15) bo = true;
        }
        return bo;
    }

    public static void main(String args[]) throws IOException {
        J_Puzzle app = new J_Puzzle();
        app.setDefaultCloseOperation(EXIT_ON_CLOSE);
        app.setLocation(Toolkit.getDefaultToolkit().getScreenSize().width / 3,
                Toolkit.getDefaultToolkit().getScreenSize().height / 4);
        app.setSize(400, 440);
        app.setVisible(true);
        app.setResizable(false);
    }
}

執行與擴展建議

將編譯後的 class 檔與圖片資源(樣圖1.jpg、樣圖2.jpg、1.jpg~30.jpg)置於同目錄下即可執行。可嘗試擴展:加入計步器、計時器、多難度選擇(3x3、5x5)、自動求解演算法(A* 或 IDA*)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved