想開發一個能自動解拼圖的遊戲送給女友,核心需求:自定義圖片、自定義宮格(3×3, 4×4, 5×5)、自動求解功能。關鍵難點在於如何讓電腦像人一樣高效拼圖。
拼圖本質是 1~n 的排列問題。定義:
i < j 但 a[i] > a[j],則 (a[i], a[j]) 為一個逆序對。核心結論:N×N 拼圖(空白格視為 N²)有解 ⇔ 排列為偶排列。參考:Fifteen Puzzle。
將 N×N 棋盤劃分為 4 個區域,逐區復位,避免已復位區域被破壞:
x ∈ [0, n-2], y ∈ [0, n-3](主體區域,除最後兩行兩列)x = n-1, y ∈ [0, n-3](最右列,除最後兩格)x ∈ [0, n-3], y ∈ [n-2, n-1](最底兩行,除最右兩列)x ∈ [n-2, n-1], y ∈ [n-2, n-1](右下角 2×2 區域)每區有專用復位規則,按順序執行即可保證全局復位。
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.Random;
public class Puzzle {
private long step = 0;
private int n = 6; // 宮格階數
private int[][] puzzle;
private int resetBlock = 0; // 目標塊編號
private int whiteBlockX, whiteBlockY; // 空白格位置
private int resetBlockX, resetBlockY; // 目標塊當前位置
private boolean isPrint = false;
public Puzzle() { init(); }
public Puzzle(int n) { this.n = n; init(); }
private void init() {
puzzle = new int[n][n];
for (int y = 0; y < n; y++)
for (int x = 0; x < n; x++)
puzzle[y][x] = x + y * n; // 0 為空白格
whiteBlockX = n - 1;
whiteBlockY = n - 1;
// 打亂:隨機交換 100 次(偶數次保證偶排列)
int times = 100;
Random random = new Random();
while (times > 0) {
int x0 = random.nextInt(n), y0 = random.nextInt(n);
int x1 = random.nextInt(n), y1 = random.nextInt(n);
if (x0 != x1 && y0 != y1) {
// 不動空白格
if ((x0 == n-1 && y0 == n-1) || (x1 == n-1 && y1 == n-1)) continue;
times--;
int t = puzzle[x0][y0];
puzzle[x0][y0] = puzzle[x1][y1];
puzzle[x1][y1] = t;
}
}
}
// 總入口:按區域順序復位
public void sort() {
for (int y = 0; y < n; y++)
for (int x = 0; x < n; x++)
if (x != n-1 || y != n-1) // 跳過空白格目標位置
reset(x, y);
}
// 核心遞歸復位邏輯(節選關鍵方法)
private void reset(int targetX, int targetY) { /* 區域一邏輯 */ }
private void reset2(int targetX, int targetY) { /* 區域二邏輯 */ }
private void reset3(int targetX) { /* 區域三邏輯 */ }
private void resetBlockToTarget(int tx, int ty) { /* 通用目標塊移動 */ }
private void resetBlockLeft/Right/Up(int tx, int ty) { /* 目標塊四向移動 */ }
private void whiteBlockLeft/Right/Up/Down() { /* 空白格四向移動 */ }
private void swapWhiteBlockAndCurrentBlock() { /* 交換空白格與目標塊 */ }
private void initResetBlock(int tx, int ty) { /* 定位目標塊 */ }
// ... 省略具體遞歸實現,完整代碼見下方
@Override
public String toString() { /* 格式化輸出棋盤狀態 */ }
public static void main(String[] args) throws Exception {
Puzzle p = new Puzzle();
System.out.println(p);
try { p.sort(); } catch (Exception e) { e.printStackTrace(); }
finally { System.out.println(p); }
}
}
// 完整代碼已在輸入中給出,此處省略重複,實際使用請複製原文 Puzzle.java 完整內容
初始打亂狀態: resetBlock=(0,5,5) 33,23,20,26,18,21, 6,16,17,10,34,31, 19,28,32,7,3,2, 11,4,12,14,27,24, 15,29,30,8,13,35, 0,1,2,3,4,5, 排序過程輸出移動步驟(↑ ↓ ← → swap)... 最終有序狀態: resetBlock=(35,5,5) 0,1,2,3,4,5, 6,7,8,9,10,11, 12,13,14,15,16,17, 18,19,20,21,22,23, 24,25,26,27,28,29, 30,31,32,33,34,35,