CantBeBlockedByAmount + expression used to specify number or creatures that can block given attacker

This commit is contained in:
Maxmtg
2013-06-21 21:42:26 +00:00
parent 06eb8e3e43
commit a75835f664
41 changed files with 230 additions and 217 deletions

View File

@@ -42,7 +42,6 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import forge.CardPredicates.Presets;
import forge.Constant.CardTypes;
import forge.card.CardCharacteristics;
import forge.card.CardDb;
import forge.card.CardRarity;
@@ -2128,106 +2127,10 @@ public class Card extends GameEntity implements Comparable<Card> {
continue;
} else if (keyword.startsWith("CantBeBlockedBy")) {
sbLong.append(this.getName()).append(" can't be blocked ");
boolean negative = true;
List<String> subs = Lists.newArrayList(TextUtil.split(keyword.split(" ", 2)[1], ','));
List<List<String>> subsAnd = Lists.newArrayList();
List<String> orClauses = new ArrayList<String>();
for( int iOr = 0; iOr < subs.size(); iOr++ ) {
String expession = subs.get(iOr);
List<String> parts = Lists.newArrayList(expession.split("[.+]"));
for(int p = 0; p < parts.size(); p++) {
String part = parts.get(p);
if( part.equalsIgnoreCase("creature")) {
parts.remove(p--);
continue;
}
// based on suppossition that each expression has at least 1 predicate except 'creature'
negative &= part.contains("non") || part.contains("without");
}
subsAnd.add(parts);
}
final boolean allNegative = negative;
if( allNegative ) sbLong.append("except ");
sbLong.append("by ");
final Function<Pair<Boolean, String>, String> withToString = new Function<Pair<Boolean, String>, String>() {
@Override
public String apply(Pair<Boolean, String> inp) {
boolean useNon = inp.getKey().booleanValue() == allNegative;
return (useNon ? "*NO* " : "" ) + inp.getRight();
}
};
for( int iOr = 0; iOr < subsAnd.size(); iOr++ ) {
List<String> andOperands = subsAnd.get(iOr);
List<Pair<Boolean, String>> prependedAdjectives = Lists.newArrayList();
List<Pair<Boolean, String>> postponedAdjectives = Lists.newArrayList();
String creatures = null;
for(String part : andOperands) {
boolean positive = true;
if (part.startsWith("non")) {
part = part.substring(3);
positive = false;
}
if(part.startsWith("with")) {
positive = !part.startsWith("without");
postponedAdjectives.add(Pair.of(positive, part.substring(positive ? 4 : 7)));
} else if ( part.startsWith("power") ) {
int kwLength = 5;
String opName = Expressions.operatorName(part.substring(kwLength, kwLength + 2));
String operand = part.substring(kwLength + 2);
postponedAdjectives.add(Pair.of(true, "power" + opName + operand));
} else if ( forge.card.CardType.isACreatureType(part)) {
creatures = StringUtils.capitalize(Lang.getPlural(part)) + ( creatures == null ? "" : " or " + creatures );
} else {
prependedAdjectives.add(Pair.of(positive, part.toLowerCase()));
}
}
StringBuilder sbShort = new StringBuilder();
if ( allNegative ) {
boolean isFirst = true;
for(Pair<Boolean, String> pre : prependedAdjectives) {
if ( isFirst ) isFirst = false;
else sbShort.append(" and/or ");
boolean useNon = pre.getKey().booleanValue() == allNegative;
if( useNon ) sbShort.append("non-");
sbShort.append(pre.getValue()).append(" ").append(creatures == null ? "creatures" : creatures);
}
if (prependedAdjectives.isEmpty())
sbShort.append(creatures == null ? "creatures" : creatures);
if(!postponedAdjectives.isEmpty()) {
if( !prependedAdjectives.isEmpty() ) {
sbShort.append(" and/or creatures");
}
sbShort.append(" with ");
sbShort.append(Lang.joinHomogenous(postponedAdjectives, withToString, allNegative ? "or" : "and"));
}
} else {
for(Pair<Boolean, String> pre : prependedAdjectives) {
boolean useNon = pre.getKey().booleanValue() == allNegative;
if( useNon ) sbShort.append("non-");
sbShort.append(pre.getValue()).append(" ");
}
sbShort.append(creatures == null ? "creatures" : creatures);
if(!postponedAdjectives.isEmpty()) {
sbShort.append(" with ");
sbShort.append(Lang.joinHomogenous(postponedAdjectives, withToString, allNegative ? "or" : "and"));
}
}
orClauses.add(sbShort.toString());
}
sbLong.append(StringUtils.join(orClauses, " or "));
if( keyword.startsWith("CantBeBlockedByAmount"))
sbLong.append(getTextForKwCantBeBlockedByAmount(keyword));
else
sbLong.append(getTextForKwCantBeBlockedByType(keyword));
}
else {
@@ -2252,6 +2155,114 @@ public class Card extends GameEntity implements Comparable<Card> {
return sb.toString();
}
private String getTextForKwCantBeBlockedByAmount(String keyword) {
String restriction = keyword.split(" ", 2)[1];
boolean isLT = "LT".equals(restriction.substring(0,2));
final String byClause = isLT ? "except by " : "by more than";
int cnt = Integer.parseInt(restriction.substring(2));
return byClause + Lang.nounWithNumeral(cnt, isLT ? "or more creature" : "creature");
}
private String getTextForKwCantBeBlockedByType(String keyword) {
boolean negative = true;
List<String> subs = Lists.newArrayList(TextUtil.split(keyword.split(" ", 2)[1], ','));
List<List<String>> subsAnd = Lists.newArrayList();
List<String> orClauses = new ArrayList<String>();
for( int iOr = 0; iOr < subs.size(); iOr++ ) {
String expession = subs.get(iOr);
List<String> parts = Lists.newArrayList(expession.split("[.+]"));
for(int p = 0; p < parts.size(); p++) {
String part = parts.get(p);
if( part.equalsIgnoreCase("creature")) {
parts.remove(p--);
continue;
}
// based on suppossition that each expression has at least 1 predicate except 'creature'
negative &= part.contains("non") || part.contains("without");
}
subsAnd.add(parts);
}
final boolean allNegative = negative;
final String byClause = allNegative ? "except by " : "by ";
final Function<Pair<Boolean, String>, String> withToString = new Function<Pair<Boolean, String>, String>() {
@Override
public String apply(Pair<Boolean, String> inp) {
boolean useNon = inp.getKey().booleanValue() == allNegative;
return (useNon ? "*NO* " : "" ) + inp.getRight();
}
};
for( int iOr = 0; iOr < subsAnd.size(); iOr++ ) {
List<String> andOperands = subsAnd.get(iOr);
List<Pair<Boolean, String>> prependedAdjectives = Lists.newArrayList();
List<Pair<Boolean, String>> postponedAdjectives = Lists.newArrayList();
String creatures = null;
for(String part : andOperands) {
boolean positive = true;
if (part.startsWith("non")) {
part = part.substring(3);
positive = false;
}
if(part.startsWith("with")) {
positive = !part.startsWith("without");
postponedAdjectives.add(Pair.of(positive, part.substring(positive ? 4 : 7)));
} else if ( part.startsWith("power") ) {
int kwLength = 5;
String opName = Expressions.operatorName(part.substring(kwLength, kwLength + 2));
String operand = part.substring(kwLength + 2);
postponedAdjectives.add(Pair.of(true, "power" + opName + operand));
} else if ( forge.card.CardType.isACreatureType(part)) {
creatures = StringUtils.capitalize(Lang.getPlural(part)) + ( creatures == null ? "" : " or " + creatures );
} else {
prependedAdjectives.add(Pair.of(positive, part.toLowerCase()));
}
}
StringBuilder sbShort = new StringBuilder();
if ( allNegative ) {
boolean isFirst = true;
for(Pair<Boolean, String> pre : prependedAdjectives) {
if ( isFirst ) isFirst = false;
else sbShort.append(" and/or ");
boolean useNon = pre.getKey().booleanValue() == allNegative;
if( useNon ) sbShort.append("non-");
sbShort.append(pre.getValue()).append(" ").append(creatures == null ? "creatures" : creatures);
}
if (prependedAdjectives.isEmpty())
sbShort.append(creatures == null ? "creatures" : creatures);
if(!postponedAdjectives.isEmpty()) {
if( !prependedAdjectives.isEmpty() ) {
sbShort.append(" and/or creatures");
}
sbShort.append(" with ");
sbShort.append(Lang.joinHomogenous(postponedAdjectives, withToString, allNegative ? "or" : "and"));
}
} else {
for(Pair<Boolean, String> pre : prependedAdjectives) {
boolean useNon = pre.getKey().booleanValue() == allNegative;
if( useNon ) sbShort.append("non-");
sbShort.append(pre.getValue()).append(" ");
}
sbShort.append(creatures == null ? "creatures" : creatures);
if(!postponedAdjectives.isEmpty()) {
sbShort.append(" with ");
sbShort.append(Lang.joinHomogenous(postponedAdjectives, withToString, allNegative ? "or" : "and"));
}
}
orClauses.add(sbShort.toString());
}
return byClause + StringUtils.join(orClauses, " or ");
}
// get the text of the abilities of a card
/**
* <p>
@@ -5141,7 +5152,7 @@ public class Card extends GameEntity implements Comparable<Card> {
* @return a boolean.
*/
public final boolean hasStartOfUnHiddenKeyword(final String keyword) {
final ArrayList<String> a = this.getUnhiddenKeyword();
final List<String> a = this.getUnhiddenKeyword();
for (int i = 0; i < a.size(); i++) {
if (a.get(i).toString().startsWith(keyword)) {
return true;

View File

@@ -1047,9 +1047,8 @@ public class AttachAi extends SpellAbilityAi {
final boolean evasive = (keyword.equals("Unblockable") || keyword.equals("Fear")
|| keyword.equals("Intimidate") || keyword.equals("Shadow")
|| keyword.equals("Flying") || keyword.equals("Horsemanship")
|| keyword.endsWith("walk") || keyword.equals("CARDNAME can't be blocked except by Walls.")
|| keyword.equals("All creatures able to block CARDNAME do so.")
|| keyword.equals("CARDNAME can't be blocked by more than one creature."));
|| keyword.endsWith("walk") || keyword.startsWith("CantBeBlockedBy")
|| keyword.equals("All creatures able to block CARDNAME do so."));
// give evasive keywords to creatures that can attack and deal damage
if (evasive) {
if (card.getNetCombatDamage() + powerBonus <= 0

View File

@@ -167,8 +167,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
final boolean evasive = (keyword.endsWith("Unblockable") || keyword.endsWith("Fear")
|| keyword.endsWith("Intimidate") || keyword.endsWith("Shadow")
|| keyword.contains("CantBeBlockedBy") || keyword.endsWith("CARDNAME can't be blocked except by Walls.")
|| keyword.endsWith("CARDNAME can't be blocked except by two or more creatures."));
|| keyword.startsWith("CantBeBlockedBy"));
final boolean combatRelevant = (keyword.endsWith("First Strike") || keyword.contains("Bushido"));
// give evasive keywords to creatures that can or do attack
if (evasive) {

View File

@@ -951,9 +951,7 @@ public class AiAttackController {
return true;
}
if (numberOfPossibleBlockers > 1
|| (numberOfPossibleBlockers == 1
&& !attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures."))) {
if (numberOfPossibleBlockers > 1 || (numberOfPossibleBlockers == 1 && CombatUtil.canAttackerBeBlockedWithAmount(attacker, 1))) {
canBeBlocked = true;
}
/*System.out.println(attacker + " canBeKilledByOne: " + canBeKilledByOne + " canKillAll: "

View File

@@ -343,7 +343,7 @@ public class ComputerUtilBlock {
for (final Card attacker : ComputerUtilBlock.getAttackersLeft()) {
if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.")) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) {
continue;
}
@@ -405,7 +405,7 @@ public class ComputerUtilBlock {
* a {@link forge.game.phase.Combat} object.
* @return a {@link forge.game.phase.Combat} object.
*/
static final Predicate<Card> rampagesOrNeedsManyToBlock = Predicates.or(CardPredicates.containsKeyword("Rampage"), CardPredicates.containsKeyword("CARDNAME can't be blocked by more than one creature."));
static final Predicate<Card> rampagesOrNeedsManyToBlock = Predicates.or(CardPredicates.containsKeyword("Rampage"), CardPredicates.containsKeyword("CantBeBlockedByAmount GT"));
private static Combat makeGangBlocks(final Player ai, final Combat combat) {
List<Card> currentAttackers = CardLists.filter(ComputerUtilBlock.getAttackersLeft(), Predicates.not(rampagesOrNeedsManyToBlock));
@@ -537,7 +537,7 @@ public class ComputerUtilBlock {
for (final Card attacker : ComputerUtilBlock.getAttackersLeft()) {
if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.")) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) {
continue;
}
@@ -579,8 +579,7 @@ public class ComputerUtilBlock {
Card attacker = attackers.get(0);
if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.")
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") || attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
attackers.remove(0);
return makeChumpBlocks(ai, combat, attackers);
}
@@ -643,8 +642,7 @@ public class ComputerUtilBlock {
for (final Card attacker : tramplingAttackers) {
if ((attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.")
&& !combat.isBlocked(attacker))
if ((attacker.hasStartOfKeyword("CantBeBlockedByAmount LT") && !combat.isBlocked(attacker))
|| attacker.hasKeyword("You may have CARDNAME assign its combat damage as though it weren't blocked.")) {
continue;
}

View File

@@ -417,11 +417,8 @@ public class ComputerUtilCard {
if (c.hasKeyword("Intimidate")) {
value += power * 6;
}
if (c.hasStartOfKeyword("CARDNAME can't be blocked except by")) {
value += power * 5;
}
if (c.hasStartOfKeyword("CARDNAME can't be blocked by")) {
value += power * 2;
if (c.hasStartOfKeyword("CantBeBlockedBy")) {
value += power * 3;
}
}

View File

@@ -28,6 +28,7 @@ import org.apache.commons.lang3.StringUtils;
import com.esotericsoftware.minlog.Log;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import forge.Card;
import forge.CardLists;
@@ -50,6 +51,8 @@ import forge.game.combat.AttackingBand;
import forge.game.player.Player;
import forge.game.player.PlayerController.ManaPaymentPurpose;
import forge.game.zone.ZoneType;
import forge.util.Expressions;
import forge.util.TextUtil;
/**
@@ -179,8 +182,7 @@ public class CombatUtil {
return true;
}
if (attacker.hasKeyword("CARDNAME can't be blocked by more than one creature.") &&
!combat.getBlockers(attacker).isEmpty()) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount GT") && !combat.getBlockers(attacker).isEmpty()) {
return false;
}
return CombatUtil.canBeBlocked(attacker);
@@ -321,24 +323,14 @@ public class CombatUtil {
return false;
}
if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.")) {
int blocks = 0;
for (final Card blocker : blockers) {
if (CombatUtil.canBlock(attacker, blocker)) {
blocks += 1;
if (blocks > 1) {
return true;
}
}
}
} else {
for (final Card blocker : blockers) {
if (CombatUtil.canBlock(attacker, blocker)) {
return true;
}
int blocks = 0;
for (final Card blocker : blockers) {
if (CombatUtil.canBlock(attacker, blocker)) {
blocks++;
}
}
return false;
return canAttackerBeBlockedWithAmount(attacker, blocks);
}
/**
@@ -356,11 +348,12 @@ public class CombatUtil {
return 0;
}
if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.")) {
if (attacker.hasKeyword("CantBeBlockedByAmount LT2")) {
return 2;
}
return 1;
} else if (attacker.hasKeyword("CantBeBlockedByAmount LT3")) {
return 3;
} else
return 1;
}
// Has the human player chosen all mandatory blocks?
@@ -390,7 +383,7 @@ public class CombatUtil {
for (final Card attacker : attackers) {
if (CombatUtil.canBlock(attacker, blocker, combat)) {
boolean must = true;
if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.")) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) {
final List<Card> possibleBlockers = CardLists.filter(defending.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
possibleBlockers.remove(blocker);
if (!CombatUtil.canBeBlocked(attacker, possibleBlockers)) {
@@ -406,11 +399,9 @@ public class CombatUtil {
}
for (final Card attacker : attackers) {
// don't accept one blocker for attackers with this keyword
if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.")
&& (combat.getBlockers(attacker).size() == 1)) {
// don't accept blocker amount for attackers with keyword defining valid blockers amount
if (!canAttackerBeBlockedWithAmount(attacker, combat.getBlockers(attacker).size()))
return false;
}
}
return true;
@@ -497,7 +488,7 @@ public class CombatUtil {
for (final Card attacker : attackersWithLure) {
if (CombatUtil.canBeBlocked(attacker, combat) && CombatUtil.canBlock(attacker, blocker)) {
boolean canBe = true;
if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.")) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) {
final List<Card> blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay();
blockers.remove(blocker);
if (!CombatUtil.canBeBlocked(attacker, blockers)) {
@@ -515,7 +506,7 @@ public class CombatUtil {
if (CombatUtil.canBeBlocked(attacker, combat) && CombatUtil.canBlock(attacker, blocker)
&& combat.isAttacking(attacker)) {
boolean canBe = true;
if (attacker.hasKeyword("CARDNAME can't be blocked except by two or more creatures.")) {
if (attacker.hasStartOfKeyword("CantBeBlockedByAmount LT")) {
final List<Card> blockers = combat.getDefenderPlayerByAttacker(attacker).getCreaturesInPlay();
blockers.remove(blocker);
if (!CombatUtil.canBeBlocked(attacker, blockers)) {
@@ -645,23 +636,13 @@ public class CombatUtil {
return false;
}
if (attacker.hasKeyword("Creatures with power less than CARDNAME's power can't block it.")
&& (attacker.getNetAttack() > blocker.getNetAttack())) {
return false;
}
if ((blocker.getNetAttack() > attacker.getNetAttack())
&& blocker.hasKeyword("CARDNAME can't be blocked by creatures "
+ "with power greater than CARDNAME's power.")) {
return false;
}
if ((blocker.getNetAttack() >= attacker.getNetDefense())
&& blocker.hasKeyword("CARDNAME can't be blocked by creatures with "
+ "power equal to or greater than CARDNAME's toughness.")) {
if (attacker.hasKeyword("Creatures with power less than CARDNAME's power can't block it.") && attacker.getNetAttack() > blocker.getNetAttack()) {
return false;
}
if (attacker.hasStartOfKeyword("CantBeBlockedBy")) {
final int keywordPosition = attacker.getKeywordPosition("CantBeBlockedBy");
if (attacker.hasStartOfKeyword("CantBeBlockedBy ")) {
final int keywordPosition = attacker.getKeywordPosition("CantBeBlockedBy ");
final String parse = attacker.getKeyword().get(keywordPosition).toString();
final String[] k = parse.split(" ", 2);
final String[] restrictions = k[1].split(",");
@@ -684,29 +665,20 @@ public class CombatUtil {
return false;
}
if (attacker.hasKeyword("Flying")
|| attacker.hasKeyword("CARDNAME can't be blocked except by creatures with flying or reach.")) {
if (!blocker.hasKeyword("Flying") && !blocker.hasKeyword("Reach")) {
return false;
}
if (attacker.hasKeyword("Flying") && !blocker.hasKeyword("Flying") && !blocker.hasKeyword("Reach")) {
return false;
}
if (attacker.hasKeyword("Horsemanship")) {
if (!blocker.hasKeyword("Horsemanship")) {
return false;
}
if (attacker.hasKeyword("Horsemanship") && !blocker.hasKeyword("Horsemanship")) {
return false;
}
if (attacker.hasKeyword("Fear")) {
if (!blocker.isArtifact() && !blocker.isBlack()) {
return false;
}
if (attacker.hasKeyword("Fear") && !blocker.isArtifact() && !blocker.isBlack()) {
return false;
}
if (attacker.hasKeyword("Intimidate")) {
if (!blocker.isArtifact() && !blocker.sharesColorWith(attacker)) {
return false;
}
if (attacker.hasKeyword("Intimidate") && !blocker.isArtifact() && !blocker.sharesColorWith(attacker)) {
return false;
}
return true;
@@ -788,6 +760,22 @@ public class CombatUtil {
}
return true;
}
public static boolean canAttackerBeBlockedWithAmount(Card attacker, int amount) {
List<String> restrictions = Lists.newArrayList();
for ( String kw : attacker.getKeyword() ) {
if ( kw.startsWith("CantBeBlockedByAmount") )
restrictions.add(TextUtil.split(kw, ' ', 2)[1]);
}
for ( String res : restrictions ) {
int operand = Integer.parseInt(res.substring(2));
String operator = res.substring(0,2);
if (Expressions.compare(amount, operator, operand) )
return false;
}
return true;
}
// can a creature attack if untapped and without summoning sickness?
/**

View File

@@ -73,8 +73,12 @@ public class Lang {
else
strCount = String.valueOf(cnt) + " ";
return strCount + countedForm;
}
}
public static <T> String nounWithNumeral(int cnt, String noun) {
String countedForm = cnt <= 1 ? noun : getPlural(noun);
return getNumeral(cnt) + " " + countedForm;
}
/**
* TODO: Write javadoc for this method.
* @param name
@@ -96,4 +100,22 @@ public class Lang {
return false;
}
public final static String[] numbers0 = new String[] {
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eightteen", "nineteen" };
public final static String[] numbers20 = new String[] {"twenty", "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety" };
public static String getNumeral(int n) {
String prefix = n < 0 ? "minus " : "";
n = Math.abs(n);
if ( n >= 0 && n < 20 )
return prefix + numbers0[n];
if ( n < 100 ) {
int n1 = n % 10;
String ones = n1 == 0 ? "" : numbers0[n1];
return prefix + numbers20[(n / 10) - 2] + " " + ones;
}
return Integer.toString(n);
}
}