AI: removed outdated AIMinimax project (#7075), removed some useless player classes, code and config files, improved docs;

This commit is contained in:
Oleg Agafonov 2024-01-15 03:14:42 +04:00
parent 6ac2f44cc1
commit 08b99fcbf7
40 changed files with 103 additions and 2462 deletions

View file

@ -1,7 +0,0 @@
maxDepth=10
maxNodes=5000
evaluatorLifeFactor=2
evaluatorPermanentFactor=1
evaluatorCreatureFactor=1
evaluatorHandFactor=1
maxThinkSeconds=30

View file

@ -12,7 +12,7 @@
<artifactId>mage-player-ai-draftbot</artifactId> <artifactId>mage-player-ai-draftbot</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Mage Player AI.DraftBot</name> <name>Mage Player AI (draft bot)</name>
<dependencies> <dependencies>
<dependency> <dependency>

View file

@ -1,18 +1,18 @@
package mage.player.ai; package mage.player.ai;
import mage.constants.RangeOfInfluence; import mage.constants.RangeOfInfluence;
import mage.game.Game; import mage.game.Game;
import mage.game.Table; import mage.game.Table;
import mage.game.tournament.TournamentType; 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 * @author nantuko
*/ */
public class ComputerDraftPlayer extends ComputerPlayer implements Player { public class ComputerDraftPlayer extends ComputerPlayer {
public ComputerDraftPlayer(String name, RangeOfInfluence range) { public ComputerDraftPlayer(String name, RangeOfInfluence range) {
super(name, range); super(name, range);
@ -46,7 +46,7 @@ public class ComputerDraftPlayer extends ComputerPlayer implements Player {
public boolean canJoinTable(Table table) { public boolean canJoinTable(Table table) {
if (table.isTournament()) { if (table.isTournament()) {
TournamentType tournamentType = table.getTournament().getTournamentType(); TournamentType tournamentType = table.getTournament().getTournamentType();
if(tournamentType != null && tournamentType.isDraft()) { if (tournamentType != null && tournamentType.isDraft()) {
return true; return true;
} }
} }

View file

@ -1,7 +0,0 @@
maxDepth=10
maxNodes=5000
evaluatorLifeFactor=2
evaluatorPermanentFactor=1
evaluatorCreatureFactor=1
evaluatorHandFactor=1
maxThinkSeconds=10

View file

@ -12,7 +12,7 @@
<artifactId>mage-player-ai-ma</artifactId> <artifactId>mage-player-ai-ma</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Mage Player AI.MA</name> <name>Mage Player AI (mad bot)</name>
<dependencies> <dependencies>
<dependency> <dependency>

View file

@ -41,12 +41,18 @@ import java.util.concurrent.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
* AI: server side bot with game simulations (mad bot, part of implementation)
*
* @author nantuko * @author nantuko
*/ */
public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ { public class ComputerPlayer6 extends ComputerPlayer {
private static final Logger logger = Logger.getLogger(ComputerPlayer6.class); 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 // same params as Executors.newFixedThreadPool
// no needs erorrs check in afterExecute here cause that pool used for FutureTask with result check already // no needs erorrs check in afterExecute here cause that pool used for FutureTask with result check already
private static final ExecutorService threadPoolSimulations = new ThreadPoolExecutor( private static final ExecutorService threadPoolSimulations = new ThreadPoolExecutor(
@ -97,7 +103,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
maxDepth = skill; maxDepth = skill;
} }
maxThink = skill * 3; maxThink = skill * 3;
maxNodes = Config2.maxNodes; maxNodes = MAX_SIMULATED_NODES_PER_CALC;
getSuggestedActions(); getSuggestedActions();
this.actionCache = new HashSet<>(); this.actionCache = new HashSet<>();
} }

View file

@ -9,6 +9,8 @@ import java.util.Date;
import java.util.LinkedList; import java.util.LinkedList;
/** /**
* AI: server side bot with game simulations (mad bot, the latest version)
*
* @author ayratn * @author ayratn
*/ */
public class ComputerPlayer7 extends ComputerPlayer6 { public class ComputerPlayer7 extends ComputerPlayer6 {

View file

@ -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"));
}
}

View file

@ -27,11 +27,11 @@ import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue; 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 * @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); private static final Logger logger = Logger.getLogger(SimulatedPlayer2.class);

View file

@ -12,7 +12,7 @@
<artifactId>mage-player-ai</artifactId> <artifactId>mage-player-ai</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Mage Player AI</name> <name>Mage Player AI (basic)</name>
<dependencies> <dependencies>
<dependency> <dependency>

View file

@ -59,11 +59,11 @@ import java.util.*;
import java.util.Map.Entry; 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 * @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 static final Logger log = Logger.getLogger(ComputerPlayer.class);
private long lastThinkTime = 0; // msecs for last AI actions calc private long lastThinkTime = 0; // msecs for last AI actions calc

View file

@ -25,7 +25,7 @@ import java.util.concurrent.TimeUnit;
/** /**
* @author BetaSteward_at_googlemail.com * @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_MIN_RATIO = 40;
private static final int THINK_MAX_RATIO = 100; private static final int THINK_MAX_RATIO = 100;

View file

@ -1,4 +1,3 @@
package mage.player.ai; package mage.player.ai;
import mage.abilities.Ability; import mage.abilities.Ability;
@ -15,6 +14,10 @@ import java.util.List;
import java.util.UUID; 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 * @author BetaSteward_at_googlemail.com
*/ */
@ -51,22 +54,19 @@ public class MCTSPlayer extends ComputerPlayer {
public List<Ability> getPlayableOptions(Game game) { public List<Ability> getPlayableOptions(Game game) {
List<Ability> all = new ArrayList<>(); List<Ability> all = new ArrayList<>();
List<ActivatedAbility> playables = getPlayableAbilities(game); List<ActivatedAbility> playables = getPlayableAbilities(game);
for (ActivatedAbility ability: playables) { for (ActivatedAbility ability : playables) {
List<Ability> options = game.getPlayer(playerId).getPlayableOptions(ability, game); List<Ability> options = game.getPlayer(playerId).getPlayableOptions(ability, game);
if (options.isEmpty()) { if (options.isEmpty()) {
if (!ability.getManaCosts().getVariableCosts().isEmpty()) { if (!ability.getManaCosts().getVariableCosts().isEmpty()) {
simulateVariableCosts(ability, all, game); simulateVariableCosts(ability, all, game);
} } else {
else {
all.add(ability); all.add(ability);
} }
} } else {
else { for (Ability option : options) {
for (Ability option: options) {
if (!ability.getManaCosts().getVariableCosts().isEmpty()) { if (!ability.getManaCosts().getVariableCosts().isEmpty()) {
simulateVariableCosts(option, all, game); simulateVariableCosts(option, all, game);
} } else {
else {
all.add(option); all.add(option);
} }
} }
@ -137,7 +137,7 @@ public class MCTSPlayer extends ComputerPlayer {
private List<List<UUID>> copyEngagement(List<List<UUID>> engagement) { private List<List<UUID>> copyEngagement(List<List<UUID>> engagement) {
List<List<UUID>> newEngagement = new ArrayList<>(); List<List<UUID>> newEngagement = new ArrayList<>();
for (List<UUID> group: engagement) { for (List<UUID> group : engagement) {
newEngagement.add(new ArrayList<>(group)); newEngagement.add(new ArrayList<>(group));
} }
return newEngagement; return newEngagement;
@ -154,7 +154,7 @@ public class MCTSPlayer extends ComputerPlayer {
List<Permanent> remaining = remove(blockers, blocker); List<Permanent> remaining = remove(blockers, blocker);
for (int i = 0; i < numGroups; i++) { for (int i = 0; i < numGroups; i++) {
if (game.getCombat().getGroups().get(i).canBlock(blocker, game)) { 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()); newEngagement.get(i).add(blocker.getId());
engagements.add(newEngagement); engagements.add(newEngagement);
// logger.debug("simulating -- found redundant block combination"); // logger.debug("simulating -- found redundant block combination");

View file

@ -24,11 +24,13 @@ import java.io.Serializable;
import java.util.*; 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 * @author BetaSteward_at_googlemail.com
*/ */
public class SimulatedPlayerMCTS extends MCTSPlayer { public final class SimulatedPlayerMCTS extends MCTSPlayer {
private boolean isSimulatedPlayer; private boolean isSimulatedPlayer;
private int actionCount = 0; private int actionCount = 0;
@ -98,28 +100,13 @@ public class SimulatedPlayerMCTS extends MCTSPlayer {
ability.addManaCostsToPay(new GenericManaCost(RandomUtil.nextInt(amount))); 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; break;
// }
} }
return ability; return ability;
} }
@Override @Override
public boolean triggerAbility(TriggeredAbility source, Game game) { public boolean triggerAbility(TriggeredAbility source, Game game) {
// logger.info("trigger");
if (source != null && source.canChooseTarget(game, playerId)) { if (source != null && source.canChooseTarget(game, playerId)) {
Ability ability; Ability ability;
List<Ability> options = getPlayableOptions(source, game); List<Ability> options = getPlayableOptions(source, game);
@ -153,7 +140,6 @@ public class SimulatedPlayerMCTS extends MCTSPlayer {
@Override @Override
public void selectAttackers(Game game, UUID attackingPlayerId) { public void selectAttackers(Game game, UUID attackingPlayerId) {
//useful only for two player games - will only attack first opponent //useful only for two player games - will only attack first opponent
// logger.info("select attackers");
UUID defenderId = game.getOpponents(playerId).iterator().next(); UUID defenderId = game.getOpponents(playerId).iterator().next();
List<Permanent> attackersList = super.getAvailableAttackers(defenderId, game); List<Permanent> attackersList = super.getAvailableAttackers(defenderId, game);
//use binary digits to calculate powerset of attackers //use binary digits to calculate powerset of attackers
@ -177,7 +163,6 @@ public class SimulatedPlayerMCTS extends MCTSPlayer {
@Override @Override
public void selectBlockers(Ability source, Game game, UUID defendingPlayerId) { public void selectBlockers(Ability source, Game game, UUID defendingPlayerId) {
// logger.info("select blockers");
int numGroups = game.getCombat().getGroups().size(); int numGroups = game.getCombat().getGroups().size();
if (numGroups == 0) { if (numGroups == 0) {
return; return;

View file

@ -1,7 +0,0 @@
maxDepth=10
maxNodes=5000
evaluatorLifeFactor=2
evaluatorPermanentFactor=1
evaluatorCreatureFactor=1
evaluatorHandFactor=1
maxThinkSeconds=30

View file

@ -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>

View file

@ -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;
}
}

View file

@ -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"));
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -59,6 +59,8 @@ import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_RESET_ALL;
import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_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 * @author BetaSteward_at_googlemail.com, JayDi85
*/ */
public class HumanPlayer extends PlayerImpl { public class HumanPlayer extends PlayerImpl {

View file

@ -36,7 +36,6 @@
<module>Mage.Game.OathbreakerFreeForAll</module> <module>Mage.Game.OathbreakerFreeForAll</module>
<module>Mage.Game.TwoPlayerDuel</module> <module>Mage.Game.TwoPlayerDuel</module>
<module>Mage.Player.AI</module> <module>Mage.Player.AI</module>
<module>Mage.Player.AIMinimax</module>
<module>Mage.Player.AI.MA</module> <module>Mage.Player.AI.MA</module>
<module>Mage.Player.AIMCTS</module> <module>Mage.Player.AIMCTS</module>
<module>Mage.Player.AI.DraftBot</module> <module>Mage.Player.AI.DraftBot</module>

View file

@ -66,7 +66,6 @@
/> />
<playerTypes> <playerTypes>
<playerType name="Human" jar="mage-player-human.jar" className="mage.player.human.HumanPlayer"/> <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 - 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 - 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"/> <playerType name="Computer - draftbot" jar="mage-player-ai-draft-bot.jar" className="mage.player.ai.ComputerDraftPlayer"/>

View file

@ -89,12 +89,6 @@
<version>${project.version}</version> <version>${project.version}</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mage-player-aiminimax</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency> <dependency>
<groupId>${project.groupId}</groupId> <groupId>${project.groupId}</groupId>
<artifactId>mage-player-ai-ma</artifactId> <artifactId>mage-player-ai-ma</artifactId>

View file

@ -1,7 +0,0 @@
maxDepth=10
maxNodes=5000
evaluatorLifeFactor=2
evaluatorPermanentFactor=1
evaluatorCreatureFactor=1
evaluatorHandFactor=1
maxThinkSeconds=30

View file

@ -23,7 +23,6 @@
<include>org.mage:mage-game-commanderduel</include> <include>org.mage:mage-game-commanderduel</include>
<include>org.mage:mage-game-freeforall</include> <include>org.mage:mage-game-freeforall</include>
<include>org.mage:mage-game-twoplayerduel</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-ai-ma</include>
<include>org.mage:mage-player-human</include> <include>org.mage:mage-player-human</include>
<include>org.mage:mage-tournament-boosterdraft</include> <include>org.mage:mage-tournament-boosterdraft</include>
@ -40,7 +39,6 @@
<exclude>org.mage:mage-game-commanderduel</exclude> <exclude>org.mage:mage-game-commanderduel</exclude>
<exclude>org.mage:mage-game-freeforall</exclude> <exclude>org.mage:mage-game-freeforall</exclude>
<exclude>org.mage:mage-game-twoplayerduel</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-ai-ma</exclude>
<exclude>org.mage:mage-player-human</exclude> <exclude>org.mage:mage-player-human</exclude>
<exclude>org.mage:mage-tournament-boosterdraft</exclude> <exclude>org.mage:mage-tournament-boosterdraft</exclude>

View file

@ -38,7 +38,7 @@ public class MulliganTestBase {
private final int freeMulligans; private final int freeMulligans;
private final List<Step> steps = new ArrayList<>(); private final List<Step> steps = new ArrayList<>();
private PlayerProxy player1; private MulliganStubPlayer player1;
public MulliganScenarioTest(MulliganType mulliganType, int freeMulligans) { public MulliganScenarioTest(MulliganType mulliganType, int freeMulligans) {
this.mulliganType = mulliganType; this.mulliganType = mulliganType;
@ -73,13 +73,13 @@ public class MulliganTestBase {
options.skipInitShuffling = true; options.skipInitShuffling = true;
game.setGameOptions(options); game.setGameOptions(options);
this.player1 = new PlayerProxy("p1", ONE); this.player1 = new MulliganStubPlayer("p1", ONE);
player1.setSteps(steps); player1.setSteps(steps);
Deck deck1 = generateDeck(player1.getId(), 40); Deck deck1 = generateDeck(player1.getId(), 40);
game.loadCards(deck1.getCards(), player1.getId()); game.loadCards(deck1.getCards(), player1.getId());
game.addPlayer(player1, deck1); game.addPlayer(player1, deck1);
PlayerProxy player2 = new PlayerProxy("p2", ONE); MulliganStubPlayer player2 = new MulliganStubPlayer("p2", ONE);
Deck deck2 = generateDeck(player2.getId(), 40); Deck deck2 = generateDeck(player2.getId(), 40);
game.loadCards(deck2.getCards(), player2.getId()); game.loadCards(deck2.getCards(), player2.getId());
game.addPlayer(player2, deck2); game.addPlayer(player2, deck2);
@ -164,12 +164,12 @@ public class MulliganTestBase {
List<UUID> discardBottom(int count); List<UUID> discardBottom(int count);
} }
static class PlayerProxy extends StubPlayer { static class MulliganStubPlayer extends StubPlayer {
private List<Step> steps = null; private List<Step> steps = null;
private int current = 0; private int current = 0;
public PlayerProxy(String name, RangeOfInfluence range) { public MulliganStubPlayer(String name, RangeOfInfluence range) {
super(name, range); super(name, range);
} }

View file

@ -11,10 +11,8 @@ import mage.target.Target;
import mage.target.TargetCard; 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 * 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. * want to set up inner choices then override it here.
* <p> * <p>
@ -29,9 +27,11 @@ import mage.target.TargetCard;
* <p> * <p>
* If you implement set up of random results for tests (die roll, flip coin, etc) and want to support AI tests * 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 * (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; private TestPlayer testPlayerLink;

View file

@ -11,12 +11,14 @@ import mage.target.Target;
import mage.target.TargetCard; import mage.target.TargetCard;
/** /**
* AI: helper class for tests
* <p>
* Copied-pasted methods from TestComputerPlayer, see docs in there * Copied-pasted methods from TestComputerPlayer, see docs in there
* *
* @author JayDi85 * @author JayDi85
*/ */
public class TestComputerPlayer7 extends ComputerPlayer7 { public final class TestComputerPlayer7 extends ComputerPlayer7 {
private TestPlayer testPlayerLink; private TestPlayer testPlayerLink;

View file

@ -11,12 +11,14 @@ import mage.target.Target;
import mage.target.TargetCard; import mage.target.TargetCard;
/** /**
* AI: helper class for tests
* <p>
* Copied-pasted methods from TestComputerPlayer, see docs in there * Copied-pasted methods from TestComputerPlayer, see docs in there
* *
* @author JayDi85 * @author JayDi85
*/ */
public class TestComputerPlayerMonteCarlo extends ComputerPlayerMCTS { public final class TestComputerPlayerMonteCarlo extends ComputerPlayerMCTS {
private TestPlayer testPlayerLink; private TestPlayer testPlayerLink;

View file

@ -63,6 +63,8 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
* Basic implementation of testable player
*
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
* @author Simown * @author Simown
* @author JayDi85 * @author JayDi85

View file

@ -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);
}
}

View file

@ -1,14 +1,16 @@
package org.mage.test.serverside.tournament; package org.mage.test.serverside.tournament;
import mage.constants.RangeOfInfluence;
import mage.game.tournament.Round; import mage.game.tournament.Round;
import mage.game.tournament.TournamentPairing; import mage.game.tournament.TournamentPairing;
import mage.game.tournament.TournamentPlayer; import mage.game.tournament.TournamentPlayer;
import mage.game.tournament.pairing.RoundPairings; import mage.game.tournament.pairing.RoundPairings;
import mage.game.tournament.pairing.SwissPairingMinimalWeightMatching; import mage.game.tournament.pairing.SwissPairingMinimalWeightMatching;
import mage.players.Player;
import mage.players.StubPlayer;
import mage.util.RandomUtil; import mage.util.RandomUtil;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.mage.test.stub.PlayerStub;
import org.mage.test.stub.TournamentStub; import org.mage.test.stub.TournamentStub;
import java.util.ArrayList; import java.util.ArrayList;
@ -21,15 +23,19 @@ import java.util.Set;
*/ */
public class SwissPairingMinimalWeightMatchingTest { public class SwissPairingMinimalWeightMatchingTest {
private Player createTourneyPlayer(int number) {
return new StubPlayer("Tourney player " + number, RangeOfInfluence.ALL);
}
@Test @Test
public void FourPlayersSecondRoundTest() { public void FourPlayersSecondRoundTest() {
// 1 > 3 // 1 > 3
// 2 > 4 // 2 > 4
TournamentPlayer player1 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player1 = new TournamentPlayer(createTourneyPlayer(1), null);
TournamentPlayer player2 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player2 = new TournamentPlayer(createTourneyPlayer(2), null);
TournamentPlayer player3 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player3 = new TournamentPlayer(createTourneyPlayer(3), null);
TournamentPlayer player4 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player4 = new TournamentPlayer(createTourneyPlayer(3), null);
List<TournamentPlayer> players = new ArrayList<>(); List<TournamentPlayer> players = new ArrayList<>();
players.add(player4); players.add(player4);
players.add(player2); players.add(player2);
@ -68,10 +74,10 @@ public class SwissPairingMinimalWeightMatchingTest {
// 1 > 2 // 1 > 2
// 3 > 4 // 3 > 4
TournamentPlayer player3 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player3 = new TournamentPlayer(createTourneyPlayer(3), null);
TournamentPlayer player2 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player2 = new TournamentPlayer(createTourneyPlayer(2), null);
TournamentPlayer player4 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player4 = new TournamentPlayer(createTourneyPlayer(4), null);
TournamentPlayer player1 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player1 = new TournamentPlayer(createTourneyPlayer(1), null);
List<TournamentPlayer> players = new ArrayList<>(); List<TournamentPlayer> players = new ArrayList<>();
players.add(player4); players.add(player4);
players.add(player2); players.add(player2);
@ -116,10 +122,10 @@ public class SwissPairingMinimalWeightMatchingTest {
// 2 > 4 // 2 > 4
// 4 left the tournament // 4 left the tournament
TournamentPlayer player1 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player1 = new TournamentPlayer(createTourneyPlayer(1), null);
TournamentPlayer player2 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player2 = new TournamentPlayer(createTourneyPlayer(2), null);
TournamentPlayer player3 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player3 = new TournamentPlayer(createTourneyPlayer(3), null);
TournamentPlayer player4 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player4 = new TournamentPlayer(createTourneyPlayer(4), null);
List<TournamentPlayer> players = new ArrayList<>(); List<TournamentPlayer> players = new ArrayList<>();
//players.add(player4); -- player 4 is not active //players.add(player4); -- player 4 is not active
players.add(player2); players.add(player2);
@ -159,11 +165,11 @@ public class SwissPairingMinimalWeightMatchingTest {
// 2 > 3 // 2 > 3
// 4 // 4
TournamentPlayer player1 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player1 = new TournamentPlayer(createTourneyPlayer(1), null);
TournamentPlayer player2 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player2 = new TournamentPlayer(createTourneyPlayer(2), null);
TournamentPlayer player3 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player3 = new TournamentPlayer(createTourneyPlayer(3), null);
TournamentPlayer player4 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player4 = new TournamentPlayer(createTourneyPlayer(4), null);
TournamentPlayer player5 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player5 = new TournamentPlayer(createTourneyPlayer(5), null);
List<TournamentPlayer> players = new ArrayList<>(); List<TournamentPlayer> players = new ArrayList<>();
players.add(player4); players.add(player4);
players.add(player2); players.add(player2);
@ -214,11 +220,11 @@ public class SwissPairingMinimalWeightMatchingTest {
// 5 left the tournament // 5 left the tournament
TournamentPlayer player1 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player1 = new TournamentPlayer(createTourneyPlayer(1), null);
TournamentPlayer player2 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player2 = new TournamentPlayer(createTourneyPlayer(2), null);
TournamentPlayer player3 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player3 = new TournamentPlayer(createTourneyPlayer(3), null);
TournamentPlayer player4 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player4 = new TournamentPlayer(createTourneyPlayer(4), null);
TournamentPlayer player5 = new TournamentPlayer(new PlayerStub(), null); TournamentPlayer player5 = new TournamentPlayer(createTourneyPlayer(5), null);
List<TournamentPlayer> players = new ArrayList<>(); List<TournamentPlayer> players = new ArrayList<>();
//players.add(player5); -- player 5 is not active //players.add(player5); -- player 5 is not active
players.add(player4); players.add(player4);
@ -268,7 +274,7 @@ public class SwissPairingMinimalWeightMatchingTest {
List<TournamentPlayer> players = new ArrayList<>(); List<TournamentPlayer> players = new ArrayList<>();
for (int i = 0; i < playersCount; i++) { 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<>(); List<TournamentPairing> playedPairs = new ArrayList<>();

File diff suppressed because it is too large Load diff

View file

@ -199,7 +199,7 @@ public abstract class MatchImpl implements Match {
matchPlayer.getPlayer().init(game); matchPlayer.getPlayer().init(game);
game.loadCards(matchPlayer.getDeck().getCards(), matchPlayer.getPlayer().getId()); game.loadCards(matchPlayer.getDeck().getCards(), matchPlayer.getPlayer().getId());
game.loadCards(matchPlayer.getDeck().getSideboard(), 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 // time limits
matchPlayer.getPlayer().setBufferTimeLeft(options.getMatchBufferTime().getBufferSecs()); matchPlayer.getPlayer().setBufferTimeLeft(options.getMatchBufferTime().getBufferSecs());
if (games.isEmpty()) { if (games.isEmpty()) {

View file

@ -1,16 +1,19 @@
package mage.players; 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 { public enum PlayerType {
HUMAN("Human"), HUMAN("Human"),
COMPUTER_DRAFT_BOT("Computer - draftbot"), COMPUTER_DRAFT_BOT("Computer - draftbot"),
COMPUTER_MINIMAX_HYBRID("Computer - minimax hybrid"),
COMPUTER_MONTE_CARLO("Computer - monte carlo"), COMPUTER_MONTE_CARLO("Computer - monte carlo"),
COMPUTER_MAD("Computer - mad"); COMPUTER_MAD("Computer - mad");
String description; final String description;
PlayerType(String description) { PlayerType(String description) {
this.description = description; this.description = description;
@ -27,6 +30,6 @@ public enum PlayerType {
return type; 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));
} }
} }

View file

@ -35,7 +35,10 @@ import java.util.UUID;
import static com.google.common.collect.Iterables.getOnlyElement; 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 @Override
public boolean choose(Outcome outcome, Target target, Ability source, Game game) { public boolean choose(Outcome outcome, Target target, Ability source, Game game) {
@ -209,7 +212,7 @@ public class StubPlayer extends PlayerImpl implements Player {
@Override @Override
public List<Integer> getMultiAmountWithIndividualConstraints(Outcome outcome, List<MultiAmountMessage> messages, public List<Integer> getMultiAmountWithIndividualConstraints(Outcome outcome, List<MultiAmountMessage> messages,
int min, int max, MultiAmountType type, Game game) { int min, int max, MultiAmountType type, Game game) {
return null; return null;
} }
@ -227,10 +230,10 @@ public class StubPlayer extends PlayerImpl implements Player {
public void pickCard(List<Card> cards, Deck deck, Draft draft) { public void pickCard(List<Card> cards, Deck deck, Draft draft) {
} }
@Override @Override
public void addPhyrexianToColors(FilterMana colors) { public void addPhyrexianToColors(FilterMana colors) {
} }
@Override @Override