It is currently 11 May 2025, 03:30
   
Text Size

07-02 MTG Forge

Moderators: timmermac, Blacksmith, KrazyTheFox, Agetian, friarsol, CCGHQ Admins

07-02 MTG Forge

Postby mtgrares » 02 Jul 2009, 18:17

I tried to improve the computer AI's attacking and blocking code. The computer will now attack with all of its 2/2 creatures even if you have a 1/4, which it wouldn't do before. The computer is still stupid and will attack with one 2/2 creature into your 1/4, but hopefully the computer has many 2/2 creatures. And to mix things up occansionally the computer will not attack with a creature or will block a non-flyer with a flyer.

The computer block 50% of the time and while this seems arbitrary, it will keep you on your toes. (Maybe the 50% should be something like 30%, I don't really know.) It is really hard to write code that works with any type of deck from slow ones to aggro.

The computer still makes some obvious makes but this is at least an improvement. The computer doesn't take into account activated abilities and values Royal Assassin, Soul Warden, and Eager Cadet as generic 1/1s, the computer doesn't take into account the creature's "worth".

Also included is the "debug" version of the exe which shows the Dos console and the various messages that MTG Forge outputs. This is useful if you have an error. When you have an error it shows a long list of method calls but only the first 2 or 3 lines are the most valuable. The class name, like CardFactory, and the line number are the important parts.

(And make sure to keep a copy of all-decks2)
Download

p.s. And for Dennis here is the code. I updated ComputerUtil_Attack2 and ComputerUtil_Block2. I have kept all of your code that checks for Doran and double strike. You seem to really understand MTG Forge, I'm glad that it is understandable. I still feel sorry that someone has to actually read my insane code, it probably needs 100 times more comments. You've done a great job and thanks to everyone else that has helped. I use the editor that you or Rob suggested, the one from sourceforge, but I still managed to crash it. I seem to crash alot of programs, hm...I guess I'm a power user, ha.

Code: Select all
import java.util.*;

public class ComputerUtil_Attack2
{
  //possible attackers and blockers
  private CardList attackers;
  private CardList blockers;
  private int blockerLife;

  private Random random = new Random();
  private final int randomInt = random.nextInt();

  public ComputerUtil_Attack2(Card[] possibleAttackers, Card[] possibleBlockers, int blockerLife)
  {
    this(new CardList(possibleAttackers), new CardList(possibleBlockers), blockerLife);
  }

  public ComputerUtil_Attack2(CardList possibleAttackers, CardList possibleBlockers, int blockerLife)
  {
    attackers = getUntappedCreatures(possibleAttackers, true);
    blockers  = getUntappedCreatures(possibleBlockers , false);
    this.blockerLife = blockerLife;

    final ArrayList<String> valuable = new ArrayList<String>();
    valuable.add("Kamahl, Pit Fighter");
    valuable.add("Elvish Piper");

    attackers = attackers.filter(new CardListFilter()
    {
      public boolean addCard(Card c)
      {
        return (0 < getAttack(c) || c.getName().equals("Guiltfeeder")) && ! valuable.contains(c.getName());
      }
    });
  }//constructor
  public CardList getUntappedCreatures(CardList in, final boolean checkCanAttack)
  {
    CardList list = new CardList(in.toArray());
    list = list.filter(new CardListFilter()
    {
      public boolean addCard(Card c)
      {
        boolean b = c.isCreature() && c.isUntapped();

        if(checkCanAttack)
          return b && CombatUtil.canAttack(c);

        return b;
      }
    });
    return list;
  }//getUntappedCreatures()

  public Combat getAttackers()
  {
    //if this method is called multiple times during a turn,
    //it will always return the same value
    //randomInt is used so that the computer doesn't always
    //do the same thing on turn 3 if he had the same creatures in play
    //I know this is a little confusing
    random.setSeed(AllZone.Phase.getTurn() + randomInt);

    Combat combat = new Combat();
   
    if (combat.getAttackers().length == 0 && (countExaltedCreatures() >= 3 || GameActionUtil.isRafiqInPlay(Constant.Player.Computer)
       || GameActionUtil.getBattleGraceAngels(Constant.Player.Computer) >= 2) && !doAssault())
    {
       int biggest = 0;
       Card att = null;
       for(int i=0; i<attackers.size();i++){
          if (attackers.get(i).getNetAttack() > biggest) {
             biggest = attackers.get(i).getNetAttack();
             att = attackers.get(i);
          }
       }
       if (att!= null)
          combat.addAttacker(att);
    }
    //do assault (all creatures attack) if you would win the game
    //or if the computer has 4 creatures and the player has 1
    else if(doAssault() || (blockers.size() == 1 && 3 < attackers.size()))
    {
      for(int i = 0; i < attackers.size(); i++)
        combat.addAttacker(attackers.get(i));
    }
    else
    {
      Card bigDef;
     @SuppressWarnings("unused") // bigAtt
      Card bigAtt;

      //should the computer randomly not attack with one attacker?
      //this should only happen 20% of the time when the computer
      //has at least 2 creatures
      boolean notAttack = (Math.abs(random.nextInt(100)) <= 20);
      if(notAttack && 2 <= attackers.size())
      {
        attackers.shuffle();
        attackers.remove(0);
      }


      //so the biggest creature will usually attack
      //I think this works, not sure, may have to change it
      //sortNonFlyingFirst has to be done first, because it reverses everything
      CardListUtil.sortNonFlyingFirst(attackers);
      CardListUtil.sortAttackLowFirst(attackers);

      for(int i = 0; i < attackers.size(); i++)
      {
        bigAtt = getBiggestAttack(attackers.get(i));
        bigDef = getBiggestDefense(attackers.get(i));

        //if attacker can destroy biggest blocker or
        //biggest blocker cannot destroy attacker
        if(CombatUtil.canDestroyAttacker(bigDef, attackers.get(i)) ||
           (! CombatUtil.canDestroyAttacker(attackers.get(i), bigAtt)))
          combat.addAttacker(attackers.get(i));
      }
    }//getAttackers()

    return combat;
  }//getAttackers()

  //returns 0/1 Card if no blockers found
  public Card getBiggestAttack(Card attack)
  {
    CardListUtil.sortAttack(blockers);
    for(int i = 0; i < blockers.size(); i++)
      if(CombatUtil.canBlock(attack, blockers.get(i)))
        return blockers.get(i);

    return AllZone.CardFactory.getCard("Birds of Paradise", "");
  }



  //returns 1/1 Card if no blockers found
  public Card getBiggestDefense(Card attack)
  {
    CardListUtil.sortDefense(blockers);
    for(int i = 0; i < blockers.size(); i++)
      if(CombatUtil.canBlock(attack, blockers.get(i)))
        return blockers.get(i);

    return AllZone.CardFactory.getCard("Elvish Piper", "");
  }

  public boolean doAssault()
  {
    CardListUtil.sortAttack(attackers);

    int totalAttack = 0;
    //presumes the Human will block
    for(int i = 0; i < (attackers.size() - blockers.size()); i++)
      totalAttack += getAttack(attackers.get(i));

    return blockerLife <= totalAttack;
  }//doAssault()
 
  public int countExaltedCreatures()
  {
     PlayerZone play = AllZone.getZone(Constant.Zone.Play, Constant.Player.Computer);
     CardList list = new CardList();
     list.addAll(play.getCards());
     list = list.filter(new CardListFilter(){

      public boolean addCard(Card c) {
         return c.getKeyword().contains("Exalted");
      }
       
     });
    
     return list.size();
  } 

  public void assignDamage(CardList list, int damage)
  {
    CardListUtil.sortAttack(list);
    int kill;
    for(int i = 0; i < list.size(); i++)
    {
      kill = list.get(i).getKillDamage();
      if(kill <= damage)
      {
        damage -= kill;
        list.get(i).setAssignedDamage(kill);
      }
    }
  }//assignDamage()
 
  public int getAttack(Card c)
  {
   if (CombatUtil.isDoranInPlay())
     return c.getNetDefense();
    
    return c.getNetAttack();
  }
}
Code: Select all
import java.util.*;

public class ComputerUtil_Block2
{
  private CardList attackers;
  private CardList possibleBlockers;
  private int blockersLife;

  //making this static helped in testing
  //random.nextBoolean() wasn't tru 50% of the time
  private static Random random = new Random();
  private final int randomInt = random.nextInt();

  public ComputerUtil_Block2(Card[] attackers, Card[] possibleBlockers, int blockerLife)
  {
    this(new CardList(attackers), new CardList(possibleBlockers), blockerLife);
  }

  public ComputerUtil_Block2(CardList in_attackers, CardList in_possibleBlockers, int in_blockersLife)
  {
    attackers        = new CardList(in_attackers.toArray());
    possibleBlockers = getUntappedCreatures(new CardList(in_possibleBlockers.toArray()));

    //the Computer will try to keep his life at X, instead of just at 1 life
    //X being from 0 to 5 life
    blockersLife     = in_blockersLife - (random.nextInt() % 6);

    attackers.shuffle();
    CardListUtil.sortAttackLowFirst(possibleBlockers);

    possibleBlockers = removeValuableBlockers(possibleBlockers);
  }
  private CardList getUntappedCreatures(CardList list)
  {
    list = list.filter(new CardListFilter()
    {
      public boolean addCard(Card c)
      {
        return c.isCreature() && c.isUntapped();
      }
    });
    return list;
  }//getUntappedCreatures()

  private CardList removeValuableBlockers(CardList in)
  {
    final String[] noBlock = {"Elvish Piper", "Urborg Syphon-Mage", "Sparksmith", "Royal Assassin", "Marble Titan", "Kamahl, Pit Fighter"};

    CardList out = in.filter(new CardListFilter()
    {
      public boolean addCard(Card c)
      {
        for(int i = 0; i < noBlock.length; i++)
          if(c.getName().equals(noBlock[i]))
            return false;

        return true;
      }
    });//CardListFilter

    return out;
  }

  //checks for flying and stuff like that
  private CardList getPossibleBlockers(Card attacker)
  {
    CardList list = new CardList();
    for(int i = 0; i < possibleBlockers.size(); i++)
      if(CombatUtil.canBlock(attacker, possibleBlockers.get(i)))
        list.add(possibleBlockers.get(i));

    return list;
  }

  //finds a blocker that destroys the attacker, the blocker is not destroyed
  //returns null if no blocker is found
  Card safeSingleBlock(Card attacker)
  {
    CardList c = getPossibleBlockers(attacker);

    for(int i = 0; i < c.size(); i++)
      if(CombatUtil.canDestroyAttacker(attacker, c.get(i)) &&
      (! CombatUtil.canDestroyAttacker(c.get(i), attacker)))
        return c.get(i);

    return null;
  }//safeSingleBlock()

  //finds a blocker, both the attacker and blocker are destroyed
  //returns null if no blocker is found
  Card tradeSingleBlock(Card attacker)
  {
    CardList c = getPossibleBlockers(attacker);

    for(int i = 0; i < c.size(); i++)
      if(CombatUtil.canDestroyAttacker(attacker, c.get(i)))
      {
      //do not block a non-flyer with a flyer
      if((! c.get(i).getKeyword().contains("Flying")) || attacker.getKeyword().contains("Flying"))
        return c.get(i);
      }
    return null;
  }//tradeSingleBlock()



  //finds a blocker, neither attacker and blocker are destroyed
  //returns null if no blocker is found
  Card shieldSingleBlock(Card attacker)
  {
    CardList c = getPossibleBlockers(attacker);

    for(int i = 0; i < c.size(); i++)
      if(! CombatUtil.canDestroyAttacker(c.get(i), attacker))
        return c.get(i);

    return null;
  }//shieldSingleBlock()



  //finds multiple blockers
  //returns an array of size 0 if not multiple blocking
  Card[] multipleBlock(Card attacker)
  {
    CardList c = getPossibleBlockers(attacker);

    int defense = getDefense(attacker) - attacker.getDamage();
    //if attacker cannot be destroyed
    if(defense > CardListUtil.sumAttack(c))
      return new Card[] {};

    CardList block = new CardList();
    c.shuffle();

    while(defense > CardListUtil.sumAttack(block))
      block.add(c.remove(0));

    Card check[] = block.toArray();

    //no single blockers, that should be handled somewhere else
    if(check.length == 1)
      return new Card[] {};

    return check;
  }//multipleBlock()


  //finds a blocker, both the attacker lives and blockers dies
  //returns null if no blocker is found
  Card chumpSingleBlock(Card attacker)
  {
    if(blockersLife <= CardListUtil.sumAttack(attackers))
    {
      CardList c = getPossibleBlockers(attacker);
      CardListUtil.sortAttackLowFirst(c);
      if(! c.isEmpty())
        return c.get(0);
    }
    return null;
  }//tradeSingleBlock()

  //force block if lethal damage somehow slips through checks (happens often with doublestrike)
  Card forceBlock(Card attacker)
  {
     CardList c = getPossibleBlockers(attacker);
     CardListUtil.sortAttackLowFirst(c);
     if(! c.isEmpty())
           return c.get(0);
     return null;
  }
 
  private void testing(String s)
  {
    boolean print = false;
    if(print)
      System.out.println(s);
  }

  public Combat getBlockers()
  {
    //if this method is called multiple times during a turn,
    //it will always return the same value
    //randomInt is used so that the computer doesn't always
    //do the same thing on turn 3 if he had the same creatures in play
    //I know this is a little confusing
    random.setSeed(AllZone.Phase.getTurn() + randomInt);

    Card c;
    Combat combat = new Combat();
    boolean shouldBlock;

    //if this isn't done, the attackers are shown backwards 3,2,1,
    //reverse the order here because below reverses the order again
    attackers.reverse();

    for(int i = 0; i < attackers.size(); i++)
    {   
     boolean doubleStrike = false;
      if(attackers.get(i).hasDoubleStrike() || attackers.get(i).getKeyword().contains("Double Strike"))
         doubleStrike = true;
       
      //the computer block 50% of the time
      shouldBlock = random.nextBoolean();

      testing("shouldBlock - " +shouldBlock);
      c = null;

      //add attacker to combat
      combat.addAttacker(attackers.get(i));

      //safe block - attacker dies, blocker lives
      //if there is only one attacker it might be a trap
      //if holding a Giant Growth and a 2/2 attacking into a 3/3
      if(shouldBlock)
      {
        c = safeSingleBlock(attackers.get(i));
        if(c != null)
          testing("safe");
      }

      if(c == null && shouldBlock && safeSingleBlock(attackers.get(i)) == null)
      {
        //shield block - attacker lives, blocker lives
        c = shieldSingleBlock(attackers.get(i));
        if(c != null)
          testing("shield");
     }

      if(c == null && shouldBlock  && safeSingleBlock(attackers.get(i)) == null)
     {
        //trade block - attacker dies, blocker dies
        c = tradeSingleBlock(attackers.get(i));

        if(c != null)
          testing("trading");
     }


      if(c == null && shouldBlock)
      {
        //chump block - attacker lives, blocker dies
        c = chumpSingleBlock(attackers.get(i));
        if(c != null)
          testing("chumping");
      }

     if(c == null && doubleStrike && AllZone.Computer_Life.getLife() <= (getAttack(attackers.get(i))*2))
      {
         c = forceBlock(attackers.get(i));
         if (c != null)
            testing("forcing");
      }
    
      if(c != null)
      {
        possibleBlockers.remove(c);
        combat.addBlocker(attackers.get(i), c);
      }

      //multiple blockers
      if(c == null && shouldBlock)
      {
        Card[] m = multipleBlock(attackers.get(i));
        for(int inner = 0; inner < m.length; inner++)
        {
          //to prevent a single flyer from blocking a single non-flyer
          //tradeSingleBlock() checks for a flyer blocking a non-flyer also
          if(m.length != 1)
          {
            possibleBlockers.remove(m[inner]);
            combat.addBlocker(attackers.get(i), m[inner]);
          }
        }//for
      }//if
    }//for attackers

    return combat;
  }//getBlockers()
 
  public int getDefense(Card c)
  {
   if (CombatUtil.isDoranInPlay())
     return c.getNetAttack();
   
    return c.getNetDefense();
  }
  public int getAttack(Card c)
  {
    if(CombatUtil.isDoranInPlay())
     return c.getNetDefense();
    
   return c.getNetAttack();
  }
}
mtgrares
DEVELOPER
 
Posts: 1352
Joined: 08 Sep 2008, 22:10
Has thanked: 3 times
Been thanked: 12 times

Re: 07-02 MTG Forge

Postby mtgrares » 02 Jul 2009, 18:18

I forgot to say it but this version is based on the 06-18 version.
mtgrares
DEVELOPER
 
Posts: 1352
Joined: 08 Sep 2008, 22:10
Has thanked: 3 times
Been thanked: 12 times

Re: 07-02 MTG Forge

Postby GandoTheBard » 02 Jul 2009, 20:12

In many strategy games Random AI works just fine as it makes up for bad play with unexpected or deep brilliance. That isn't the case here. I'd try and avoid any instances of random dumb plays as much as possible. That's my 2.5 cents on this subject.
visit my personal homepage here: http://outofthebrokensky.com

Listen to my podcast with famed AJ_Impy "Freed from the Real" on http://puremtgo.com
User avatar
GandoTheBard
Tester
 
Posts: 1043
Joined: 06 Sep 2008, 18:43
Has thanked: 0 time
Been thanked: 0 time

Re: 07-02 MTG Forge

Postby Rob Cashwalker » 02 Jul 2009, 20:43

GandoTheBard wrote:In many strategy games Random AI works just fine as it makes up for bad play with unexpected or deep brilliance. That isn't the case here. I'd try and avoid any instances of random dumb plays as much as possible. That's my 2.5 cents on this subject.
Random DUMB Plays, I'll agree with. Random semi-intelligent plays, will make it interesting.

For example, I just programmed a method for CardFactoryUtil, AI_getBestLand(). There were getBestArtifact, getBestEnchantment, getBestCreature and getMostExpensivePermanent. But nothing good for lands to use in my generic "destroy target ___" keyword. So I wrote one.

It starts off by filtering the land list for non basic lands. If non-zero, then choose one at random. Theory being that any given non-basic we have available is no more threatening to the computer than any other. (we can always add better ranking in the future)
Then if there are no non-basics, then it determines the least represented basic type (greater than zero).
Of that type, it looks for one that's not tapped. If all tapped, then choose one at random.
The Force will be with you, Always.
User avatar
Rob Cashwalker
Programmer
 
Posts: 2167
Joined: 09 Sep 2008, 15:09
Location: New York
Has thanked: 5 times
Been thanked: 40 times

Re: 07-02 MTG Forge

Postby DennisBergkamp » 02 Jul 2009, 20:49

I just recently added the mostExpensivePermanent one, I can't remember for which card.. maybe Tradewind Rider?
The land one looks also very useful :)

Anyway, this is some cool stuff rares, I've seen a lot of plays where the computer won't attack with his 2/2s, 3/3s even if all I have is a 0/6 wall in play. Hopefully this will fix those situations :)
User avatar
DennisBergkamp
AI Programmer
 
Posts: 2602
Joined: 09 Sep 2008, 15:46
Has thanked: 0 time
Been thanked: 0 time

Re: 07-02 MTG Forge

Postby Mr.Chaos » 03 Jul 2009, 05:53

Grabbed it. Lets see how the new combat AI works.
Good to see you still put time into "the old version", Rares. :wink:

Sidestepping for a moment:
This game must be good. I mean, it has been what, more than 2 years ago that I installed the first beta of this game and started emailing Rares about his creation. (no forum to go to back then, them were the early days of creation. My oldest surviving email to him is from june 2007, but I think I played a beta vesion even before that.) Since then, many beta versions have seen the light, the game got it's own forum, a bunch of new coders and I still play a few rounds of MTGForge every day.
Plenty of other games I have didn't keep my interest that long.
So this game has serious staying power!
Thank you all, who contributed to this game but most of all to Rares, for starting it in the first place.
](*,) = coder at work, according to a coder.It does explain some of the bugs. :wink:
Mr.Chaos
Tester
 
Posts: 625
Joined: 06 Sep 2008, 08:15
Has thanked: 0 time
Been thanked: 0 time

Re: 07-02 MTG Forge

Postby mtgrares » 08 Jul 2009, 20:04

Thanks.
mtgrares
DEVELOPER
 
Posts: 1352
Joined: 08 Sep 2008, 22:10
Has thanked: 3 times
Been thanked: 12 times


Return to Forge

Who is online

Users browsing this forum: No registered users and 22 guests


Who is online

In total there are 22 users online :: 0 registered, 0 hidden and 22 guests (based on users active over the past 10 minutes)
Most users ever online was 4143 on 23 Jan 2024, 08:21

Users browsing this forum: No registered users and 22 guests

Login Form