magarena/src/magic/ai/MTDF.java

203 lines
6.0 KiB
Java
Raw Normal View History

2013-03-07 19:02:14 -08:00
package magic.ai;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
2020-01-15 12:02:42 -08:00
import magic.model.MagicGame;
import magic.model.MagicGameLog;
import magic.model.MagicPlayer;
import magic.model.event.MagicEvent;
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 extends MagicAI {
2015-02-20 20:39:22 -08:00
private final boolean CHEAT;
private final Map<Long,TTEntry> table = new HashMap<>();
private long END;
2015-02-20 20:39:22 -08:00
public MTDF(final boolean aCheat) {
CHEAT = aCheat;
}
@Override
2013-03-07 19:02:14 -08:00
public Object[] findNextEventChoiceResults(final MagicGame sourceGame, final MagicPlayer scorePlayer) {
final int artificialLevel = scorePlayer.getAiProfile().getAiLevel();
2015-02-20 07:13:52 -08:00
final long startTime = System.currentTimeMillis();
2015-12-31 02:54:52 -08:00
END = startTime + artificialLevel * 1000;
final MagicGame root = new MagicGame(sourceGame, scorePlayer);
//root.setMainPhases(artificialLevel);
2015-12-31 02:54:52 -08:00
2015-02-20 20:39:22 -08:00
if (!CHEAT) {
root.hideHiddenCards();
}
2015-12-31 02:54:52 -08:00
final MagicEvent event = root.getNextEvent();
final List<Object[]> choices = event.getArtificialChoiceResults(root);
if (choices.size() == 1) {
return sourceGame.map(choices.get(0));
}
root.setFastChoices(true);
final TTEntry result = iterative_deepening(root, choices);
2015-12-31 02:54:52 -08:00
2015-02-20 07:13:52 -08:00
// Logging.
final long timeTaken = System.currentTimeMillis() - startTime;
2015-02-20 17:48:07 -08:00
log("MTDF" +
2015-02-20 22:28:24 -08:00
" cheat=" + CHEAT +
2015-02-20 07:13:52 -08:00
" index=" + scorePlayer.getIndex() +
" life=" + scorePlayer.getLife() +
" turn=" + sourceGame.getTurn() +
" phase=" + sourceGame.getPhase().getType() +
" time=" + timeTaken
);
final Object[] chosen = choices.get(result.chosen);
for (final Object[] choice : choices) {
2015-02-20 07:13:52 -08:00
final StringBuilder buf = new StringBuilder();
ArtificialChoiceResults.appendResult(choice, buf);
log((choice == chosen ? "* " : " ") + buf);
2015-02-20 07:13:52 -08:00
}
2015-02-20 07:13:52 -08:00
return sourceGame.map(chosen);
2013-03-07 19:02:14 -08:00
}
private boolean hasTime() {
2015-12-31 02:54:52 -08:00
return System.currentTimeMillis() < END;
}
private TTEntry iterative_deepening(final MagicGame root, final List<Object[]> choices) {
TTEntry result = null;
int firstguess = 0;
for (int d = 1; hasTime(); d++) {
firstguess = MTDF(root, choices, firstguess, d);
if (hasTime()) {
result = table.get(root.getStateId());
}
}
return result;
}
private int MTDF(final MagicGame root, final List<Object[]> choices, int f, int d) {
int g = f;
int lowerbound = Integer.MIN_VALUE;
int upperbound = Integer.MAX_VALUE;
table.clear();
2015-02-19 21:16:13 -08:00
while (lowerbound < upperbound) {
int beta = (g == lowerbound) ? g + 1 : g;
g = AlphaBetaWithMemory(root, choices, 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, final List<Object[]> choices, 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() || !hasTime()) {
/* leaf node */
int g = game.getScore();
entry.update(g, alpha, beta);
return g;
}
final boolean isMax = game.getScorePlayer() == game.getNextEvent().getPlayer();
final boolean isMin = !isMax;
2015-12-31 02:54:52 -08:00
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;
2015-12-31 02:54:52 -08:00
for (final Object[] choice : choices) {
if ((isMax && g >= beta) ||
(isMin && g <= alpha)) {
break;
}
2015-12-31 02:54:52 -08:00
game.snapshot();
game.executeNextEvent(choice);
final List<Object[]> choices_child = d == 1 ?
Collections.<Object[]>emptyList():
game.advanceToNextEventWithChoices();
final int g_child = AlphaBetaWithMemory(game, choices_child, a, b, d - 1);
game.restore();
2015-12-31 02:54:52 -08:00
idx++;
if ((isMax && g_child > g) ||
(isMin && g_child < g)) {
g = g_child;
entry.chosen = idx;
}
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);
}
2015-12-31 02:54:52 -08:00
entry.update(g, alpha, beta);
return g;
}
2015-12-31 02:54:52 -08:00
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;
2015-12-31 02:54:52 -08:00
void update(int g, int alpha, int beta) {
2015-12-31 02:54:52 -08:00
/* Traditional transposition table storing of bounds */
/* Fail low result implies an upper bound */
if (g <= alpha) {
2015-12-31 02:54:52 -08:00
upperbound = g;
}
2015-12-31 02:54:52 -08:00
/* Found an accurate minimax value - will not occur if called with zero window */
if (g > alpha && g < beta) {
2015-12-31 02:54:52 -08:00
lowerbound = g;
upperbound = g;
}
2015-12-31 02:54:52 -08:00
/* Fail high result implies a lower bound */
if (g >= beta) {
2015-12-31 02:54:52 -08:00
lowerbound = g;
}
}
2013-03-07 19:02:14 -08:00
}