* AI: fixed that computer can't target cards on battlefield if it contains tokens;

This commit is contained in:
Oleg Agafonov 2020-01-02 04:46:20 +04:00
parent de07960ee5
commit 50195e8f35
4 changed files with 121 additions and 78 deletions

View file

@ -18,41 +18,40 @@ import mage.view.PermanentView;
import java.awt.*; import java.awt.*;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.RasterFormatException; import java.awt.image.RasterFormatException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.awt.image.BufferedImage;
/** /**
* @author stravant@gmail.com * @author stravant@gmail.com
* * <p>
* Common base class for card renderers for each card frame / card type. * Common base class for card renderers for each card frame / card type.
* * <p>
* Follows the template method pattern to implement a new renderer, implement * Follows the template method pattern to implement a new renderer, implement
* the following methods (they are called in the following order): * the following methods (they are called in the following order):
* * <p>
* * drawBorder() Draws the outermost border of the card, white border or black * * drawBorder() Draws the outermost border of the card, white border or black
* border * border
* * <p>
* * drawBackground() Draws the background texture / color of the card * * drawBackground() Draws the background texture / color of the card
* * <p>
* * drawArt() Draws the card's art * * drawArt() Draws the card's art
* * <p>
* * drawFrame() Draws the card frame (over the art and background) * * drawFrame() Draws the card frame (over the art and background)
* * <p>
* * drawOverlays() Draws summoning sickness and possible other overlays * * drawOverlays() Draws summoning sickness and possible other overlays
* * <p>
* * drawCounters() Draws counters on the card, such as +1/+1 and -1/-1 * * drawCounters() Draws counters on the card, such as +1/+1 and -1/-1
* counters * counters
* * <p>
* Predefined methods that the implementations can use: * Predefined methods that the implementations can use:
* * <p>
* * drawRules(font, bounding box) * * drawRules(font, bounding box)
* * <p>
* * drawNameLine(font, bounding box) * * drawNameLine(font, bounding box)
* * <p>
* * drawTypeLine(font, bounding box) * * drawTypeLine(font, bounding box)
*
*/ */
public abstract class CardRenderer { public abstract class CardRenderer {
@ -74,24 +73,24 @@ public abstract class CardRenderer {
// Common layout metrics between all cards // Common layout metrics between all cards
// Polygons for counters // Polygons for counters
private static final Polygon PLUS_COUNTER_POLY = new Polygon(new int[]{ private static final Polygon PLUS_COUNTER_POLY = new Polygon(new int[]{
0, 5, 10, 10, 5, 0 0, 5, 10, 10, 5, 0
}, new int[]{ }, new int[]{
3, 0, 3, 10, 9, 10 3, 0, 3, 10, 9, 10
}, 6); }, 6);
private static final Polygon MINUS_COUNTER_POLY = new Polygon(new int[]{ private static final Polygon MINUS_COUNTER_POLY = new Polygon(new int[]{
0, 5, 10, 10, 5, 0 0, 5, 10, 10, 5, 0
}, new int[]{ }, new int[]{
0, 1, 0, 7, 10, 7 0, 1, 0, 7, 10, 7
}, 6); }, 6);
private static final Polygon TIME_COUNTER_POLY = new Polygon(new int[]{ private static final Polygon TIME_COUNTER_POLY = new Polygon(new int[]{
0, 10, 8, 10, 0, 2 0, 10, 8, 10, 0, 2
}, new int[]{ }, new int[]{
0, 0, 5, 10, 10, 5 0, 0, 5, 10, 10, 5
}, 6); }, 6);
private static final Polygon OTHER_COUNTER_POLY = new Polygon(new int[]{ private static final Polygon OTHER_COUNTER_POLY = new Polygon(new int[]{
1, 9, 9, 1 1, 9, 9, 1
}, new int[]{ }, new int[]{
1, 1, 9, 9 1, 1, 9, 9
}, 4); }, 4);
// Paint for a card back // Paint for a card back
@ -253,11 +252,11 @@ public abstract class CardRenderer {
int x2 = (int) (0.8 * cardWidth); int x2 = (int) (0.8 * cardWidth);
int y1 = (int) (0.2 * cardHeight); int y1 = (int) (0.2 * cardHeight);
int y2 = (int) (0.8 * cardHeight); int y2 = (int) (0.8 * cardHeight);
int xPoints[] = { int[] xPoints = {
x1, x2, x1, x2 x1, x2, x1, x2
}; };
int yPoints[] = { int[] yPoints = {
y1, y1, y2, y2 y1, y1, y2, y2
}; };
g.setColor(new Color(255, 255, 255, 200)); g.setColor(new Color(255, 255, 255, 200));
g.setStroke(new BasicStroke(7)); g.setStroke(new BasicStroke(7));
@ -267,10 +266,10 @@ public abstract class CardRenderer {
g.drawPolygon(xPoints, yPoints, 4); g.drawPolygon(xPoints, yPoints, 4);
g.setStroke(new BasicStroke(1)); g.setStroke(new BasicStroke(1));
int[] xPoints2 = { int[] xPoints2 = {
x1, x2, cardWidth / 2 x1, x2, cardWidth / 2
}; };
int[] yPoints2 = { int[] yPoints2 = {
y1, y1, cardHeight / 2 y1, y1, cardHeight / 2
}; };
g.setColor(new Color(0, 0, 0, 100)); g.setColor(new Color(0, 0, 0, 100));
g.fillPolygon(xPoints2, yPoints2, 3); g.fillPolygon(xPoints2, yPoints2, 3);
@ -300,8 +299,8 @@ public abstract class CardRenderer {
try { try {
BufferedImage subImg BufferedImage subImg
= artImage.getSubimage( = artImage.getSubimage(
(int) (artRect.getX() * fullCardImgWidth), (int) (artRect.getY() * fullCardImgHeight), (int) (artRect.getX() * fullCardImgWidth), (int) (artRect.getY() * fullCardImgHeight),
(int) artWidth, (int) artHeight); (int) artWidth, (int) artHeight);
g.drawImage(subImg, g.drawImage(subImg,
x, y, x, y,
(int) targetWidth, (int) targetHeight, (int) targetWidth, (int) targetHeight,
@ -438,20 +437,25 @@ public abstract class CardRenderer {
} }
private Color getRarityColor() { private Color getRarityColor() {
switch (cardView.getRarity()) { if (cardView.getRarity() != null) {
case RARE: switch (cardView.getRarity()) {
return new Color(255, 191, 0); case RARE:
case UNCOMMON: return new Color(255, 191, 0);
return new Color(192, 192, 192); case UNCOMMON:
case MYTHIC: return new Color(192, 192, 192);
return new Color(213, 51, 11); case MYTHIC:
case SPECIAL: return new Color(213, 51, 11);
return new Color(204, 0, 255); case SPECIAL:
case BONUS: return new Color(204, 0, 255);
return new Color(129, 228, 228); case BONUS:
case COMMON: return new Color(129, 228, 228);
default: case COMMON:
return Color.black; default:
return Color.black;
}
} else {
// tokens
return Color.black;
} }
} }

View file

@ -70,4 +70,40 @@ public class EnterLeaveBattlefieldExileTargetTest extends CardTestPlayerBase {
assertHandCount(playerB, "Pillarfield Ox", 1); assertHandCount(playerB, "Pillarfield Ox", 1);
assertExileCount(playerB, 0); assertExileCount(playerB, 0);
} }
@Test
public void testAngelOfSerenityTargets() {
// test NPE error while AI targeting battlefield with tokens
// Flying
// When Angel of Serenity enters the battlefield, you may exile up to three other target creatures from the battlefield and/or creature cards from graveyards.
addCard(Zone.HAND, playerA, "Angel of Serenity");
addCard(Zone.BATTLEFIELD, playerA, "Plains", 7);
//
// Create two 2/2 white Knight Ally creature tokens.
addCard(Zone.HAND, playerA, "Allied Reinforcements", 1);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
//
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 2);
addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 2);
addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion", 2);
addCard(Zone.GRAVEYARD, playerB, "Balduvian Bears", 2);
// create tokens
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Allied Reinforcements");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
// angel
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Angel of Serenity");
setChoice(playerA, "Yes");
//addTarget(playerA, "Silvercoat Lion^Balduvian Bears"); // AI must target
//setStrictChooseMode(true); // AI must target
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Knight Ally", 2);
assertPermanentCount(playerA, "Angel of Serenity", 1);
}
} }

View file

@ -1,8 +1,5 @@
package mage.cards; package mage.cards;
import java.util.List;
import java.util.UUID;
import mage.MageObject; import mage.MageObject;
import mage.Mana; import mage.Mana;
import mage.abilities.Abilities; import mage.abilities.Abilities;
@ -17,13 +14,16 @@ import mage.game.Game;
import mage.game.GameState; import mage.game.GameState;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import java.util.List;
import java.util.UUID;
public interface Card extends MageObject { public interface Card extends MageObject {
UUID getOwnerId(); UUID getOwnerId();
String getCardNumber(); String getCardNumber();
Rarity getRarity(); Rarity getRarity(); // null for tokens
void setOwnerId(UUID ownerId); void setOwnerId(UUID ownerId);
@ -77,15 +77,15 @@ public interface Card extends MageObject {
* @param zone * @param zone
* @param sourceId * @param sourceId
* @param game * @param game
* @param flag If zone * @param flag If zone
* <ul> * <ul>
* <li>LIBRARY: <ul><li>true - put on top</li><li>false - put on * <li>LIBRARY: <ul><li>true - put on top</li><li>false - put on
* bottom</li></ul></li> * bottom</li></ul></li>
* <li>BATTLEFIELD: <ul><li>true - tapped</li><li>false - * <li>BATTLEFIELD: <ul><li>true - tapped</li><li>false -
* untapped</li></ul></li> * untapped</li></ul></li>
* <li>GRAVEYARD: <ul><li>true - not from Battlefield</li><li>false - from * <li>GRAVEYARD: <ul><li>true - not from Battlefield</li><li>false - from
* Battlefield</li></ul></li> * Battlefield</li></ul></li>
* </ul> * </ul>
* @return true if card was moved to zone * @return true if card was moved to zone
*/ */
boolean moveToZone(Zone zone, UUID sourceId, Game game, boolean flag); boolean moveToZone(Zone zone, UUID sourceId, Game game, boolean flag);
@ -95,8 +95,8 @@ public interface Card extends MageObject {
/** /**
* Moves the card to an exile zone * Moves the card to an exile zone
* *
* @param exileId set to null for generic exile zone * @param exileId set to null for generic exile zone
* @param name used for exile zone with the specified exileId * @param name used for exile zone with the specified exileId
* @param sourceId * @param sourceId
* @param game * @param game
* @return true if card was moved to zone * @return true if card was moved to zone
@ -122,7 +122,6 @@ public interface Card extends MageObject {
List<Mana> getMana(); List<Mana> getMana();
/** /**
*
* @return true if there exists various art images for this card * @return true if there exists various art images for this card
*/ */
boolean getUsesVariousArt(); boolean getUsesVariousArt();
@ -149,7 +148,6 @@ public interface Card extends MageObject {
Card copy(); Card copy();
/** /**
*
* @return The main card of a split half card or adventure spell card, otherwise the card itself is * @return The main card of a split half card or adventure spell card, otherwise the card itself is
* returned * returned
*/ */
@ -169,7 +167,7 @@ public interface Card extends MageObject {
boolean removeAttachment(UUID permanentId, Game game); boolean removeAttachment(UUID permanentId, Game game);
default boolean isOwnedBy(UUID controllerId){ default boolean isOwnedBy(UUID controllerId) {
return getOwnerId().equals(controllerId); return getOwnerId().equals(controllerId);
} }
} }

View file

@ -186,22 +186,27 @@ public final class RateCard {
// ratings from card rarity // ratings from card rarity
// some cards can have different rarity -- it's will be used from first set // some cards can have different rarity -- it's will be used from first set
int newRating; int newRating;
switch (card.getRarity()) { if (card.getRarity() != null) {
case COMMON: switch (card.getRarity()) {
newRating = DEFAULT_NOT_RATED_CARD_RATING; case COMMON:
break; newRating = DEFAULT_NOT_RATED_CARD_RATING;
case UNCOMMON: break;
newRating = DEFAULT_NOT_RATED_UNCOMMON_RATING; case UNCOMMON:
break; newRating = DEFAULT_NOT_RATED_UNCOMMON_RATING;
case RARE: break;
newRating = DEFAULT_NOT_RATED_RARE_RATING; case RARE:
break; newRating = DEFAULT_NOT_RATED_RARE_RATING;
case MYTHIC: break;
newRating = DEFAULT_NOT_RATED_MYTHIC_RATING; case MYTHIC:
break; newRating = DEFAULT_NOT_RATED_MYTHIC_RATING;
default: break;
newRating = DEFAULT_NOT_RATED_CARD_RATING; default:
break; newRating = DEFAULT_NOT_RATED_CARD_RATING;
break;
}
} else {
// tokens
newRating = DEFAULT_NOT_RATED_CARD_RATING;
} }
int oldRating = baseRatings.getOrDefault(card.getName(), 0); int oldRating = baseRatings.getOrDefault(card.getName(), 0);