#include "Main.h" const bool fastTestMode = false; String TestPlayer::VisualizationDescription(const Vector &supplyCards, bool ignoreBuyID, bool ignoreCounts) const { const BuyAgendaMenu *menu = dynamic_cast(&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->cost < maxCost && (ignoreBuyID || buyID[supplyCards.FindFirstIndex(curEntry.c)] == '1') && curEntry.c->name != "estate") 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) { // // Threads sometime run in lock-step: we could try to kick them out of it by staggered sleeping // //int randomSeed = Utility::Hash32((int)&threadLocalStorage); //Sleep(randomSeed % 3 + rand() % 3); *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; // // Higher-ranked leaders receive higher weight to reward strategies that are able to defeat them // _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; // // Make a game just so we can easily grab the supply piles // DominionGame g; g.Init(cards); Vector playerList; playerList.PushEnd(PlayerInfo(0, "p0", new PlayerHuman())); playerList.PushEnd(PlayerInfo(1, "p1", new PlayerHuman())); g.NewGame(playerList, _gameOptions); _supplyCards = g.data().supplyCards; // // Start the pool and leaders with variations on big money // InitializePool(cards); while(_leaders.Length() < _parameters.leaderCount) { _leaders.PushEnd(_pool.RandomElement()); } } void TestChamber::AssignBuyIDs(const Grid &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 leaderBuyIDs; Vector leaderMenuIDs; Vector leaderSupplyCounters(supplyCount, 0); // // To make for more diverse leaders, certain cards are prevented from occuring in more than half of the leader buy orders // 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); //leaderSupplyCounters[supplyIndex] = Utility::Bound(leaderSupplyCounters[supplyIndex], 3, 6); } 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 { // // Fixed set of possible permutations // Vector newPool; for(TestPlayer *p : _leaders) { // // Try possible alterations to the menu entries // const BuyMenu &menu = dynamic_cast(&(p->p->Agenda()))->GetMenu(); for(UINT menuIndex = 0; menuIndex < menu.entries.Length(); menuIndex++) { const BuyMenuEntry &curEntry = menu.entries[menuIndex]; if(curEntry.count != 99) { // // Eliminate the current menu entry // BuyMenu menuCopy = menu; menuCopy.entries[menuIndex].count = 0; newPool.PushEnd(new TestPlayer(new PlayerHeuristic(new BuyAgendaMenu(menuCopy)))); // // Adjust count of current menu entry // 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)))); } } // // Replace entry with 1, 2, and 4 of each possible supply card // 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)))); } } } } } // // Try possible alterations to the buy thresholds // 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 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)) { // // Override the previous version of this buy pattern, under thea assumption that the later generation one is better. // _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) { //ComputeCounters(cards, directory + "counters/" + String::ZeroPad(_generation, 3) + "_" + String(_metaIndex) + ".txt"); } } } void TestChamber::StrategizeStep(const CardDatabase &cards) { Assert(_pool.Length() != 0, "StrategizeStart not called"); Console::WriteLine("Testing generation " + String(_generation) + _metaSuffix); // // Run all AIs in the current pool against the leaders // Grid poolResults = TestPool(cards, _directory + "generations/" + String::ZeroPad(_generation, 3) + _metaSuffix + ".txt"); AssignBuyIDs(poolResults); // // Sort the pool according to their scores relative to the previous leaders // _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 TestChamber::RunAllPairsTests(const CardDatabase &cards, const Vector &playersA, const Vector &playersB, UINT minGameCount, UINT maxGameCount) { Grid 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 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 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 > players; for(UINT playerIndex = 0; playerIndex < _allLeaderHistory.Length(); playerIndex++) { players.PushEnd(make_pair(_allLeaderHistory[playerIndex], results(leaderIndex, playerIndex).winRatio[0])); } players.Sort([](const pair &a, const pair &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 &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 leaderList(1, leader); Grid 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++) { //Console::WriteLine("Opponent " + String(leaderIndex) + ": " + String(results(leaderIndex, 0).winRatio[1])); file << "Opponent " << leaderIndex << ":\t" << String((results(leaderIndex, 0).winRatio[1] - 0.5) * 200.0) << "\t" << players[leaderIndex]->VisualizationDescription(_supplyCards, true) << endl; } //file << endl << "Full description" << endl; //for(UINT leaderIndex = 0; leaderIndex < leaderCount; leaderIndex++) //{ // file << "Opponent " << leaderIndex << ":\t" << players[leaderIndex]->p->ControllerName() << endl; //} } void TestChamber::ComputeLeaderboard(const CardDatabase &cards, const Vector &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 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 << "Full description" << endl; //for(UINT leaderIndex = 0; leaderIndex < leaderCount; leaderIndex++) //{ // file << "Leader " << leaderIndex << ":\t" << players[leaderIndex]->p->ControllerName() << 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 TestChamber::TestPool(const CardDatabase &cards, const String &filename) { Grid 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 << "Leaders" << endl; file << "Index\tRating\tName" << endl; for(UINT leaderIndex = 0; leaderIndex < _leaders.Length(); leaderIndex++) { file << leaderIndex << '\t' << _leaders[leaderIndex]->rating << '\t' << _leaders[leaderIndex]->p->ControllerName() << 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 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]; //if(a != NULL && b != NULL) _pool.PushEnd(new TestPlayer(AIUtility::MakeRandomPlayer(cards, a, b))); //_pool.PushEnd(new TestPlayer(AIUtility::MakeTwoCardPlayer(cards, a, b))); TestPlayer *newPlayer = new TestPlayer(new PlayerHeuristic(new BuyAgendaMenu(cards, _gameOptions))); newPlayer->p = newPlayer->p->Mutate(cards, _gameOptions); _pool.PushEnd(newPlayer); //newPlayer->p = new PlayerHeuristic(new BuyAgendaMenu(menu)); } } 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)))); } /*for(UINT mutationTestIndex = 0; mutationTestIndex < 1000000; mutationTestIndex++) { PlayerHeuristic *p = _pool.RandomElement()->p->Mutate(cards, options); for(UINT mutation = 0; mutation < 50; mutation++) { p = p->Mutate(cards, options); } }*/ } 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 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 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; }