From 81d4b3d84f895b24b9ac3a9d0be3aa51d192bef4 Mon Sep 17 00:00:00 2001 From: Vittorio Alfieri Date: Mon, 22 Feb 2021 19:16:54 +0100 Subject: [PATCH] Improved AI: now it can plays cards using alternative cost too, fixed thisturn restriction in some primitives. --- .../bin/Res/sets/primitives/borderline.txt | 15 ++- projects/mtg/bin/Res/sets/primitives/mtg.txt | 21 ++-- projects/mtg/include/AIPlayerBaka.h | 1 + projects/mtg/src/AIPlayerBaka.cpp | 113 +++++++++++++++--- 4 files changed, 117 insertions(+), 33 deletions(-) diff --git a/projects/mtg/bin/Res/sets/primitives/borderline.txt b/projects/mtg/bin/Res/sets/primitives/borderline.txt index 3f8acf230..94adcb0bf 100644 --- a/projects/mtg/bin/Res/sets/primitives/borderline.txt +++ b/projects/mtg/bin/Res/sets/primitives/borderline.txt @@ -4462,7 +4462,8 @@ toughness=5 [/card] [card] name=Bellowing Elk -auto=aslongas(other creature[fresh]|mybattlefield) trample && indestructible +auto=if thisturn(creature|mybattlefield)~morethan~1 then transforms((,newability[trample],newability[indestructible])) ueot +auto=@movedTo(other creature|myBattlefield):transforms((,newability[trample],newability[indestructible])) ueot text=As long as you had another creature enter the battlefield under your control this turn, Bellowing Elk has trample and indestructible. mana={3}{G} type=Creature @@ -9218,7 +9219,7 @@ type=Land [card] name=Cleaving Reaper abilities=flying,trample -autograveyard=@movedto(*[angel;berserker]|mybattlefield):transforms((,newability[{L:3}:name(Move back to hand) moveto(myhand)])) ueot +autograveyard={L:3}:restriction{thisturn(*[angel;berserker]|mybattlefield)~morethan~0} name(Move back to hand) moveto(myhand) text=Flying, trample -- Pay 3 life: Return Cleaving Reaper from your graveyard to your hand. Activate this ability only if you had an Angel or Berserker enter the battlefield under your control this turn. mana={3}{B}{B} type=Creature @@ -10066,7 +10067,7 @@ type=Instant [card] name=Confounding Conundrum auto=draw:1 controller -auto=@movedTo(land|opponentBattlefield) restriction{type(land[fresh]|opponentBattlefield)~morethan~1}:ability$!name(Move to hand) moveTo(ownerHand) notatarget(land|myBattlefield)!$ opponent +auto=@movedTo(land|opponentBattlefield) restriction{thisturn(land|opponentbattlefield)~morethan~1}:ability$!name(Move to hand) moveTo(ownerHand) notatarget(land|myBattlefield)!$ opponent text=When Confounding Conundrum enters the battlefield, draw a card. -- Whenever a land enters the battlefield under an opponent's control, if that player had another land enter the battlefield under their control this turn, they return a land they control to its owner's hand. mana={1}{U} type=Enchantment @@ -35371,6 +35372,7 @@ toughness=1 [/card] [card] name=Nimble Trapfinder +auto=if thisturn(creature[cleric;rogue;warrior;wizard]|mybattlefield)~morethan~1 then transforms((,newability[unblockable])) ueot auto=@movedTo(other creature[cleric;rogue;warrior;wizard]|myBattlefield):all(this) transforms((,newability[unblockable])) ueot auto=@each my combatbegins:if compare(calculateparty)~equalto~4 then name(creatures get draw card on damage) name(creatures get draw card on damage) all(creature|myBattlefield) transforms((,newability[@combatdamagefoeof(player) from(this):draw:1 controller],newability[@combatdamageof(player) from(this):draw:1 controller])) ueot text=Nimble Trapfinder can't be blocked if you had another Cleric, Rogue, Warrior, or Wizard enter the battlefield under your control this turn. -- At the beginning of combat on your turn, if you have a full party, creatures you control gain "Whenever this creature deals combat damage to a player, draw a card" until end of turn. @@ -43605,7 +43607,8 @@ toughness=2 [/card] [card] name=Saddled Rimestag -auto=aslongas(creature[fresh]|mybattlefield) 2/2 +auto=if thisturn(creature|mybattlefield)~morethan~1 then 2/2 ueot +auto=@movedto(other creature|myBattlefield):2/2 ueot text=Saddled Rimestag gets +2/+2 as long as you had another creature enter the battlefield under your control this turn. mana={1}{G} type=Snow Creature @@ -45115,8 +45118,8 @@ type=Instant [card] name=Searing Blaze target=player -auto=if type(land[fresh]|mybattlefield)~lessthan~1 then damage:1 && damage:1 target(creature|targetedpersonsBattlefield) -auto=if type(land[fresh]|mybattlefield)~morethan~0 then damage:3 && damage:3 target(creature|targetedpersonsBattlefield) +auto=if thisturn(land|mybattlefield)~lessthan~1 then damage:1 && damage:1 target(creature|targetedpersonsBattlefield) +auto=if thisturn(land|mybattlefield)~morethan~0 then damage:3 && damage:3 target(creature|targetedpersonsBattlefield) text=Searing Blaze deals 1 damage to target player and 1 damage to target creature that player controls. -- Landfall — If you had a land enter the battlefield under your control this turn, Searing Blaze deals 3 damage to that player and 3 damage to that creature instead. mana={R}{R} type=Instant diff --git a/projects/mtg/bin/Res/sets/primitives/mtg.txt b/projects/mtg/bin/Res/sets/primitives/mtg.txt index 8e5860117..c85104469 100644 --- a/projects/mtg/bin/Res/sets/primitives/mtg.txt +++ b/projects/mtg/bin/Res/sets/primitives/mtg.txt @@ -142,6 +142,7 @@ toughness=5 name=Abolish target=artifact,enchantment other={D(plains|myhand)} name(Discard a Plains) +otherrestriction=type(plains|myhand)~morethan~0 auto=destroy text=You may discard a Plains card rather than pay Abolish's mana cost. -- Destroy target artifact or enchantment. mana={1}{W}{W} @@ -8609,7 +8610,7 @@ type=Artifact name=Baloth Cage Trap auto=token(Beast,Creature Beast,4/4,green) other={1}{G} -otherrestriction=type(artifact[fresh]|opponentbattlefield)~morethan~0 +otherrestriction=thisturn(artifact|opponentbattlefield)~morethan~0 text=If an opponent had an artifact enter the battlefield under his or her control this turn, you may pay {1}{G} rather than pay Baloth Cage Trap's mana cost. -- Put a 4/4 green Beast creature token onto the battlefield. mana={3}{G}{G} type=Instant @@ -50816,7 +50817,7 @@ toughness=1 name=Groundswell target=creature auto=2/2 -auto=aslongas(land[fresh]|mybattlefield) 2/2 +auto=if thisturn(land|mybattlefield)~morethan~0 then 2/2 text=Target creature gets +2/+2 until end of turn. -- Landfall - If you had a land enter the battlefield under your control this turn, that creature gets +4/+4 until end of turn instead. mana={G} type=Instant @@ -65765,7 +65766,7 @@ target=<2>land auto=destroy auto=damage:4 all(creature) other={3}{R}{R} -otherrestriction=type(land[fresh]|opponentbattlefield)~morethan~1 +otherrestriction=thisturn(land|opponentbattlefield)~morethan~1 text=If an opponent had two or more lands enter the battlefield under his or her control this turn, you may pay {3}{R}{R} rather than pay Lavaball Trap's mana cost. -- Destroy two target lands. Lavaball Trap deals 4 damage to each creature. mana={6}{R}{R} type=Instant @@ -77563,7 +77564,7 @@ text=Whenever you cast an artifact spell, you may pay {1}. If you do, put a 1/1 [card] name=Mysteries of the Deep auto=draw:2 -auto=aslongas(land[fresh]|mybattlefield) draw:1 +auto=if thisturn(land|mybattlefield)~morethan~0 then draw:1 text=Draw two cards. -- Landfall - If you had a land enter the battlefield under your control this turn, draw three cards instead. mana={4}{U} type=Instant @@ -81091,7 +81092,7 @@ type=Legendary Enchantment [card] name=Oath of Chandra auto=damage:3 target(creature|opponentbattlefield) -auto=@each endofturn:if type(planeswalker[fresh]|mybattlefield)~morethan~0 then damage:2 opponent +auto=@each endofturn restriction{thisturn(planeswalker|mybattlefield)~morethan~0}:damage:2 opponent text=When Oath of Chandra enters the battlefield, it deals 3 damage to target creature an opponent controls. -- At the beginning of each end step, if a planeswalker entered the battlefield under your control this turn, Oath of Chandra deals 2 damage to each opponent. mana={1}{R} type=Legendary Enchantment @@ -85238,7 +85239,7 @@ target=creature auto=tap auto=frozen other={U} -otherrestriction=type(creature[green&fresh]|opponentbattlefield)~morethan~0 +otherrestriction=thisturn(creature[green]|mybattlefield)~morethan~0 text=If an opponent had a green creature enter the battlefield under his or her control this turn, you may pay {U} rather than pay Permafrost Trap's mana cost. -- Tap up to two target creatures. Those creatures don't untap during their controller's next untap step. mana={2}{U}{U} type=Instant @@ -94205,7 +94206,7 @@ toughness=2 name=Rest for the Weary target=player auto=life:4 -auto=aslongas(land[fresh]|mybattlefield) life:4 +auto=if thisturn(land|mybattlefield)~morethan~0 then life:4 text=Target player gains 4 life. -- Landfall - If you had a land enter the battlefield under your control this turn, that player gains 8 life instead. mana={1}{W} type=Instant @@ -122089,7 +122090,7 @@ toughness=4 name=Tomb Hex target=creature auto=-2/-2 -auto=aslongas(land[fresh]|mybattlefield) -2/-2 +auto=if thisturn(land|mybattlefield)~morethan~0 then -2/-2 text=Target creature gets -2/-2 until end of turn. -- Landfall - If you had a land enter the battlefield under your control this turn, that creature gets -4/-4 until end of turn instead. mana={2}{B} type=Instant @@ -124579,7 +124580,7 @@ subtype=Plains Island [/card] [card] name=Tunnel Ignus -auto=@movedto(land|opponentbattlefield) restriction{type(land[fresh]|opponentbattlefield)~morethan~1}:damage:3 opponent +auto=@movedto(land|opponentbattlefield) restriction{thisturn(land|opponentbattlefield)~morethan~1}:damage:3 opponent text=Whenever a land enters the battlefield under an opponent's control, if that player had another land enter the battlefield under his or her control this turn, Tunnel Ignus deals 3 damage to that player. mana={1}{R} type=Creature @@ -133164,7 +133165,7 @@ name=Whiplash Trap target=<2>creature auto=moveTo(ownerhand) other={U} -otherrestriction=type(creature[fresh]|battlefield)~morethan~1 +otherrestriction=thisturn(creature|opponentbattlefield)~morethan~1 text=If an opponent had two or more creatures enter the battlefield under his or her control this turn, you may pay {U} rather than pay Whiplash Trap's mana cost. -- Return two target creatures to their owners' hands. mana={3}{U}{U} type=Instant diff --git a/projects/mtg/include/AIPlayerBaka.h b/projects/mtg/include/AIPlayerBaka.h index 3bafc786c..11aa80322 100644 --- a/projects/mtg/include/AIPlayerBaka.h +++ b/projects/mtg/include/AIPlayerBaka.h @@ -93,6 +93,7 @@ class AIPlayerBaka: public AIPlayer{ virtual AIStats * getStats(); + bool payAlternative; MTGCardInstance * nextCardToPlay; MTGCardInstance * activateCombo(); TargetChooser * GetComboTc(GameObserver * observer, TargetChooser * tc = NULL); diff --git a/projects/mtg/src/AIPlayerBaka.cpp b/projects/mtg/src/AIPlayerBaka.cpp index 7bbe0e5fb..290de1ca1 100644 --- a/projects/mtg/src/AIPlayerBaka.cpp +++ b/projects/mtg/src/AIPlayerBaka.cpp @@ -2746,6 +2746,7 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty cd.init(); cd.setType(type); card = NULL; + payAlternative = false; gotPayments = vector(); //canplayfromgraveyard while ((card = cd.nextmatch(game->graveyard, card))) @@ -2828,10 +2829,21 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty int currentCost = card->getManaCost()->getConvertedCost(); int hasX = card->getManaCost()->hasX(); 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. - if ((currentCost > maxCost || hasX) && (gotPayments.size() || pMana->canAfford(card->getManaCost(),card->has(Constants::ANYTYPEOFMANA)))) + ManaCost* manaToPay = card->getManaCost(); + if(hasFlashback && !card->has(Constants::CANPLAYFROMGRAVEYARD) && !card->has(Constants::TEMPFLASHBACK) && !card->getManaCost()->getKicker()){ + manaToPay = card->getManaCost()->getFlashback(); + gotPayments = canPayMana(card,manaToPay,card->has(Constants::ANYTYPEOFMANA)); + } + if(hasRetrace && !card->has(Constants::CANPLAYFROMGRAVEYARD) && !card->has(Constants::TEMPFLASHBACK) && !card->getManaCost()->getKicker()){ + manaToPay = card->getManaCost()->getRetrace(); + gotPayments = canPayMana(card,manaToPay,card->has(Constants::ANYTYPEOFMANA)); + } + else { + 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. + if ((currentCost > maxCost || hasX) && (gotPayments.size() || pMana->canAfford(manaToPay,card->has(Constants::ANYTYPEOFMANA)))) { TargetChooserFactory tcf(observer); TargetChooser * tc = tcf.createTargetChooser(card); @@ -3221,6 +3233,8 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty card = NULL; // fixed bug causing AI never play a card there are one or more cards in exile or other zones... while ((card = cd.nextmatch(game->hand, card))) { + bool localpayAlternative = false; + if (!CanHandleCost(card->getManaCost(),card)) continue; @@ -3272,10 +3286,17 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty int currentCost = card->getManaCost()->getConvertedCost(); int hasX = card->getManaCost()->hasX(); gotPayments.clear(); + ManaCost* manaToPay = card->getManaCost(); 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. - if ((currentCost > maxCost || hasX) && (gotPayments.size() || pMana->canAfford(card->getManaCost(),card->has(Constants::ANYTYPEOFMANA)))) + if(card->getManaCost()->getAlternative() && !gotPayments.size() && !pMana->canAfford(card->getManaCost(),0) && !card->getManaCost()->getKicker()){ // Now AI can cast cards using alternative cost. + localpayAlternative = true; + manaToPay = card->getManaCost()->getAlternative(); + if(!pMana->canAfford(manaToPay,0)) + gotPayments = canPayMana(card,card->getManaCost()->getAlternative(),card->has(Constants::ANYTYPEOFMANA)); + } + //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(manaToPay,card->has(Constants::ANYTYPEOFMANA)))) { TargetChooserFactory tcf(observer); TargetChooser * tc = tcf.createTargetChooser(card); @@ -3353,12 +3374,34 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty } DebugTrace("Should I play from hand" << (card ? card->name : "Nothing" ) << "?" << endl <<"shouldPlayPercentage = "<< shouldPlayPercentage); - if(card->getRestrictions().size()) - { - AbilityFactory af(observer); - int canPlay = af.parseCastRestrictions(card,card->controller(),card->getRestrictions()); - if(!canPlay) - continue; + if(localpayAlternative){ + if(card->getOtherRestrictions().size()) + { + AbilityFactory af(observer); + int canPlay = af.parseCastRestrictions(card,card->controller(),card->getOtherRestrictions()); + if(!canPlay){ + localpayAlternative = false; + canPlay = true; + if(card->getRestrictions().size()) + canPlay = af.parseCastRestrictions(card,card->controller(),card->getRestrictions()); //Check if card can be casted at least with normal cost. + } + if(!canPlay) + continue; + } + } else{ + if(card->getRestrictions().size()) + { + AbilityFactory af(observer); + int canPlay = af.parseCastRestrictions(card,card->controller(),card->getRestrictions()); + if(!canPlay && card->getManaCost()->getAlternative()){ + canPlay = true; + if(card->getOtherRestrictions().size()) + canPlay = af.parseCastRestrictions(card,card->controller(),card->getOtherRestrictions()); //Check if card can be casted at least with other cost. + if(canPlay) localpayAlternative = true; + } + if(!canPlay) + continue; + } } int randomChance = randomGenerator.random(); int chance = randomChance % 100; @@ -3369,6 +3412,7 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty DebugTrace("shouldPlayPercentage was less than 10 this was a lottery roll on RNG"); } nextCardToPlay = card; + payAlternative = localpayAlternative; maxCost = currentCost; if (hasX) maxCost = pMana->getConvertedCost(); @@ -3384,6 +3428,9 @@ MTGCardInstance * AIPlayerBaka::FindCardToPlay(ManaCost * pMana, const char * ty if(!pMana->canAfford(nextCardToPlay->getManaCost()->getRetrace(),0)) gotPayments = canPayMana(nextCardToPlay,nextCardToPlay->getManaCost()->getRetrace(),nextCardToPlay->has(Constants::ANYTYPEOFMANA)); } + } else if(payAlternative){ + if(!pMana->canAfford(nextCardToPlay->getManaCost()->getAlternative(),0)) // Now AI can cast cards using alternative cost. + gotPayments = canPayMana(nextCardToPlay,nextCardToPlay->getManaCost()->getAlternative(),nextCardToPlay->has(Constants::ANYTYPEOFMANA)); } else { if(!pMana->canAfford(nextCardToPlay->getManaCost(),0) || nextCardToPlay->getManaCost()->getKicker()) gotPayments = canPayMana(nextCardToPlay,nextCardToPlay->getManaCost(),nextCardToPlay->has(Constants::ANYTYPEOFMANA)); @@ -3547,11 +3594,36 @@ int AIPlayerBaka::computeActions() { if (ipotential) { - if(payTheManaCost(nextCardToPlay->getManaCost(),nextCardToPlay->has(Constants::ANYTYPEOFMANAABILITY),nextCardToPlay,gotPayments)) - { - AIAction * a = NEW AIAction(this, nextCardToPlay); - clickstream.push(a); - gotPayments.clear(); + if(nextCardToPlay->currentZone == game->graveyard && !nextCardToPlay->has(Constants::CANPLAYFROMGRAVEYARD) && !nextCardToPlay->has(Constants::TEMPFLASHBACK)){ //Now AI can play cards with flashback and retrace costs. + if(nextCardToPlay->getManaCost()->getFlashback()){ + if(payTheManaCost(nextCardToPlay->getManaCost()->getFlashback(),nextCardToPlay->has(Constants::ANYTYPEOFMANA),nextCardToPlay,gotPayments)) + { + AIAction * a = NEW AIAction(this, nextCardToPlay); + clickstream.push(a); + gotPayments.clear(); + } + } else if(nextCardToPlay->getManaCost()->getRetrace()){ + if(payTheManaCost(nextCardToPlay->getManaCost()->getRetrace(),nextCardToPlay->has(Constants::ANYTYPEOFMANA),nextCardToPlay,gotPayments)) + { + AIAction * a = NEW AIAction(this, nextCardToPlay); + clickstream.push(a); + gotPayments.clear(); + } + } + } else if(payAlternative){ // Now AI can cast cards using alternative cost. + if(payTheManaCost(nextCardToPlay->getManaCost()->getAlternative(),nextCardToPlay->has(Constants::ANYTYPEOFMANA),nextCardToPlay,gotPayments)) + { + AIAction * a = NEW AIAction(this, nextCardToPlay); + clickstream.push(a); + gotPayments.clear(); + } + } else { + if(payTheManaCost(nextCardToPlay->getManaCost(),nextCardToPlay->has(Constants::ANYTYPEOFMANA),nextCardToPlay,gotPayments)) + { + AIAction * a = NEW AIAction(this, nextCardToPlay); + clickstream.push(a); + gotPayments.clear(); + } } } #ifndef AI_CHANGE_TESTING @@ -3722,6 +3794,13 @@ int AIPlayerBaka::computeActions() gotPayments.clear(); } } + } else if(payAlternative){ // Now AI can cast cards using alternative cost. + if(payTheManaCost(nextCardToPlay->getManaCost()->getAlternative(),nextCardToPlay->has(Constants::ANYTYPEOFMANA),nextCardToPlay,gotPayments)) + { + AIAction * a = NEW AIAction(this, nextCardToPlay); + clickstream.push(a); + gotPayments.clear(); + } } else { if(payTheManaCost(nextCardToPlay->getManaCost(),nextCardToPlay->has(Constants::ANYTYPEOFMANA),nextCardToPlay,gotPayments)) {