【问题标题】:Best way to represent game card class in C#在 C# 中表示游戏卡类的最佳方式
【发布时间】:2011-03-18 12:39:18
【问题描述】:

我使用 Card 类,它包含 2 个枚举属性(套件 - 心形钻石黑桃和梅花)和从 2 到 A 的卡片值。并覆盖 ToString() 方法以返回类似 Ah Ad 等内容。一切正常,但枚举值可以't 以数字开头,因此我列举的卡值看起来像 x2,x3,x4 ......它并不漂亮。

还需要简单的方法来从单个字符串中解析几张卡片。

谁知道设计这个类的最佳方法?

【问题讨论】:

  • 你可以让你枚举'Ace' 'Two' 'Three' ... 'Jack', 'Queen', 'King'

标签: c# architecture enums


【解决方案1】:

您不能将 Jack、Queen、King 和 Ace 分别指定为 11、12、13 和 14 吗?它最终看起来像:

public class Card
{
    public int Value { get; private set; }
    public enum SuitType
    {
        Clubs, Spades, Hearts, Diamonds
    }
    public SuitType Suit { get; private set; }
    public Card(int value, SuitType suit)
    {
        Suit = suit;
        Value = value;
    }
    public Card(string input)
    {
        if (input == null || input.Length < 2 || input.Length > 2)
            throw new ArgumentException();
        switch (input[0])
        {
            case 'C': case 'c':
                Suit = SuitType.Clubs;
                break;
            case 'S': case 's':
                Suit = SuitType.Spades;
                break;
            case 'H': case 'h':
                Suit = SuitType.Hearts;
                break;
            case 'D': case 'd':
                Suit = SuitType.Diamonds;
                break;
            default:
                throw new ArgumentException();
        }
        int uncheckedValue = (int)input[1];
        if (uncheckedValue > 14 || uncheckedValue < 1)
            throw new ArgumentException();
        Value = uncheckedValue;
    }
    public string encode()
    {
        string encodedCard = "";
        switch (Suit)
        {
            case SuitType.Clubs:
                encodedCard += 'c';
                break;
            case SuitType.Spades:
                encodedCard += 's';
                break;
            case SuitType.Hearts:
                encodedCard += 'h';
                break;
            case SuitType.Diamonds:
                encodedCard += 'd';
                break;
        }
        encodedCard += (char) Value;
        return encodedCard;
    }
    public override string ToString()
    {
        string output = "";
        if (Value > 10)
        {
            switch (Value)
            {
                case 11:
                    output += "Jack";
                    break;
                case 12:
                    output += "Queen";
                    break;
                case 13:
                    output += "King";
                    break;
                case 14:
                    output += "Ace";
                    break;
            }
        }
        else
        {
            output += Value;
        }
        output += " of " + System.Enum.GetName(typeof(SuitType), Suit);
        return output;
    }
}

编辑: 我添加了一些字符串功能。 我从Jon Hanna's answer 中获取了Card(string input) 的结构。

【讨论】:

  • 您也可以为枚举“SuitType”分配真实的整数值。
【解决方案2】:

点卡有一个明显的数值,我们可以添加 J=11、Q=12、K=13。

根据所建模的游戏,A=14 可能比 A=1 更方便(因此可以更简单地计算手牌的不同相对值)。

枚举没有真正的优势,特别是因为枚举允许超出范围的值,除非您明确检查它们(例如,没有什么可以阻止某人将 (CardValue)54 分配给卡值枚举值)。

ToString 可以通过值{null,"1","2","3","4","5","6","7","8","9","10","J","Q","K"} 的数组来辅助。同样{'♥','♦','♠','♣'} 可以提供更好的输出。

解析总是比输出字符串更棘手,即使您对接受的内容非常严格,因为您必须处理潜在的无效输入。一个简单的方法是:

private Card(string input)
{
  if(input == null)
    throw new ArgumentNullException();
  if(input.length < 2 || input.length > 3)
    throw new ArgumentException();
  switch(input[input.Length - 1])
  {
    case 'H': case 'h': case '♥':
      _suit = Suit.Hearts;
      break;
    case 'D': case 'd': case '♦':
      _suit = Suit.Diamonds;
      break;
    case 'S': case 's': case '♠':
      _suit = Suit.Spades;
      break;
    case 'C': case 'c': case '♣':
      _suit = Suit.Clubs;
      break;
    default:
      throw new ArgumentException();
  }
  switch(input[0])
  {
    case "J": case "j":
      _cardValue = 11;
      break;
    case "Q": case "q":
      _cardValue = 12;
      break;
    case "K": case "k":
      _cardValue = 13;
      break;
    case "A": case "a":
      _cardValue = 1;
      break;
    default:
      if(!int.TryParse(input.substring(0, input.Length - 1), out _cardValue) || _cardValue < 2 || _cardVaue > 10)
        throw new ArgumentException;
      break;
  }
}
public static Card Parse(string cardString)
{
  return new Card(cardString);
}

您可能希望添加一个静态方法,在解析时读取更大的字符串 yield returning 卡片,以便更轻松地对多张卡片进行编码。

【讨论】:

  • @Jon 我认为考虑从字符串中解析卡片真的很聪明——这无疑会非常有用。请问您为什么不直接公开Card(string input) 构造函数?将其设为私有并使用静态方法调用它有什么好处吗?
  • @Evl-ntnt,我看不出你比 1 有什么优势)。产量问题是有一个名为IEnumerable&lt;Card&gt; ParseHand(string) 之类的方法,它遍历一个字符串,找到它传递给上面的描述卡片的子字符串,然后yield 返回每个。它在上述基础上构建得足够简单。
  • @T.K.解析在上面的原始问题中。不公开 Card(string input) 构造函数的主要原因是,在不作用于各种字符串的类型上使用这样的构造函数是不寻常的,更常见的是使用 Parse 方法(参见 numeric 和 DateTime BCL 中的类型),我想匹配该约定。构造函数不是剥那只猫的唯一方法,但它很方便。也可以在Parse 方法中获取花色和值,然后将其传递给现有的构造函数,选择一个而不是另一个只是一种实现选择。
  • @Jon 好的。在我所做的大部分工作中,我都使用过类似的构造函数,但不知道这种约定。谢谢。
  • @T.K. tbh,我更喜欢公开构造函数,如果它是私有类,我也会这样做。
【解决方案3】:

当我第一次开始使用 card.dll 时,我使用枚举来计算花色和卡片排名,但后来我不想处理同样的问题并编写额外的代码来补偿字符串,因为我写了一个只有两个变量的抽象类Info (Flag (byte)) 和 (Name(string)) 由 Rank 类和 Suit 类实现,它们将是 Card 类的成员。我发现这对于命名约定和过滤目的要好得多。我喜欢使用枚举,但必须解决变量命名可能会很麻烦,所以有时最好不要将变量名称作为字符串获取。

因此,当 Card 构造函数被调用时,输入卡 ID,然后将其传递到 Rank 和 Suit,然后将 ID 在代码中的含义分开 (101 = 100 (suit flag) + 1(等级标志))。受保护的抽象 SetName(int cardID) 和 SetFlag(int cardID) 同时通过 Rank 和 Suit 在信息的构造函数中处理其余部分。枚举不再有问题,仍然可以通过标志按数字过滤。

【讨论】:

    【解决方案4】:

    这个卡牌命名系统使用 1 到 4 * 100(告诉花色标志)+ 1 到 13(表示卡牌等级)。 500 + 14 到 16 是小小丑、大小丑和狂野。

    public class Card
    {
        short id;
    
        public Card(string zFile)
        {
            this.id = Convert.ToInt16(zFile.Split('.')[0].Trim());
            this.Rank = new Rank(id);
            this.Suit = new Suit(id);
        }
    
        public override string ToString()
        {
            if (Suit.Flag == 5)
                return Suit.Name;
            return string.Concat(Rank.Name, " of ", Suit.Name);
        }
        public override int GetHashCode()
        {
            return id;
        }
    
        public Rank Rank { get; private set; }
        public Suit Suit { get; private set; }
    
        public static Card GetGreaterRank(Card value1, Card value2)
        {               
            return  (value1.Rank >= value2.Rank) ? value1 : value2;                        
        }
    
        public static bool CompareRank(Card value1, Card value2)
        {
            return (value1.Rank.Flag == value2.Rank.Flag);
        }
        public static bool CompareSuit(Card value1, Card value2)
        {
            return (value1.Suit.Flag == value2.Suit.Flag);
        }
    };    
    public abstract class Info
    {
        protected Info(short cardID)
        {
            Flag = SetFlag(cardID);            
        }
    
        protected string SetName(short cardID, params string[] names)
        {
            for (int i = 0; i < names.Length; i++)
            {
               if (Flag == (i + 1))
                  return names[i];
            }
            return "Unknown";
        }
    
        protected abstract byte SetFlag(short cardID);
    
        public static implicit operator byte(Info info)
        {
            return info.Flag;
        }
    
        public byte Flag { get; protected set; }
        public string Name { get; protected set; }
    };
    
    public class Rank : Info
    {
        internal Rank(short cardID) : base(cardID) 
        { 
            string name = SetName(cardID, "A","2","3","4","5","6","7",
                   "8","9","10","J","Q","K","Little Joker","Big Joker","Wild");
            Name = (name == "Unknown") ? string.Concat(name, " Rank") : name;
        }
    
        protected override byte SetFlag(short cardID)
        {
            return Convert.ToByte(cardID.ToString().Remove(0, 1));
        }        
    };
    
    public class Suit : Info
    {
        internal Suit(short cardID) : base(cardID) 
        { 
            string name = SetName(cardID,"Clubs","Diamonds","Hearts","Spades");
            Name = (name == "Unknown") ? string.Concat(name, " Suit") ? name;
        }
    
        protected override byte SetFlag(short cardID)
        {
            return Convert.ToByte(cardID.ToString().Remove(1));
        }
    };
    

    所以现在,如果您有名为 101.png 的卡片图像文件并将其传递给 Card ctor,它将传递给 Rank and Suit 为您获取信息。实际上,您在为图像文件提供名称的代码(数字)方面所做的一切。

    【讨论】:

      【解决方案5】:

      我可能会从 2 个枚举开始,1 个代表 Suits,1 个代表 Faces。然后根据这些枚举声明一个公共属性“Suit”和一个公共属性“Face”。您可能还需要一个具有卡片可以具有的不同唯一值的数组(即 1 到 13)。

      【讨论】:

        【解决方案6】:

        你可以用数字开始枚举(尽管最好从零开始)

        public enum Card
        {
           Two = 2,
           Three,
           Four,
           ...
        }
        

        【讨论】:

          【解决方案7】:

          把我之前写的刮一下,这样更好。

          using System;
          
          enum Suit
          {
              Clubs,
              Hearts,
              Diamonds,
              Spades
          }
          
          class Card
          {
              Suit Suit
              {
                  get;
                  private set;
              }
          
              int Value
              {
                  get;
                  private set;
              }
          
              Card(Suit suit, int value)
              {
                  Suit = suit;
                  Value = value;
              }
          
              private const string[] valsToString = new string[] { "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A" };
          
              bool IsValid()
              {
                  return Value >= 2 && Value <= 14;
              }
          
              override string ToString()
              {
                  return string.Format("{0} of {1}", valsToString[Value - 2], Suit);
              }
          }
          

          【讨论】:

          • 我认为这行不通。如果一个值在
          猜你喜欢
          • 1970-01-01
          • 2013-01-17
          • 1970-01-01
          • 2010-10-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多