我认为模拟扑克游戏会很有趣。宝贝现在正在迈步,最终我将尝试将其变成GUI。到目前为止,我拥有的代码非常基本,例如填充纸牌,对纸牌改组以及向玩家分发两张卡。试图了解继承的工作原理,Java泛型的正确使用,并掌握强大/有趣的OOP的概念。 Collections Framework也是我正在尝试的东西。

我真的很感谢能帮助我更好地理解Java的任何东西。

import java.util.*;


class Deck {
    // create possible card combinations
    public final String[] SUITS = { "H", "D", "C", "S" };
    public final String[] RANKS = { "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3", "2" };

    // maximum number of cards
    public final int deckLength = SUITS.length * RANKS.length;
    public List<String> fullDeck = new ArrayList<>();

    public Deck() {
        for(int i = 0; i < SUITS.length; i++) {
            for(int j = 0; j < RANKS.length; j++) {
                fullDeck.add(RANKS[j] + SUITS[i]);
            }
        }
   }

    public Deck(List<String> fullDeck) {
        this.fullDeck = fullDeck;
        for(int i = 0; i < SUITS.length; i++) {
            for(int j = 0; j < RANKS.length; j++) {
                fullDeck.add(RANKS[j] + SUITS[i]);
            }
        }
    }

    public List<String> shuffle(List<String> fullDeck) {
        this.fullDeck = fullDeck;
        Collections.shuffle(fullDeck);
        return fullDeck;
    }

    // this was mainly used for testing purposes
    // to ensure shuffling was indeed taking place
    public void showDeck(List<String> fullDeck) {
        this.fullDeck = fullDeck;
        for(int i = 0; i < deckLength; i++) {
            System.out.printf("%s ",fullDeck.get(i));
        }
    }
}

class Hands extends Deck {

    public String[] hand = new String[2];
    Random random = new Random();

    // select 2 cards to distribute to player
    public String[] getHand(List<String> fullDeck) {
        super.fullDeck = fullDeck;
        for(int i = 0; i < this.hand.length; i++) {
            this.hand[i] = fullDeck.get(random.nextInt(super.deckLength));
        }
        return this.hand;
    }

    // show player hand
    public void showHand() {
        for(int i = 0; i < this.hand.length; i++) {
            System.out.printf("%s ", this.hand[i]);

        }
    }
}

public class Cards {
    public static void main(String[] args) {

        List<String> cards = new ArrayList<>();
        Deck deck = new Deck(cards);
        deck.shuffle(cards);

        Hands hands = new Hands();
        hands.getHand(cards);
        hands.showHand();

    }
}


评论

为了与扑克界的惯例保持同步,我将“ 10”等级更改为“ T”,并小写了西服

稍微相关:codereview.stackexchange.com/q/36916/31562

showDeck应该改为toString覆盖。

我认为getHand()方法应该在Deck类中

#1 楼

欢迎使用代码审查,也欢迎使用Java!总体来说,您的代码很干净而且易于理解,因此感谢您。

枚举

将这些值存储在字符串数组中不是很可扩展:

public final String[] SUITS = { "H", "D", "C", "S" };
public final String[] RANKS = { "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3", "2" }


首先要注意的是,与原始数组相比,List提供了更多的选择和灵活性。但是,我认为最好将这些值存储为Enum

public enum SUITS {
    H, D, C, S
}


这将允许您在switch语句中使用这些值,并提供其他好处。最重要的是,它更具可读性和可扩展性。

变量名

我强烈建议不要在诸如for (int i = 0; i < something; i++);之类的简单构造之外使用单字母变量名。代替HDCS,我只是将它们拼写为HEARTSDIAMONDS等。使用额外的字符使代码更具可读性没有缺点。在代码的其余部分中,您不会犯此错误,所以很好。

OOP不当

我不认为Hand应该继承自Deck

class Hands extends Deck {


我建议您将继承视为“是”关系。手不是甲板,因此不应被视为一副。例如,您不太可能需要“洗牌”一只手。对我而言,这与从Hand继承DeckCard相同。它不会最终对组织或理解代码有所帮助。

我不理解为什么Cards是一类:

public class Cards {


这里应使用单数形式的Card。然后,HandDeck与卡可以具有“具有”关系,并使用Card对象。在这里设置的方式:

    List<String> cards = new ArrayList<>();
    Deck deck = new Deck(cards);
    deck.shuffle(cards);

    Hands hands = new Hands();
    hands.getHand(cards);
    hands.showHand();


确实没有任何意义。为什么Cards对象将Decks或Hands作为对象?肯定应该是相反的方式。

我期待在另一个问题中看到您的代码的修订版!

评论


\ $ \ begingroup \ $
那么,您是否建议将RANKS和SUITS都放入自己的枚举中?我熟悉枚举,只是很少使用它们。另外,谢谢您的答复!
\ $ \ endgroup \ $
– dan
2014年10月9日,下午1:21

\ $ \ begingroup \ $
我建议,是的。这不是唯一的方法,但是它为您提供了更多选择。欢迎您!
\ $ \ endgroup \ $
–巴佐拉
2014年10月9日,下午1:22

\ $ \ begingroup \ $
我同意将级别和西服表示为枚举,但不是因为它是可扩展的。在这4种产品中,您不太可能需要另外一套衣服,我怀疑我们会把“方头和镐头”看作是额外的衣服(也许是dlc吗?...):P
\ $ \ endgroup \ $
–颜
2014年10月9日15:04

\ $ \ begingroup \ $
但是,实际上要具有建设性,枚举很棒,因为您可以给等级赋予多个值(因此Ace可以为10或1,具体取决于情况),并且该行为可以封装在枚举的方法内
\ $ \ endgroup \ $
–颜
2014年10月9日15:08

\ $ \ begingroup \ $
从Java 7开始,可以在switch语句中使用String。
\ $ \ endgroup \ $
–西蒙·福斯伯格
2014年10月10日上午10:28

#2 楼

我只专注于Deck类。

您在fullDeck类中使用Deck很奇怪:




什么时候您会曾经使用过Deck(List<String> fullDeck)构造函数吗?一个应该传递什么样的参数?如果尝试此方法怎么办?我不应该得到一副神奇宝贝卡吗?不幸的是,这是行不通的。

List<String> pokemon = new LinkedList<>(Arrays.asList(new String[] {
    "Bulbasaur", "Ivysaur", "Venusaur", "Charmander", ...
}));
Deck d = new Deck(pokemon);



即使这行得通,让其他人与你班级的内脏混在一起也是冒险的。假设我随后做了

pokemon.removeFirst();


,那么卡座上的卡也将被移除,这对于您的Deck类中的代码来说是未知的。

shuffle()应该不带参数,不返回任何值。没有理由为什么需要再次告知您的牌组它应该持有什么卡。
showDeck()也是如此—显示应为“只读”操作,并且不应导致牌组“采用”新的卡片清单。
fullDeck是卡片清单,对吗?因此,请改为调用字段cards

接下来,对于正确的面向对象设计,您应该考虑Deck应该支持哪些类型的操作。例如,您应该能够从纸堆的顶部绘制一张卡片,然后将纸牌返回到纸堆的底部。因此,您将引入诸如

public String drawCard() throws NoSuchElementException {
    if (this.cards.isEmpty()) {
        throw new NoSuchElementException();
    }
    return this.cards.remove(0);
}

public void returnCard(String card) {
    // We trust that the card is valid, and not a Pokémon card.
    this.cards.add(card);
}


之类的方法,而不是让showDeck()打印其输出,我建议实现toString()

public String toString() {
    // String.join() was introduced in Java 8.  In older versions,
    // you would use a StringBuilder.
    return String.join(" ", this.cards);
}


建议的实现

import java.util.*;

class Deck {
    // create possible card combinations
    private static final String[] SUITS = {
        "H", "D", "C", "S"
    };
    private static final String[] RANKS = {
        "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3", "2"
    };

    private List<String> cards;

    public Deck() {
        this.cards = new LinkedList<>();
        for (int i = 0; i < SUITS.length; i++) {
            for (int j = 0; j < RANKS.length; j++) {
                this.cards.add(RANKS[j] + SUITS[i]);
            }
        }
    }

    public void shuffle() {
        Collections.shuffle(this.cards);
    }

    public String drawCard() throws NoSuchElementException {
        if (this.cards.isEmpty()) {
            throw new NoSuchElementException();
        }
        return this.cards.remove(0);
    }

    public void returnCard(String card) {
        this.cards.add(card);
    }

    public void toString() {
        return String.join(" ", this.cards);
    }
}


#3 楼


通常优先于import单个库(不包含.*)。因为您只使用了ArrayList中的Listutil,所以将它们分开。
所有字段都必须是private,因为它们不应该是接口的一部分。
您真的希望这仅适用于扑克吗? ?如果您希望具有一定的可重用性(对于其他游戏使用相同的Deck),则可以让游戏决定使用哪张卡(并非每个游戏都使用标准的52张卡)。
由于fullDeck拥有全部Deck中的卡片,则不需要deckLength


评论


\ $ \ begingroup \ $
您提出了一个非常有趣的观点。现在,我不会仅将其用于扑克,最终会为其他纸牌游戏添加选项。谢谢你的主意!
\ $ \ endgroup \ $
– dan
2014年10月9日,1:25

#4 楼

您已经对Deck类有一些很好的建议,因此,我将重点介绍您的Hands类。

首先命名:


该类表示一个人的手吧?因此,应将其命名为Hand

getHand确实会返回一手牌,但是它的主要功能是设置一手牌,或者换句话说,给这只手牌(或者更确切地说,让该手拿牌)。

关于结构:

有时候,它有助于考虑现实生活中的情况:从牌组中拿牌是手牌还是玩家的责任?通常,不是这样,并且Hand类也不应该对此负责。

您的Hand类也非常静态。硬编码每手可以拥有多少张卡,因此您不能玩奥马哈。立即添加纸牌的方式,您也无法玩在不同时间点添加纸牌的游戏(例如Stud)。

更可重用和实用的Hand类可能看起来像this:

public class Hand {

    private List<Card> cards; // note the Card class, it's a lot more practical than a string
                              // eg you could add a "hidden" field, easily compare suits of cards, etc.

    public void addCards(List<Card> cardstoAdd) {
        cards.add(cardstoAdd);
    }

    public List<Card> getCards() {
        return cards;
    }

    public SomeEnumOrClass calculateHandValue() {
        // calculate the best possible hand
    }

    @Override
    public void toString() {
        // hand to string for debug purposes
    }
}

class Player {
    Hand hand;
    ...
}

class Dealer {

    public void deal(List<Player> player, int cardCountPerPlayer) {
        // give each player cards
    }

    public void giveCards(Player player, int cardCount) {
        // give one player cards
    }
    ...
}

class Table {
    private Dealer dealer;
    private List<Player> player;
    ...
}


您还可以创建CardCollection类(包含用于添加,获取和显示卡的逻辑),Hand类可以对其进行扩展。这样,您还可以添加TableCards类,该类也扩展了CardCollection

#5 楼

我建议给Rank枚举基数。这可以帮助您计算游戏情况。像这样的东西:

public enum Rank {
    TWO(2),
    THREE(3),
    FOUR(4),
    FIVE(5),
    SIX(6),
    SEVEN(7),
    EIGHT(8),
    NINE(9),
    TEN(10),
    JACK(11),
    QUEEN(12),
    KING(13),
    ACE(14);

    private int cardinality;

    private Rank(final int cardinality) {
        this.cardinality = cardinality;
    }

    public int getCardinality() {
        return cardinality;
    }
}


评论


\ $ \ begingroup \ $
建议将Ace ace表示为值1。如果您决定遍历值列表,则希望第二个值更有意义。
\ $ \ endgroup \ $
–黑手
2015年2月7日在17:21

#6 楼

Card对象应该代表一种卡片(例如“锹形插孔”或“菱形六”)还是应该代表在任何给定时间只能在一个地方的实体?例如,如果您将两个牌组一起洗牌,并且一起洗牌在一起的牌组的前两张牌都是“俱乐部皇后”,那么这些牌应该用相同的Card对象表示,还是应该用一张卡牌对象代表“我建议您定义一个“纸牌容器”的概念[用于...],该位置在一个特定牌组的位置0,而另一个“在同一牌组的位置1的俱乐部女王”?

然后将“卡片”定义为具有西装和等级的对象,并且始终位于某种卡片容器中,但可以从一个容器移动到另一个容器。每个容器都应具有识别占用它的卡片的方式,并且每个卡都应具有识别一个装有它的容器的方式。将卡从一个容器移动到另一个容器应导致将其从前一个容器中删除并添加到后者中。

#7 楼

我想指出的是其他答案中已经显示的两件事,但没有指出为什么应该这样更改。

在此代码中:

public String[] getHand(List<String> fullDeck) {
    super.fullDeck = fullDeck;
    for(int i = 0; i < this.hand.length; i++) {
        this.hand[i] = fullDeck.get(random.nextInt(super.deckLength));
    }
    return this.hand;
}


这不是应该给玩家两张牌的方法。使用此代码,玩家有可能会获得相同的两张牌。那不应该发生。这就是为什么200_success的答案非常重要的原因,在这种情况下,先对牌进行洗牌,然后对给出的牌进行以下操作:

return this.cards.remove(0);



public final String[] SUITS = { "H", "D", "C", "S" };
public final String[] RANKS = { "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3", "2" };


现在假设我有一个Deck,只需通过Deck deck = new Deck();

然后我就可以做到:deck.RANKS[2] = "Haha";和violà,我已经更改了您的“常量”。数组是可变的,因此可以更改其值。并且由于您的变量是public,因此可以从其他类访问它。

这就是为什么这些变量应绝对私有的原因。而且由于它们应被用作常量,因此它们也应该是静态的。

private static final String[] SUITS = { "H", "D", "C", "S" };
private static final String[] RANKS = { "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3", "2" };


但这仍然使它们可变,这就是为什么enum是一个好的方法的原因。无法在运行时将成员添加到枚举。

另一种方法是使用

private static final List<String> SUITS = Collections.unmodifialbleList(Arrays.asList("H", "D", "C", "S"));


,因为它们具有private可见性但是,可以将它们用作private static final String[],可以确保代码不会更改它们的任何索引。