Created
September 25, 2022 09:43
-
-
Save VienosNotes/36c68b8c2c330d887afe502afb7a0cb5 to your computer and use it in GitHub Desktop.
Opening hand simulator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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