什么是存储玩家手中的纸牌的最佳数据结构?

8

我是Java的初学者,目前正在为Android创建一个像gin rummy这样的纸牌游戏。我想知道创建Hand类的最佳实现方式是什么?在哪里存储由Deck.dealt()返回的卡牌是最好的方式?

  1. 数组
  2. ArrayList
  3. Vector
  4. HashSet
  5. LinkedList

另外,如果有人能提供gin rummy开源链接,我将不胜感激。


2
我的建议是:学习集合之间的区别(例如,ArrayListLinkedList之间有什么区别?),自己尝试一些东西,然后再提出更具体的问题。关于集合之间的区别,您可以尝试参考每个集合的JavaDoc文档。 - Thomas
@Thomas - 我已经检查了上面的集合,所以我可以说我理解了它。我只是不能决定使用哪一个,因为阅读很多卡牌游戏开源,它们有不同的实现,所以我想知道在创建 Hand 类时使用什么是理想的。 - Zack
2
你应该能够自己决定。只需获取所有要求并选择最适合的数据结构即可。一些要求可能包括:是否允许多个相等的对象?是否需要排序?您知道最大卡数以及最大和平均卡数之间的比率吗?您从/向结构中读取的频率有多高?您需要有效的随机访问还是无论如何都会循环遍历所有条目?... - Thomas
2
一般来说(假设标准的52张牌组),这些数据结构都没有特别好的,因为对于一个最大只有52个元素的集合进行排序、迭代、添加和删除基本对象,无论是在空间还是时间上都不会显著增加开销(除非你陷入某种无限循环)。但我仍然建议使用任何动态数据结构进行练习。 - Brian
@Thomas - 为了我的问题,你会选择什么?如果你能给我一个答案我会很感激。 - Zack
如果你有标准的52张牌(即每种类型只有一张牌),我可能会选择使用LinkedHashSetTreeSet来获得集合语义和一些排序(具体取决于你想要哪种排序)。另外,一个映射表也可能是一个选项,例如Map<CardType, Card> - Thomas
6个回答

24
如果你真的想要理解集合类型之间的微妙差别,那么就请听我细讲。
除了在游戏Bohnanza中(这是有史以来最伟大的纸牌游戏之一,但我会让它结束), List 在技术上并不合适。 List 表示,如果一个手牌包含梅花A和K,而另一个手牌包含梅花K和A,那么这两个手牌基本上不是相同的手牌。这比仅仅“我想记住用户希望看到他们的卡片的顺序”要强烈得多,后者是许多非 List 集合都具有的特性,如 LinkedHashSet 和 Guava 的 ImmutableSet。 List 还意味着索引 N 上的卡牌有某种特殊的意义。 我所知道的没有一款纸牌游戏符合这个条件。 Set 通常不适用于纸牌游戏--只用于使用单副完全独特的卡牌的游戏。
为了允许重复但仍然具有独立于顺序的相等性,使用的类型是 Guava 的 Multiset。例如,HashMultisetImmutableMultiset。请注意,大多数 multiset 实现通过仅存储卡牌和计数来表示多个“相等”卡牌,因此在对它们进行迭代时,您手中重复的卡牌必须始终一起出现。如果让用户自由控制手中卡牌的顺序很重要,则需要使用 LinkedListMultiset
现在上课结束了...好吧,说实话。调用 myHand.equals(yourHand) ,或使用整个手牌作为 Map 中的键实际上你永远不会这样做...所以放心地使用 ArrayList,你会没问题的。 :-)

我不同意使用List就表示两个顺序不同的牌手是不同的。只有当你将Hand.equals()实现传递到List.equals()时才是真的。但我不会这样做。如果我要将Hand放入HashMap中(例如),我希望包含相同卡牌且顺序相同的两个不同的牌手仍被视为不同的牌手,并从对Hand.equals()的调用中返回false - Erick Robertson
是的,你的想法更接近于实际生活中的做法。 - Kevin Bourrillion

3

我认为一个好的想法是使用接口(List用于元素有序,Set用于元素无序)。你可以选择你喜欢的实现方式,例如:

List<Card> deck = new ArrayList<Card>();

或者

Set<Card> deck = new HashSet<Card>();

3
Zack正在询问的部分是他是否应该使用有序列表或无序集合。 - Erick Robertson

1

将它们存储在一个 ArrayList 中。

手牌中的卡牌是按照一定顺序排列的,而不是无序的堆叠。这种排序可以通过 List 而不是 Set 来保留。

ArrayList 还可以让您通过索引选择特定的卡牌,在实现游戏时非常有用。

请记住,只要您正确设计了 Hand 类,您就可以随时轻松地更改此数据结构。只要您在设计任何类时记住这一点,如果您意识到需要不同的东西,您就可以随时更改它。


1
这取决于您是否可以拥有多个相同卡片的实例,还是只能有一个(例如在扑克游戏中)。我不会让内部表示依赖于UI呈现,因为该顺序可能稍后添加或使用适当的集合(如LinkedHashSet),它具有设置语义和迭代器使用时的排序(插入顺序)。 - Thomas
如果你只允许拥有一张特定类型的卡牌,那么你需要使用一些集合语义。如果使用列表,你需要遍历整个列表来进行检查。此外,如果我玩纸牌游戏(模拟游戏),我的手中的牌的顺序有时是任意的。 - Thomas
@Thomas 你是说你只能拥有一张类型的卡片,因为牌堆中每种卡片只有一张吗?如果是这样,那么这是通过只有一个该卡片的对象来实现的。如果它在你的手中,那么它就不能在抽牌堆、弃牌堆、桌面或其他任何地方。所以你不必担心这个问题。如果这是一种限制,比如你正在玩双倍牌组,只能拥有一张任何类型的卡片,那么你可以单独处理这种情况。我很好奇你所说的是哪个具体的游戏,因为我从未遇到过这种情况。 - Erick Robertson
@greuze 我们在谈论手牌,而不是牌堆。 OP 想知道将 Deck.dealt() 返回的卡牌放入手牌时的最佳存储方式。 - Erick Robertson
1
@greuze 我同意我会对一副牌和一手牌使用相同的行为。我会对它们进行排序。一副牌和一手牌中的牌都是有序的。一副牌中有一张顶部的牌,然后是下一张牌,再下一张牌。手牌也是如此。顶部有一张牌,当展开时出现在左边。下一张牌就在旁边,以此类推。这影响用户查看他的牌的顺序。对于大多数纸牌游戏,大多数玩家也会以某种方式对其牌进行排序。这需要排序。 - Erick Robertson
显示剩余4条评论

1

嗯,据我所知,HashSet更快,但如果你想制作一款纸牌游戏,那么也许你会希望对牌进行排序。这就是为什么我建议使用List。如果你是初学者,那么最好的选择可能是使用ArrayList。它易于使用和理解。至少这是我会做的。如果你想了解更多,我建议阅读每个的独特属性,以便自己决定。是的,正如greuze之前所说,你应该使用接口来获得更多的灵活性。


1

首先,在最新版本的Java中,不建议使用Vector,因此您可以忽略它。

其次,如果您阅读了这些剩余类的Javadoc,就会知道它们都有优点或缺点。有些有顺序,有些可以接受重复值,有些则不能等等。因此,我认为最好的方法是为您的应用程序编写一些伪代码,而不是基于特定类(只需编写诸如“将卡添加到手中”,“从手中删除卡”之类的内容)。一旦您拥有了一些这样的伪代码,您将能够更清楚地看到自己的要求;您是否想按特定顺序保留手中的牌?您是否想通过关键字从手中检索卡片?

然后,您的选择将更加清晰。


@use1168884 - 那很有道理。另外,阅读一些Java最佳实践时发现,在性能方面HashSet优于Vector和ArrayList。 - Zack

0

将纸牌堆保持在列表中是有意义的,因为它确实维护了顺序。我倾向于使用Lists.newArrayList()来创建列表。Lists是Guava的一部分。 我强烈建议您使用并了解Guava,因为它具有许多有用的功能。

将手牌保存在某种数据结构中,可以轻松排序,以便更容易地比较手牌。 另一方面,据我所知,金鱼扑克牌手不是很大。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接