Java記憶游戲:深入理解對象相等性與游戲狀態管理

Java記憶游戲:深入理解對象相等性與游戲狀態管理

本文深入探討了Java多米諾記憶游戲開發中常見的兩個關鍵問題:對象比較不當導致的多米諾牌無法正確匹配,以及游戲狀態(多米諾牌揭示狀態)未及時更新導致游戲無法結束。通過詳細解析 equals() 和 hashCode() 方法的正確覆寫,以及在游戲邏輯中有效管理對象狀態,本教程旨在幫助開發者構建功能完善、邏輯嚴謹的Java記憶游戲。

1. 問題分析與根源

在開發基于對象的記憶游戲時,兩個常見的問題可能導致游戲行為異常:

  • 對象比較不準確: 當需要判斷兩個游戲元素(如多米諾牌)是否“相等”時,如果僅僅使用 == 運算符,它會比較對象的內存地址,而非其內部屬性值。這導致即使兩張多米諾牌的數字相同,程序也可能認為它們不匹配。此外,即使嘗試使用 equals() 方法,如果該方法在自定義類中未被正確覆寫,其默認行為通常與 == 相同,即比較引用。
  • 游戲狀態未更新: 游戲元素的內部狀態(例如多米諾牌是否已被揭示)未能根據游戲規則及時更新。這會導致即使玩家猜對了,多米諾牌也不會保持翻開狀態,進而影響游戲結束條件的判斷。

針對上述問題,我們將以一個Java多米諾記憶游戲為例,詳細講解如何通過覆寫 equals() 和 hashCode() 方法以及正確管理對象狀態來解決這些問題。

2. 解決對象比較問題:覆寫 equals() 和 hashCode()

在Java中,當我們需要根據對象的實際內容(而非內存地址)來判斷它們是否相等時,必須在自定義類中覆寫 Object 類的 equals() 方法。同時,為了遵循Java約定和確保集合類(如 HashMap, HashSet)的正確行為,當覆寫 equals() 時,也必須覆寫 hashCode() 方法。

2.1 覆寫 Domino.equals() 方法

原始的 Domino 類中的 equals() 方法存在邏輯錯誤:它只檢查 top 和 bottom 是否相等,這導致只有雙面牌(如 [2][2])才會被認為是相等的,而不同位置但值相同的牌則無法匹配。

public boolean equals(Domino other) { // 錯誤的覆寫方式     if (top == bottom) // 錯誤:只檢查自身是否為雙面牌         return true;     return false; }

正確的 equals() 覆寫應該比較當前 Domino 對象與傳入的 Object 對象(在類型轉換后)的 top 和 bottom 屬性。

立即學習Java免費學習筆記(深入)”;

@Override public boolean equals(Object obj) {     // 1. 檢查是否是同一個對象的引用     if (this == obj) {         return true;     }     // 2. 檢查傳入對象是否為null或類型不匹配     if (obj == null || getClass() != obj.getClass()) { // 或者使用 !(obj instanceof Domino)         return false;     }     // 3. 類型轉換     final Domino other = (Domino) obj;     // 4. 比較關鍵屬性     if (this.getTop() != other.getTop()) {         return false;     }     if (this.getBottom() != other.getBottom()) {         return false;     }     return true; }

注意事項:

  • @Override 注解是可選的,但強烈建議使用,它能幫助編譯器檢查你是否正確覆寫了父類方法。
  • equals() 方法的參數類型必須是 Object,否則它不是真正的覆寫,而是一個重載方法。
  • 在比較屬性時,考慮到 Domino 構造函數已經保證了 top

2.2 覆寫 Domino.hashCode() 方法

根據Java約定,如果兩個對象通過 equals() 方法判斷為相等,那么它們的 hashCode() 方法必須返回相同的值。覆寫 hashCode() 的目的是為相等的對象生成一致的哈希碼,這對于基于哈希的集合(如 HashMap, HashSet)的正確運行至關重要。

@Override public int hashCode() {     int hash = 7; // 任意一個非零常數     hash = 59 * hash + this.getTop(); // 將屬性值納入哈希計算     hash = 59 * hash + this.getBottom();     return hash; }

注意事項:

  • 哈希碼的計算應基于參與 equals() 比較的所有屬性。
  • 使用質數(如59)進行乘法運算有助于減少哈希沖突。
  • 一個好的 hashCode() 實現應盡可能地將不相等的對象分散到不同的哈希桶中。

3. 解決游戲狀態更新問題:更新 guess() 方法

多米諾牌在匹配成功后未能保持揭示狀態,是因為 Domino 對象的 revealed 屬性沒有被設置為 true。這個狀態更新的邏輯應該發生在 MemoryLane 類的 guess() 方法中,當判斷兩張牌匹配成功時。

原始的 guess() 方法:

public boolean guess(int i, int k) {     if(board[i] == board[k]) { // 錯誤:使用 == 比較對象引用         return true;     }     return false; }

修正后的 guess() 方法不僅要使用 equals() 進行對象內容比較,還要在匹配成功后調用 setRevealed(true) 來更新多米諾牌的狀態。

public boolean guess(int i, int k) {     // 使用覆寫后的 equals 方法進行內容比較     if (board[i].equals(board[k])) {         // 如果匹配成功,設置這兩張多米諾牌為已揭示狀態         board[i].setRevealed(true);         board[k].setRevealed(true);         return true;     }     // 如果不匹配,不需要做任何狀態改變,直接返回false     return false; }

4. 簡化 isRevealed() 方法

在 Domino 類中,isRevealed() 方法可以簡化,因為它本質上只是返回 revealed 字段的值。

原始的 isRevealed():

public boolean isRevealed() {     if (revealed)         return true;     return false; }

簡化后的 isRevealed():

public boolean isRevealed() {     return revealed; }

5. 完整代碼示例(修正后的關鍵類)

以下是經過上述修正后的 Domino 類和 MemoryLane 類中 guess 方法的關鍵代碼片段。MemoryLaneDriver 類保持不變,因為它已經能夠正確調用游戲邏輯。

5.1 修正后的 Domino 類

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; // 簡化后的方法     }      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()) {             return false;         }         final Domino other = (Domino) obj;         if (this.getTop() != other.getTop()) {             return false;         }         if (this.getBottom() != other.getBottom()) {             return false;         }         return true;     } }

5.2 修正后的 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;             }         }     }      // 修正后的 guess 方法     public boolean guess(int i, int k) {         if (board[i].equals(board[k])) { // 使用覆寫后的 equals 方法             board[i].setRevealed(true);  // 設置為已揭示             board[k].setRevealed(true);  // 設置為已揭示             return true;         }         return false;     }      public String peek(int a, int b) {         String text = ""; // 可以直接初始化為空字符串         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 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;     } }

6. 總結

通過本次教程,我們深入理解了在Java中進行對象比較和狀態管理的重要性。關鍵點包括:

  • 正確覆寫 equals() 和 hashCode(): 這是實現基于對象內容比較的基礎。務必記住,當覆寫 equals() 時,必須同時覆寫 hashCode(),以確保程序行為的正確性和一致性,尤其是在使用集合類時。
  • 及時更新對象狀態: 游戲邏輯中,當滿足特定條件時,必須顯式地更新相關對象的內部狀態(例如通過調用 setRevealed(true))。這是確保游戲流程正確推進和結束條件能夠被正確判斷的關鍵。

遵循這些原則,開發者可以構建出功能更健壯、邏輯更清晰的面向對象程序,特別是在游戲開發等需要精細狀態管理和對象比較的場景中。

? 版權聲明
THE END
喜歡就支持一下吧
點贊11 分享