From 3555ddba3381e4333841c612ec30a2f24b269418 Mon Sep 17 00:00:00 2001 From: Eduardo MG Date: Wed, 23 Apr 2025 18:51:05 -0600 Subject: [PATCH] Update AIPlayerBaka.cpp Changing how AI decides to attack with creatures that have some kind of evasion. Experimenting with how AI decides to block --- projects/mtg/src/AIPlayerBaka.cpp | 536 +++++++++++++++++++----------- 1 file changed, 340 insertions(+), 196 deletions(-) diff --git a/projects/mtg/src/AIPlayerBaka.cpp b/projects/mtg/src/AIPlayerBaka.cpp index 1d49afec4..9757e7c7b 100644 --- a/projects/mtg/src/AIPlayerBaka.cpp +++ b/projects/mtg/src/AIPlayerBaka.cpp @@ -46,9 +46,9 @@ int OrderedAIAction::getEfficiency(AADamager * aad) return 0; } - if(p && target) - if(p == target->controller()) - return 0; + if(p && target) + if(p == target->controller()) + return 0; if (dTarget && aad && (aad->getDamage() == dTarget->toughness)) return 100; @@ -107,7 +107,7 @@ int OrderedAIAction::getEfficiency() { target = a->source; } - + AACastCard * CC = dynamic_cast (a); if (CC) return 99; @@ -126,13 +126,13 @@ int OrderedAIAction::getEfficiency() break; if (!coreAbilityCardTarget->regenerateTokens && currentPhase == MTG_PHASE_COMBATBLOCKERS - && (coreAbilityCardTarget->defenser || coreAbilityCardTarget->blockers.size()) - ) + && (coreAbilityCardTarget->defenser || coreAbilityCardTarget->blockers.size()) + ) { efficiency = 95; } - //TODO If the card is the target of a damage spell - break; + //TODO If the card is the target of a damage spell + break; } case MTGAbility::STANDARD_PREVENT: { @@ -272,7 +272,7 @@ int OrderedAIAction::getEfficiency() } case MTGAbility::STANDARD_PUMP: { - efficiency = 0; + efficiency = 0; if(!coreAbilityCardTarget) break; if(!target && !dynamic_cast (a) && (((MTGCardInstance *)a->source)->hasSubtype(Subtypes::TYPE_AURA) || ((MTGCardInstance *)a->source)->hasSubtype(Subtypes::TYPE_EQUIPMENT))) @@ -296,9 +296,9 @@ int OrderedAIAction::getEfficiency() int suggestion = af.abilityEfficiency(a, p, MODE_ABILITY); //i do not set a starting eff. on this ability, this allows Ai to sometimes randomly do it as it normally does. int currentPhase = g->getCurrentGamePhase(); - if ((currentPhase == MTG_PHASE_COMBATBLOCKERS) || (currentPhase == MTG_PHASE_COMBATATTACKERS)) + if ((currentPhase == MTG_PHASE_COMBATBLOCKERS) || (currentPhase == MTG_PHASE_COMBATATTACKERS)) { - if (suggestion == BAKA_EFFECT_GOOD && target->controller() == p) + if (suggestion == BAKA_EFFECT_GOOD && target->controller() == p) { if(coreAbilityCardTarget->defenser || coreAbilityCardTarget->blockers.size()) { @@ -350,30 +350,30 @@ int OrderedAIAction::getEfficiency() break; } case MTGAbility::MANA_PRODUCER://only way to hit this condition is nested manaabilities, ai skips manaproducers by defualt when finding an ability to use. - { - AManaProducer * manamaker = dynamic_cast(a); - GenericActivatedAbility * GAA = dynamic_cast(ability); - if(GAA) - { - AForeach * forMana = dynamic_cast(GAA->ability); - if (manamaker && forMana) - { - int outPut = forMana->checkActivation(); - if (ability->getCost() && outPut > int(ability->getCost()->getConvertedCost() +1) && currentPhase == MTG_PHASE_FIRSTMAIN && ability->source->controller()->game->hand->nb_cards > 1) - efficiency = 60;//might be a bit random, but better than never using them. - } - } - else - efficiency = 0; - break; - } + { + AManaProducer * manamaker = dynamic_cast(a); + GenericActivatedAbility * GAA = dynamic_cast(ability); + if(GAA) + { + AForeach * forMana = dynamic_cast(GAA->ability); + if (manamaker && forMana) + { + int outPut = forMana->checkActivation(); + if (ability->getCost() && outPut > int(ability->getCost()->getConvertedCost() +1) && currentPhase == MTG_PHASE_FIRSTMAIN && ability->source->controller()->game->hand->nb_cards > 1) + efficiency = 60;//might be a bit random, but better than never using them. + } + } + else + efficiency = 0; + break; + } case MTGAbility::STANDARDABILITYGRANT: { efficiency = 0; if (!target) break; - + //ensuring that Ai grants abilities to creatures during first main, so it can actually use them in combat. //quick note: the eff is multiplied by creatures ranking then divided by the number of cards in hand. //the reason i do this is to encourage more casting and less waste of mana on abilities. @@ -391,8 +391,8 @@ int OrderedAIAction::getEfficiency() } if (!target->has(a->abilitygranted) && g->getCurrentGamePhase() == MTG_PHASE_COMBATBEGIN - && p == target->controller() - ) + && p == target->controller() + ) { efficiency += efficiencyModifier; } @@ -404,8 +404,8 @@ int OrderedAIAction::getEfficiency() } if ((suggestion == BAKA_EFFECT_BAD && p == target->controller()) - || (suggestion == BAKA_EFFECT_GOOD && p != target->controller()) - ) + || (suggestion == BAKA_EFFECT_GOOD && p != target->controller()) + ) { efficiency = 0; //stop giving trample to the players creatures. @@ -547,13 +547,13 @@ int OrderedAIAction::getEfficiency() } } if ((suggestion == BAKA_EFFECT_BAD && p == target->controller()) - || (suggestion == BAKA_EFFECT_GOOD && p != target->controller())) + || (suggestion == BAKA_EFFECT_GOOD && p != target->controller())) { efficiency = 0; } else { - //without a base to start with Wrand % 5 almost always returns 0. + //without a base to start with Wrand % 5 almost always returns 0. efficiency = 10 + (owner->getRandomGenerator()->random() % 20); //Small percentage of chance for unknown abilities } } @@ -650,11 +650,11 @@ int OrderedAIAction::getEfficiency() } if(p->game->battlefield->countByType("token") >= 25) efficiency = 0; - + } else if (GenericRevealAbility * grA = dynamic_cast(a)) { - if(grA->source->getAICustomCode().size() && grA->source->alias != 185709)//Sphinx of Jwar Isle so the ai will ignore it + if(grA->source->getAICustomCode().size()) { //efficiency = 45 + (owner->getRandomGenerator()->random() % 50); AbilityFactory af(g); @@ -684,7 +684,7 @@ int OrderedAIAction::getEfficiency() { AIPlayer * chk = (AIPlayer*)p; if(may->ability && may->ability->getActionTc() && chk->chooseTarget(may->ability->getActionTc(),NULL,NULL,true)) - efficiency = 50 + (owner->getRandomGenerator()->random() % 50); + efficiency = 50 + (owner->getRandomGenerator()->random() % 50); } if (p->game->hand->nb_cards == 0) efficiency = (int) ((float) efficiency * 1.3); //increase chance of using ability if hand is empty @@ -720,18 +720,18 @@ int OrderedAIAction::getEfficiency() { efficiency += 55; } - + if (ability->source) { if(ability->source->hasType(Subtypes::TYPE_PLANESWALKER) || ability->source->hasType(Subtypes::TYPE_BATTLE)) efficiency += 50; else if(ability->source->hasType(Subtypes::TYPE_LAND)) - { // probably a shockland, don't pay life if hand is empty - if (p->life<=2) - // check that's not a manland(like Celestial Colonnade) - if(efficiency < 50) - efficiency = 0; - } + { // probably a shockland, don't pay life if hand is empty + if (p->life<=2) + // check that's not a manland(like Celestial Colonnade) + if(efficiency < 50) + efficiency = 0; + } } SAFE_DELETE(transAbility); @@ -781,7 +781,7 @@ int OrderedAIAction::getRevealedEfficiency(MTGAbility * ability2) { target = a->source; } - + AACastCard * CC = dynamic_cast (a); if (CC) return 99; @@ -800,8 +800,8 @@ int OrderedAIAction::getRevealedEfficiency(MTGAbility * ability2) break; if (!coreAbilityCardTarget->regenerateTokens && currentPhase == MTG_PHASE_COMBATBLOCKERS - && (coreAbilityCardTarget->defenser || coreAbilityCardTarget->blockers.size()) - ) + && (coreAbilityCardTarget->defenser || coreAbilityCardTarget->blockers.size()) + ) { eff2 = 95; } @@ -1010,27 +1010,27 @@ int OrderedAIAction::getRevealedEfficiency(MTGAbility * ability2) break; } case MTGAbility::MANA_PRODUCER://only way to hit this condition is nested manaabilities, ai skips manaproducers by defualt when finding an ability to use. - { - AManaProducer * manamaker = dynamic_cast(a); - GenericActivatedAbility * GAA = dynamic_cast(ability2); - AForeach * forMana = dynamic_cast(GAA->ability); - if (manamaker && forMana) { - int outPut = forMana->checkActivation(); - if (ability2->getCost() && outPut > int(ability2->getCost()->getConvertedCost() +1) && currentPhase == MTG_PHASE_FIRSTMAIN && ability2->source->controller()->game->hand->nb_cards > 1) - eff2 = 60;//might be a bit random, but better than never using them. + AManaProducer * manamaker = dynamic_cast(a); + GenericActivatedAbility * GAA = dynamic_cast(ability2); + AForeach * forMana = dynamic_cast(GAA->ability); + if (manamaker && forMana) + { + int outPut = forMana->checkActivation(); + if (ability2->getCost() && outPut > int(ability2->getCost()->getConvertedCost() +1) && currentPhase == MTG_PHASE_FIRSTMAIN && ability2->source->controller()->game->hand->nb_cards > 1) + eff2 = 60;//might be a bit random, but better than never using them. + } + else + eff2 = 0; + break; } - else - eff2 = 0; - break; - } case MTGAbility::STANDARDABILITYGRANT: { eff2 = 0; if (!target) break; - + //ensuring that Ai grants abilities to creatures during first main, so it can actually use them in combat. //quick note: the eff is multiplied by creatures ranking then divided by the number of cards in hand. //the reason i do this is to encourage more casting and less waste of mana on abilities. @@ -1048,8 +1048,8 @@ int OrderedAIAction::getRevealedEfficiency(MTGAbility * ability2) } if (!target->has(a->abilitygranted) && g->getCurrentGamePhase() == MTG_PHASE_COMBATBEGIN - && p == target->controller() - ) + && p == target->controller() + ) { eff2 += eff2Modifier; } @@ -1061,8 +1061,8 @@ int OrderedAIAction::getRevealedEfficiency(MTGAbility * ability2) } if ((suggestion == BAKA_EFFECT_BAD && p == target->controller()) - || (suggestion == BAKA_EFFECT_GOOD && p != target->controller()) - ) + || (suggestion == BAKA_EFFECT_GOOD && p != target->controller()) + ) { eff2 = 0; //stop giving trample to the players creatures. @@ -1203,13 +1203,13 @@ int OrderedAIAction::getRevealedEfficiency(MTGAbility * ability2) } } if ((suggestion == BAKA_EFFECT_BAD && p == target->controller()) - || (suggestion == BAKA_EFFECT_GOOD && p != target->controller())) + || (suggestion == BAKA_EFFECT_GOOD && p != target->controller())) { eff2 = 0; } else { - //without a base to start with Wrand % 5 almost always returns 0. + //without a base to start with Wrand % 5 almost always returns 0. eff2 = 10 + (owner->getRandomGenerator()->random() % 20); //Small percentage of chance for unknown abilities } } @@ -1290,7 +1290,7 @@ int OrderedAIAction::getRevealedEfficiency(MTGAbility * ability2) } if(p->game->battlefield->countByType("token") >= 25) eff2 = 0; - + } //At this point the "basic" eff2 is computed, we further tweak it depending on general decisions, independent of theAbility type @@ -1299,7 +1299,7 @@ int OrderedAIAction::getRevealedEfficiency(MTGAbility * ability2) { AIPlayer * chk = (AIPlayer*)p; if(may->ability && may->ability->getActionTc() && chk->chooseTarget(may->ability->getActionTc(),NULL,NULL,true)) - eff2 = 50 + (owner->getRandomGenerator()->random() % 50); + eff2 = 50 + (owner->getRandomGenerator()->random() % 50); } if (p->game->hand->nb_cards == 0) eff2 = (int) ((float) eff2 * 1.3); //increase chance of using ability if hand is empty @@ -1351,7 +1351,7 @@ MTGCardInstance * AIPlayerBaka::chooseCard(TargetChooser * tc, MTGCardInstance * MTGPlayerCards * playerZones = source->controller()->game; if (comboHint && comboHint->cardTargets.size()) { - tc = GetComboTc(observer,tc); + tc = GetComboTc(observer,tc); } for(int players = 0; players < 2;++players) { @@ -1376,9 +1376,9 @@ MTGCardInstance * AIPlayerBaka::chooseCard(TargetChooser * tc, MTGCardInstance * bool AIPlayerBaka::payTheManaCost(ManaCost * cost, int anytypeofmana, MTGCardInstance * target,vectorgotPayments) { - DebugTrace("AIPlayerBaka: AI attempting to pay a mana cost." << endl - << "- Target: " << (target ? target->name : "None" ) << endl - << "- Cost: " << (cost ? cost->toString() : "NULL") ); + DebugTrace("AIPlayerBaka: AI attempting to pay a mana cost." << endl + << "- Target: " << (target ? target->name : "None" ) << endl + << "- Cost: " << (cost ? cost->toString() : "NULL") ); if (!cost) { @@ -1450,7 +1450,7 @@ bool AIPlayerBaka::payTheManaCost(ManaCost * cost, int anytypeofmana, MTGCardIns } } if(k == gotPayments.size()-1)//only add it once, and at the end. - paid->add(this->getManaPool());//incase some of our payments were mana already in the pool/. + paid->add(this->getManaPool());//incase some of our payments were mana already in the pool/. if(paid->canAfford(cost, anytypeofmana)) { if((!cost->hasX() && !cost->hasAnotherCost()) || k == gotPayments.size()-1) @@ -1575,7 +1575,7 @@ vector AIPlayerBaka::canPayMana(MTGCardInstance * target, ManaCost if(!cost || (cost && !cost->getConvertedCost()) || !target) return vector(); map usedCards; - + return canPayMana(target, cost, anytypeofmana, usedCards); } @@ -1857,7 +1857,7 @@ vector AIPlayerBaka::canPaySunBurst(ManaCost * cost) } } } - + for(int i = fullColor;i < cost->getConvertedCost();i++) { for (size_t i = 0; i < observer->mLayers->actionLayer()->manaObjects.size(); i++) @@ -1902,7 +1902,7 @@ int AIPlayerBaka::CanHandleCost(ManaCost * cost, MTGCardInstance * card) { ec->costs[i]->setSource(card); if(!ec->costs[i]->tc->countValidTargets()) - return 0; + return 0; if(!chooseCard(ec->costs[i]->tc,card)) return 0; } @@ -1981,7 +1981,7 @@ int AIPlayerBaka::createAbilityTargets(MTGAbility * a, MTGCardInstance * c, Rank MTGCardInstance * cTargeting = dynamic_cast(potentialTargets[0]); if(cTargeting) - check = NEW OrderedAIAction(this, a,c,cTargeting); + check = NEW OrderedAIAction(this, a,c,cTargeting); Player * pTargeting = dynamic_cast(potentialTargets[0]); if(pTargeting) @@ -2084,17 +2084,17 @@ int AIPlayerBaka::selectAbility() } } } - // Try Deck hints first - if (selectHintAbility()) + // Try Deck hints first + if (selectHintAbility()) return 1; - if(observer->mLayers->stackLayer()->lastActionController == this) - { - //this is here for 2 reasons, MTG rules state that priority is passed with each action. - //without this ai is able to chain cast {t}:damage:1 target(creature) from everything it can all at once. - //this not only is illegal but cause ai to waste abilities ei:all damage:1 on a single 1/1 creature. - return 1; - } + if(observer->mLayers->stackLayer()->lastActionController == this) + { + //this is here for 2 reasons, MTG rules state that priority is passed with each action. + //without this ai is able to chain cast {t}:damage:1 target(creature) from everything it can all at once. + //this not only is illegal but cause ai to waste abilities ei:all damage:1 on a single 1/1 creature. + return 1; + } RankingContainer ranking; list::iterator it; @@ -2504,8 +2504,8 @@ int AIPlayerBaka::chooseTarget(TargetChooser * _tc, Player * forceTarget,MTGCard { if(observer->mExtraPayment) { - observer->mExtraPayment->action->CheckUserInput(JGE_BTN_SEC); - observer->mExtraPayment = NULL; + observer->mExtraPayment->action->CheckUserInput(JGE_BTN_SEC); + observer->mExtraPayment = NULL; } //there should never be a case where a extra cost target selection is happening at the same time as this.. //extracost uses "chooseCard()" to determine its targets. @@ -2522,7 +2522,7 @@ int AIPlayerBaka::chooseTarget(TargetChooser * _tc, Player * forceTarget,MTGCard assert(tc); if (comboHint && comboHint->cardTargets.size()) { - tc = GetComboTc(observer,tc); + tc = GetComboTc(observer,tc); } if(!checkOnly && tc->maxtargets > 1) { @@ -2619,7 +2619,7 @@ int AIPlayerBaka::chooseTarget(TargetChooser * _tc, Player * forceTarget,MTGCard } } if(playerTargetedZone > 1) - target = target->opponent(); + target = target->opponent(); playerTargetedZone--; } if (potentialTargets.size()) @@ -2665,10 +2665,10 @@ int AIPlayerBaka::getEfficiency(MTGAbility * ability) check = NEW OrderedAIAction(this, ability, pTarget, ability->source); else check = NEW OrderedAIAction(this, ability, ability->source); - + if (!check) return -1; - + int result = getEfficiency(check); SAFE_DELETE(check); return result; @@ -2757,8 +2757,8 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty payAlternative = NONE; gotPayments = vector(); //canplayfromgraveyard - while ((card = cd.nextmatch(game->graveyard, card))) - { + while ((card = cd.nextmatch(game->graveyard, card))) + { bool hasFlashback = false; if(card->getManaCost()) @@ -2784,11 +2784,11 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty /*// Case were manacost is equal to flashback cost, if they are different the AI hangs if (hasFlashback && (card->getManaCost() != card->getManaCost()->getFlashback())) - continue; + continue; // Case were manacost is equal to retrace cost, if they are different the AI hangs if (hasRetrace && (card->getManaCost() != card->getManaCost()->getRetrace())) - continue;*/ + continue;*/ if (card->hasType(Subtypes::TYPE_LAND)) { @@ -2816,7 +2816,7 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty if (card->hasType(Subtypes::TYPE_BATTLE) && card->types.size() > 0 && game->inPlay->hasTypeSpecificInt(Subtypes::TYPE_BATTLE,card->types[1])) continue; - + if(hints && hints->HintSaysItsForCombo(observer,card)) { if(hints->canWeCombo(observer,card,this)) @@ -2834,7 +2834,7 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty else { nextCardToPlay = NULL; - continue; + continue; } } int currentCost = card->getManaCost()->getConvertedCost(); @@ -2987,7 +2987,7 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty if (card->hasType(Subtypes::TYPE_BATTLE) && card->types.size() > 0 && game->inPlay->hasTypeSpecificInt(Subtypes::TYPE_BATTLE,card->types[1])) continue; - + if(hints && hints->HintSaysItsForCombo(observer,card)) { if(hints->canWeCombo(observer,card,this)) @@ -3005,7 +3005,7 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty else { nextCardToPlay = NULL; - continue; + continue; } } int currentCost = card->getManaCost()->getConvertedCost(); @@ -3013,7 +3013,7 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty gotPayments.clear(); if((!pMana->canAfford(card->getManaCost(),0) || card->getManaCost()->getKicker())) gotPayments = canPayMana(card,card->getManaCost(),card->has(Constants::ANYTYPEOFMANA)); - //for preformence reason we only look for specific mana if the payment couldn't be made with pmana. + //for preformence reason we only look for specific mana if the payment couldn't be made with pmana. if ((currentCost > maxCost || hasX) && (gotPayments.size() || pMana->canAfford(card->getManaCost(),card->has(Constants::ANYTYPEOFMANA)))) { TargetChooserFactory tcf(observer); @@ -3122,10 +3122,10 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty if (game->playRestrictions->canPutIntoZone(card, game->stack) == PlayRestriction::CANT_PLAY) continue; - + if (card->hasType(Subtypes::TYPE_LEGENDARY) && game->inPlay->findByName(card->name)) continue; - + if(hints && hints->HintSaysItsForCombo(observer,card)) { if(hints->canWeCombo(observer,card,this)) @@ -3143,7 +3143,7 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty else { nextCardToPlay = NULL; - continue; + continue; } } int currentCost = card->getManaCost()->getConvertedCost(); @@ -3151,7 +3151,7 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty gotPayments.clear(); if((!pMana->canAfford(card->getManaCost(),0) || card->getManaCost()->getKicker())) gotPayments = canPayMana(card,card->getManaCost(),card->has(Constants::ANYTYPEOFMANA)); - //for preformence reason we only look for specific mana if the payment couldn't be made with pmana. + //for preformence reason we only look for specific mana if the payment couldn't be made with pmana. if ((currentCost > maxCost || hasX) && (gotPayments.size() || pMana->canAfford(card->getManaCost(),card->has(Constants::ANYTYPEOFMANA)))) { TargetChooserFactory tcf(observer); @@ -3275,8 +3275,8 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty //PLaneswalkers are now legendary so this is redundant //if (card->hasType(Subtypes::TYPE_PLANESWALKER) && card->types.size() > 0 && game->inPlay->hasTypeSpecificInt(Subtypes::TYPE_PLANESWALKER,card->types[1])) - //continue; - + //continue; + if(hints && hints->HintSaysItsForCombo(observer,card)) { if(hints->canWeCombo(observer,card,this)) @@ -3294,7 +3294,7 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty else { nextCardToPlay = NULL; - continue; + continue; } } int currentCost = card->getManaCost()->getConvertedCost(); @@ -3487,7 +3487,7 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty if(hints && hints->HintSaysItsForCombo(observer,nextCardToPlay)) { DebugTrace(" AI wants to play a card that belongs to a combo."); - nextCardToPlay = NULL; + nextCardToPlay = NULL; } } @@ -3521,7 +3521,7 @@ MTGCardInstance * AIPlayerBaka::activateCombo() if(comboCards.size()) { nextCardToPlay = comboCards.back(); - + DebugTrace("ai is doing a combo:" << nextCardToPlay->getName()); DebugTrace("ai is doing a combo:" << nextCardToPlay->getName()); DebugTrace("ai is doing a combo:" << nextCardToPlay->getName()); @@ -3574,7 +3574,7 @@ int AIPlayerBaka::computeActions() if(doThis >= 0) { if(object->abilitiesMenu->isMultipleChoice) - observer->mLayers->actionLayer()->ButtonPressedOnMultipleChoice(doThis); + observer->mLayers->actionLayer()->ButtonPressedOnMultipleChoice(doThis); else observer->mLayers->actionLayer()->doReactTo(doThis); } @@ -3985,16 +3985,16 @@ int AIPlayerBaka::getCreaturesInfo(Player * player, int neededInfo, int untapMod int AIPlayerBaka::chooseAttackers() { - int myCreatures = getCreaturesInfo(this, INFO_NBCREATURES, -1, 1); - if (myCreatures < 1) - return 0; + int myCreatures = getCreaturesInfo(this, INFO_NBCREATURES, -1, 1); + if (myCreatures < 1) + return 0; //Attack with all creatures //How much damage can the other player do during his next Attack ? int opponentForce = getCreaturesInfo(opponent(), INFO_CREATURESPOWER); int opponentCreatures = getCreaturesInfo(opponent(), INFO_NBCREATURES); int myForce = getCreaturesInfo(this, INFO_CREATURESPOWER, -1, 1); - if(opponent()->life < 5) - agressivity += 31; + if(opponent()->life < 5) + agressivity += 31; bool attack = ((myCreatures > opponentCreatures) || (myForce > opponentForce) || (myForce > 2 * opponent()->life)); if (agressivity > 80 && !attack && life > opponentForce) @@ -4002,7 +4002,7 @@ int AIPlayerBaka::chooseAttackers() opponentCreatures = getCreaturesInfo(opponent(), INFO_NBCREATURES, -1); opponentForce = getCreaturesInfo(opponent(), INFO_CREATURESPOWER, -1); attack = (myCreatures >= opponentCreatures && myForce > opponentForce) - || (myForce > opponentForce) || (myForce > opponent()->life) || ((life - opponentForce) > 30) ; + || (myForce > opponentForce) || (myForce > opponent()->life) || ((life - opponentForce) > 30) ; } printf("Choose attackers : %i %i %i %i -> %i\n", opponentForce, opponentCreatures, myForce, myCreatures, attack); @@ -4012,46 +4012,98 @@ int AIPlayerBaka::chooseAttackers() MTGCardInstance * card = NULL; while ((card = cd.nextmatch(game->inPlay, card))) { - if ((hints && hints->HintSaysAlwaysAttack(observer, card)) || card->has(Constants::UNBLOCKABLE)) + if (shouldAIForceAttack(card, attack)) { - if (!card->isAttacker()) + if (card->attackCost) { - if (card->attackCost) - { - MTGAbility * a = observer->mLayers->actionLayer()->getAbility(MTGAbility::ATTACK_COST); - doAbility(a,card); - observer->cardClick(card, MTGAbility::ATTACK_COST); - } + MTGAbility* a = observer->mLayers->actionLayer()->getAbility(MTGAbility::ATTACK_COST); + doAbility(a, card); + observer->cardClick(card, MTGAbility::ATTACK_COST); } observer->cardClick(card, MTGAbility::MTG_ATTACK_RULE); } } - if (attack) - { - CardDescriptor cd; - cd.init(); - cd.setType("creature"); - MTGCardInstance * card = NULL; - while ((card = cd.nextmatch(game->inPlay, card))) - { - if(hints && hints->HintSaysDontAttack(observer,card)) - continue; - if (!card->isAttacker()) - { - if (card->attackCost) - { - MTGAbility * a = observer->mLayers->actionLayer()->getAbility(MTGAbility::ATTACK_COST); - doAbility(a, card); - observer->cardClick(card, MTGAbility::ATTACK_COST); - } - observer->cardClick(card, MTGAbility::MTG_ATTACK_RULE); - } - } - } return 1; } +bool AIPlayerBaka::shouldAIForceAttack(MTGCardInstance* card, bool globalAttack) +{ + if (globalAttack) + return true; + + if (!card || card->isAttacker()) + return false; + + if (hints) + { + if (hints->HintSaysDontAttack(observer, card)) + return false; + if (hints->HintSaysAlwaysAttack(observer, card)) + return true; + } + + if (card->has(Constants::UNBLOCKABLE)) + return true; + + // Flags for opponent defenses + bool oppHasShadow = false; + bool oppHasAirDefense = false; + bool oppHasHorsemanship = false; + bool oppHasBlackOrArtifact = false; + bool oppHasMatchingColorOrArtifact = false; + + // Flags for landwalk checks + bool oppHasSwamp = opponent()->game->inPlay->hasType("Swamp"); + bool oppHasIsland = opponent()->game->inPlay->hasType("Island"); + bool oppHasForest = opponent()->game->inPlay->hasType("Forest"); + bool oppHasMountain = opponent()->game->inPlay->hasType("Mountain"); + bool oppHasPlains = opponent()->game->inPlay->hasType("Plains"); + + MTGCardInstance* oppCard = NULL; + CardDescriptor desc; + desc.init(); + desc.setType("creature"); + + while ((oppCard = desc.nextmatch(opponent()->game->inPlay, oppCard))) + { + if (oppCard->isTapped()) + continue; + + if (oppCard->has(Constants::SHADOW)) + oppHasShadow = true; + if (oppCard->has(Constants::FLYING) || oppCard->has(Constants::REACH)) + oppHasAirDefense = true; + if (oppCard->has(Constants::HORSEMANSHIP)) + oppHasHorsemanship = true; + + if (oppCard->hasColor(Constants::MTG_COLOR_BLACK) || oppCard->hasType("Artifact")) + oppHasBlackOrArtifact = true; + + // Intimidate check: artifact or shares color + if (oppCard->hasType("Artifact") || (oppCard->colors & card->colors)) + oppHasMatchingColorOrArtifact = true; + } + + // Decision logic based on evasion + if ((card->has(Constants::SHADOW) && !oppHasShadow) || + (card->has(Constants::FLYING) && !oppHasAirDefense) || + (card->has(Constants::HORSEMANSHIP) && !oppHasHorsemanship) || + (card->has(Constants::FEAR) && !oppHasBlackOrArtifact) || + (card->has(Constants::INTIMIDATE) && !oppHasMatchingColorOrArtifact)) + return true; + + // Landwalk abilities + if ((card->has(Constants::SWAMPWALK) && oppHasSwamp) || + (card->has(Constants::ISLANDWALK) && oppHasIsland) || + (card->has(Constants::FORESTWALK) && oppHasForest) || + (card->has(Constants::MOUNTAINWALK) && oppHasMountain) || + (card->has(Constants::PLAINSWALK) && oppHasPlains)) + return true; + + return false; +} + /* Can I first strike my oponent and get away with murder ? */ int AIPlayerBaka::canFirstStrikeKill(MTGCardInstance * card, MTGCardInstance *ennemy) { @@ -4070,35 +4122,36 @@ int AIPlayerBaka::canFirstStrikeKill(MTGCardInstance * card, MTGCardInstance *en int AIPlayerBaka::chooseBlockers() { - //Should not block during my own turn... if (observer->currentPlayer == this) return 0; - map opponentsToughness; + + map opponentsToughness; int opponentForce = getCreaturesInfo(opponent(), INFO_CREATURESPOWER); - //Initialize the list of opponent's attacking cards toughness CardDescriptor cdAttackers; cdAttackers.init(); cdAttackers.setType("Creature"); - MTGCardInstance * card = NULL; + MTGCardInstance* card = NULL; + + // Gather all attacking creatures and store their toughness while ((card = cdAttackers.nextmatch(opponent()->game->inPlay, card))) { if (card->isAttacker()) opponentsToughness[card] = card->toughness; } - //A Descriptor to find untapped creatures in our game CardDescriptor cd; cd.init(); cd.setType("Creature"); cd.unsecureSetTapped(-1); card = NULL; - // We first try to block the major threats, those that are marked in the Top 3 of our stats + // First pass: auto-block top 3 threats if can be killed while ((card = cd.nextmatch(game->inPlay, card))) { - if(hints && hints->HintSaysDontBlock(observer,card)) + if (hints && hints->HintSaysDontBlock(observer, card)) continue; + observer->cardClick(card, MTGAbility::MTG_BLOCK_RULE); int set = 0; while (!set) @@ -4109,8 +4162,8 @@ int AIPlayerBaka::chooseBlockers() } else { - MTGCardInstance * attacker = card->defenser; - map::iterator it = opponentsToughness.find(attacker); + MTGCardInstance* attacker = card->defenser; + map::iterator it = opponentsToughness.find(attacker); if (it == opponentsToughness.end()) { opponentsToughness[attacker] = attacker->toughness; @@ -4125,7 +4178,7 @@ int AIPlayerBaka::chooseBlockers() { if (card->blockCost) { - MTGAbility * a = observer->mLayers->actionLayer()->getAbility(MTGAbility::BLOCK_COST); + MTGAbility* a = observer->mLayers->actionLayer()->getAbility(MTGAbility::BLOCK_COST); doAbility(a, card); observer->cardClick(card, MTGAbility::BLOCK_COST); } @@ -4135,13 +4188,13 @@ int AIPlayerBaka::chooseBlockers() } } - //If blocking one of the major threats is not enough to kill it, - // We change strategy, first we unassign its blockers that where assigned above + // Second pass: unassign if attacker is not expected to die card = NULL; while ((card = cd.nextmatch(game->inPlay, card))) { - if(hints && hints->HintSaysDontBlock(observer,card)) + if (hints && hints->HintSaysDontBlock(observer, card)) continue; + if (card->defenser && opponentsToughness[card->defenser] > 0) { while (card->defenser) @@ -4151,53 +4204,144 @@ int AIPlayerBaka::chooseBlockers() } } - //Assign the "free" potential blockers to attacking creatures that are not blocked enough + // Third pass: intelligent blocking card = NULL; while ((card = cd.nextmatch(game->inPlay, card))) { - if(hints && hints->HintSaysDontBlock(observer,card)) + if (hints && hints->HintSaysDontBlock(observer, card)) continue; - if (!card->defenser) + if (card->defenser) + continue; + + MTGCardInstance* bestAttacker = NULL; + int bestScore = -1; + + for (map::iterator it = opponentsToughness.begin(); it != opponentsToughness.end(); ++it) { - if (card->blockCost) + MTGCardInstance* attacker = it->first; + if (!attacker) + continue; + + int currentBlockers = (int)attacker->blockers.size(); + int totalAssignedDamage = 0; + + list::iterator itb; + for (itb = attacker->blockers.begin(); itb != attacker->blockers.end(); ++itb) { - MTGAbility * a = observer->mLayers->actionLayer()->getAbility(MTGAbility::BLOCK_COST); - doAbility(a, card); + MTGCardInstance* blocker = *itb; + if (blocker) + totalAssignedDamage += blocker->power; } - observer->cardClick(card, MTGAbility::MTG_BLOCK_RULE); - int set = 0; - while (!set) + + int maxBlockers = 1; + if (attacker->basicAbilities[Constants::MENACE]) maxBlockers = 2; + if (attacker->basicAbilities[Constants::THREEBLOCKERS]) maxBlockers = 3; + + if (totalAssignedDamage >= attacker->toughness || currentBlockers >= maxBlockers) + continue; + + bool canKill = (card->power >= attacker->toughness); + bool survives = (card->toughness > attacker->power); + + if (!canKill && !survives) + continue; + + if (!canKill && survives) { - if (!card->defenser) + int estimatedDamage = opponentForce; + if ((estimatedDamage - attacker->power) < life) + continue; + } + + int score = attacker->power * 2 + attacker->toughness; + if (getStats() && getStats()->isInTop(attacker, 3, false)) + score += 100; + + if (score > bestScore) + { + bestScore = score; + bestAttacker = attacker; + } + } + + if (bestAttacker) + { + int requiredBlockers = 1; + if (bestAttacker->basicAbilities[Constants::MENACE]) requiredBlockers = 2; + if (bestAttacker->basicAbilities[Constants::THREEBLOCKERS]) requiredBlockers = 3; + + int currentBlockers = (int)bestAttacker->blockers.size(); + if (currentBlockers >= requiredBlockers) + continue; + + bool canKill = (card->power >= bestAttacker->toughness); + bool survives = (card->toughness > bestAttacker->power); + bool hasFirstStrike = card->has(Constants::FIRSTSTRIKE) || card->has(Constants::DOUBLESTRIKE); + + // Block alone if this card is strong enough (e.g., first strike + power) + if (hasFirstStrike && canKill) + { + if (card->blockCost) { - set = 1; + MTGAbility* a = observer->mLayers->actionLayer()->getAbility(MTGAbility::BLOCK_COST); + doAbility(a, card); } - else + observer->cardClick(card, MTGAbility::MTG_BLOCK_RULE); + opponentsToughness[bestAttacker] -= card->power; + continue; + } + + vector extraBlockers; + if (requiredBlockers > 1) + { + CardDescriptor cd2; + cd2.init(); + cd2.setType("Creature"); + cd2.unsecureSetTapped(-1); + MTGCardInstance* c2 = NULL; + while ((c2 = cd2.nextmatch(game->inPlay, c2))) { - MTGCardInstance * attacker = card->defenser; - if (opponentsToughness[attacker] <= 0 || (card->toughness <= attacker->power && opponentForce * 2 < life && !canFirstStrikeKill(card, attacker)) || attacker->nbOpponents() > 1) + if (c2 == card || c2->defenser || (hints && hints->HintSaysDontBlock(observer, c2))) + continue; + + int combinedPower = c2->power + card->power; + bool combinedCanKill = (combinedPower >= bestAttacker->toughness); + bool eitherSurvives = (c2->toughness > bestAttacker->power || card->toughness > bestAttacker->power); + + if (combinedCanKill || eitherSurvives) { - if (card->blockCost) - { - MTGAbility * a = observer->mLayers->actionLayer()->getAbility(MTGAbility::BLOCK_COST); - doAbility(a, card); - } - if((!attacker->basicAbilities[Constants::MENACE] && !attacker->basicAbilities[Constants::THREEBLOCKERS]) || - (attacker->basicAbilities[Constants::MENACE] && attacker->blockers.size() > 2) || - (attacker->basicAbilities[Constants::THREEBLOCKERS] && attacker->blockers.size() > 3)) - observer->cardClick(card, MTGAbility::MTG_BLOCK_RULE); - else - set = 1; + extraBlockers.push_back(c2); + if ((int)extraBlockers.size() + currentBlockers + 1 >= requiredBlockers) + break; } - else + } + } + + if (currentBlockers + (int)extraBlockers.size() + 1 >= requiredBlockers) + { + if (card->blockCost) + { + MTGAbility* a = observer->mLayers->actionLayer()->getAbility(MTGAbility::BLOCK_COST); + doAbility(a, card); + } + observer->cardClick(card, MTGAbility::MTG_BLOCK_RULE); + opponentsToughness[bestAttacker] -= card->power; + + for (size_t i = 0; i < extraBlockers.size(); ++i) + { + MTGCardInstance* extra = extraBlockers[i]; + if (extra->blockCost) { - set = 1; + MTGAbility* a = observer->mLayers->actionLayer()->getAbility(MTGAbility::BLOCK_COST); + doAbility(a, extra); } + observer->cardClick(extra, MTGAbility::MTG_BLOCK_RULE); + opponentsToughness[bestAttacker] -= extra->power; } } } } - + return 1; } @@ -4269,7 +4413,7 @@ int AIPlayerBaka::receiveEvent(WEvent * event) AIPlayerBaka::AIPlayerBaka(GameObserver *observer, string file, string fileSmall, string avatarFile, MTGDeck * deck) : - AIPlayer(observer, file, fileSmall, deck) +AIPlayer(observer, file, fileSmall, deck) { nextCardToPlay = NULL; @@ -4312,7 +4456,7 @@ AIPlayerBaka::AIPlayerBaka(GameObserver *observer, string file, string fileSmall if (fileSmall == "ai_baka_eviltwin") mAvatar->SetHFlip(true); - + initTimer(); } @@ -4379,17 +4523,17 @@ int AIPlayerBaka::Act(float dt) else { if (observer->currentActionPlayer == this)//if im not the action player why would i requestnextphase? - observer->userRequestNextGamePhase(); + observer->userRequestNextGamePhase(); } } else { while(clickstream.size()) { - AIAction * action = clickstream.front(); - action->Act(); - SAFE_DELETE(action); - clickstream.pop(); + AIAction * action = clickstream.front(); + action->Act(); + SAFE_DELETE(action); + clickstream.pop(); } } return 1;