Skip to content

Instantly share code, notes, and snippets.

@foolmoron
Last active August 1, 2024 01:53
Show Gist options
  • Save foolmoron/943426178f68726b62298e4dba2d9850 to your computer and use it in GitHub Desktop.
Save foolmoron/943426178f68726b62298e4dba2d9850 to your computer and use it in GitHub Desktop.
Card matching game monte carlo
using System.Diagnostics;
const int N_SIMS = 100_000_000;
const int N_THREADS = 1_000;
const int N_PLAYERS = 6;
const int N_WINS = 3;
const int N_PAIRS = 24;
float[] playerAccuracy = [ 0.90f, 0.87f, 0.83f, 0.80f, 0.90f, 0.85f ];
var tasks = Enumerable.Range(0, N_THREADS).Select(i => Task.Run(RunSims)).ToArray();
Stopwatch sw = Stopwatch.StartNew();
Task.WaitAll(tasks);
sw.Stop();
int[] playerWinsTotal = new int[N_PLAYERS];
foreach (var task in tasks) {
for (int p = 0; p < N_PLAYERS; p++) {
playerWinsTotal[p] += task.Result[p];
}
}
Console.WriteLine($"{N_SIMS} runs in {sw.Elapsed.TotalSeconds:f3}s:");
for (int p = 0; p < playerWinsTotal.Length; p++) {
Console.WriteLine($"\tPlayer {p+1} (acc {playerAccuracy[p]:f2}%): {(double)playerWinsTotal[p] / N_SIMS:p}");
}
int[] RunSims() {
int[] playerWins = new int[N_PLAYERS];
for (int i = 0; i < (N_SIMS / N_THREADS); i++) {
playerWins[RunSim(playerAccuracy)]++;
}
return playerWins;
}
static int RunSim(float[] playerAccuracy) {
Random r = new();
int[] playerMatches = new int[N_PLAYERS];
List<int> allCards = Enumerable.Range(0, N_PAIRS * 2).ToList();
List<int> unknownCards = Enumerable.Range(0, N_PAIRS * 2).ToList();
HashSet<int> knownHalfPairs = new(N_PAIRS * 2);
HashSet<(int, int)> knownFullPairs = new(N_PAIRS);
while (true) {
for (int p = 0; p < N_PLAYERS; p++) {
var picks = (-1, -1);
if (r.NextSingle() > playerAccuracy[p]) { // forgot
var firstIdx = r.Next(allCards.Count);
var secondIdx = r.Next(allCards.Count - 1);
if (secondIdx >= firstIdx) {
secondIdx++;
}
picks = (allCards[firstIdx], allCards[secondIdx]);
} else if (knownFullPairs.Count != 0) {
picks = knownFullPairs.First();
} else {
var firstIdx = r.Next(unknownCards.Count);
picks.Item1 = unknownCards[firstIdx];
var other = GetOther(picks.Item1);
if (knownHalfPairs.Contains(other)) {
picks.Item2 = other;
} else {
var secondIdx = r.Next(unknownCards.Count - 1);
if (secondIdx >= firstIdx) {
secondIdx++;
}
picks.Item2 = unknownCards[secondIdx];
}
}
if (picks.Item1 == GetOther(picks.Item2)) {
playerMatches[p]++;
if (playerMatches[p] == N_WINS) {
return p;
}
allCards.Remove(picks.Item1);
allCards.Remove(picks.Item2);
unknownCards.Remove(picks.Item1);
unknownCards.Remove(picks.Item2);
knownHalfPairs.Remove(picks.Item1);
knownHalfPairs.Remove(picks.Item2);
knownFullPairs.Remove(picks);
knownFullPairs.Remove((picks.Item2, picks.Item1));
} else {
unknownCards.Remove(picks.Item1);
unknownCards.Remove(picks.Item2);
var other1 = GetOther(picks.Item1);
if (knownHalfPairs.Remove(other1)) {
knownFullPairs.Add((picks.Item1, other1));
} else {
knownHalfPairs.Add(picks.Item1);
}
var other2 = GetOther(picks.Item2);
if (knownHalfPairs.Remove(other2)) {
knownFullPairs.Add((picks.Item2, other2));
} else {
knownHalfPairs.Add(picks.Item2);
}
}
}
}
}
static int GetOther(int index) {
return index % 2 == 0 ? index + 1 : index - 1;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment