本文提供一個基於 Java Swing 的經典拼圖遊戲完整實現。專案包含圖片載入、打亂邏輯、勝負判定、菜單操作(開始、重置、換圖、查看樣圖)等核心功能,適合初學者學習 GUI 程式設計與事件處理。
JFrame,實作 ActionListener,負責遊戲初始化、菜單欄建立、拼圖按鈕佈局、打亂演算法與勝負檢測。將一張 360x360 的圖片切割為 4x4 的 15 個小塊(最後一塊為空白),每塊 90x90 像素,存入 ImageIcon[][] 陣列。使用 GridBagLayout 將 16 個按鈕(含空白佔位)排列於容器中。
採用費雪-耶茨洗牌演算法生成不重複的隨機索引陣列 randomnumber(),將按鈕位置對應映射到預設的 16 個座標點,確保拼圖可解性(本範例未嚴格驗證逆序數)。
在 actionPerformed 中,檢測被點擊按鈕上下左右相鄰位置是否為空白區域(高度不等於 90),若是則交換位置。移動後呼叫 check() 判斷是否復位成功。
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*)。