07-02 MTG Forge
by mtgrares
Moderators: timmermac, Blacksmith, KrazyTheFox, Agetian, friarsol, CCGHQ Admins
07-02 MTG Forge
by 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.
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
by 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
by 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
Listen to my podcast with famed AJ_Impy "Freed from the Real" on http://puremtgo.com
-
GandoTheBard - Tester
- Posts: 1043
- Joined: 06 Sep 2008, 18:43
- Has thanked: 0 time
- Been thanked: 0 time
Re: 07-02 MTG Forge
by Rob Cashwalker » 02 Jul 2009, 20:43
Random DUMB Plays, I'll agree with. Random semi-intelligent plays, will make it interesting.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.
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.
-
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
by 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
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

-
DennisBergkamp - AI Programmer
- Posts: 2602
- Joined: 09 Sep 2008, 15:46
- Has thanked: 0 time
- Been thanked: 0 time
Re: 07-02 MTG Forge
by 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.
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.
Good to see you still put time into "the old version", Rares.

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.
](./images/smilies/eusa_wall.gif)

- Mr.Chaos
- Tester
- Posts: 625
- Joined: 06 Sep 2008, 08:15
- Has thanked: 0 time
- Been thanked: 0 time
7 posts
• Page 1 of 1
Who is online
Users browsing this forum: No registered users and 22 guests