diff --git a/71_Poker/csharp/Games.cs b/71_Poker/csharp/Games.cs index 28d69e2e..099690d0 100644 --- a/71_Poker/csharp/Games.cs +++ b/71_Poker/csharp/Games.cs @@ -8,15 +8,11 @@ internal class Game private readonly IReadWrite _io; private readonly IRandom _random; - private int _playerBet; private int _playerTotalBet; - private int I; private int Z; private int _computerTotalBet; private int V; - private bool _playerFolds; - private bool _computerFolds; public Game(IReadWrite io, IRandom random) { @@ -29,8 +25,8 @@ internal class Game internal void Play() { var deck = new Deck(); - var human = new Human(200); - var computer = new Computer(200); + var human = new Human(200, _io); + var computer = new Computer(200, _io, _random); var table = new Table(_io, deck, human, computer); _io.Write(Resource.Streams.Title); @@ -44,8 +40,6 @@ internal class Game internal bool PlayHand(Table table) { - _playerFolds = _computerFolds = false; - table.Pot=0; while(true) { _io.WriteLine(); @@ -56,47 +50,23 @@ internal class Game } _io.WriteLine("The ante is $5. I will deal:"); _io.WriteLine(); - if (table.Human.Balance <= 5 && PlayerIsBroke()) { return false; } + if (table.Human.Balance <= 5 && table.Human.IsBroke()) { return false; } table.Deal(); - (table.Computer.KeepMask, I) = table.Computer.Hand.Analyze(2); _io.WriteLine(); - if (I == 6) + Z = table.Computer.Hand.Rank switch { - Z=1; - if (Get0To9() > 7) - { - table.Computer.KeepMask = 0b11100; - I=7; - Z=23; - } - else if (Get0To9() > 7) - { - table.Computer.KeepMask = 0b11110; - I=7; - Z=23; - } - else if (Get0To9() < 1) - { - table.Computer.KeepMask = 0b11111; - I=7; - Z=23; - } - } - else - { - Z=0; - if (table.Computer.Hand.Rank >= 13) - { - Z = table.Computer.Hand.Rank <= 16 || Get0To9() < 1 ? 35 : 2; - } - else if (Get0To9() < 2) - { - I=7; - Z=23; - } - } + _ when table.Computer.Hand.IsWeak => + table.Computer.BluffIf(Get0To9() > 7, 0b11100) ?? + table.Computer.BluffIf(Get0To9() > 7, 0b11110) ?? + table.Computer.BluffIf(Get0To9() < 1, 0b11111) ?? + 1, + < 13 => table.Computer.BluffIf(Get0To9() < 2) ?? 0, + <= 16 => 35, + _ when Get0To9() < 1 => 35, + _ => 2 + }; if (Z <= 1) { _computerTotalBet = 0; @@ -111,43 +81,27 @@ internal class Game _playerTotalBet = 0; } if (GetWager()) { return false; } - if (CheckIfSomeoneFolded() is {} response) { return response; } + if (table.SomeoneHasFolded()) { return ShouldContinue(); } table.Draw(); - V=I; - (_, I) = table.Computer.Hand.Analyze(1); - if (V == 7) + Z = table.Computer.Hand.Rank switch { - Z = 28; - } - else if (I == 6) - { - Z = 1; - } - else if (table.Computer.Hand.Rank < 13) - { - Z = Get0To9() == 6 ? 19 : 2; - } - else - { - if (table.Computer.Hand.Rank >= 16) - { - Z = 2; - } - else - { - Z = Get0To9() == 8 ? 11 : 19; - } - } + _ when table.Computer.IsBluffing => 28, + _ when table.Computer.Hand.IsWeak => 1, + < 13 => Get0To9() == 0 ? 19 : 2, + < 16 => Get0To9() == 8 ? 11 : 19, + _ => 2 + }; + _computerTotalBet = 0; _playerTotalBet = 0; if (GetWager()) { return false; } if (_playerBet != 0) { - if (CheckIfSomeoneFolded() is {} response2) { return response2; } + if (table.SomeoneHasFolded()) { return ShouldContinue(); } } - else if (V != 7 && I == 6) + else if (!table.Computer.IsBluffing && table.Computer.Hand.IsWeak) { _io.WriteLine("I'll check"); } @@ -158,60 +112,21 @@ internal class Game _io.WriteLine($"I'll bet ${V}"); _computerTotalBet = V; if (GetWager()) { return false; } - if (CheckIfSomeoneFolded() is {} response3) { return response3; } + if (table.SomeoneHasFolded()) { return ShouldContinue(); } } - _io.WriteLine(); - _io.WriteLine("Now we compare hands:"); - _io.WriteLine("My hand:"); - _io.Write(table.Computer.Hand); - table.Human.Hand.Analyze(0); - _io.WriteLine(); - _io.Write($"You have {table.Human.Hand.Name}"); - _io.Write($"and I have {table.Computer.Hand.Name}"); - if (table.Computer.Hand > table.Human.Hand) { return ComputerWins(); } - if (table.Human.Hand > table.Computer.Hand) { return PlayerWins(); } - _io.WriteLine("The hand is drawn."); - _io.WriteLine($"All $ {table.Pot} remains in the pot."); - } - - bool? CheckIfSomeoneFolded() - { - if (_playerFolds) + if (table.GetWinner() is { } winner) { - _io.WriteLine(); - return ComputerWins(); + winner.TakeWinnings(); + return ShouldContinue(); } - else if (_computerFolds) - { - _io.WriteLine(); - return PlayerWins(); - } - else - { - return null; - } - } - - bool ComputerWins() - { - _io.WriteLine("I win."); - table.Computer.Balance += table.Pot; - return ShouldContinue(); } bool ShouldContinue() { - _io.WriteLine($"Now I have ${table.Computer.Balance}and you have ${table.Human.Balance}"); + _io.WriteLine($"Now I have $ {table.Computer.Balance} and you have $ {table.Human.Balance} "); return _io.ReadYesNo("Do you wish to continue"); } - bool PlayerWins() - { - _io.WriteLine("You win."); - table.Human.Balance += table.Pot; - return ShouldContinue(); - } - bool GetWager() { _playerBet = 0; @@ -230,12 +145,12 @@ internal class Game _playerTotalBet += bet.Amount; break; } - if (PlayerIsBroke()) { return true; } + if (table.Human.IsBroke()) { return true; } continue; } else { - _playerFolds = true; + table.Human.Fold(); return UpdatePot(); } } @@ -244,7 +159,7 @@ internal class Game { if (_playerTotalBet > 5) { - _computerFolds = true; + table.Computer.Fold(); _io.WriteLine("I fold."); return false; } @@ -300,7 +215,7 @@ internal class Game { return Line_3360(); } - else if (table.Computer.TrySellWatch(table.Human, _io)) + else if (table.Computer.TrySellWatch(table.Human)) { return false; } @@ -312,20 +227,6 @@ internal class Game _io.WriteLine("I'm busted. Congratulations!"); return true; } - - bool PlayerIsBroke() - { - _io.WriteLine(); - _io.WriteLine("You can't bet with what you haven't got."); - - if (Computer.TryBuyWatch(table.Human, _io, _random)) { return false; } - - // 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. - - _io.WriteLine("Your wad is shot. So long, sucker!"); - return true; - } } } @@ -341,6 +242,9 @@ internal record Bet (int Amount) : IAction internal abstract class Player { + private Table? _table; + private bool _hasFolded; + protected Player(int bank) { Hand = Hand.Empty; @@ -350,36 +254,76 @@ internal abstract class Player public Hand Hand { get; set; } public int Balance { get; set; } public int Bet { get; private set; } + public bool HasFolded => _hasFolded; + + 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(Hand hand) + { + Hand = hand; + _hasFolded = false; + } public void Pay(int amount) { Balance -= amount; } + + public virtual void TakeWinnings() + { + Balance += Table.Pot; + Table.Pot = 0; + } + + public void Fold() + { + _hasFolded = true; + } } internal class Human : Player { - public Human(int bank) + private readonly IReadWrite _io; + + public Human(int bank, IReadWrite io) : base(bank) { HasWatch = true; + _io = io; } public bool HasWatch { get; set; } - public void DrawCards(Deck deck, IReadWrite io) + public void DrawCards(Deck deck) { - var count = io.ReadNumber("How many cards do you want", 3, "You can't draw more than three cards."); + 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:"); + _io.WriteLine("What are their numbers:"); for (var i = 1; i <= count; i++) { - Hand = Hand.Replace((int)io.ReadNumber(), deck.DealCard()); + Hand = Hand.Replace((int)_io.ReadNumber(), deck.DealCard()); } - io.WriteLine("Your new hand:"); - io.Write(Hand); + _io.WriteLine("Your new hand:"); + _io.Write(Hand); + } + + public bool IsBroke() + { + _io.WriteLine(); + _io.WriteLine("You can't bet with what you haven't got."); + + if (Table.Computer.TryBuyWatch(this)) { return false; } + + // 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. + + _io.WriteLine("Your wad is shot. So long, sucker!"); + return true; } public void ReceiveWatch() @@ -393,61 +337,90 @@ internal class Human : Player HasWatch = false; Balance += amount; } + + public override void TakeWinnings() + { + _io.WriteLine("You win."); + base.TakeWinnings(); + } } internal class Computer : Player { - public Computer(int bank) + private readonly IReadWrite _io; + private readonly IRandom _random; + private bool _isBluffing; + + public Computer(int bank, IReadWrite io, IRandom random) : base(bank) { + _io = io; + _random = random; } - public int KeepMask { get; set; } + public bool IsBluffing => _isBluffing; - public void DrawCards(Deck deck, IReadWrite io) + public override void NewHand(Hand hand) { + base.NewHand(hand); + _isBluffing = false; + } + + public int? BluffIf(bool shouldBluff, int? keepMask = null) + { + if (!shouldBluff) { return null; } + + _isBluffing = true; + Hand.KeepMask = keepMask ?? Hand.KeepMask; + return 23; + } + + public void DrawCards(Deck deck) + { + var keepMask = Hand.KeepMask; var count = 0; for (var i = 1; i <= 5; i++) { - if ((KeepMask & (1 << (i - 1))) == 0) + if ((keepMask & (1 << (i - 1))) == 0) { Hand = Hand.Replace(i, deck.DealCard()); count++; } } - io.WriteLine(); - io.Write($"I am taking {count} card"); + _io.WriteLine(); + _io.Write($"I am taking {count} card"); if (count != 1) { - io.WriteLine("s"); + _io.WriteLine("s"); } } - public static bool TryBuyWatch(Human human, IReadWrite io, IRandom random) + public bool TryBuyWatch(Human human) { if (!human.HasWatch) { return false; } - var response = io.ReadString("Would you like to sell your watch"); + 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 + 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); + _io.WriteLine(message); human.SellWatch(value); + // The original code does not have the computer part with any money return true; } - public bool TrySellWatch(Human human, IReadWrite io) + public bool TrySellWatch(Human human) { if (human.HasWatch) { return false; } - var response = io.ReadString("Would you like to buy back your watch for $50"); + var response = _io.ReadString("Would you like to buy back your watch for $50"); if (response.StartsWith("N", InvariantCultureIgnoreCase)) { return false; } // The original code does not deduct $50 from the player @@ -455,6 +428,12 @@ internal class Computer : Player human.ReceiveWatch(); return true; } + + public override void TakeWinnings() + { + _io.WriteLine("I win."); + base.TakeWinnings(); + } } internal class Table @@ -469,6 +448,9 @@ internal class Table _deck = deck; Human = human; Computer = computer; + + human.Sit(this); + computer.Sit(this); } public Human Human { get; } @@ -476,12 +458,12 @@ internal class Table public void Deal() { - Pot += 10; + Pot = 10; Human.Pay(5); Computer.Pay(5); - Human.Hand = _deck.DealHand(); - Computer.Hand = _deck.DealHand(); + Human.NewHand(_deck.DealHand()); + Computer.NewHand(_deck.DealHand()); _io.WriteLine("Your hand:"); _io.Write(Human.Hand); @@ -491,8 +473,8 @@ internal class Table { _io.WriteLine(); _io.Write("Now we draw -- "); - Human.DrawCards(_deck, _io); - Computer.DrawCards(_deck, _io); + Human.DrawCards(_deck); + Computer.DrawCards(_deck); _io.WriteLine(); } @@ -500,4 +482,41 @@ internal class Table { } + + public bool SomeoneHasFolded() + { + if (Human.HasFolded) + { + _io.WriteLine(); + Computer.TakeWinnings(); + } + else if (Computer.HasFolded) + { + _io.WriteLine(); + Human.TakeWinnings(); + } + else + { + return false; + } + + Pot = 0; + return true; + } + + public Player? GetWinner() + { + _io.WriteLine(); + _io.WriteLine("Now we compare hands:"); + _io.WriteLine("My hand:"); + _io.Write(Computer.Hand); + _io.WriteLine(); + _io.Write($"You have {Human.Hand.Name}"); + _io.Write($"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; + } } \ No newline at end of file diff --git a/71_Poker/csharp/Hand.cs b/71_Poker/csharp/Hand.cs index c806e490..6e078089 100644 --- a/71_Poker/csharp/Hand.cs +++ b/71_Poker/csharp/Hand.cs @@ -7,42 +7,48 @@ internal class Hand public static readonly Hand Empty = new Hand(); private readonly Card[] _cards; - private readonly Card _highCard; private readonly string _name1; private readonly string _name2; - private readonly int _keepMask; - private readonly Func _iTransform; private Hand() { _cards = Array.Empty(); _name1 = ""; _name2 = ""; - _iTransform = Identity; Name = ""; } public Hand(IEnumerable cards) + : this(cards, isAfterDraw: false) + { + } + + private Hand(IEnumerable cards, bool isAfterDraw) { _cards = cards.ToArray(); - (Rank, _name1, _name2, _highCard, _keepMask, _iTransform) = Analyze(); + (Rank, _name1, _name2, HighCard, KeepMask) = Analyze(); Name = GetHandName(); + + IsWeak = Rank < 10 + || Rank == 10 && isAfterDraw + || Rank <= 12 && HighCard.Rank <= 6; } public string Name { get; } public int 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); + return new Hand(_cards, isAfterDraw: true); } - public (int, int) Analyze(int i) => (_keepMask, _iTransform(i)); - - private (int, string, string, Card, int, Func) Analyze() + private (int, string, string, Card, int) Analyze() { var suitMatchCount = 0; for (var i = 0; i < _cards.Length; i++) @@ -54,7 +60,7 @@ internal class Hand } if (suitMatchCount == 4) { - return (15, "A Flus", "h in", _cards[0], 0b11111, Identity); + return (15, "A Flus", "h in", _cards[0], 0b11111); } var sortedCards = _cards.OrderBy(c => c.Rank).ToArray(); @@ -92,42 +98,27 @@ internal class Hand { if (handRank == 10) { - return (14, "Straig", "ht", sortedCards[4], 0b11111, Identity); + return (14, "Straig", "ht", sortedCards[4], 0b11111); } handRank=10; keepMask=0b11110; } } - if (handRank < 10) - { - return (9, "Schmal", "tz, ", sortedCards[4], 0b11000, To6); - } - var iTransform = Identity; - if (handRank == 10) - { - iTransform = To6If1; - } - else if (handRank <= 12 && highCard.Rank <= 6) - { - iTransform = To6; - } - return (handRank, handName1, handName2, highCard, keepMask, iTransform); + return handRank < 10 + ? (9, "Schmal", "tz, ", sortedCards[4], 0b11000) + : (handRank, handName1, handName2, highCard, keepMask); } - private int Identity(int x) => x; - private int To6(int _) => 6; - private int To6If1(int x) => x == 1 ? 6 : x; - private string GetHandName() { var sb = new StringBuilder(_name1).Append(_name2); if (_name1 == "A Flus") { - sb.Append(_highCard.Suit).AppendLine(); + sb.Append(HighCard.Suit).AppendLine(); } else { - sb.Append(_highCard.Rank) + sb.Append(HighCard.Rank) .AppendLine(_name1 == "Schmal" || _name1 == "Straig" ? " High" : "'s"); } return sb.ToString(); @@ -152,9 +143,9 @@ internal class Hand public static bool operator >(Hand x, Hand y) => x.Rank > y.Rank || - x.Rank == y.Rank && x._highCard > y._highCard; + 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; + x.Rank == y.Rank && x.HighCard < y.HighCard; }