本教程旨在解决Java多米诺记忆游戏中常见的逻辑问题包括多米诺卡不能正确显示为已揭示状态和游戏不能正常结束。核心解决方案包括equals()和hashCode()以正确覆盖Java对象以实现值比较并确保在正确猜测时使用setrevealed()更新多米诺卡的状态。通过这些改进游戏将能够正确识别匹配并根据卡片状态判断游戏的胜利条件。1. 问题分析在开发基于java的多米诺记忆游戏时开发者可能会遇到以下两个主要问题多米诺牌匹配后未能保持揭示状态 即使玩家猜对了匹配的多米诺牌这些牌在后续的游戏界面刷新中仍然显示为未揭示状态。游戏不能正常结束 即使所有的多米诺牌都被正确匹配并且应该被揭示游戏也无法检测到胜利条件并终止。这些问题的根源主要在于Java对象的误用和对象状态管理的不完善。1.1 对象比较的误区 与 equals()在Java中操作符用于比较基本数据类型的值和对象的引用地址。当用于对象时board[i] board[k]事实上检查board[i]和board[k]这两个参考是否指向内存中相同对象的例子。然而在记忆游戏中我们希望比较两张多米诺卡的“值”是否相等即它们的顶部和底部数字是否相同而不是它们是否相同。虽然原始Domino类中有一个equals(Domino other)但实际上存在逻辑错误if (top bottom) return true;而且在memorylane的guess方法中这种自定义的equals方法并没有调用而是使用了board[i] board[k]引用比较。因此即使两张多米诺牌的价值相同它们也不会被视为匹配因为它们是不同对象的例子。1.2 缺乏状态管理setRevealed() 未被调用domino类定义了revealed布尔变量和setreveleded(boolean revealed)该方法旨在标记多米诺卡是否已被揭示。然而在memorylane的guess方法中即使判断是匹配的尽管判断是错误的也没有调用setrevealed(true)更新相应多米诺品牌的revealed状态。这意味着多米诺品牌的披露状态从未实际改变导致它们在tostring方法渲染时总是显示为未披露。立即学习“Java免费学习笔记(深入)1.3 在游戏结束时判断错误的条件memorylane中的gameover()方法通过通过通过通过通过board数组检查所有多米诺卡的isrevealed()状态来判断游戏是否结束。因为setrevealed()方法从未被调用过isRevealed()总是返回false导致count变量永远达不到board.length所以游戏永远不会结束。2. 解决方案和实现要解决上述问题我们需要关键修改Domino类和MemoryLane类。2.1 正确覆盖 Domino 类的 equals() 和 hashCode() 方法在Java中当需要比较两个对象的逻辑相等性(即它们的值是否相等而不是引用地址)时必须覆盖Object提供的equals()方法。同时根据Java规范如果覆盖equals()方法也必须覆盖hashcode()方法以维持equals()和hashcode()之间的合同。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() { // 简化isrevealed方法直接返回revealed状态 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; } // 检查obj是否与nulll或类型不匹配 if (!(obj instanceof Domino)) { return false; } // 将obj强制转换为Domino类型 final Domino other (Domino) obj; // 比较top和bottom属性是否相等 if (this.getTop() !(obj instanceof Domino)) { return false; } // 将obj强制转换为Domino类型 final Domino other (Domino) obj; // 比较top和bottom属性是否相等 if (this.getTop() ! other.getTop()) { return false; } if (this.getBottom() ! other.getBottom()) { return false; } return true; } }equals() 和 hashCode() 合同的重要性如果两个对象是根据equalss(Object)方法是相等的所以调用这两个对象中任何一个的hashCode()方法都必须产生相同的整数结果。如果两个对象是根据equalss(Object)方法不相等因此调用这两个对象中任何一个的hashCode()方法不需要产生不同的整数结果。然而为不同的对象生成不同的哈希码可以改进哈希表(如hashMap)、HashSet的性能。2.2 更新 MemoryLane 类的 guess() 方法现在Domino对象可以正确比较值。接下来我们需要修改Memorylane中的guess()方法比较Domino中新覆盖的equals()方法并在匹配成功时更新多米诺卡的披露状态。MemoryLane 类修改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) { // 使用Dominoequals法进行值比较 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 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 toString() { String text new String(); 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; } }3. 完整的示例代码为了提供一个可操作的完整示例我们组织了Domino和MemoryLane作为内部或单独的文件。这里有一个例子将它们作为包含在Main类中的内部类别以便于测试。import java.util.Random; import java.util.Scanner; public class Main { public static void main(String[] args) { new Main().runGame(); } public void runGame() { String message Welcome to Memory Lane! \n Choose two indexes to reveal the corresponding dominoes. \n If the dominoes match, they stay revealed. \n Reveal all the dominoes to win the game! \n; System.out.println(message); Scanner input new Scanner(System.in); // 使用max2生成(2*2)2 6张多米诺牌 MemoryLane game new MemoryLane(2); long start System.currentTimeMillis(); while (!game.gameOver()) { // 打印当前游戏板状态 System.out.println(game); System.out.print(First: ); int first input.nextInt(); System.out.print(Second: ); int second input.nextInt(); // 检查用户输入的索引是否有效 if (first 0 || first game.board.length || second 0 || second game.board.length) { System.out.println(Invalid index. Please enter numbers between 0 and (game.board.length - 1)); continue; // 跳过这个循环重新输入 } // 尝试猜测 boolean isMatch game.guess(first, second); System.out.println(game.peek(first, second) \n); // 显示所选卡片 if (isMatch) { System.out.println(Its a match! These dominoes will stay revealed.); } else { System.out.println(No match. Try again! These dominoes will stay revealed.); } else { System.out.println(No match. Try again!); // 对不匹配的情况需要重新隐藏卡片。 // 这一需求在原始问题中没有提及但在不匹配的情况下记忆游戏通常会隐藏牌。 // 这里不自动隐藏以保持与原代码逻辑一致。 // 如需隐藏可在此添加 board[i].setRevealed(false); board[k].setRevealed(false); // 但是需要修改peek方法使其临时揭示然后根据是否匹配来决定是否保持揭示。 // 鉴于不允许修改驱动程序我们保持现有行为。 } } long stop System.currentTimeMillis(); long elapsed (stop - start) / 1000; System.out.println(game); // 游戏结束时显示最终状态 System.out.println(\nYou win!); System.out.println(Total time: elapsed s); input.close(); } // Domino Class 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; // 确保比较多米诺牌的top和botom值 if (this.getTop() ! obj.getClass()) { // 类型检查比较严格 return false; } final Domino other (Domino) obj; // 确保比较多米诺牌的top和botom值 if (this.getTop() ! other.getTop()) { return false; } if (this.getBottom() ! other.getBottom()) { return false; } return true; } } // MemoryLane Class public class MemoryLane { private Domino[] board; public MemoryLane(int max) { board new Domino[(max * max) max]; // 例如当max2时数组的大小为6 int i 0; // 生成多米诺牌对 for (int top 1; top max; top) { for (int bot 1; bot max; bot) { if (top bot) { // 避免重复如(1、2)和(2、1) 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); // 交换元素 Domino temp board[index]; board[index] board[i]; board[i] temp; } } public boolean guess(int i, int k) { // 确保索引有效且不同 if (i k || i 0 || i board.length || k 0 || k board.length) { return false; // 无效猜测 } // 核心修复:使用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 ; if (a 0 a board.length b 0 b board.length) { text ([ board[a].getTop() ] [ board[b].getTop() ]\n); text ([ board[a].getBottom() ] [ board[b].getBottom() ]\n); } else { text Invalid peek indices.\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() , board[i].getBottom() ] ); } text (\n); 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; } } }4. 注意事项及总结equals() 和 hashCode() 的重要性 对于自定义类当需要比较对象的逻辑内容而不是内存地址时必须正确覆盖equals方法。同时为了确保哈希集合如hashset、Hashmap键中的正常工作必须同时覆盖hashcode()方法并遵循其合同。这是Java面向对象编程的基本而重要的原则。对象状态管理 明确对象应该如何响应外部操作改变自己的状态。在这种情况下当多米诺卡成功匹配时其revealed状态必须显式更新才能反映在游戏界面上影响游戏结束条件。游戏逻辑的完整性 确保游戏的胜利或失败条件能够根据正确的状态来判断。当所有关键状态(如revealed)都得到正确维护时gameOver()等方法可以准确反映游戏过程。代码可读性和维护性 良好的命名、清晰的逻辑和适当的注释有助于提高代码的可读性和未来的维护性。通过以上修改多米诺记忆游戏将能够正确识别匹配保持揭示状态并在所有卡被揭示后正常结束。这不仅解决了功能问题而且加强了对Java对象比较和状态管理核心概念的理解。
Java多米诺记忆游戏逻辑修复与对象比较深度解析
本教程旨在解决Java多米诺记忆游戏中常见的逻辑问题包括多米诺卡不能正确显示为已揭示状态和游戏不能正常结束。核心解决方案包括equals()和hashCode()以正确覆盖Java对象以实现值比较并确保在正确猜测时使用setrevealed()更新多米诺卡的状态。通过这些改进游戏将能够正确识别匹配并根据卡片状态判断游戏的胜利条件。1. 问题分析在开发基于java的多米诺记忆游戏时开发者可能会遇到以下两个主要问题多米诺牌匹配后未能保持揭示状态 即使玩家猜对了匹配的多米诺牌这些牌在后续的游戏界面刷新中仍然显示为未揭示状态。游戏不能正常结束 即使所有的多米诺牌都被正确匹配并且应该被揭示游戏也无法检测到胜利条件并终止。这些问题的根源主要在于Java对象的误用和对象状态管理的不完善。1.1 对象比较的误区 与 equals()在Java中操作符用于比较基本数据类型的值和对象的引用地址。当用于对象时board[i] board[k]事实上检查board[i]和board[k]这两个参考是否指向内存中相同对象的例子。然而在记忆游戏中我们希望比较两张多米诺卡的“值”是否相等即它们的顶部和底部数字是否相同而不是它们是否相同。虽然原始Domino类中有一个equals(Domino other)但实际上存在逻辑错误if (top bottom) return true;而且在memorylane的guess方法中这种自定义的equals方法并没有调用而是使用了board[i] board[k]引用比较。因此即使两张多米诺牌的价值相同它们也不会被视为匹配因为它们是不同对象的例子。1.2 缺乏状态管理setRevealed() 未被调用domino类定义了revealed布尔变量和setreveleded(boolean revealed)该方法旨在标记多米诺卡是否已被揭示。然而在memorylane的guess方法中即使判断是匹配的尽管判断是错误的也没有调用setrevealed(true)更新相应多米诺品牌的revealed状态。这意味着多米诺品牌的披露状态从未实际改变导致它们在tostring方法渲染时总是显示为未披露。立即学习“Java免费学习笔记(深入)1.3 在游戏结束时判断错误的条件memorylane中的gameover()方法通过通过通过通过通过board数组检查所有多米诺卡的isrevealed()状态来判断游戏是否结束。因为setrevealed()方法从未被调用过isRevealed()总是返回false导致count变量永远达不到board.length所以游戏永远不会结束。2. 解决方案和实现要解决上述问题我们需要关键修改Domino类和MemoryLane类。2.1 正确覆盖 Domino 类的 equals() 和 hashCode() 方法在Java中当需要比较两个对象的逻辑相等性(即它们的值是否相等而不是引用地址)时必须覆盖Object提供的equals()方法。同时根据Java规范如果覆盖equals()方法也必须覆盖hashcode()方法以维持equals()和hashcode()之间的合同。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() { // 简化isrevealed方法直接返回revealed状态 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; } // 检查obj是否与nulll或类型不匹配 if (!(obj instanceof Domino)) { return false; } // 将obj强制转换为Domino类型 final Domino other (Domino) obj; // 比较top和bottom属性是否相等 if (this.getTop() !(obj instanceof Domino)) { return false; } // 将obj强制转换为Domino类型 final Domino other (Domino) obj; // 比较top和bottom属性是否相等 if (this.getTop() ! other.getTop()) { return false; } if (this.getBottom() ! other.getBottom()) { return false; } return true; } }equals() 和 hashCode() 合同的重要性如果两个对象是根据equalss(Object)方法是相等的所以调用这两个对象中任何一个的hashCode()方法都必须产生相同的整数结果。如果两个对象是根据equalss(Object)方法不相等因此调用这两个对象中任何一个的hashCode()方法不需要产生不同的整数结果。然而为不同的对象生成不同的哈希码可以改进哈希表(如hashMap)、HashSet的性能。2.2 更新 MemoryLane 类的 guess() 方法现在Domino对象可以正确比较值。接下来我们需要修改Memorylane中的guess()方法比较Domino中新覆盖的equals()方法并在匹配成功时更新多米诺卡的披露状态。MemoryLane 类修改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) { // 使用Dominoequals法进行值比较 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 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 toString() { String text new String(); 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; } }3. 完整的示例代码为了提供一个可操作的完整示例我们组织了Domino和MemoryLane作为内部或单独的文件。这里有一个例子将它们作为包含在Main类中的内部类别以便于测试。import java.util.Random; import java.util.Scanner; public class Main { public static void main(String[] args) { new Main().runGame(); } public void runGame() { String message Welcome to Memory Lane! \n Choose two indexes to reveal the corresponding dominoes. \n If the dominoes match, they stay revealed. \n Reveal all the dominoes to win the game! \n; System.out.println(message); Scanner input new Scanner(System.in); // 使用max2生成(2*2)2 6张多米诺牌 MemoryLane game new MemoryLane(2); long start System.currentTimeMillis(); while (!game.gameOver()) { // 打印当前游戏板状态 System.out.println(game); System.out.print(First: ); int first input.nextInt(); System.out.print(Second: ); int second input.nextInt(); // 检查用户输入的索引是否有效 if (first 0 || first game.board.length || second 0 || second game.board.length) { System.out.println(Invalid index. Please enter numbers between 0 and (game.board.length - 1)); continue; // 跳过这个循环重新输入 } // 尝试猜测 boolean isMatch game.guess(first, second); System.out.println(game.peek(first, second) \n); // 显示所选卡片 if (isMatch) { System.out.println(Its a match! These dominoes will stay revealed.); } else { System.out.println(No match. Try again! These dominoes will stay revealed.); } else { System.out.println(No match. Try again!); // 对不匹配的情况需要重新隐藏卡片。 // 这一需求在原始问题中没有提及但在不匹配的情况下记忆游戏通常会隐藏牌。 // 这里不自动隐藏以保持与原代码逻辑一致。 // 如需隐藏可在此添加 board[i].setRevealed(false); board[k].setRevealed(false); // 但是需要修改peek方法使其临时揭示然后根据是否匹配来决定是否保持揭示。 // 鉴于不允许修改驱动程序我们保持现有行为。 } } long stop System.currentTimeMillis(); long elapsed (stop - start) / 1000; System.out.println(game); // 游戏结束时显示最终状态 System.out.println(\nYou win!); System.out.println(Total time: elapsed s); input.close(); } // Domino Class 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; // 确保比较多米诺牌的top和botom值 if (this.getTop() ! obj.getClass()) { // 类型检查比较严格 return false; } final Domino other (Domino) obj; // 确保比较多米诺牌的top和botom值 if (this.getTop() ! other.getTop()) { return false; } if (this.getBottom() ! other.getBottom()) { return false; } return true; } } // MemoryLane Class public class MemoryLane { private Domino[] board; public MemoryLane(int max) { board new Domino[(max * max) max]; // 例如当max2时数组的大小为6 int i 0; // 生成多米诺牌对 for (int top 1; top max; top) { for (int bot 1; bot max; bot) { if (top bot) { // 避免重复如(1、2)和(2、1) 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); // 交换元素 Domino temp board[index]; board[index] board[i]; board[i] temp; } } public boolean guess(int i, int k) { // 确保索引有效且不同 if (i k || i 0 || i board.length || k 0 || k board.length) { return false; // 无效猜测 } // 核心修复:使用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 ; if (a 0 a board.length b 0 b board.length) { text ([ board[a].getTop() ] [ board[b].getTop() ]\n); text ([ board[a].getBottom() ] [ board[b].getBottom() ]\n); } else { text Invalid peek indices.\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() , board[i].getBottom() ] ); } text (\n); 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; } } }4. 注意事项及总结equals() 和 hashCode() 的重要性 对于自定义类当需要比较对象的逻辑内容而不是内存地址时必须正确覆盖equals方法。同时为了确保哈希集合如hashset、Hashmap键中的正常工作必须同时覆盖hashcode()方法并遵循其合同。这是Java面向对象编程的基本而重要的原则。对象状态管理 明确对象应该如何响应外部操作改变自己的状态。在这种情况下当多米诺卡成功匹配时其revealed状态必须显式更新才能反映在游戏界面上影响游戏结束条件。游戏逻辑的完整性 确保游戏的胜利或失败条件能够根据正确的状态来判断。当所有关键状态(如revealed)都得到正确维护时gameOver()等方法可以准确反映游戏过程。代码可读性和维护性 良好的命名、清晰的逻辑和适当的注释有助于提高代码的可读性和未来的维护性。通过以上修改多米诺记忆游戏将能够正确识别匹配保持揭示状态并在所有卡被揭示后正常结束。这不仅解决了功能问题而且加强了对Java对象比较和状态管理核心概念的理解。