Skip to content

Commit

Permalink
Add CWars2 with three cards - with discard option
Browse files Browse the repository at this point in the history
  • Loading branch information
Zomis committed Jun 20, 2018
1 parent 0add6dd commit 5edbec0
Show file tree
Hide file tree
Showing 7 changed files with 466 additions and 156 deletions.
7 changes: 6 additions & 1 deletion cardshifter-core/src/main/java/com/cardshifter/ai/AIs.java
Expand Up @@ -25,11 +25,14 @@ public class AIs {
public static ScoreConfigFactory<Entity, ECSAction> loser() {
ScoreConfigFactory<Entity, ECSAction> config = new ScoreConfigFactory<>();
config.withScorer(new PredicateScorer<>(action -> action.getName().equals(CyborgChroniclesGame.END_TURN_ACTION)));
config.withScorer(new SimpleScorer<>(AttackAnalyze::discardScore));
return config;
}

public static ScoreConfigFactory<Entity, ECSAction> idiot() {
return new ScoreConfigFactory<>();
ScoreConfigFactory<Entity, ECSAction> config = new ScoreConfigFactory<>();
config.withScorer(new SimpleScorer<>(AttackAnalyze::discardScore));
return config;
}

public static ScoreConfigFactory<Entity, ECSAction> medium() {
Expand All @@ -39,6 +42,7 @@ public static ScoreConfigFactory<Entity, ECSAction> medium() {
config.withScorer(new PredicateScorer<>(action -> action.getName().equals(CyborgChroniclesGame.ENCHANT_ACTION)), -10); // this AI does not enchant
config.withScorer(new SimpleScorer<Entity, ECSAction>(AttackAnalyze::scrapScore));
config.withScorer(new SimpleScorer<Entity, ECSAction>(AttackAnalyze::attackScore));
config.withScorer(new SimpleScorer<Entity, ECSAction>(AttackAnalyze::discardScore));
return config;
}

Expand All @@ -53,6 +57,7 @@ public static ScoreConfigFactory<Entity, ECSAction> fighter() {
// config.withScorer(new SimpleScorer<>(AttackAnalyze::scrapScore));
config.withScorer(new SimpleScorer<Entity, ECSAction>(AttackAnalyze::attackScore));
config.withScorer(new SimpleScorer<Entity, ECSAction>(AttackAnalyze::enchantScore));
config.withScorer(new SimpleScorer<Entity, ECSAction>(AttackAnalyze::discardScore));
return config;
}

Expand Down
Expand Up @@ -7,6 +7,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.cardshifter.core.groovy.MulliganSingleCardsAction;
import com.cardshifter.modapi.actions.ECSAction;
import com.cardshifter.modapi.actions.TargetSet;
import com.cardshifter.modapi.base.ComponentRetriever;
Expand All @@ -25,157 +26,177 @@

public class AttackAnalyze {

private static final ResourceRetriever health = ResourceRetriever.forResource(CyborgChroniclesResources.HEALTH);
private static final ResourceRetriever attack = ResourceRetriever.forResource(CyborgChroniclesResources.ATTACK);
private static final ResourceRetriever scrapCost = ResourceRetriever.forResource(CyborgChroniclesResources.SCRAP_COST);

public static double attackScore(ECSAction action, ScoreParameters<Entity> params) {
if (!action.getName().equals(CyborgChroniclesGame.ATTACK_ACTION)) {
return 0;
}

TargetSet targets = action.getTargetSets().get(0);
targets.clearTargets();
List<Entity> possibleTargets = targets.findPossibleTargets();
Optional<Entity> player = possibleTargets.stream().filter(e -> e.hasComponent(PlayerComponent.class)).findAny();
if (player.isPresent()) {
// attacking a player is really the best option there is
targets.addTarget(player.get());
return 100;
}
if (possibleTargets.isEmpty()) {
throw new RuntimeException("Attack action has no targets: " + action);
}

possibleTargets.sort(Comparator.comparingInt(e -> attackVS(action.getOwner(), e)));
Entity chosenTarget = possibleTargets.get(possibleTargets.size() - 1);
targets.addTarget(chosenTarget);

return attackVS(action.getOwner(), chosenTarget);
}

private static int attackVS(Entity attacker, Entity target) {
int score = 0;
boolean attackerDies = attack.getFor(target) >= health.getFor(attacker);
boolean targetDies = attack.getFor(attacker) >= health.getFor(target);
int trampleDamage = attack.getFor(attacker) - health.getFor(target);
int additionalDamageBack = attack.getFor(target) - health.getFor(attacker);
if (attackerDies && !targetDies) {
// this is really the worst option there is
return -10;
}

if (targetDies) {
score += 100;
score -= trampleDamage;
}
if (attackerDies) {
score -= 97;
score += additionalDamageBack;
}
return score;
}

public static double scrapNeeded(ECSAction action, ScoreParameters<Entity> params) {
if (!action.getName().equals(CyborgChroniclesGame.SCRAP_ACTION)) {
return 0;
}

Entity entity = action.getOwner();
ComponentRetriever<CardComponent> card = Retrievers.component(CardComponent.class);
Entity owner = card.get(entity).getOwner();
HandComponent hand = owner.getComponent(HandComponent.class);
return hand.stream().mapToInt(e -> scrapCost.getOrDefault(e, 0)).sum();
}

public static double scrapIfCanGetKilled(ECSAction action, ScoreParameters<Entity> params) {
if (!action.getName().equals(CyborgChroniclesGame.SCRAP_ACTION)) {
return 0;
}

Entity entity = action.getOwner();
ComponentRetriever<CardComponent> card = Retrievers.component(CardComponent.class);
Entity owner = card.get(entity).getOwner();
Set<Entity> players = entity.getGame().getEntitiesWithComponent(PlayerComponent.class);
Entity opponent = players.stream().filter(pl -> pl.getComponent(PlayerComponent.class).getIndex() != owner.getComponent(PlayerComponent.class).getIndex()).findAny().get();

ZoneComponent battlefield = opponent.getComponent(BattlefieldComponent.class);
int myHealth = health.getFor(entity);
int myAttack = attack.getFor(entity);

List<Entity> creaturesCanKill = battlefield.stream().filter(e -> attack.getOrDefault(e, 0) >= myHealth).collect(Collectors.toList());
if (creaturesCanKill.isEmpty()) {
// This creature cannot die from any opponent creature, then it is safe
return -10;
}

Stream<Entity> creaturesCanNotDie = creaturesCanKill.stream().filter(e -> health.getOrDefault(e, 0) > myAttack);
if (creaturesCanNotDie.findAny().isPresent()) {
// If I stay here, then I am toast.
return 1;
}

// We can kill each other
return 0.25;
}

public static double scrapScore(ECSAction action, ScoreParameters<Entity> params) {
if (!action.getName().equals(CyborgChroniclesGame.SCRAP_ACTION)) {
return 0;
}

Entity entity = action.getOwner();
ComponentRetriever<CardComponent> card = Retrievers.component(CardComponent.class);

ZoneComponent battlefield = card.get(entity).getCurrentZone();

List<Entity> creatures = battlefield.getCards();
if (creatures.size() <= 3) {
return -health.getFor(entity);
}

creatures.sort(Comparator.comparingInt(e -> health.getFor(e) + attack.getFor(e)));
if (entity == creatures.get(0)) {
// Only consider scrapping the creature with lowest health
return 4 - health.getFor(entity);
}
return -1;
}

public static double health(ECSAction action, ScoreParameters<Entity> params) {
return health.getOrDefault(action.getOwner(), 0);
}

public static double attack(ECSAction action, ScoreParameters<Entity> params) {
return attack.getOrDefault(action.getOwner(), 0);
}

public static double enchantScore(ECSAction action, ScoreParameters<Entity> params) {
if (!action.getName().equals(CyborgChroniclesGame.ENCHANT_ACTION)) {
return 0;
}

TargetSet targets = action.getTargetSets().get(0);
targets.clearTargets();
List<Entity> possibleTargets = targets.findPossibleTargets();

if (possibleTargets.isEmpty()) {
return -1;
}

Entity enchantment = action.getOwner();
int attackBonus = attack.getFor(enchantment);
int healthBonus = health.getFor(enchantment);

possibleTargets.sort(Comparator.comparingDouble(e -> enchantScore(e, attackBonus, healthBonus)));
Entity chosenTarget = possibleTargets.get(possibleTargets.size() - 1);
targets.addTarget(chosenTarget);

return enchantScore(chosenTarget, attackBonus, healthBonus);
}

private static double enchantScore(Entity e, int attackBonus, int healthBonus) {
return 1.5*(health.getFor(e) + healthBonus) + attack.getFor(e) + attackBonus;
}
private static final ResourceRetriever health = ResourceRetriever.forResource(CyborgChroniclesResources.HEALTH);
private static final ResourceRetriever attack = ResourceRetriever.forResource(CyborgChroniclesResources.ATTACK);
private static final ResourceRetriever scrapCost = ResourceRetriever.forResource(CyborgChroniclesResources.SCRAP_COST);

public static double attackScore(ECSAction action, ScoreParameters<Entity> params) {
if (!action.getName().equals(CyborgChroniclesGame.ATTACK_ACTION)) {
return 0;
}

TargetSet targets = action.getTargetSets().get(0);
targets.clearTargets();
List<Entity> possibleTargets = targets.findPossibleTargets();
Optional<Entity> player = possibleTargets.stream().filter(e -> e.hasComponent(PlayerComponent.class)).findAny();
if (player.isPresent()) {
// attacking a player is really the best option there is
targets.addTarget(player.get());
return 100;
}
if (possibleTargets.isEmpty()) {
throw new RuntimeException("Attack action has no targets: " + action);
}

possibleTargets.sort(Comparator.comparingInt(e -> attackVS(action.getOwner(), e)));
Entity chosenTarget = possibleTargets.get(possibleTargets.size() - 1);
targets.addTarget(chosenTarget);

return attackVS(action.getOwner(), chosenTarget);
}

private static int attackVS(Entity attacker, Entity target) {
int score = 0;
boolean attackerDies = attack.getFor(target) >= health.getFor(attacker);
boolean targetDies = attack.getFor(attacker) >= health.getFor(target);
int trampleDamage = attack.getFor(attacker) - health.getFor(target);
int additionalDamageBack = attack.getFor(target) - health.getFor(attacker);
if (attackerDies && !targetDies) {
// this is really the worst option there is
return -10;
}

if (targetDies) {
score += 100;
score -= trampleDamage;
}
if (attackerDies) {
score -= 97;
score += additionalDamageBack;
}
return score;
}

public static double scrapNeeded(ECSAction action, ScoreParameters<Entity> params) {
if (!action.getName().equals(CyborgChroniclesGame.SCRAP_ACTION)) {
return 0;
}

Entity entity = action.getOwner();
ComponentRetriever<CardComponent> card = Retrievers.component(CardComponent.class);
Entity owner = card.get(entity).getOwner();
HandComponent hand = owner.getComponent(HandComponent.class);
return hand.stream().mapToInt(e -> scrapCost.getOrDefault(e, 0)).sum();
}

public static double scrapIfCanGetKilled(ECSAction action, ScoreParameters<Entity> params) {
if (!action.getName().equals(CyborgChroniclesGame.SCRAP_ACTION)) {
return 0;
}

Entity entity = action.getOwner();
ComponentRetriever<CardComponent> card = Retrievers.component(CardComponent.class);
Entity owner = card.get(entity).getOwner();
Set<Entity> players = entity.getGame().getEntitiesWithComponent(PlayerComponent.class);
Entity opponent = players.stream().filter(pl -> pl.getComponent(PlayerComponent.class).getIndex() != owner.getComponent(PlayerComponent.class).getIndex()).findAny().get();

ZoneComponent battlefield = opponent.getComponent(BattlefieldComponent.class);
int myHealth = health.getFor(entity);
int myAttack = attack.getFor(entity);

List<Entity> creaturesCanKill = battlefield.stream().filter(e -> attack.getOrDefault(e, 0) >= myHealth).collect(Collectors.toList());
if (creaturesCanKill.isEmpty()) {
// This creature cannot die from any opponent creature, then it is safe
return -10;
}

Stream<Entity> creaturesCanNotDie = creaturesCanKill.stream().filter(e -> health.getOrDefault(e, 0) > myAttack);
if (creaturesCanNotDie.findAny().isPresent()) {
// If I stay here, then I am toast.
return 1;
}

// We can kill each other
return 0.25;
}

public static double scrapScore(ECSAction action, ScoreParameters<Entity> params) {
if (!action.getName().equals(CyborgChroniclesGame.SCRAP_ACTION)) {
return 0;
}

Entity entity = action.getOwner();
ComponentRetriever<CardComponent> card = Retrievers.component(CardComponent.class);

ZoneComponent battlefield = card.get(entity).getCurrentZone();

List<Entity> creatures = battlefield.getCards();
if (creatures.size() <= 3) {
return -health.getFor(entity);
}

creatures.sort(Comparator.comparingInt(e -> health.getFor(e) + attack.getFor(e)));
if (entity == creatures.get(0)) {
// Only consider scrapping the creature with lowest health
return 4 - health.getFor(entity);
}
return -1;
}

public static double health(ECSAction action, ScoreParameters<Entity> params) {
return health.getOrDefault(action.getOwner(), 0);
}

public static double attack(ECSAction action, ScoreParameters<Entity> params) {
return attack.getOrDefault(action.getOwner(), 0);
}

public static double enchantScore(ECSAction action, ScoreParameters<Entity> params) {
if (!action.getName().equals(CyborgChroniclesGame.ENCHANT_ACTION)) {
return 0;
}

TargetSet targets = action.getTargetSets().get(0);
targets.clearTargets();
List<Entity> possibleTargets = targets.findPossibleTargets();

if (possibleTargets.isEmpty()) {
return -1;
}

Entity enchantment = action.getOwner();
int attackBonus = attack.getFor(enchantment);
int healthBonus = health.getFor(enchantment);

possibleTargets.sort(Comparator.comparingDouble(e -> enchantScore(e, attackBonus, healthBonus)));
Entity chosenTarget = possibleTargets.get(possibleTargets.size() - 1);
targets.addTarget(chosenTarget);

return enchantScore(chosenTarget, attackBonus, healthBonus);
}

public static double discardScore(ECSAction action, ScoreParameters<Entity> params) {
if (!action.getName().equals(MulliganSingleCardsAction.ACTION_NAME)) {
return 0;
}

TargetSet targets = action.getTargetSets().get(0);
targets.clearTargets();
List<Entity> possibleTargets = targets.findPossibleTargets();

if (possibleTargets.isEmpty()) {
return -1;
}

// TODO: Need some kind of target-scorers to choose the best targets.
Entity chosenTarget = possibleTargets.get(possibleTargets.size() - 1);
targets.addTarget(chosenTarget);

return 0;
}

private static double enchantScore(Entity e, int attackBonus, int healthBonus) {
return 1.5 * (health.getFor(e) + healthBonus) + attack.getFor(e) + attackBonus;
}

}

0 comments on commit 5edbec0

Please sign in to comment.