- Fixed a Bug where AI would not block any attacker in Demo mode (bug introduced in r2759)
- Fixed a Bug where AI would not correctly assign blockers if the first attacker is super strong. - Added a hack to prevent AI from an infinite loop while choosing a target. There are edge cases where the AI gets to choose the targets for a TargetChooser that doesn't belong to it. I couldn't dig too long for the root cause, so I added a "return 0" when the case happens. Should probably open a ticket - Added a "Hint" System in AI decks, to help the AI with its strategy. This is not really usable yet, it only works with abilities (not cards to play), and I only added some basic code for counters and tokens. This can probably be extended, but let's wait until we see it working on that other game I'm working on, before rushing into adding hints to all AI decks... - minor cleanup of AI Code
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
#include "PrecompiledHeader.h"
|
||||
|
||||
#include "AIHints.h"
|
||||
#include "AIPlayer.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);
|
||||
vector<string> parameters = split(line,':');
|
||||
mCondition = (parameters.size() == 1)? "" : parameters[0];
|
||||
string action = parameters[parameters.size() - 1];
|
||||
|
||||
vector<string> splitAction = parseBetween(action, "sourceid(", ")");
|
||||
if (splitAction.size())
|
||||
{
|
||||
mAction = splitAction[0];
|
||||
mSourceId = atoi(splitAction[1].c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
mAction = action;
|
||||
mSourceId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
AIHints::AIHints(AIPlayer * 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;
|
||||
}
|
||||
|
||||
//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 = GameObserver::GetInstance()->mLayers->actionLayer();
|
||||
|
||||
for (int i = 1; i < al->mCount; 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->cost))
|
||||
{
|
||||
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->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->cost))
|
||||
{
|
||||
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->cost);
|
||||
for (int i = 0; i < Constants::MTG_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 (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;
|
||||
}
|
||||
Reference in New Issue
Block a user