#HINT:good(icy prison) ai will act as though the effects of this card are now good, choosing to cast it on it's own creatures. #HINT:bad(ancestral recall) ai will now use this card targeting the opponent. good in cases where you are trying to mill the opponent the effects in wagic have gotten so complex that ai simply has no idea what some cards should target. this helps in most of the cases.
587 lines
18 KiB
C++
587 lines
18 KiB
C++
#include "PrecompiledHeader.h"
|
|
|
|
#include "AIHints.h"
|
|
#include "AIPlayerBaka.h"
|
|
#include "utils.h"
|
|
#include "AllAbilities.h"
|
|
|
|
#include <sstream>
|
|
|
|
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<string> splitAction = parseBetween(action, "sourceid(", ")");
|
|
if (splitAction.size())
|
|
{
|
|
mAction = splitAction[0];
|
|
mSourceId = atoi(splitAction[1].c_str());
|
|
}
|
|
else
|
|
{
|
|
mAction = action;
|
|
mSourceId = 0;
|
|
}
|
|
|
|
vector<string> splitDontAttack = parseBetween(action, "dontattackwith(", ")");
|
|
if(splitDontAttack.size())
|
|
{
|
|
mCombatAttackTip = splitDontAttack[1];
|
|
}
|
|
|
|
vector<string> splitAlwaysAttack = parseBetween(action, "alwaysattackwith(", ")");
|
|
if(splitAlwaysAttack.size())
|
|
{
|
|
mCombatAlwaysAttackTip = splitAlwaysAttack[1];
|
|
}
|
|
|
|
vector<string> splitDontBlock = parseBetween(action, "dontblockwith(", ")");
|
|
if(splitDontBlock.size())
|
|
{
|
|
mCombatBlockTip = splitDontBlock[1];
|
|
}
|
|
|
|
vector<string> splitAlwaysBlock = parseBetween(action, "alwaysblockwith(", ")");
|
|
if(splitAlwaysBlock.size())
|
|
{
|
|
mCombatAlwaysBlockTip = splitAlwaysBlock[1];
|
|
}
|
|
|
|
vector<string> splitSetEffgood = parseBetween(action, "good(", ")");
|
|
if(splitSetEffgood.size())
|
|
{
|
|
mCardEffGood = splitSetEffgood[1];
|
|
}
|
|
|
|
vector<string> splitSetEffbad = parseBetween(action, "bad(", ")");
|
|
if(splitSetEffbad.size())
|
|
{
|
|
mCardEffBad = splitSetEffbad[1];
|
|
}
|
|
|
|
vector<string> 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--)
|
|
{
|
|
vector<string>asTc;
|
|
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]);
|
|
vector<string>cht = 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;
|
|
}
|
|
|
|
|
|
vector<string>AIHints::mCastOrder()
|
|
{
|
|
for(unsigned int i = 0; i < hints.size();i++)
|
|
{
|
|
if (hints[i]->castOrder.size())
|
|
{
|
|
return hints[i]->castOrder;
|
|
}
|
|
}
|
|
return vector<string>();
|
|
}
|
|
|
|
//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<AACounter *> (a) )
|
|
{
|
|
vector<string> 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<ATokenCreator *> (a) )
|
|
{
|
|
vector<string> 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<MTGAbility *> AIHints::findAbilities(AIHint * hint)
|
|
{
|
|
std::vector<MTGAbility *> 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<MTGAbility *> 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;
|
|
}
|