#include "Main.h"
const bool fastTestMode = false;
String TestPlayer::VisualizationDescription(const Vector<Card*> &supplyCards, bool ignoreBuyID, bool ignoreCounts) const
{
const BuyAgendaMenu *menu = dynamic_cast<const BuyAgendaMenu*>(&p->Agenda());
BuyMenu m = menu->GetMenu();
m.Cleanup();
m.Merge();
int maxCost = 999;
String result = "e" + String(m.estateBuyThreshold) + "-d" + String(m.duchyBuyThreshold) + "-p" + String(m.provinceBuyThreshold) + "-";
for(UINT index = 0; index < m.entries.Length(); index++)
{
const BuyMenuEntry &curEntry = m.entries[index];
if(curEntry.count > 0 && (curEntry.c->name == "peddler" || curEntry.c->cost < maxCost))
{
if(ignoreCounts) result += curEntry.c->name + "|";
else result += curEntry.c->name + "@" + String(curEntry.count) + "|";
}
if(curEntry.count >= 15)
{
maxCost = curEntry.c->cost;
}
}
result.PopEnd();
return result;
}
void TestChamberTask::Run(ThreadLocalStorage *threadLocalStorage)
{
*result = chamber->Test(*cards, params, false);
}
void TestChamber::FreeMemory()
{
_pool.FreeMemory();
_leaders.FreeMemory();
_bestLeaderHistory.FreeMemory();
_allLeaderHistory.FreeMemory();
_allLeaderHistoryHashes.FreeMemory();
_supplyCards.FreeMemory();
_leaderWeights.FreeMemory();
_generation = 1;
}
void TestChamber::StrategizeStart(const CardDatabase &cards, const GameOptions &options, const String &directory, const String &metaSuffix)
{
FreeMemory();
_gameOptions = options;
_metaSuffix = metaSuffix;
_directory = directory;
_leaderWeights.Allocate(_parameters.leaderCount);
for(UINT leaderIndex = 0; leaderIndex < _parameters.leaderCount; leaderIndex++)
{
_leaderWeights[leaderIndex] = Math::LinearMap(0.0, _parameters.leaderCount - 1.0, _parameters.leaderCurveMin, _parameters.leaderCurveMax, double(leaderIndex));
}
double sum = _leaderWeights.Sum();
for(UINT leaderIndex = 0; leaderIndex < _parameters.leaderCount; leaderIndex++) _leaderWeights[leaderIndex] /= sum;
if(_parameters.leaderCount == 1) _leaderWeights[0] = 1.0;
DominionGame g;
g.Init(cards);
Vector<PlayerInfo> playerList;
playerList.PushEnd(PlayerInfo(0, "p0", new PlayerHuman()));
playerList.PushEnd(PlayerInfo(1, "p1", new PlayerHuman()));
g.NewGame(playerList, _gameOptions);
_supplyCards = g.data().supplyCards;
InitializePool(cards);
while(_leaders.Length() < _parameters.leaderCount)
{
_leaders.PushEnd(_pool.RandomElement());
}
}
void TestChamber::AssignBuyIDs(const Grid<TestResult> &poolResults)
{
const UINT supplyCount = _supplyCards.Length();
for(UINT poolIndex = 0; poolIndex < _pool.Length(); poolIndex++)
{
String curID = "";
for(UINT supplyIndex = 0; supplyIndex < supplyCount; supplyIndex++)
{
char c = '0';
for(UINT leaderIndex = 0; leaderIndex < _leaders.Length(); leaderIndex++)
{
if(_supplyCards[supplyIndex]->expansion == "core" || poolResults(poolIndex, leaderIndex).buyRatio[supplyIndex] > 0.25) c = '1';
}
curID.PushEnd(c);
}
_pool[poolIndex]->buyID = curID;
}
}
void TestChamber::AssignNewLeaders(const CardDatabase &cards)
{
_leaders.FreeMemory();
const UINT supplyCount = _supplyCards.Length();
Vector<String> leaderBuyIDs;
Vector<String> leaderMenuIDs;
Vector<int> leaderSupplyCounters(supplyCount, 0);
for(UINT supplyIndex = 0; supplyIndex < supplyCount; supplyIndex++)
{
Card *c = _supplyCards[supplyIndex];
if(c->strength == -1) leaderSupplyCounters[supplyIndex] = _parameters.leaderCount;
else leaderSupplyCounters[supplyIndex] = Math::Ceiling((double)_parameters.leaderCount / 2.0);
}
while(_leaders.Length() < _parameters.leaderCount)
{
bool newLeaderFound = false;
for(UINT poolIndex = 0; poolIndex < _pool.Length() && !newLeaderFound; poolIndex++)
{
TestPlayer *curPlayer = _pool[poolIndex];
if(!leaderBuyIDs.Contains(curPlayer->buyID) && !leaderMenuIDs.Contains(curPlayer->VisualizationDescription(_supplyCards, true, true)))
{
bool overusedCardFound = false;
for(UINT supplyIndex = 0; supplyIndex < supplyCount; supplyIndex++)
{
if(curPlayer->buyID[supplyIndex] == '1' && leaderSupplyCounters[supplyIndex] <= 0) overusedCardFound = true;
}
if(!overusedCardFound)
{
newLeaderFound = true;
_leaders.PushEnd(curPlayer);
leaderBuyIDs.PushEnd(curPlayer->buyID);
leaderMenuIDs.PushEnd(curPlayer->VisualizationDescription(_supplyCards, true, true));
for(UINT supplyIndex = 0; supplyIndex < supplyCount; supplyIndex++)
{
if(_supplyCards[supplyIndex]->expansion != "core" && curPlayer->buyID[supplyIndex] == '1') leaderSupplyCounters[supplyIndex]--;
}
}
}
}
if(!newLeaderFound)
{
Console::WriteLine("No leader found!");
TestPlayer *newPlayer = new TestPlayer(new PlayerHeuristic(new BuyAgendaMenu(cards, _gameOptions)));
newPlayer->p = newPlayer->p->Mutate(cards, _gameOptions);
_leaders.PushEnd(newPlayer);
}
}
}
void TestChamber::GenerateNewPool(const CardDatabase &cards)
{
_pool.FreeMemory();
for(UINT leaderIndex = 0; leaderIndex < _leaders.Length(); leaderIndex++) _pool.PushEnd(_leaders[leaderIndex]);
const bool useMutationPool = true;
if(useMutationPool)
{
while(_pool.Length() < _parameters.poolSize)
{
TestPlayer *parent = _leaders.RandomElement();
TestPlayer *mutatedChild = new TestPlayer(parent->p->Mutate(cards, _gameOptions));
while(rnd() >= _parameters.mutationTerminationProbability)
{
mutatedChild = new TestPlayer(mutatedChild->p->Mutate(cards, _gameOptions));
}
_pool.PushEnd(mutatedChild);
}
}
else
{
Vector<TestPlayer*> newPool;
for(TestPlayer *p : _leaders)
{
const BuyMenu &menu = dynamic_cast<const BuyAgendaMenu*>(&(p->p->Agenda()))->GetMenu();
for(UINT menuIndex = 0; menuIndex < menu.entries.Length(); menuIndex++)
{
const BuyMenuEntry &curEntry = menu.entries[menuIndex];
if(curEntry.count != 99)
{
BuyMenu menuCopy = menu;
menuCopy.entries[menuIndex].count = 0;
newPool.PushEnd(new TestPlayer(new PlayerHeuristic(new BuyAgendaMenu(menuCopy))));
for(int countDelta = -2; countDelta <= 2; countDelta++)
{
int newCount = curEntry.count + countDelta;
if(newCount != curEntry.count && newCount >= 1 && newCount <= 10)
{
BuyMenu menuCopy = menu;
menuCopy.entries[menuIndex].count = newCount;
newPool.PushEnd(new TestPlayer(new PlayerHeuristic(new BuyAgendaMenu(menuCopy))));
}
}
int cardCountList[3] = {1, 2, 4};
for(UINT supplyIndex = 0; supplyIndex < _supplyCards.Length(); supplyIndex++)
{
Card *c = _supplyCards[supplyIndex];
if(c != curEntry.c && curEntry.CardValidInSlot(c) && c->name != "curse" && c->name != "copper" && c->name != "estate" && c->name != "duchy" && c->name != "province" && c->name != "colony")
{
for(UINT countIndex = 0; countIndex < 3; countIndex++)
{
BuyMenu menuCopy = menu;
menuCopy.entries[menuIndex].c = c;
menuCopy.entries[menuIndex].count = cardCountList[countIndex];
newPool.PushEnd(new TestPlayer(new PlayerHeuristic(new BuyAgendaMenu(menuCopy))));
}
}
}
}
}
for(int countDelta = -2; countDelta <= 2; countDelta++)
{
int newEstateCount = menu.estateBuyThreshold + countDelta;
if(newEstateCount != menu.estateBuyThreshold && newEstateCount >= 0 && newEstateCount <= 8)
{
BuyMenu menuCopy = menu;
menuCopy.estateBuyThreshold = newEstateCount;
newPool.PushEnd(new TestPlayer(new PlayerHeuristic(new BuyAgendaMenu(menuCopy))));
}
int newDuchyCount = menu.duchyBuyThreshold + countDelta;
if(newDuchyCount != menu.duchyBuyThreshold && newDuchyCount >= 0 && newDuchyCount <= 8)
{
BuyMenu menuCopy = menu;
menuCopy.duchyBuyThreshold = newDuchyCount;
newPool.PushEnd(new TestPlayer(new PlayerHeuristic(new BuyAgendaMenu(menuCopy))));
}
int newProvinceCount = menu.provinceBuyThreshold + countDelta;
if(_gameOptions.prosperity && newProvinceCount != menu.provinceBuyThreshold && newProvinceCount >= 0 && newProvinceCount <= 8)
{
BuyMenu menuCopy = menu;
menuCopy.provinceBuyThreshold = newProvinceCount;
newPool.PushEnd(new TestPlayer(new PlayerHeuristic(new BuyAgendaMenu(menuCopy))));
}
}
}
Vector<String> IDs;
for(TestPlayer *p : newPool)
{
String id = p->VisualizationDescription(_supplyCards, true);
if(!IDs.Contains(id))
{
IDs.PushEnd(id);
_pool.PushEnd(p);
}
}
}
}
void TestChamber::SaveVisualizationFiles(const CardDatabase &cards)
{
if(_generation % 4 == 0)
{
ComputeLeaderboard(cards, _leaders, _directory + "leaderboard/" + String::ZeroPad(_generation, 3) + _metaSuffix + ".txt", _parameters.visualizationGameCount);
_bestLeaderHistory.PushEnd(_leaders[0]);
for(UINT leaderIndex = 0; leaderIndex < _leaders.Length(); leaderIndex++)
{
UINT curHash = _leaders[leaderIndex]->buyID.Hash32();
if(_allLeaderHistoryHashes.Contains(curHash))
{
_allLeaderHistory[_allLeaderHistoryHashes.FindFirstIndex(curHash)] = _leaders[leaderIndex];
}
else
{
_allLeaderHistoryHashes.PushEnd(curHash);
_allLeaderHistory.PushEnd(_leaders[leaderIndex]);
}
}
ComputeProgression(cards, _leaders[0], _bestLeaderHistory, _directory + "progression/" + String::ZeroPad(_generation, 3) + _metaSuffix + ".txt");
if(_generation % 64 == 0)
{
}
}
}
void TestChamber::StrategizeStep(const CardDatabase &cards)
{
Assert(_pool.Length() != 0, "StrategizeStart not called");
Console::WriteLine("Testing generation " + String(_generation) + _metaSuffix);
Grid<TestResult> poolResults = TestPool(cards, _directory + "generations/" + String::ZeroPad(_generation, 3) + _metaSuffix + ".txt");
AssignBuyIDs(poolResults);
_pool.Sort([](const TestPlayer *a, const TestPlayer *b) { return a->rating > b->rating; });
Console::WriteLine("generation" + String::ZeroPad(_generation, 3) + ", leader win percentage: " + String(_pool[0]->rating * 100.0) + "%");
Console::WriteLine(_pool[0]->VisualizationDescription(_supplyCards, true));
AssignNewLeaders(cards);
GenerateNewPool(cards);
SaveVisualizationFiles(cards);
_generation++;
}
Grid<TestResult> TestChamber::RunAllPairsTests(const CardDatabase &cards, const Vector<TestPlayer*> &playersA, const Vector<TestPlayer*> &playersB, UINT minGameCount, UINT maxGameCount)
{
Grid<TestResult> allResults(playersA.Length(), playersB.Length());
TestParameters params;
params.minGameCount = minGameCount;
params.maxGameCount = maxGameCount;
params.options = _gameOptions;
if(fastTestMode)
{
params.minGameCount = 10;
params.maxGameCount = 10;
}
TaskList<WorkerThreadTask*> tasks;
for(UINT playerAIndex = 0; playerAIndex < playersA.Length(); playerAIndex++)
{
for(UINT playerBIndex = 0; playerBIndex < playersB.Length(); playerBIndex++)
{
TestChamberTask *newTask = new TestChamberTask;
newTask->chamber = this;
newTask->cards = &cards;
newTask->params = params;
newTask->params.players[0] = playersA[playerAIndex]->p;
newTask->params.players[1] = playersB[playerBIndex]->p;
newTask->result = &allResults(playerAIndex, playerBIndex);
tasks.InsertTask(newTask);
}
}
ThreadPool threadPool;
threadPool.Init(7);
threadPool.Go(tasks);
return allResults;
}
void TestChamber::ComputeCounters(const CardDatabase &cards, const String &filename)
{
ofstream file(filename.CString());
file << "Counters" << endl;
Grid<TestResult> results = RunAllPairsTests(cards, _leaders, _allLeaderHistory, _parameters.visualizationGameCount, _parameters.visualizationGameCount);
file << endl << "Kingdom cards:\t" << _gameOptions.ToString() << endl;
const UINT supplyCount = _supplyCards.Length();
file << endl << "Leaders:\t" << _leaders.Length() << endl;
for(UINT leaderIndex = 0; leaderIndex < _leaders.Length(); leaderIndex++)
{
file << endl << "Leader" << leaderIndex << "\t0\t" << _leaders[leaderIndex]->VisualizationDescription(_supplyCards, true) << endl;
Vector< pair<TestPlayer*, double> > players;
for(UINT playerIndex = 0; playerIndex < _allLeaderHistory.Length(); playerIndex++)
{
players.PushEnd(make_pair(_allLeaderHistory[playerIndex], results(leaderIndex, playerIndex).winRatio[0]));
}
players.Sort([](const pair<TestPlayer*, double> &a, const pair<TestPlayer*, double> &b) { return a.second < b.second; } );
for(int playerIndex = 0; playerIndex < Math::Min(int(_allLeaderHistory.Length()), 20); playerIndex++)
{
Console::WriteLine("Counter" + String(playerIndex) + "\t" + String(results(leaderIndex, 0).winRatio[1]));
file << "Opponent " << playerIndex << ":\t" << String((players[playerIndex].second - 0.5) * 200.0) << "\t" << players[playerIndex].first->VisualizationDescription(_supplyCards, true) << endl;
}
}
}
void TestChamber::ComputeProgression(const CardDatabase &cards, TestPlayer *leader, const Vector<TestPlayer*> &players, const String &filename)
{
const UINT leaderCount = players.Length();
Console::WriteLine("Generating progression comparison for generation " + String(_generation) + _metaSuffix);
ofstream file(filename.CString());
file << "Progression" << endl;
Vector<TestPlayer*> leaderList(1, leader);
Grid<TestResult> results = RunAllPairsTests(cards, players, leaderList, _parameters.visualizationGameCount, _parameters.visualizationGameCount);
file << endl << "Kingdom cards:\t" << _gameOptions.ToString() << endl;
const UINT supplyCount = _supplyCards.Length();
for(UINT leaderIndexA = 0; leaderIndexA < leaderCount; leaderIndexA++)
{
String curID = "";
for(UINT supplyIndex = 0; supplyIndex < supplyCount; supplyIndex++)
{
char c = '0';
if(_supplyCards[supplyIndex]->expansion == "core" || results(leaderIndexA, 0).buyRatio[supplyIndex] > 0.25) c = '1';
curID.PushEnd(c);
}
players[leaderIndexA]->buyID = curID;
}
file << endl << "Leader\t0\t" << players.Last()->VisualizationDescription(_supplyCards, true) << endl;
file << endl << "Opponents:\t" << leaderCount << endl;
for(UINT leaderIndex = 0; leaderIndex < leaderCount; leaderIndex++)
{
file << "Opponent " << leaderIndex << ":\t" << String((results(leaderIndex, 0).winRatio[1] - 0.5) * 200.0) << "\t" << players[leaderIndex]->VisualizationDescription(_supplyCards, true) << endl;
}
}
void TestChamber::ComputeLeaderboard(const CardDatabase &cards, const Vector<TestPlayer*> &players, const String &filename, UINT gameCount)
{
Console::WriteLine("Generating leaderboard comparison for generation " + String(_generation) + _metaSuffix);
const UINT leaderCount = players.Length();
ofstream file(filename.CString());
Grid<TestResult> results = RunAllPairsTests(cards, players, players, gameCount, gameCount);
file << "Leaderboard" << endl;
file << endl << "Kingdom cards:\t" << _gameOptions.ToString() << endl;
const UINT supplyCount = _supplyCards.Length();
for(UINT leaderIndexA = 0; leaderIndexA < leaderCount; leaderIndexA++)
{
String curID = "";
for(UINT supplyIndex = 0; supplyIndex < supplyCount; supplyIndex++)
{
char c = '0';
for(UINT leaderIndexB = 0; leaderIndexB < leaderCount; leaderIndexB++)
{
if(_supplyCards[supplyIndex]->expansion == "core" || results(leaderIndexA, leaderIndexB).buyRatio[supplyIndex] > 0.25) c = '1';
}
curID.PushEnd(c);
}
players[leaderIndexA]->buyID = curID;
}
file << endl << "Leaders:\t" << leaderCount << endl;
for(UINT leaderIndexA = 0; leaderIndexA < leaderCount; leaderIndexA++)
{
double averageWinRatio = 0.0;
for(UINT leaderIndexB = 0; leaderIndexB < leaderCount; leaderIndexB++) if(leaderIndexA != leaderIndexB) averageWinRatio += results(leaderIndexA, leaderIndexB).winRatio[0];
if(leaderCount > 1) averageWinRatio /= double(leaderCount - 1);
file << "Leader " << leaderIndexA << ":\t" << (averageWinRatio - 0.5) * 200.0 << "\t" << players[leaderIndexA]->VisualizationDescription(_supplyCards, true) << endl;
}
file << endl << "Tournament" << endl;
file << "player";
for(UINT leaderIndexA = 0; leaderIndexA < leaderCount; leaderIndexA++) file << "\t" << leaderIndexA;
file << endl;
for(UINT leaderIndexA = 0; leaderIndexA < leaderCount; leaderIndexA++)
{
file << leaderIndexA;
for(UINT leaderIndexB = 0; leaderIndexB < leaderCount; leaderIndexB++)
{
file << "\t" << (results(leaderIndexA, leaderIndexB).winRatio[0] - 0.5) * 200.0;
}
file << endl;
}
file << endl << "Buy Statistics" << endl;
file << "player";
for(UINT supplyIndex = 0; supplyIndex < supplyCount; supplyIndex++) file << "\t" << _supplyCards[supplyIndex]->name;
file << endl;
for(UINT leaderIndexA = 0; leaderIndexA < leaderCount; leaderIndexA++)
{
file << leaderIndexA;
for(UINT supplyIndex = 0; supplyIndex < supplyCount; supplyIndex++)
{
double value = 0.0;
for(UINT leaderIndexB = 0; leaderIndexB < leaderCount; leaderIndexB++)
{
value += results(leaderIndexA, leaderIndexB).buyRatio[supplyIndex];
}
file << "\t" << value / double(leaderCount);
}
file << endl;
}
}
Grid<TestResult> TestChamber::TestPool(const CardDatabase &cards, const String &filename)
{
Grid<TestResult> allResults = RunAllPairsTests(cards, _pool, _leaders, 200, _parameters.standardGameCount);
const UINT supplyCount = allResults(0, 0).buyRatio.Length();
const UINT leaderCount = _leaders.Length();
double score = 0.0;
for(UINT playerIndex = 0; playerIndex < _pool.Length(); playerIndex++)
{
double rating = 0.0;
for(UINT leaderIndex = 0; leaderIndex < leaderCount; leaderIndex++) rating += allResults(playerIndex, leaderIndex).winRatio[0] * _leaderWeights[leaderIndex];
_pool[playerIndex]->rating = rating;
}
ofstream file(filename.CString());
file << "Generation" << endl;
file << endl << "Kingdom cards:\t" << _gameOptions.ToString() << endl;
file << endl << "Leaders:\t" << leaderCount << endl;
for(UINT leaderIndex = 0; leaderIndex < leaderCount; leaderIndex++)
{
file << "Leader " << leaderIndex << ":\t" << (_leaders[leaderIndex]->rating - 0.5) * 200.0 << "\t" << _leaders[leaderIndex]->VisualizationDescription(_supplyCards, true, false) << endl;
}
file << endl << "Index\tRating";
for(UINT leaderIndex = 0; leaderIndex < _leaders.Length(); leaderIndex++) file << "\tL" << leaderIndex;
for(UINT supplyIndex = 0; supplyIndex < supplyCount; supplyIndex++) file << '\t' << _supplyCards[supplyIndex]->PrettyName();
file << "\tName" << endl;
for(UINT playerIndex = 0; playerIndex < _pool.Length(); playerIndex++)
{
file << playerIndex << '\t' << _pool[playerIndex]->rating;
Vector<double> buyRatio(supplyCount, 0.0);
for(UINT leaderIndex = 0; leaderIndex < _leaders.Length(); leaderIndex++)
{
for(UINT supplyIndex = 0; supplyIndex < supplyCount; supplyIndex++) buyRatio[supplyIndex] += allResults(playerIndex, leaderIndex).buyRatio[supplyIndex];
file << '\t' << allResults(playerIndex, leaderIndex).winRatio[0];
}
for(UINT supplyIndex = 0; supplyIndex < supplyCount; supplyIndex++) file << '\t' << buyRatio[supplyIndex];
file << '\t' << _pool[playerIndex]->p->ControllerName() << endl;
}
return allResults;
}
void TestChamber::InitializePool(const CardDatabase &cards)
{
_pool.FreeMemory();
for(UINT supplyIndexA = 0; supplyIndexA < 11; supplyIndexA++)
{
Card *a = NULL;
if(supplyIndexA < 10) a = _gameOptions.supplyPiles[supplyIndexA];
for(UINT supplyIndexB = 0; supplyIndexB < 11; supplyIndexB++)
{
Card *b = NULL;
if(supplyIndexB < 10) b = _gameOptions.supplyPiles[supplyIndexB];
TestPlayer *newPlayer = new TestPlayer(new PlayerHeuristic(new BuyAgendaMenu(cards, _gameOptions)));
newPlayer->p = newPlayer->p->Mutate(cards, _gameOptions);
_pool.PushEnd(newPlayer);
}
}
for(UINT supplyIndexA = 0; supplyIndexA < 11; supplyIndexA++)
{
Card *a = NULL;
if(supplyIndexA > 0) a = _gameOptions.supplyPiles[supplyIndexA - 1];
if(supplyIndexA == 0) _bestLeaderHistory.PushEnd(new TestPlayer(new PlayerHeuristic(new BuyAgendaMenu(cards, _gameOptions, a, NULL))));
}
}
TestResult TestChamber::Test(const CardDatabase &cards, const TestParameters ¶ms, bool useConsole)
{
#ifndef LOGGING_CONSTANT
logging = false;
decisionText = false;
#endif
TestResult result;
if(useConsole)
{
Console::WriteLine("*** AI Test, max " + String(params.maxGameCount) + " games");
Console::WriteLine("Supply: " + params.options.ToString());
}
Vector<PlayerInfo> players;
for(UINT playerIndex = 0; playerIndex < 2; playerIndex++)
{
result.winRatio[playerIndex] = 0.0;
players.PushEnd(PlayerInfo(playerIndex, "p" + String(playerIndex), params.players[playerIndex]));
if(useConsole) Console::WriteLine(players.Last().name + ": " + players.Last().controller->ControllerName());
}
DominionGame game;
game.Init(cards);
Clock timer;
int gamesSampled = 0;
bool significantDifferenceFound = false;
for(UINT gameIndex = 0; gameIndex < params.maxGameCount && !significantDifferenceFound; gameIndex++)
{
game.NewGame(players, params.options);
AIUtility::AdvanceAIs(game);
gamesSampled++;
const UINT supplyCount = game.data().supplyCards.Length();
if(gameIndex == 0) result.buyRatio.Allocate(supplyCount, 0.0);
for(UINT supplyIndex = 0; supplyIndex < supplyCount; supplyIndex++)
{
if(game.data().players[0].ledger.cardsBought.Contains(game.data().supplyCards[supplyIndex])) result.buyRatio[supplyIndex] += 1.0;
}
Vector<int> winners = game.state().WinningPlayers();
for(UINT playerIndex = 0; playerIndex < 2; playerIndex++)
{
if(winners.Contains(playerIndex)) result.winRatio[playerIndex] += 1.0 / double(winners.Length());
}
if(gameIndex >= params.minGameCount && gamesSampled % 200 == 0)
{
double errorMargin = 2.0 / sqrt(gamesSampled);
double spread = fabs((result.winRatio[0] - result.winRatio[1]) / double(gamesSampled));
significantDifferenceFound = (errorMargin <= spread);
}
}
for(UINT supplyIndex = 0; supplyIndex < result.buyRatio.Length(); supplyIndex++) result.buyRatio[supplyIndex] /= double(gamesSampled);
for(UINT playerIndex = 0; playerIndex < 2; playerIndex++)
{
result.winRatio[playerIndex] /= double(gamesSampled);
if(useConsole) Console::WriteLine(players[playerIndex].name + ": " + String(result.winRatio[playerIndex] * 100.0) + "%");
}
if(useConsole) Console::WriteLine("Time: " + String(timer.Elapsed()) + "s, games=" + String(gamesSampled));
return result;
}