forked from External/mage
AI: removed outdated AIMinimax project (#7075), removed some useless player classes, code and config files, improved docs;
This commit is contained in:
parent
6ac2f44cc1
commit
08b99fcbf7
40 changed files with 103 additions and 2462 deletions
|
|
@ -1,7 +0,0 @@
|
|||
maxDepth=10
|
||||
maxNodes=5000
|
||||
evaluatorLifeFactor=2
|
||||
evaluatorPermanentFactor=1
|
||||
evaluatorCreatureFactor=1
|
||||
evaluatorHandFactor=1
|
||||
maxThinkSeconds=30
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<artifactId>mage-player-ai-draftbot</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Mage Player AI.DraftBot</name>
|
||||
<name>Mage Player AI (draft bot)</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
|
||||
|
||||
package mage.player.ai;
|
||||
|
||||
import mage.constants.RangeOfInfluence;
|
||||
import mage.game.Game;
|
||||
import mage.game.Table;
|
||||
import mage.game.tournament.TournamentType;
|
||||
import mage.players.Player;
|
||||
|
||||
/**
|
||||
* AI: server side bot for drafts (draftbot, the latest version)
|
||||
* <p>
|
||||
* Can play drafts only, concede/lose on any real game and tourney
|
||||
*
|
||||
* @author nantuko
|
||||
*/
|
||||
public class ComputerDraftPlayer extends ComputerPlayer implements Player {
|
||||
public class ComputerDraftPlayer extends ComputerPlayer {
|
||||
|
||||
public ComputerDraftPlayer(String name, RangeOfInfluence range) {
|
||||
super(name, range);
|
||||
|
|
@ -46,7 +46,7 @@ public class ComputerDraftPlayer extends ComputerPlayer implements Player {
|
|||
public boolean canJoinTable(Table table) {
|
||||
if (table.isTournament()) {
|
||||
TournamentType tournamentType = table.getTournament().getTournamentType();
|
||||
if(tournamentType != null && tournamentType.isDraft()) {
|
||||
if (tournamentType != null && tournamentType.isDraft()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
maxDepth=10
|
||||
maxNodes=5000
|
||||
evaluatorLifeFactor=2
|
||||
evaluatorPermanentFactor=1
|
||||
evaluatorCreatureFactor=1
|
||||
evaluatorHandFactor=1
|
||||
maxThinkSeconds=10
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<artifactId>mage-player-ai-ma</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Mage Player AI.MA</name>
|
||||
<name>Mage Player AI (mad bot)</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
|
|
|||
|
|
@ -41,12 +41,18 @@ import java.util.concurrent.*;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* AI: server side bot with game simulations (mad bot, part of implementation)
|
||||
*
|
||||
* @author nantuko
|
||||
*/
|
||||
public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
|
||||
public class ComputerPlayer6 extends ComputerPlayer {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(ComputerPlayer6.class);
|
||||
|
||||
// TODO: add and research maxNodes logs, is it good to increase to 50000 for better results?
|
||||
// TODO: increase maxNodes due AI skill level?
|
||||
private static final int MAX_SIMULATED_NODES_PER_CALC = 5000;
|
||||
|
||||
// same params as Executors.newFixedThreadPool
|
||||
// no needs erorrs check in afterExecute here cause that pool used for FutureTask with result check already
|
||||
private static final ExecutorService threadPoolSimulations = new ThreadPoolExecutor(
|
||||
|
|
@ -97,7 +103,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
|
|||
maxDepth = skill;
|
||||
}
|
||||
maxThink = skill * 3;
|
||||
maxNodes = Config2.maxNodes;
|
||||
maxNodes = MAX_SIMULATED_NODES_PER_CALC;
|
||||
getSuggestedActions();
|
||||
this.actionCache = new HashSet<>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import java.util.Date;
|
|||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* AI: server side bot with game simulations (mad bot, the latest version)
|
||||
*
|
||||
* @author ayratn
|
||||
*/
|
||||
public class ComputerPlayer7 extends ComputerPlayer6 {
|
||||
|
|
|
|||
|
|
@ -1,59 +0,0 @@
|
|||
|
||||
|
||||
package mage.player.ai;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public final class Config2 {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(Config2.class);
|
||||
|
||||
// public static final int maxDepth;
|
||||
public static final int maxNodes;
|
||||
public static final int evaluatorLifeFactor;
|
||||
public static final int evaluatorPermanentFactor;
|
||||
public static final int evaluatorCreatureFactor;
|
||||
public static final int evaluatorHandFactor;
|
||||
// public static final int maxThinkSeconds;
|
||||
|
||||
static {
|
||||
Properties p = new Properties();
|
||||
try {
|
||||
File file = new File(Config2.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
|
||||
File propertiesFile = new File(file.getParent() + File.separator + "AIMinimax.properties");
|
||||
if (propertiesFile.exists()) {
|
||||
p.load(new FileInputStream(propertiesFile));
|
||||
} else {
|
||||
// p.setProperty("maxDepth", "10");
|
||||
p.setProperty("maxNodes", "50000");
|
||||
p.setProperty("evaluatorLifeFactor", "2");
|
||||
p.setProperty("evaluatorPermanentFactor", "1");
|
||||
p.setProperty("evaluatorCreatureFactor", "1");
|
||||
p.setProperty("evaluatorHandFactor", "1");
|
||||
// p.setProperty("maxThinkSeconds", "30");
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
logger.error(null, ex);
|
||||
} catch (URISyntaxException ex) {
|
||||
logger.error(null, ex);
|
||||
}
|
||||
// maxDepth = Integer.parseInt(p.getProperty("maxDepth"));
|
||||
maxNodes = Integer.parseInt(p.getProperty("maxNodes"));
|
||||
evaluatorLifeFactor = Integer.parseInt(p.getProperty("evaluatorLifeFactor"));
|
||||
evaluatorPermanentFactor = Integer.parseInt(p.getProperty("evaluatorPermanentFactor"));
|
||||
evaluatorCreatureFactor = Integer.parseInt(p.getProperty("evaluatorCreatureFactor"));
|
||||
evaluatorHandFactor = Integer.parseInt(p.getProperty("evaluatorHandFactor"));
|
||||
// maxThinkSeconds = Integer.parseInt(p.getProperty("maxThinkSeconds"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -27,11 +27,11 @@ import java.util.*;
|
|||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
/**
|
||||
* AI: mock player in simulated games (each player replaced by simulated)
|
||||
* AI: helper class to simulate games with computer bot (each player replaced by simulated)
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class SimulatedPlayer2 extends ComputerPlayer {
|
||||
public final class SimulatedPlayer2 extends ComputerPlayer {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(SimulatedPlayer2.class);
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
<artifactId>mage-player-ai</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Mage Player AI</name>
|
||||
<name>Mage Player AI (basic)</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
|
|
|||
|
|
@ -59,11 +59,11 @@ import java.util.*;
|
|||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* suitable for two player games and some multiplayer games
|
||||
* AI: basic server side bot with simple actions support (game, draft, construction/sideboarding)
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||
*/
|
||||
public class ComputerPlayer extends PlayerImpl implements Player {
|
||||
public class ComputerPlayer extends PlayerImpl {
|
||||
|
||||
private static final Logger log = Logger.getLogger(ComputerPlayer.class);
|
||||
private long lastThinkTime = 0; // msecs for last AI actions calc
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import java.util.concurrent.TimeUnit;
|
|||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class ComputerPlayerMCTS extends ComputerPlayer implements Player {
|
||||
public class ComputerPlayerMCTS extends ComputerPlayer {
|
||||
|
||||
private static final int THINK_MIN_RATIO = 40;
|
||||
private static final int THINK_MAX_RATIO = 100;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.player.ai;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
|
|
@ -15,6 +14,10 @@ import java.util.List;
|
|||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* AI: server side bot with monte carlo logic (experimental, the latest version)
|
||||
* <p>
|
||||
* Simple implementation for random play, outdate and do not support,
|
||||
* see <a href="https://github.com/magefree/mage/issues/7075">more details here</a>
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
|
|
@ -51,22 +54,19 @@ public class MCTSPlayer extends ComputerPlayer {
|
|||
public List<Ability> getPlayableOptions(Game game) {
|
||||
List<Ability> all = new ArrayList<>();
|
||||
List<ActivatedAbility> playables = getPlayableAbilities(game);
|
||||
for (ActivatedAbility ability: playables) {
|
||||
for (ActivatedAbility ability : playables) {
|
||||
List<Ability> options = game.getPlayer(playerId).getPlayableOptions(ability, game);
|
||||
if (options.isEmpty()) {
|
||||
if (!ability.getManaCosts().getVariableCosts().isEmpty()) {
|
||||
simulateVariableCosts(ability, all, game);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
all.add(ability);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (Ability option: options) {
|
||||
} else {
|
||||
for (Ability option : options) {
|
||||
if (!ability.getManaCosts().getVariableCosts().isEmpty()) {
|
||||
simulateVariableCosts(option, all, game);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
all.add(option);
|
||||
}
|
||||
}
|
||||
|
|
@ -137,7 +137,7 @@ public class MCTSPlayer extends ComputerPlayer {
|
|||
|
||||
private List<List<UUID>> copyEngagement(List<List<UUID>> engagement) {
|
||||
List<List<UUID>> newEngagement = new ArrayList<>();
|
||||
for (List<UUID> group: engagement) {
|
||||
for (List<UUID> group : engagement) {
|
||||
newEngagement.add(new ArrayList<>(group));
|
||||
}
|
||||
return newEngagement;
|
||||
|
|
@ -154,7 +154,7 @@ public class MCTSPlayer extends ComputerPlayer {
|
|||
List<Permanent> remaining = remove(blockers, blocker);
|
||||
for (int i = 0; i < numGroups; i++) {
|
||||
if (game.getCombat().getGroups().get(i).canBlock(blocker, game)) {
|
||||
List<List<UUID>>newEngagement = copyEngagement(engagement);
|
||||
List<List<UUID>> newEngagement = copyEngagement(engagement);
|
||||
newEngagement.get(i).add(blocker.getId());
|
||||
engagements.add(newEngagement);
|
||||
// logger.debug("simulating -- found redundant block combination");
|
||||
|
|
|
|||
|
|
@ -24,11 +24,13 @@ import java.io.Serializable;
|
|||
import java.util.*;
|
||||
|
||||
/**
|
||||
* plays randomly
|
||||
* AI: helper class to simulate games with MCTS AI (each player replaced by simulated)
|
||||
* <p>
|
||||
* Plays randomly
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class SimulatedPlayerMCTS extends MCTSPlayer {
|
||||
public final class SimulatedPlayerMCTS extends MCTSPlayer {
|
||||
|
||||
private boolean isSimulatedPlayer;
|
||||
private int actionCount = 0;
|
||||
|
|
@ -98,28 +100,13 @@ public class SimulatedPlayerMCTS extends MCTSPlayer {
|
|||
ability.addManaCostsToPay(new GenericManaCost(RandomUtil.nextInt(amount)));
|
||||
}
|
||||
}
|
||||
// check if ability kills player, if not then it's ok to play
|
||||
// if (ability.isUsesStack()) {
|
||||
// Game testSim = game.copy();
|
||||
// activateAbility((ActivatedAbility) ability, testSim);
|
||||
// StackObject testAbility = testSim.getStack().pop();
|
||||
// testAbility.resolve(testSim);
|
||||
// testSim.applyEffects();
|
||||
// testSim.checkStateAndTriggered();
|
||||
// if (!testSim.getPlayer(playerId).hasLost()) {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// else {
|
||||
break;
|
||||
// }
|
||||
}
|
||||
return ability;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean triggerAbility(TriggeredAbility source, Game game) {
|
||||
// logger.info("trigger");
|
||||
if (source != null && source.canChooseTarget(game, playerId)) {
|
||||
Ability ability;
|
||||
List<Ability> options = getPlayableOptions(source, game);
|
||||
|
|
@ -153,7 +140,6 @@ public class SimulatedPlayerMCTS extends MCTSPlayer {
|
|||
@Override
|
||||
public void selectAttackers(Game game, UUID attackingPlayerId) {
|
||||
//useful only for two player games - will only attack first opponent
|
||||
// logger.info("select attackers");
|
||||
UUID defenderId = game.getOpponents(playerId).iterator().next();
|
||||
List<Permanent> attackersList = super.getAvailableAttackers(defenderId, game);
|
||||
//use binary digits to calculate powerset of attackers
|
||||
|
|
@ -177,7 +163,6 @@ public class SimulatedPlayerMCTS extends MCTSPlayer {
|
|||
|
||||
@Override
|
||||
public void selectBlockers(Ability source, Game game, UUID defendingPlayerId) {
|
||||
// logger.info("select blockers");
|
||||
int numGroups = game.getCombat().getGroups().size();
|
||||
if (numGroups == 0) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
maxDepth=10
|
||||
maxNodes=5000
|
||||
evaluatorLifeFactor=2
|
||||
evaluatorPermanentFactor=1
|
||||
evaluatorCreatureFactor=1
|
||||
evaluatorHandFactor=1
|
||||
maxThinkSeconds=30
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.mage</groupId>
|
||||
<artifactId>mage-server-plugins</artifactId>
|
||||
<version>1.4.50</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>mage-player-aiminimax</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Mage Player AI Minimax</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>mage</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>mage-player-ai</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>src</sourceDirectory>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<configuration>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
|
||||
<finalName>mage-player-aiminimax</finalName>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<root.dir>${project.basedir}/../..</root.dir>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
|
||||
|
||||
package mage.player.ai;
|
||||
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class Attackers extends TreeMap<Integer, List<Permanent>> {
|
||||
|
||||
public List<Permanent> getAttackers() {
|
||||
List<Permanent> attackers = new ArrayList<>();
|
||||
for (List<Permanent> l: this.values()) {
|
||||
for (Permanent permanent: l) {
|
||||
attackers.add(permanent);
|
||||
}
|
||||
}
|
||||
return attackers;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
|
||||
|
||||
package mage.player.ai;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Properties;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public final class Config {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(Config.class);
|
||||
|
||||
// public static final int maxDepth;
|
||||
public static final int maxNodes;
|
||||
public static final int evaluatorLifeFactor;
|
||||
public static final int evaluatorPermanentFactor;
|
||||
public static final int evaluatorCreatureFactor;
|
||||
public static final int evaluatorHandFactor;
|
||||
// public static final int maxThinkSeconds;
|
||||
|
||||
static {
|
||||
Properties p = new Properties();
|
||||
try {
|
||||
File file = new File(Config.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
|
||||
p.load(new FileInputStream(new File(file.getParent() + File.separator + "AIMinimax.properties")));
|
||||
} catch (IOException ex) {
|
||||
logger.fatal("", ex);
|
||||
} catch (URISyntaxException ex) {
|
||||
logger.fatal("", ex);
|
||||
}
|
||||
// maxDepth = Integer.parseInt(p.getProperty("maxDepth"));
|
||||
maxNodes = Integer.parseInt(p.getProperty("maxNodes"));
|
||||
evaluatorLifeFactor = Integer.parseInt(p.getProperty("evaluatorLifeFactor"));
|
||||
evaluatorPermanentFactor = Integer.parseInt(p.getProperty("evaluatorPermanentFactor"));
|
||||
evaluatorCreatureFactor = Integer.parseInt(p.getProperty("evaluatorCreatureFactor"));
|
||||
evaluatorHandFactor = Integer.parseInt(p.getProperty("evaluatorHandFactor"));
|
||||
// maxThinkSeconds = Integer.parseInt(p.getProperty("maxThinkSeconds"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
|
||||
package mage.player.ai;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.abilities.ActivatedAbility;
|
||||
import mage.abilities.keyword.DoubleStrikeAbility;
|
||||
import mage.abilities.keyword.FirstStrikeAbility;
|
||||
import mage.abilities.keyword.TrampleAbility;
|
||||
import mage.abilities.mana.ActivatedManaAbilityImpl;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Zone;
|
||||
import mage.counters.BoostCounter;
|
||||
import mage.counters.Counter;
|
||||
import mage.counters.CounterType;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*
|
||||
* this evaluator is only good for two player games
|
||||
*
|
||||
*/
|
||||
public final class GameStateEvaluator {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(GameStateEvaluator.class);
|
||||
|
||||
private static final int LIFE_FACTOR = Config.evaluatorLifeFactor;
|
||||
private static final int PERMANENT_FACTOR = Config.evaluatorPermanentFactor;
|
||||
private static final int CREATURE_FACTOR = Config.evaluatorCreatureFactor;
|
||||
private static final int HAND_FACTOR = Config.evaluatorHandFactor;
|
||||
|
||||
public static final int WIN_SCORE = Integer.MAX_VALUE - 1;
|
||||
public static final int LOSE_SCORE = Integer.MIN_VALUE + 1;
|
||||
|
||||
public static int evaluate(UUID playerId, Game game) {
|
||||
return evaluate(playerId, game, false);
|
||||
}
|
||||
|
||||
public static int evaluate(UUID playerId, Game game, boolean ignoreTapped) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
Player opponent = game.getPlayer(game.getOpponents(playerId).stream().findFirst().orElse(null));
|
||||
if (opponent == null) {
|
||||
return WIN_SCORE;
|
||||
}
|
||||
|
||||
if (game.checkIfGameIsOver()) {
|
||||
if (player.hasLost() || opponent.hasWon()) {
|
||||
return LOSE_SCORE;
|
||||
}
|
||||
if (opponent.hasLost() || player.hasWon()) {
|
||||
return WIN_SCORE;
|
||||
}
|
||||
}
|
||||
int lifeScore = (player.getLife() - opponent.getLife()) * LIFE_FACTOR;
|
||||
int poisonScore = (opponent.getCounters().getCount(CounterType.POISON) - player.getCounters().getCount(CounterType.POISON)) * LIFE_FACTOR * 2;
|
||||
int permanentScore = 0;
|
||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(playerId)) {
|
||||
permanentScore += evaluatePermanent(permanent, game, ignoreTapped);
|
||||
}
|
||||
for (Permanent permanent : game.getBattlefield().getAllActivePermanents(opponent.getId())) {
|
||||
permanentScore -= evaluatePermanent(permanent, game, ignoreTapped);
|
||||
}
|
||||
permanentScore *= PERMANENT_FACTOR;
|
||||
|
||||
int handScore = 0;
|
||||
handScore = player.getHand().size() - opponent.getHand().size();
|
||||
handScore *= HAND_FACTOR;
|
||||
|
||||
int score = lifeScore + poisonScore + permanentScore + handScore;
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("game state for player " + player.getName() + " evaluated to- lifeScore:" + lifeScore + " permanentScore:" + permanentScore + " handScore:" + handScore + " total:" + score);
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
public static int evaluatePermanent(Permanent permanent, Game game, boolean ignoreTapped) {
|
||||
int value = 0;
|
||||
if (ignoreTapped) {
|
||||
value = 5;
|
||||
} else {
|
||||
value = permanent.isTapped() ? 4 : 5;
|
||||
}
|
||||
if (permanent.getCardType(game).contains(CardType.CREATURE)) {
|
||||
value += evaluateCreature(permanent, game) * CREATURE_FACTOR;
|
||||
}
|
||||
value += permanent.getAbilities().getActivatedManaAbilities(Zone.BATTLEFIELD).size();
|
||||
for (ActivatedAbility ability : permanent.getAbilities().getActivatedAbilities(Zone.BATTLEFIELD)) {
|
||||
if (!(ability instanceof ActivatedManaAbilityImpl) && ability.canActivate(ability.getControllerId(), game).canActivate()) {
|
||||
value += ability.getEffects().size();
|
||||
}
|
||||
}
|
||||
for (Counter counter : permanent.getCounters(game).values()) {
|
||||
if (!(counter instanceof BoostCounter)) {
|
||||
value += counter.getCount();
|
||||
}
|
||||
}
|
||||
value += permanent.getAbilities().getStaticAbilities(Zone.BATTLEFIELD).size();
|
||||
value += permanent.getAbilities().getTriggeredAbilities(Zone.BATTLEFIELD).size();
|
||||
value += permanent.getManaCost().manaValue();
|
||||
//TODO: add a difficulty to calculation to ManaCost - sort permanents by difficulty for casting when evaluating game states
|
||||
return value;
|
||||
}
|
||||
|
||||
public static int evaluateCreature(Permanent creature, Game game) {
|
||||
int value = 0;
|
||||
value += creature.getPower().getValue();
|
||||
value += creature.getToughness().getValue();
|
||||
// if (creature.canAttack(game))
|
||||
// value += creature.getPower().getValue();
|
||||
// if (!creature.isTapped())
|
||||
// value += 2;
|
||||
value += creature.getAbilities().getEvasionAbilities().size();
|
||||
value += creature.getAbilities().getProtectionAbilities().size();
|
||||
value += creature.getAbilities().containsKey(FirstStrikeAbility.getInstance().getId()) ? 1 : 0;
|
||||
value += creature.getAbilities().containsKey(DoubleStrikeAbility.getInstance().getId()) ? 2 : 0;
|
||||
value += creature.getAbilities().containsKey(TrampleAbility.getInstance().getId()) ? 1 : 0;
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
|
||||
|
||||
package mage.player.ai;
|
||||
|
||||
import java.util.List;
|
||||
import mage.abilities.Ability;
|
||||
import mage.game.Game;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class SimulatedAction {
|
||||
|
||||
private Game game;
|
||||
private List<Ability> abilities;
|
||||
|
||||
public SimulatedAction(Game game, List<Ability> abilities) {
|
||||
this.game = game;
|
||||
this.abilities = abilities;
|
||||
}
|
||||
|
||||
public Game getGame() {
|
||||
return this.game;
|
||||
}
|
||||
|
||||
public List<Ability> getAbilities() {
|
||||
return this.abilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.abilities.toString();
|
||||
}
|
||||
|
||||
public boolean usesStack() {
|
||||
if (abilities != null && !abilities.isEmpty()) {
|
||||
return abilities.get(abilities.size() -1).isUsesStack();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,265 +0,0 @@
|
|||
|
||||
|
||||
package mage.player.ai;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.ActivatedAbility;
|
||||
import mage.abilities.SpellAbility;
|
||||
import mage.abilities.TriggeredAbility;
|
||||
import mage.abilities.common.PassAbility;
|
||||
import mage.abilities.costs.mana.GenericManaCost;
|
||||
import mage.game.Game;
|
||||
import mage.game.combat.Combat;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.match.MatchPlayer;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.stack.StackAbility;
|
||||
import mage.players.Player;
|
||||
import mage.target.Target;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class SimulatedPlayer extends ComputerPlayer {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(SimulatedPlayer.class);
|
||||
private boolean isSimulatedPlayer;
|
||||
private transient ConcurrentLinkedQueue<Ability> allActions;
|
||||
protected int maxDepth;
|
||||
|
||||
public SimulatedPlayer(Player originalPlayer, boolean isSimulatedPlayer, int maxDepth) {
|
||||
super(originalPlayer.getId());
|
||||
this.maxDepth = maxDepth;
|
||||
this.isSimulatedPlayer = isSimulatedPlayer;
|
||||
this.matchPlayer = new MatchPlayer(originalPlayer.getMatchPlayer(), this);
|
||||
}
|
||||
|
||||
public SimulatedPlayer(final SimulatedPlayer player) {
|
||||
super(player);
|
||||
this.isSimulatedPlayer = player.isSimulatedPlayer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimulatedPlayer copy() {
|
||||
return new SimulatedPlayer(this);
|
||||
}
|
||||
|
||||
public List<Ability> simulatePriority(Game game) {
|
||||
allActions = new ConcurrentLinkedQueue<>();
|
||||
Game sim = game.copy();
|
||||
|
||||
ActivatedAbility pass = new PassAbility();
|
||||
simulateOptions(sim, pass);
|
||||
|
||||
List<Ability> list = new ArrayList<>(allActions);
|
||||
//Collections.shuffle(list);
|
||||
Collections.reverse(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
protected void simulateOptions(Game game, ActivatedAbility previousActions) {
|
||||
allActions.add(previousActions);
|
||||
List<ActivatedAbility> playables = game.getPlayer(playerId).getPlayable(game, isSimulatedPlayer);
|
||||
for (ActivatedAbility ability: playables) {
|
||||
List<Ability> options = game.getPlayer(playerId).getPlayableOptions(ability, game);
|
||||
if (options.isEmpty()) {
|
||||
if (!ability.getManaCosts().getVariableCosts().isEmpty()) {
|
||||
simulateVariableCosts(ability, game);
|
||||
}
|
||||
else {
|
||||
allActions.add(ability);
|
||||
}
|
||||
// simulateAction(game, previousActions, ability);
|
||||
}
|
||||
else {
|
||||
// ExecutorService simulationExecutor = Executors.newFixedThreadPool(4);
|
||||
for (Ability option: options) {
|
||||
if (!ability.getManaCosts().getVariableCosts().isEmpty()) {
|
||||
simulateVariableCosts(option, game);
|
||||
}
|
||||
else {
|
||||
allActions.add(option);
|
||||
}
|
||||
// SimulationWorker worker = new SimulationWorker(game, this, previousActions, option);
|
||||
// simulationExecutor.submit(worker);
|
||||
}
|
||||
// simulationExecutor.shutdown();
|
||||
// while(!simulationExecutor.isTerminated()) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// protected void simulateAction(Game game, SimulatedAction previousActions, Ability action) {
|
||||
// List<Ability> actions = new ArrayList<Ability>(previousActions.getAbilities());
|
||||
// actions.add(action);
|
||||
// Game sim = game.copy();
|
||||
// if (sim.getPlayer(playerId).activateAbility((ActivatedAbility) action.copy(), sim)) {
|
||||
// sim.applyEffects();
|
||||
// sim.getPlayers().resetPassed();
|
||||
// allActions.add(new SimulatedAction(sim, actions));
|
||||
// }
|
||||
// }
|
||||
|
||||
//add a generic mana cost for each amount possible
|
||||
protected void simulateVariableCosts(Ability ability, Game game) {
|
||||
int numAvailable = getAvailableManaProducers(game).size() - ability.getManaCosts().manaValue();
|
||||
int start = 0;
|
||||
if (!(ability instanceof SpellAbility)) {
|
||||
//only use x=0 on spell abilities
|
||||
if (numAvailable == 0)
|
||||
return;
|
||||
else
|
||||
start = 1;
|
||||
}
|
||||
for (int i = start; i < numAvailable; i++) {
|
||||
Ability newAbility = ability.copy();
|
||||
newAbility.addManaCostsToPay(new GenericManaCost(i));
|
||||
allActions.add(newAbility);
|
||||
}
|
||||
}
|
||||
|
||||
/*@Override
|
||||
public boolean playXMana(VariableManaCost cost, ManaCosts<ManaCost> costs, Game game) {
|
||||
//simulateVariableCosts method adds a generic mana cost for each option
|
||||
for (ManaCost manaCost: costs) {
|
||||
if (manaCost instanceof GenericManaCost) {
|
||||
cost.setPayment(manaCost.getPayment());
|
||||
logger.debug("simulating -- X = " + cost.getPayment().count());
|
||||
break;
|
||||
}
|
||||
}
|
||||
cost.setPaid();
|
||||
return true;
|
||||
}*/
|
||||
|
||||
public List<Combat> addAttackers(Game game) {
|
||||
Map<Integer, Combat> engagements = new HashMap<>();
|
||||
//useful only for two player games - will only attack first opponent
|
||||
UUID defenderId = game.getOpponents(playerId).iterator().next();
|
||||
List<Permanent> attackersList = super.getAvailableAttackers(defenderId, game);
|
||||
//use binary digits to calculate powerset of attackers
|
||||
int powerElements = (int) Math.pow(2, attackersList.size());
|
||||
StringBuilder binary = new StringBuilder();
|
||||
for (int i = powerElements - 1; i >= 0; i--) {
|
||||
Game sim = game.copy();
|
||||
binary.setLength(0);
|
||||
binary.append(Integer.toBinaryString(i));
|
||||
while (binary.length() < attackersList.size()) {
|
||||
binary.insert(0, '0');
|
||||
}
|
||||
for (int j = 0; j < attackersList.size(); j++) {
|
||||
if (binary.charAt(j) == '1') {
|
||||
setStoredBookmark(sim.bookmarkState()); // makes it possible to UNDO a declared attacker with costs from e.g. Propaganda
|
||||
if (!sim.getCombat().declareAttacker(attackersList.get(j).getId(), defenderId, playerId, sim)) {
|
||||
sim.undo(playerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (engagements.put(sim.getCombat().getValue().hashCode(), sim.getCombat()) != null) {
|
||||
logger.debug("simulating -- found redundant attack combination");
|
||||
}
|
||||
else if (logger.isDebugEnabled()) {
|
||||
logger.debug("simulating -- attack:" + sim.getCombat().getGroups().size());
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(engagements.values());
|
||||
}
|
||||
|
||||
public List<Combat> addBlockers(Game game) {
|
||||
Map<Integer, Combat> engagements = new HashMap<>();
|
||||
int numGroups = game.getCombat().getGroups().size();
|
||||
if (numGroups == 0) return new ArrayList<>();
|
||||
|
||||
//add a node with no blockers
|
||||
Game sim = game.copy();
|
||||
engagements.put(sim.getCombat().getValue().hashCode(), sim.getCombat());
|
||||
sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARED_BLOCKERS, playerId, playerId));
|
||||
|
||||
List<Permanent> blockers = getAvailableBlockers(game);
|
||||
addBlocker(game, blockers, engagements);
|
||||
|
||||
return new ArrayList<>(engagements.values());
|
||||
}
|
||||
|
||||
protected void addBlocker(Game game, List<Permanent> blockers, Map<Integer, Combat> engagements) {
|
||||
if (blockers.isEmpty())
|
||||
return;
|
||||
int numGroups = game.getCombat().getGroups().size();
|
||||
//try to block each attacker with each potential blocker
|
||||
Permanent blocker = blockers.get(0);
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("simulating -- block:" + blocker);
|
||||
List<Permanent> remaining = remove(blockers, blocker);
|
||||
for (int i = 0; i < numGroups; i++) {
|
||||
if (game.getCombat().getGroups().get(i).canBlock(blocker, game)) {
|
||||
Game sim = game.copy();
|
||||
sim.getCombat().getGroups().get(i).addBlocker(blocker.getId(), playerId, sim);
|
||||
if (engagements.put(sim.getCombat().getValue().hashCode(), sim.getCombat()) != null)
|
||||
logger.debug("simulating -- found redundant block combination");
|
||||
addBlocker(sim, remaining, engagements); // and recurse minus the used blocker
|
||||
}
|
||||
}
|
||||
addBlocker(game, remaining, engagements);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean triggerAbility(TriggeredAbility source, Game game) {
|
||||
Ability ability = source.copy();
|
||||
List<Ability> options = getPlayableOptions(ability, game);
|
||||
if (options.isEmpty()) {
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("simulating -- triggered ability:" + ability);
|
||||
game.getStack().push(new StackAbility(ability, playerId));
|
||||
if (ability.activate(game, false) && ability.isUsesStack()) {
|
||||
game.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId()));
|
||||
}
|
||||
game.applyEffects();
|
||||
game.getPlayers().resetPassed();
|
||||
}
|
||||
else {
|
||||
SimulationNode parent = (SimulationNode) game.getCustomData();
|
||||
if (parent.getDepth() == maxDepth) return true;
|
||||
logger.debug(indent(parent.getDepth()) + "simulating -- triggered ability - adding children:" + options.size());
|
||||
for (Ability option: options) {
|
||||
addAbilityNode(parent, option, game);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void addAbilityNode(SimulationNode parent, Ability ability, Game game) {
|
||||
Game sim = game.copy();
|
||||
sim.getStack().push(new StackAbility(ability, playerId));
|
||||
ability.activate(sim, false);
|
||||
if (ability.activate(sim, false) && ability.isUsesStack()) {
|
||||
game.fireEvent(new GameEvent(GameEvent.EventType.TRIGGERED_ABILITY, ability.getId(), ability, ability.getControllerId()));
|
||||
}
|
||||
sim.applyEffects();
|
||||
SimulationNode newNode = new SimulationNode(parent, sim, playerId);
|
||||
logger.debug(indent(newNode.getDepth()) + "simulating -- node #:" + SimulationNode.getCount() + " triggered ability option");
|
||||
for (Target target: ability.getTargets()) {
|
||||
for (UUID targetId: target.getTargets()) {
|
||||
newNode.getTargets().add(targetId);
|
||||
}
|
||||
}
|
||||
parent.children.add(newNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean priority(Game game) {
|
||||
//should never get here
|
||||
return false;
|
||||
}
|
||||
|
||||
protected String indent(int num) {
|
||||
char[] fill = new char[num];
|
||||
Arrays.fill(fill, ' ');
|
||||
return new String(fill);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
|
||||
|
||||
package mage.player.ai;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.game.Game;
|
||||
import mage.game.combat.Combat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class SimulationNode implements Serializable {
|
||||
|
||||
protected static int nodeCount;
|
||||
|
||||
protected Game game;
|
||||
protected int gameValue;
|
||||
protected List<Ability> abilities;
|
||||
protected int depth;
|
||||
protected List<SimulationNode> children = new ArrayList<>();
|
||||
protected SimulationNode parent;
|
||||
protected List<UUID> targets = new ArrayList<>();
|
||||
protected List<String> choices = new ArrayList<>();
|
||||
protected UUID playerId;
|
||||
protected Combat combat;
|
||||
|
||||
public SimulationNode(SimulationNode parent, Game game, UUID playerId) {
|
||||
this.parent = parent;
|
||||
this.game = game;
|
||||
if (parent == null)
|
||||
this.depth = 1;
|
||||
else
|
||||
this.depth = parent.getDepth() + 1;
|
||||
this.playerId = playerId;
|
||||
game.setCustomData(this);
|
||||
nodeCount++;
|
||||
}
|
||||
|
||||
public SimulationNode(SimulationNode parent, Game game, List<Ability> abilities, UUID playerId) {
|
||||
this(parent, game, playerId);
|
||||
this.abilities = abilities;
|
||||
}
|
||||
|
||||
public SimulationNode(SimulationNode parent, Game game, Ability ability, UUID playerId) {
|
||||
this(parent, game, playerId);
|
||||
this.abilities = new ArrayList<>();
|
||||
abilities.add(ability);
|
||||
}
|
||||
|
||||
public static void resetCount() {
|
||||
nodeCount = 0;
|
||||
}
|
||||
|
||||
public static int getCount() {
|
||||
return nodeCount;
|
||||
}
|
||||
|
||||
public Game getGame() {
|
||||
return this.game;
|
||||
}
|
||||
|
||||
public int getGameValue() {
|
||||
return this.gameValue;
|
||||
}
|
||||
|
||||
public void setGameValue(int value) {
|
||||
this.gameValue = value;
|
||||
}
|
||||
|
||||
public List<Ability> getAbilities() {
|
||||
return this.abilities;
|
||||
}
|
||||
|
||||
public SimulationNode getParent() {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
public List<SimulationNode> getChildren() {
|
||||
return this.children;
|
||||
}
|
||||
|
||||
public int getDepth() {
|
||||
return this.depth;
|
||||
}
|
||||
|
||||
public UUID getPlayerId() {
|
||||
return this.playerId;
|
||||
}
|
||||
|
||||
public Combat getCombat() {
|
||||
return this.combat;
|
||||
}
|
||||
|
||||
public void setCombat(Combat combat) {
|
||||
this.combat = combat;
|
||||
}
|
||||
|
||||
public List<UUID> getTargets() {
|
||||
return this.targets;
|
||||
}
|
||||
|
||||
public List<String> getChoices() {
|
||||
return this.choices;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
|
||||
|
||||
package mage.player.ai;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import mage.abilities.Ability;
|
||||
import mage.game.Game;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
public class SimulationWorker implements Callable {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(SimulationWorker.class);
|
||||
|
||||
private Game game;
|
||||
private SimulatedAction previousActions;
|
||||
private Ability action;
|
||||
private SimulatedPlayer player;
|
||||
|
||||
public SimulationWorker(Game game, SimulatedPlayer player, SimulatedAction previousActions, Ability action) {
|
||||
this.game = game;
|
||||
this.player = player;
|
||||
this.previousActions = previousActions;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call() {
|
||||
try {
|
||||
// player.simulateAction(game, previousActions, action);
|
||||
} catch (Exception ex) {
|
||||
logger.error(null, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -59,6 +59,8 @@ import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_RESET_ALL;
|
|||
import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_RESET_ALL;
|
||||
|
||||
/**
|
||||
* Human: server side logic to exchange game data between server app and another player's app
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||
*/
|
||||
public class HumanPlayer extends PlayerImpl {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@
|
|||
<module>Mage.Game.OathbreakerFreeForAll</module>
|
||||
<module>Mage.Game.TwoPlayerDuel</module>
|
||||
<module>Mage.Player.AI</module>
|
||||
<module>Mage.Player.AIMinimax</module>
|
||||
<module>Mage.Player.AI.MA</module>
|
||||
<module>Mage.Player.AIMCTS</module>
|
||||
<module>Mage.Player.AI.DraftBot</module>
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@
|
|||
/>
|
||||
<playerTypes>
|
||||
<playerType name="Human" jar="mage-player-human.jar" className="mage.player.human.HumanPlayer"/>
|
||||
<!--<playerType name="Computer - minimax" jar="mage-player-aiminimax.jar" className="mage.player.ai.ComputerPlayer3"/>-->
|
||||
<playerType name="Computer - mad" jar="mage-player-ai-ma.jar" className="mage.player.ai.ComputerPlayer7"/>
|
||||
<playerType name="Computer - monte carlo" jar="mage-player-aimcts.jar" className="mage.player.ai.ComputerPlayerMCTS"/>
|
||||
<playerType name="Computer - draftbot" jar="mage-player-ai-draft-bot.jar" className="mage.player.ai.ComputerDraftPlayer"/>
|
||||
|
|
|
|||
|
|
@ -89,12 +89,6 @@
|
|||
<version>${project.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>mage-player-aiminimax</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>mage-player-ai-ma</artifactId>
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
maxDepth=10
|
||||
maxNodes=5000
|
||||
evaluatorLifeFactor=2
|
||||
evaluatorPermanentFactor=1
|
||||
evaluatorCreatureFactor=1
|
||||
evaluatorHandFactor=1
|
||||
maxThinkSeconds=30
|
||||
|
|
@ -23,7 +23,6 @@
|
|||
<include>org.mage:mage-game-commanderduel</include>
|
||||
<include>org.mage:mage-game-freeforall</include>
|
||||
<include>org.mage:mage-game-twoplayerduel</include>
|
||||
<include>org.mage:mage-player-aiminimax</include>
|
||||
<include>org.mage:mage-player-ai-ma</include>
|
||||
<include>org.mage:mage-player-human</include>
|
||||
<include>org.mage:mage-tournament-boosterdraft</include>
|
||||
|
|
@ -40,7 +39,6 @@
|
|||
<exclude>org.mage:mage-game-commanderduel</exclude>
|
||||
<exclude>org.mage:mage-game-freeforall</exclude>
|
||||
<exclude>org.mage:mage-game-twoplayerduel</exclude>
|
||||
<exclude>org.mage:mage-player-aiminimax</exclude>
|
||||
<exclude>org.mage:mage-player-ai-ma</exclude>
|
||||
<exclude>org.mage:mage-player-human</exclude>
|
||||
<exclude>org.mage:mage-tournament-boosterdraft</exclude>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public class MulliganTestBase {
|
|||
private final int freeMulligans;
|
||||
private final List<Step> steps = new ArrayList<>();
|
||||
|
||||
private PlayerProxy player1;
|
||||
private MulliganStubPlayer player1;
|
||||
|
||||
public MulliganScenarioTest(MulliganType mulliganType, int freeMulligans) {
|
||||
this.mulliganType = mulliganType;
|
||||
|
|
@ -73,13 +73,13 @@ public class MulliganTestBase {
|
|||
options.skipInitShuffling = true;
|
||||
game.setGameOptions(options);
|
||||
|
||||
this.player1 = new PlayerProxy("p1", ONE);
|
||||
this.player1 = new MulliganStubPlayer("p1", ONE);
|
||||
player1.setSteps(steps);
|
||||
Deck deck1 = generateDeck(player1.getId(), 40);
|
||||
game.loadCards(deck1.getCards(), player1.getId());
|
||||
game.addPlayer(player1, deck1);
|
||||
|
||||
PlayerProxy player2 = new PlayerProxy("p2", ONE);
|
||||
MulliganStubPlayer player2 = new MulliganStubPlayer("p2", ONE);
|
||||
Deck deck2 = generateDeck(player2.getId(), 40);
|
||||
game.loadCards(deck2.getCards(), player2.getId());
|
||||
game.addPlayer(player2, deck2);
|
||||
|
|
@ -164,12 +164,12 @@ public class MulliganTestBase {
|
|||
List<UUID> discardBottom(int count);
|
||||
}
|
||||
|
||||
static class PlayerProxy extends StubPlayer {
|
||||
static class MulliganStubPlayer extends StubPlayer {
|
||||
|
||||
private List<Step> steps = null;
|
||||
private int current = 0;
|
||||
|
||||
public PlayerProxy(String name, RangeOfInfluence range) {
|
||||
public MulliganStubPlayer(String name, RangeOfInfluence range) {
|
||||
super(name, range);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@ import mage.target.Target;
|
|||
import mage.target.TargetCard;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
|
||||
/**
|
||||
* AI: helper class for tests
|
||||
* <p>
|
||||
* Mock class to inject test player support in the inner choice calls, e.g. in PlayerImpl. If you
|
||||
* want to set up inner choices then override it here.
|
||||
* <p>
|
||||
|
|
@ -29,9 +27,11 @@ import mage.target.TargetCard;
|
|||
* <p>
|
||||
* If you implement set up of random results for tests (die roll, flip coin, etc) and want to support AI tests
|
||||
* (same random results in simulated games) then override same methods in SimulatedPlayer2 too
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
|
||||
public class TestComputerPlayer extends ComputerPlayer {
|
||||
public final class TestComputerPlayer extends ComputerPlayer {
|
||||
|
||||
private TestPlayer testPlayerLink;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ import mage.target.Target;
|
|||
import mage.target.TargetCard;
|
||||
|
||||
/**
|
||||
* AI: helper class for tests
|
||||
* <p>
|
||||
* Copied-pasted methods from TestComputerPlayer, see docs in there
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
|
||||
public class TestComputerPlayer7 extends ComputerPlayer7 {
|
||||
public final class TestComputerPlayer7 extends ComputerPlayer7 {
|
||||
|
||||
private TestPlayer testPlayerLink;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ import mage.target.Target;
|
|||
import mage.target.TargetCard;
|
||||
|
||||
/**
|
||||
* AI: helper class for tests
|
||||
* <p>
|
||||
* Copied-pasted methods from TestComputerPlayer, see docs in there
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
|
||||
public class TestComputerPlayerMonteCarlo extends ComputerPlayerMCTS {
|
||||
public final class TestComputerPlayerMonteCarlo extends ComputerPlayerMCTS {
|
||||
|
||||
private TestPlayer testPlayerLink;
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ import java.util.regex.Pattern;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Basic implementation of testable player
|
||||
*
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
* @author Simown
|
||||
* @author JayDi85
|
||||
|
|
|
|||
|
|
@ -1,95 +0,0 @@
|
|||
package org.mage.test.serverside;
|
||||
|
||||
import mage.cards.Card;
|
||||
import mage.cards.Sets;
|
||||
import mage.cards.decks.Deck;
|
||||
import mage.constants.ColoredManaSymbol;
|
||||
import mage.constants.MultiplayerAttackOption;
|
||||
import mage.constants.RangeOfInfluence;
|
||||
import mage.game.Game;
|
||||
import mage.game.GameException;
|
||||
import mage.game.GameOptions;
|
||||
import mage.game.TwoPlayerDuel;
|
||||
import mage.game.mulligan.MulliganType;
|
||||
import mage.player.ai.ComputerPlayer;
|
||||
import mage.players.Player;
|
||||
import mage.players.PlayerType;
|
||||
import mage.util.RandomUtil;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.MageTestBase;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author ayratn
|
||||
*/
|
||||
public class PlayGameTest extends MageTestBase {
|
||||
|
||||
private static final List<String> colorChoices = new ArrayList<>(Arrays.asList("bu", "bg", "br", "bw", "ug", "ur", "uw", "gr", "gw", "rw", "bur", "buw", "bug", "brg", "brw", "bgw", "wur", "wug", "wrg", "rgu"));
|
||||
private static final int DECK_SIZE = 40;
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void playOneGame() throws GameException, FileNotFoundException, IllegalArgumentException {
|
||||
Game game = new TwoPlayerDuel(MultiplayerAttackOption.LEFT, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 60, 20, 7);
|
||||
|
||||
Player computerA = createPlayer("ComputerA", PlayerType.COMPUTER_MINIMAX_HYBRID);
|
||||
// Player playerA = createPlayer("ComputerA", "Computer - mad");
|
||||
// Deck deck = Deck.load(Sets.loadDeck("RB Aggro.dck"));
|
||||
Deck deck = generateRandomDeck();
|
||||
|
||||
if (deck.getMaindeckCards().size() < DECK_SIZE) {
|
||||
throw new IllegalArgumentException("Couldn't load deck, deck size = " + deck.getMaindeckCards().size() + ", but must be " + DECK_SIZE);
|
||||
}
|
||||
game.addPlayer(computerA, deck);
|
||||
game.loadCards(deck.getCards(), computerA.getId());
|
||||
|
||||
Player computerB = createPlayer("ComputerB", PlayerType.COMPUTER_MINIMAX_HYBRID);
|
||||
// Player playerB = createPlayer("ComputerB", "Computer - mad");
|
||||
// Deck deck2 = Deck.load(Sets.loadDeck("RB Aggro.dck"));
|
||||
Deck deck2 = generateRandomDeck();
|
||||
if (deck2.getMaindeckCards().size() < DECK_SIZE) {
|
||||
throw new IllegalArgumentException("Couldn't load deck, deck size = " + deck2.getMaindeckCards().size() + ", but must be " + DECK_SIZE);
|
||||
}
|
||||
game.addPlayer(computerB, deck2);
|
||||
game.loadCards(deck2.getCards(), computerB.getId());
|
||||
|
||||
// parseScenario("scenario1.txt");
|
||||
// game.cheat(playerA.getId(), commandsA);
|
||||
// game.cheat(playerA.getId(), libraryCardsA, handCardsA, battlefieldCardsA, graveyardCardsA);
|
||||
// game.cheat(playerB.getId(), commandsB);
|
||||
// game.cheat(playerB.getId(), libraryCardsB, handCardsB, battlefieldCardsB, graveyardCardsB);
|
||||
//boolean testMode = false;
|
||||
boolean testMode = true;
|
||||
|
||||
long t1 = System.nanoTime();
|
||||
GameOptions options = new GameOptions();
|
||||
options.testMode = true;
|
||||
game.setGameOptions(options);
|
||||
game.start(computerA.getId());
|
||||
long t2 = System.nanoTime();
|
||||
|
||||
logger.info("Winner: " + game.getWinner());
|
||||
logger.info("Time: " + (t2 - t1) / 1000000 + " ms");
|
||||
/*if (!game.getWinner().equals("Player ComputerA is the winner")) {
|
||||
throw new RuntimeException("Lost :(");
|
||||
}*/
|
||||
}
|
||||
|
||||
private Deck generateRandomDeck() {
|
||||
String selectedColors = colorChoices.get(RandomUtil.nextInt(colorChoices.size())).toUpperCase(Locale.ENGLISH);
|
||||
List<ColoredManaSymbol> allowedColors = new ArrayList<>();
|
||||
logger.info("Building deck with colors: " + selectedColors);
|
||||
for (int i = 0; i < selectedColors.length(); i++) {
|
||||
char c = selectedColors.charAt(i);
|
||||
allowedColors.add(ColoredManaSymbol.lookup(c));
|
||||
}
|
||||
List<Card> cardPool = Sets.generateRandomCardPool(45, allowedColors);
|
||||
return ComputerPlayer.buildDeck(DECK_SIZE, cardPool, allowedColors);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
package org.mage.test.serverside.tournament;
|
||||
|
||||
import mage.constants.RangeOfInfluence;
|
||||
import mage.game.tournament.Round;
|
||||
import mage.game.tournament.TournamentPairing;
|
||||
import mage.game.tournament.TournamentPlayer;
|
||||
import mage.game.tournament.pairing.RoundPairings;
|
||||
import mage.game.tournament.pairing.SwissPairingMinimalWeightMatching;
|
||||
import mage.players.Player;
|
||||
import mage.players.StubPlayer;
|
||||
import mage.util.RandomUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.stub.PlayerStub;
|
||||
import org.mage.test.stub.TournamentStub;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -21,15 +23,19 @@ import java.util.Set;
|
|||
*/
|
||||
public class SwissPairingMinimalWeightMatchingTest {
|
||||
|
||||
private Player createTourneyPlayer(int number) {
|
||||
return new StubPlayer("Tourney player " + number, RangeOfInfluence.ALL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void FourPlayersSecondRoundTest() {
|
||||
// 1 > 3
|
||||
// 2 > 4
|
||||
|
||||
TournamentPlayer player1 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player2 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player3 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player4 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player1 = new TournamentPlayer(createTourneyPlayer(1), null);
|
||||
TournamentPlayer player2 = new TournamentPlayer(createTourneyPlayer(2), null);
|
||||
TournamentPlayer player3 = new TournamentPlayer(createTourneyPlayer(3), null);
|
||||
TournamentPlayer player4 = new TournamentPlayer(createTourneyPlayer(3), null);
|
||||
List<TournamentPlayer> players = new ArrayList<>();
|
||||
players.add(player4);
|
||||
players.add(player2);
|
||||
|
|
@ -68,10 +74,10 @@ public class SwissPairingMinimalWeightMatchingTest {
|
|||
// 1 > 2
|
||||
// 3 > 4
|
||||
|
||||
TournamentPlayer player3 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player2 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player4 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player1 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player3 = new TournamentPlayer(createTourneyPlayer(3), null);
|
||||
TournamentPlayer player2 = new TournamentPlayer(createTourneyPlayer(2), null);
|
||||
TournamentPlayer player4 = new TournamentPlayer(createTourneyPlayer(4), null);
|
||||
TournamentPlayer player1 = new TournamentPlayer(createTourneyPlayer(1), null);
|
||||
List<TournamentPlayer> players = new ArrayList<>();
|
||||
players.add(player4);
|
||||
players.add(player2);
|
||||
|
|
@ -116,10 +122,10 @@ public class SwissPairingMinimalWeightMatchingTest {
|
|||
// 2 > 4
|
||||
// 4 left the tournament
|
||||
|
||||
TournamentPlayer player1 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player2 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player3 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player4 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player1 = new TournamentPlayer(createTourneyPlayer(1), null);
|
||||
TournamentPlayer player2 = new TournamentPlayer(createTourneyPlayer(2), null);
|
||||
TournamentPlayer player3 = new TournamentPlayer(createTourneyPlayer(3), null);
|
||||
TournamentPlayer player4 = new TournamentPlayer(createTourneyPlayer(4), null);
|
||||
List<TournamentPlayer> players = new ArrayList<>();
|
||||
//players.add(player4); -- player 4 is not active
|
||||
players.add(player2);
|
||||
|
|
@ -159,11 +165,11 @@ public class SwissPairingMinimalWeightMatchingTest {
|
|||
// 2 > 3
|
||||
// 4
|
||||
|
||||
TournamentPlayer player1 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player2 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player3 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player4 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player5 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player1 = new TournamentPlayer(createTourneyPlayer(1), null);
|
||||
TournamentPlayer player2 = new TournamentPlayer(createTourneyPlayer(2), null);
|
||||
TournamentPlayer player3 = new TournamentPlayer(createTourneyPlayer(3), null);
|
||||
TournamentPlayer player4 = new TournamentPlayer(createTourneyPlayer(4), null);
|
||||
TournamentPlayer player5 = new TournamentPlayer(createTourneyPlayer(5), null);
|
||||
List<TournamentPlayer> players = new ArrayList<>();
|
||||
players.add(player4);
|
||||
players.add(player2);
|
||||
|
|
@ -214,11 +220,11 @@ public class SwissPairingMinimalWeightMatchingTest {
|
|||
// 5 left the tournament
|
||||
|
||||
|
||||
TournamentPlayer player1 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player2 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player3 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player4 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player5 = new TournamentPlayer(new PlayerStub(), null);
|
||||
TournamentPlayer player1 = new TournamentPlayer(createTourneyPlayer(1), null);
|
||||
TournamentPlayer player2 = new TournamentPlayer(createTourneyPlayer(2), null);
|
||||
TournamentPlayer player3 = new TournamentPlayer(createTourneyPlayer(3), null);
|
||||
TournamentPlayer player4 = new TournamentPlayer(createTourneyPlayer(4), null);
|
||||
TournamentPlayer player5 = new TournamentPlayer(createTourneyPlayer(5), null);
|
||||
List<TournamentPlayer> players = new ArrayList<>();
|
||||
//players.add(player5); -- player 5 is not active
|
||||
players.add(player4);
|
||||
|
|
@ -268,7 +274,7 @@ public class SwissPairingMinimalWeightMatchingTest {
|
|||
|
||||
List<TournamentPlayer> players = new ArrayList<>();
|
||||
for (int i = 0; i < playersCount; i++) {
|
||||
players.add(new TournamentPlayer(new PlayerStub(), null));
|
||||
players.add(new TournamentPlayer(createTourneyPlayer(i + 1), null));
|
||||
}
|
||||
|
||||
List<TournamentPairing> playedPairs = new ArrayList<>();
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -199,7 +199,7 @@ public abstract class MatchImpl implements Match {
|
|||
matchPlayer.getPlayer().init(game);
|
||||
game.loadCards(matchPlayer.getDeck().getCards(), matchPlayer.getPlayer().getId());
|
||||
game.loadCards(matchPlayer.getDeck().getSideboard(), matchPlayer.getPlayer().getId());
|
||||
game.addPlayer(matchPlayer.getPlayer(), matchPlayer.getDeck());
|
||||
game.addPlayer(matchPlayer.getPlayer(), matchPlayer.getDeck()); // TODO: keeps old player?!
|
||||
// time limits
|
||||
matchPlayer.getPlayer().setBufferTimeLeft(options.getMatchBufferTime().getBufferSecs());
|
||||
if (games.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
package mage.players;
|
||||
|
||||
/**
|
||||
* Created by IGOUDT on 2-4-2017.
|
||||
* Server: all possible player types
|
||||
* <p>
|
||||
* Warning, do not change description - it must be same with config.xml file
|
||||
*
|
||||
* @author IGOUDT
|
||||
*/
|
||||
public enum PlayerType {
|
||||
HUMAN("Human"),
|
||||
COMPUTER_DRAFT_BOT("Computer - draftbot"),
|
||||
COMPUTER_MINIMAX_HYBRID("Computer - minimax hybrid"),
|
||||
COMPUTER_MONTE_CARLO("Computer - monte carlo"),
|
||||
COMPUTER_MAD("Computer - mad");
|
||||
|
||||
String description;
|
||||
final String description;
|
||||
|
||||
PlayerType(String description) {
|
||||
this.description = description;
|
||||
|
|
@ -27,6 +30,6 @@ public enum PlayerType {
|
|||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(String.format("PlayerType (%s) is not configured", description));
|
||||
throw new IllegalArgumentException(String.format("PlayerType (%s) is not configured in server's config.xml", description));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,10 @@ import java.util.UUID;
|
|||
|
||||
import static com.google.common.collect.Iterables.getOnlyElement;
|
||||
|
||||
public class StubPlayer extends PlayerImpl implements Player {
|
||||
/**
|
||||
* Empty player, do nothing, used for tests only
|
||||
*/
|
||||
public class StubPlayer extends PlayerImpl {
|
||||
|
||||
@Override
|
||||
public boolean choose(Outcome outcome, Target target, Ability source, Game game) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue