扑克牌

本案例以10点半的游戏为例讲解,也可以扩展到其他扑克牌类游戏,总体的思路是类似的。

10点半游戏介绍

  • 每个玩家可以从牌堆中起任意数量的牌。
  • 起到A~10的牌,按牌面数计分。
  • 起到JQK大小王,记0.5分。
  • 超过了10.5分,就爆炸了。
  • 比较每个玩家的得分,没爆炸并且得分最高的就赢了。

定义扑克牌的数据结构

  • 扑克的数值用1~13表示。其中1代表A,11代表J,12代表Q,13代表K。
  • 14代表小王,15代表大王,大小王的花色就不重要了。
  • 四种花色用枚举定义。
    enum CardFlower
    {
      RedHeart,
      RedSquare,
      BlackHeart,
      BlackSquare
    }
    struct Card
    {
      public int Number;
      public CardFlower Flower;
    }
    

初始化整套扑克牌

  • 从1到13初始化。
  • 每一数字的牌有4种花色。
  • 大小王单独处理。

    const int CARD_COUNT = 54;
    static Card[] cards = new Card[CARD_COUNT];
    // 初始化整套扑克牌
    static void Init()
    {
      int index = 0;
    
      // 从1到13初始化卡牌
      for (int i = 1; i <= 13; i++)
      {
          // (每个数字)有4种花色
          for (int j = 0; j < 4; j++)
          {
              cards[index] = new Card();
              cards[index].Number = i;
              cards[index].Flower = (CardFlower)j;
    
              // 初始化了一张
              index++;
          }
      }
    
      // 大小王特殊处理
      cards[52].Number = 14;  // 用14表示小王
      cards[53].Number = 15;  // 用15表示大王
    }
    

显示扑克牌

  • 遍历扑克牌数组。
  • 对J,Q,K,A,大小王特殊处理。

    // 显示一张卡牌
    static void Show(Card card)
    {
      // 不显示没有初始化的卡牌
      if (card.Number == 0)
          return;
    
      string cn; 
      switch(card.Number)
      {
          case 1: cn = "A"; break;
          case 11: cn = "J"; break;
          case 12: cn = "Q"; break;
          case 13: cn = "K"; break;
          case 14: cn = "小王"; break;
          case 15: cn = "大王"; break;
          default:
              cn = card.Number.ToString();
              break;
      }
    
      cn = (card.Number < 14 ? card.Flower + " " : "") + cn;
      Console.WriteLine(cn);
    }
    
  • 为了使用方便还提供两个重载。
    // 显示一组卡牌
    static void Show(Card[] cs)
    {
      // 这是典型的遍历数组的方法
      for (int i = 0; i < cs.Length; i++)
      {
          Show(cs[i]);
      }
    }
    // 显示所有卡牌
    static void Show()
    {
      Show(cards);
    }
    

洗牌

  • 随机选择0~53中的两个位置。
  • 交换这两个位置的牌,完成一轮洗牌。
  • 进行足够多次数的洗牌。

    // 用于产生随机数的对象
    static Random r = new Random();
    // 洗牌,默认洗1000次。
    static void ExchangeCards(int count=1000)
    {
      // 交换count次
      for (int i = 0; i < count; i++ )
      {
          // 随机找两张牌
          int index1 = r.Next(CARD_COUNT);
          int index2 = r.Next(CARD_COUNT);
    
          // 交换
          Card temp = cards[index1];
          cards[index1] = cards[index2];
          cards[index2] = temp;
      }
    }
    

计算得分

  • 起到A~10的牌,按牌面数计分。
  • 起到JQK大小王,记0.5分。
    private static double Score(Card card)
    {
      if (card.Number >= 11)
          return 0.5;
      else
          return card.Number;
    }
    

起牌

  • 玩家可以用数组保存起到的牌。后续还可以使用集合来保存。
    static Card[] playerA = new Card[CARD_COUNT];
    static Card[] playerB = new Card[CARD_COUNT];
    
  • 起牌时,从0开始,顺序的从洗好的扑克牌数组中拿出牌。
  • 需要用到一个索引变量,记录当前应该拿哪张牌。
    // 当前起牌的索引
    static int cardIndex = 0;
    
  • 如果是玩家A起牌,就从牌堆中拿出一起牌,放到自己的扑克牌数组里。

    static void PlayA()
    {
      // 以下代码实现玩家A起到一张牌
      int playerCardIndex = 0;
      // 起到的牌是cards[cardIndex]
      // 要放到玩家A自己的数组playerA中
      playerA[playerCardIndex] = cards[cardIndex];
    
      // 起到一张牌后将起牌的索引加1,下次再起牌,起到的就是下一张牌了。
      cardIndex++;
      // 每次放到playerA中的位置不能重复,以免覆盖之前的数据。
      playerCardIndex++;
    }
    

玩家分别起牌并算分

  • 每个玩家可以从牌堆中起任意数量的牌。

    static double PlayA()
    { 
      int playerCardIndex = 0;
      double score = 0;
    
      while (true)
      {
          Console.WriteLine("轮到PlayerA起牌[a:起牌, p:过]:");
    
          string input = Console.ReadLine();
          if (input == "a")
          {
              playerA[playerCardIndex] = cards[cardIndex];
              // 提示起到的牌
              Show(cards[cardIndex]);
    
              // 计算得分
              score += Score(cards[cardIndex]);
    
              // 提示总分
              Console.WriteLine("当前得分:" + score);
    
              cardIndex++;
              playerCardIndex++;
          }
          else if (input == "p")
          {
              // 过
              break;
          }
          else
          {
              Console.WriteLine("输入有误.");
          }
      }
    
      return score;
    }
    
  • 玩家B的代码类似,其实可以将玩家A和玩家B的代码合成一个函数,但为了便于理解和学习,我们还是分成两个函数。

    private static double PlayB()
    {
      int myCardIndex = 0;
      double score = 0;
    
      while (true)
      {
          Console.WriteLine("轮到PlayerB起牌[b:起牌, p:过]:");
    
          string input = Console.ReadLine();
          if (input == "b")
          {
              playerB[myCardIndex] = cards[cardIndex];
              // 提示起到的牌
              Show(cards[cardIndex]);
    
              // 计算得分
              score += Score(cards[cardIndex]);
    
              // 提示总分
              Console.WriteLine("当前得分:"+ score);
    
              cardIndex++;
              myCardIndex++;
          }
          else if (input == "p")
          {
              // 过
              break;
          }
          else
          {
              Console.WriteLine("输入有误.");
          }
      }
    
      return score;
    }
    

判断胜负

  • 比较每个玩家的得分,没爆炸并且得分最高的就赢了。

    // 返回0表示平局、1表示玩家A获胜、2表示玩家B获胜。
    static int Compare(double scoreA, double scoreB)
    {
      // 玩家A和玩家B都爆了。
      if (scoreA > 10.5 && scoreB > 10.5)
          return 0;
    
      // 玩家A爆了,玩家B没爆。
      if (scoreA > 10.5)
          return 2;
    
      // 玩家B爆了,玩家A没爆。
      if (scoreB > 10.5)
          return 1;
    
      // 都没爆,比较分数大小。
      if (scoreA > scoreB)
          return 1;
      else if (scoreB > scoreA)
          return 2;
      else
          return 0;
    }
    

游戏业务逻辑的实现

static void Main(string[] args)
{
    // 初始化一套新牌
    Init();
    // 洗牌
    ExchangeCards();

    // 轮到A了
    double scoreA = PlayA();
    // 轮到B了
    double scoreB = PlayB();

    // 比较大小
    int result = Comparer(scoreA, scoreB);

    switch (result)
    {
        case 0:
            Console.WriteLine("平局");
            break;
        case 1:
            Console.WriteLine("A获胜");
            break;
        case 2:
            Console.WriteLine("B获胜");
            break;
    }
}

项目源码

下载

results matching ""

    No results matching ""