Files
wagic/projects/mtg/src/AIHints.cpp
2013-12-18 05:50:30 +01:00

585 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 = 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;
for(unsigned int i = 0; i < hints.size();i++)
{
int comboPartsHold = 0;
int comboPartsUntil = 0;
int comboPartsRestriction = 0;
if(gotCombo)
return gotCombo;//because more than 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;
}