扑克牌:它们应该是枚举还是结构或类?

时间:2012-03-29 20:40:21

标签: c# struct

我正在设计游戏网站,许多(希望有数千名)玩家会相互同时玩某些纸牌游戏。甲板是标准牌52牌。每张卡都有一套西装和一个等级。这些卡片将被随机洗牌,处理,挑选,订购和播放。我的问题是,Card应该是枚举,结构还是类?

  • 对于枚举: 让每张卡都是字节0..51。因此卡片占用的空间非常小。您可以将手表示为8字节的位集。您可以在需要时非常快速地计算给定卡的套装和等级:即suit(n)= n / 13。这将非常有效。如果您需要为卡片编写方法,请通过扩展方法编写。

  • 对于struct: 不,这就像编写机器代码一样。卡是一种简单的结构,保存的数据非常少,不可变,很小。它没有太多行为,因此将其作为结构并将其视为被动数据结构。当需要时,您可以非常快速地从给定卡中计算0..51中的索引。

  • 上课时间: 不,这不是一种面向对象的思维方式。制作卡片课程。让它一成不变。准确创建52个实例。让卡池保存这些实例。因此,当需要黑桃皇后时,它会向卡片池询问。即使有数千场比赛正在进行,也会有一个也只有一个黑桃皇后。如果需要,可以在卡中存储索引字段0..51。

我倾向于最后一个选项(课程),但我不确定。我不太担心表现;在此过程中,我可能会犯更严重的错误。我担心的是我的整个观点可能是错的;也许这是一个非常简单的决定,我犹豫不决,因为我缺乏其他人拥有的一些知识。

您怎么看?

编辑:关于卡片的行为。我认为一张卡片只会知道其他卡片。例如,它可以定义关于“谁击败谁在桥中”的部分顺序。它不需要知道关于甲板的任何信息。这将是服务器端代码;当然,它不需要知道在屏幕上绘制等等。

7 个答案:

答案 0 :(得分:13)

在决定引用类型或值类型时,您应该问自己的基本问题当然是是我在逻辑上建模事物引用这就是为什么值类型和引用类型首先被称为“值类型”和“引用类型”。

您是否打算将“卡片”作为参考处理,就像处理具有身份的物理对象一样?例如,假设您正在为Canasta建模,它同时使用两个标准卡片组进行播放。你是否想要跟踪两个不同的黑桃皇后并将它们视为参考不同的

或者您打算将它们视为,就像处理数字一样?你永远不会说“这里的六号与那里的六号不同”,因为数字只能通过它们的数值来区分。

答案 1 :(得分:5)

为卡片设置structclass,卡片的值应为enum,卡片的套装也为enum

要制作套牌,您可以使用class,它包含card的列表,有Shuffle等操作。

答案 2 :(得分:2)

您可以将每张卡片表示为一个数字,然后将它们包装在枚举和类中。如果介于0到51之间的每个数字代表一张卡,则可以使用枚举:

public enum Card
{
   HeartsAce = 0,
   Hearts2 = 1,
   // ... and so on
}

int nCard = 16;
Card myCard = (Card)nCard;

你也可以上课:

public class Card
{
    private readonly int _nSuitNumber = 0;
    private readonly int _nCardNumber = 0;

    public Card(int a_nNumber)
    {
        _nSuitNumber = a_nNumber / 13;  // 1 = Hearts, 2 = Diamonds ...
        _nCardNumber = a_nNumber % 13;  // 1 = ace, 2 = two
    }
}

或者更好地结合它们。

public class Card
{
    private readonly Suit _suit;
    private readonly Value _value;

    public Card(int a_nNumber)
    {
        _suit = (Suit)(a_nNumber / 13);  // 0 = Hearts, 1 = Diamonds ...
        _value = (Value)(a_nNumber % 13 + 1);  // 1 = ace, 2 = two
    }

    public Suit Suit
    {
        get { return _suit; }
    }

    public Value Value
    {
        get { return _value; }
    }

    public int ToNumber()
    {
        return (int)_suit * 13 + ((int)_value - 1);
    }
}

public enum Suit
{
    Hearts = 0,
    Diamonds = 1,
    Clubs = 2,
    Spades = 3
}

public enum Value
{
    Ace = 1,
    Two = 2,
    Three = 3,
    Four = 4,
    Five = 5,
    Six = 6,
    Seven = 7,
    Eight = 8,
    Nine = 9,
    Ten = 10,
    Jack = 11,
    Queen = 12,
    King = 13,
}

通过这门课程,您可以获得数字(0-52)和课程。这个类只不过是数字的包装器。您可以根据需要向类添加任何操作,方法和属性。当您存储或传输数据时,您只需使用数字。

答案 3 :(得分:2)

我不相信你真的在这里问正确的问题。如何代表您的卡片应该取决于您想要对卡片做什么;通过回答现在如何代表您的卡片的问题,您将自动约束自己可以解决其他更有趣/更具挑战性的问题,这些问题将在以后遇到。

您真正应该考虑的事情是您将如何实施计划的行为。例如

  • 发出“卡”后会发生什么?您想表示在持有这些卡的实体之间实际移动的卡吗? (例如,甲板,玩家的手,弃牌堆,桌子等),或者您是否希望这些实体仅仅引用卡的中央存储库?或者可能使用完全独立的计数机制?

  • 你打算如何“订购”卡片?你选择实施的游戏可能有不同的规则(例如Ace低或高?皇家套装都有相同的价值吗?会有王牌吗?)这些可能是决定是逐游戏决定的,甚至在游戏过程中也发生了变化,因此根据它的排序简单地定义一张卡片可能不是一个好的抽象。

    • 您尝试表示哪种行为和游戏规则取决于卡?卡本身是否有任何依赖?

    • 卡片是否会参与用户互动,还是纯粹是程序中定义纸牌游戏的部分所使用的逻辑对象?

    • 您是否考虑过使用不同类型卡片的可能性?您在原始问题中提到了一个类,但如果一张卡要定义任何类型的状态,行为或与其他类交互,那么您可能首先要考虑在担心细节之前定义一个Card接口。

作为对你问题的直接回答 - 我个人并不认为你已经提供了足够的答案信息,而不是基于猜测和个人偏好的意见;这没关系,因为通常很难在你开始实施之前提出所有的答案。您可能想尝试所有三种,看看哪种最适合您的需求。不要通过事先做出这些决定来约束你的设计。

答案 4 :(得分:0)

您可能会考虑要尝试构建的抽象。如果一张卡片是其他功能传递的低级对象,那么更简单的enum可能更合适,但是如果您希望卡片能够draw()本身或其他任何你需要的卡片更复杂的东西。卡片如何适合您的应用程序?

答案 5 :(得分:0)

我认为这个问题在逻辑上可以得到解答。我将从面向对象的角度来看一下它。我认为一张牌有两个子元素,套装和价值。那些应该是enum。手(类)应该包含一张卡片列表,这使我相信最好将卡片作为结构或类。为了确定一只手是否有一对,这将是手类的一个功能。

由于有多个人玩不同类型游戏的游戏,每个牌组都必须知道它有哪种类型的牌以及牌数。但即使在这种情况下,卡本身也是不可改变的。

答案 6 :(得分:0)

我会使用枚举的类或结构,并在其上放置一个内部构造函数:

publlic class PlayingCard
{
    internal PlayingdCard(CardSuit suit, CardFace face)
    {
        this.Suit = suit;
        this.Face = face;
    } 

    public CardSuit Suit { get; private set; }

    public CardFace Face { get; private set; }
}

定义您的Deck类来管理卡片,但(最重要的是)让它在构建卡片时分配卡片:

public class Deck
{
    private List<PlayingCard> cards = new List<PlayingCard>();
    public Deck()
    {
        for(var i = 0; i < 4; i++)
        {
            for (var j = 1 to 13)
            {
                this.cards.Add(new PlayingCard((CardSuit)i, (CardFace)j));
            }
        }
    }

    public void Shuffle() { /* implement me */ }
    public PlayingCard GetNextCard() { /* implement me */ }

}

这样,甲板大小是固定的,面部和套装是用尽可能少的代码创建的(虽然我确信有人可以想出一种方法来LINQify)。