Skip to content

Instantly share code, notes, and snippets.

@VienosNotes
Created September 25, 2022 09:43
Show Gist options
  • Save VienosNotes/36c68b8c2c330d887afe502afb7a0cb5 to your computer and use it in GitHub Desktop.
Save VienosNotes/36c68b8c2c330d887afe502afb7a0cb5 to your computer and use it in GitHub Desktop.
Opening hand simulator
using System.Diagnostics;
const int deckSize = 60;
const int sampleHandsCount = 3;
const int openingHandsCards = 7;
const int simulateTimes = 10_000_000;
Console.WriteLine("Opening hand simulator v1.0");
Console.WriteLine($" @deckSize: {deckSize}, @sampleHandsCount: {sampleHandsCount}, @openingHandsCards: {openingHandsCards}");
Console.WriteLine($" @simulateTimes: {simulateTimes}");
Console.WriteLine($"");
var rand = new Random();
var stopwatch = new Stopwatch();
stopwatch.Start();
foreach (var l in Enumerable.Range(20, 10))
{
SimulateDeck(deckSize, l);
}
stopwatch.Stop();
Console.WriteLine($"({stopwatch.Elapsed.TotalMilliseconds}ms.)");
// 規定数の初手を生成し、期待値に一番近かった初手の土地の枚数を返す
char SimulateOnce(ReadOnlySpan<bool> deck, double expectedLandsCount)
{
char candidate = (char)0;
double lastDiff = double.PositiveInfinity;
for (int i = 0; i < sampleHandsCount; i++)
{
var lands = SimulateOpeningHand(deck);
var diff = Math.Abs(lands - expectedLandsCount);
if (diff < lastDiff)
{
lastDiff = diff;
candidate = lands;
}
}
return candidate;
}
// 初手1回分の土地の枚数を返す
char SimulateOpeningHand(ReadOnlySpan<bool> deck)
{
Span<char> buf = stackalloc char[openingHandsCards];
var count = 0;
while (count < openingHandsCards)
{
int idx;
lock (rand)
{
idx = rand.Next(59);
}
var dup = false;
for (var cur = 0; cur < count; cur++)
{
if (buf[cur] == idx)
{
dup = true;
break;
}
}
if (!dup)
{
buf[count++] = (char)idx;
}
}
char landsCount = (char)0;
for (int i = 0; i < openingHandsCards; i++)
{
if (deck[buf[i]])
{
landsCount++;
}
}
return landsCount;
}
void SimulateDeck(int deckCards, int lands)
{
var ratio = GetExpectedCount(deckCards, lands);
Console.WriteLine($"=== simulate {lands}/{deckCards} lands ({ratio:F2} lands expected) ===");
var deck = Enumerable.Repeat(true, lands).Concat(Enumerable.Repeat(false, deckCards - lands)).ToArray();
char[] result = new char[simulateTimes];
for (int i = 0; i < simulateTimes; i++)
{
result[i] = SimulateOnce(deck, ratio);
}
var grouped = result.GroupBy(l => l).OrderBy(l => l.Key).ToList();
var max = grouped.MaxBy(l => l.Count()).Key;
foreach (var n in Enumerable.Range(0, 8))
{
Console.Write(n == max ? "*" : " ");
var r = grouped.FirstOrDefault(g => g.Key == n);
var count = r?.Count() ?? 0;
var percentage = ((count / (double)simulateTimes) * 100).ToString("F2").PadLeft(6);
Console.WriteLine($"{n}: {percentage}%");
}
}
double GetExpectedCount(int deckCards, int lands)
{
var deck = Enumerable.Repeat(true, lands).Concat(Enumerable.Repeat(false, deckCards - lands)).ToArray();
char[] buf = new char[simulateTimes];
for (int i = 0; i < simulateTimes; i++)
{
buf[i] =SimulateOpeningHand(deck);
}
var expected = buf.Average(c => c);
return ((int)(expected*1000)) / 1000.0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment