本教程旨在解決Java多米諾骨牌記憶游戲中,匹配成功的多米諾骨牌無法保持揭示狀態,且游戲無法正常結束的問題。核心解決方案包括:正確重寫 Domino 類的 equals() 和 hashCode() 方法以實現基于內容的比較,而非內存地址比較;以及在 MemoryLane 類的 guess() 方法中調用 setRevealed() 方法來更新多米諾骨牌的顯示狀態。通過這些修正,游戲將能夠正確識別匹配,并按照預期邏輯結束。
問題分析:多米諾骨牌未正確揭示與游戲未結束的原因
在提供的多米諾骨牌記憶游戲代碼中,存在兩個主要問題導致游戲功能不完整:
- 多米諾骨牌匹配判斷不準確: MemoryLane 類中的 guess 方法使用 board[i] == board[k] 來判斷兩個多米諾骨牌是否匹配。在 Java 中,對于對象類型,== 運算符比較的是兩個引用是否指向內存中的同一個對象實例,而不是它們的內容是否相等。因此,即使兩個多米諾骨牌具有相同的 top 和 bottom 值,如果它們是不同的對象實例,== 也會返回 false。這導致了即使玩家猜對了,多米諾骨牌也無法被正確識別為匹配。
- 多米諾骨牌狀態未更新: Domino 類中包含 setRevealed(Boolean revealed) 方法用于設置多米諾骨牌的揭示狀態,但在 MemoryLane 類的 guess 方法中,當匹配成功時,并未調用此方法來將匹配的多米諾骨牌設置為已揭示。這使得 isRevealed() 方法始終返回 false(因為 revealed 成員變量的默認值為 false),從而導致 MemoryLane 的 gameOver() 方法無法正確判斷所有多米諾骨牌是否都被揭示,進而游戲無法結束。
解決方案一:正確實現對象比較——重寫 equals() 和 hashCode()
為了使 Domino 對象能夠基于其內容(即 top 和 bottom 值)進行比較,我們需要在 Domino 類中重寫 equals() 方法。同時,根據 Java 規范,如果重寫了 equals() 方法,也必須重寫 hashCode() 方法,以維護它們之間的契約:如果兩個對象通過 equals() 方法比較是相等的,那么它們的 hashCode() 方法必須產生相同的整數結果。
以下是 Domino 類中 equals() 和 hashCode() 方法的正確實現:
public class Domino { private int top, bottom; private boolean revealed; public Domino(int x, int y) { if (x > y) { top = y; bottom = x; } else { top = x; bottom = y; } } public int getTop() { return top; } public int getBottom() { return bottom; } public boolean isRevealed() { return revealed; // 簡化了原有的 if-else 結構 } public void setRevealed(boolean revealed) { this.revealed = revealed; } @Override public int hashCode() { int hash = 7; hash = 59 * hash + this.getTop(); hash = 59 * hash + this.getBottom(); return hash; } @Override public boolean equals(Object obj) { if (this == obj) { // 檢查是否是同一個對象實例 return true; } if (obj == NULL || getClass() != obj.getClass()) { // 檢查是否為null或類型不匹配 return false; } final Domino other = (Domino) obj; // 類型轉換 if (this.getTop() != other.getTop()) { // 比較 top 值 return false; } if (this.getBottom() != other.getBottom()) { // 比較 bottom 值 return false; } return true; // 如果 top 和 bottom 都相等,則認為對象相等 } }
說明:
- equals(Object obj):
- 首先檢查是否為同一個對象引用(this == obj),如果是則直接返回 true。
- 然后檢查傳入對象是否為 null 或類型不匹配(getClass() != obj.getClass()),如果是則返回 false。
- 將 obj 強制轉換為 Domino 類型。
- 最后,比較 top 和 bottom 屬性是否相等。
- hashCode():
- 使用 top 和 bottom 屬性計算哈希值。一種常見的做法是使用一個質數(如59)乘以當前哈希值并加上字段值,以分散哈希碼并減少碰撞。
解決方案二:確保匹配多米諾骨牌保持揭示狀態
在 MemoryLane 類的 guess 方法中,當兩個多米諾骨牌被正確匹配時,需要調用它們的 setRevealed(true) 方法,將它們的狀態設置為已揭示。
立即學習“Java免費學習筆記(深入)”;
以下是 MemoryLane 類中 guess 方法的修正:
import java.util.Arrays; import java.util.Random; public class MemoryLane { private Domino[] board; public MemoryLane(int max) { board = new Domino[(max * max) + max]; int i = 0; for (int top = 1; top <= max; top++) { for (int bot = 1; bot <= max; bot++) { if (top <= bot) { board[i] = new Domino(top, bot); i++; board[i] = new Domino(top, bot); i++; } } } shuffle(); } private void shuffle() { int index; Random random = new Random(); for (int i = board.length - 1; i > 0; i--) { index = random.nextInt(i + 1); if (index != i) { Domino temp = board[index]; board[index] = board[i]; board[i] = temp; } } } public boolean guess(int i, int k) { // 使用重寫后的 equals 方法進行內容比較 if (board[i].equals(board[k])) { board[i].setRevealed(true); // 設置第一個多米諾骨牌為已揭示 board[k].setRevealed(true); // 設置第二個多米諾骨牌為已揭示 return true; } return false; } public String peek(int a, int b) { String text = ""; // 使用空字符串初始化,避免 String new String() text += ("[" + board[a].getTop() + "] [" + board[b].getTop() + "]n"); text += ("[" + board[a].getBottom() + "] [" + board[b].getBottom() + "]n"); return text; } public boolean gameOver() { int count = 0; for (int i = 0; i < board.length; i++) { if (board[i].isRevealed()) { count++; } } return (count == board.length); // 當所有多米諾骨牌都已揭示時,游戲結束 } // 調試方法,顯示所有多米諾骨牌的真實值(可選,原答案中包含) public String debug() { String text = ""; for (int i = 0; i < board.length; i++) { text += ("[" + board[i].getTop() + "] "); } text += ('n'); for (int i = 0; i < board.length; i++) { text += ("[" + board[i].getBottom() + "] "); } return text; } @Override public String toString() { String text = ""; for (int i = 0; i < board.length; i++) { if (board[i].isRevealed()) { text += ("[" + board[i].getTop() + "] "); } else { text += ("[ ] "); } } text += ('n'); for (int i = 0; i < board.length; i++) { if (board[i].isRevealed()) { text += ("[" + board[i].getBottom() + "] "); } else { text += ("[ ] "); } } return text; } }
說明:
- guess(int i, int k): 現在使用 board[i].equals(board[k]) 進行比較,這將正確判斷兩個多米諾骨牌的內容是否相等。
- 如果判斷為匹配成功,則 board[i].setRevealed(true) 和 board[k].setRevealed(true) 將被調用,從而更新這些多米諾骨牌的 revealed 狀態。
- gameOver() 方法現在能夠正確統計已揭示的多米諾骨牌數量,并在所有多米諾骨牌都被揭示時返回 true,從而使游戲正常結束。
注意事項與總結
- equals() 與 hashCode() 契約: 再次強調,當您在 Java 中重寫 equals() 方法時,務必同時重寫 hashCode() 方法。這是 Java 集合框架(如 HashMap、HashSet)正確工作的基礎,否則可能導致對象在集合中行為異常。
- 對象比較的正確性: 理解 == 運算符和 equals() 方法在對象比較上的區別至關重要。== 比較引用地址,而 equals() 比較對象內容(如果被正確重寫)。
- 代碼可讀性與維護: 在 Domino 類的 isRevealed() 方法中,將 if (revealed) return true; return false; 簡化為 return revealed; 可以提高代碼的簡潔性。同樣,在 String text = new String(); 處,直接使用 String text = “”; 更為簡潔高效。
- 驅動類不變性: 由于 MemoryLaneDriver 類被設定為不可修改,所有的修正都必須集中在 Domino 和 MemoryLane 這兩個核心業務邏輯類中,這正是本教程所遵循的原則。
通過以上修正,您的多米諾骨牌記憶游戲將能夠正確地識別匹配、揭示多米諾骨牌,并最終判斷游戲勝利,提供完整的游戲體驗。