
问题分析:多米诺骨牌未正确揭示与游戏未结束的原因
在提供的多米诺骨牌记忆游戏代码中,存在两个主要问题导致游戏功能不完整:
- 多米诺骨牌匹配判断不准确: 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 这两个核心业务逻辑类中,这正是本教程所遵循的原则。
通过以上修正,您的多米诺骨牌记忆游戏将能够正确地识别匹配、揭示多米诺骨牌,并最终判断游戏胜利,提供完整的游戏体验。










