Wordless: 一個周末打造的小爆游戲
這個項目是什么
Wordless就是個類似 Wordle 的猜單詞游戲,用 Next.js 搭建的。玩家有 6 次機會猜出單詞,支持 3 到 8 個字母的單詞。說實話,開始只是想做點跟 wordle 不一樣?xùn)|西,沒想到做著做著就越來越有意思了。
點擊這里可以體驗:https://wordless.online/
我用了一個周末把代碼擼完以后,直接發(fā)布上線,也沒怎么關(guān)注,沒想到幾個月過去了,這個小游戲的流量一直很穩(wěn)定,有 50%的自然搜索,40%的直接訪問流量,這可是個非常漂亮的流量數(shù)據(jù)呀。
用了什么技術(shù)
主要框架
-
React 18.3.1 - 沒什么好說的,前端必備
-
Next.js 14.2.4 - 選它主要是因為 SSR 和 API 路由很方便
-
TypeScript - 雖然寫起來麻煩點,但能避免很多低級錯誤
-
Tailwind CSS - 寫樣式賊快,不用想類名
UI 相關(guān)
-
Radix UI - 彈窗、通知這些組件用的,無障礙做得不錯
-
Lucide React - 圖標(biāo)庫,簡潔好看
-
Canvas Confetti - 猜對了撒彩帶的特效,挺有意思的
其他工具
-
SWR - 數(shù)據(jù)獲取用的,緩存機制不錯
-
Zod - 數(shù)據(jù)驗證,比手寫 if-else 強多了
-
nspell - 檢查單詞拼寫是否正確
文件結(jié)構(gòu)
wordlessgame/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── api/ # API 路由
│ │ │ ├── ai-completion/ # AI 輔助功能
│ │ │ ├── validate-word/ # 單詞驗證
│ │ │ └── words/ # 單詞生成
│ │ ├── layout.tsx # 根布局
│ │ └── page.tsx # 主頁面
│ ├── components/ # React 組件
│ │ ├── ui/ # 基礎(chǔ) UI 組件
│ │ ├── game-grid.tsx # 游戲網(wǎng)格
│ │ ├── key-board.tsx # 虛擬鍵盤
│ │ └── result-modal.tsx # 結(jié)果彈窗
│ ├── data/ # 靜態(tài)數(shù)據(jù)
│ │ └── word-lists.ts # 單詞詞庫
│ ├── lib/ # 工具函數(shù)
│ │ ├── api.ts # API 調(diào)用
│ │ └── utils.ts # 工具函數(shù)
│ ├── ai/ # AI 相關(guān)功能
│ └── styles/ # 樣式文件
└── public/ # 靜態(tài)資源
核心功能實現(xiàn)
1. 游戲狀態(tài)管理
游戲使用 React Hooks 管理復(fù)雜的狀態(tài):
// 主要狀態(tài)
const [columns, setColumns] = useState(3); // 單詞長度
const [gridContent, setGridContent] = useState<string[]>([]); // 網(wǎng)格內(nèi)容
const [currentCell, setCurrentCell] = useState(-1); // 當(dāng)前輸入位置
const [word, setWord] = useState(''); // 目標(biāo)單詞
const [matchResults, setMatchResults] = useState<string[]>([]); // 匹配結(jié)果
const [cellMatchClasses, setCellMatchClasses] = useState<string[]>([]); // 樣式類
2. 單詞生成系統(tǒng)
采用優(yōu)化的單詞生成器,避免重復(fù)選擇同一單詞:
class WordGenerator {
private static instance: WordGenerator;
private readonly cache: Map<number, string[]> = new Map();
private readonly usedIndices: Map<number, Set<number>> = new Map();
private readonly shuffledIndices: Map<number, number[]> = new Map();
public getRandomWord(length: number): string {
// Fisher-Yates 洗牌算法確保隨機性
// 避免重復(fù)選擇相同單詞
}
}
3. 游戲邏輯算法
const matchWord = (guessedWord: string, targetWord: string) => {
const result = new Array(guessedWord.length).fill('X'); // X=不匹配
const targetCounts = new Map<string, number>();
// 第一遍:標(biāo)記完全匹配的字母
for (let i = 0; i < guessedWord.length; i++) {
if (guessedWord[i] === targetWord[i]) {
result[i] = 'C'; // C=正確位置
} else {
targetCounts.set(targetWord[i]!, (targetCounts.get(targetWord[i]!) || 0) + 1);
}
}
// 第二遍:標(biāo)記位置錯誤的字母
for (let i = 0; i < guessedWord.length; i++) {
if (result[i] !== 'C') {
const char = guessedWord[i]!;
if (targetCounts.get(char)! > 0) {
result[i] = 'P'; // P=位置錯誤
targetCounts.set(char, targetCounts.get(char)! - 1);
}
}
}
return result;
};
4. 游戲網(wǎng)格組件
export function GameGrid({
gridContent,
columns,
currentCell,
cellMatchClasses,
flippingRows
}: GameGridProps) {
return (
<div className={`grid ${gridCol} gap-2 mb-8`}>
{gridContent.map((content, index) => {
const matchClass = cellMatchClasses[index];
const isFlipping = flippingRows.has(Math.floor(index / columns));
return (
<div
className={cn(
'w-14 h-14 flex items-center justify-center text-2xl font-bold rounded-md',
matchClass === 'C' ? 'bg-green-500 text-white' :
matchClass === 'P' ? 'bg-yellow-500 text-white' :
matchClass === 'X' ? 'bg-zinc-400 text-white' : 'bg-white',
isFlipping ? 'animate-flip' : ''
)}
style={{
animationDelay: isFlipping ? `${(index % columns) * 100}ms` : '0ms'
}}
>
{content}
</div>
);
})}
</div>
);
}
5. 虛擬鍵盤組件
使用 memo 優(yōu)化渲染性能:
const KeyButton = memo(({
letter,
isMatched,
noMatched,
onClick
}: KeyButtonProps) => {
return (
<button
onClick={() => onClick(letter)}
className={cn(
'w-14 h-14 rounded-md font-bold transition-colors',
isMatched ? 'bg-green-500 text-white' :
noMatched ? 'bg-zinc-400 text-white' :
'bg-white hover:bg-violet-50'
)}
>
{letter}
</button>
);
});
數(shù)據(jù)結(jié)構(gòu)
單詞詞庫
詞庫按長度分類存儲,每個長度包含 500+ 個單詞:
export const WORD_LISTS: Record<number, string[]> = {
3: ['ace', 'age', 'air', ...], // 500+ 三字母單詞
4: ['able', 'acid', 'aged', ...], // 500+ 四字母單詞
5: ['about', 'above', 'abuse', ...], // 500+ 五字母單詞
6: ['abroad', 'accept', 'access', ...], // 500+ 六字母單詞
7: ['abandon', 'ability', 'absence', ...], // 500+ 七字母單詞
8: ['absolute', 'academic', 'accepted', ...] // 500+ 八字母單詞
};
API 設(shè)計
Edge Runtime API
使用 Next.js Edge Runtime 提供快速的 API 響應(yīng):
export const runtime = 'edge';
export async function GET(request: NextRequest) {
// API 邏輯
return NextResponse.json({ data });
}
用戶體驗優(yōu)化
1. 動畫效果
-
卡片翻轉(zhuǎn)動畫:使用 CSS
animate-flip類 -
按鍵反饋:按鍵按下時的視覺反饋
-
勝利慶祝:使用 canvas-confetti 庫
2. 響應(yīng)式設(shè)計
-
移動端優(yōu)化的鍵盤布局
-
自適應(yīng)網(wǎng)格大小
-
觸摸友好的交互
3. 性能優(yōu)化
-
使用 React.memo 減少不必要的重渲染
-
useCallback 和 useMemo 優(yōu)化函數(shù)和計算
-
單詞生成器的緩存機制
部署和 SEO
SEO 優(yōu)化
export const metadata: Metadata = {
title: "Unlimited Wordless Online: Guess the Word in 6 Tries!",
description: "Wordless Online: Endless Word Challenges...",
keywords: "wordless,wordly, wordle, game, puzzle, word, words, letters, play, online, guess,unlimited",
};
分析工具
-
Google Analytics 用戶行為追蹤
-
自定義事件追蹤
開發(fā)工具
代碼質(zhì)量
-
ESLint - 代碼規(guī)范檢查
-
Prettier - 代碼格式化
-
TypeScript - 類型安全
包管理
- PNPM - 快速、節(jié)省磁盤空間的包管理器
游戲流程
-
初始化:選擇單詞長度(3-8個字母)
-
生成目標(biāo)單詞:從詞庫中隨機選擇
-
用戶輸入:通過虛擬鍵盤或物理鍵盤輸入
-
驗證輸入:檢查是否為有效單詞
-
匹配算法:計算字母匹配情況
-
視覺反饋:顯示顏色提示(綠色=正確位置,黃色=錯誤位置,灰色=不存在)
-
游戲結(jié)束:6次嘗試后或猜中單詞后結(jié)束
-
結(jié)果展示:顯示結(jié)果彈窗和慶祝動畫
特色功能
1. 可變單詞長度
支持 3-8 個字母的單詞,增加游戲難度選擇
2. 智能單詞生成
避免重復(fù),確保每次游戲都有新鮮感
3. 實時反饋
即時的視覺和交互反饋,提升用戶體驗
4. 無限游戲
沒有次數(shù)限制,可以連續(xù)游戲
5. 響應(yīng)式設(shè)計
適配各種設(shè)備屏幕尺寸
技術(shù)亮點
-
現(xiàn)代化技術(shù)棧:使用最新的 React、Next.js 和 TypeScript
-
性能優(yōu)化:多層次的性能優(yōu)化策略
-
可維護性:清晰的代碼結(jié)構(gòu)和組件分離
-
用戶體驗:流暢的動畫和交互效果
-
可擴展性:模塊化設(shè)計便于功能擴展
總結(jié)
Wordless 是一款用現(xiàn)代 Web 技術(shù)打造的游戲,干凈利落,跑得飛快,代碼還容易維護。沒堆花里胡哨的東西,但該有的全都有——好玩、流暢、寫得明白。
歡迎來體驗: https://wordless.online/

浙公網(wǎng)安備 33010602011771號