背後的故事
這件事源於前段時間和一位做教育培訓的老同學吃飯。
他現在在某線上教育平台做產品,聊到最近在做什麼時,就問我能不能幫他們做一個數獨遊戲,方便老師直接用在教學場景裡。
我愣了一下,問他具體想要什麼樣的。
他說:「就……讓老師能在課件裡直接插一張數獨題,不用登入、別搞那麼複雜,能一個連結搞定最好了。」
我心想,這不就是 API 嗎?
總結來說,這個 API 就是提供數獨圖片和可互動的數獨遊戲。
不用註冊,不用申請 API Key,直接透過 URL 呼叫就能用。
而且是**即時生成**的——每次請求都是伺服器端即時計算,毫秒級回應。
研究的過程
第一個版本:暴力生成
最早用的是最直接的方法——先生成完整解,再隨機挖空,然後驗證唯一解:
```javascript
// 生成完整數獨
function generateComplete() {
const board = Array(81).fill(0);
fillBoard(board); // 回溯填數
return board;
}
// 挖空生成題目
function createPuzzle(board, holes) {
const puzzle = [...board];
const positions = shuffle([...Array(81).keys()]);
for (let i = 0; i < holes; i++) {
puzzle[positions[i]] = 0;
}
// 驗證唯一解 - 找第二個解
const solutions = [];
findSolutions(puzzle, solutions, 2);
return solutions.length === 1 ? puzzle : null;
}
簡單粗暴,但跑起來平均要 好幾秒 才能生成一道題——使用者等不了這麼久。
第二個版本:邊填邊驗證
後來改成「候選數填充 + 提前剪枝」:
function generateFast() {
const board = Array(81).fill(0);
function fill(index) {
if (index === 81) return true;
const candidates = shuffle([1, 2, 3, 4, 5, 6, 7, 8, 9]);
for (const num of candidates) {
if (isValid(board, index, num)) {
board[index] = num;
if (fill(index + 1)) return true;
board[index] = 0;
}
}
return false;
}
fill(0);
return board;
}
加上剪枝優化後,速度提升到 幾十毫秒,可以接受了。
但新問題來了——難度怎麼控制?
第三個版本:挖空數量不等於難度
一開始我以為挖得越多越難:
function createPuzzleByHoles(board, difficulty) {
const holesMap = {
easy: 35,
medium: 45,
hard: 55
};
return digHoles(board, holesMap[difficulty]);
}
結果測試時發現問題:同樣挖 45 個格子的題,有的 30 秒就解出來,有的要花 10 分鐘。
第四個版本:引入難度評估指標
後來引入了「最少候選數」和「回溯步數」來評估難度:
function evaluateDifficulty(puzzle) {
let minCandidates = Infinity;
let backtrackSteps = 0;
function solve(board, steps) {
const emptyIndex = board.indexOf(0);
if (emptyIndex === -1) return true;
const candidates = getCandidates(board, emptyIndex);
minCandidates = Math.min(minCandidates, candidates.length);
for (const num of candidates) {
board[emptyIndex] = num;
backtrackSteps++;
if (solve(board, backtrackSteps)) return true;
board[emptyIndex] = 0;
}
return false;
}
solve([...puzzle], 0);
// 難度由候選數分布和回溯步數決定
return {
minCandidates,
backtrackSteps,
score: minCandidates * 0.3 + backtrackSteps * 0.7
};
}
配合人工調參,才把六個難度等級拉開差距。
還有一個坑:唯一解驗證
理論上從完整解挖空應該能保證唯一解,但實際跑下來,有些組合會卡死——有空格填不出來,或者出現多個解。
我最後用的是「逐格驗證回溯」:
function verifyUniqueSolution(puzzle) {
let solutionCount = 0;
function findSolutions(board, limit) {
if (solutionCount >= limit) return;
const emptyIndex = board.indexOf(0);
if (emptyIndex === -1) {
solutionCount++;
return;
}
for (let num = 1; num <= 9; num++) {
if (isValid(board, emptyIndex, num)) {
board[emptyIndex] = num;
findSolutions(board, limit);
board[emptyIndex] = 0;
}
}
}
findSolutions([...puzzle], 2);
return solutionCount === 1;
}
每挖掉一格就驗證一次,確保每一步都不會走向死路。這一步比較慢,但因為是在後台非同步生成,使用者感知不到。
現在整套流程跑下來:
- 毫秒級生成回應
- 有且只有一個正確答案
- 難度係數精準可控
幾種常見的用法
1. 取得一張數獨圖片
https://www.sudoku100.com/sudoku-img
這會回傳一張隨機難度的數獨圖片,每次刷新都不一樣。
2. 指定難度
https://www.sudoku100.com/sudoku-img/easy
https://www.sudoku100.com/sudoku-img/medium
https://www.sudoku100.com/sudoku-img/hard
……
支援 6 個難度等級:入門、簡單、中等、困難、專家、極端。
3. 固定題目
有時候你需要一張固定的數獨,例如每次打開都是同一道題。
https://www.sudoku100.com/id/238
這個 ID 238 代表一道固定的中等難度數獨,每次訪問都是同一題。
雖然圖片是即時生成的,但由於演算法是確定性的,相同的 ID 會產出完全相同的數獨。
4. 調整尺寸和格式
預設是 720px 的 PNG 圖片。如果你想換成其他尺寸或格式:
https://www.sudoku100.com/sudoku-img?width=720&format=webp
寬度可以自訂,格式支援 PNG、WebP、JPG。
5. 嵌入可互動的數獨遊戲
如果你的網站需要讓使用者直接在線玩數獨,可以用 iframe 嵌入:
<iframe
src="https://www.sudoku100.com/embed/interactive"
width="800"
height="600"
style="border: none; border-radius: 6px;">
</iframe>
支援觸控與鍵盤操作,並具備響應式佈局。
實際應用場景
我看過一些有趣的用法:
- 教育培訓網站:在課程內容中插入數獨練習題
- 部落格文章:在教學中放一張數獨圖片作為示例
- 列印素材:生成高解析圖片用於列印
- 社群媒體:每天發一張數獨到公眾號或微博
用在 AI 工具裡
最近很多人把它搭配 ChatGPT 這類 AI 工具使用。
例如你讓 AI 寫一篇關於數獨的文章,它可以直接在內容中插入一張數獨圖片連結。
不需要寫程式,AI 可以直接理解並生成可用的連結。
有什麼限制
說實話,目前用下來沒遇到什麼限制:
- 不需要 API Key
- 沒有請求頻率限制
- 完全免費
- 商用也沒問題
怎麼取得更多固定題目
網站上有一個模板頁面,列出了所有可用的固定數獨 ID。
ID 的編碼規則如下:
- 第一位數代表難度(1=入門,2=簡單…6=極端)
- 後面的數字是實際的圖片 ID
例如 325 就是難度 3 的第 25 道題。
最後
寫這篇文章,只是想分享一下這個專案的心路歷程。同時我們也承諾,這個 API 完全免費、沒有任何限制,並且會永久免費。
主要是因為這個專案的初心來自教育應用,加上演算法優化之後,其實不需要太多伺服器資源就能支撐大規模併發請求。
所以還是那句話——為科技向善貢獻一份力量。





















