每个玩家都可以得到纸牌之手
甲板上的牌可以被洗掉,并且可以从甲板上一次发一张牌,然后添加到玩家手中。
具体地说,我对
dealCard
类中的方法Deck
的良好方法签名感到困惑。我可以将播放器方法传递给-void dealCard(Player player)
之类的方法,也可以获取卡并添加到播放器-Card dealCard()
的手中。根据OOPS的实践,以下哪种实施方式是好的?我在这种情况下如何看待?任何建议都可能会有所帮助 package main;
//Each card has a value and a suite.
public class Card {
int value;
SUITE suite;
public Card(int value, SUITE suite) {
this.value = value;
this.suite = suite;
}
public int getValue() {
return value;
}
public SUITE getSuite() {
return suite;
}
public void setValue(int value) {
this.value = value;
}
public void setSuite(SUITE suite) {
this.suite = suite;
}
@Override
public String toString() {
return "Card{" +
"value=" + value +
", suite=" + suite +
'}';
}
}
package main;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public class Deck {
List<Card> cardDeck;
public Deck() {
this.cardDeck = new ArrayList<Card>();
for(int value = 1 ; value <= 13 ; value++){
for(SUITE suite : SUITE.values()){
cardDeck.add(new Card(value,suite));
}
}
}
@Override
public String toString() {
return "Deck{" +
"cardDeck=" + cardDeck +
'}';
}
public void shuffle(){
Random rand = new Random();
//Generate two random numbers between 0 to 51
for(int i = 0 ; i < 20 ; i ++){
int firstCard = rand.nextInt(52);
int secondCard = rand.nextInt(52);
Collections.swap(cardDeck,firstCard,secondCard);
}
}
public void dealCard(Player player){
//Get next card and add to hand of the player
Card removedCard = cardDeck.remove(0);
player.getHand().add(removedCard);
}
public Card dealCard(){
Card removedCard = cardDeck.remove(0);
return removedCard;
}
//Size of the deck for testing purpose
public int getSizeOfDeck(){
return cardDeck.size();
}
}
package main;
import java.util.ArrayList;
import java.util.List;
public class Player {
List<Card> hand;
public Player() {
this.hand = new ArrayList<Card>();
}
public List<Card> getHand() {
return hand;
}
@Override
public String toString() {
return "Player{" +
"hand=" + hand +
'}';
}
}
package main;
public enum SUITE {
HEART,
SPADE,
CLUB,
DIAMOND
}
package main;
public class Main {
public static void main(String args[]) {
Deck deck = new Deck();
System.out.println(deck);
System.out.println("Size of deck is: "+deck.getSizeOfDeck());
deck.shuffle();
System.out.println("Deck after shuffling is "+deck);
Player player1 = new Player();
Player player2 = new Player();
deck.dealCard(player1);
System.out.println("Size of deck after dealing a card to player is "+deck.getSizeOfDeck());
deck.dealCard(player2);
System.out.println("Size of deck after dealing a card to player is "+deck.getSizeOfDeck());
System.out.println("Hand of player 1 is "+player1.getHand());
System.out.println("Hand of player 2 is "+player2.getHand());
player1.getHand().add(deck.dealCard());
}
}
#1 楼
在那么小的空间中有太多话要说。可以通过多种方式回答此问题,这主要是由于您需要对卡进行建模的原因。没有适当的上下文,需求将发生变化,因此可能会更改整个模型。因此,假设您打算构建一个(或多个)纸牌游戏。关注点分离
有很多重要的OOP原则,但我主要是为了解决关注点分离(或与计算机科学相关的关注点,因为通常这是您首先需要考虑的问题:为什么拥有类/对象,以及它们的预期目的是什么?
经过某种简化可以说:一个对象/类不必关心自身之外的东西。这可以允许替换任何类的内部,只要公共API(调用的公共方法)它)保持不变。我将尝试举例说明与您提供的各种类相关的内容。
Card
换句话说,应该将
Card
不必担心自己可能会使用它以及如何使用它。处理卡的基本方法应该保持不变。更具体地讲,这似乎有些奇怪
Card
可以更改其值或适合。什么样的游戏卡可以做到这一点?如果它是钻石之王,那么它通常永远保持这种状态。 对我来说,让卡具有定义输出外观的方法会更加自然。例如,选择其他文本变体:“值:13,花色:黑桃”,“黑桃王”,“ K♠”甚至“🂮”。最后两个示例来自:Wikipedia的Unicode打牌。
父级
Card
可能应该为空,并允许子类指定该卡的标准。像普通的纸牌,Uno纸牌或Pokemon纸牌等。Deck
Deck
由Card
组成,但是除了基本的卡处理外,对卡的了解应该不多。而且,使用Deck对Player
或Game
也不应该有任何了解。 这将回答您在
dealCard()
上的问题。它对播放器一无所知,基本上只应该从卡组中取出Card
,然后将其退还给可能随便使用的人。 Deck
的其他有趣功能可能是将卡重新插入到卡座中,并可能验证卡在这种卡座中是否合法。想像蜘蛛纸牌游戏(仅使用Spade套件)或使用双层游戏。Player
或Hand
吗?我不是确定我会在
Player
类中添加什么,但是在纸牌游戏的背景下,我想我更喜欢使用Hand
类来描述当前的纸牌集。 在这种情况下,很自然地向手添加/移除卡。并以各种替代方式进行显示。相对于另一手牌,它可能有一些通用的方法来描述一手牌的顺序。
对于子类化,也可以想象该手牌可以计算出该手牌的整体价值,例如扑克价值,或者是否包含组或系列,我可以想象有一些方法可以计算我的手的价值,例如扑克手的价值,或者我是否有任何系列或组。子类可以指定哪只手彼此之间最有价值。
代码回顾
通常,您的代码看起来不错,带有适当的缩进和括号用法。我希望在与定义相关的给定位置注释方法/类时,保持一点一致性。您可以/应该考虑使用Javadoc或给定的文档标准来描述您的类。
一个
shuffle()
方法每次只能交换2张卡,只能随机播放20张卡,这似乎是一种很糟糕的随机播放卡组的方法。我相信您可以做得更好。我想也许可以在卡座中循环一次,然后将卡片放置位置切换到卡座中的其他地方?更好的演示方法会更好。请参阅上面有关
Card
类的评论。有关
SUIT
枚举的次要细节。我将其订购为SPADE
,HEART
,DIAMOND
和CLUB
,因为这是很多纸牌游戏中纸牌价值的常见排序方式。并不是完全为Uppercasing感到兴奋,但是不确定Java中用来表示此类全局变量的通用方法是什么。PS!我已经使用Wikipedia作参考,确实存在更好的描述,但是为了保持一致,我选择在这里使用它们。
PPS!另一个有趣的原理是SOLID原理,但是如果您是编程的新手,可能很难将其扎根。
评论
\ $ \ begingroup \ $
和随机数一样,很容易出错。因此,我建议使用已知的算法进行混洗,例如Fisher-Yates。
\ $ \ endgroup \ $
–艾米莉·L。
17年4月25日在22:43
\ $ \ begingroup \ $
@EmilyL。只需调用Collections.shuffle
\ $ \ endgroup \ $
– Pete Kirkham
17年4月26日在12:23
\ $ \ begingroup \ $
@PeteKirkham我的评论主要是回复一次Maybe在卡座中循环一次,然后将卡片放置位置切换到卡座中的其他位置吗?指出要想出一个好的算法比想象的要难。当然,Collections.shuffle。应该使用(可能是Fisher-Yates)。
\ $ \ endgroup \ $
–艾米莉·L。
17年4月26日在13:17
\ $ \ begingroup \ $
要使用一些适当的行话,发牌应包括两个操作,第一个操作是从卡组中抽出一张牌。应通过从交易重命名功能来确保清晰度!
\ $ \ endgroup \ $
–user14393
17-4-27 4:00
#2 楼
我认为,如果Deck构造函数不实例化Cards而是接受Cards,那就更好了:
public Deck(final List<Card> cards) {
this.cardDeck = cards;
}
对象rand的范围可以扩大,我认为您不必每次都创建一个新的Random对象,这应该可以为您提供更好的分配。 (就我所知道的情况,我不确定我提出的关于更好分配的最后一点。)
private final Random rand = new Random();
public void shuffle(){
//Generate two random numbers between 0 to 51
for(int i = 0 ; i < 20 ; i ++){
int firstCard = rand.nextInt(52);
int secondCard = rand.nextInt(52);
Collections.swap(cardDeck,firstCard,secondCard);
}
}
不要盲目地暴露手播放器的外观,如下所示:
public List<Card> getHand() {
return hand;
}
我认为最好公开所需的功能,例如:
public class Player {
private final List<Card> hand;
public Player() {
this.hand = new ArrayList<Card>();
}
public void dealCards(Card... cards) {
// implementation here..
}
public boolean holdsCard(Card card) {
// implementation here..
}
}
基本上,每当您看到如下所示的方法链时。.
player.getHand().add(removedCard);
您可以考虑自己的设计。
通常最好使用私有实例变量,因为您似乎对它们具有默认访问权限。 (因此,访问修饰符=默认访问权限。)
总体来说不错。
评论
\ $ \ begingroup \ $
使用Java,即使在同一线程上创建的每个Random对象都在同一毫秒内创建,也具有唯一的种子。
\ $ \ endgroup \ $
–莫妮卡基金的诉讼
17年4月26日在16:53
#3 楼
对于您的随机播放算法,除非您有扎实的统计背景,否则我建议您使用既定算法。由于算法仅交换20对卡,因此您可以确保至少有12张卡保留在其初始位置。在Fischer-Yates混洗中,每张纸牌都有相等的机会被置换,从而导致公平的混洗。另外,您的SUIT枚举应在主要使用的卡类中定义。在任何情况下都不需要使用没有Card的SUIT,这将消除Card和Main类之间的纠缠。
评论
\ $ \ begingroup \ $
我甚至建议使用Collections.suffle()帮助器方法...
\ $ \ endgroup \ $
–提莫西·卡特勒(Timothy Truckle)
17年4月26日在7:51
\ $ \ begingroup \ $
或只是不洗牌。绘制时只需从集合中选择一个随机条目。
\ $ \ endgroup \ $
–塔米尔
17年4月26日在7:54
\ $ \ begingroup \ $
我正在Deck类中使用SUIT创建初始52张卡。如果我将SUIT移至CARD,我猜不可能
\ $ \ endgroup \ $
–mc20
17年4月26日在8:22
\ $ \ begingroup \ $
@TimothyTruckle是的,重塑方向盘毫无意义
\ $ \ endgroup \ $
– JollyJoker
17年4月26日在12:06
\ $ \ begingroup \ $
@ mc20您应该将Suit枚举移动到Card类,同时使其可以公开访问。由于您已经在Deck中使用Card,因此也可以在Deck中使用Suit。
\ $ \ endgroup \ $
–kcazllerraf
17年4月27日在1:40
#4 楼
我还将添加...关于包装的思考
没有卡的西服是没用的,所以西服应该与卡在同一个包装或子包装中。玩家可以使用卡,但不需要卡。因此,播放器应位于其他包装中。这对于权限级别很重要,因为您可能希望将Card Set方法设置为Protected。将玩家移至新软件包将使Deck有权重写卡,但不会授予玩家。
请考虑“您需要什么” /“这看起来像什么”
我还要提出一个论点,即您不需要纸牌或手牌,并且两者都是纸牌池。语义在变量名中。
在Shuffle上
我要说改变
for(int i = 0 ; i < 20 ; i ++){
int firstCard = rand.nextInt(52);
到
for(int firstCard = 0 ; firstCard < cardDeck.length() ; firstCard++){
int secondCard = rand.nextInt(cardDeck.length());
这样可以确保在随机播放中,至少触摸每张卡一次。由于Deck并非线程安全的,因此我还将使Random对象成为全局对象,以便您可以在实例化时使其一次并重用它。另外,您应该迭代卡组的长度,而不是静态的52,因为我们对卡组的真正了解是它包含0+张卡片。
您还需要考虑“关心线程?”。如果2个线程共享一个卡座,则它们可能会重复或丢弃卡。但是,使Deck Thread-safe也增加了开销,因此您应该只在您认为重要的范围内容纳它。
评论
\ $ \ begingroup \ $
修改后的代码仍然不能确保每张卡至少被触摸一次,因为在所有迭代中,卡被多重选择的可能性非常高。取而代之的是,最好从一个牌组到另一个牌组进行洗牌-然后肯定会碰到每张牌。实现是微不足道的。
\ $ \ endgroup \ $
–肯乔格
17年4月26日在6:46
\ $ \ begingroup \ $
@Konchong每张卡至少被触摸一次。当索引到达该位置时,卡片处于其原始位置(在这种情况下,它将作为firstCard被触摸),或者在较早的索引处被移动(在这种情况下,随后被触摸)
\ $ \ endgroup \ $
–塔米尔
17-4-26在7:53
\ $ \ begingroup \ $
仍然不是一个公平的洗牌;请参阅其他答案的评论以获得更好的建议。
\ $ \ endgroup \ $
– Toby Speight
17年4月26日在14:09
#5 楼
等级,西服和卡片都应该是不变的。我将枚举用于等级,西装和卡片。示例代码在这里。我使用了一个抽象的Cards类,该类包含一系列纸牌,并将其扩展为套牌类和手牌类。评论
\ $ \ begingroup \ $
使它们不变的原因是什么?
\ $ \ endgroup \ $
–mc20
17-4-26在8:23
\ $ \ begingroup \ $
实际上只有一个俱乐部,一个ace和一个aceOfClubs。他们永远都不会改变。一种简单的方法是使它们成为枚举。如果它们是枚举,则很容易添加其他状态和/或行为。
\ $ \ endgroup \ $
–雷·塔耶克(Ray Tayek)
17年4月26日在9:00
评论
供参考,它是“西装”,而不是“西装”。非常糟糕的随机播放。
此外,随机播放中还有一个错误。您假设套牌中有52张卡片,如果抽出了任何卡片,可能会抛出界外错误。您也可以将2张卡套组合到卡组中,从而允许52张以上的卡(例如在赌场中)
这是您控制设计的任务还是项目?