Files
wagic/projects/mtg/src/AIPlayer.cpp
Xawotihs e50fdba648 - Replaced static parts by per-instance parts of of several classes when they were not threadsafe (AIMomirPlayer, SimpleMenu, Trash, AIAction, MTGCardInstance, ATutorialMessage, MTGRules). The direct consequence is that we could consumme more memory. So, tell me if you have problems with low memory devices (PSP), there are some threadsafe optimizations that could be implemented if needed.
- Reworked the testsuite to be able to work multithreaded. This is deactivated by default everywhere except in QT_CONFIG as one testcase still refuses to pass in multithreaded mode. On my 4 cores linux desktop, the 650 tests passes now in 4 seconds (1 fails).
- Replaced usage of CardSelectorSingleton by a card selector per game observer.
- Modified the resource manager to be optionnal and per game observer instance instead of being a singleton. Two reasons here : threading AND Open Gl access. I only updated the crashing parts called from the game observer, so most of the code is still using the single instance. Beware of copy-paste concerning resources ...
- Cleaned up the game observer constructors
- Fixed several problems in action logging code while testing proliferate decks
- Cleaned up Threading implementation based on QThread
2011-11-06 17:31:44 +00:00

360 lines
11 KiB
C++

#include "PrecompiledHeader.h"
#include "AIPlayer.h"
#include "GameStateDuel.h"
#include "DeckManager.h"
#include "CardSelectorSingleton.h"
// Instances for Factory
#include "AIPlayerBaka.h"
#ifdef AI_CHANGE_TESTING
#include "AIPlayerBakaB.h"
#endif
const char * const MTG_LAND_TEXTS[] = { "artifact", "forest", "island", "mountain", "swamp", "plains", "other lands" };
AIAction::AIAction(AIPlayer * owner, MTGCardInstance * c, MTGCardInstance * t)
: owner(owner), ability(NULL), player(NULL), click(c), target(t)
{
// useability tweak - assume that the user is probably going to want to see the full res card,
// so prefetch it. The idea is that we do it here as we want to start the prefetch before it's time to render,
// and waiting for it to actually go into play is too late, as we start drawing the card during the interrupt window.
// This is a good intercept point, as the AI has committed to using this card.
// if we're not in text mode, always get the thumb
if (owner->getObserver()->getCardSelector()->GetDrawMode() != DrawMode::kText)
{
//DebugTrace("Prefetching AI card going into play: " << c->getImageName());
owner->getObserver()->getResourceManager()->RetrieveCard(c, RETRIEVE_THUMB);
// also cache the large image if we're using kNormal mode
if (owner->getObserver()->getCardSelector()->GetDrawMode() == DrawMode::kNormal)
{
owner->getObserver()->getResourceManager()->RetrieveCard(c);
}
}
}
int AIAction::Act()
{
GameObserver * g = owner->getObserver();
if (player && !playerAbilityTarget)
{
g->cardClick(NULL, player);
return 1;
}
if (ability)
{
g->cardClick(click, ability);
if (target && !mAbilityTargets.size())
{
g->cardClick(target);
return 1;
}
else if(playerAbilityTarget && !mAbilityTargets.size())
{
g->cardClick(NULL,(Player*)playerAbilityTarget);
return 1;
}
if(mAbilityTargets.size())
{
return clickMultiAct(mAbilityTargets);
}
}
else if(mAbilityTargets.size())
{
return clickMultiAct(mAbilityTargets);
}
else if (click)
{ //Shouldn't be used, really...
g->cardClick(click, click);
if (target)
g->cardClick(target);
return 1;
}
return 0;
}
int AIAction::clickMultiAct(vector<Targetable*>& actionTargets)
{
GameObserver * g = owner->getObserver();
TargetChooser * tc = g->getCurrentTargetChooser();
if(!tc) return 0;
vector<Targetable*>::iterator ite = actionTargets.begin();
while(ite != actionTargets.end())
{
MTGCardInstance * card = ((MTGCardInstance *) (*ite));
if(card == (MTGCardInstance*)tc->source)//click source first.
{
g->cardClick(card);
ite = actionTargets.erase(ite);
continue;
}
ite++;
}
owner->getRandomGenerator()->random_shuffle(actionTargets.begin(), actionTargets.end());
//shuffle to make it less predictable, otherwise ai will always seem to target from right to left. making it very obvious.
for(int k = 0;k < int(actionTargets.size());k++)
{
int type = actionTargets[k]->typeAsTarget();
switch (type)
{
case TARGET_CARD:
{
if(k < tc->maxtargets)
{
MTGCardInstance * card = ((MTGCardInstance *) actionTargets[k]);
if(k+1 == int(actionTargets.size()))
tc->done = true;
g->cardClick(card);
}
}
break;
}
}
tc->attemptsToFill++;
return 1;
}
AIPlayer::AIPlayer(GameObserver *observer, string file, string fileSmall, MTGDeck * deck) :
Player(observer, file, fileSmall, deck)
{
agressivity = 50;
forceBestAbilityUse = false;
playMode = Player::MODE_AI;
mFastTimerMode = false;
}
AIPlayer::~AIPlayer()
{
while (!clickstream.empty())
{
AIAction * action = clickstream.front();
SAFE_DELETE(action);
clickstream.pop();
}
}
int AIPlayer::Act(float dt)
{
if (observer->currentPlayer == this)
observer->userRequestNextGamePhase();
return 1;
}
int AIPlayer::clickMultiTarget(TargetChooser * tc, vector<Targetable*>& potentialTargets)
{
vector<Targetable*>::iterator ite = potentialTargets.begin();
while(ite != potentialTargets.end())
{
MTGCardInstance * card = ((MTGCardInstance *) (*ite));
Player * pTarget = (Player*)(*ite);
if(card && card == (MTGCardInstance*)tc->source)//if the source is part of the targetting deal with it first. second click is "confirming click".
{
clickstream.push(NEW AIAction(this, card));
DebugTrace("Ai clicked source as a target: " << (card ? card->name : "None" ) << endl );
ite = potentialTargets.erase(ite);
continue;
}
if(pTarget && pTarget->typeAsTarget() == TARGET_PLAYER)
{
clickstream.push(NEW AIAction(this, pTarget));
DebugTrace("Ai clicked Player as a target");
ite = potentialTargets.erase(ite);
continue;
}
ite++;
}
randomGenerator.random_shuffle(potentialTargets.begin(), potentialTargets.end());
if(potentialTargets.size())
clickstream.push(NEW AIAction(this, NULL,tc->source,potentialTargets));
while(clickstream.size())
{
AIAction * action = clickstream.front();
action->Act();
SAFE_DELETE(action);
clickstream.pop();
}
return 1;
}
int AIPlayer::clickSingleTarget(TargetChooser * tc, vector<Targetable*>& potentialTargets, MTGCardInstance * chosenCard)
{
int i = randomGenerator.random() % potentialTargets.size();
int type = potentialTargets[i]->typeAsTarget();
switch (type)
{
case TARGET_CARD:
{
if(!chosenCard)
{
MTGCardInstance * card = ((MTGCardInstance *) potentialTargets[i]);
clickstream.push(NEW AIAction(this, card));
}
break;
}
case TARGET_PLAYER:
{
Player * player = ((Player *) potentialTargets[i]);
clickstream.push(NEW AIAction(this, player));
break;
}
}
return 1;
}
AIPlayer * AIPlayerFactory::createAIPlayer(GameObserver *observer, MTGAllCards * collection, Player * opponent, int deckid)
{
char deckFile[512];
string avatarFilename; // default imagename
char deckFileSmall[512];
if (deckid == GameStateDuel::MENUITEM_EVIL_TWIN)
{ //Evil twin
sprintf(deckFile, "%s", opponent->deckFile.c_str());
DebugTrace("Evil Twin => " << opponent->deckFile);
avatarFilename = "avatar.jpg";
sprintf(deckFileSmall, "%s", "ai_baka_eviltwin");
}
else
{
if (!deckid)
{
//random deck
int nbdecks = 0;
int found = 1;
while (found && nbdecks < options[Options::AIDECKS_UNLOCKED].number)
{
found = 0;
char buffer[512];
sprintf(buffer, "ai/baka/deck%i.txt", nbdecks + 1);
if (FileExists(buffer))
{
found = 1;
nbdecks++;
}
}
if (!nbdecks)
return NULL;
deckid = 1 + WRand() % (nbdecks);
}
sprintf(deckFile, "ai/baka/deck%i.txt", deckid);
DeckMetaData *aiMeta = DeckManager::GetInstance()->getDeckMetaDataByFilename( deckFile, true);
avatarFilename = aiMeta->getAvatarFilename();
sprintf(deckFileSmall, "ai_baka_deck%i", deckid);
}
int deckSetting = EASY;
if ( opponent )
{
bool isOpponentAI = opponent->isAI() == 1;
DeckMetaData *meta = DeckManager::GetInstance()->getDeckMetaDataByFilename( opponent->deckFile, isOpponentAI);
if ( meta && meta->getVictoryPercentage() >= 65)
deckSetting = HARD;
}
// AIPlayerBaka will delete MTGDeck when it's time
AIPlayerBaka * baka = NEW AIPlayerBaka(observer, deckFile, deckFileSmall, avatarFilename, NEW MTGDeck(deckFile, collection,0, deckSetting));
baka->deckId = deckid;
return baka;
}
int AIPlayer::receiveEvent(WEvent * event)
{
return 0;
}
void AIPlayer::Render()
{
}
bool AIPlayer::parseLine(const string& s)
{
size_t limiter = s.find("=");
if (limiter == string::npos) limiter = s.find(":");
string areaS;
if (limiter != string::npos)
{
areaS = s.substr(0, limiter);
if (areaS.compare("rvalues") == 0)
{
randomGenerator.loadRandValues(s.substr(limiter + 1));
return true;
}
}
return Player::parseLine(s);
}
#ifdef AI_CHANGE_TESTING
AIPlayer * AIPlayerFactory::createAIPlayerTest(GameObserver *observer, MTGAllCards * collection, Player * opponent, string _folder)
{
char deckFile[512];
string avatarFilename; // default imagename
char deckFileSmall[512];
string folder = _folder.size() ? _folder : "ai/baka/";
int deckid = 0;
//random deck
int nbdecks = 0;
int found = 1;
while (found && nbdecks < options[Options::AIDECKS_UNLOCKED].number)
{
found = 0;
char buffer[512];
sprintf(buffer, "%sdeck%i.txt", folder.c_str(), nbdecks + 1);
if (FileExists(buffer))
{
found = 1;
nbdecks++;
}
}
if (!nbdecks)
{
if (_folder.size())
return createAIPlayerTest(observer, collection, opponent, "");
return NULL;
}
deckid = 1 + WRand() % (nbdecks);
sprintf(deckFile, "%sdeck%i.txt", folder.c_str(), deckid);
DeckMetaData *aiMeta = DeckManager::GetInstance()->getDeckMetaDataByFilename( deckFile, true);
avatarFilename = aiMeta->getAvatarFilename();
sprintf(deckFileSmall, "ai_baka_deck%i", deckid);
int deckSetting = EASY;
if ( opponent )
{
bool isOpponentAI = opponent->isAI() == 1;
DeckMetaData *meta = DeckManager::GetInstance()->getDeckMetaDataByFilename( opponent->deckFile, isOpponentAI);
if ( meta->getVictoryPercentage() >= 65)
deckSetting = HARD;
}
// AIPlayerBaka will delete MTGDeck when it's time
AIPlayerBaka * baka = opponent ?
NEW AIPlayerBakaB(observer, deckFile, deckFileSmall, avatarFilename, NEW MTGDeck(deckFile, collection,0, deckSetting)) :
NEW AIPlayerBaka(observer, deckFile, deckFileSmall, avatarFilename, NEW MTGDeck(deckFile, collection,0, deckSetting));
baka->deckId = deckid;
baka->setObserver(observer);
return baka;
}
#endif