2011-04-03 20:07:36 -07:00
|
|
|
package magic.ai;
|
|
|
|
|
2011-08-10 08:28:35 -07:00
|
|
|
import magic.data.LRUCache;
|
2011-04-03 20:07:36 -07:00
|
|
|
import magic.model.MagicGame;
|
2012-02-25 07:46:42 -08:00
|
|
|
import magic.model.MagicGameLog;
|
2011-04-03 20:07:36 -07:00
|
|
|
import magic.model.MagicPlayer;
|
2011-08-10 07:43:46 -07:00
|
|
|
import magic.model.choice.MagicBuilderPayManaCostResult;
|
2011-08-10 08:28:35 -07:00
|
|
|
import magic.model.event.MagicEvent;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
2012-09-26 23:11:44 -07:00
|
|
|
import java.util.Collections;
|
2011-08-10 08:28:35 -07:00
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.LinkedList;
|
|
|
|
import java.util.List;
|
2011-04-03 20:07:36 -07:00
|
|
|
|
|
|
|
/*
|
2011-06-29 00:47:57 -07:00
|
|
|
AI using Monte Carlo Tree Search
|
|
|
|
|
|
|
|
Classical MCTS (UCT)
|
|
|
|
- use UCB1 formula for selection with C = sqrt(2)
|
|
|
|
- reward either 0 or 1
|
|
|
|
- backup by averaging
|
|
|
|
- uniform random simulated playout
|
|
|
|
- score = XX% (25000 matches against MMAB-1)
|
|
|
|
|
|
|
|
Enchancements to basic UCT
|
|
|
|
- use ratio selection (v + 10)/(n + 10)
|
|
|
|
- UCB1 with C = 1.0
|
|
|
|
- UCB1 with C = 2.0
|
|
|
|
- UCB1 with C = 3.0
|
|
|
|
- use normal bound max(1,v + 2 * std(v))
|
|
|
|
- reward depends on length of playout
|
|
|
|
- backup by robust max
|
|
|
|
|
|
|
|
References:
|
2011-04-03 20:07:36 -07:00
|
|
|
UCT algorithm from Kocsis and Sezepesvari 2006
|
2011-07-04 00:49:42 -07:00
|
|
|
|
2011-07-04 00:25:46 -07:00
|
|
|
Consistency Modifications for Automatically Tuned Monte-Carlo Tree Search
|
|
|
|
consistent -> child of root with greatest number of simulations is optimal
|
|
|
|
frugal -> do not need to visit the whole tree
|
|
|
|
eps-greedy is not consisteny for fixed eps (with prob eps select randomly, else use score)
|
|
|
|
eps-greedy is consistent but not frugal if eps dynamically decreases to 0
|
|
|
|
UCB1 is consistent but not frugal
|
|
|
|
score = average is not consistent
|
|
|
|
score = (total reward + K)/(total simulation + 2K) is consistent and frugal!
|
|
|
|
using v_t threshold ensures consistency for case of reward in {0,1} using any score function
|
|
|
|
v(s) < v_t (0.3), randomy pick a child, else pick child that maximize score
|
2011-07-04 00:49:42 -07:00
|
|
|
|
|
|
|
Monte-Carlo Tree Search in Lines of Action
|
|
|
|
1-ply lookahread to detect direct win for player to move
|
|
|
|
secure child formula for decision v + A/sqrt(n)
|
|
|
|
evaluation cut-off: use score function to stop simulation early
|
|
|
|
use evaluation score to remove "bad" moves during simulation
|
|
|
|
use evaluation score to keep k-best moves
|
|
|
|
mixed: start with corrective, rest of the moves use greedy
|
2011-04-03 20:07:36 -07:00
|
|
|
*/
|
|
|
|
public class MCTSAI implements MagicAI {
|
2011-06-19 20:28:55 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
private static int MIN_SCORE = Integer.MAX_VALUE;
|
2011-07-20 19:52:07 -07:00
|
|
|
static int MIN_SIM = Integer.MAX_VALUE;
|
2011-09-10 06:04:08 -07:00
|
|
|
private static final int MAX_ACTIONS = 10000;
|
2011-07-20 19:52:07 -07:00
|
|
|
static double UCB1_C = 0.4;
|
2011-07-12 01:34:56 -07:00
|
|
|
static double RATIO_K = 1.0;
|
2011-06-06 02:05:09 -07:00
|
|
|
|
2011-07-15 21:00:11 -07:00
|
|
|
static {
|
|
|
|
if (System.getProperty("min_sim") != null) {
|
|
|
|
MIN_SIM = Integer.parseInt(System.getProperty("min_sim"));
|
|
|
|
System.err.println("MIN_SIM = " + MIN_SIM);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (System.getProperty("min_score") != null) {
|
|
|
|
MIN_SCORE = Integer.parseInt(System.getProperty("min_score"));
|
|
|
|
System.err.println("MIN_SCORE = " + MIN_SCORE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (System.getProperty("ucb1_c") != null) {
|
|
|
|
UCB1_C = Double.parseDouble(System.getProperty("ucb1_c"));
|
|
|
|
System.err.println("UCB1_C = " + UCB1_C);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (System.getProperty("ratio_k") != null) {
|
|
|
|
RATIO_K = Double.parseDouble(System.getProperty("ratio_k"));
|
|
|
|
System.err.println("RATIO_K = " + RATIO_K);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-08 20:05:09 -07:00
|
|
|
private final boolean LOGGING;
|
2011-06-19 23:17:03 -07:00
|
|
|
private final boolean CHEAT;
|
2011-06-29 00:47:57 -07:00
|
|
|
|
|
|
|
//cache the set of choices at the root to avoid recomputing it all the time
|
|
|
|
private List<Object[]> RCHOICES;
|
2011-06-11 01:25:23 -07:00
|
|
|
|
2011-06-15 01:16:41 -07:00
|
|
|
//cache nodes to reuse them in later decision
|
2011-08-05 01:41:04 -07:00
|
|
|
private final LRUCache<Long, MCTSGameTree> CACHE = new LRUCache<Long, MCTSGameTree>(1000);
|
2011-06-14 00:24:07 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
MCTSAI() {
|
2011-06-29 00:47:57 -07:00
|
|
|
//default: no logging, cheats
|
2011-06-07 05:57:29 -07:00
|
|
|
this(false, true);
|
2011-04-08 20:05:09 -07:00
|
|
|
}
|
|
|
|
|
2011-06-29 00:47:57 -07:00
|
|
|
public MCTSAI(final boolean log, final boolean cheat) {
|
|
|
|
LOGGING = log || (System.getProperty("debug") != null);
|
2011-06-07 05:57:29 -07:00
|
|
|
CHEAT = cheat;
|
2011-04-08 20:05:09 -07:00
|
|
|
}
|
|
|
|
|
2011-06-27 21:10:36 -07:00
|
|
|
private void log(final String message) {
|
2012-08-26 01:54:43 -07:00
|
|
|
MagicGameLog.log(message);
|
2011-04-03 20:07:36 -07:00
|
|
|
if (LOGGING) {
|
2011-06-05 19:45:45 -07:00
|
|
|
System.err.println(message);
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
|
|
|
}
|
2011-06-09 21:03:27 -07:00
|
|
|
|
2011-07-27 22:22:00 -07:00
|
|
|
public Object[] findNextEventChoiceResults(
|
2011-06-10 21:27:24 -07:00
|
|
|
final MagicGame startGame,
|
2011-04-03 20:07:36 -07:00
|
|
|
final MagicPlayer scorePlayer) {
|
2011-06-13 01:25:31 -07:00
|
|
|
|
2011-07-03 20:49:39 -07:00
|
|
|
// Determine possible choices
|
2011-06-29 00:47:57 -07:00
|
|
|
MagicGame choiceGame = new MagicGame(startGame, scorePlayer);
|
2011-06-13 01:25:31 -07:00
|
|
|
final MagicEvent event = choiceGame.getNextEvent();
|
2011-06-29 00:47:57 -07:00
|
|
|
RCHOICES = event.getArtificialChoiceResults(choiceGame);
|
|
|
|
choiceGame = null;
|
2011-06-13 01:25:31 -07:00
|
|
|
|
2011-06-29 00:47:57 -07:00
|
|
|
final int size = RCHOICES.size();
|
2011-04-03 20:07:36 -07:00
|
|
|
|
2011-07-03 20:49:39 -07:00
|
|
|
// No choice
|
2011-06-23 19:46:35 -07:00
|
|
|
assert size > 0 : "ERROR! No choice found at start of MCTS";
|
2011-04-03 20:07:36 -07:00
|
|
|
|
2011-07-03 20:49:39 -07:00
|
|
|
// Single choice
|
2011-04-03 20:07:36 -07:00
|
|
|
if (size == 1) {
|
2011-06-29 00:47:57 -07:00
|
|
|
return startGame.map(RCHOICES.get(0));
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
|
|
|
|
2011-07-03 20:49:39 -07:00
|
|
|
//normal: max time is 1000 * level
|
2011-07-03 20:07:07 -07:00
|
|
|
int MAX_TIME = 1000 * startGame.getArtificialLevel(scorePlayer.getIndex());
|
2011-07-03 20:49:39 -07:00
|
|
|
int MAX_SIM = Integer.MAX_VALUE;
|
|
|
|
|
2011-07-03 20:07:07 -07:00
|
|
|
final long START_TIME = System.currentTimeMillis();
|
2011-04-07 23:44:28 -07:00
|
|
|
|
2011-04-03 20:07:36 -07:00
|
|
|
//root represents the start state
|
2011-06-29 00:47:57 -07:00
|
|
|
final MCTSGameTree root = MCTSGameTree.getNode(CACHE, startGame, RCHOICES);
|
2011-06-10 21:27:24 -07:00
|
|
|
|
2011-07-12 19:47:14 -07:00
|
|
|
log("MCTS cached=" + root.getNumSim());
|
|
|
|
|
2011-06-29 00:47:57 -07:00
|
|
|
//end simulations once root is AI win or time is up
|
2011-07-03 20:49:39 -07:00
|
|
|
int sims;
|
|
|
|
for (sims = 0;
|
2011-07-03 20:07:07 -07:00
|
|
|
System.currentTimeMillis() - START_TIME < MAX_TIME &&
|
|
|
|
sims < MAX_SIM &&
|
2011-06-29 00:47:57 -07:00
|
|
|
!root.isAIWin();
|
|
|
|
sims++) {
|
2011-06-28 00:07:33 -07:00
|
|
|
|
2011-06-17 01:35:17 -07:00
|
|
|
//clone the MagicGame object for simulation
|
2011-06-10 21:27:24 -07:00
|
|
|
final MagicGame rootGame = new MagicGame(startGame, scorePlayer);
|
2011-06-07 05:57:29 -07:00
|
|
|
if (!CHEAT) {
|
2013-03-09 17:32:27 -08:00
|
|
|
rootGame.hideHiddenCards();
|
2011-06-07 05:57:29 -07:00
|
|
|
}
|
2011-04-03 20:07:36 -07:00
|
|
|
|
2011-06-29 00:47:57 -07:00
|
|
|
//pass in a clone of the state,
|
|
|
|
//genNewTreeNode grows the tree by one node
|
2011-04-05 22:27:39 -07:00
|
|
|
//and returns the path from the root to the new node
|
2011-06-15 21:17:26 -07:00
|
|
|
final LinkedList<MCTSGameTree> path = growTree(root, rootGame);
|
2011-06-14 00:24:07 -07:00
|
|
|
|
2011-06-23 19:46:35 -07:00
|
|
|
assert path.size() >= 2 : "ERROR! length of MCTS path is " + path.size();
|
2011-06-11 00:40:25 -07:00
|
|
|
|
2011-04-05 22:27:39 -07:00
|
|
|
// play a simulated game to get score
|
|
|
|
// update all nodes along the path from root to new node
|
2011-06-10 21:27:24 -07:00
|
|
|
final double score = randomPlay(path.getLast(), rootGame);
|
2011-06-10 20:10:50 -07:00
|
|
|
|
2011-06-19 23:24:44 -07:00
|
|
|
// update score and game theoretic value along the chosen path
|
2011-07-03 20:49:39 -07:00
|
|
|
MCTSGameTree child = null;
|
|
|
|
MCTSGameTree parent = null;
|
|
|
|
while (!path.isEmpty()) {
|
|
|
|
child = parent;
|
|
|
|
parent = path.removeLast();
|
|
|
|
|
2011-07-01 01:39:02 -07:00
|
|
|
parent.updateScore(child, score);
|
2011-06-10 20:10:50 -07:00
|
|
|
|
|
|
|
if (child != null && child.isSolved()) {
|
2011-07-03 20:49:39 -07:00
|
|
|
final int steps = child.getSteps() + 1;
|
2011-06-10 20:10:50 -07:00
|
|
|
if (parent.isAI() && child.isAIWin()) {
|
2011-07-03 20:49:39 -07:00
|
|
|
parent.setAIWin(steps);
|
|
|
|
} else if (parent.isOpp() && child.isAILose()) {
|
|
|
|
parent.setAILose(steps);
|
2011-06-10 20:10:50 -07:00
|
|
|
} else if (parent.isAI() && child.isAILose()) {
|
2011-07-03 20:49:39 -07:00
|
|
|
parent.incLose(steps);
|
|
|
|
} else if (parent.isOpp() && child.isAIWin()) {
|
|
|
|
parent.incLose(steps);
|
|
|
|
}
|
2011-06-10 20:10:50 -07:00
|
|
|
}
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
|
|
|
}
|
2011-06-10 00:24:40 -07:00
|
|
|
|
2011-06-23 19:46:35 -07:00
|
|
|
assert root.size() > 0 : "ERROR! Root has no children but there are " + size + " choices";
|
2011-06-10 06:42:45 -07:00
|
|
|
|
2011-06-29 00:47:57 -07:00
|
|
|
//select the best child/choice
|
2011-06-07 19:54:04 -07:00
|
|
|
final MCTSGameTree first = root.first();
|
2011-06-29 00:47:57 -07:00
|
|
|
double maxD = first.getDecision();
|
2011-06-07 19:54:04 -07:00
|
|
|
int bestC = first.getChoice();
|
2011-09-08 02:14:57 -07:00
|
|
|
for (final MCTSGameTree node : root) {
|
2011-06-29 00:47:57 -07:00
|
|
|
final double D = node.getDecision();
|
2011-06-07 19:54:04 -07:00
|
|
|
final int C = node.getChoice();
|
2011-06-29 00:47:57 -07:00
|
|
|
if (D > maxD) {
|
|
|
|
maxD = D;
|
2011-06-07 19:54:04 -07:00
|
|
|
bestC = C;
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
|
|
|
}
|
2011-06-05 21:29:44 -07:00
|
|
|
|
2011-07-12 19:34:52 -07:00
|
|
|
log(outputChoice(scorePlayer, root, START_TIME, bestC, sims));
|
|
|
|
|
|
|
|
return startGame.map(RCHOICES.get(bestC));
|
|
|
|
}
|
|
|
|
|
|
|
|
private String outputChoice(
|
|
|
|
final MagicPlayer scorePlayer,
|
|
|
|
final MCTSGameTree root,
|
|
|
|
final long START_TIME,
|
|
|
|
final int bestC,
|
|
|
|
final int sims) {
|
|
|
|
|
2011-09-03 00:12:00 -07:00
|
|
|
final StringBuilder out = new StringBuilder();
|
2011-07-12 19:34:52 -07:00
|
|
|
final long duration = System.currentTimeMillis() - START_TIME;
|
|
|
|
|
2011-07-12 23:39:12 -07:00
|
|
|
out.append("MCTS" +
|
|
|
|
" index=" + scorePlayer.getIndex() +
|
2011-07-12 19:47:14 -07:00
|
|
|
" life=" + scorePlayer.getLife() +
|
|
|
|
" time=" + duration +
|
|
|
|
" sims=" + sims);
|
2011-07-12 19:34:52 -07:00
|
|
|
out.append('\n');
|
|
|
|
|
2011-09-08 02:14:57 -07:00
|
|
|
for (final MCTSGameTree node : root) {
|
2011-07-12 19:34:52 -07:00
|
|
|
if (node.getChoice() == bestC) {
|
|
|
|
out.append("* ");
|
|
|
|
} else {
|
|
|
|
out.append(" ");
|
2011-06-09 21:03:27 -07:00
|
|
|
}
|
2011-07-12 19:34:52 -07:00
|
|
|
out.append('[');
|
|
|
|
out.append((int)(node.getV() * 100));
|
|
|
|
out.append('/');
|
|
|
|
out.append(node.getNumSim());
|
|
|
|
out.append('/');
|
|
|
|
if (node.isAIWin()) {
|
|
|
|
out.append("win");
|
|
|
|
out.append(':');
|
|
|
|
out.append(node.getSteps());
|
|
|
|
} else if (node.isAILose()) {
|
|
|
|
out.append("lose");
|
|
|
|
out.append(':');
|
|
|
|
out.append(node.getSteps());
|
|
|
|
} else {
|
|
|
|
out.append("?");
|
|
|
|
}
|
|
|
|
out.append(']');
|
|
|
|
out.append(CR2String(RCHOICES.get(node.getChoice())));
|
|
|
|
out.append('\n');
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
2011-07-12 19:47:14 -07:00
|
|
|
return out.toString().trim();
|
2011-06-10 20:45:40 -07:00
|
|
|
}
|
|
|
|
|
2011-06-15 21:17:26 -07:00
|
|
|
private LinkedList<MCTSGameTree> growTree(final MCTSGameTree root, final MagicGame game) {
|
2011-06-10 20:10:50 -07:00
|
|
|
final LinkedList<MCTSGameTree> path = new LinkedList<MCTSGameTree>();
|
2011-06-15 21:17:26 -07:00
|
|
|
boolean found = false;
|
2011-04-05 22:27:39 -07:00
|
|
|
MCTSGameTree curr = root;
|
|
|
|
path.add(curr);
|
2011-04-04 20:31:42 -07:00
|
|
|
|
2011-06-23 19:42:42 -07:00
|
|
|
for (List<Object[]> choices = getNextChoices(game, false);
|
2011-09-06 06:10:13 -07:00
|
|
|
!choices.isEmpty();
|
2011-06-23 19:42:42 -07:00
|
|
|
choices = getNextChoices(game, false)) {
|
2011-06-14 19:40:21 -07:00
|
|
|
|
|
|
|
assert choices.size() > 0 : "ERROR! No choice at start of genNewTreeNode";
|
2011-06-18 08:55:38 -07:00
|
|
|
|
2011-06-27 21:10:36 -07:00
|
|
|
assert !curr.hasDetails() || MCTSGameTree.checkNode(curr, choices) :
|
2012-08-22 22:10:28 -07:00
|
|
|
"ERROR! Inconsistent node found" + "\n" +
|
|
|
|
game + " " +
|
|
|
|
printPath(path) + " " +
|
|
|
|
MCTSGameTree.printNode(curr, choices);
|
2011-06-04 01:50:52 -07:00
|
|
|
|
|
|
|
final MagicEvent event = game.getNextEvent();
|
2011-06-15 00:59:27 -07:00
|
|
|
|
2011-06-19 23:24:44 -07:00
|
|
|
//first time considering the choices available at this node,
|
|
|
|
//fill in additional details for curr
|
2011-06-15 00:59:27 -07:00
|
|
|
if (!curr.hasDetails()) {
|
|
|
|
curr.setIsAI(game.getScorePlayer() == event.getPlayer());
|
|
|
|
curr.setMaxChildren(choices.size());
|
2011-06-20 20:46:20 -07:00
|
|
|
assert curr.setChoicesStr(choices);
|
2011-06-14 00:24:07 -07:00
|
|
|
}
|
|
|
|
|
2011-06-15 21:17:26 -07:00
|
|
|
//look for first non root AI node along this path and add it to cache
|
|
|
|
if (!found && curr != root && curr.isAI()) {
|
|
|
|
found = true;
|
2011-06-20 20:46:20 -07:00
|
|
|
assert curr.isCached() || printPath(path);
|
2011-06-27 21:10:36 -07:00
|
|
|
MCTSGameTree.addNode(CACHE, game, curr);
|
2011-06-15 21:17:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//there are unexplored children of node
|
|
|
|
//assume we explore children of a node in increasing order of the choices
|
2011-04-05 22:27:39 -07:00
|
|
|
if (curr.size() < choices.size()) {
|
2011-06-19 23:24:44 -07:00
|
|
|
final int idx = curr.size();
|
2011-09-08 02:14:57 -07:00
|
|
|
final Object[] choice = choices.get(idx);
|
2011-06-14 00:24:07 -07:00
|
|
|
game.executeNextEvent(choice);
|
2011-07-03 21:09:02 -07:00
|
|
|
final MCTSGameTree child = new MCTSGameTree(curr, idx, game.getScore());
|
2011-06-27 21:10:36 -07:00
|
|
|
assert (child.desc = MCTSGameTree.obj2String(choice[0])).equals(child.desc);
|
2011-04-05 22:27:39 -07:00
|
|
|
curr.addChild(child);
|
|
|
|
path.add(child);
|
|
|
|
return path;
|
2011-06-15 21:17:26 -07:00
|
|
|
|
|
|
|
//all the children are in the tree, find the "best" child to explore
|
2011-04-05 22:27:39 -07:00
|
|
|
} else {
|
2011-06-11 07:12:16 -07:00
|
|
|
|
2011-06-14 20:23:22 -07:00
|
|
|
assert curr.size() == choices.size() : "ERROR! Different number of choices in node and game" +
|
2011-06-27 21:10:36 -07:00
|
|
|
printPath(path) + MCTSGameTree.printNode(curr, choices);
|
2011-06-09 22:36:56 -07:00
|
|
|
|
2011-07-08 00:35:59 -07:00
|
|
|
MCTSGameTree next = null;
|
|
|
|
double bestS = Double.NEGATIVE_INFINITY ;
|
2011-09-08 02:14:57 -07:00
|
|
|
for (final MCTSGameTree child : curr) {
|
2011-07-03 21:09:02 -07:00
|
|
|
final double raw = child.getUCT();
|
|
|
|
final double S = child.modify(raw);
|
2011-06-29 00:47:57 -07:00
|
|
|
if (S > bestS) {
|
|
|
|
bestS = S;
|
2011-06-10 20:10:50 -07:00
|
|
|
next = child;
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
|
|
|
}
|
2011-04-06 21:31:41 -07:00
|
|
|
|
|
|
|
//move down the tree
|
2011-06-10 20:10:50 -07:00
|
|
|
curr = next;
|
2011-06-29 00:47:57 -07:00
|
|
|
|
|
|
|
//update the game state and path
|
2011-04-05 22:27:39 -07:00
|
|
|
game.executeNextEvent(choices.get(curr.getChoice()));
|
|
|
|
path.add(curr);
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
2011-04-05 22:27:39 -07:00
|
|
|
}
|
2011-04-06 21:31:41 -07:00
|
|
|
|
2011-04-04 20:31:42 -07:00
|
|
|
return path;
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
|
|
|
|
2011-06-28 18:39:14 -07:00
|
|
|
//returns a reward in the range [0, 1]
|
2011-06-10 20:10:50 -07:00
|
|
|
private double randomPlay(final MCTSGameTree node, final MagicGame game) {
|
|
|
|
//terminal node, no need for random play
|
|
|
|
if (game.isFinished()) {
|
2011-06-14 00:24:07 -07:00
|
|
|
if (game.getLosingPlayer() == game.getScorePlayer()) {
|
2011-06-24 21:35:41 -07:00
|
|
|
node.setAILose(0);
|
2011-06-28 18:39:14 -07:00
|
|
|
return 0.0;
|
2011-06-10 20:10:50 -07:00
|
|
|
} else {
|
2011-06-24 21:35:41 -07:00
|
|
|
node.setAIWin(0);
|
2011-06-10 20:10:50 -07:00
|
|
|
return 1.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-16 20:48:15 -07:00
|
|
|
final int startActions = game.getNumActions();
|
2011-06-23 19:42:42 -07:00
|
|
|
getNextChoices(game, true);
|
2011-07-12 19:34:52 -07:00
|
|
|
final int actions = Math.min(MAX_ACTIONS, game.getNumActions() - startActions);
|
2011-06-10 20:45:40 -07:00
|
|
|
|
2011-09-03 06:37:40 -07:00
|
|
|
if (!game.isFinished()) {
|
2011-06-28 21:19:12 -07:00
|
|
|
return 0.5;
|
2011-04-08 20:00:06 -07:00
|
|
|
} else if (game.getLosingPlayer() == game.getScorePlayer()) {
|
2011-07-03 05:19:15 -07:00
|
|
|
return actions/(2.0 * MAX_ACTIONS);
|
2011-04-05 22:27:39 -07:00
|
|
|
} else {
|
2011-07-03 05:19:15 -07:00
|
|
|
return 1.0 - actions/(2.0 * MAX_ACTIONS);
|
2011-04-05 22:27:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-11 07:12:16 -07:00
|
|
|
private List<Object[]> getNextChoices(
|
|
|
|
final MagicGame game,
|
|
|
|
final boolean sim) {
|
2011-07-12 01:34:56 -07:00
|
|
|
|
|
|
|
|
2011-06-16 20:48:15 -07:00
|
|
|
final int startActions = game.getNumActions();
|
2011-06-21 00:36:52 -07:00
|
|
|
|
2011-06-29 00:47:57 -07:00
|
|
|
//use fast choices during simulation
|
2011-06-21 00:36:52 -07:00
|
|
|
game.setFastChoices(sim);
|
2011-06-06 18:53:35 -07:00
|
|
|
|
2011-06-16 20:48:15 -07:00
|
|
|
// simulate game until it is finished or simulated MAX_ACTIONS actions
|
2011-06-29 00:47:57 -07:00
|
|
|
while (!game.isFinished() &&
|
|
|
|
(game.getNumActions() - startActions) < MAX_ACTIONS) {
|
2011-07-11 07:30:20 -07:00
|
|
|
|
|
|
|
//do not accumulate score down the tree when not in simulation
|
|
|
|
if (!sim) {
|
|
|
|
game.setScore(0);
|
|
|
|
}
|
2011-06-17 01:35:17 -07:00
|
|
|
|
2011-04-07 23:44:28 -07:00
|
|
|
if (!game.hasNextEvent()) {
|
2012-12-19 23:21:37 -08:00
|
|
|
game.executePhase();
|
2011-04-07 23:44:28 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
//game has next event
|
|
|
|
final MagicEvent event = game.getNextEvent();
|
|
|
|
|
|
|
|
if (!event.hasChoice()) {
|
|
|
|
game.executeNextEvent(MagicEvent.NO_CHOICE_RESULTS);
|
|
|
|
continue;
|
|
|
|
}
|
2011-06-06 18:53:35 -07:00
|
|
|
|
2011-04-07 23:44:28 -07:00
|
|
|
//event has choice
|
2011-06-06 18:53:35 -07:00
|
|
|
|
|
|
|
if (sim) {
|
|
|
|
//get simulation choice and execute
|
2011-06-10 23:19:49 -07:00
|
|
|
final Object[] choice = event.getSimulationChoiceResult(game);
|
2011-06-23 19:46:35 -07:00
|
|
|
assert choice != null : "ERROR! No choice found during MCTS sim";
|
2011-06-14 00:24:07 -07:00
|
|
|
game.executeNextEvent(choice);
|
2011-07-11 07:30:20 -07:00
|
|
|
|
|
|
|
//terminate early if score > MIN_SCORE or score < -MIN_SCORE
|
|
|
|
if (game.getScore() < -MIN_SCORE) {
|
|
|
|
game.setLosingPlayer(game.getScorePlayer());
|
|
|
|
}
|
|
|
|
if (game.getScore() > MIN_SCORE) {
|
2012-08-24 20:08:50 -07:00
|
|
|
game.setLosingPlayer(game.getScorePlayer().getOpponent());
|
2011-07-11 07:30:20 -07:00
|
|
|
}
|
2011-04-07 23:44:28 -07:00
|
|
|
} else {
|
2011-06-10 06:42:45 -07:00
|
|
|
//get list of possible AI choices
|
2011-06-28 00:07:33 -07:00
|
|
|
List<Object[]> choices = null;
|
|
|
|
if (game.getNumActions() == 0) {
|
2011-06-29 00:47:57 -07:00
|
|
|
//map the RCHOICES to the current game instead of recomputing the choices
|
|
|
|
choices = new ArrayList<Object[]>(RCHOICES.size());
|
2011-09-08 02:14:57 -07:00
|
|
|
for (final Object[] choice : RCHOICES) {
|
2011-06-28 00:07:33 -07:00
|
|
|
choices.add(game.map(choice));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
choices = event.getArtificialChoiceResults(game);
|
|
|
|
}
|
|
|
|
assert choices != null;
|
|
|
|
|
2011-06-06 18:53:35 -07:00
|
|
|
final int size = choices.size();
|
2011-06-23 19:46:35 -07:00
|
|
|
assert size > 0 : "ERROR! No choice found during MCTS getACR";
|
2011-06-28 00:07:33 -07:00
|
|
|
|
2011-06-14 00:24:07 -07:00
|
|
|
if (size == 1) {
|
|
|
|
//single choice
|
2011-06-06 18:53:35 -07:00
|
|
|
game.executeNextEvent(choices.get(0));
|
|
|
|
} else {
|
|
|
|
//multiple choice
|
|
|
|
return choices;
|
|
|
|
}
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
|
|
|
}
|
2011-06-06 18:53:35 -07:00
|
|
|
|
2011-06-29 00:47:57 -07:00
|
|
|
//game is finished or number of actions > MAX_ACTIONS
|
2011-09-06 06:10:13 -07:00
|
|
|
return Collections.emptyList();
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
2011-07-03 20:04:10 -07:00
|
|
|
|
2011-09-08 02:14:57 -07:00
|
|
|
private static String CR2String(final Object[] choiceResults) {
|
2011-09-03 00:12:00 -07:00
|
|
|
final StringBuilder buffer=new StringBuilder();
|
2011-07-03 20:04:10 -07:00
|
|
|
if (choiceResults!=null) {
|
|
|
|
buffer.append(" (");
|
|
|
|
boolean first=true;
|
|
|
|
for (final Object choiceResult : choiceResults) {
|
|
|
|
if (first) {
|
|
|
|
first=false;
|
|
|
|
} else {
|
|
|
|
buffer.append(',');
|
|
|
|
}
|
|
|
|
buffer.append(choiceResult);
|
|
|
|
}
|
|
|
|
buffer.append(')');
|
|
|
|
}
|
|
|
|
return buffer.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean printPath(final List<MCTSGameTree> path) {
|
2011-09-08 02:14:57 -07:00
|
|
|
final StringBuilder sb = new StringBuilder();
|
|
|
|
for (final MCTSGameTree p : path) {
|
2011-07-03 20:04:10 -07:00
|
|
|
sb.append(" -> ").append(p.desc);
|
|
|
|
}
|
|
|
|
log(sb.toString());
|
|
|
|
return true;
|
|
|
|
}
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//each tree node stores the choice from the parent that leads to this node
|
2011-04-07 02:19:23 -07:00
|
|
|
class MCTSGameTree implements Iterable<MCTSGameTree> {
|
2011-07-03 21:09:02 -07:00
|
|
|
|
|
|
|
private final MCTSGameTree parent;
|
2011-06-09 22:36:56 -07:00
|
|
|
private final LinkedList<MCTSGameTree> children = new LinkedList<MCTSGameTree>();
|
2011-06-10 20:10:50 -07:00
|
|
|
private final int choice;
|
|
|
|
private boolean isAI;
|
2012-10-03 01:17:01 -07:00
|
|
|
private boolean isCached;
|
2011-06-14 19:40:21 -07:00
|
|
|
private int maxChildren = -1;
|
2012-10-03 01:17:01 -07:00
|
|
|
private int numLose;
|
|
|
|
private int numSim;
|
|
|
|
private int evalScore;
|
|
|
|
private int steps;
|
|
|
|
private double sum;
|
|
|
|
private double S;
|
2011-09-10 06:04:08 -07:00
|
|
|
String desc;
|
|
|
|
private String[] choicesStr;
|
2011-06-27 21:10:36 -07:00
|
|
|
|
2011-07-01 19:58:14 -07:00
|
|
|
//min sim for using robust max
|
2011-07-12 01:34:56 -07:00
|
|
|
private int maxChildSim = MCTSAI.MIN_SIM;
|
2011-07-01 19:58:14 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
MCTSGameTree(final MCTSGameTree parent, final int choice, final int evalScore) {
|
2011-07-03 21:09:02 -07:00
|
|
|
this.evalScore = evalScore;
|
|
|
|
this.choice = choice;
|
|
|
|
this.parent = parent;
|
|
|
|
}
|
|
|
|
|
2011-06-27 21:10:36 -07:00
|
|
|
private static boolean log(final String message) {
|
|
|
|
System.err.println(message);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
private static int obj2StringHash(final Object obj) {
|
2011-06-27 21:10:36 -07:00
|
|
|
return obj2String(obj).hashCode();
|
|
|
|
}
|
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
static String obj2String(final Object obj) {
|
2011-06-27 21:10:36 -07:00
|
|
|
if (obj == null) {
|
|
|
|
return "null";
|
|
|
|
} else if (obj instanceof MagicBuilderPayManaCostResult) {
|
|
|
|
return ((MagicBuilderPayManaCostResult)obj).getText();
|
|
|
|
} else {
|
|
|
|
return obj.toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-30 19:30:42 -07:00
|
|
|
static void addNode(
|
2011-08-05 01:41:04 -07:00
|
|
|
final LRUCache<Long, MCTSGameTree> cache,
|
2011-06-30 19:30:42 -07:00
|
|
|
final MagicGame game,
|
|
|
|
final MCTSGameTree node) {
|
2011-06-27 21:10:36 -07:00
|
|
|
if (node.isCached()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
final long gid = game.getGameId();
|
|
|
|
cache.put(gid, node);
|
|
|
|
node.setCached();
|
|
|
|
assert log("ADDED: " + game.getIdString());
|
|
|
|
}
|
|
|
|
|
2011-06-30 19:30:42 -07:00
|
|
|
static MCTSGameTree getNode(
|
2011-08-05 01:41:04 -07:00
|
|
|
final LRUCache<Long, MCTSGameTree> cache,
|
2011-06-30 19:30:42 -07:00
|
|
|
final MagicGame game,
|
|
|
|
final List<Object[]> choices) {
|
2011-06-27 21:10:36 -07:00
|
|
|
final long gid = game.getGameId();
|
2011-09-08 02:14:57 -07:00
|
|
|
final MCTSGameTree candidate = cache.get(gid);
|
2011-06-27 21:10:36 -07:00
|
|
|
|
|
|
|
if (candidate != null) {
|
|
|
|
assert log("CACHE HIT");
|
|
|
|
assert log("HIT : " + game.getIdString());
|
2011-06-29 00:47:57 -07:00
|
|
|
assert printNode(candidate, choices);
|
2011-06-27 21:10:36 -07:00
|
|
|
return candidate;
|
|
|
|
} else {
|
|
|
|
assert log("CACHE MISS");
|
|
|
|
assert log("MISS : " + game.getIdString());
|
2011-07-03 21:09:02 -07:00
|
|
|
final MCTSGameTree root = new MCTSGameTree(null, -1, -1);
|
2011-06-27 21:10:36 -07:00
|
|
|
assert (root.desc = "root").equals(root.desc);
|
|
|
|
return root;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-09-08 02:14:57 -07:00
|
|
|
static boolean checkNode(final MCTSGameTree curr, final List<Object[]> choices) {
|
2011-06-27 21:10:36 -07:00
|
|
|
if (curr.getMaxChildren() != choices.size()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
for (int i = 0; i < choices.size(); i++) {
|
|
|
|
final String checkStr = obj2String(choices.get(i)[0]);
|
|
|
|
if (!curr.choicesStr[i].equals(checkStr)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2011-09-08 02:14:57 -07:00
|
|
|
for (final MCTSGameTree child : curr) {
|
2011-06-27 21:10:36 -07:00
|
|
|
final String checkStr = obj2String(choices.get(child.getChoice())[0]);
|
|
|
|
if (!child.desc.equals(checkStr)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-09-08 02:14:57 -07:00
|
|
|
static boolean printNode(final MCTSGameTree curr, final List<Object[]> choices) {
|
2011-06-27 21:10:36 -07:00
|
|
|
if (curr.choicesStr != null) {
|
2011-09-08 02:14:57 -07:00
|
|
|
for (final String str : curr.choicesStr) {
|
2011-06-27 21:10:36 -07:00
|
|
|
log("PAREN: " + str);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
log("PAREN: not defined");
|
|
|
|
}
|
2011-09-08 02:14:57 -07:00
|
|
|
for (final MCTSGameTree child : curr) {
|
2011-06-27 21:10:36 -07:00
|
|
|
log("CHILD: " + child.desc);
|
|
|
|
}
|
2011-09-08 02:14:57 -07:00
|
|
|
for (final Object[] choice : choices) {
|
2011-06-27 21:10:36 -07:00
|
|
|
log("GAME : " + obj2String(choice[0]));
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2011-04-03 20:07:36 -07:00
|
|
|
|
2011-06-14 00:24:07 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
boolean isCached() {
|
2011-06-15 21:17:26 -07:00
|
|
|
return isCached;
|
|
|
|
}
|
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
private void setCached() {
|
2011-06-15 21:17:26 -07:00
|
|
|
isCached = true;
|
|
|
|
}
|
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
boolean hasDetails() {
|
2011-06-15 21:17:26 -07:00
|
|
|
return maxChildren != -1;
|
2011-06-15 00:59:27 -07:00
|
|
|
}
|
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
boolean setChoicesStr(final List<Object[]> choices) {
|
2011-06-14 20:23:22 -07:00
|
|
|
choicesStr = new String[choices.size()];
|
|
|
|
for (int i = 0; i < choices.size(); i++) {
|
2011-06-27 21:10:36 -07:00
|
|
|
choicesStr[i] = obj2String(choices.get(i)[0]);
|
2011-06-14 20:23:22 -07:00
|
|
|
}
|
2011-06-20 20:46:20 -07:00
|
|
|
return true;
|
2011-06-14 20:23:22 -07:00
|
|
|
}
|
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
void setMaxChildren(final int mc) {
|
2011-06-10 20:10:50 -07:00
|
|
|
maxChildren = mc;
|
|
|
|
}
|
2011-06-14 19:40:21 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
private int getMaxChildren() {
|
2011-06-14 19:40:21 -07:00
|
|
|
return maxChildren;
|
|
|
|
}
|
2011-06-10 20:10:50 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
boolean isAI() {
|
2011-06-10 20:10:50 -07:00
|
|
|
return isAI;
|
|
|
|
}
|
2011-07-03 20:49:39 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
boolean isOpp() {
|
2011-07-03 20:49:39 -07:00
|
|
|
return !isAI;
|
|
|
|
}
|
2011-06-10 20:10:50 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
void setIsAI(final boolean ai) {
|
2011-06-10 20:10:50 -07:00
|
|
|
this.isAI = ai;
|
|
|
|
}
|
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
boolean isSolved() {
|
2011-06-10 20:10:50 -07:00
|
|
|
return evalScore == Integer.MAX_VALUE || evalScore == Integer.MIN_VALUE;
|
|
|
|
}
|
2011-04-07 02:19:23 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
void updateScore(final MCTSGameTree child, final double delta) {
|
2011-07-01 19:58:14 -07:00
|
|
|
final double oldMean = (numSim > 0) ? sum/numSim : 0;
|
2011-07-01 01:39:02 -07:00
|
|
|
sum += delta;
|
2011-04-10 07:35:57 -07:00
|
|
|
numSim += 1;
|
2011-07-01 01:39:02 -07:00
|
|
|
final double newMean = sum/numSim;
|
2011-07-01 19:58:14 -07:00
|
|
|
S += (delta - oldMean) * (delta - newMean);
|
2011-07-01 01:39:02 -07:00
|
|
|
|
2011-07-01 19:58:14 -07:00
|
|
|
//if child has sufficient simulations, backup using robust max instead of average
|
|
|
|
if (child != null && child.getNumSim() > maxChildSim) {
|
|
|
|
maxChildSim = child.getNumSim();
|
2011-07-11 07:30:20 -07:00
|
|
|
sum = child.sum;
|
|
|
|
numSim = child.numSim;
|
2011-07-01 01:39:02 -07:00
|
|
|
}
|
2011-06-27 23:12:17 -07:00
|
|
|
}
|
2011-07-03 20:04:10 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
double getUCT() {
|
2011-07-12 01:34:56 -07:00
|
|
|
return getV() + MCTSAI.UCB1_C * Math.sqrt(Math.log(parent.getNumSim()) / getNumSim());
|
2011-07-03 20:04:10 -07:00
|
|
|
}
|
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
private double getRatio() {
|
2011-07-12 01:34:56 -07:00
|
|
|
return (getSum() + MCTSAI.RATIO_K)/(getNumSim() + 2*MCTSAI.RATIO_K);
|
2011-07-03 20:04:10 -07:00
|
|
|
}
|
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
private double getNormal() {
|
2011-07-04 01:17:20 -07:00
|
|
|
return Math.max(1.0, getV() + 2 * Math.sqrt(getVar()));
|
2011-07-03 20:04:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//decrease score of lose node, boost score of win nodes
|
2011-09-10 06:04:08 -07:00
|
|
|
double modify(final double sc) {
|
2011-07-03 20:04:10 -07:00
|
|
|
if ((!parent.isAI() && isAIWin()) || (parent.isAI() && isAILose())) {
|
|
|
|
return sc - 2.0;
|
|
|
|
} else if ((parent.isAI() && isAIWin()) || (!parent.isAI() && isAILose())) {
|
|
|
|
return sc + 2.0;
|
|
|
|
} else {
|
|
|
|
return sc;
|
|
|
|
}
|
|
|
|
}
|
2011-06-27 23:12:17 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
private double getVar() {
|
2011-06-27 23:12:17 -07:00
|
|
|
final int MIN_SAMPLES = 10;
|
|
|
|
if (numSim < MIN_SAMPLES) {
|
2011-07-01 01:39:02 -07:00
|
|
|
return 1.0;
|
2011-06-27 23:12:17 -07:00
|
|
|
} else {
|
|
|
|
return S/(numSim - 1);
|
|
|
|
}
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
boolean isAIWin() {
|
2011-06-10 20:10:50 -07:00
|
|
|
return evalScore == Integer.MAX_VALUE;
|
|
|
|
}
|
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
boolean isAILose() {
|
2011-06-10 20:10:50 -07:00
|
|
|
return evalScore == Integer.MIN_VALUE;
|
|
|
|
}
|
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
void incLose(final int lsteps) {
|
2011-06-10 20:10:50 -07:00
|
|
|
numLose++;
|
2011-07-03 20:49:39 -07:00
|
|
|
steps = Math.max(steps, lsteps);
|
2011-06-10 20:10:50 -07:00
|
|
|
if (numLose == maxChildren) {
|
|
|
|
if (isAI) {
|
2011-07-03 20:49:39 -07:00
|
|
|
setAILose(steps);
|
2011-06-10 20:10:50 -07:00
|
|
|
} else {
|
2011-07-03 20:49:39 -07:00
|
|
|
setAIWin(steps);
|
2011-06-10 20:10:50 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
int getChoice() {
|
2011-04-03 20:07:36 -07:00
|
|
|
return choice;
|
|
|
|
}
|
2011-06-24 21:35:41 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
int getSteps() {
|
2011-06-24 21:35:41 -07:00
|
|
|
return steps;
|
|
|
|
}
|
2011-04-03 20:07:36 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
void setAIWin(final int aSteps) {
|
2011-06-24 21:35:41 -07:00
|
|
|
this.evalScore = Integer.MAX_VALUE;
|
2011-09-09 22:36:00 -07:00
|
|
|
this.steps = aSteps;
|
2011-06-10 20:10:50 -07:00
|
|
|
}
|
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
void setAILose(final int aSteps) {
|
2011-06-10 20:10:50 -07:00
|
|
|
evalScore = Integer.MIN_VALUE;
|
2011-09-09 22:36:00 -07:00
|
|
|
this.steps = aSteps;
|
2011-06-10 20:10:50 -07:00
|
|
|
}
|
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
private int getEvalScore() {
|
2011-04-07 23:44:28 -07:00
|
|
|
return evalScore;
|
|
|
|
}
|
2011-07-01 01:39:02 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
double getDecision() {
|
2011-07-01 01:39:02 -07:00
|
|
|
//boost decision score of win nodes by BOOST
|
2011-06-29 00:47:57 -07:00
|
|
|
final int BOOST = 1000000;
|
2011-06-10 20:10:50 -07:00
|
|
|
if (isAIWin()) {
|
2011-06-29 00:47:57 -07:00
|
|
|
return BOOST + getNumSim();
|
2011-06-10 20:10:50 -07:00
|
|
|
} else if (isAILose()) {
|
2011-06-14 00:24:07 -07:00
|
|
|
return getNumSim();
|
2011-06-10 20:10:50 -07:00
|
|
|
} else {
|
|
|
|
return getNumSim();
|
|
|
|
}
|
|
|
|
}
|
2011-06-09 23:23:20 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
int getNumSim() {
|
2011-04-03 20:07:36 -07:00
|
|
|
return numSim;
|
|
|
|
}
|
2011-07-01 01:39:02 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
private double getSum() {
|
2011-07-04 01:17:20 -07:00
|
|
|
return parent.isAI() ? sum : 1.0 - sum;
|
|
|
|
}
|
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
private double getAvg() {
|
2011-07-04 01:17:20 -07:00
|
|
|
return sum / numSim;
|
2011-07-01 01:39:02 -07:00
|
|
|
}
|
2011-04-03 20:07:36 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
double getV() {
|
2011-07-04 01:17:20 -07:00
|
|
|
return getSum() / numSim;
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
2011-06-30 06:00:00 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
private double getSecureScore() {
|
2011-07-04 01:17:20 -07:00
|
|
|
return getV() + 1.0/Math.sqrt(numSim);
|
2011-04-10 07:35:57 -07:00
|
|
|
}
|
2011-04-03 20:07:36 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
void addChild(final MCTSGameTree child) {
|
2011-06-14 19:40:21 -07:00
|
|
|
assert children.size() < maxChildren : "ERROR! Number of children nodes exceed maxChildren";
|
2011-04-03 20:07:36 -07:00
|
|
|
children.add(child);
|
|
|
|
}
|
2011-04-10 07:35:57 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
MCTSGameTree first() {
|
2011-04-10 07:35:57 -07:00
|
|
|
return children.get(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Iterator<MCTSGameTree> iterator() {
|
|
|
|
return children.iterator();
|
|
|
|
}
|
2011-04-03 20:07:36 -07:00
|
|
|
|
2011-09-10 06:04:08 -07:00
|
|
|
int size() {
|
2011-04-03 20:07:36 -07:00
|
|
|
return children.size();
|
|
|
|
}
|
|
|
|
}
|
2011-06-09 21:03:27 -07:00
|
|
|
|