using System.Text;
namespace Akwaier.Engine;
///
/// Rekent de regels na, zodat je bij een wijziging meteen ziet of er iets stuk
/// is gegaan. Starten met: Akwaier.exe zelftest [verslagbestand]
///
public static class Zelftest
{
private static StringBuilder _uit;
private static int _fout;
public static int Draai(string verslagPad)
{
_uit = new StringBuilder();
_fout = 0;
Prijsstaffel();
Legaliteit();
LosseSteen();
EnkeleFusie();
GelijkeKetens();
DrievoudigeFusie();
Stichten();
Kopen();
Verdeelsleutel();
VolledigePartijen();
Regel("");
Regel(_fout == 0 ? "ALLES GOED" : $"{_fout} CONTROLE(S) MISLUKT");
try { File.WriteAllText(verslagPad, _uit.ToString()); } catch (Exception) { }
return _fout;
}
// ------------------------------------------------------------ hulpmiddelen
private static void Regel(string s) => _uit.AppendLine(s);
private static void Kop(string s) { Regel(""); Regel(s); }
private static void Eis(bool goed, string wat)
{
Regel($" {(goed ? "ok " : "FOUT")}: {wat}");
if (!goed) _fout++;
}
/// Een leeg bord met zes schone spelers, om een stand na te bouwen.
private static Spel Schoon(int zaad = 1)
{
var g = new Spel(0, "", zaad);
for (int x = 1; x <= 15; x++)
for (int y = 1; y <= 15; y++)
Vul(g, x, y, Regels.Leeg);
for (int q = 1; q <= 6; q++)
{
g.Spelers[q].Geld = Regels.Startgeld;
for (int c = 1; c <= 8; c++) g.Spelers[q].Aandelen[c] = 0;
}
return g;
}
/// Een vakje rechtstreeks zetten; alleen voor het opbouwen van een proefstand.
private static void Vul(Spel g, int x, int y, int waarde)
{
var veld = typeof(Spel).GetField("_bord",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var groottes = typeof(Spel).GetField("_groottes",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var bord = (int[,])veld.GetValue(g);
var maten = (int[])groottes.GetValue(g);
int oud = bord[x, y];
if (oud == waarde) return;
if (oud >= 1) maten[oud]--;
bord[x, y] = waarde;
if (waarde >= 1) maten[waarde]++;
}
/// Een hand met de opgegeven vakjes klaarleggen.
private static void Handvol(Spel g, int speler, params (int X, int Y)[] vakjes)
{
var veld = typeof(Spel).GetField("_handen",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
var handen = (int[,])veld.GetValue(g);
for (int s = 0; s < Regels.HandGrootte; s++) handen[speler, s] = 0;
for (int s = 0; s < vakjes.Length; s++)
{
for (int n = 1; n <= 225; n++)
if (Regels.TegelPositie(n) == vakjes[s]) { handen[speler, s] = n; break; }
}
}
// -------------------------------------------------------------- de proeven
private static void Prijsstaffel()
{
Kop("1 prijsstaffel");
var g = Schoon();
for (int x = 1; x <= 5; x++) Vul(g, x, 1, 3); // keten C, vijf tegels
Eis(g.Prijs(3) == 1300, $"5 tegels C kost 1300 (is {g.Prijs(3)})");
g.Spelers[1].Aandelen[3] = 4;
Eis(g.Prijs(3) == 1340, $"met 4 uitstaande aandelen 1340 (is {g.Prijs(3)})");
Eis(g.Prijs(4) == 0, "een keten die niet bestaat kost niets");
}
private static void Legaliteit()
{
Kop("2 legaliteit");
var g = Schoon();
Vul(g, 5, 5, Regels.Los);
Eis(!g.Bekijk(5, 6).Mag, "naast een losse steen leggen mag niet");
Eis(g.Bekijk(8, 8).Mag, "op een leeg stuk bord leggen mag wel");
Vul(g, 6, 6, 2);
Eis(g.Bekijk(5, 6).Mag, "het mag zodra je ook keten B raakt");
Eis(g.KanStichten(8, 8), "een vrij liggend vakje is stichtbaar");
Eis(!g.KanStichten(5, 6), "een vakje met buren niet");
}
private static void LosseSteen()
{
Kop("3 losse steen wordt opgeslokt");
var g = Schoon();
Handvol(g, 1, (5, 6));
Vul(g, 5, 5, Regels.Los);
Vul(g, 6, 6, 2); Vul(g, 7, 6, 2);
var r = g.Leg(1, 0);
Eis(r.Keten == 2 && g.Vak(5, 5) == 2, "de losse steen gaat mee in keten B");
Eis(g.KetenGrootte(2) == 4, $"B groeit van 2 naar 4 (is {g.KetenGrootte(2)})");
}
private static void EnkeleFusie()
{
Kop("4 fusie met bonus");
var g = Schoon();
Handvol(g, 1, (4, 1));
for (int x = 1; x <= 3; x++) Vul(g, x, 1, 1); // A: drie tegels
for (int x = 5; x <= 10; x++) Vul(g, x, 1, 2); // B: zes tegels
g.Spelers[1].Aandelen[1] = 6;
g.Spelers[2].Aandelen[1] = 3;
g.Spelers[3].Aandelen[1] = 1;
int prijsA = g.Prijs(1);
var r = g.Leg(1, 0);
var u = r.Uitkeringen[0];
Eis(u.Keten == 1 && u.Naar == 2, "A gaat op in B");
Eis(u.Pot == (3 + 10) * prijsA,
$"pot = (3 tegels + 10 aandelen) x {prijsA} = {(3 + 10) * prijsA} (is {u.Pot})");
Eis(u.Bedragen[1] == (int)Math.Round(2.0 / 3.0 * u.Pot, MidpointRounding.AwayFromZero),
$"grootaandeelhouder krijgt twee derde ({u.Bedragen.GetValueOrDefault(1)})");
Eis(u.Bedragen[2] == (int)Math.Round(1.0 / 3.0 * u.Pot, MidpointRounding.AwayFromZero),
$"tweede krijgt een derde ({u.Bedragen.GetValueOrDefault(2)})");
Eis(!u.Bedragen.ContainsKey(3), "de derde aandeelhouder krijgt niets");
Eis(g.Spelers[1].Aandelen[2] == 3 && g.Spelers[2].Aandelen[2] == 1
&& g.Spelers[3].Aandelen[2] == 0, "aandelen half omgeruild: 6->3, 3->1, 1->0");
Eis(g.Spelers[1].Aandelen[1] == 0, "de aandelen A zijn weg");
Eis(g.KetenGrootte(2) == 10 && g.KetenGrootte(1) == 0, "B is nu 10 tegels, A bestaat niet meer");
Eis(g.TeStichten().Contains(1), "A kan opnieuw gesticht worden");
}
private static void GelijkeKetens()
{
Kop("5 twee even grote ketens");
var g = Schoon();
Handvol(g, 1, (4, 1));
for (int x = 1; x <= 3; x++) Vul(g, x, 1, 1);
for (int x = 5; x <= 7; x++) Vul(g, x, 1, 2);
var kans = g.Bekijk(4, 1);
Eis(kans.Gelijkspel && kans.Gelijken.Count == 2, "het gelijkspel wordt gezien");
var r = g.Leg(1, 0, 1); // de speler kiest A
Eis(r.Keten == 1 && g.KetenGrootte(1) == 7 && g.KetenGrootte(2) == 0,
"de keuze telt: A blijft en wordt 7 tegels");
}
private static void DrievoudigeFusie()
{
Kop("6 drievoudige fusie");
var g = Schoon();
Handvol(g, 1, (2, 2));
Vul(g, 1, 2, 1); Vul(g, 3, 2, 2); Vul(g, 2, 1, 3);
for (int x = 4; x <= 8; x++) Vul(g, x, 2, 2);
g.Spelers[1].Aandelen[1] = 2; g.Spelers[1].Aandelen[3] = 2;
var r = g.Leg(1, 0);
Eis(r.Keten == 2 && r.Opgeslokt.Count == 2, "A en C gaan beide op in B");
Eis(r.Uitkeringen.Count == 2, "er zijn twee uitkeringen");
Eis(g.KetenGrootte(2) == 9 && g.KetenGrootte(1) == 0 && g.KetenGrootte(3) == 0,
$"B is nu 9 tegels (is {g.KetenGrootte(2)})");
}
private static void Stichten()
{
Kop("7 stichten");
var g = Schoon();
Handvol(g, 1, (8, 8));
g.Sticht(1, 0, 5);
Eis(g.Vak(8, 8) == 5 && g.KetenGrootte(5) == 1, "keten E begint met één tegel");
Eis(g.Spelers[1].Aandelen[5] == 1, "de stichter krijgt één gratis aandeel");
Eis(!g.TeStichten().Contains(5), "E staat niet meer op de stichtlijst");
}
private static void Kopen()
{
Kop("8 kopen");
var g = Schoon();
for (int x = 1; x <= 5; x++) Vul(g, x, 1, 3); // C kost 1300
g.Spelers[1].Geld = 3000;
int n = g.Koop(1, 3, 3);
Eis(n == 2, $"drie gevraagd, twee betaalbaar (is {n})");
Eis(g.Spelers[1].Geld == 400 && g.Spelers[1].Aandelen[3] == 2, "saldo en bezit kloppen");
Eis(g.Koop(1, 3, 3) == 0, "met 400 over kan er niets meer bij");
Eis(g.Koop(1, 4, 3) == 0, "een keten die niet bestaat levert niets op");
}
private static void Verdeelsleutel()
{
Kop("9 verdeelsleutel");
var gevallen = new (int[] Bezit, double[] Verwacht)[]
{
(new[]{9,0,0,0,0,0}, new double[]{1,0,0,0,0,0}),
(new[]{5,5,5,0,0,0}, new[]{1/3.0,1/3.0,1/3.0,0,0,0}),
(new[]{5,5,2,0,0,0}, new double[]{.5,.5,0,0,0,0}),
(new[]{9,2,2,2,2,2}, new[]{2/3.0,1/15.0,1/15.0,1/15.0,1/15.0,1/15.0}),
(new[]{9,2,2,2,2,0}, new[]{2/3.0,1/12.0,1/12.0,1/12.0,1/12.0,0}),
(new[]{9,2,2,2,0,0}, new[]{2/3.0,1/9.0,1/9.0,1/9.0,0,0}),
(new[]{9,2,2,0,0,0}, new[]{2/3.0,1/6.0,1/6.0,0,0,0}),
(new[]{9,4,1,0,0,0}, new[]{2/3.0,1/3.0,0,0,0,0}),
};
bool alles = true, telt = true;
foreach (var (bezit, verwacht) in gevallen)
{
double[] got = Bonus.Verdeling(bezit);
for (int i = 0; i < 6; i++) if (Math.Abs(got[i] - verwacht[i]) > 1e-9) alles = false;
if (Math.Abs(got.Sum() - 1) > 1e-9) telt = false;
}
Eis(alles, "alle acht gevallen geven de juiste breuken");
Eis(telt, "elke verdeling telt op tot 1");
}
private static void VolledigePartijen()
{
Kop("10 zestig volledig uitgespeelde partijen");
int zetten = 0, fouten = 0;
string eerste = "";
for (int zaad = 0; zaad < 60; zaad++)
{
var g = new Spel(0, "", zaad);
int n = 0;
while (true)
{
int p = g.VolgendeSpeler();
if (p == 0) break;
g.ComputerBeurt(p);
n++;
string mis = Controleer(g);
if (mis != null) { fouten++; if (eerste == "") eerste = $"zaad {zaad}, zet {n}: {mis}"; break; }
if (n > 400) { fouten++; if (eerste == "") eerste = $"zaad {zaad}: loopt niet af"; break; }
}
zetten += n;
}
Eis(fouten == 0, fouten == 0
? $"na elke zet kloppen bord, ketens, aandelen, geld en de 225 stenen ({zetten} zetten)"
: $"{fouten} partij(en) misgegaan, eerste: {eerste}");
}
/// Rekent na of de spelstand nog samenhangend is.
private static string Controleer(Spel g)
{
var geteld = new int[9];
var bezet = new HashSet<(int, int)>();
for (int x = 1; x <= 15; x++)
for (int y = 1; y <= 15; y++)
{
int v = g.Vak(x, y);
if (v != Regels.Leeg && v != Regels.Los && (v < 1 || v > 8))
return $"vakje {Regels.Naam(x, y)} heeft waarde {v}";
if (v >= 1) geteld[v]++;
if (v != Regels.Leeg) bezet.Add((x, y));
}
for (int c = 1; c <= 8; c++)
if (g.KetenGrootte(c) != geteld[c])
return $"keten {Regels.KetenLetter(c)} telt {g.KetenGrootte(c)}, op het bord {geteld[c]}";
for (int q = 1; q <= 6; q++)
{
if (g.Spelers[q].Geld < 0) return $"speler {q} staat rood";
for (int c = 1; c <= 8; c++)
if (g.Spelers[q].Aandelen[c] < 0) return $"speler {q} heeft negatieve aandelen";
}
var stenen = new HashSet(g.Zak);
for (int p = 1; p <= 6; p++)
for (int s = 0; s < Regels.HandGrootte; s++)
{
int t = g.Hand(p, s);
if (t == 0) continue;
if (!stenen.Add(t)) return $"steen {t} komt dubbel voor";
if (bezet.Contains(Regels.TegelPositie(t)))
return $"steen {t} wijst naar een bezet vakje";
}
if (stenen.Count + bezet.Count != 225)
return $"{stenen.Count} losse + {bezet.Count} gelegde stenen is geen 225";
return null;
}
}