magarena/src/magic/ai/MTDF.java

183 lines
5.8 KiB
Java
Raw Normal View History

2013-03-07 19:02:14 -08:00
package magic.ai;
import magic.model.MagicGame;
import magic.model.event.MagicEvent;
2013-03-07 19:02:14 -08:00
import magic.model.MagicPlayer;
2015-02-20 07:13:52 -08:00
import magic.model.MagicGameLog;
import magic.exception.GameException;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
2013-03-07 19:02:14 -08:00
/*
* MTD(f) algorithm by Aske Plaat
* https://askeplaat.wordpress.com/534-2/mtdf-algorithm/
2013-03-07 19:02:14 -08:00
*/
public class MTDF implements MagicAI {
private static final int MAX_SEARCH_DEPTH = 100;
private final Map<Long,TTEntry> table = new HashMap<>();
2013-03-07 19:02:14 -08:00
public Object[] findNextEventChoiceResults(final MagicGame sourceGame, final MagicPlayer scorePlayer) {
final int artificialLevel = sourceGame.getArtificialLevel(scorePlayer.getIndex());
2015-02-20 07:13:52 -08:00
final long startTime = System.currentTimeMillis();
final long endTime = startTime + artificialLevel * 1000;
final MagicGame root = new MagicGame(sourceGame, scorePlayer);
//root.setMainPhases(artificialLevel);
//root.setFastChoices(true);
final MagicEvent event = root.getNextEvent();
final List<Object[]> choices = event.getArtificialChoiceResults(root);
if (choices.size() == 1) {
return sourceGame.map(choices.get(0));
}
2015-02-20 07:13:52 -08:00
final TTEntry entry = iterative_deepening(root, endTime);
// Logging.
final long timeTaken = System.currentTimeMillis() - startTime;
2015-02-20 17:48:07 -08:00
log("MTDF" +
2015-02-20 07:13:52 -08:00
" index=" + scorePlayer.getIndex() +
" life=" + scorePlayer.getLife() +
" turn=" + sourceGame.getTurn() +
" phase=" + sourceGame.getPhase().getType() +
" time=" + timeTaken
);
2015-02-20 07:13:52 -08:00
final Object[] chosen = choices.get(entry.chosen);
for (final Object[] result : choices) {
final StringBuilder buf = new StringBuilder();
ArtificialChoiceResults.appendResult(result, buf);
log((result == chosen ? "* " : " ") + buf);
}
2015-02-20 07:13:52 -08:00
return sourceGame.map(chosen);
2013-03-07 19:02:14 -08:00
}
private TTEntry iterative_deepening(final MagicGame root, final long end) {
int firstguess = 1;
for (int d = 1; d <= MAX_SEARCH_DEPTH; d++) {
table.clear();
firstguess = MTDF(root, firstguess, d);
if (System.currentTimeMillis() > end) {
break;
}
}
return table.get(root.getStateId());
}
private int MTDF(final MagicGame root, int f, int d) {
int g = f;
int lowerbound = Integer.MIN_VALUE;
int upperbound = Integer.MAX_VALUE;
2015-02-19 21:16:13 -08:00
while (lowerbound < upperbound) {
int beta = (g == lowerbound) ? g + 1 : g;
g = AlphaBetaWithMemory(root, beta - 1, beta, d);
if (g < beta) {
upperbound = g;
} else {
lowerbound = g;
}
2015-02-19 21:16:13 -08:00
}
return g;
}
private int AlphaBetaWithMemory(final MagicGame game, int alpha, int beta, int d) {
/* Transposition table lookup */
final long id = game.getStateId();
TTEntry entry = table.get(id);
if (entry != null) {
if (entry.lowerbound >= beta) {
return entry.lowerbound;
}
if (entry.upperbound <= alpha) {
return entry.upperbound;
}
alpha = Math.max(alpha, entry.lowerbound);
beta = Math.min(beta, entry.upperbound);
} else {
entry = new TTEntry();
table.put(id, entry);
}
if (d == 0 || game.isFinished()) {
/* leaf node */
int g = game.getScore();
entry.update(g, alpha, beta);
return g;
}
final MagicEvent event = game.getNextEvent();
final List<Object[]> results = event.getArtificialChoiceResults(game);
final boolean isMax = game.getScorePlayer() == event.getPlayer();
final boolean isMin = !isMax;
int g = isMax ? Integer.MIN_VALUE : Integer.MAX_VALUE;
int a = alpha; /* save original alpha value */
int b = beta; /* save original beta value */
int idx = -1;
for (final Object[] result : results) {
if ((isMax && g >= beta) ||
(isMin && g <= alpha)) {
break;
}
idx++;
game.snapshot();
game.executeNextEvent(result);
game.advanceToNextEventWithChoice();
final int g_child = AlphaBetaWithMemory(game, a, b, d - 1);
if ((isMax && g_child > g) ||
(isMin && g_child < g)) {
g = g_child;
entry.chosen = idx;
}
game.restore();
if (isMax) {
a = Math.max(a, g);
} else {
b = Math.min(b, g);
}
}
final long id_check = game.getStateId();
if (id != id_check) {
table.put(id_check, entry);
table.remove(id);
}
entry.update(g, alpha, beta);
return g;
}
2015-02-20 07:13:52 -08:00
private void log(final String message) {
MagicGameLog.log(message);
}
}
class TTEntry {
int lowerbound = Integer.MIN_VALUE;
int upperbound = Integer.MAX_VALUE;
int chosen = -1;
void update(int g, int alpha, int beta) {
/* Traditional transposition table storing of bounds */
/* Fail low result implies an upper bound */
if (g <= alpha) {
upperbound = g;
}
/* Found an accurate minimax value - will not occur if called with zero window */
if (g > alpha && g < beta) {
lowerbound = g;
upperbound = g;
}
/* Fail high result implies a lower bound */
if (g >= beta) {
lowerbound = g;
}
}
2013-03-07 19:02:14 -08:00
}