Added the blackjack simulator.
This commit is contained in:
parent
622fe06e25
commit
884f51108f
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,7 +4,10 @@
|
|||||||
# Build files.
|
# Build files.
|
||||||
*/bin/
|
*/bin/
|
||||||
*/obj/
|
*/obj/
|
||||||
|
*/*/bin/
|
||||||
|
*/*/obj/
|
||||||
convimg.out
|
convimg.out
|
||||||
|
|
||||||
# Other stuff.
|
# Other stuff.
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
*/.vs/
|
||||||
|
|||||||
25
BlackjackSim/BlackjackSim.sln
Normal file
25
BlackjackSim/BlackjackSim.sln
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.8.34309.116
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlackjackSim", "BlackjackSim\BlackjackSim.csproj", "{2A2464A5-9F6B-464A-A980-20DFB65250A8}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{2A2464A5-9F6B-464A-A980-20DFB65250A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{2A2464A5-9F6B-464A-A980-20DFB65250A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{2A2464A5-9F6B-464A-A980-20DFB65250A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{2A2464A5-9F6B-464A-A980-20DFB65250A8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {4B86B10A-5893-4CB6-9542-D8974BFC75EC}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
10
BlackjackSim/BlackjackSim/BlackjackSim.csproj
Normal file
10
BlackjackSim/BlackjackSim/BlackjackSim.csproj
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>disable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
72
BlackjackSim/BlackjackSim/Card.cs
Normal file
72
BlackjackSim/BlackjackSim/Card.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace BlackjackSim;
|
||||||
|
|
||||||
|
public readonly struct Card : IEquatable<Card>
|
||||||
|
{
|
||||||
|
public readonly SuitKind suit;
|
||||||
|
public readonly ValueKind value;
|
||||||
|
|
||||||
|
public Card()
|
||||||
|
{
|
||||||
|
suit = SuitKind.Spades;
|
||||||
|
value = ValueKind.Ace;
|
||||||
|
}
|
||||||
|
public Card(SuitKind suit, ValueKind value)
|
||||||
|
{
|
||||||
|
this.suit = suit;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
public Card(ValueKind value, SuitKind suit)
|
||||||
|
{
|
||||||
|
this.suit = suit;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Card[] GetDeck()
|
||||||
|
{
|
||||||
|
SuitKind[] suits = Enum.GetValues<SuitKind>();
|
||||||
|
ValueKind[] values = Enum.GetValues<ValueKind>();
|
||||||
|
|
||||||
|
Card[] totalCards = new Card[suits.Length * values.Length];
|
||||||
|
for (int s = 0; s < suits.Length; s++)
|
||||||
|
{
|
||||||
|
for (int v = 0; v < values.Length; v++)
|
||||||
|
{
|
||||||
|
totalCards[s * values.Length + v] = (suits[s], values[v]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return totalCards;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetValue(bool acesAreEleven) => value switch
|
||||||
|
{
|
||||||
|
ValueKind.Ace => acesAreEleven ? 11 : 1,
|
||||||
|
ValueKind.Two => 2,
|
||||||
|
ValueKind.Three => 3,
|
||||||
|
ValueKind.Four => 4,
|
||||||
|
ValueKind.Five => 5,
|
||||||
|
ValueKind.Six => 6,
|
||||||
|
ValueKind.Seven => 7,
|
||||||
|
ValueKind.Eight => 8,
|
||||||
|
ValueKind.Nine => 9,
|
||||||
|
ValueKind.Ten or ValueKind.Jack or ValueKind.Queen or ValueKind.King => 10,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
|
||||||
|
public override bool Equals(object? obj) => obj is Card objCard && Equals(objCard);
|
||||||
|
public bool Equals(Card other) => suit == other.suit && value == other.value;
|
||||||
|
public override int GetHashCode() => suit.GetHashCode() ^ value.GetHashCode();
|
||||||
|
public override string ToString() => $"{value} of {suit}";
|
||||||
|
|
||||||
|
public static bool operator ==(Card card, SuitKind suit) => card.suit == suit;
|
||||||
|
public static bool operator ==(Card card, ValueKind value) => card.value == value;
|
||||||
|
public static bool operator !=(Card card, SuitKind suit) => card.suit != suit;
|
||||||
|
public static bool operator !=(Card card, ValueKind value) => card.value != value;
|
||||||
|
|
||||||
|
public static implicit operator Card(ValueTuple<SuitKind, ValueKind> tuple) =>
|
||||||
|
new(tuple.Item1, tuple.Item2);
|
||||||
|
public static implicit operator Card(ValueTuple<ValueKind, SuitKind> tuple) =>
|
||||||
|
new(tuple.Item1, tuple.Item2);
|
||||||
|
}
|
||||||
16
BlackjackSim/BlackjackSim/DealerBase.cs
Normal file
16
BlackjackSim/BlackjackSim/DealerBase.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
namespace BlackjackSim;
|
||||||
|
|
||||||
|
public abstract class DealerBase : IPerson
|
||||||
|
{
|
||||||
|
public required int DrawTo { get; init; }
|
||||||
|
public required double BlackjackPayment { get; init; }
|
||||||
|
public required double WinPayment { get; init; }
|
||||||
|
|
||||||
|
public double HouseEdge => HouseWins / (HouseLossesRegular * WinPayment + HouseLossesBlackjack * BlackjackPayment);
|
||||||
|
public int HouseWins { get; set; }
|
||||||
|
public int HouseLossesRegular { get; set; }
|
||||||
|
public int HouseLossesBlackjack { get; set; }
|
||||||
|
|
||||||
|
public abstract void OnGameBegin(Game game);
|
||||||
|
public abstract bool ShouldResetShoe();
|
||||||
|
}
|
||||||
13
BlackjackSim/BlackjackSim/Dealers/DealerStandard.cs
Normal file
13
BlackjackSim/BlackjackSim/Dealers/DealerStandard.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
namespace BlackjackSim.Dealers;
|
||||||
|
|
||||||
|
public class DealerStandard : DealerBase
|
||||||
|
{
|
||||||
|
public override void OnGameBegin(Game game)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
public override bool ShouldResetShoe()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
312
BlackjackSim/BlackjackSim/Game.cs
Normal file
312
BlackjackSim/BlackjackSim/Game.cs
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace BlackjackSim;
|
||||||
|
|
||||||
|
public class Game
|
||||||
|
{
|
||||||
|
public DealerBase Dealer { get; private init; }
|
||||||
|
public PlayerBase[] Players { get; private init; }
|
||||||
|
|
||||||
|
public int ShoeSize { get; init; } = 6;
|
||||||
|
|
||||||
|
private Shoe? shoe;
|
||||||
|
|
||||||
|
public Game(DealerBase dealer, params PlayerBase[] players)
|
||||||
|
{
|
||||||
|
Dealer = dealer;
|
||||||
|
Players = players;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlayRound(bool debug = false)
|
||||||
|
{
|
||||||
|
if (Players.Length == 0)
|
||||||
|
{
|
||||||
|
if (debug) Console.WriteLine("!! No players! Ignoring this round.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (debug)
|
||||||
|
{
|
||||||
|
Console.WriteLine(" Starting a game of blackjack.");
|
||||||
|
Console.WriteLine($" Dealer is {Dealer.GetType().Name}");
|
||||||
|
|
||||||
|
Dictionary<Type, int> playerTypes = [];
|
||||||
|
foreach (PlayerBase p in Players)
|
||||||
|
{
|
||||||
|
Type pType = p.GetType();
|
||||||
|
if (playerTypes.TryGetValue(pType, out int count)) playerTypes[pType] = count + 1;
|
||||||
|
else playerTypes.Add(pType, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerTypes.Count == 1)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" Players are {Players.Length} {Players[0].GetType().Name}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine(" Players:");
|
||||||
|
foreach (KeyValuePair<Type, int> type in playerTypes)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" {type.Value} {type.Key.Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dealer.OnGameBegin(this);
|
||||||
|
foreach (PlayerBase p in Players)
|
||||||
|
{
|
||||||
|
p.OnGameBegin();
|
||||||
|
p.DeltaMoneyThisRound = 0;
|
||||||
|
}
|
||||||
|
if (Dealer.ShouldResetShoe())
|
||||||
|
{
|
||||||
|
if (debug) Console.WriteLine("! Dealer has requested a shoe reset!");
|
||||||
|
shoe = resetShoe();
|
||||||
|
}
|
||||||
|
else shoe ??= resetShoe();
|
||||||
|
|
||||||
|
// STEP 0: Bets are collected.
|
||||||
|
Hand dealerHand = new(Dealer);
|
||||||
|
Dictionary<PlayerBase, List<Hand>> playerHands = [];
|
||||||
|
List<Hand> totalHands = [];
|
||||||
|
foreach (PlayerBase p in Players)
|
||||||
|
{
|
||||||
|
Hand hand = new(p)
|
||||||
|
{
|
||||||
|
bet = Math.Min(p.PlaceInitialBet(), p.Money)
|
||||||
|
};
|
||||||
|
totalHands.Add(hand);
|
||||||
|
if (playerHands.TryGetValue(p, out List<Hand>? hands)) hands.Add(hand);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
List<Hand> newHands = [hand];
|
||||||
|
playerHands.Add(p, newHands);
|
||||||
|
p.YourGivenHands(newHands);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 1: Deal out the cards.
|
||||||
|
bool dealerVisibleAce = false;
|
||||||
|
for (int i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
// Dealer card first.
|
||||||
|
Card dealerCard = tryGetFromShoe();
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
// Show the first dealer card to the other players.
|
||||||
|
if (debug) Console.WriteLine($" Dealer drew {dealerCard}");
|
||||||
|
foreach (PlayerBase p in Players)
|
||||||
|
{
|
||||||
|
p.OnSeenCard(dealerCard, false);
|
||||||
|
p.InitialVisibleDealerCard(dealerCard);
|
||||||
|
}
|
||||||
|
if (dealerCard == ValueKind.Ace) dealerVisibleAce = true; // For the insurance step.
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Don't show any future dealer cards to the players.
|
||||||
|
if (debug) Console.WriteLine($" (Hidden) Dealer drew {dealerCard}");
|
||||||
|
}
|
||||||
|
dealerHand.cards.Add(dealerCard);
|
||||||
|
|
||||||
|
// Deal one card to each hand.
|
||||||
|
foreach (Hand h in totalHands)
|
||||||
|
{
|
||||||
|
Card playerCard = tryGetFromShoe();
|
||||||
|
h.cards.Add(playerCard);
|
||||||
|
|
||||||
|
// Notify players.
|
||||||
|
foreach (PlayerBase p in Players)
|
||||||
|
{
|
||||||
|
p.OnSeenCard(playerCard, h.player == p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 1b: Check for insurance.
|
||||||
|
if (dealerVisibleAce)
|
||||||
|
{
|
||||||
|
if (debug) Console.WriteLine($"! Dealer has a visible ace! Insurance will commence.");
|
||||||
|
bool isBlackjack = dealerHand.IsBlackjack();
|
||||||
|
|
||||||
|
foreach (PlayerBase p in Players)
|
||||||
|
{
|
||||||
|
double insuranceBet = Math.Min(p.MakeInsuranceBet(), p.Money);
|
||||||
|
if (isBlackjack)
|
||||||
|
{
|
||||||
|
p.DeltaMoneyThisRound += insuranceBet * 2;
|
||||||
|
p.InsuranceWon++;
|
||||||
|
p.InsuranceDelta = insuranceBet * 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
p.DeltaMoneyThisRound -= insuranceBet;
|
||||||
|
p.InsuranceLost++;
|
||||||
|
p.InsuranceDelta = -insuranceBet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBlackjack) goto _handCompare; // You can't beat a dealer blackjack.
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 2: Play blackjack.
|
||||||
|
foreach (KeyValuePair<PlayerBase, List<Hand>> ph in playerHands)
|
||||||
|
{
|
||||||
|
PlayerBase player = ph.Key;
|
||||||
|
List<Hand> hands = ph.Value;
|
||||||
|
for (int i = 0; i < hands.Count; i++)
|
||||||
|
{
|
||||||
|
Hand h = hands[i];
|
||||||
|
|
||||||
|
bool hasDoubled = false;
|
||||||
|
_retry:
|
||||||
|
if (h.cards.Count == 2)
|
||||||
|
{
|
||||||
|
if ((h.cards[0].GetValue(false) == h.cards[1].GetValue(false) ||
|
||||||
|
h.GetValue() == 16) && player.ShouldSplit(h))
|
||||||
|
{
|
||||||
|
// Split the hand into two. You can split multiple times.
|
||||||
|
Hand other = new(player)
|
||||||
|
{
|
||||||
|
bet = h.bet,
|
||||||
|
cards = [h.cards[1]]
|
||||||
|
};
|
||||||
|
h.cards.RemoveAt(1);
|
||||||
|
hands.Add(other);
|
||||||
|
player.HandsSplit++;
|
||||||
|
|
||||||
|
goto _retry;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.ShouldDouble(h) && !hasDoubled)
|
||||||
|
{
|
||||||
|
// Double bet, get one card, and call it done.
|
||||||
|
h.bet *= 2;
|
||||||
|
Card newCard = tryGetFromShoe();
|
||||||
|
|
||||||
|
// Notify players of new card.
|
||||||
|
foreach (PlayerBase p in Players) p.OnSeenCard(newCard, player == p);
|
||||||
|
h.cards.Add(newCard);
|
||||||
|
player.OnDouble(h);
|
||||||
|
hasDoubled = true;
|
||||||
|
player.HandsDoubled++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (h.GetValue() < 21 && player.ShouldHit(h))
|
||||||
|
{
|
||||||
|
// Add new card to hand.
|
||||||
|
Card newCard = tryGetFromShoe();
|
||||||
|
|
||||||
|
// Notify players of new card.
|
||||||
|
foreach (PlayerBase p in Players) p.OnSeenCard(newCard, player == p);
|
||||||
|
h.cards.Add(newCard);
|
||||||
|
player.OnHit(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 3a: Dealer reveals hidden card.
|
||||||
|
if (debug) Console.WriteLine($" Dealer reveals hand: {dealerHand.GetValue()}");
|
||||||
|
for (int i = 1; i < dealerHand.cards.Count; i++)
|
||||||
|
{
|
||||||
|
// Notify players.
|
||||||
|
foreach (PlayerBase p in Players) p.OnSeenCard(dealerHand.cards[i], false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 3b: Dealer draws until its limit (hit on value).
|
||||||
|
while (dealerHand.GetValue() <= Dealer.DrawTo)
|
||||||
|
{
|
||||||
|
// Add new card to hand.
|
||||||
|
Card newCard = tryGetFromShoe();
|
||||||
|
dealerHand.cards.Add(newCard);
|
||||||
|
if (debug) Console.WriteLine($" Dealer drawing new card: {newCard} (total {dealerHand.GetValue()})");
|
||||||
|
|
||||||
|
// Notify players of new card.
|
||||||
|
foreach (PlayerBase p in Players) p.OnSeenCard(newCard, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 4: Compare hands.
|
||||||
|
_handCompare:
|
||||||
|
int dealerValue = dealerHand.GetValue();
|
||||||
|
int handIndex = 0;
|
||||||
|
foreach (Hand h in totalHands)
|
||||||
|
{
|
||||||
|
int yourValue = h.GetValue();
|
||||||
|
HandStatus status;
|
||||||
|
if (dealerHand.IsBlackjack())
|
||||||
|
{
|
||||||
|
if (h.IsBlackjack()) status = HandStatus.Push;
|
||||||
|
else status = HandStatus.Lose;
|
||||||
|
}
|
||||||
|
else if (yourValue > 21) status = HandStatus.Lose; // You've bust.
|
||||||
|
else if (dealerValue > 21) status = HandStatus.Won; // Dealer bust.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (h.IsBlackjack()) status = HandStatus.WonBlackjack;
|
||||||
|
else if (dealerValue > yourValue) status = HandStatus.Lose;
|
||||||
|
else if (dealerValue < yourValue) status = HandStatus.Won;
|
||||||
|
else status = HandStatus.Push;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debug) Console.WriteLine($" Hand {handIndex} has {status} ({yourValue}).");
|
||||||
|
|
||||||
|
h.status = status;
|
||||||
|
PlayerBase player = (PlayerBase)h.player;
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case HandStatus.Won:
|
||||||
|
player.DeltaMoneyThisRound += h.bet * Dealer.WinPayment;
|
||||||
|
player.HandsWon++;
|
||||||
|
Dealer.HouseLossesRegular++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HandStatus.WonBlackjack:
|
||||||
|
player.DeltaMoneyThisRound += h.bet * Dealer.BlackjackPayment;
|
||||||
|
player.HandsWon++;
|
||||||
|
player.HandsBlackjacked++;
|
||||||
|
Dealer.HouseLossesBlackjack++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HandStatus.Lose:
|
||||||
|
player.DeltaMoneyThisRound -= h.bet;
|
||||||
|
player.HandsLost++;
|
||||||
|
Dealer.HouseWins++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HandStatus.Push: // Push means money back.
|
||||||
|
player.HandsPushed++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
handIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalHands.Count(x => x.status == HandStatus.Won || x.status == HandStatus.WonBlackjack) >= 2) Console.ReadKey();
|
||||||
|
|
||||||
|
// STEP 5: Apply money deltas.
|
||||||
|
foreach (PlayerBase p in Players) p.Money += p.DeltaMoneyThisRound;
|
||||||
|
// Now we're done.
|
||||||
|
|
||||||
|
Card tryGetFromShoe()
|
||||||
|
{
|
||||||
|
if (shoe!.CardsRemaining > 0) return shoe.Get();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (debug) Console.WriteLine("!! Shoe has unexpectedly run out of cards!");
|
||||||
|
shoe = resetShoe();
|
||||||
|
return shoe.Get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Shoe resetShoe()
|
||||||
|
{
|
||||||
|
if (debug) Console.WriteLine($" Resetting shoe ({ShoeSize} decks).");
|
||||||
|
Shoe shoe = new(ShoeSize);
|
||||||
|
foreach (PlayerBase p in Players) p.OnShoeReset(ShoeSize);
|
||||||
|
return shoe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
BlackjackSim/BlackjackSim/Hand.cs
Normal file
57
BlackjackSim/BlackjackSim/Hand.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace BlackjackSim;
|
||||||
|
|
||||||
|
public class Hand
|
||||||
|
{
|
||||||
|
public IPerson player;
|
||||||
|
public List<Card> cards = [];
|
||||||
|
public double bet;
|
||||||
|
|
||||||
|
public HandStatus status;
|
||||||
|
|
||||||
|
public Hand(IPerson player)
|
||||||
|
{
|
||||||
|
this.player = player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsBlackjack() => cards.Count == 2 &&
|
||||||
|
((cards[0] == ValueKind.Ace && cards[1].GetValue(false) == 10) ||
|
||||||
|
(cards[1] == ValueKind.Ace && cards[0].GetValue(false) == 10));
|
||||||
|
public int GetValue()
|
||||||
|
{
|
||||||
|
// Count all non-aces first.
|
||||||
|
int aceCount = cards.Count(x => x == ValueKind.Ace);
|
||||||
|
int sum = 0;
|
||||||
|
foreach (Card c in cards)
|
||||||
|
{
|
||||||
|
if (c == ValueKind.Ace) continue;
|
||||||
|
sum += c.GetValue(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// You will never count two aces as 11, so we don't need to
|
||||||
|
// search too deep. All other aces count as 1, the last one
|
||||||
|
// is the only one we need to compare for.
|
||||||
|
for (int i = 0; i < aceCount; i++)
|
||||||
|
{
|
||||||
|
if (i < aceCount - 1) sum += 1;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (sum >= 21) sum += 1;
|
||||||
|
else sum += 11;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HandStatus
|
||||||
|
{
|
||||||
|
Incomplete,
|
||||||
|
Won,
|
||||||
|
WonBlackjack,
|
||||||
|
Push,
|
||||||
|
Lose
|
||||||
|
}
|
||||||
3
BlackjackSim/BlackjackSim/IPerson.cs
Normal file
3
BlackjackSim/BlackjackSim/IPerson.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace BlackjackSim;
|
||||||
|
|
||||||
|
public interface IPerson;
|
||||||
56
BlackjackSim/BlackjackSim/PlayerBase.cs
Normal file
56
BlackjackSim/BlackjackSim/PlayerBase.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace BlackjackSim;
|
||||||
|
|
||||||
|
public abstract class PlayerBase : IPerson
|
||||||
|
{
|
||||||
|
public double InitialMoney { get; set; } = 10_000;
|
||||||
|
public double Money { get; set; }
|
||||||
|
public double DeltaMoneyThisRound { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public PlayerBase(string name)
|
||||||
|
{
|
||||||
|
Money = InitialMoney;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int HandsPlayed => HandsWon + HandsLost + HandsPushed;
|
||||||
|
public int HandsWon { get; set; }
|
||||||
|
public int HandsLost { get; set; }
|
||||||
|
public int HandsPushed { get; set; }
|
||||||
|
public int HandsBlackjacked { get; set; }
|
||||||
|
|
||||||
|
public int HandsDoubled { get; set; }
|
||||||
|
public int HandsSplit { get; set; }
|
||||||
|
|
||||||
|
public int InsurancePlayed => InsuranceWon + InsuranceLost;
|
||||||
|
public int InsuranceWon { get; set; }
|
||||||
|
public int InsuranceLost { get; set; }
|
||||||
|
public double InsuranceDelta { get; set; }
|
||||||
|
|
||||||
|
public virtual void OnGameBegin() { }
|
||||||
|
public virtual void OnShoeReset(int decks) { }
|
||||||
|
|
||||||
|
public abstract double PlaceInitialBet();
|
||||||
|
|
||||||
|
public virtual void YourGivenHands(List<Hand> hands) { }
|
||||||
|
public virtual void OnSeenCard(Card card, bool yours) { }
|
||||||
|
public virtual void InitialVisibleDealerCard(Card card) { }
|
||||||
|
|
||||||
|
public abstract double MakeInsuranceBet();
|
||||||
|
|
||||||
|
public abstract bool ShouldHit(Hand hand);
|
||||||
|
public virtual void OnHit(Hand hand) { }
|
||||||
|
|
||||||
|
public abstract bool ShouldDouble(Hand hand);
|
||||||
|
public virtual void OnDouble(Hand hand) { }
|
||||||
|
|
||||||
|
public abstract bool ShouldSplit(Hand hand);
|
||||||
|
public virtual void OnSplit(Hand hand) { }
|
||||||
|
|
||||||
|
public virtual void OnWonHand(Hand hand) { }
|
||||||
|
public virtual void OnLostHand(Hand hand) { }
|
||||||
|
public virtual void OnHandIsBlackjack(Hand hand) { }
|
||||||
|
public virtual void OnHandPush(Hand hand) { }
|
||||||
|
}
|
||||||
14
BlackjackSim/BlackjackSim/Players/PlayerAlwaysStand.cs
Normal file
14
BlackjackSim/BlackjackSim/Players/PlayerAlwaysStand.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
namespace BlackjackSim.Players;
|
||||||
|
|
||||||
|
public class PlayerAlwaysStand : PlayerBase
|
||||||
|
{
|
||||||
|
public double BetSize { get; set; } = 100;
|
||||||
|
|
||||||
|
public PlayerAlwaysStand() : base("Always Stand") { }
|
||||||
|
|
||||||
|
public override double PlaceInitialBet() => BetSize;
|
||||||
|
public override double MakeInsuranceBet() => 0;
|
||||||
|
public override bool ShouldHit(Hand hand) => false;
|
||||||
|
public override bool ShouldDouble(Hand hand) => false;
|
||||||
|
public override bool ShouldSplit(Hand hand) => false;
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace BlackjackSim.Players;
|
||||||
|
|
||||||
|
public class PlayerCardCountingSimple : PlayerTabular // Build on top of a good system.
|
||||||
|
{
|
||||||
|
public PlayerCardCountingSimple()
|
||||||
|
{
|
||||||
|
Name = "Card Counting (Simple)";
|
||||||
|
}
|
||||||
|
|
||||||
|
private int counter;
|
||||||
|
private int decks;
|
||||||
|
|
||||||
|
public override void OnShoeReset(int decks)
|
||||||
|
{
|
||||||
|
this.decks = decks;
|
||||||
|
counter = 0;
|
||||||
|
}
|
||||||
|
public override void OnSeenCard(Card card, bool yours)
|
||||||
|
{
|
||||||
|
int val = card.GetValue(true);
|
||||||
|
if (val <= 6) counter++;
|
||||||
|
else if (val >= 10) counter--;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override double PlaceInitialBet() => BetSize * Math.Pow(1.1, -counter);
|
||||||
|
}
|
||||||
17
BlackjackSim/BlackjackSim/Players/PlayerPercentageBet.cs
Normal file
17
BlackjackSim/BlackjackSim/Players/PlayerPercentageBet.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
namespace BlackjackSim.Players;
|
||||||
|
|
||||||
|
public class PlayerPercentageBet : PlayerBase
|
||||||
|
{
|
||||||
|
public double PercentageBet { get; set; }
|
||||||
|
|
||||||
|
public PlayerPercentageBet(double percentage) : base($"Percentage Bet {percentage}%")
|
||||||
|
{
|
||||||
|
PercentageBet = percentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override double PlaceInitialBet() => Money * (PercentageBet * 0.01);
|
||||||
|
public override double MakeInsuranceBet() => 0;
|
||||||
|
public override bool ShouldHit(Hand hand) => hand.GetValue() <= 16;
|
||||||
|
public override bool ShouldDouble(Hand hand) => false;
|
||||||
|
public override bool ShouldSplit(Hand hand) => false;
|
||||||
|
}
|
||||||
80
BlackjackSim/BlackjackSim/Players/PlayerProbabilistic.cs
Normal file
80
BlackjackSim/BlackjackSim/Players/PlayerProbabilistic.cs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace BlackjackSim.Players;
|
||||||
|
|
||||||
|
public class PlayerProbabilistic : PlayerBase
|
||||||
|
{
|
||||||
|
public double BetValue { get; set; } = 100;
|
||||||
|
|
||||||
|
public PlayerProbabilistic() : base("Probabilistic Choices") { }
|
||||||
|
|
||||||
|
private readonly List<Card> seenCards = [], remainingCards = [];
|
||||||
|
private Card dealerCard;
|
||||||
|
|
||||||
|
public override void OnShoeReset(int decks)
|
||||||
|
{
|
||||||
|
seenCards.Clear();
|
||||||
|
remainingCards.Clear();
|
||||||
|
for (int i = 0; i < decks; i++) remainingCards.AddRange(Card.GetDeck());
|
||||||
|
}
|
||||||
|
public override void OnSeenCard(Card card, bool yours)
|
||||||
|
{
|
||||||
|
seenCards.Add(card);
|
||||||
|
remainingCards.Remove(card);
|
||||||
|
}
|
||||||
|
public override void InitialVisibleDealerCard(Card card) => dealerCard = card;
|
||||||
|
|
||||||
|
public override double PlaceInitialBet() => BetValue;
|
||||||
|
public override double MakeInsuranceBet()
|
||||||
|
{
|
||||||
|
// Calculate odds that the dealer's hand equals 21.
|
||||||
|
Hand temp = new(this)
|
||||||
|
{
|
||||||
|
cards = [dealerCard]
|
||||||
|
};
|
||||||
|
|
||||||
|
int matches = 0, doesntMatch = 0;
|
||||||
|
for (int i = 0; i < remainingCards.Count; i++)
|
||||||
|
{
|
||||||
|
Card added = remainingCards[i];
|
||||||
|
temp.cards.Add(added);
|
||||||
|
if (temp.GetValue() == 21) matches++;
|
||||||
|
else doesntMatch++;
|
||||||
|
temp.cards.Remove(added);
|
||||||
|
}
|
||||||
|
|
||||||
|
double trueOdds = (double)matches / (matches + doesntMatch);
|
||||||
|
double weightedOdds = ApplyBias(trueOdds, 0.65);
|
||||||
|
return BetValue * 0.5 * weightedOdds; // Bet proportionally.
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ShouldDouble(Hand hand) => false; // TODO
|
||||||
|
public override bool ShouldSplit(Hand hand) => false; // TODO: How??
|
||||||
|
public override bool ShouldHit(Hand hand)
|
||||||
|
{
|
||||||
|
// Determine odds that the next card will bust.
|
||||||
|
Hand temp = new(this)
|
||||||
|
{
|
||||||
|
cards = new(hand.cards),
|
||||||
|
};
|
||||||
|
|
||||||
|
int willBust = 0, wontBust = 0;
|
||||||
|
for (int i = 0; i < remainingCards.Count; i++)
|
||||||
|
{
|
||||||
|
Card added = remainingCards[i];
|
||||||
|
temp.cards.Add(added);
|
||||||
|
if (temp.GetValue() > 21) willBust++;
|
||||||
|
else wontBust++;
|
||||||
|
temp.cards.Remove(added);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((double)willBust / (willBust + wontBust) >= 0.5) return false;
|
||||||
|
else return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double ApplyBias(double x, double bias)
|
||||||
|
{
|
||||||
|
double k = (1 - bias) * (1 - bias) * (1 - bias); // Better than pow.
|
||||||
|
return x * k / (x * k - x + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
BlackjackSim/BlackjackSim/Players/PlayerRandomized.cs
Normal file
30
BlackjackSim/BlackjackSim/Players/PlayerRandomized.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace BlackjackSim.Players;
|
||||||
|
|
||||||
|
public class PlayerRandomized : PlayerBase
|
||||||
|
{
|
||||||
|
public double MinBetValue { get; set; } = 100;
|
||||||
|
public double MaxBetValue { get; set; } = 100;
|
||||||
|
|
||||||
|
public double BetInsuranceOdds { get; set; } = 0.5;
|
||||||
|
public double HitOdds { get; set; } = 0.5;
|
||||||
|
public double DoubleOdds { get; set; } = 0.5;
|
||||||
|
public double SplitOdds { get; set; } = 0.5;
|
||||||
|
|
||||||
|
private readonly Random rand = new();
|
||||||
|
private double betValue;
|
||||||
|
|
||||||
|
public PlayerRandomized() : base("Randomized Player") { }
|
||||||
|
|
||||||
|
public override double PlaceInitialBet()
|
||||||
|
{
|
||||||
|
betValue = rand.NextDouble() * (MaxBetValue - MinBetValue) + MinBetValue;
|
||||||
|
return betValue;
|
||||||
|
}
|
||||||
|
public override double MakeInsuranceBet() => rand.NextDouble() <= BetInsuranceOdds ? betValue / 2 : 0;
|
||||||
|
|
||||||
|
public override bool ShouldHit(Hand hand) => rand.NextDouble() <= HitOdds;
|
||||||
|
public override bool ShouldDouble(Hand hand) => rand.NextDouble() <= DoubleOdds;
|
||||||
|
public override bool ShouldSplit(Hand hand) => rand.NextDouble() <= SplitOdds;
|
||||||
|
}
|
||||||
17
BlackjackSim/BlackjackSim/Players/PlayerStandard.cs
Normal file
17
BlackjackSim/BlackjackSim/Players/PlayerStandard.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
namespace BlackjackSim.Players;
|
||||||
|
|
||||||
|
public class PlayerStandard : PlayerBase
|
||||||
|
{
|
||||||
|
public double BetSize { get; set; }
|
||||||
|
|
||||||
|
public PlayerStandard(double betSize) : base($"Standard Bet {betSize}")
|
||||||
|
{
|
||||||
|
BetSize = betSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override double PlaceInitialBet() => BetSize;
|
||||||
|
public override double MakeInsuranceBet() => 0;
|
||||||
|
public override bool ShouldHit(Hand hand) => hand.GetValue() <= 16;
|
||||||
|
public override bool ShouldDouble(Hand hand) => false;
|
||||||
|
public override bool ShouldSplit(Hand hand) => true;
|
||||||
|
}
|
||||||
134
BlackjackSim/BlackjackSim/Players/PlayerTabular.cs
Normal file
134
BlackjackSim/BlackjackSim/Players/PlayerTabular.cs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace BlackjackSim.Players;
|
||||||
|
|
||||||
|
public class PlayerTabular : PlayerBase
|
||||||
|
{
|
||||||
|
public double BetSize { get; set; } = 100;
|
||||||
|
|
||||||
|
public Dictionary<int, Choice[]> HardTotals = new()
|
||||||
|
{
|
||||||
|
{ 21, [Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand ] },
|
||||||
|
{ 20, [Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand ] },
|
||||||
|
{ 19, [Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand ] },
|
||||||
|
{ 18, [Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand ] },
|
||||||
|
{ 17, [Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand ] },
|
||||||
|
{ 16, [Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ 15, [Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ 14, [Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ 13, [Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ 12, [Choice.Hit , Choice.Hit , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ 11, [Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit ] },
|
||||||
|
{ 10, [Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ 9, [Choice.Hit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ 8, [Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ 7, [Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ 6, [Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ 5, [Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ 4, [Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ 3, [Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ 2, [Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
};
|
||||||
|
public Dictionary<ValueCombined, Choice[]> AceSoftTotals = new()
|
||||||
|
{
|
||||||
|
{ ValueCombined.Ten , [Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand ] },
|
||||||
|
{ ValueCombined.Nine , [Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand ] },
|
||||||
|
{ ValueCombined.Eight, [Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.DoubleStand, Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand ] },
|
||||||
|
{ ValueCombined.Seven, [Choice.DoubleStand, Choice.DoubleStand, Choice.DoubleStand, Choice.DoubleStand, Choice.DoubleStand, Choice.Stand , Choice.Stand , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ ValueCombined.Six , [Choice.Hit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ ValueCombined.Five , [Choice.Hit , Choice.Hit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ ValueCombined.Four , [Choice.Hit , Choice.Hit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ ValueCombined.Three, [Choice.Hit , Choice.Hit , Choice.Hit , Choice.DoubleHit , Choice.DoubleHit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ ValueCombined.Two , [Choice.Hit , Choice.Hit , Choice.Hit , Choice.DoubleHit , Choice.DoubleHit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
};
|
||||||
|
public Dictionary<ValueCombined, Choice[]> Pairs = new()
|
||||||
|
{
|
||||||
|
{ ValueCombined.Ace , [Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split ] },
|
||||||
|
{ ValueCombined.Ten , [Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand , Choice.Stand ] },
|
||||||
|
{ ValueCombined.Nine , [Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Stand , Choice.Split , Choice.Split , Choice.Stand , Choice.Stand ] },
|
||||||
|
{ ValueCombined.Eight, [Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split ] },
|
||||||
|
{ ValueCombined.Seven, [Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ ValueCombined.Six , [Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ ValueCombined.Five , [Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.DoubleHit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ ValueCombined.Four , [Choice.Hit , Choice.Hit , Choice.Hit , Choice.Split , Choice.Split , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ ValueCombined.Three, [Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
{ ValueCombined.Two , [Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Split , Choice.Hit , Choice.Hit , Choice.Hit , Choice.Hit ] },
|
||||||
|
};
|
||||||
|
|
||||||
|
public PlayerTabular() : base("Tabular Gameplay") { }
|
||||||
|
|
||||||
|
private Card dealerCard;
|
||||||
|
|
||||||
|
public override double PlaceInitialBet() => BetSize;
|
||||||
|
public override double MakeInsuranceBet() => 0;
|
||||||
|
public override void InitialVisibleDealerCard(Card card) => dealerCard = card;
|
||||||
|
|
||||||
|
public override bool ShouldDouble(Hand hand)
|
||||||
|
{
|
||||||
|
Choice choice = GetChoice(hand);
|
||||||
|
return choice == Choice.DoubleHit || choice == Choice.DoubleStand;
|
||||||
|
}
|
||||||
|
public override bool ShouldSplit(Hand hand) => GetChoice(hand) == Choice.Split;
|
||||||
|
public override bool ShouldHit(Hand hand)
|
||||||
|
{
|
||||||
|
Choice choice = GetChoice(hand);
|
||||||
|
return choice == Choice.Hit || choice == Choice.DoubleHit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Choice GetChoice(Hand hand)
|
||||||
|
{
|
||||||
|
ValueCombined dealerCard = CardToValue(this.dealerCard);
|
||||||
|
Choice[] row;
|
||||||
|
if (hand.cards.Count == 2)
|
||||||
|
{
|
||||||
|
// Check for pairs.
|
||||||
|
ValueCombined cardA = CardToValue(hand.cards[0]),
|
||||||
|
cardB = CardToValue(hand.cards[1]);
|
||||||
|
if (cardA == cardB) row = Pairs[cardA]; // Load pair table.
|
||||||
|
else if (cardA == ValueCombined.Ace) row = AceSoftTotals[cardB]; // Load soft totals.
|
||||||
|
else if (cardB == ValueCombined.Ace) row = AceSoftTotals[cardA]; // Load soft totals.
|
||||||
|
else row = HardTotals[hand.GetValue()]; // Not pair or ace, load hard totals.
|
||||||
|
}
|
||||||
|
else row = HardTotals[hand.GetValue()]; // Load hard totals.
|
||||||
|
return row[(int)dealerCard];
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Choice
|
||||||
|
{
|
||||||
|
Stand,
|
||||||
|
Hit,
|
||||||
|
DoubleHit,
|
||||||
|
DoubleStand,
|
||||||
|
Split
|
||||||
|
}
|
||||||
|
public enum ValueCombined
|
||||||
|
{
|
||||||
|
Two = 0,
|
||||||
|
Three = 1,
|
||||||
|
Four = 2,
|
||||||
|
Five = 3,
|
||||||
|
Six = 4,
|
||||||
|
Seven = 5,
|
||||||
|
Eight = 6,
|
||||||
|
Nine = 7,
|
||||||
|
Ten = 8,
|
||||||
|
Ace = 9
|
||||||
|
}
|
||||||
|
public static ValueCombined CardToValue(Card c) => c.value switch
|
||||||
|
{
|
||||||
|
ValueKind.Two => ValueCombined.Two,
|
||||||
|
ValueKind.Three => ValueCombined.Three,
|
||||||
|
ValueKind.Four => ValueCombined.Four,
|
||||||
|
ValueKind.Five => ValueCombined.Five,
|
||||||
|
ValueKind.Six => ValueCombined.Six,
|
||||||
|
ValueKind.Seven => ValueCombined.Seven,
|
||||||
|
ValueKind.Eight => ValueCombined.Eight,
|
||||||
|
ValueKind.Nine => ValueCombined.Nine,
|
||||||
|
ValueKind.Ten => ValueCombined.Ten,
|
||||||
|
ValueKind.Jack => ValueCombined.Ten,
|
||||||
|
ValueKind.Queen => ValueCombined.Ten,
|
||||||
|
ValueKind.King => ValueCombined.Ten,
|
||||||
|
ValueKind.Ace => ValueCombined.Ace,
|
||||||
|
_ => throw new("invalid card?") // Shouldn't happen under correct usage.
|
||||||
|
};
|
||||||
|
}
|
||||||
275
BlackjackSim/BlackjackSim/Program.cs
Normal file
275
BlackjackSim/BlackjackSim/Program.cs
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
/*****722871
|
||||||
|
* Date: 9/18/2024
|
||||||
|
* Programmer: Kyle
|
||||||
|
* Program Name: Blackjack
|
||||||
|
* Program Description: Simulates casino blackjack as accurately as possible.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using BlackjackSim.Dealers;
|
||||||
|
using BlackjackSim.Players;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BlackjackSim;
|
||||||
|
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static async Task Main()
|
||||||
|
{
|
||||||
|
Console.CursorVisible = false;
|
||||||
|
Console.OutputEncoding = Encoding.Unicode;
|
||||||
|
DealerBase dealer = new DealerStandard()
|
||||||
|
{
|
||||||
|
BlackjackPayment = 1.5f,
|
||||||
|
WinPayment = 1.0f,
|
||||||
|
DrawTo = 16,
|
||||||
|
};
|
||||||
|
await SimulatePlayerGraph(dealer, new PlayerCardCountingSimple());
|
||||||
|
Console.ResetColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task SimulatePlayerGraph(DealerBase dealer, PlayerBase player)
|
||||||
|
{
|
||||||
|
Game game = new(dealer, player);
|
||||||
|
List<GraphSegment> moneyGraph = [];
|
||||||
|
int rounds = 0;
|
||||||
|
while (player.Money > 1)
|
||||||
|
{
|
||||||
|
game.PlayRound(false);
|
||||||
|
moneyGraph.Add(new(player.Money, player.DeltaMoneyThisRound));
|
||||||
|
RenderOnce(dealer, player, moneyGraph);
|
||||||
|
|
||||||
|
rounds++;
|
||||||
|
await Task.Delay(20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static Task SimulatePlayerGraphFast(DealerBase dealer, PlayerBase player, int iters)
|
||||||
|
{
|
||||||
|
Game game = new(dealer, player);
|
||||||
|
List<GraphSegment> moneyGraph = [];
|
||||||
|
|
||||||
|
for (int r = 0; r < iters; r++)
|
||||||
|
{
|
||||||
|
moneyGraph.Clear();
|
||||||
|
player.Money = player.InitialMoney;
|
||||||
|
|
||||||
|
int roundsThisTime = 0;
|
||||||
|
while (player.Money > 1 && roundsThisTime < 10_000 && r < iters)
|
||||||
|
{
|
||||||
|
game.PlayRound(false);
|
||||||
|
lock (moneyGraph)
|
||||||
|
{
|
||||||
|
moneyGraph.Add(new(player.Money, player.DeltaMoneyThisRound));
|
||||||
|
}
|
||||||
|
r++;
|
||||||
|
roundsThisTime++;
|
||||||
|
}
|
||||||
|
RenderOnce(dealer, player, moneyGraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int lastHandsWon, lastHandsLost, lastHandsPushed, lastHandsDoubled, lastHandsSplit, lastHandsBlackjacked, lastInsuranceWins, lastInsuranceLosses, insuranceShowDirection;
|
||||||
|
private static void RenderOnce(DealerBase dealer, PlayerBase player, List<GraphSegment> segments)
|
||||||
|
{
|
||||||
|
Console.SetCursorPosition(0, 1);
|
||||||
|
Console.ResetColor();
|
||||||
|
Console.WriteLine($"\x1b[37m Player Funds: \x1b[97m${player.Money:0.00} ");
|
||||||
|
|
||||||
|
double moneyBefore = player.Money - player.DeltaMoneyThisRound;
|
||||||
|
double percent = (player.Money / moneyBefore - 1) * 100;
|
||||||
|
string color = player.DeltaMoneyThisRound > 0 ? "\x1b[32m" : (player.DeltaMoneyThisRound < 0 ? "\x1b[31m" : "\x1b[37m");
|
||||||
|
Console.WriteLine($"\x1b[37m Delta: {color}${player.DeltaMoneyThisRound,8:0.00} ({percent,5:0.0}%) ");
|
||||||
|
|
||||||
|
Console.WriteLine($"\n\x1b[37m Hands Won: {(player.HandsWon > lastHandsWon ? "\x1b[92m+" : "\x1b[90m ")}{player.HandsWon,4} ({100.0 * player.HandsWon / player.HandsPlayed,4:0.0}%) ");
|
||||||
|
Console.WriteLine($"\x1b[37m Hands Lost: {(player.HandsLost > lastHandsLost ? "\x1b[91m+" : "\x1b[90m ")}{player.HandsLost,4} ({100.0 * player.HandsLost / player.HandsPlayed,4:0.0}%) ");
|
||||||
|
Console.WriteLine($"\x1b[37m Hands Pushed: {(player.HandsPushed > lastHandsPushed ? "\x1b[97m+" : "\x1b[90m ")}{player.HandsPushed,4} ({100.0 * player.HandsPushed / player.HandsPlayed,4:0.0}%) ");
|
||||||
|
Console.WriteLine($"\x1b[37m Blackjacks: {(player.HandsBlackjacked > lastHandsBlackjacked ? "\x1b[33m+" : "\x1b[90m ")}{player.HandsBlackjacked,4} ({100.0 * player.HandsBlackjacked / player.HandsPlayed,4:0.0}%) ");
|
||||||
|
|
||||||
|
Console.WriteLine($"\n\x1b[37m Hands Doubled: {(player.HandsDoubled > lastHandsDoubled ? "\x1b[97m+" : "\x1b[90m ")}{player.HandsDoubled,3} ({100.0 * player.HandsDoubled / player.HandsPlayed,4:0.0}%) ");
|
||||||
|
Console.WriteLine($"\x1b[37m Hands Split: {(player.HandsSplit > lastHandsSplit ? "\x1b[97m+" : "\x1b[90m ")}{player.HandsSplit,3} ({100.0 * player.HandsSplit / player.HandsPlayed,4:0.0}%) ");
|
||||||
|
|
||||||
|
percent = 100 * dealer.HouseEdge - 100;
|
||||||
|
Console.WriteLine($"\n\x1b[37m House Edge: {(percent > 0 ? "\x1b[31m" : "\x1b[32m")}{percent,5:0.0}% ");
|
||||||
|
|
||||||
|
int centerX = (int)(Console.WindowWidth * 0.5);
|
||||||
|
Console.SetCursorPosition(centerX, 4);
|
||||||
|
string winWrite = $"\x1b[37mInsurance Wins: {(player.InsuranceWon > lastInsuranceWins ? "\x1b[92m+" : "\x1b[90m ")}{player.InsuranceWon,4} ({100.0 * player.InsuranceWon / player.InsurancePlayed,4:0.0}%) ";
|
||||||
|
Console.Write(winWrite);
|
||||||
|
if (player.InsuranceWon > lastInsuranceWins) insuranceShowDirection = 1;
|
||||||
|
|
||||||
|
Console.SetCursorPosition(centerX, 5);
|
||||||
|
string loseWrite = $"\x1b[37mInsurance Losses: {(player.InsuranceLost > lastInsuranceLosses ? "\x1b[91m+" : "\x1b[90m ")}{player.InsuranceLost,4} ({100.0 * player.InsuranceLost / player.InsurancePlayed,4:0.0}%) ";
|
||||||
|
Console.Write(loseWrite);
|
||||||
|
if (player.InsuranceLost > lastInsuranceLosses) insuranceShowDirection = -1;
|
||||||
|
|
||||||
|
if (insuranceShowDirection > 0)
|
||||||
|
{
|
||||||
|
Console.Write(new string(' ', 10));
|
||||||
|
Console.SetCursorPosition(centerX + 33, 4);
|
||||||
|
Console.Write($" ↑ ${player.InsuranceDelta:0.00}");
|
||||||
|
}
|
||||||
|
else if (insuranceShowDirection < 0)
|
||||||
|
{
|
||||||
|
Console.Write($" ↓ ${-player.InsuranceDelta:0.00}");
|
||||||
|
Console.SetCursorPosition(centerX + 33, 4);
|
||||||
|
Console.Write(new string(' ', 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
lastHandsWon = player.HandsWon;
|
||||||
|
lastHandsLost = player.HandsLost;
|
||||||
|
lastHandsPushed = player.HandsPushed;
|
||||||
|
lastHandsDoubled = player.HandsDoubled;
|
||||||
|
lastHandsSplit = player.HandsSplit;
|
||||||
|
lastHandsBlackjacked = player.HandsBlackjacked;
|
||||||
|
lastInsuranceWins = player.InsuranceWon;
|
||||||
|
lastInsuranceLosses = player.InsuranceLost;
|
||||||
|
|
||||||
|
StringBuilder[] total, local;
|
||||||
|
lock (segments)
|
||||||
|
{
|
||||||
|
total = GenerateGraph(segments.Count >= 10000 ? segments[^10000..] : segments, 40, 15);
|
||||||
|
local = GenerateGraph(segments.Count >= 40 ? segments[^40..] : segments, 40, 15);
|
||||||
|
}
|
||||||
|
RenderTwoGraphs(total, local, 1, 40, 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static StringBuilder[] GenerateGraph(IEnumerable<GraphSegment> segments, int width,
|
||||||
|
int height, bool render = false)
|
||||||
|
{
|
||||||
|
StringBuilder[] result = new StringBuilder[height];
|
||||||
|
Dictionary<int, List<(int x, int d)>> points = [];
|
||||||
|
|
||||||
|
// Locate min and max of the graph.
|
||||||
|
double maxMoney = int.MinValue;
|
||||||
|
double minMoney = int.MaxValue;
|
||||||
|
int count = 0;
|
||||||
|
foreach (GraphSegment seg in segments)
|
||||||
|
{
|
||||||
|
if (seg.money > maxMoney) maxMoney = seg.money;
|
||||||
|
else if (seg.money < minMoney) minMoney = seg.money;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts the graph segments into list of X points by Y.
|
||||||
|
// Used for the stringbuilders, since they're grouped by Y
|
||||||
|
// rather than X.
|
||||||
|
int index = 0;
|
||||||
|
foreach (GraphSegment seg in segments)
|
||||||
|
{
|
||||||
|
// Change lerp scale.
|
||||||
|
int x = (int)((double)index / count * width);
|
||||||
|
int y = (int)((seg.money - minMoney) / (maxMoney - minMoney) * (height * 2));
|
||||||
|
|
||||||
|
y = height * 2 - y;
|
||||||
|
|
||||||
|
if (points.TryGetValue(y, out List<(int x, int d)>? p)) p.Add((x, Math.Sign(seg.delta)));
|
||||||
|
else points.Add(y, [(x, Math.Sign(seg.delta))]);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the lists. Slows it down a little but I got issues with negative spacing.
|
||||||
|
foreach (KeyValuePair<int, List<(int x, int d)>> xVals in points)
|
||||||
|
xVals.Value.Sort((a, b) => a.x.CompareTo(b.x));
|
||||||
|
|
||||||
|
// Generate the lines. Two sets of y-values can fit on each
|
||||||
|
// line, so that's what we'll do.
|
||||||
|
for (int i = 0; i < height; i++)
|
||||||
|
{
|
||||||
|
StringBuilder line = new("\x1b[0m"); // Start with reset.
|
||||||
|
List<(int x, int d)> upper, lower;
|
||||||
|
if (!points.TryGetValue(i * 2, out upper!)) upper = [];
|
||||||
|
if (!points.TryGetValue(i * 2 + 1, out lower!)) lower = [];
|
||||||
|
|
||||||
|
int curPos = 0;
|
||||||
|
|
||||||
|
// The color only changes when we hit a point.
|
||||||
|
List<(int x, int d)> totalHits = [.. upper.Concat(lower).Distinct(DirectionComparer.Instance)];
|
||||||
|
foreach ((int x, int d) tuple in totalHits)
|
||||||
|
{
|
||||||
|
if (curPos > tuple.x) continue; // Why???
|
||||||
|
line.Append(new string(' ', tuple.x - curPos));
|
||||||
|
bool top = upper.Contains(tuple), bot = lower.Contains(tuple);
|
||||||
|
|
||||||
|
if (tuple.d > 0) line.Append("\x1b[92m");
|
||||||
|
else if (tuple.d < 0) line.Append("\x1b[91m");
|
||||||
|
else line.Append("\x1b[37m");
|
||||||
|
|
||||||
|
if (top && bot) line.Append('█');
|
||||||
|
else if (top) line.Append('▀');
|
||||||
|
else if (bot) line.Append('▄');
|
||||||
|
else line.Append(' ');
|
||||||
|
curPos = tuple.x + 1;
|
||||||
|
}
|
||||||
|
line.Append(new string(' ', width - curPos));
|
||||||
|
result[i] = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (render) RenderOneGraph(result, width, height);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RenderOneGraph(StringBuilder[] lines, int width, int height)
|
||||||
|
{
|
||||||
|
// Render the graph.
|
||||||
|
int top = Console.WindowHeight - height - 1;
|
||||||
|
int left = (Console.WindowWidth - width - 1) / 2;
|
||||||
|
for (int y = 0; y < height; y++)
|
||||||
|
{
|
||||||
|
Console.SetCursorPosition(left, top + y);
|
||||||
|
Console.Write(lines[y]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static void RenderTwoGraphs(StringBuilder[] linesA, StringBuilder[] linesB,
|
||||||
|
int spacing, int widthA, int widthB)
|
||||||
|
{
|
||||||
|
int height = Math.Max(linesA.Length, linesB.Length);
|
||||||
|
StringBuilder[] total = new StringBuilder[height];
|
||||||
|
|
||||||
|
int totalWidth = widthA + spacing + widthB;
|
||||||
|
|
||||||
|
// Combine graphs.
|
||||||
|
for (int i = 0; i < height; i++)
|
||||||
|
{
|
||||||
|
StringBuilder combined;
|
||||||
|
if (i < linesA.Length) combined = linesA[i];
|
||||||
|
else combined = new(new string(' ', widthA));
|
||||||
|
combined.Append(new string(' ', spacing));
|
||||||
|
if (i < linesB.Length) combined.Append(linesB[i]);
|
||||||
|
total[i] = combined;
|
||||||
|
}
|
||||||
|
// Render the graph.
|
||||||
|
int top = Console.WindowHeight - height - 1;
|
||||||
|
int left = (Console.WindowWidth - totalWidth - 1) / 2;
|
||||||
|
for (int y = 0; y < height; y++)
|
||||||
|
{
|
||||||
|
Console.SetCursorPosition(left, top + y);
|
||||||
|
Console.Write(total[y]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DirectionComparer : IEqualityComparer<(int x, int d)>
|
||||||
|
{
|
||||||
|
public static readonly DirectionComparer Instance = new();
|
||||||
|
|
||||||
|
public bool Equals((int x, int d) a, (int x, int d) b) => a.x == b.x;
|
||||||
|
public int GetHashCode((int x, int d) item) =>
|
||||||
|
item.x.GetHashCode() ^ item.d.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct GraphSegment
|
||||||
|
{
|
||||||
|
public readonly double money;
|
||||||
|
public readonly double delta;
|
||||||
|
|
||||||
|
public GraphSegment(double money, double delta)
|
||||||
|
{
|
||||||
|
this.money = money;
|
||||||
|
this.delta = delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
BlackjackSim/BlackjackSim/Shoe.cs
Normal file
44
BlackjackSim/BlackjackSim/Shoe.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace BlackjackSim;
|
||||||
|
|
||||||
|
public class Shoe
|
||||||
|
{
|
||||||
|
public int TotalDecks => decks;
|
||||||
|
public int TotalCards => 52 * decks;
|
||||||
|
public int CardsTaken => 52 * decks - cards.Count;
|
||||||
|
public int CardsRemaining => cards.Count;
|
||||||
|
|
||||||
|
private readonly List<Card> cards;
|
||||||
|
private readonly int decks;
|
||||||
|
private readonly Random rand;
|
||||||
|
|
||||||
|
public Shoe(int decks)
|
||||||
|
{
|
||||||
|
this.decks = decks;
|
||||||
|
cards = [];
|
||||||
|
rand = new();
|
||||||
|
|
||||||
|
for (int i = 0; i < decks; i++) cards.AddRange(Card.GetDeck());
|
||||||
|
|
||||||
|
// SHUFFLE: pick a random index out of the cards that haven't been
|
||||||
|
// shuffled and add it to the bottom of the list.
|
||||||
|
int notShuffled = cards.Count;
|
||||||
|
while (notShuffled > 0)
|
||||||
|
{
|
||||||
|
int index = rand.Next(notShuffled);
|
||||||
|
Card c = cards[index];
|
||||||
|
cards.RemoveAt(index);
|
||||||
|
cards.Add(c);
|
||||||
|
notShuffled--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Card Get()
|
||||||
|
{
|
||||||
|
Card card = cards[0];
|
||||||
|
cards.RemoveAt(0);
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
}
|
||||||
9
BlackjackSim/BlackjackSim/SuitKind.cs
Normal file
9
BlackjackSim/BlackjackSim/SuitKind.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace BlackjackSim;
|
||||||
|
|
||||||
|
public enum SuitKind
|
||||||
|
{
|
||||||
|
Diamonds,
|
||||||
|
Clubs,
|
||||||
|
Hearts,
|
||||||
|
Spades,
|
||||||
|
}
|
||||||
18
BlackjackSim/BlackjackSim/ValueKind.cs
Normal file
18
BlackjackSim/BlackjackSim/ValueKind.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace BlackjackSim;
|
||||||
|
|
||||||
|
public enum ValueKind
|
||||||
|
{
|
||||||
|
Two,
|
||||||
|
Three,
|
||||||
|
Four,
|
||||||
|
Five,
|
||||||
|
Six,
|
||||||
|
Seven,
|
||||||
|
Eight,
|
||||||
|
Nine,
|
||||||
|
Ten,
|
||||||
|
Jack,
|
||||||
|
Queen,
|
||||||
|
King,
|
||||||
|
Ace
|
||||||
|
}
|
||||||
@ -12,3 +12,8 @@ I have about 1-2 weeks for each project. Check the Git commits for specific date
|
|||||||
- No additional libraries were used, only the built in TI libraries and the [TI CE toolchain](https://github.com/CE-Programming/toolchain).
|
- No additional libraries were used, only the built in TI libraries and the [TI CE toolchain](https://github.com/CE-Programming/toolchain).
|
||||||
- Doesn't run great. It uses 16-bit color mode, so the graphics are somewhat slow to render. I attempted to find the way to switch to 4-bit color mode, but I didn't find enough useful info (the best I've found so far is to dissect the GraphX assembly code). Still runs decent though, I've made as many optimizations as I easily could with the renderer, and everything else is fast.
|
- Doesn't run great. It uses 16-bit color mode, so the graphics are somewhat slow to render. I attempted to find the way to switch to 4-bit color mode, but I didn't find enough useful info (the best I've found so far is to dissect the GraphX assembly code). Still runs decent though, I've made as many optimizations as I easily could with the renderer, and everything else is fast.
|
||||||
- **WARNING**: This one can't be built without reconfiguring the `.clangd` file to include the path to the toolchain.
|
- **WARNING**: This one can't be built without reconfiguring the `.clangd` file to include the path to the toolchain.
|
||||||
|
- Blackjack/
|
||||||
|
- This program simulates casino blackjack rules. It allows for customizable player strategies.
|
||||||
|
- I made a few default strategies (draw until 17, simple card counting, simple probabilities).
|
||||||
|
- No additional libraries were used.
|
||||||
|
- It has two custom-rendered graphs on the console display. I haven't figured out how to use XTerm yet, so I'm generating individual characters.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user