2011-04-03 20:07:36 -07:00
|
|
|
package magic.ai;
|
|
|
|
|
2011-06-09 21:03:27 -07:00
|
|
|
import java.util.*;
|
2011-04-03 20:07:36 -07:00
|
|
|
|
|
|
|
import magic.model.MagicGame;
|
|
|
|
import magic.model.phase.MagicPhase;
|
|
|
|
import magic.model.MagicPlayer;
|
|
|
|
import magic.model.event.MagicEvent;
|
2011-05-27 00:36:29 -07:00
|
|
|
import magic.model.MagicRandom;
|
2011-04-03 20:07:36 -07:00
|
|
|
|
|
|
|
/*
|
|
|
|
UCT algorithm from Kocsis and Sezepesvari 2006
|
|
|
|
|
|
|
|
function playOneSeq(root)
|
|
|
|
nodes = [root]
|
|
|
|
while (nodes.last is not leaf) do
|
|
|
|
nodes append descendByUCB1(node.last)
|
|
|
|
//assume value of leaf nodes are known
|
|
|
|
//node.init is all elements except the last one
|
|
|
|
updateValue(nodes.init, -nodes.last.value)
|
|
|
|
|
|
|
|
function descendByUCB1(node)
|
|
|
|
nb = sum of nb in node's children
|
|
|
|
for each node n in node's children
|
|
|
|
if n.nb = 0
|
|
|
|
v[n] = infinity
|
|
|
|
else
|
|
|
|
v[n] = 1 - n.value/n.nb + sqrt(2 * log(nb) / n.nb)
|
|
|
|
return n that maximizes v[n]
|
|
|
|
|
|
|
|
function updateValue(nodes, value)
|
|
|
|
for each node n in nodes
|
|
|
|
n.value += value
|
|
|
|
n.nb += 1
|
|
|
|
value = 1 - value
|
|
|
|
|
|
|
|
Modified UCT for MoGO in Wang and Gelly 2007
|
|
|
|
|
|
|
|
function playOneGame(state)
|
|
|
|
create node root from current game state
|
|
|
|
init tree to empty tree
|
|
|
|
while there is time and memory
|
|
|
|
//build the game tree one node at a time
|
|
|
|
playOneSeqMC(root, tree)
|
|
|
|
return descendByUCB1(root)
|
|
|
|
|
|
|
|
function playOneSeqMC(root, tree)
|
|
|
|
nodes = [root]
|
|
|
|
while (nodes.last is not in the tree)
|
|
|
|
nodes append descendByUCB1(node.last)
|
|
|
|
tree add nodes.last
|
|
|
|
nodes.last.value = getValueByMC(nodes.last)
|
|
|
|
updateValue(nodes.init, -nodes.last.value)
|
|
|
|
|
|
|
|
function getValueByMC(node)
|
|
|
|
play one random game starting from node
|
|
|
|
return 1 if player 1 (max) wins, 0 if player 2 wins (min)
|
|
|
|
*/
|
|
|
|
|
|
|
|
//AI using Monte Carlo Tree Search
|
|
|
|
public class MCTSAI implements MagicAI {
|
2011-06-06 02:05:09 -07:00
|
|
|
|
2011-06-05 21:29:44 -07:00
|
|
|
private final List<Integer> simLengths = new LinkedList<Integer>();
|
2011-04-08 20:05:09 -07:00
|
|
|
private final boolean LOGGING;
|
2011-06-07 05:57:29 -07:00
|
|
|
private final boolean CHEAT;
|
2011-06-05 21:29:44 -07:00
|
|
|
private int MAXTIME;
|
|
|
|
private long STARTTIME;
|
2011-06-09 23:09:02 -07:00
|
|
|
private boolean USE_CACHE = true;
|
2011-06-09 23:28:47 -07:00
|
|
|
private static final int MAXEVENTS = 1000;
|
|
|
|
//higher C -> more exploration less exploitation
|
|
|
|
static final double C = 0.3;
|
2011-04-03 20:07:36 -07:00
|
|
|
|
2011-06-09 21:03:27 -07:00
|
|
|
//store the top 10000 most used nodes
|
|
|
|
private final CacheNode cache = new CacheNode(10000);
|
|
|
|
|
2011-04-08 20:05:09 -07:00
|
|
|
public MCTSAI() {
|
2011-06-07 05:57:29 -07:00
|
|
|
this(false, true);
|
2011-04-08 20:05:09 -07:00
|
|
|
}
|
|
|
|
|
2011-06-07 05:57:29 -07:00
|
|
|
public MCTSAI(boolean printLog, boolean cheat) {
|
2011-04-08 20:05:09 -07:00
|
|
|
LOGGING = printLog;
|
2011-06-07 05:57:29 -07:00
|
|
|
CHEAT = cheat;
|
2011-04-08 20:05:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private void log(final String 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-04-10 02:44:39 -07:00
|
|
|
private void logi(final int num) {
|
|
|
|
if (LOGGING) {
|
2011-06-05 19:45:45 -07:00
|
|
|
System.err.print(num);
|
2011-04-10 02:44:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-08 20:05:09 -07:00
|
|
|
private void logc(final char message) {
|
2011-04-05 22:27:39 -07:00
|
|
|
if (LOGGING) {
|
2011-06-05 19:45:45 -07:00
|
|
|
System.err.print(message);
|
2011-04-05 22:27:39 -07:00
|
|
|
}
|
|
|
|
}
|
2011-06-09 21:03:27 -07:00
|
|
|
|
2011-06-09 23:09:02 -07:00
|
|
|
|
|
|
|
private void addNode(final long gid, final MCTSGameTree node) {
|
|
|
|
if (USE_CACHE) {
|
|
|
|
cache.put(gid, node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-09 21:03:27 -07:00
|
|
|
private MCTSGameTree getNode(long gid) {
|
2011-06-09 23:09:02 -07:00
|
|
|
if (USE_CACHE && cache.containsKey(gid)) {
|
2011-06-09 21:03:27 -07:00
|
|
|
return cache.get(gid);
|
|
|
|
} else {
|
|
|
|
return new MCTSGameTree(-1, -1);
|
|
|
|
}
|
|
|
|
}
|
2011-04-05 22:27:39 -07:00
|
|
|
|
2011-06-09 23:28:47 -07:00
|
|
|
private double UCT(final boolean isMax, final MCTSGameTree parent, final MCTSGameTree child) {
|
|
|
|
return (isMax ? 1.0 : -1.0) * child.getV() + C * Math.sqrt(Math.log(parent.getNumSim()) / child.getNumSim());
|
|
|
|
}
|
|
|
|
|
2011-06-09 21:03:27 -07:00
|
|
|
private MCTSGameTree getNode(final long gid, final int choice, final int evalScore) {
|
|
|
|
final MCTSGameTree node = getNode(gid);
|
|
|
|
if (node.getEvalScore() == evalScore && node.getChoice() == choice) {
|
|
|
|
return node;
|
|
|
|
} else {
|
|
|
|
return new MCTSGameTree(choice, evalScore);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-03 20:07:36 -07:00
|
|
|
public synchronized Object[] findNextEventChoiceResults(
|
|
|
|
final MagicGame game,
|
|
|
|
final MagicPlayer scorePlayer) {
|
2011-06-06 19:32:35 -07:00
|
|
|
//ArtificialLevel = number of seconds to run MCTSAI
|
|
|
|
MAXTIME = 1000 * game.getArtificialLevel();
|
2011-06-05 21:29:44 -07:00
|
|
|
STARTTIME = System.currentTimeMillis();
|
2011-04-08 20:00:06 -07:00
|
|
|
final String pinfo = "MCTS " + scorePlayer.getIndex() + " (" + scorePlayer.getLife() + ")";
|
2011-04-05 22:27:39 -07:00
|
|
|
final List<Object[]> choices = getCR(game, scorePlayer);
|
2011-04-03 20:07:36 -07:00
|
|
|
final int size = choices.size();
|
|
|
|
|
|
|
|
// No choice results
|
|
|
|
if (size == 0) {
|
2011-04-04 20:31:42 -07:00
|
|
|
log(pinfo + " NO CHOICE");
|
|
|
|
return null;
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Single choice result
|
|
|
|
if (size == 1) {
|
2011-04-05 22:27:39 -07:00
|
|
|
final ArtificialChoiceResults selected = getACR(choices).get(0);
|
|
|
|
log(pinfo + " " + selected);
|
|
|
|
return game.map(selected.choiceResults);
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// repeat a number of simulations
|
|
|
|
// each simulation does the following
|
|
|
|
// selects a path down the game tree and create a new leaf
|
|
|
|
// score the leaf by doing a random play to the end of the game
|
|
|
|
// update the score of all the ancestors of the leaf
|
2011-04-05 22:27:39 -07:00
|
|
|
// return the "best" choice
|
2011-04-07 23:44:28 -07:00
|
|
|
|
2011-04-03 20:07:36 -07:00
|
|
|
//root represents the start state
|
2011-06-09 21:03:27 -07:00
|
|
|
final MCTSGameTree root = getNode(game.getGameId());
|
2011-04-10 02:44:39 -07:00
|
|
|
int numSim = 0;
|
2011-06-05 21:29:44 -07:00
|
|
|
simLengths.clear();
|
|
|
|
for (int i = 1; System.currentTimeMillis() - STARTTIME < MAXTIME; i++) {
|
2011-04-05 22:27:39 -07:00
|
|
|
//create a new MagicGame for simulation
|
2011-04-09 01:43:39 -07:00
|
|
|
final MagicGame start = new MagicGame(game, scorePlayer);
|
2011-06-07 05:57:29 -07:00
|
|
|
if (!CHEAT) {
|
|
|
|
start.setKnownCards();
|
|
|
|
}
|
2011-04-03 20:07:36 -07:00
|
|
|
|
2011-04-05 22:27:39 -07:00
|
|
|
//pass in a clone of the state, genNewTreeNode grows the tree by one node
|
|
|
|
//and returns the path from the root to the new node
|
|
|
|
final List<MCTSGameTree> path = genNewTreeNode(root, start);
|
2011-04-03 20:07:36 -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-04-10 02:44:39 -07:00
|
|
|
final double score = randomPlay(start);
|
|
|
|
logc((score > 0.0) ? '.' : 'X');
|
2011-04-05 22:27:39 -07:00
|
|
|
for (MCTSGameTree node : path) {
|
2011-04-03 20:07:36 -07:00
|
|
|
node.updateScore(score);
|
|
|
|
}
|
2011-04-10 02:44:39 -07:00
|
|
|
|
|
|
|
numSim++;
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
2011-04-05 22:27:39 -07:00
|
|
|
logc('\n');
|
2011-04-03 20:07:36 -07:00
|
|
|
|
2011-04-10 07:35:57 -07:00
|
|
|
//select the best choice (child that has the highest secure score)
|
2011-06-07 19:54:04 -07:00
|
|
|
final MCTSGameTree first = root.first();
|
|
|
|
int maxV = first.getNumSim();
|
|
|
|
int bestC = first.getChoice();
|
2011-04-05 22:27:39 -07:00
|
|
|
final List<ArtificialChoiceResults> achoices = getACR(choices);
|
2011-04-07 02:19:23 -07:00
|
|
|
for (MCTSGameTree node : root) {
|
2011-04-10 07:35:57 -07:00
|
|
|
achoices.get(node.getChoice()).worker = (int)(node.getV() * 100);
|
2011-04-05 22:27:39 -07:00
|
|
|
achoices.get(node.getChoice()).gameCount = node.getNumSim();
|
2011-06-07 19:54:04 -07:00
|
|
|
final int V = node.getNumSim();
|
|
|
|
final int C = node.getChoice();
|
|
|
|
if (V > maxV) {
|
|
|
|
maxV = V;
|
|
|
|
bestC = C;
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
|
|
|
}
|
2011-04-07 23:44:28 -07:00
|
|
|
|
2011-06-05 21:29:44 -07:00
|
|
|
final long duration = System.currentTimeMillis() - STARTTIME;
|
2011-06-06 02:36:16 -07:00
|
|
|
log("MCTS: time: " + duration + " sims: " + numSim);
|
2011-06-05 21:29:44 -07:00
|
|
|
|
|
|
|
if (LOGGING) {
|
|
|
|
int minL = 1000000;
|
|
|
|
int maxL = -1;
|
|
|
|
int sumL = 0;
|
|
|
|
for (int len : simLengths) {
|
|
|
|
sumL += len;
|
|
|
|
if (len > maxL) maxL = len;
|
|
|
|
if (len < minL) minL = len;
|
|
|
|
}
|
|
|
|
log("min: " + minL + " max: " + maxL + " avg: " + (sumL / simLengths.size()));
|
|
|
|
}
|
2011-04-03 20:07:36 -07:00
|
|
|
|
2011-06-09 21:03:27 -07:00
|
|
|
if (LOGGING) {
|
|
|
|
log(pinfo);
|
|
|
|
final ArtificialChoiceResults selected = (bestC >= 0) ? achoices.get(bestC) : null;
|
|
|
|
for (final ArtificialChoiceResults achoice : achoices) {
|
|
|
|
log((achoice == selected ? "* ":" ") + achoice);
|
|
|
|
}
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
2011-06-09 21:03:27 -07:00
|
|
|
|
|
|
|
return game.map(choices.get(bestC));
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
|
|
|
|
2011-04-05 22:27:39 -07:00
|
|
|
private List<Object[]> getCR(final MagicGame game, final MagicPlayer player) {
|
2011-04-03 20:07:36 -07:00
|
|
|
final MagicGame choiceGame = new MagicGame(game, player);
|
|
|
|
final MagicEvent event = choiceGame.getNextEvent();
|
|
|
|
return event.getArtificialChoiceResults(choiceGame);
|
|
|
|
}
|
|
|
|
|
2011-04-05 22:27:39 -07:00
|
|
|
private List<ArtificialChoiceResults> getACR(final List<Object[]> choices) {
|
|
|
|
final List<ArtificialChoiceResults> aiChoiceResultsList =
|
|
|
|
new ArrayList<ArtificialChoiceResults>();
|
2011-04-03 20:07:36 -07:00
|
|
|
for (final Object choiceResults[] : choices) {
|
|
|
|
aiChoiceResultsList.add(new ArtificialChoiceResults(choiceResults));
|
|
|
|
}
|
|
|
|
return aiChoiceResultsList;
|
|
|
|
}
|
|
|
|
|
|
|
|
// p is parent of n
|
|
|
|
// n.nb is how many times the node n is simulated
|
|
|
|
// sum of nb in all children of parent of n (same as p.nb)
|
|
|
|
// select node n (child of node) that maximize v[n]
|
2011-04-10 02:44:39 -07:00
|
|
|
// where v[n] = 1 - n.value/n.nb + C * sqrt(log(nb) / n.nb)
|
2011-04-04 20:31:42 -07:00
|
|
|
// find a path from root to an unexplored node
|
2011-04-06 21:31:41 -07:00
|
|
|
private List<MCTSGameTree> genNewTreeNode(final MCTSGameTree root, final MagicGame game) {
|
|
|
|
final List<MCTSGameTree> path = new LinkedList<MCTSGameTree>();
|
2011-04-05 22:27:39 -07:00
|
|
|
MCTSGameTree curr = root;
|
|
|
|
path.add(curr);
|
2011-04-04 20:31:42 -07:00
|
|
|
|
2011-06-06 18:53:35 -07:00
|
|
|
for (List<Object[]> choices = getNextMultiChoiceEvent(game, curr != root, false);
|
2011-06-04 01:50:52 -07:00
|
|
|
choices != null;
|
2011-06-06 18:53:35 -07:00
|
|
|
choices = getNextMultiChoiceEvent(game, curr != root, false)) {
|
2011-06-04 01:50:52 -07:00
|
|
|
|
|
|
|
final MagicEvent event = game.getNextEvent();
|
2011-04-07 02:19:23 -07:00
|
|
|
assert choices.size() > 1 : "number of choices is " + choices.size();
|
2011-04-05 22:27:39 -07:00
|
|
|
|
|
|
|
if (curr.size() < choices.size()) {
|
|
|
|
//there are unexplored children of node
|
|
|
|
//assume we explore children of a node in increasing order of the choices
|
2011-04-07 02:19:23 -07:00
|
|
|
game.executeNextEvent(choices.get(curr.size()));
|
2011-06-09 21:03:27 -07:00
|
|
|
|
|
|
|
final MCTSGameTree child = getNode(game.getGameId(), curr.size(), game.getScore());
|
|
|
|
if (event.getPlayer() == game.getScorePlayer()) {
|
2011-06-09 23:09:02 -07:00
|
|
|
addNode(game.getGameId(), child);
|
2011-06-09 21:03:27 -07:00
|
|
|
}
|
|
|
|
|
2011-04-05 22:27:39 -07:00
|
|
|
curr.addChild(child);
|
|
|
|
path.add(child);
|
|
|
|
return path;
|
|
|
|
} else {
|
2011-06-09 22:36:56 -07:00
|
|
|
while (curr.size() > choices.size()) {
|
|
|
|
System.err.println("ERROR! MCTS: Invalid node encountered");
|
|
|
|
curr.removeLast();
|
|
|
|
}
|
|
|
|
|
|
|
|
//curr.size() == choices.size()
|
|
|
|
|
2011-06-09 23:23:20 -07:00
|
|
|
final boolean isMax = game.getScorePlayer() == event.getPlayer();
|
2011-06-09 22:31:42 -07:00
|
|
|
final List<MCTSGameTree> invalid = new LinkedList<MCTSGameTree>();
|
2011-06-09 23:28:47 -07:00
|
|
|
|
|
|
|
MCTSGameTree child = curr.first();
|
|
|
|
double bestV = UCT(isMax, curr, child);
|
2011-04-07 02:19:23 -07:00
|
|
|
for (MCTSGameTree node : curr) {
|
2011-04-08 20:00:06 -07:00
|
|
|
if (node.getChoice() >= choices.size()) {
|
2011-06-09 22:31:42 -07:00
|
|
|
invalid.add(node);
|
2011-04-08 20:00:06 -07:00
|
|
|
continue;
|
|
|
|
}
|
2011-06-09 23:28:47 -07:00
|
|
|
final double v = UCT(isMax, curr, child);
|
2011-04-08 18:47:53 -07:00
|
|
|
if (v > bestV) {
|
2011-04-05 22:27:39 -07:00
|
|
|
bestV = v;
|
2011-04-06 21:31:41 -07:00
|
|
|
child = node;
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
|
|
|
}
|
2011-04-06 21:31:41 -07:00
|
|
|
|
|
|
|
//move down the tree
|
|
|
|
curr = child;
|
2011-04-08 18:47:53 -07:00
|
|
|
|
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
|
|
|
|
|
|
|
//game is finished
|
2011-04-07 02:19:23 -07:00
|
|
|
assert game.isFinished() : "game is not finished";
|
2011-04-04 20:31:42 -07:00
|
|
|
return path;
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
|
|
|
|
2011-04-10 02:44:39 -07:00
|
|
|
private double randomPlay(final MagicGame game) {
|
2011-06-06 02:05:09 -07:00
|
|
|
// empirical evidence suggest that number of events should be less than 300
|
2011-06-06 18:53:35 -07:00
|
|
|
final List<Object[]> elist = getNextMultiChoiceEvent(game, true, true);
|
|
|
|
final int events = (Integer)(elist.get(0)[0]);
|
2011-04-08 20:00:06 -07:00
|
|
|
|
|
|
|
if (game.getLosingPlayer() == null) {
|
|
|
|
return 0;
|
|
|
|
} else if (game.getLosingPlayer() == game.getScorePlayer()) {
|
2011-06-06 21:43:37 -07:00
|
|
|
return -(1.0 - events/300.0);
|
2011-04-05 22:27:39 -07:00
|
|
|
} else {
|
2011-06-06 21:43:37 -07:00
|
|
|
return 1.0 - events/300.0;
|
2011-04-05 22:27:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-06 18:53:35 -07:00
|
|
|
private List<Object[]> getNextMultiChoiceEvent(MagicGame game, boolean fastChoices, boolean sim) {
|
2011-04-05 22:27:39 -07:00
|
|
|
game.setFastChoices(fastChoices);
|
2011-04-07 23:44:28 -07:00
|
|
|
|
2011-06-06 18:53:35 -07:00
|
|
|
int events = 0;
|
|
|
|
|
|
|
|
// simulate game until it is finished or simulated 300 events
|
|
|
|
while (!game.isFinished() && events < MAXEVENTS) {
|
2011-04-07 23:44:28 -07:00
|
|
|
if (!game.hasNextEvent()) {
|
2011-04-05 22:27:39 -07:00
|
|
|
game.getPhase().executePhase(game);
|
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-06 19:32:35 -07:00
|
|
|
final Object[] choice = event.getSimulationChoiceResult(game);
|
|
|
|
if (choice == null) {
|
|
|
|
//invalid game state
|
|
|
|
return Collections.<Object[]>singletonList(new Object[]{-1});
|
|
|
|
} else {
|
|
|
|
game.executeNextEvent(choice);
|
|
|
|
events++;
|
|
|
|
}
|
2011-04-07 23:44:28 -07:00
|
|
|
} else {
|
2011-06-06 18:53:35 -07:00
|
|
|
//get possible AI choices, if more than one return list of choices
|
|
|
|
final List<Object[]> choices = event.getArtificialChoiceResults(game);
|
|
|
|
final int size = choices.size();
|
|
|
|
if (size == 0) {
|
|
|
|
//invalid game state
|
|
|
|
return null;
|
|
|
|
} else if (size == 1) {
|
|
|
|
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
|
|
|
|
|
|
|
if (LOGGING) {
|
|
|
|
simLengths.add(events);
|
|
|
|
}
|
2011-04-07 23:44:28 -07:00
|
|
|
|
2011-04-05 22:27:39 -07:00
|
|
|
//game is finished
|
2011-06-06 18:53:35 -07:00
|
|
|
if (sim) {
|
|
|
|
return Collections.<Object[]>singletonList(new Object[]{events});
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//only store one copy of MagicGame
|
|
|
|
//each tree node stores the choice from the parent that leads to this node
|
|
|
|
//so we only need one copy of MagicGame for MCTSAI
|
2011-04-07 02:19:23 -07:00
|
|
|
class MCTSGameTree implements Iterable<MCTSGameTree> {
|
2011-06-09 23:23:20 -07:00
|
|
|
|
2011-04-03 20:07:36 -07:00
|
|
|
private final int choice;
|
2011-06-09 22:36:56 -07:00
|
|
|
private final LinkedList<MCTSGameTree> children = new LinkedList<MCTSGameTree>();
|
2011-04-03 20:07:36 -07:00
|
|
|
private int numSim = 0;
|
2011-04-10 02:44:39 -07:00
|
|
|
private double score = 0;
|
2011-04-07 23:44:28 -07:00
|
|
|
private int evalScore = 0;
|
2011-04-03 20:07:36 -07:00
|
|
|
|
2011-04-07 23:44:28 -07:00
|
|
|
public MCTSGameTree(int choice, int evalScore) {
|
|
|
|
this.evalScore = evalScore;
|
2011-04-03 20:07:36 -07:00
|
|
|
this.choice = choice;
|
|
|
|
}
|
2011-04-07 02:19:23 -07:00
|
|
|
|
2011-04-10 07:35:57 -07:00
|
|
|
public void updateScore(final double score) {
|
|
|
|
this.score += score;
|
|
|
|
numSim += 1;
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public int getChoice() {
|
|
|
|
return choice;
|
|
|
|
}
|
|
|
|
|
2011-04-07 23:44:28 -07:00
|
|
|
public int getEvalScore() {
|
|
|
|
return evalScore;
|
|
|
|
}
|
|
|
|
|
2011-04-10 02:44:39 -07:00
|
|
|
public double getScore() {
|
2011-04-03 20:07:36 -07:00
|
|
|
return score;
|
|
|
|
}
|
2011-06-09 23:23:20 -07:00
|
|
|
|
2011-04-03 20:07:36 -07:00
|
|
|
public int getNumSim() {
|
|
|
|
return numSim;
|
|
|
|
}
|
|
|
|
|
|
|
|
public double getV() {
|
2011-04-10 02:44:39 -07:00
|
|
|
return score / numSim;
|
2011-04-03 20:07:36 -07:00
|
|
|
}
|
2011-04-10 07:35:57 -07:00
|
|
|
|
|
|
|
public double getSecureScore() {
|
|
|
|
return getV() + 1.0/Math.sqrt(getNumSim());
|
|
|
|
}
|
2011-04-03 20:07:36 -07:00
|
|
|
|
2011-04-05 22:27:39 -07:00
|
|
|
public void addChild(MCTSGameTree child) {
|
2011-04-03 20:07:36 -07:00
|
|
|
children.add(child);
|
|
|
|
}
|
2011-04-10 07:35:57 -07:00
|
|
|
|
2011-06-09 22:36:56 -07:00
|
|
|
public void removeLast() {
|
|
|
|
children.removeLast();
|
2011-06-09 22:15:48 -07:00
|
|
|
}
|
|
|
|
|
2011-04-10 07:35:57 -07:00
|
|
|
public MCTSGameTree first() {
|
|
|
|
return children.get(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Iterator<MCTSGameTree> iterator() {
|
|
|
|
return children.iterator();
|
|
|
|
}
|
2011-04-03 20:07:36 -07:00
|
|
|
|
|
|
|
public int size() {
|
|
|
|
return children.size();
|
|
|
|
}
|
|
|
|
}
|
2011-06-09 21:03:27 -07:00
|
|
|
|
|
|
|
|
|
|
|
class CacheNode extends LinkedHashMap<Long, MCTSGameTree> {
|
|
|
|
private final int capacity;
|
|
|
|
public CacheNode(int capacity) {
|
|
|
|
super(capacity + 1, 1.1f, true);
|
|
|
|
this.capacity = capacity;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected boolean removeEldestEntry(Map.Entry eldest) {
|
|
|
|
return size() > capacity;
|
|
|
|
}
|
|
|
|
}
|