#include "PrecompiledHeader.h" #include "TestSuiteAI.h" #include "MTGAbility.h" #include "MTGRules.h" #include "ActionLayer.h" #include "GuiCombat.h" #include "Rules.h" #include "GameObserver.h" #include "GameStateShop.h" using std::string; // NULL is sent in place of a MTGDeck since there is no way to create a MTGDeck without a proper deck file. // TestSuiteAI will be responsible for managing its own deck state. TestSuiteAI::TestSuiteAI(TestSuite * _suite, int playerId) : AIPlayerBaka(NULL, "testsuite", "testsuite", "baka.jpg") { this->game = _suite->buildDeck(playerId); game->setOwner(this); suite = _suite; timer = 0; playMode = MODE_TEST_SUITE; this->deckName = "Test Suite AI"; } MTGCardInstance * TestSuiteAI::getCard(string action) { int mtgid = Rules::getMTGId(action); if (mtgid) return Rules::getCardByMTGId(mtgid); //This mostly handles tokens GameObserver * g = GameObserver::GetInstance(); std::transform(action.begin(), action.end(), action.begin(), ::tolower); for (int i = 0; i < 2; i++) { Player * p = g->players[i]; MTGGameZone * zones[] = { p->game->library, p->game->hand, p->game->inPlay, p->game->graveyard }; for (int j = 0; j < 4; j++) { MTGGameZone * zone = zones[j]; for (int k = 0; k < zone->nb_cards; k++) { MTGCardInstance * card = zone->cards[k]; if (!card) return NULL; string name = card->getLCName(); if (name.compare(action) == 0) return card; } } } DebugTrace("TESTUISTEAI: Can't find card:" << action.c_str()); return NULL; } Interruptible * TestSuite::getActionByMTGId(int mtgid) { ActionStack * as = GameObserver::GetInstance()->mLayers->stackLayer(); Interruptible * action = NULL; while ((action = as->getNext(action, 0, 0, 1))) { if (action->source && action->source->getMTGId() == mtgid) { return action; } } return NULL; } int TestSuiteAI::displayStack() { if (playMode == MODE_AI) return 0; return 1; } int TestSuiteAI::Act(float dt) { GameObserver * g = GameObserver::GetInstance(); g->gameOver = NULL; // Prevent draw rule from losing the game if (playMode == MODE_AI && suite->aiMaxCalls) { suite->aiMaxCalls--; suite->timerLimit = 40; //TODO Remove this limitation when AI is not using a stupid timer anymore... AIPlayerBaka::Act(dt); } if (playMode == MODE_HUMAN) { g->mLayers->CheckUserInput(0); return 1; } timer += 1; if (timer < suite->timerLimit) return 1; timer = 0; string action = suite->getNextAction(); g->mLayers->stackLayer()->Dump(); // DamageResolverLayer * drl = g->mLayers->combatLayer(); DebugTrace("TESTSUITE command: " << action); if (g->mLayers->stackLayer()->askIfWishesToInterrupt == this) { if (action.compare("no") != 0 && action.compare("yes") != 0) { g->mLayers->stackLayer()->cancelInterruptOffer(); suite->currentAction--; return 1; } } if (action == "") { //end of game suite->assertGame(); g->gameOver = g->players[0]; DebugTrace("================================ END OF TEST =======================\n"); return 1; } if (action.compare("eot") == 0) { if (g->getCurrentGamePhase() != Constants::MTG_PHASE_CLEANUP) suite->currentAction--; g->userRequestNextGamePhase(); } else if (action.compare("human") == 0) { DebugTrace("TESTSUITE You have control"); playMode = MODE_HUMAN; return 1; } else if (action.compare("ai") == 0) { DebugTrace("TESTSUITE Switching to AI"); playMode = MODE_AI; return 1; } else if (action.compare("next") == 0) { GuiCombat * gc = g->mLayers->combatLayer(); if (ORDER == g->combatStep || DAMAGE == g->combatStep) gc->clickOK(); else g->userRequestNextGamePhase(); } else if (action.compare("yes") == 0) g->mLayers->stackLayer()->setIsInterrupting(this); else if (action.compare("endinterruption") == 0) g->mLayers->stackLayer()->endOfInterruption(); else if (action.compare("no") == 0) { if (g->mLayers->stackLayer()->askIfWishesToInterrupt == this) g->mLayers->stackLayer()->cancelInterruptOffer(); } else if (action.find("choice ") != string::npos) { DebugTrace("TESTSUITE choice !!!"); int choice = atoi(action.substr(action.find("choice ") + 7).c_str()); g->mLayers->actionLayer()->doReactTo(choice); } else if (action.find(" -momir- ") != string::npos) { int start = action.find(" -momir- "); int cardId = Rules::getMTGId(action.substr(start + 9).c_str()); int cardIdHand = Rules::getMTGId(action.substr(0, start).c_str()); MTGMomirRule * a = ((MTGMomirRule *) g->mLayers->actionLayer()->getAbility(MTGAbility::MOMIR)); a->reactToClick(Rules::getCardByMTGId(cardIdHand), cardId); g->mLayers->actionLayer()->stuffHappened = 1; } else if (action.find("p1") != string::npos || action.find("p2") != string::npos) { Player * p = g->players[1]; size_t start = action.find("p1"); if (start != string::npos) p = g->players[0]; g->cardClick(NULL, p); } else { int mtgid = Rules::getMTGId(action); Interruptible * toInterrupt = NULL; if (mtgid) { DebugTrace("TESTSUITE CARD ID:" << mtgid); toInterrupt = suite->getActionByMTGId(mtgid); } if (toInterrupt) { g->stackObjectClicked(toInterrupt); return 1; } MTGCardInstance * card = getCard(action); if (card) { DebugTrace("TESTSUITE Clicking ON: " << card->name); card->currentZone->needShuffle = true; //mimic library shuffle g->cardClick(card, card); g->forceShuffleLibraries(); //mimic library shuffle return 1; } } return 0; } TestSuiteActions::TestSuiteActions() { nbitems = 0; } void TestSuiteActions::add(string s) { actions[nbitems] = s; nbitems++; } TestSuitePlayerData::TestSuitePlayerData() { life = 20; manapool = NEW ManaCost(); } TestSuitePlayerData::~TestSuitePlayerData() { SAFE_DELETE(manapool); } TestSuitePlayerZone::TestSuitePlayerZone() { nbitems = 0; } void TestSuitePlayerZone::add(int cardId) { cards[nbitems] = cardId; nbitems++; } TestSuiteState::TestSuiteState() { } void TestSuiteState::parsePlayerState(int playerId, string s) { size_t limiter = s.find(":"); string areaS; int area; if (limiter != string::npos) { areaS = s.substr(0, limiter); if (areaS.compare("graveyard") == 0) { area = 0; } else if (areaS.compare("library") == 0) { area = 1; } else if (areaS.compare("hand") == 0) { area = 2; } else if (areaS.compare("inplay") == 0 || areaS.compare("battlefield") == 0) { area = 3; } else if (areaS.compare("life") == 0) { playerData[playerId].life = atoi((s.substr(limiter + 1)).c_str()); return; } else if (areaS.compare("manapool") == 0) { SAFE_DELETE(playerData[playerId].manapool); playerData[playerId].manapool = ManaCost::parseManaCost(s.substr(limiter + 1)); return; } else { return; // ERROR } s = s.substr(limiter + 1); while (s.size()) { unsigned int value; limiter = s.find(","); if (limiter != string::npos) { string ss = s.substr(0, limiter); // ss is needed because trim requires a non-const reference, value = Rules::getMTGId(trim(ss)); // while in g++ functions cannot take non-const references from temporary values s = s.substr(limiter + 1); } else { value = Rules::getMTGId(trim(s)); s = ""; } if (value) playerData[playerId].zones[area].add(value); } } else { //ERROR } } string TestSuite::getNextAction() { currentAction++; if (actions.nbitems && currentAction <= actions.nbitems) { return actions.actions[currentAction - 1]; } return ""; } MTGPlayerCards * TestSuite::buildDeck(int playerId) { int list[100]; int nbcards = 0; for (int j = 0; j < 4; j++) { for (int k = 0; k < initState.playerData[playerId].zones[j].nbitems; k++) { int cardid = initState.playerData[playerId].zones[j].cards[k]; list[nbcards] = cardid; nbcards++; } } MTGPlayerCards * deck = NEW MTGPlayerCards(list, nbcards); return deck; } void TestSuite::initGame() { //The first test runs slowly, the other ones run faster. //This way a human can see what happens when testing a specific file, // or go faster when it comes to the whole test suite. //Warning, putting this value too low (< 3) will give unexpected results if (!timerLimit) { timerLimit = 40; } else { timerLimit = 3; } //Put the GameObserver in the initial state GameObserver * g = GameObserver::GetInstance(); DebugTrace("TESTSUITE Init Game"); g->phaseRing->goToPhase(initState.phase, g->players[0]); g->currentGamePhase = initState.phase; for (int i = 0; i < 2; i++) { AIPlayer * p = (AIPlayer *) (g->players[i]); p->forceBestAbilityUse = forceAbility; p->life = initState.playerData[i].life; p->getManaPool()->copy(initState.playerData[i].manapool); MTGGameZone * playerZones[] = { p->game->graveyard, p->game->library, p->game->hand, p->game->inPlay }; for (int j = 0; j < 4; j++) { MTGGameZone * zone = playerZones[j]; for (int k = 0; k < initState.playerData[i].zones[j].nbitems; k++) { MTGCardInstance * card = Rules::getCardByMTGId(initState.playerData[i].zones[j].cards[k]); if (card && zone != p->game->library) { if (zone == p->game->inPlay) { MTGCardInstance * copy = p->game->putInZone(card, p->game->library, p->game->stack); Spell * spell = NEW Spell(copy); spell->resolve(); if (!summoningSickness && p->game->inPlay->nb_cards > k) p->game->inPlay->cards[k]->summoningSickness = 0; delete spell; } else { if (!p->game->library->hasCard(card)) { LOG ("TESTUITE ERROR, CARD NOT FOUND IN LIBRARY\n"); } p->game->putInZone(card, p->game->library, zone); } } else { if (!card) { LOG ("TESTUITE ERROR, card is NULL\n"); } } } } } DebugTrace("TESTUITE Init Game Done !"); } int TestSuite::Log(const char * text) { ofstream file(JGE_GET_RES("test/results.html").c_str(), ios_base::app); if (file) { file << text; file << "\n"; file.close(); } DebugTrace(text); return 1; } int TestSuite::assertGame() { //compare the game state with the results char result[4096]; sprintf(result, "