diff --git a/71_Poker/csharp/Cards/Card.cs b/71_Poker/csharp/Cards/Card.cs new file mode 100644 index 00000000..d3176213 --- /dev/null +++ b/71_Poker/csharp/Cards/Card.cs @@ -0,0 +1,11 @@ +namespace Poker.Cards; + +internal record struct Card (Rank Rank, Suit Suit) +{ + public override string ToString() => $"{Rank} of {Suit}"; + + public static bool operator <(Card x, Card y) => x.Rank < y.Rank; + public static bool operator >(Card x, Card y) => x.Rank > y.Rank; + + public static int operator -(Card x, Card y) => x.Rank - y.Rank; +} diff --git a/71_Poker/csharp/Cards/Deck.cs b/71_Poker/csharp/Cards/Deck.cs new file mode 100644 index 00000000..d3a101b4 --- /dev/null +++ b/71_Poker/csharp/Cards/Deck.cs @@ -0,0 +1,28 @@ +using static Poker.Cards.Rank; + +namespace Poker.Cards; + +internal class Deck +{ + private readonly Card[] _cards; + private int _nextCard; + + public Deck() + { + _cards = Ranks.SelectMany(r => Enum.GetValues().Select(s => new Card(r, s))).ToArray(); + } + + public void Shuffle(IRandom _random) + { + for (int i = 0; i < _cards.Length; i++) + { + var j = _random.Next(_cards.Length); + (_cards[i], _cards[j]) = (_cards[j], _cards[i]); + } + _nextCard = 0; + } + + public Card DealCard() => _cards[_nextCard++]; + + public Hand DealHand() => new Hand(Enumerable.Range(0, 5).Select(_ => DealCard())); +} diff --git a/71_Poker/csharp/Cards/Hand.cs b/71_Poker/csharp/Cards/Hand.cs new file mode 100644 index 00000000..34c14ac4 --- /dev/null +++ b/71_Poker/csharp/Cards/Hand.cs @@ -0,0 +1,131 @@ +using System.Text; +using static Poker.Cards.HandRank; +namespace Poker.Cards; + +internal class Hand +{ + public static readonly Hand Empty = new Hand(); + + private readonly Card[] _cards; + + private Hand() + { + _cards = Array.Empty(); + Rank = None; + } + + public Hand(IEnumerable cards) + : this(cards, isAfterDraw: false) + { + } + + private Hand(IEnumerable cards, bool isAfterDraw) + { + _cards = cards.ToArray(); + (Rank, HighCard, KeepMask) = Analyze(); + + IsWeak = Rank < PartialStraight + || Rank == PartialStraight && isAfterDraw + || Rank <= TwoPair && HighCard.Rank <= 6; + } + + public string Name => Rank.ToString(HighCard); + public HandRank Rank { get; } + public Card HighCard { get; } + public int KeepMask { get; set; } + public bool IsWeak { get; } + + public Hand Replace(int cardNumber, Card newCard) + { + if (cardNumber < 1 || cardNumber > _cards.Length) { return this; } + + _cards[cardNumber - 1] = newCard; + return new Hand(_cards, isAfterDraw: true); + } + + private (HandRank, Card, int) Analyze() + { + var suitMatchCount = 0; + for (var i = 0; i < _cards.Length; i++) + { + if (i < _cards.Length-1 && _cards[i].Suit == _cards[i+1].Suit) + { + suitMatchCount++; + } + } + if (suitMatchCount == 4) + { + return (Flush, _cards[0], 0b11111); + } + var sortedCards = _cards.OrderBy(c => c.Rank).ToArray(); + + var handRank = Schmaltz; + var keepMask = 0; + Card highCard = default; + for (var i = 0; i < sortedCards.Length - 1; i++) + { + var matchesNextCard = sortedCards[i].Rank == sortedCards[i+1].Rank; + var matchesPreviousCard = i > 0 && sortedCards[i].Rank == sortedCards[i - 1].Rank; + + if (matchesNextCard) + { + keepMask |= 0b11 << i; + highCard = sortedCards[i]; + handRank = matchesPreviousCard switch + { + _ when handRank < Pair => Pair, + true when handRank == Pair => Three, + _ when handRank == Pair => TwoPair, + _ when handRank == TwoPair => FullHouse, + true => Four, + _ => FullHouse + }; + } + } + if (keepMask == 0) + { + if (sortedCards[3] - sortedCards[0] == 3) + { + keepMask=0b1111; + handRank=PartialStraight; + } + if (sortedCards[4] - sortedCards[1] == 3) + { + if (handRank == PartialStraight) + { + return (Straight, sortedCards[4], 0b11111); + } + handRank=PartialStraight; + keepMask=0b11110; + } + } + return handRank < PartialStraight + ? (Schmaltz, sortedCards[4], 0b11000) + : (handRank, highCard, keepMask); + } + + public override string ToString() + { + var sb = new StringBuilder(); + for (var i = 0; i < _cards.Length; i++) + { + var cardDisplay = $" {i+1} -- {_cards[i]}"; + // Emulates the effect of the BASIC PRINT statement using the ',' to align text to 14-char print zones + sb.Append(cardDisplay.PadRight(cardDisplay.Length + 14 - cardDisplay.Length % 14)); + if (i % 2 == 1) + { + sb.AppendLine(); + } + } + sb.AppendLine(); + return sb.ToString(); + } + + public static bool operator >(Hand x, Hand y) => + x.Rank > y.Rank || + x.Rank == y.Rank && x.HighCard > y.HighCard; + + public static bool operator <(Hand x, Hand y) => + x.Rank < y.Rank || + x.Rank == y.Rank && x.HighCard < y.HighCard; +} diff --git a/71_Poker/csharp/Cards/HandRank.cs b/71_Poker/csharp/Cards/HandRank.cs new file mode 100644 index 00000000..878746ee --- /dev/null +++ b/71_Poker/csharp/Cards/HandRank.cs @@ -0,0 +1,34 @@ +namespace Poker.Cards; + +internal class HandRank +{ + public static HandRank None = new(0, ""); + public static HandRank Schmaltz = new(1, "schmaltz, ", c => $"{c.Rank} high"); + public static HandRank PartialStraight = new(2, ""); // The original code does not assign a display string here + public static HandRank Pair = new(3, "a pair of ", c => $"{c.Rank}'s"); + public static HandRank TwoPair = new(4, "two pair, ", c => $"{c.Rank}'s"); + public static HandRank Three = new(5, "three ", c => $"{c.Rank}'s"); + public static HandRank Straight = new(6, "straight", c => $"{c.Rank} high"); + public static HandRank Flush = new(7, "a flush in ", c => c.Suit.ToString()); + public static HandRank FullHouse = new(8, "full house, ", c => $"{c.Rank}'s"); + public static HandRank Four = new(9, "four ", c => $"{c.Rank}'s"); + // The original code does not detect a straight flush or royal flush + + private readonly int _value; + private readonly string _displayName; + private readonly Func _suffixSelector; + + private HandRank(int value, string displayName, Func? suffixSelector = null) + { + _value = value; + _displayName = displayName; + _suffixSelector = suffixSelector ?? (_ => ""); + } + + public string ToString(Card highCard) => $"{_displayName}{_suffixSelector.Invoke(highCard)}"; + + public static bool operator >(HandRank x, HandRank y) => x._value > y._value; + public static bool operator <(HandRank x, HandRank y) => x._value < y._value; + public static bool operator >=(HandRank x, HandRank y) => x._value >= y._value; + public static bool operator <=(HandRank x, HandRank y) => x._value <= y._value; +} diff --git a/71_Poker/csharp/Cards/Rank.cs b/71_Poker/csharp/Cards/Rank.cs new file mode 100644 index 00000000..cd44922a --- /dev/null +++ b/71_Poker/csharp/Cards/Rank.cs @@ -0,0 +1,50 @@ +namespace Poker.Cards; + +internal struct Rank : IComparable +{ + public static IEnumerable Ranks => new[] + { + Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King, Ace + }; + + public static Rank Two = new(2); + public static Rank Three = new(3); + public static Rank Four = new(4); + public static Rank Five = new(5); + public static Rank Six = new(6); + public static Rank Seven = new(7); + public static Rank Eight = new(8); + public static Rank Nine = new(9); + public static Rank Ten = new(10); + public static Rank Jack = new(11, "Jack"); + public static Rank Queen = new(12, "Queen"); + public static Rank King = new(13, "King"); + public static Rank Ace = new(14, "Ace"); + + private readonly int _value; + private readonly string _name; + + private Rank(int value, string? name = null) + { + _value = value; + _name = name ?? $" {value} "; + } + + public override string ToString() => _name; + + public int CompareTo(Rank other) => this - other; + + public static bool operator <(Rank x, Rank y) => x._value < y._value; + public static bool operator >(Rank x, Rank y) => x._value > y._value; + public static bool operator ==(Rank x, Rank y) => x._value == y._value; + public static bool operator !=(Rank x, Rank y) => x._value != y._value; + + public static int operator -(Rank x, Rank y) => x._value - y._value; + + public static bool operator <=(Rank rank, int value) => rank._value <= value; + public static bool operator >=(Rank rank, int value) => rank._value >= value; + + public override bool Equals(object? obj) => obj is Rank other && this == other; + + public override int GetHashCode() => _value.GetHashCode(); +} diff --git a/71_Poker/csharp/Cards/Suit.cs b/71_Poker/csharp/Cards/Suit.cs new file mode 100644 index 00000000..0a246999 --- /dev/null +++ b/71_Poker/csharp/Cards/Suit.cs @@ -0,0 +1,9 @@ +namespace Poker.Cards; + +internal enum Suit +{ + Clubs, + Diamonds, + Hearts, + Spades +} diff --git a/71_Poker/csharp/Game.cs b/71_Poker/csharp/Game.cs new file mode 100644 index 00000000..c7621a36 --- /dev/null +++ b/71_Poker/csharp/Game.cs @@ -0,0 +1,33 @@ +using Poker.Cards; +using Poker.Players; +using Poker.Resources; + +namespace Poker; + +internal class Game +{ + private readonly IReadWrite _io; + private readonly IRandom _random; + + public Game(IReadWrite io, IRandom random) + { + _io = io; + _random = random; + } + + internal void Play() + { + _io.Write(Resource.Streams.Title); + _io.Write(Resource.Streams.Instructions); + + var deck = new Deck(); + var human = new Human(200, _io); + var computer = new Computer(200, _io, _random); + var table = new Table(_io, _random, deck, human, computer); + + do + { + table.PlayHand(); + } while (table.ShouldPlayAnotherHand()); + } +} diff --git a/71_Poker/csharp/IReadWriteExtensions.cs b/71_Poker/csharp/IReadWriteExtensions.cs new file mode 100644 index 00000000..7559a328 --- /dev/null +++ b/71_Poker/csharp/IReadWriteExtensions.cs @@ -0,0 +1,48 @@ +using Poker.Strategies; +using static System.StringComparison; + +namespace Poker; + +internal static class IReadWriteExtensions +{ + internal static bool ReadYesNo(this IReadWrite io, string prompt) + { + while (true) + { + var response = io.ReadString(prompt); + if (response.Equals("YES", InvariantCultureIgnoreCase)) { return true; } + if (response.Equals("NO", InvariantCultureIgnoreCase)) { return false; } + io.WriteLine("Answer Yes or No, please."); + } + } + + internal static float ReadNumber(this IReadWrite io) => io.ReadNumber(""); + + internal static int ReadNumber(this IReadWrite io, string prompt, int max, string maxPrompt) + { + io.Write(prompt); + while (true) + { + var response = io.ReadNumber(); + if (response <= max) { return (int)response; } + io.WriteLine(maxPrompt); + } + } + + internal static Strategy ReadHumanStrategy(this IReadWrite io, bool noCurrentBets) + { + while(true) + { + io.WriteLine(); + var bet = io.ReadNumber("What is your bet"); + if (bet != (int)bet) + { + if (noCurrentBets && bet == .5) { return Strategy.Check; } + io.WriteLine("No small change, please."); + continue; + } + if (bet == 0) { return Strategy.Fold; } + return Strategy.Bet(bet); + } + } +} \ No newline at end of file diff --git a/71_Poker/csharp/Players/Computer.cs b/71_Poker/csharp/Players/Computer.cs new file mode 100644 index 00000000..5ab2e77d --- /dev/null +++ b/71_Poker/csharp/Players/Computer.cs @@ -0,0 +1,130 @@ +using Poker.Cards; +using Poker.Strategies; +using static System.StringComparison; + +namespace Poker.Players; + +internal class Computer : Player +{ + private readonly IReadWrite _io; + private readonly IRandom _random; + + public Computer(int bank, IReadWrite io, IRandom random) + : base(bank) + { + _io = io; + _random = random; + Strategy = Strategy.None; + } + + public Strategy Strategy { get; set; } + + public override void NewHand() + { + base.NewHand(); + + Strategy = (Hand.IsWeak, Hand.Rank < HandRank.Three, Hand.Rank < HandRank.FullHouse) switch + { + (true, _, _) when _random.Next(10) < 2 => Strategy.Bluff(23, 0b11100), + (true, _, _) when _random.Next(10) < 2 => Strategy.Bluff(23, 0b11110), + (true, _, _) when _random.Next(10) < 1 => Strategy.Bluff(23, 0b11111), + (true, _, _) => Strategy.Fold, + (false, true, _) => _random.Next(10) < 2 ? Strategy.Bluff(23) : Strategy.Check, + (false, false, true) => Strategy.Bet(35), + (false, false, false) => _random.Next(10) < 1 ? Strategy.Bet(35) : Strategy.Raise + }; + } + + protected override void DrawCards(Deck deck) + { + var keepMask = Strategy.KeepMask ?? Hand.KeepMask; + var count = 0; + for (var i = 1; i <= 5; i++) + { + if ((keepMask & (1 << (i - 1))) == 0) + { + Hand = Hand.Replace(i, deck.DealCard()); + count++; + } + } + + _io.WriteLine(); + _io.Write($"I am taking {count} card"); + if (count != 1) + { + _io.WriteLine("s"); + } + + Strategy = (Hand.IsWeak, Hand.Rank < HandRank.Three, Hand.Rank < HandRank.FullHouse) switch + { + _ when Strategy is Bluff => Strategy.Bluff(28), + (true, _, _) => Strategy.Fold, + (false, true, _) => _random.Next(10) == 0 ? Strategy.Bet(19) : Strategy.Raise, + (false, false, true) => _random.Next(10) == 0 ? Strategy.Bet(11) : Strategy.Bet(19), + (false, false, false) => Strategy.Raise + }; + } + + public int GetWager(int wager) + { + wager += _random.Next(10); + if (Balance < Table.Human.Bet + wager) + { + if (Table.Human.Bet == 0) { return Balance; } + + if (Balance >= Table.Human.Bet) + { + _io.WriteLine("I'll see you."); + Bet = Table.Human.Bet; + Table.CollectBets(); + } + else + { + RaiseFunds(); + } + } + + return wager; + } + + public bool TryBuyWatch() + { + if (!Table.Human.HasWatch) { return false; } + + var response = _io.ReadString("Would you like to sell your watch"); + if (response.StartsWith("N", InvariantCultureIgnoreCase)) { return false; } + + var (value, message) = (_random.Next(10) < 7) switch + { + true => (75, "I'll give you $75 for it."), + false => (25, "That's a pretty crummy watch - I'll give you $25.") + }; + + _io.WriteLine(message); + Table.Human.SellWatch(value); + // The original code does not have the computer part with any money + + return true; + } + + public void RaiseFunds() + { + if (Table.Human.HasWatch) { return; } + + var response = _io.ReadString("Would you like to buy back your watch for $50"); + if (response.StartsWith("N", InvariantCultureIgnoreCase)) { return; } + + // The original code does not deduct $50 from the player + Balance += 50; + Table.Human.ReceiveWatch(); + IsBroke = true; + } + + public void CheckFunds() { IsBroke = Balance <= Table.Ante; } + + public override void TakeWinnings() + { + _io.WriteLine("I win."); + base.TakeWinnings(); + } +} diff --git a/71_Poker/csharp/Players/Human.cs b/71_Poker/csharp/Players/Human.cs new file mode 100644 index 00000000..ec3e7d3c --- /dev/null +++ b/71_Poker/csharp/Players/Human.cs @@ -0,0 +1,90 @@ +using Poker.Cards; +using Poker.Strategies; + +namespace Poker.Players; + +internal class Human : Player +{ + private readonly IReadWrite _io; + + public Human(int bank, IReadWrite io) + : base(bank) + { + HasWatch = true; + _io = io; + } + + public bool HasWatch { get; set; } + + protected override void DrawCards(Deck deck) + { + var count = _io.ReadNumber("How many cards do you want", 3, "You can't draw more than three cards."); + if (count == 0) { return; } + + _io.WriteLine("What are their numbers:"); + for (var i = 1; i <= count; i++) + { + Hand = Hand.Replace((int)_io.ReadNumber(), deck.DealCard()); + } + + _io.WriteLine("Your new hand:"); + _io.Write(Hand); + } + + internal bool SetWager() + { + var strategy = _io.ReadHumanStrategy(Table.Computer.Bet == 0 && Bet == 0); + if (strategy is Strategies.Bet or Check) + { + if (Bet + strategy.Value < Table.Computer.Bet) + { + _io.WriteLine("If you can't see my bet, then fold."); + return false; + } + if (Balance - Bet - strategy.Value >= 0) + { + HasBet = true; + Bet += strategy.Value; + return true; + } + RaiseFunds(); + } + else + { + Fold(); + Table.CollectBets(); + } + return false; + } + + public void RaiseFunds() + { + _io.WriteLine(); + _io.WriteLine("You can't bet with what you haven't got."); + + if (Table.Computer.TryBuyWatch()) { return; } + + // The original program had some code about selling a tie tack, but due to a fault + // in the logic the code was unreachable. I've omitted it in this port. + + IsBroke = true; + } + + public void ReceiveWatch() + { + // In the original code the player does not pay any money to receive the watch back. + HasWatch = true; + } + + public void SellWatch(int amount) + { + HasWatch = false; + Balance += amount; + } + + public override void TakeWinnings() + { + _io.WriteLine("You win."); + base.TakeWinnings(); + } +} diff --git a/71_Poker/csharp/Players/Player.cs b/71_Poker/csharp/Players/Player.cs new file mode 100644 index 00000000..65e7e685 --- /dev/null +++ b/71_Poker/csharp/Players/Player.cs @@ -0,0 +1,59 @@ +using Poker.Cards; + +namespace Poker.Players; + +internal abstract class Player +{ + private Table? _table; + private bool _hasFolded; + + protected Player(int bank) + { + Hand = Hand.Empty; + Balance = bank; + } + + public Hand Hand { get; set; } + public int Balance { get; set; } + public bool HasBet { get; set; } + public int Bet { get; set; } + public bool HasFolded => _hasFolded; + public bool IsBroke { get; protected set; } + + protected Table Table => + _table ?? throw new InvalidOperationException("The player must be sitting at the table."); + + public void Sit(Table table) => _table = table; + + public virtual void NewHand() + { + Bet = 0; + Hand = Table.Deck.DealHand(); + _hasFolded = false; + } + + public int AnteUp() + { + Balance -= Table.Ante; + return Table.Ante; + } + + public void DrawCards() + { + Bet = 0; + DrawCards(Table.Deck); + } + + protected abstract void DrawCards(Deck deck); + + public virtual void TakeWinnings() + { + Balance += Table.Pot; + Table.Pot = 0; + } + + public void Fold() + { + _hasFolded = true; + } +} diff --git a/71_Poker/csharp/Poker.csproj b/71_Poker/csharp/Poker.csproj index d3fe4757..3870320c 100644 --- a/71_Poker/csharp/Poker.csproj +++ b/71_Poker/csharp/Poker.csproj @@ -6,4 +6,12 @@ enable enable + + + + + + + + diff --git a/71_Poker/csharp/Program.cs b/71_Poker/csharp/Program.cs new file mode 100644 index 00000000..c3200a96 --- /dev/null +++ b/71_Poker/csharp/Program.cs @@ -0,0 +1,5 @@ +global using Games.Common.IO; +global using Games.Common.Randomness; +global using Poker; + +new Game(new ConsoleIO(), new RandomNumberGenerator()).Play(); \ No newline at end of file diff --git a/71_Poker/csharp/Resources/Instructions.txt b/71_Poker/csharp/Resources/Instructions.txt new file mode 100644 index 00000000..7f82c07e --- /dev/null +++ b/71_Poker/csharp/Resources/Instructions.txt @@ -0,0 +1,5 @@ +Welcome to the casino. We each have $200. +I will open the betting before the draw; you open after. +To fold bet 0; to check bet .5. +Enough talk -- Let's get down to business. + diff --git a/71_Poker/csharp/Resources/Resource.cs b/71_Poker/csharp/Resources/Resource.cs new file mode 100644 index 00000000..0ceddb0d --- /dev/null +++ b/71_Poker/csharp/Resources/Resource.cs @@ -0,0 +1,17 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Poker.Resources; + +internal static class Resource +{ + internal static class Streams + { + public static Stream Instructions => GetStream(); + public static Stream Title => GetStream(); + } + + private static Stream GetStream([CallerMemberName] string? name = null) + => Assembly.GetExecutingAssembly().GetManifestResourceStream($"Poker.Resources.{name}.txt") + ?? throw new ArgumentException($"Resource stream {name} does not exist", nameof(name)); +} \ No newline at end of file diff --git a/71_Poker/csharp/Resources/Title.txt b/71_Poker/csharp/Resources/Title.txt new file mode 100644 index 00000000..2f5c6aa2 --- /dev/null +++ b/71_Poker/csharp/Resources/Title.txt @@ -0,0 +1,5 @@ + Poker + Creative Computing Morristown, New Jersey + + + diff --git a/71_Poker/csharp/Strategies/Bet.cs b/71_Poker/csharp/Strategies/Bet.cs new file mode 100644 index 00000000..7299898d --- /dev/null +++ b/71_Poker/csharp/Strategies/Bet.cs @@ -0,0 +1,8 @@ +namespace Poker.Strategies; + +internal class Bet : Strategy +{ + public Bet(int amount) => Value = amount; + + public override int Value { get; } +} diff --git a/71_Poker/csharp/Strategies/Bluff.cs b/71_Poker/csharp/Strategies/Bluff.cs new file mode 100644 index 00000000..ed51e33e --- /dev/null +++ b/71_Poker/csharp/Strategies/Bluff.cs @@ -0,0 +1,12 @@ +namespace Poker.Strategies; + +internal class Bluff : Bet +{ + public Bluff(int amount, int? keepMask) + : base(amount) + { + KeepMask = keepMask; + } + + public override int? KeepMask { get; } +} \ No newline at end of file diff --git a/71_Poker/csharp/Strategies/Check.cs b/71_Poker/csharp/Strategies/Check.cs new file mode 100644 index 00000000..4bbab973 --- /dev/null +++ b/71_Poker/csharp/Strategies/Check.cs @@ -0,0 +1,6 @@ +namespace Poker.Strategies; + +internal class Check : Strategy +{ + public override int Value => 0; +} diff --git a/71_Poker/csharp/Strategies/Fold.cs b/71_Poker/csharp/Strategies/Fold.cs new file mode 100644 index 00000000..2ba31c33 --- /dev/null +++ b/71_Poker/csharp/Strategies/Fold.cs @@ -0,0 +1,6 @@ +namespace Poker.Strategies; + +internal class Fold : Strategy +{ + public override int Value => -1; +} diff --git a/71_Poker/csharp/Strategies/None.cs b/71_Poker/csharp/Strategies/None.cs new file mode 100644 index 00000000..4be351a8 --- /dev/null +++ b/71_Poker/csharp/Strategies/None.cs @@ -0,0 +1,6 @@ +namespace Poker.Strategies; + +internal class None : Strategy +{ + public override int Value => -1; +} diff --git a/71_Poker/csharp/Strategies/Raise.cs b/71_Poker/csharp/Strategies/Raise.cs new file mode 100644 index 00000000..7be9e522 --- /dev/null +++ b/71_Poker/csharp/Strategies/Raise.cs @@ -0,0 +1,6 @@ +namespace Poker.Strategies; + +internal class Raise : Bet +{ + public Raise() : base(2) { } +} diff --git a/71_Poker/csharp/Strategies/Strategy.cs b/71_Poker/csharp/Strategies/Strategy.cs new file mode 100644 index 00000000..ae6c93d2 --- /dev/null +++ b/71_Poker/csharp/Strategies/Strategy.cs @@ -0,0 +1,15 @@ +namespace Poker.Strategies; + +internal abstract class Strategy +{ + public static Strategy None = new None(); + public static Strategy Fold = new Fold(); + public static Strategy Check = new Check(); + public static Strategy Raise = new Raise(); + public static Strategy Bet(float amount) => new Bet((int)amount); + public static Strategy Bet(int amount) => new Bet(amount); + public static Strategy Bluff(int amount, int? keepMask = null) => new Bluff(amount, keepMask); + + public abstract int Value { get; } + public virtual int? KeepMask { get; } +} diff --git a/71_Poker/csharp/Table.cs b/71_Poker/csharp/Table.cs new file mode 100644 index 00000000..da819801 --- /dev/null +++ b/71_Poker/csharp/Table.cs @@ -0,0 +1,214 @@ +using Poker.Cards; +using Poker.Players; +using Poker.Strategies; + +namespace Poker; + +internal class Table +{ + private readonly IReadWrite _io; + private readonly IRandom _random; + public int Pot; + + public Table(IReadWrite io, IRandom random, Deck deck, Human human, Computer computer) + { + _io = io; + _random = random; + Deck = deck; + Human = human; + Computer = computer; + + human.Sit(this); + computer.Sit(this); + } + + public int Ante { get; } = 5; + public Deck Deck { get; } + public Human Human { get; } + public Computer Computer { get; } + + internal void PlayHand() + { + while (true) + { + _io.WriteLine(); + Computer.CheckFunds(); + if (Computer.IsBroke) { return; } + + _io.WriteLine($"The ante is ${Ante}. I will deal:"); + _io.WriteLine(); + if (Human.Balance <= Ante) + { + Human.RaiseFunds(); + if (Human.IsBroke) { return; } + } + + Deal(_random); + + _io.WriteLine(); + GetWagers("I'll open with ${0}", "I check.", allowRaiseAfterCheck: true); + if (SomeoneIsBroke() || SomeoneHasFolded()) { return; } + + Draw(); + + GetWagers(); + if (SomeoneIsBroke()) { return; } + if (!Human.HasBet) + { + GetWagers("I'll bet ${0}", "I'll check"); + } + if (SomeoneIsBroke() || SomeoneHasFolded()) { return; } + if (GetWinner() is { } winner) + { + winner.TakeWinnings(); + return; + } + } + } + + private void Deal(IRandom random) + { + Deck.Shuffle(random); + + Pot = Human.AnteUp() + Computer.AnteUp(); + + Human.NewHand(); + Computer.NewHand(); + + _io.WriteLine("Your hand:"); + _io.Write(Human.Hand); + } + + private void Draw() + { + _io.WriteLine(); + _io.Write("Now we draw -- "); + Human.DrawCards(); + Computer.DrawCards(); + _io.WriteLine(); + } + + private void GetWagers(string betFormat, string checkMessage, bool allowRaiseAfterCheck = false) + { + if (Computer.Strategy is Bet) + { + Computer.Bet = Computer.GetWager(Computer.Strategy.Value); + if (Computer.IsBroke) { return; } + + _io.WriteLine(betFormat, Computer.Bet); + } + else + { + _io.WriteLine(checkMessage); + if (!allowRaiseAfterCheck) { return; } + } + + GetWagers(); + } + + private void GetWagers() + { + while (true) + { + Human.HasBet = false; + while (true) + { + if (Human.SetWager()) { break; } + if (Human.IsBroke || Human.HasFolded) { return; } + } + if (Human.Bet == Computer.Bet) + { + CollectBets(); + return; + } + if (Computer.Strategy is Fold) + { + if (Human.Bet > 5) + { + Computer.Fold(); + _io.WriteLine("I fold."); + return; + } + } + if (Human.Bet > 3 * Computer.Strategy.Value) + { + if (Computer.Strategy is not Raise) + { + _io.WriteLine("I'll see you."); + Computer.Bet = Human.Bet; + CollectBets(); + return; + } + } + + var raise = Computer.GetWager(Human.Bet - Computer.Bet); + if (Computer.IsBroke) { return; } + _io.WriteLine($"I'll see you, and raise you {raise}"); + Computer.Bet = Human.Bet + raise; + } + } + + internal void CollectBets() + { + Human.Balance -= Human.Bet; + Computer.Balance -= Computer.Bet; + Pot += Human.Bet + Computer.Bet; + } + + private bool SomeoneHasFolded() + { + if (Human.HasFolded) + { + _io.WriteLine(); + Computer.TakeWinnings(); + } + else if (Computer.HasFolded) + { + _io.WriteLine(); + Human.TakeWinnings(); + } + else + { + return false; + } + + Pot = 0; + return true; + } + + private bool SomeoneIsBroke() => Human.IsBroke || Computer.IsBroke; + + private Player? GetWinner() + { + _io.WriteLine(); + _io.WriteLine("Now we compare hands:"); + _io.WriteLine("My hand:"); + _io.Write(Computer.Hand); + _io.WriteLine(); + _io.WriteLine($"You have {Human.Hand.Name}"); + _io.WriteLine($"and I have {Computer.Hand.Name}"); + if (Computer.Hand > Human.Hand) { return Computer; } + if (Human.Hand > Computer.Hand) { return Human; } + _io.WriteLine("The hand is drawn."); + _io.WriteLine($"All $ {Pot} remains in the pot."); + return null; + } + + internal bool ShouldPlayAnotherHand() + { + if (Computer.IsBroke) + { + _io.WriteLine("I'm busted. Congratulations!"); + return true; + } + + if (Human.IsBroke) + { + _io.WriteLine("Your wad is shot. So long, sucker!"); + return true; + } + + _io.WriteLine($"Now I have $ {Computer.Balance} and you have $ {Human.Balance}"); + return _io.ReadYesNo("Do you wish to continue"); + } +} \ No newline at end of file