mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2025-12-28 21:54:17 -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>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources/*.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\00_Common\dotnet\Games.Common\Games.Common.csproj" />
|
||||
</ItemGroup>
|
||||
</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