大家好!今天我們要挑戰一個經典的單詞猜謎游戲——“劊子手”(Hangman),并使用 JavaFX 這個強大的 GUI 工具包來賦予它現代化的交互體驗。這個項目不僅有趣,而且是學習和實踐 JavaFX 核心概念的絕佳途徑,涵蓋了布局管理、自定義繪制、事件處理、狀態管理等多個方面。
我們將構建的這個版本不僅僅是基礎功能,還包含了:
- 單詞分類選擇: 增加游戲的可玩性和重玩價值。
- 圖形化絞刑架: 使用
Canvas
動態繪制小人被“吊起”的過程,提供直觀的視覺反饋。 - 屏幕虛擬鍵盤: 提供界面內交互,更適合觸屏或純鼠標操作。
- 清晰的狀態反饋: 實時顯示猜測進度、錯誤次數、猜錯的字母等。
無論你是想系統學習 JavaFX,還是尋找一個有一定復雜度的練手項目,這篇文章都將為你提供詳細的實現步驟和深入的技術解析。
一、 設計藍圖:游戲規則與界面構思
在動手編碼前,清晰的設計是成功的關鍵。
游戲規則核心:
- 選詞: 程序從預設的單詞庫(按分類)中隨機選擇一個秘密單詞。
- 顯示: 單詞以隱藏形式(如下劃線
_
)展示給玩家,非字母字符(如空格、連字符)直接顯示。 - 猜測: 玩家通過點擊虛擬鍵盤上的字母進行猜測。
- 反饋:
- 猜對: 單詞中所有對應的下劃線被替換為正確的字母。
- 猜錯: 錯誤次數增加,并在畫布上繪制“小人”的一部分。猜錯的字母會被記錄并顯示。
- 勝負條件:
- 勝利: 在錯誤次數達到上限前,猜出所有字母。
- 失敗: 錯誤次數達到上限(通常是6次,對應小人的6個部分),游戲結束,顯示答案。
- 重玩: 提供“新游戲”功能。
界面布局規劃 (BorderPane
):
- 頂部 (Top): 使用
VBox
放置單詞分類選擇ComboBox
和一個主要的Label
(statusLabel
) 用于顯示游戲提示信息。 - 左側 (Left): 使用
VBox
放置Canvas
(hangmanCanvas
) 用于繪制絞刑架和小人。 - 中部 (Center): 使用
VBox
放置核心信息:隱藏的單詞 (wordLabel
)、錯誤次數統計 (errorsLabel
)、猜錯的字母列表 (wrongGuessesLabel
) 以及“新游戲”按鈕 (newGameButton
)。 - 底部 (Bottom): 使用
TilePane
(keyboardPane
) 自動排列 A-Z 的字母按鈕,形成虛擬鍵盤。
二、 JavaFX 實現深度剖析
現在,讓我們深入代碼,逐一解析關鍵技術的實現。
1. 項目基礎與狀態管理
我們創建 HangmanGameFX_EN
類繼承自 Application
。核心的游戲狀態由以下成員變量維護:
private Map<String, List<String>> wordCategories = new HashMap<>(); // 單詞庫
private String currentCategory = "Animals"; // 當前分類
private String secretWord; // 秘密單詞 (大寫)
private StringBuilder displayedWord; // 顯示給玩家的單詞 (帶下劃線)
private int errors; // 當前錯誤次數
private Set<Character> guessedLetters; // 已猜字母集合 (高效去重)
private boolean gameOver; // 游戲結束標志
// ... UI 元素引用 ...
private Map<Character, Button> keyboardButtons = new HashMap<>(); // 鍵盤按鈕引用
wordCategories
: 使用Map
存儲分類和對應的List<String>
,結構清晰。displayedWord
: 使用StringBuilder
而非String
,因為需要頻繁修改其中的字符(替換下劃線),StringBuilder
性能更好。guessedLetters
: 使用Set<Character>
存儲已猜字母,利用Set
的特性可以快速判斷一個字母是否已被猜過(contains
操作效率高)。keyboardButtons
: 使用Map<Character, Button>
存儲虛擬鍵盤上每個字母按鈕的引用,鍵是字母本身,值是對應的Button
對象。這使得我們可以通過字母快速找到并禁用對應的按鈕。
2. 構建動態用戶界面
UI 的構建被拆分到各個 create...Pane()
方法中。
- 分類選擇 (
createTopPane
):ComboBox<String>
控件綁定wordCategories
的鍵集。通過setOnAction
監聽選擇變化,一旦改變,就更新currentCategory
并調用initializeGame()
開始基于新分類的游戲。 - 單詞顯示 (
createCenterPane
):wordLabel
使用等寬字體 (Monospaced
),確保每個下劃線_
占用的寬度一致,視覺效果更好。 - 虛擬鍵盤 (
createKeyboardPane
):- 使用
TilePane
是個巧妙的選擇。它會自動將子節點(按鈕)排列成網格狀,只需設置setPrefColumns
(期望的列數)和間距 (setHgap
,setVgap
),布局非常方便。 - 循環創建 A-Z 按鈕,為每個按鈕設置
setOnAction
,調用handleGuess(letter)
并傳入對應的字母。同時,將按鈕存入keyboardButtons
Map。
- 使用
3. Canvas 繪圖:動態的絞刑架
這是游戲最具視覺特色的部分。
- 設置 (
createHangmanPane
): 創建Canvas
并獲取其GraphicsContext
(gc
)。設置線條寬度和顏色。 - 繪制絞刑架 (
drawGallows
): 在游戲初始化時調用,使用gc.strokeLine()
繪制幾條直線構成基本的絞刑架結構。坐標計算基于CANVAS_WIDTH
和CANVAS_HEIGHT
的比例,使得圖形能適應畫布大小。 - 繪制小人 (
drawHangmanPart
): 這個方法根據傳入的errorCount
(1-6) 繪制小人的一個新部分(頭、身體、四肢)。使用switch
語句,每個case
對應一個錯誤階段,調用gc.strokeOval()
畫頭,gc.strokeLine()
畫身體和四肢。坐標同樣是相對計算的。 - 清空畫布 (
clearCanvas
): 在每次開始新游戲時,需要調用gc.clearRect()
清除上一次繪制的內容。
// 在 drawHangmanPart(int errorCount) 中
gc.setStroke(HANGMAN_COLOR); // 確保顏色正確
switch (errorCount) {case 1: // Headgc.strokeOval(headX - headRadius, headY - headRadius, headRadius * 2, headRadius * 2);break;case 2: // Bodygc.strokeLine(headX, bodyStartY, headX, bodyEndY);break;// ... case 3, 4, 5, 6 for arms and legs ...
}
4. 核心游戲邏輯:處理猜測 (handleGuess
)
這是響應玩家點擊鍵盤按鈕的核心方法,邏輯嚴謹性至關重要:
private void handleGuess(char letter) {// 1. 狀態檢查: 游戲是否結束?字母是否已猜過?if (gameOver) return;letter = Character.toUpperCase(letter); // 統一轉大寫if (guessedLetters.contains(letter)) {// ... (提示已猜過) ...return;}// 2. 更新狀態: 添加到已猜集合,禁用對應按鈕guessedLetters.add(letter);keyboardButtons.get(letter).setDisable(true);// 3. 檢查猜測是否正確boolean found = false;for (int i = 0; i < secretWord.length(); i++) {if (secretWord.charAt(i) == letter) {// 關鍵:更新 displayedWord 中對應位置的下劃線int displayIndex = i * 2; // 乘以2是因為每個字符后有空格if (displayIndex < displayedWord.length()) {displayedWord.setCharAt(displayIndex, letter);}found = true;}}// 4. 更新單詞顯示 UIwordLabel.setText(displayedWord.toString());// 5. 根據猜測結果更新狀態和反饋if (found) {// ... (提示猜對,更新狀態標簽顏色) ...if (checkWin()) endGame(true); // 檢查是否勝利} else {errors++;// ... (更新錯誤標簽,繪制小人部分,更新猜錯字母列表,提示猜錯) ...if (checkLoss()) endGame(false); // 檢查是否失敗}
}
關鍵點解析:
- 狀態優先檢查: 首先判斷游戲是否結束以及字母是否已猜,避免無效操作。
- 狀態更新原子性: 將字母加入
guessedLetters
和禁用按鈕緊密關聯。 displayedWord
更新: 遍歷secretWord
,找到匹配字母后,計算其在displayedWord
中的正確索引(考慮到空格,是i * 2
)并替換下劃線。這是保證單詞正確顯示的核心。- 分支處理: 清晰地分為
found
(猜對) 和else
(猜錯) 兩個分支,分別處理 UI 反饋、狀態更新(errors++
)和勝負檢查。
5. 勝負判斷與游戲結束
checkWin()
: 實現非常簡潔,只要displayedWord
中不再包含_
,就意味著所有字母都已猜出,玩家獲勝。checkLoss()
: 同樣簡單,只要errors
達到MAX_ERRORS
,玩家失敗。endGame(boolean won)
: 負責游戲結束時的收尾工作:設置gameOver
標志,禁用所有鍵盤按鈕和分類選擇,并根據won
參數顯示最終的勝利或失敗信息(失敗時揭示答案)。
6. 游戲初始化與重置 (initializeGame
)
提供良好的“再來一局”體驗很重要。initializeGame
方法做了所有必要的重置工作:
- 重置錯誤次數、已猜字母集合、
gameOver
標志。 - 調用
chooseNewWord()
獲取新單詞。 - 重新生成帶下劃線的
displayedWord
。 - 重置所有 UI 元素到初始狀態(標簽文本、畫布、鍵盤按鈕可用性)。
三、 總結與展望
通過這個 Hangman 項目,我們不僅實現了一個經典游戲,更重要的是,我們深入實踐了 JavaFX 的許多核心功能:
- 布局系統:
BorderPane
,VBox
,HBox
,TilePane
的組合使用。 - 控件交互:
Button
,Label
,ComboBox
的事件處理和狀態更新。 - 自定義繪圖: 利用
Canvas
和GraphicsContext
實現動態圖形繪制。 - 狀態管理: 通過成員變量和枚舉(雖然此版本未使用枚舉,但概念相通)清晰地管理游戲進程。
- 數據結構應用:
Map
用于單詞庫和按鈕引用,Set
用于高效存儲已猜字母,StringBuilder
用于高效構建顯示單詞。
這個項目也為進一步探索留下了空間:
- 美化與主題: 應用 CSS 打造更個性化的外觀。
- 動畫效果: 為小人繪制、字母顯示等添加過渡動畫。
- 音效反饋: 增加點擊、猜對、猜錯、游戲結束的音效。
- 單詞庫管理: 從文件加載單詞,甚至允許用戶添加自定義單詞或分類。
- 更智能的提示: 例如提供一個“提示”按鈕,隨機顯示一個未猜中的字母(并計為一次錯誤)。
希望這篇詳細的開發日志能幫助你理解使用 JavaFX 構建交互式應用的具體過程,并激發你動手嘗試和創造的熱情。Happy coding!
附注: 上述代碼片段是說明性的,完整的可運行代碼文前資源文件中的java完整示例。確保你的開發環境已正確配置 JavaFX。