mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-01-07 02:24:33 -08:00
Merge pull request #770 from drewjcooper/csharp-71-poker
csharp 71 poker
This commit is contained in:
11
71_Poker/csharp/Cards/Card.cs
Normal file
11
71_Poker/csharp/Cards/Card.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
28
71_Poker/csharp/Cards/Deck.cs
Normal file
28
71_Poker/csharp/Cards/Deck.cs
Normal file
@@ -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<Suit>().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()));
|
||||||
|
}
|
||||||
131
71_Poker/csharp/Cards/Hand.cs
Normal file
131
71_Poker/csharp/Cards/Hand.cs
Normal file
@@ -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<Card>();
|
||||||
|
Rank = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Hand(IEnumerable<Card> cards)
|
||||||
|
: this(cards, isAfterDraw: false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private Hand(IEnumerable<Card> 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;
|
||||||
|
}
|
||||||
34
71_Poker/csharp/Cards/HandRank.cs
Normal file
34
71_Poker/csharp/Cards/HandRank.cs
Normal file
@@ -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<Card, string> _suffixSelector;
|
||||||
|
|
||||||
|
private HandRank(int value, string displayName, Func<Card, string>? 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;
|
||||||
|
}
|
||||||
50
71_Poker/csharp/Cards/Rank.cs
Normal file
50
71_Poker/csharp/Cards/Rank.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
namespace Poker.Cards;
|
||||||
|
|
||||||
|
internal struct Rank : IComparable<Rank>
|
||||||
|
{
|
||||||
|
public static IEnumerable<Rank> 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();
|
||||||
|
}
|
||||||
9
71_Poker/csharp/Cards/Suit.cs
Normal file
9
71_Poker/csharp/Cards/Suit.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Poker.Cards;
|
||||||
|
|
||||||
|
internal enum Suit
|
||||||
|
{
|
||||||
|
Clubs,
|
||||||
|
Diamonds,
|
||||||
|
Hearts,
|
||||||
|
Spades
|
||||||
|
}
|
||||||
33
71_Poker/csharp/Game.cs
Normal file
33
71_Poker/csharp/Game.cs
Normal file
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
48
71_Poker/csharp/IReadWriteExtensions.cs
Normal file
48
71_Poker/csharp/IReadWriteExtensions.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
130
71_Poker/csharp/Players/Computer.cs
Normal file
130
71_Poker/csharp/Players/Computer.cs
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
90
71_Poker/csharp/Players/Human.cs
Normal file
90
71_Poker/csharp/Players/Human.cs
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
59
71_Poker/csharp/Players/Player.cs
Normal file
59
71_Poker/csharp/Players/Player.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,4 +6,12 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="Resources/*.txt" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
5
71_Poker/csharp/Program.cs
Normal file
5
71_Poker/csharp/Program.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
global using Games.Common.IO;
|
||||||
|
global using Games.Common.Randomness;
|
||||||
|
global using Poker;
|
||||||
|
|
||||||
|
new Game(new ConsoleIO(), new RandomNumberGenerator()).Play();
|
||||||
5
71_Poker/csharp/Resources/Instructions.txt
Normal file
5
71_Poker/csharp/Resources/Instructions.txt
Normal file
@@ -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.
|
||||||
|
|
||||||
17
71_Poker/csharp/Resources/Resource.cs
Normal file
17
71_Poker/csharp/Resources/Resource.cs
Normal file
@@ -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));
|
||||||
|
}
|
||||||
5
71_Poker/csharp/Resources/Title.txt
Normal file
5
71_Poker/csharp/Resources/Title.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Poker
|
||||||
|
Creative Computing Morristown, New Jersey
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
8
71_Poker/csharp/Strategies/Bet.cs
Normal file
8
71_Poker/csharp/Strategies/Bet.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Poker.Strategies;
|
||||||
|
|
||||||
|
internal class Bet : Strategy
|
||||||
|
{
|
||||||
|
public Bet(int amount) => Value = amount;
|
||||||
|
|
||||||
|
public override int Value { get; }
|
||||||
|
}
|
||||||
12
71_Poker/csharp/Strategies/Bluff.cs
Normal file
12
71_Poker/csharp/Strategies/Bluff.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
6
71_Poker/csharp/Strategies/Check.cs
Normal file
6
71_Poker/csharp/Strategies/Check.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Poker.Strategies;
|
||||||
|
|
||||||
|
internal class Check : Strategy
|
||||||
|
{
|
||||||
|
public override int Value => 0;
|
||||||
|
}
|
||||||
6
71_Poker/csharp/Strategies/Fold.cs
Normal file
6
71_Poker/csharp/Strategies/Fold.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Poker.Strategies;
|
||||||
|
|
||||||
|
internal class Fold : Strategy
|
||||||
|
{
|
||||||
|
public override int Value => -1;
|
||||||
|
}
|
||||||
6
71_Poker/csharp/Strategies/None.cs
Normal file
6
71_Poker/csharp/Strategies/None.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Poker.Strategies;
|
||||||
|
|
||||||
|
internal class None : Strategy
|
||||||
|
{
|
||||||
|
public override int Value => -1;
|
||||||
|
}
|
||||||
6
71_Poker/csharp/Strategies/Raise.cs
Normal file
6
71_Poker/csharp/Strategies/Raise.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Poker.Strategies;
|
||||||
|
|
||||||
|
internal class Raise : Bet
|
||||||
|
{
|
||||||
|
public Raise() : base(2) { }
|
||||||
|
}
|
||||||
15
71_Poker/csharp/Strategies/Strategy.cs
Normal file
15
71_Poker/csharp/Strategies/Strategy.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
214
71_Poker/csharp/Table.cs
Normal file
214
71_Poker/csharp/Table.cs
Normal file
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user