65.9K
CodeProject 正在变化。 阅读更多。
Home

SOLID Poker - 第一部分 - 确定扑克牌型

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (28投票s)

2017年3月21日

CPOL

7分钟阅读

viewsIcon

44540

downloadIcon

421

扑克规则有许多令人惊讶的变体,这使得扑克成为 SOLID 项目的绝佳候选项目,该项目将说明 SOLID 原则以及相关的模式和实践。

作为额外奖励,这个项目肯定会考验和磨练你的扑克技巧。

目录

系列文章

本项目和文档将跨越多篇文章,以确保项目的每个部分都能得到必要的关注和时间。这提供了良好的结构,使信息更容易理解。

每篇文章都建立在先前的文章之上并依赖于它们,并且每篇文章都将进一步开发先前文章中的代码。先前的文章为当前文章提供了必要的背景和上下文。

以下是迄今为止已发布的该系列文章的完整列表

如何找到工作

本系列文章使用一个扑克项目来涵盖大多数 .NET 开发人员雇主所需的专业知识关键领域。

就业市场变得非常具有竞争力。仅仅优秀已不足够;潜在雇主不能有任何理由说:“不行”。你不仅需要优秀,还需要满足所有要求,并了解最新的理论和术语。

除了基本要素(才能、培训、基础编程技能、经验、情商 - 软技能)之外,大多数 .NET 开发人员雇主还要求具备以下关键领域的经验:

  • DRY 原则
  • SOLID 原则
  • 单元测试
  • MVC(模型-视图-控制器)及相关技术
  • ORM(对象关系映射):Entity Framework、NHibernate 等。
  • IoC(控制反转)容器:Unity、Spring.Net 等。
  • DI(依赖注入)
  • 领域驱动设计 (DDD):初级至中级开发人员可能不需要,但高级开发人员绝对需要。
  • 面向方面编程 (AOP):初级至中级开发人员可能不需要,但高级开发人员绝对需要。

DRY 原则

DRY(Don't Repeat Yourself - 不要重复自己)众所周知;然而,它绝对是基础,并且融入了所有良好/稳健的工程模式/技术中。知识和逻辑不应重复;它们应该被重用,从而科学与技术通过伟大的头脑在前辈工作的基础上不断成长和发展。

DRY 在所有级别都很重要:字段、函数、类、组件。因此,SOLID 原则的一个主要目标是提高可重用性,并减少重复。

SOLID 原则

本系列文章和扑克项目将重点介绍 SOLID 原则及相关技术/模式的实现和说明。如果您还不熟悉 SOLID 原则,这里有一个简短易懂的介绍。

  • S - SRP:单一职责原则:一个类应该只有一个职责(即,软件规范的任何潜在变更都只能影响该类的规范)。根据定义,类中所有代码之间需要有高度的内聚性;所有代码必须协同工作以完成单一职责。重用多职责类是很有问题的,因为该类可能承担不必要的职责。
  • O - OCP:开闭原则:“软件实体……应该对扩展开放,对修改关闭。” 扩展/改进是好的;然而,功能的用户/使用者需要保证原始/核心功能保持不变。例如:除了已弃用的功能外,从一个 .NET Framework 升级到下一个 .NET Framework 很容易,因为原始功能保持不变。
  • L - LSP:里氏替换原则:“程序中的对象应该能够被其子类型的实例替换,而不会改变程序的正确性。” 该原则在很大程度上依赖于以下两个 SOLID 原则:开闭原则、依赖倒置原则。
  • I - ISP:接口隔离原则:“许多客户端特定的接口优于一个通用的接口。” 像类一样,接口也应该遵守单一职责原则;一个接口应该专注于单一职责。
  • D - DIP:依赖倒置原则:实体应“依赖于抽象,而非具体实现”。实体应该是松耦合的、松绑的或解耦的。更高级别的实体不应依赖于更低级别的实体,而应依赖于抽象/契约(接口、枚举、数据对象)。

参考维基百科 - SOLID - 面向对象设计

SOLID 原则总结

SOLID 是一个助记符,用于帮助记忆关键的面向对象设计原则。SOLID 原则和面向对象设计的主要目标是开发模块化代码,以便模块易于维护、扩展和替换。

例如:软件工程师通常欣赏计算机硬件并喜欢升级他们的个人电脑。目标是让代码像计算机硬件和计算机硬件子组件(电路板、电容器、电阻器等)一样模块化。这种相同的模块化在所有工程领域都有实现。

扑克规则与变体

扑克规则有许多变体。本项目将从以下规则开始:

  • 游戏玩法:五张牌抽牌:这是最简单、最知名的变体。
  • 牌型规则:确定扑克牌型以及牌型之间的比较方式。
    • 高牌规则
    • 无鬼牌

扑克阅读材料

依赖注入 - 扑克变体

随着项目的进展,将开发实现扑克规则变体的依赖项。然后,可以通过注入不同的依赖项来将软件从一个扑克变体更改为另一个,例如:可以将软件从五张牌抽牌更改为德州扑克。

原则、模式和实践的实现

在 SOLID Poker 项目的早期阶段,只实现了 DRY 和 SOLID 原则。

注意:SOLID 原则不适用于私有方法,但私有方法确实说明了 DRY 原则。

项目代码:注释

代码中的注释非常详细,并且变量和函数名称具有描述性。为了避免重复内容/注释,只有在确实有需要补充的地方才会提供额外的代码注释。

如果文章有不清楚或需要补充的地方,请发表评论。

项目代码:契约

实现扑克规则变体的依赖项将遵守 SOLIDPoker.Contract 项目中包含的契约。

  • Enums
    • CardSuit:梅花、方块、红桃等。
    • CardValue:J、Q、K、A 等。
    • PokerHandType:同花顺、四条等。
  • 接口
    • IPokerHandAssessor
  • DTO:数据传输对象,或简称为数据对象
    • Card
    • PokerHand

这些契约允许更高级别的实体根据抽象而非具体实现进行依赖,这符合依赖倒置原则(最后一个 SOLID 原则)。随着开发的继续,契约将得到扩展。

项目代码:确定扑克牌型

这段代码位于以下项目中:SOLIDPokerPokerHandMachine

排列扑克牌

这只是一个 private 函数,将被 public 函数重用,而这些 public 函数反过来满足实体之间的契约。

/// <summary>
/// The cards are arranged in ascending order, so that the Hand Type is easily seen.
/// If an Ace is to be used as a 1 (before 2) in a Straight, 
/// then the Ace will be at the beginning, otherwise, it will be at the end.
/// </summary>
/// <param name="pokerHand">The poker hand to be arranged.</param>
void ArrangeCards(PokerHand pokerHand)
{
    //First determine whether the poker hand is a Straight or a Straight Flush.
    //The IsStraight function also sorts the Poker Hand in ascending order.
    bool straight = IsStraight(pokerHand);

    //Move Aces to the end if:
    if (!straight || //Not a straight
        pokerHand[4].Value == CardValue.King)//Straight with a king at the end
    {
        //Move all Aces To the End
        while (pokerHand[0].Value == CardValue.Ace)
        {
            pokerHand.Add(pokerHand[0]);
            pokerHand.RemoveAt(0);
        }
    }
}

判断是否为顺子

这只是一个 private 函数,将被 public 函数重用,而这些 public 函数反过来满足实体之间的契约。

/// <summary>
/// Determines whether the card values are in sequence. 
/// The hand type would then be either Straight or Straight Flush.
/// </summary>
/// <param name="pokerHand">The poker hand to be evaluated.</param>
/// <returns>Boolean indicating whether the card values are in sequence.</returns>
bool IsStraight(PokerHand pokerHand)
{
    //Sort ascending
    pokerHand.Sort((pokerCard1, pokerCard2) => 
    pokerCard1.Value.CompareTo(pokerCard2.Value));

    //Determines whether the card values are in sequence.
    return
        //Check whether the last 4 cards are in sequence.
        pokerHand[1].Value == pokerHand[2].Value - 1 &&
        pokerHand[2].Value == pokerHand[3].Value - 1 &&
        pokerHand[3].Value == pokerHand[4].Value - 1
        &&
        (
        //Check that the first two cards are in sequence
        pokerHand[0].Value == pokerHand[1].Value - 1
        //or the first card is an Ace and the last card is a King.
        || pokerHand[0].Value==CardValue.Ace && pokerHand[4].Value==CardValue.King
        );
}

确定扑克牌型

这个函数是 public 的,用于满足 IPokerHandAssessor 接口(契约)之一的要求。

/// <summary>
/// Determines the poker hand type. For example: Straight Flush or Four of a Kind.
/// </summary>
/// <param name="pokerHand">The poker hand to be evaluated.</param>
/// <returns>The poker hand type.
/// For example: Straight Flush or Four of a Kind.</returns>
public PokerHandType DeterminePokerHandType(PokerHand pokerHand)
{
    //Check whether all cards are in the same suit
    bool allSameSuit = pokerHand.GroupBy(card => card.Suit).Count() == 1;

    //Check whether the Poker Hand Type is: Straight
    bool straight = IsStraight(pokerHand);

    //Determine Poker Hand Type
    if (allSameSuit && straight)
        return PokerHandType.StraightFlush;

    if (allSameSuit)
        return PokerHandType.Flush;

    if (straight)
        return PokerHandType.Straight;

    //Find sets of cards with the same value.
    //Example: QQQ KK
    List<int> sameCardSet1, sameCardSet2;
    FindSetsOfCardsWithSameValue(pokerHand, out sameCardSet1, out sameCardSet2);

    //Continue Determining Poker Hand Type
    if (sameCardSet1.Count == 4)
        return PokerHandType.FourOfAKind;

    if (sameCardSet1.Count + sameCardSet2.Count == 5)
        return PokerHandType.FullHouse;

    if (sameCardSet1.Count == 3)
        return PokerHandType.ThreeOfAKind;

    if (sameCardSet1.Count + sameCardSet2.Count == 4)
        return PokerHandType.TwoPair;

    if (sameCardSet1.Count == 2)
        return PokerHandType.Pair;

    return PokerHandType.HighCard;
}

Hello World

这个小程序唯一目的是测试和说明到目前为止创建的功能:确定扑克牌型。单元测试将在后续文章中进行。

Hello World 代码

这是两个简单的辅助函数,用于减少测试代码量。

/// <summary>
/// Determine the Poker Hand Type & write the result to the console.
/// </summary>
/// <param name="pokerHand"></param>
static void DeterminePokerHandType_and_writeToConsole(PokerHand pokerHand)
{
    IPokerHandAssessor assessor = new HighRules_NoJoker();
    PokerHandType handType = assessor.DeterminePokerHandType(pokerHand);
    Console.WriteLine("Poker hand type determined: " + EnumToTitle(handType));
}

/// <summary>
/// Converts an enum to a presentable title.
/// </summary>
/// <param name="enumToConvert">The enum to be converted.</param>
/// <returns>A presentable title.</returns>
static string EnumToTitle(Enum enumToConvert)
{
    return System.Text.RegularExpressions.Regex
    .Replace(enumToConvert.ToString(), "[A-Z]", " $0").Trim();
}

Hello World 构造函数

以下代码有太多的样板代码;这个问题将在本系列的第三部分通过开发一个扑克牌型生成器来解决。

static void Main(string[] args)
{
    Console.WriteLine("Hello World - SOLID Poker!");
    Console.WriteLine("");

    //Royal Flush
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ten },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Jack },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Queen },
        new Card { Suit = CardSuit.Spade, Value = CardValue.King },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace }
        ));

    //Flush
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Queen },
        new Card { Suit = CardSuit.Spade, Value = CardValue.King },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Two },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Three }
        ));

    //Straight
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ten },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Jack },
        new Card { Suit = CardSuit.Club, Value = CardValue.Queen },
        new Card { Suit = CardSuit.Spade, Value = CardValue.King },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace }
        ));

    //Four of a Kind
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Club, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Diamond, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Heart, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ten }
        ));

    //Full House
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Club, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Diamond, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Heart, Value = CardValue.Ten },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ten }
        ));

    //Three of a Kind
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Club, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Diamond, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Heart, Value = CardValue.Nine },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ten }
        ));

    //Two Pair
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Club, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Diamond, Value = CardValue.King },
        new Card { Suit = CardSuit.Heart, Value = CardValue.Ten },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ten }
        ));

    //Pair
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Club, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Diamond, Value = CardValue.King },
        new Card { Suit = CardSuit.Heart, Value = CardValue.Queen },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ten }
        ));

    //High Card
    DeterminePokerHandType_and_writeToConsole(new PokerHand(
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ace },
        new Card { Suit = CardSuit.Club, Value = CardValue.Nine },
        new Card { Suit = CardSuit.Diamond, Value = CardValue.King },
        new Card { Suit = CardSuit.Heart, Value = CardValue.Queen },
        new Card { Suit = CardSuit.Spade, Value = CardValue.Ten }
        ));

    Console.Read();
}

看见就说

目标是提供清晰、无错误的内容,对此您的帮助将不胜感激。如果您发现错误或潜在的改进之处,请务必发表评论。欢迎所有反馈。

摘要

SOLID Poker - 第一部分主要作为一篇介绍性文章,提供了本系列文章的背景和上下文。本文为后续文章奠定了坚实的基础,使后续文章可以更专注于代码。

希望本文能帮助你在下一次面试中脱颖而出,尤其是在 SOLID 原则这个话题上。

© . All rights reserved.