#include "PrecompiledHeader.h" #include "AIHints.h" #include "AIPlayerBaka.h" #include "utils.h" #include "AllAbilities.h" #include AIHint::AIHint(string _line) { string line = _line; if (!line.length()) { DebugTrace("AIHINTS: line is empty"); return; } std::transform(line.begin(), line.end(), line.begin(), ::tolower); mCondition = line; string action = line; vector splitAction = parseBetween(action, "sourceid(", ")"); if (splitAction.size()) { mAction = splitAction[0]; mSourceId = atoi(splitAction[1].c_str()); } else { mAction = action; mSourceId = 0; } vector splitDontAttack = parseBetween(action, "dontattackwith(", ")"); if(splitDontAttack.size()) { mCombatAttackTip = splitDontAttack[1]; } vector splitAlwaysAttack = parseBetween(action, "alwaysattackwith(", ")"); if(splitAlwaysAttack.size()) { mCombatAlwaysAttackTip = splitAlwaysAttack[1]; } vector splitDontBlock = parseBetween(action, "dontblockwith(", ")"); if(splitDontBlock.size()) { mCombatBlockTip = splitDontBlock[1]; } vector splitAlwaysBlock = parseBetween(action, "alwaysblockwith(", ")"); if(splitAlwaysBlock.size()) { mCombatAlwaysBlockTip = splitAlwaysBlock[1]; } vector splitSetEffgood = parseBetween(action, "good(", ")"); if(splitSetEffgood.size()) { mCardEffGood = splitSetEffgood[1]; } vector splitSetEffbad = parseBetween(action, "bad(", ")"); if(splitSetEffbad.size()) { mCardEffBad = splitSetEffbad[1]; } vector splitCastOrder = parseBetween(action, "castpriority(", ")"); if(splitCastOrder.size()) { castOrder = split(splitCastOrder[1],','); } if(action.find( "combo ") != string::npos) { string Combo = ""; Combo = action.c_str() + 6; combos.push_back(Combo); } } AIHints::AIHints(AIPlayerBaka * player): mPlayer(player) { } void AIHints::add(string line) { hints.push_back(NEW AIHint(line)); } AIHints::~AIHints() { for (size_t i = 0; i < hints.size(); ++i) SAFE_DELETE(hints[i]); hints.clear(); } AIHint * AIHints::getByCondition (string condition) { if (!condition.size()) return NULL; for (size_t i = 0; i < hints.size(); ++i) { if (hints[i]->mCondition.compare(condition) == 0) return hints[i]; } return NULL; } bool AIHints::HintSaysDontAttack(GameObserver* observer,MTGCardInstance * card) { TargetChooserFactory tfc(observer); TargetChooser * hintTc = NULL; for(unsigned int i = 0; i < hints.size();i++) { if (hints[i]->mCombatAttackTip.size()) { hintTc = tfc.createTargetChooser(hints[i]->mCombatAttackTip,card); if(hintTc && hintTc->canTarget(card,true)) { SAFE_DELETE(hintTc); return true; } SAFE_DELETE(hintTc); } } return false; } bool AIHints::HintSaysAlwaysAttack(GameObserver* observer,MTGCardInstance * card) { TargetChooserFactory tfc(observer); TargetChooser * hintTc = NULL; for(unsigned int i = 0; i < hints.size();i++) { if (hints[i]->mCombatAlwaysAttackTip.size()) { hintTc = tfc.createTargetChooser(hints[i]->mCombatAlwaysAttackTip,card); if(hintTc && hintTc->canTarget(card,true)) { SAFE_DELETE(hintTc); return true; } SAFE_DELETE(hintTc); } } return false; } bool AIHints::HintSaysDontBlock(GameObserver* observer,MTGCardInstance * card) { TargetChooserFactory tfc(observer); TargetChooser * hintTc = NULL; for(unsigned int i = 0; i < hints.size();i++) { if (hints[i]->mCombatBlockTip.size()) { hintTc = tfc.createTargetChooser(hints[i]->mCombatBlockTip,card); if(hintTc && hintTc->canTarget(card,true)) { SAFE_DELETE(hintTc); return true; } SAFE_DELETE(hintTc); } } return false; } bool AIHints::HintSaysAlwaysBlock(GameObserver* observer,MTGCardInstance * card) { TargetChooserFactory tfc(observer); TargetChooser * hintTc = NULL; for(unsigned int i = 0; i < hints.size();i++) { if (hints[i]->mCombatAlwaysBlockTip.size()) { hintTc = tfc.createTargetChooser(hints[i]->mCombatAlwaysBlockTip,card); if(hintTc && hintTc->canTarget(card,true)) { SAFE_DELETE(hintTc); return true; } SAFE_DELETE(hintTc); } } return false; } bool AIHints::HintSaysCardIsGood(GameObserver* observer,MTGCardInstance * card) { TargetChooserFactory tfc(observer); TargetChooser * hintTc = NULL; for(unsigned int i = 0; i < hints.size();i++) { if (hints[i]->mCardEffGood.size()) { hintTc = tfc.createTargetChooser(hints[i]->mCardEffGood,card); if(hintTc && hintTc->canTarget(card,true)) { SAFE_DELETE(hintTc); return true; } SAFE_DELETE(hintTc); } } return false; } bool AIHints::HintSaysCardIsBad(GameObserver* observer,MTGCardInstance * card) { TargetChooserFactory tfc(observer); TargetChooser * hintTc = NULL; for(unsigned int i = 0; i < hints.size();i++) { if (hints[i]->mCardEffBad.size()) { hintTc = tfc.createTargetChooser(hints[i]->mCardEffBad,card); if(hintTc && hintTc->canTarget(card,true)) { SAFE_DELETE(hintTc); return true; } SAFE_DELETE(hintTc); } } return false; } bool AIHints::HintSaysItsForCombo(GameObserver* observer,MTGCardInstance * card) { TargetChooserFactory tfc(observer); TargetChooser * hintTc = NULL; bool forCombo = false; for(unsigned int i = 0; i < hints.size();i++) { if (hints[i]->combos.size()) { //time to find the parts and condiations of the combo. string part = ""; if(!hints[i]->partOfCombo.size() && hints[i]->combos.size()) { for(unsigned int cPart = 0; cPart < hints[i]->combos.size(); cPart++) { //here we disect the different parts of a given combo part = hints[i]->combos[cPart]; hints[i]->partOfCombo = split(part,'^'); for(int dPart = int(hints[i]->partOfCombo.size()-1);dPart >= 0;dPart--) { vectorasTc; asTc = parseBetween(hints[i]->partOfCombo[dPart],"hold(",")"); if(asTc.size()) { hints[i]->hold.push_back(asTc[1]); asTc.clear(); } asTc = parseBetween(hints[i]->partOfCombo[dPart],"until(",")"); if(asTc.size()) { hints[i]->until.push_back(asTc[1]); asTc.clear(); } asTc = parseBetween(hints[i]->partOfCombo[dPart],"restriction{","}"); if(asTc.size()) { hints[i]->restrict.push_back(asTc[1]); asTc.clear(); } asTc = parseBetween(hints[i]->partOfCombo[dPart],"cast(",")"); if(asTc.size()) { hints[i]->casting.push_back(asTc[1]); vectorcht = parseBetween(hints[i]->partOfCombo[dPart],"targeting(",")"); if(cht.size()) hints[i]->cardTargets[asTc[1]] = cht[1]; } asTc = parseBetween(hints[i]->partOfCombo[dPart],"totalmananeeded(",")"); if(asTc.size()) { hints[i]->manaNeeded = asTc[1]; asTc.clear(); } if(dPart == 0) break; } } }//we collect the peices of a combo on first run. for(unsigned int hPart = 0; hPart < hints[i]->hold.size(); hPart++) { hintTc = tfc.createTargetChooser(hints[i]->hold[hPart],card); if(hintTc && hintTc->canTarget(card,true)) { forCombo = true; } SAFE_DELETE(hintTc); } } } return forCombo;//return forCombo that way all hints that are combos are predisected. } //if it's not part of a combo or there is more to gather, then return false bool AIHints::canWeCombo(GameObserver* observer,MTGCardInstance * card,AIPlayerBaka * Ai) { TargetChooserFactory tfc(observer); TargetChooser * hintTc = NULL; bool gotCombo = false; int comboPartsHold = 0; int comboPartsUntil = 0; int comboPartsRestriction = 0; for(unsigned int i = 0; i < hints.size();i++) { comboPartsHold = 0; comboPartsUntil = 0; comboPartsRestriction = 0; if(gotCombo) return gotCombo;//because more then one might be possible at any time. if (hints[i]->hold.size()) { for(unsigned int hPart = 0; hPart < hints[i]->hold.size(); hPart++) { hintTc = tfc.createTargetChooser(hints[i]->hold[hPart],card); int TcCheck = hintTc->countValidTargets(); if(hintTc && TcCheck >= hintTc->maxtargets) { comboPartsHold +=1; } SAFE_DELETE(hintTc); } } if (hints[i]->until.size()) { for(unsigned int hPart = 0; hPart < hints[i]->until.size(); hPart++) { hintTc = tfc.createTargetChooser(hints[i]->until[hPart],card); int TcCheck = hintTc->countValidTargets(); if(hintTc && TcCheck >= hintTc->maxtargets) { comboPartsUntil +=1; } SAFE_DELETE(hintTc); } } if (hints[i]->restrict.size()) { for(unsigned int hPart = 0; hPart < hints[i]->restrict.size(); hPart++) { AbilityFactory af(observer); int checkCond = af.parseCastRestrictions(card,card->controller(),hints[i]->restrict[hPart]); if(checkCond >= 1) { comboPartsRestriction +=1; } } } if( comboPartsUntil >= int(hints[i]->until.size()) && comboPartsHold >= int(hints[i]->hold.size()) && comboPartsRestriction >= int(hints[i]->restrict.size()) && hints[i]->combos.size() ) { ManaCost * needed = ManaCost::parseManaCost(hints[i]->manaNeeded, NULL, card); if(Ai->canPayManaCost(card,needed).size()||!needed->getConvertedCost()) { gotCombo = true; Ai->comboHint = hints[i];//set the combo we are doing. } SAFE_DELETE(needed); } } return gotCombo; } vectorAIHints::mCastOrder() { for(unsigned int i = 0; i < hints.size();i++) { if (hints[i]->castOrder.size()) { return hints[i]->castOrder; } } return vector(); } //return true if a given ability matches a hint's description //Eventually this will look awfully similar to the parser...any way to merge them somehow ? bool AIHints::abilityMatches(MTGAbility * ability, AIHint * hint) { string s = hint->mAction; MTGAbility * a = AbilityFactory::getCoreAbility(ability); //Here we want to check that the card reacting to the MTGAbility is the one mentioned in the hint, // to avoid mistaking the MTGAbility with a similar one. //Ideally we would find all cards with this ID, and see if the ability reacts to a click on one of these cards. // This is the poor man's version, based on the fact that most cards are the source of their own abilities if (hint->mSourceId && ((!a->source) || a->source->getMTGId() != hint->mSourceId)) return false; if ( AACounter * counterAbility = dynamic_cast (a) ) { vector splitCounter = parseBetween(s, "counter(", ")"); if (!splitCounter.size()) return false; string counterstring = counterAbility->name; std::transform(counterstring.begin(), counterstring.end(), counterstring.begin(), ::tolower); return (splitCounter[1].compare(counterstring) == 0); } if ( ATokenCreator * tokenAbility = dynamic_cast (a) ) { vector splitToken = parseBetween(s, "token(", ")"); if (!splitToken.size()) return false; return (tokenAbility->tokenId && tokenAbility->tokenId == atoi(splitToken[1].c_str())); } return false; } //Finds all mtgAbility matching the Hint description // For now we limit findings vector AIHints::findAbilities(AIHint * hint) { std::vector elems; ActionLayer * al = mPlayer->getObserver()->mLayers->actionLayer(); for (size_t i = 1; i < al->mObjects.size(); i++) //0 is not a mtgability...hackish { MTGAbility * a = ((MTGAbility *) al->mObjects[i]); if (abilityMatches(a, hint)) elems.push_back(a); } return elems; } //Finds a mtgAbility matching the Hint description, and returns a valid AIAction matching this mtgability RankingContainer AIHints::findActions(AIHint * hint) { RankingContainer ranking; vector abilities = findAbilities(hint); for (size_t i = 0; i < abilities.size(); ++i) { MTGAbility * a = abilities[i]; for (int j = 0; j < mPlayer->game->inPlay->nb_cards; j++) { MTGCardInstance * card = mPlayer->game->inPlay->cards[j]; if (a->isReactingToClick(card, a->getCost())) { mPlayer->createAbilityTargets(a, card, ranking); //TODO make that function static? break; //For performance... ? } } } return ranking; } //Returns true if a card with the given MTG ID exists bool AIHints::findSource(int sourceId) { for (int i = 0; i < mPlayer->game->inPlay->nb_cards; i++) { MTGCardInstance * c = mPlayer->game->inPlay->cards[i]; if (c->getMTGId() == sourceId) return true; } return false; } string AIHints::constraintsNotFulfilled(AIAction * action, AIHint * hint, ManaCost * potentialMana) { std::stringstream out; if (!action) { if (hint->mCombatAttackTip.size()) { out << "to see if this can attack[" << hint->mCombatAttackTip << "]"; return out.str(); } if (hint->mCombatBlockTip.size()) { out << "to see if this can block[" << hint->mCombatBlockTip << "]"; return out.str(); } if (hint->mSourceId && !findSource(hint->mSourceId)) { out << "needcardinplay[" << hint->mSourceId << "]"; return out.str(); } out << "needability[" << hint->mAction << "]"; return out.str(); } MTGAbility * a = action->ability; if (!a) return "not supported"; MTGCardInstance * card = action->click; if (!card) return "not supported"; //dummy test: would the ability work if we were sure to fulfill its mana requirements? if (!a->isReactingToClick(card, a->getCost())) { DebugTrace("This shouldn't happen, this AIAction doesn't seem like a good choice"); return "not supported"; } if (!a->isReactingToClick(card, potentialMana)) { //Not enough Mana, try to find which mana we should get in priority ManaCost * diff = potentialMana->Diff(a->getCost()); for (int i = 0; i < Constants::NB_Colors; i++) { if(diff->getCost(i) < 0) { out << "needmana[" << Constants::MTGColorChars[i] << "]"; if (Constants::MTGColorChars[i] == 'r') DebugTrace("Got it"); SAFE_DELETE(diff); return out.str(); } } //TODO, handle more cases where the cost cannot be paid return "not supported, can't afford cost for some reason"; } //No problem found, we believe this is a good action to perform return ""; } AIAction * AIHints::findAbilityRecursive(AIHint * hint, ManaCost * potentialMana) { RankingContainer ranking = findActions(hint); AIAction * a = NULL; if (ranking.size()) { a = NEW AIAction(ranking.begin()->first); } string s = constraintsNotFulfilled(a, hint, potentialMana); if (hint->mCombatAttackTip.size() || hint->mCombatBlockTip.size() || hint->castOrder.size()) return NULL; if (s.size()) { SAFE_DELETE(a); AIHint * nextHint = getByCondition(s); DebugTrace("**I Need " << s << ", this can be provided by " << (nextHint ? nextHint->mAction : "NULL") << "\n\n"); if (nextHint && nextHint != hint) return findAbilityRecursive(nextHint, potentialMana); return NULL; } return a; } AIAction * AIHints::suggestAbility(ManaCost * potentialMana) { for (size_t i = 0; i < hints.size(); ++i) { //Don't suggest abilities that require a condition, for now if (hints[i]->mCondition.size()) continue; AIAction * a = findAbilityRecursive(hints[i], potentialMana); if (a) { DebugTrace("**I Decided that the best to fulfill " << hints[i]->mAction << " is to play " << a->ability->getMenuText() << "\n\n"); return a; } } return NULL; }