Substantial refactoring to implement correct Naive, UCT Monte Carlo tree search methods.

Removed unnecessary distinction between policy and tree search (tree search is a special kind of policy).
Calculation of all valid moves / arbitrary sets of moves is now a seperate class, as it serves a different purpose than a policy.
Introduced regression error in AlphaBeta test.
This commit is contained in:
cs6601
2012-08-28 10:40:37 -04:00
parent 36291171e5
commit bb5990a04f
39 changed files with 550 additions and 431 deletions

View File

@@ -1,5 +0,0 @@
package cs6601.p1;
public class GameController {
}

View File

@@ -1,168 +0,0 @@
package cs6601.p1.generator;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import cs6601.p1.GameConfig;
import cs6601.p1.GameState;
import cs6601.p1.StateEvaluator;
public class AlphaBetaMoveGenerator implements MoveGenerator {
private static final Logger LOGGER = Logger.getLogger(AlphaBetaMoveGenerator.class.getName());
private static final int DEFAULT_RECURSIVE_PLAYS = 3;
private final ValidMoveGenerator validMoveGenerator = new ValidMoveGenerator();
private String bestPick = MoveGenerator.PASS;
@Override
public String genMove(GameConfig gameConfig, GameState gameState,
String initialColor) {
int alpha = Integer.MIN_VALUE;
int beta = Integer.MAX_VALUE;
if ("b".equals(initialColor)) {
getMaxValue(gameConfig,gameState,initialColor,false,DEFAULT_RECURSIVE_PLAYS*2,alpha,beta);
return bestPick;
} else if ("w".equals(initialColor)) {
getMinValue(gameConfig,gameState,initialColor,false,DEFAULT_RECURSIVE_PLAYS*2,alpha,beta);
return bestPick;
} else {
return MoveGenerator.PASS;
}
}
private int getMaxValue(GameConfig gameConfig, GameState gameState,
String initialColor, boolean playAsOpponent, int recursionLevel, int alpha, int beta) {
if (terminalTest(recursionLevel)) {
return getUtility(gameConfig,gameState);
}
String colorPlaying = getColorToPlay(initialColor, playAsOpponent);
List<String> validMoves = validMoveGenerator.genMoves(gameConfig,
gameState, colorPlaying, MoveGenerator.ALL_MOVES);
int value = Integer.MIN_VALUE;
//Map<Integer,String> firstMovesByScore = new HashMap<Integer,String> ();
for (String nextMove : validMoves) {
GameState nextState = new GameState(gameState);
if (!nextState.playStone(colorPlaying, nextMove)) {
throw new RuntimeException("Illegal move attempted during search!");
}
int minValue = getMinValue(gameConfig, nextState,
initialColor, !playAsOpponent, recursionLevel-1, alpha, beta);
//value = Math.max(value, minResult);
if (minValue > value) {
value = minValue;
if (recursionLevel == DEFAULT_RECURSIVE_PLAYS * 2) {
bestPick = nextMove;
}
}
///
//if (recursionLevel == 2 * DEFAULT_RECURSIVE_PLAYS) {
// firstMovesByScore.put(value, nextMove);
//}
///
if (value >= beta) {
return value;
}
alpha = Math.max(alpha,value);
}
return value;
}
private int getMinValue(GameConfig gameConfig, GameState gameState,
String initialColor, boolean playAsOpponent, int recursionLevel, int alpha, int beta) {
if (terminalTest(recursionLevel)) {
return getUtility(gameConfig,gameState);
}
String colorPlaying = getColorToPlay(initialColor, playAsOpponent);
List<String> validMoves = validMoveGenerator.genMoves(gameConfig,
gameState, colorPlaying, MoveGenerator.ALL_MOVES);
int value = Integer.MAX_VALUE;
//Map<Integer,String> firstMovesByScore = new HashMap<Integer,String> ();
for (String nextMove : validMoves) {
GameState nextState = new GameState(gameState);
if (!nextState.playStone(colorPlaying, nextMove)) {
throw new RuntimeException("Illegal move attempted during search!");
}
int maxValue = getMaxValue(gameConfig, nextState,
initialColor, !playAsOpponent, recursionLevel-1, alpha, beta);
//value = Math.min(value, maxValue);
if (maxValue < value) {
value = maxValue;
if (recursionLevel == 2 * DEFAULT_RECURSIVE_PLAYS) {
//firstMovesByScore.put(value, nextMove);
bestPick = nextMove;
}
}
///
//if (recursionLevel == 2 * DEFAULT_RECURSIVE_PLAYS) {
// firstMovesByScore.put(value, nextMove);
//}
///
if (value <= alpha) {
return value;
}
beta = Math.min(beta,value);
}
//if (recursionLevel == DEFAULT_RECURSIVE_PLAYS * 2) {
// bestPick = firstMovesByScore.get(value);
//}
return value;
}
private boolean terminalTest(int recursionLevel) {
return recursionLevel < 1;
}
private int getUtility(GameConfig gameConfig, GameState gameState) {
StateEvaluator stateEvaluator = new StateEvaluator(gameConfig);
return stateEvaluator.scoreGame(gameState).getAggregateScore();
}
private String getColorToPlay(String color, boolean playAsOpponent) {
if (playAsOpponent) {
if ("w".equals(color)) {
return "b";
} else if ("b".equals(color)) {
return "w";
} else {
return "?"; // invalid color will cause randomMoveGenerator to
// PASS
}
} else {
return color;
}
}
/**
* AlphaBetaMoveGenerator2 does not support this method.
*/
@Override
public List<String> genMoves(GameConfig gameConfig, GameState gameState,
String color, int nMoves) {
String[] pass = new String[] {PASS};
LOGGER.info("Minimax genMoves() stub returning [PASS]");
return Arrays.asList(pass);
}
}

View File

@@ -1,109 +0,0 @@
package cs6601.p1.generator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import cs6601.p1.GameConfig;
import cs6601.p1.GameScore;
import cs6601.p1.GameState;
import cs6601.p1.StateEvaluator;
public class MonteCarloMoveGenerator implements MoveGenerator {
//private static final Logger LOGGER = Logger
// .getLogger(MonteCarloMoveGenerator.class.getName());
private static final int DEFAULT_RECURSIVE_PLAYS = 3;
private static final int DEFAULT_PLAYS_PER_LEVEL = 10;
//private static final int MAX_RANDOM_TRIES = 10;
private final RandomMoveGenerator randomMoveGenerator = new RandomMoveGenerator();
@Override
public String genMove(GameConfig gameConfig, GameState gameState,
String color) {
MoveCandidate moveCandidate = findBestMonteCarloResult(
DEFAULT_RECURSIVE_PLAYS * 2, DEFAULT_PLAYS_PER_LEVEL,
gameConfig, gameState, color, false, PASS);
return moveCandidate.move;
}
private MoveCandidate findBestMonteCarloResult(int recursionLevels,
int playsPerLevel, GameConfig gameConfig, GameState gameState,
String initialColor, boolean playAsOpponent, String bestPrevMove) {
StateEvaluator stateEvaluator = new StateEvaluator(gameConfig);
List<MoveCandidate> randomMoveCandidates = new ArrayList<MoveCandidate>();
String colorPlaying = getColorToPlay(initialColor, playAsOpponent);
List<String> randomMoves = randomMoveGenerator.genMoves(gameConfig,
gameState, colorPlaying, DEFAULT_PLAYS_PER_LEVEL);
for (String randomMove : randomMoves) {
GameState stateCopy = new GameState(gameState);
stateCopy.playStone(colorPlaying, randomMove);
if (recursionLevels > 1) {
randomMoveCandidates.add(findBestMonteCarloResult(recursionLevels - 1,
playsPerLevel, gameConfig, stateCopy, initialColor,
!playAsOpponent, randomMove));
} else {
GameScore score = stateEvaluator.scoreGame(stateCopy);
randomMoveCandidates.add(new MoveCandidate(randomMove, score));
}
}
// TODO use a sorted list and just return the last element
MoveCandidate bestMove = randomMoveCandidates.get(0);
double bestScoreSoFar = bestMove.score.getScore(colorPlaying);
for (MoveCandidate moveCandidate : randomMoveCandidates) {
if (moveCandidate.score.getScore(colorPlaying) > bestScoreSoFar) {
bestMove = moveCandidate;
bestScoreSoFar = moveCandidate.score.getScore(colorPlaying);
}
}
// Fix to prevent thinking that the _opponent's_ best move is the move
// to make.
// If evaluating an opponent's move, the best move (for my opponent) is
// my previous move which gives the opponent the highest score.
if (playAsOpponent) {
return new MoveCandidate(bestPrevMove, bestMove.score);
} else { // if evaluating my own move, the move which gives me the
// highest score is the best.
return bestMove;
}
}
private String getColorToPlay(String color, boolean playAsOpponent) {
if (playAsOpponent) {
if ("w".equals(color)) {
return "b";
} else if ("b".equals(color)) {
return "w";
} else {
return "?"; // invalid color will cause randomMoveGenerator to
// PASS
}
} else {
return color;
}
}
/**
* MonteCarloMoveGenerator does not support this method - just pass.
*
* @param gameConfig
* @param gameState
* @param color
* @return
*/
@Override
public List<String> genMoves(GameConfig gameConfig, GameState gameState,
String color, int nMoves) {
String[] pass = new String[] {PASS};
return Arrays.asList(pass);
}
}

View File

@@ -1,13 +0,0 @@
package cs6601.p1.generator;
import java.util.List;
import cs6601.p1.GameConfig;
import cs6601.p1.GameState;
public interface MoveGenerator {
static final String PASS = "PASS";
static final int ALL_MOVES = 0;
public String genMove(GameConfig gameConfig, GameState gameState, String color);
public List<String> genMoves(GameConfig gameConfig, GameState gameState, String color, int nMoves);
}

View File

@@ -1,4 +1,4 @@
package cs6601.p1; package net.woodyfolsom.msproj;
public class Command { public class Command {
public enum TYPE { boardsize, clear_board, final_status_list, genmove, INVALID, komi, list_commands, name, play, quit, version }; public enum TYPE { boardsize, clear_board, final_status_list, genmove, INVALID, komi, list_commands, name, play, quit, version };

View File

@@ -1,4 +1,4 @@
package cs6601.p1; package net.woodyfolsom.msproj;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;

View File

@@ -1,4 +1,4 @@
package cs6601.p1; package net.woodyfolsom.msproj;
import java.util.Arrays; import java.util.Arrays;

View File

@@ -1,4 +1,4 @@
package cs6601.p1; package net.woodyfolsom.msproj;
public class GameConfig { public class GameConfig {
private double komi; private double komi;

View File

@@ -0,0 +1,5 @@
package net.woodyfolsom.msproj;
public class GameController {
}

View File

@@ -1,4 +1,4 @@
package cs6601.p1; package net.woodyfolsom.msproj;
public class GameScore { public class GameScore {

View File

@@ -1,11 +1,12 @@
package cs6601.p1; package net.woodyfolsom.msproj;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import net.woodyfolsom.msproj.policy.Policy;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import cs6601.p1.generator.MoveGenerator;
public class GameState { public class GameState {
private static final Logger LOGGER = Logger.getLogger(GameState.class.getName()); private static final Logger LOGGER = Logger.getLogger(GameState.class.getName());
@@ -61,7 +62,7 @@ public class GameState {
public boolean playStone(String player, String coord) { public boolean playStone(String player, String coord) {
//Opponent passes? Just ignore it. //Opponent passes? Just ignore it.
if (MoveGenerator.PASS.equalsIgnoreCase(coord)) { if (Policy.PASS.equalsIgnoreCase(coord)) {
return true; return true;
} }

View File

@@ -1,17 +1,18 @@
package cs6601.p1; package net.woodyfolsom.msproj;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import net.woodyfolsom.msproj.Command.TYPE;
import net.woodyfolsom.msproj.policy.AlphaBeta;
import net.woodyfolsom.msproj.policy.Minimax;
import net.woodyfolsom.msproj.policy.Policy;
import net.woodyfolsom.msproj.policy.MonteCarloUCT;
import net.woodyfolsom.msproj.policy.RandomMovePolicy;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator; import org.apache.log4j.xml.DOMConfigurator;
import cs6601.p1.Command.TYPE;
import cs6601.p1.generator.AlphaBetaMoveGenerator;
import cs6601.p1.generator.MinimaxMoveGenerator;
import cs6601.p1.generator.MonteCarloMoveGenerator;
import cs6601.p1.generator.MoveGenerator;
import cs6601.p1.generator.RandomMoveGenerator;
public class GoGame { public class GoGame {
private static final int INVALID_MOVE_GENERATOR = 1; private static final int INVALID_MOVE_GENERATOR = 1;
@@ -21,34 +22,34 @@ public class GoGame {
private boolean shutDown = false; private boolean shutDown = false;
private GameConfig gameConfig = new GameConfig(); private GameConfig gameConfig = new GameConfig();
private GameState gameState = new GameState(9); private GameState gameState = new GameState(9);
private MoveGenerator moveGenerator; private Policy moveGenerator;
public GoGame(MoveGenerator moveGenerator) { public GoGame(Policy moveGenerator) {
this.moveGenerator = moveGenerator; this.moveGenerator = moveGenerator;
} }
public static void main(String[] args) { public static void main(String[] args) {
configureLogging(); configureLogging();
if (args.length == 0) { if (args.length == 0) {
MoveGenerator defaultMoveGenerator = new MonteCarloMoveGenerator(); Policy defaultMoveGenerator = new MonteCarloUCT(new RandomMovePolicy(), 2000L);
LOGGER.info("No MoveGenerator specified. Using default: " + defaultMoveGenerator.getClass().getName()); LOGGER.info("No MoveGenerator specified. Using default: " + defaultMoveGenerator.getClass().getName());
new GoGame(defaultMoveGenerator).play(); new GoGame(defaultMoveGenerator).play();
} else { } else {
new GoGame(createMoveGenerator(args[0])).play(); new GoGame(createPolicy(args[0])).play();
} }
} }
private static MoveGenerator createMoveGenerator(String generatorName) { private static Policy createPolicy(String policyName) {
if ("random".equals(generatorName)) { if ("random".equals(policyName)) {
return new RandomMoveGenerator(); return new RandomMovePolicy();
} else if ("minimax".equals(generatorName)) { } else if ("minimax".equals(policyName)) {
return new MinimaxMoveGenerator(); return new Minimax();
} else if ("alphabeta".equals(generatorName)) { } else if ("alphabeta".equals(policyName)) {
return new AlphaBetaMoveGenerator(); return new AlphaBeta();
} else if ("montecarlo".equals(generatorName)) { } else if ("montecarlo".equals(policyName)) {
return new MonteCarloMoveGenerator(); return new MonteCarloUCT(new RandomMovePolicy(), 2000L);
} else { } else {
LOGGER.info("Unable to create MoveGenerator for unsupported name: " + generatorName); LOGGER.info("Unable to create Policy for unsupported name: " + policyName);
System.exit(INVALID_MOVE_GENERATOR); System.exit(INVALID_MOVE_GENERATOR);
//This line will never be executed but prevents the compiler from complaining. //This line will never be executed but prevents the compiler from complaining.
return null; return null;
@@ -77,7 +78,7 @@ public class GoGame {
LOGGER.info("Generating move for:\n" + gameState); LOGGER.info("Generating move for:\n" + gameState);
String player = cmd.getStringField(1); String player = cmd.getStringField(1);
String nextMove = moveGenerator.genMove(gameConfig, gameState, String nextMove = moveGenerator.getAction(gameConfig, gameState,
player); player);
gameState.playStone(player, nextMove); gameState.playStone(player, nextMove);

View File

@@ -1,4 +1,4 @@
package cs6601.p1; package net.woodyfolsom.msproj;
public class GtpClient { public class GtpClient {

View File

@@ -1,4 +1,4 @@
package cs6601.p1; package net.woodyfolsom.msproj;
public class LibertyCounter { public class LibertyCounter {
public static int countLiberties(GameBoard gameBoard, char colLabel, int rowNum, char groupColor) { public static int countLiberties(GameBoard gameBoard, char colLabel, int rowNum, char groupColor) {

View File

@@ -1,4 +1,4 @@
package cs6601.p1; package net.woodyfolsom.msproj;
public class StateEvaluator { public class StateEvaluator {
private final GameConfig gameConfig; private final GameConfig gameConfig;

View File

@@ -1,4 +1,4 @@
package cs6601.p1; package net.woodyfolsom.msproj;
public class TerritoryMarker { public class TerritoryMarker {
public static final char BLACK_TERRITORY = 'x'; public static final char BLACK_TERRITORY = 'x';

View File

@@ -0,0 +1,27 @@
package net.woodyfolsom.msproj;
import java.math.BigInteger;
import java.security.SecureRandom;
public class ZobristHashGenerator {
private long[] randomBitFields;
private ZobristHashGenerator() {
}
public static ZobristHashGenerator getInstance(int boardSize) {
int nRandomFields = 3 * boardSize * boardSize;
ZobristHashGenerator zobHashGen = new ZobristHashGenerator();
SecureRandom secureRandom = new SecureRandom();
zobHashGen.randomBitFields = new long[nRandomFields];
byte[] nextBytes = new byte[8];
for (int i = 0; i < nRandomFields; i++) {
secureRandom.nextBytes(nextBytes);
zobHashGen.randomBitFields[i] = new BigInteger(nextBytes)
.longValue();
}
// TODO add check for minimum hamming distance/colinearity check
return zobHashGen;
}
}

View File

@@ -0,0 +1,13 @@
package net.woodyfolsom.msproj.policy;
import java.util.List;
import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameState;
public interface ActionGenerator {
public static final int ALL_ACTIONS = 0;
public List<String> getActions(GameConfig gameConfig, GameState gameState,
String color, int numActions);
}

View File

@@ -0,0 +1,144 @@
package net.woodyfolsom.msproj.policy;
import java.util.List;
import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameState;
import net.woodyfolsom.msproj.StateEvaluator;
//import org.apache.log4j.Logger;
public class AlphaBeta implements Policy {
//private static final Logger LOGGER = Logger.getLogger(AlphaBeta.class
// .getName());
private static final int DEFAULT_RECURSIVE_PLAYS = 3;
private final ValidMoveGenerator validMoveGenerator = new ValidMoveGenerator();
private String bestPick = Policy.PASS;
@Override
public String getAction(GameConfig gameConfig, GameState gameState,
String initialColor) {
int alpha = Integer.MIN_VALUE;
int beta = Integer.MAX_VALUE;
if ("b".equals(initialColor)) {
getMaxValue(gameConfig, gameState, initialColor, false,
DEFAULT_RECURSIVE_PLAYS * 2, alpha, beta);
return bestPick;
} else if ("w".equals(initialColor)) {
getMinValue(gameConfig, gameState, initialColor, false,
DEFAULT_RECURSIVE_PLAYS * 2, alpha, beta);
return bestPick;
} else {
return Policy.PASS;
}
}
private int getMaxValue(GameConfig gameConfig, GameState gameState,
String initialColor, boolean playAsOpponent, int recursionLevel,
int alpha, int beta) {
if (terminalTest(recursionLevel)) {
return getUtility(gameConfig, gameState);
}
String colorPlaying = getColorToPlay(initialColor, playAsOpponent);
List<String> validMoves = validMoveGenerator.getActions(gameConfig,
gameState, colorPlaying, ActionGenerator.ALL_ACTIONS);
int value = Integer.MIN_VALUE;
for (String nextMove : validMoves) {
GameState nextState = new GameState(gameState);
if (!nextState.playStone(colorPlaying, nextMove)) {
throw new RuntimeException(
"Illegal move attempted during search!");
}
int minValue = getMinValue(gameConfig, nextState, initialColor,
!playAsOpponent, recursionLevel - 1, alpha, beta);
if (minValue > value) {
value = minValue;
if (recursionLevel == DEFAULT_RECURSIVE_PLAYS * 2) {
bestPick = nextMove;
}
}
if (value >= beta) {
return value;
}
alpha = Math.max(alpha, value);
}
return value;
}
private int getMinValue(GameConfig gameConfig, GameState gameState,
String initialColor, boolean playAsOpponent, int recursionLevel,
int alpha, int beta) {
if (terminalTest(recursionLevel)) {
return getUtility(gameConfig, gameState);
}
String colorPlaying = getColorToPlay(initialColor, playAsOpponent);
List<String> validMoves = validMoveGenerator.getActions(gameConfig,
gameState, colorPlaying, ActionGenerator.ALL_ACTIONS);
int value = Integer.MAX_VALUE;
for (String nextMove : validMoves) {
GameState nextState = new GameState(gameState);
if (!nextState.playStone(colorPlaying, nextMove)) {
throw new RuntimeException(
"Illegal move attempted during search!");
}
int maxValue = getMaxValue(gameConfig, nextState, initialColor,
!playAsOpponent, recursionLevel - 1, alpha, beta);
if (maxValue < value) {
value = maxValue;
if (recursionLevel == 2 * DEFAULT_RECURSIVE_PLAYS) {
bestPick = nextMove;
}
}
if (value <= alpha) {
return value;
}
beta = Math.min(beta, value);
}
return value;
}
private boolean terminalTest(int recursionLevel) {
return recursionLevel < 1;
}
private int getUtility(GameConfig gameConfig, GameState gameState) {
StateEvaluator stateEvaluator = new StateEvaluator(gameConfig);
return stateEvaluator.scoreGame(gameState).getAggregateScore();
}
private String getColorToPlay(String color, boolean playAsOpponent) {
if (playAsOpponent) {
if ("w".equals(color)) {
return "b";
} else if ("b".equals(color)) {
return "w";
} else {
return "?"; // invalid color will cause randomMoveGenerator to
// PASS
}
} else {
return color;
}
}
}

View File

@@ -0,0 +1,60 @@
package net.woodyfolsom.msproj.policy;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameState;
public abstract class GameTreeNode {
private GameConfig gameConfig;
private GameState gameState;
private GameTreeNode parent;
private Map<String, GameTreeNode> children = new HashMap<String, GameTreeNode>();
private String player;
public GameTreeNode(GameConfig gameConfig, GameState gameState,
String player) {
this.gameConfig = gameConfig;
this.gameState = gameState;
this.player = player;
}
public void addChild(String action, GameTreeNode child) {
children.put(action, child);
child.parent = this;
}
public Set<String> getActions() {
return children.keySet();
}
public GameTreeNode getChild(String action) {
return children.get(action);
}
public int getChildrenSize() {
return children.size();
}
public GameConfig getGameConfig() {
return gameConfig;
}
public GameState getGameState() {
return gameState;
}
public GameTreeNode getParent() {
return parent;
}
public String getPlayer() {
return player;
}
public boolean isRoot() {
return parent == null;
}
}

View File

@@ -1,32 +1,30 @@
package cs6601.p1.generator; package net.woodyfolsom.msproj.policy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; //import java.util.Arrays;
import java.util.List; import java.util.List;
import org.apache.log4j.Logger; import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameScore;
import net.woodyfolsom.msproj.GameState;
import net.woodyfolsom.msproj.StateEvaluator;
import cs6601.p1.GameConfig; //import org.apache.log4j.Logger;
import cs6601.p1.GameScore;
import cs6601.p1.GameState;
import cs6601.p1.StateEvaluator;
public class MinimaxMoveGenerator implements MoveGenerator {
private static final Logger LOGGER = Logger.getLogger(MinimaxMoveGenerator.class.getName()); public class Minimax implements Policy {
//private static final Logger LOGGER = Logger.getLogger(Minimax.class.getName());
//private static final Logger LOGGER = Logger
// .getLogger(MonteCarloMoveGenerator.class.getName());
private static final int DEFAULT_RECURSIVE_PLAYS = 1; private static final int DEFAULT_RECURSIVE_PLAYS = 1;
private final ValidMoveGenerator validMoveGenerator = new ValidMoveGenerator(); private final ValidMoveGenerator validMoveGenerator = new ValidMoveGenerator();
@Override @Override
public String genMove(GameConfig gameConfig, GameState gameState, public String getAction(GameConfig gameConfig, GameState gameState,
String color) { String color) {
MoveCandidate moveCandidate = findBestMinimaxResult( MoveCandidate moveCandidate = findBestMinimaxResult(
DEFAULT_RECURSIVE_PLAYS * 2, DEFAULT_RECURSIVE_PLAYS * 2,
gameConfig, gameState, color, false, PASS); gameConfig, gameState, color, false, Policy.PASS);
return moveCandidate.move; return moveCandidate.move;
} }
@@ -40,8 +38,8 @@ public class MinimaxMoveGenerator implements MoveGenerator {
String colorPlaying = getColorToPlay(initialColor, playAsOpponent); String colorPlaying = getColorToPlay(initialColor, playAsOpponent);
List<String> validMoves = validMoveGenerator.genMoves(gameConfig, List<String> validMoves = validMoveGenerator.getActions(gameConfig,
gameState, colorPlaying, MoveGenerator.ALL_MOVES); gameState, colorPlaying, ActionGenerator.ALL_ACTIONS);
for (String randomMove : validMoves) { for (String randomMove : validMoves) {
GameState stateCopy = new GameState(gameState); GameState stateCopy = new GameState(gameState);
@@ -71,6 +69,7 @@ public class MinimaxMoveGenerator implements MoveGenerator {
// to make. // to make.
// If evaluating an opponent's move, the best move (for my opponent) is // If evaluating an opponent's move, the best move (for my opponent) is
// my previous move which gives the opponent the highest score. // my previous move which gives the opponent the highest score.
// This should only happen if recursionLevels is initially odd.
if (playAsOpponent) { if (playAsOpponent) {
return new MoveCandidate(bestPrevMove, bestMove.score); return new MoveCandidate(bestPrevMove, bestMove.score);
} else { // if evaluating my own move, the move which gives me the } else { // if evaluating my own move, the move which gives me the
@@ -93,15 +92,4 @@ public class MinimaxMoveGenerator implements MoveGenerator {
return color; return color;
} }
} }
/**
* MinimaxMoveGenerator does not support this method.
*/
@Override
public List<String> genMoves(GameConfig gameConfig, GameState gameState,
String color, int nMoves) {
String[] pass = new String[] {PASS};
LOGGER.info("Minimax genMoves() stub returning [PASS]");
return Arrays.asList(pass);
}
} }

View File

@@ -0,0 +1,61 @@
package net.woodyfolsom.msproj.policy;
import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameState;
public abstract class MonteCarlo implements Policy {
protected Policy movePolicy;
protected long searchTimeLimit;
public MonteCarlo(Policy movePolicy, long searchTimeLimit) {
this.movePolicy = movePolicy;
this.searchTimeLimit = searchTimeLimit;
}
public abstract MonteCarloTreeNode descend(MonteCarloTreeNode node);
@Override
public String getAction(GameConfig gameConfig, GameState gameState,
String initialColor) {
long initialTime = System.currentTimeMillis();
//If for some reason no moves are evaluated within the time limit, pass.
//Note that this may lose the game by forfeit even when picking any random move could
//result in a win.
String bestMove = Policy.PASS;
MonteCarloTreeNode rootNode = new MonteCarloTreeNode(gameConfig, gameState, initialColor);
MonteCarloTreeNode selectedNode;
while (System.currentTimeMillis() - initialTime < searchTimeLimit) {
//TODO these return types may need to be lists for some MC methods
selectedNode = descend(rootNode);
selectedNode = grow(selectedNode);
int reward = rollout(selectedNode);
update(selectedNode, reward);
}
return bestMove;
}
public abstract MonteCarloTreeNode grow(MonteCarloTreeNode node);
public abstract int rollout(MonteCarloTreeNode node);
public abstract void update(MonteCarloTreeNode node, int reward);
static String getColorToPlay(String color, boolean playAsOpponent) {
if (playAsOpponent) {
if ("w".equals(color)) {
return "b";
} else if ("b".equals(color)) {
return "w";
} else {
return "?"; // invalid color will cause randomMoveGenerator to
// PASS
}
} else {
return color;
}
}
}

View File

@@ -0,0 +1,30 @@
package net.woodyfolsom.msproj.policy;
import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameState;
public class MonteCarloTreeNode extends GameTreeNode {
private int numVisits;
private int numWins;
public MonteCarloTreeNode(GameConfig gameConfig, GameState gameState,
String player) {
super(gameConfig, gameState, player);
}
public int getNumVisits() {
return numVisits;
}
public void setNumVisits(int numVisits) {
this.numVisits = numVisits;
}
public int getNumWins() {
return numWins;
}
public void setNumWins(int numWins) {
this.numWins = numWins;
}
}

View File

@@ -0,0 +1,34 @@
package net.woodyfolsom.msproj.policy;
public class MonteCarloUCT extends MonteCarlo {
public MonteCarloUCT(Policy movePolicy, long searchTimeLimit) {
super(movePolicy, searchTimeLimit);
}
@Override
public MonteCarloTreeNode descend(MonteCarloTreeNode node) {
// TODO Auto-generated method stub
return null;
}
@Override
public MonteCarloTreeNode grow(MonteCarloTreeNode node) {
// TODO Auto-generated method stub
return null;
}
@Override
public int rollout(MonteCarloTreeNode node) {
// TODO Auto-generated method stub
return 0;
}
@Override
public void update(MonteCarloTreeNode node, int reward) {
// TODO Auto-generated method stub
}
}

View File

@@ -1,6 +1,6 @@
package cs6601.p1.generator; package net.woodyfolsom.msproj.policy;
import cs6601.p1.GameScore; import net.woodyfolsom.msproj.GameScore;
public class MoveCandidate { public class MoveCandidate {
public final String move; public final String move;

View File

@@ -0,0 +1,10 @@
package net.woodyfolsom.msproj.policy;
import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameState;
public interface Policy {
static final String PASS = "PASS";
public String getAction(GameConfig gameConfig, GameState gameState, String color);
}

View File

@@ -1,17 +1,18 @@
package cs6601.p1.generator; package net.woodyfolsom.msproj.policy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import cs6601.p1.GameConfig; import net.woodyfolsom.msproj.GameConfig;
import cs6601.p1.GameState; import net.woodyfolsom.msproj.GameState;
public class RandomMoveGenerator implements MoveGenerator {
public class RandomMovePolicy implements Policy {
/** /**
* Does NOT modify the gameState. * Does NOT modify the gameState.
*/ */
public String genMove(GameConfig gameConfig, GameState gameState, public String getAction(GameConfig gameConfig, GameState gameState,
String color) { String color) {
GameState gameStateCopy = new GameState(gameState); GameState gameStateCopy = new GameState(gameState);
List<String> emptyCoordinates = gameStateCopy.getEmptyCoords(); List<String> emptyCoordinates = gameStateCopy.getEmptyCoords();

View File

@@ -1,26 +1,18 @@
package cs6601.p1.generator; package net.woodyfolsom.msproj.policy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.apache.log4j.Logger; import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameState;
import cs6601.p1.GameConfig; //import org.apache.log4j.Logger;
import cs6601.p1.GameState;
public class ValidMoveGenerator implements MoveGenerator { public class ValidMoveGenerator implements ActionGenerator {
private static final Logger LOGGER = Logger.getLogger(MinimaxMoveGenerator.class.getName()); //private static final Logger LOGGER = Logger.getLogger(ValidMoveGenerator.class.getName());
@Override
public String genMove(GameConfig gameConfig, GameState gameState,
String color) {
LOGGER.info("ValidMoveGenerator genMove() stub returning PASS");
return PASS;
}
@Override @Override
public List<String> genMoves(GameConfig gameConfig, GameState gameState, public List<String> getActions(GameConfig gameConfig, GameState gameState,
String color, int nMoves) { String color, int nMoves) {
GameState gameStateCopy = new GameState(gameState); GameState gameStateCopy = new GameState(gameState);
@@ -36,7 +28,7 @@ public class ValidMoveGenerator implements MoveGenerator {
} }
if (validMoves.size() == 0) { if (validMoves.size() == 0) {
validMoves.add(PASS); validMoves.add(Policy.PASS);
} }
return validMoves; return validMoves;

View File

@@ -1,9 +1,15 @@
package cs6601.p1; package net.woodyfolsom.msproj;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import net.woodyfolsom.msproj.GameBoard;
import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameScore;
import net.woodyfolsom.msproj.GameState;
import net.woodyfolsom.msproj.StateEvaluator;
import org.junit.Test; import org.junit.Test;
public class CaptureTest { public class CaptureTest {

View File

@@ -1,7 +1,9 @@
package cs6601.p1; package net.woodyfolsom.msproj;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import net.woodyfolsom.msproj.GameScore;
import org.junit.Test; import org.junit.Test;
public class GameScoreTest { public class GameScoreTest {

View File

@@ -1,8 +1,11 @@
package cs6601.p1; package net.woodyfolsom.msproj;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import net.woodyfolsom.msproj.GameBoard;
import net.woodyfolsom.msproj.GameState;
import org.junit.Test; import org.junit.Test;
public class IllegalMoveTest { public class IllegalMoveTest {

View File

@@ -1,7 +1,10 @@
package cs6601.p1; package net.woodyfolsom.msproj;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import net.woodyfolsom.msproj.GameBoard;
import net.woodyfolsom.msproj.GameState;
import org.junit.Test; import org.junit.Test;
public class LegalMoveTest { public class LegalMoveTest {

View File

@@ -1,12 +1,16 @@
package cs6601.p1; package net.woodyfolsom.msproj;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import net.woodyfolsom.msproj.GameBoard;
import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameScore;
import net.woodyfolsom.msproj.GameState;
import net.woodyfolsom.msproj.StateEvaluator;
import org.junit.Test; import org.junit.Test;
import cs6601.p1.generator.AlphaBetaMoveGenerator;
import cs6601.p1.generator.MoveGenerator;
public class StateEvaluatorTest { public class StateEvaluatorTest {
GameConfig gameConfig = new GameConfig(); GameConfig gameConfig = new GameConfig();

View File

@@ -1,4 +1,8 @@
package cs6601.p1; package net.woodyfolsom.msproj;
import net.woodyfolsom.msproj.GameBoard;
import net.woodyfolsom.msproj.GameState;
import net.woodyfolsom.msproj.TerritoryMarker;
import org.junit.Test; import org.junit.Test;

View File

@@ -0,0 +1,17 @@
package net.woodyfolsom.msproj;
import static org.junit.Assert.*;
import net.woodyfolsom.msproj.ZobristHashGenerator;
import org.junit.Test;
public class ZobristHashGeneratorTest {
@Test
public void testConstructor5x5() {
ZobristHashGenerator zobHashGen = ZobristHashGenerator.getInstance(5);
assertNotNull(zobHashGen);
}
}

View File

@@ -1,53 +1,57 @@
package cs6601.p1.generator; package net.woodyfolsom.msproj.policy;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import org.junit.Test; import net.woodyfolsom.msproj.GameBoard;
import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameState;
import net.woodyfolsom.msproj.policy.AlphaBeta;
import net.woodyfolsom.msproj.policy.Policy;
import cs6601.p1.GameBoard; import org.junit.Test;
import cs6601.p1.GameConfig;
import cs6601.p1.GameState;
public class AlphaBetaTest { public class AlphaBetaTest {
@Test @Test
public void testGenmoveAsW() { public void testGenmoveAsW() {
MoveGenerator moveGenerator = new AlphaBetaMoveGenerator(); Policy treeSearch = new AlphaBeta();
GameState gameState = new GameState(6); GameState gameState = new GameState(6);
gameState.playStone('A', 2, GameBoard.WHITE_STONE); gameState.playStone('A', 2, GameBoard.WHITE_STONE);
gameState.playStone('B', 1, GameBoard.WHITE_STONE); gameState.playStone('B', 1, GameBoard.WHITE_STONE);
gameState.playStone('C', 2, GameBoard.WHITE_STONE); gameState.playStone('C', 2, GameBoard.WHITE_STONE);
gameState.playStone('B', 2, GameBoard.BLACK_STONE); gameState.playStone('B', 2, GameBoard.BLACK_STONE);
String move = moveGenerator.genMove(new GameConfig(), gameState, "b"); String move = treeSearch.getAction(new GameConfig(), gameState, "b");
System.out.println(gameState); System.out.println(gameState);
System.out.println("Generated move: " + move); System.out.println("Generated move: " + move);
assertEquals("Expected B3 but was: " + move, "B3", move); assertEquals("Expected B3 but was: " + move, "B3", move);
gameState.playStone("b", move); gameState.playStone("b", move);
System.out.println(gameState); System.out.println(gameState);
assertEquals(MoveGenerator.PASS,moveGenerator.genMove(new GameConfig(), gameState, "?")); assertEquals(Policy.PASS,
treeSearch.getAction(new GameConfig(), gameState, "?"));
} }
@Test @Test
public void testGenmoveAsB() { public void testGenmoveAsB() {
MoveGenerator moveGenerator = new AlphaBetaMoveGenerator(); Policy treeSearch = new AlphaBeta();
GameState gameState = new GameState(6); GameState gameState = new GameState(6);
gameState.playStone('A', 2, GameBoard.BLACK_STONE); gameState.playStone('A', 2, GameBoard.BLACK_STONE);
gameState.playStone('B', 1, GameBoard.BLACK_STONE); gameState.playStone('B', 1, GameBoard.BLACK_STONE);
gameState.playStone('C', 2, GameBoard.BLACK_STONE); gameState.playStone('C', 2, GameBoard.BLACK_STONE);
gameState.playStone('B', 2, GameBoard.WHITE_STONE); gameState.playStone('B', 2, GameBoard.WHITE_STONE);
String move = moveGenerator.genMove(new GameConfig(), gameState, "b"); String move = treeSearch.getAction(new GameConfig(), gameState, "b");
System.out.println(gameState); System.out.println(gameState);
System.out.println("Generated move: " + move); System.out.println("Generated move: " + move);
assertEquals("Expected B3 but was: " + move, "B3", move); assertEquals("Expected B3 but was: " + move, "B3", move);
gameState.playStone("b", move); gameState.playStone("b", move);
System.out.println(gameState); System.out.println(gameState);
assertEquals(MoveGenerator.PASS,moveGenerator.genMove(new GameConfig(), gameState, "?")); assertEquals(Policy.PASS,
treeSearch.getAction(new GameConfig(), gameState, "?"));
} }
} }

View File

@@ -1,32 +1,33 @@
package cs6601.p1.generator; package net.woodyfolsom.msproj.policy;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import net.woodyfolsom.msproj.GameBoard;
import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameState;
import net.woodyfolsom.msproj.policy.Policy;
import org.junit.Test; import org.junit.Test;
import cs6601.p1.GameBoard;
import cs6601.p1.GameConfig;
import cs6601.p1.GameState;
import cs6601.p1.generator.MoveGenerator;
public class MinimaxTest { public class MinimaxTest {
@Test @Test
public void testGenmove() { public void testGenmove() {
MoveGenerator moveGenerator = new MinimaxMoveGenerator(); Policy moveGenerator = new Minimax();
GameState gameState = new GameState(5); GameState gameState = new GameState(5);
gameState.playStone('A', 2, GameBoard.BLACK_STONE); gameState.playStone('A', 2, GameBoard.BLACK_STONE);
gameState.playStone('B', 1, GameBoard.BLACK_STONE); gameState.playStone('B', 1, GameBoard.BLACK_STONE);
gameState.playStone('C', 2, GameBoard.BLACK_STONE); gameState.playStone('C', 2, GameBoard.BLACK_STONE);
gameState.playStone('B', 4, GameBoard.BLACK_STONE); gameState.playStone('B', 4, GameBoard.BLACK_STONE);
String move = moveGenerator.genMove(new GameConfig(), gameState, "w"); String move = moveGenerator.getAction(new GameConfig(), gameState, "w");
System.out.println("Generated move: " + move); System.out.println("Generated move: " + move);
gameState.playStone("w", move); gameState.playStone("w", move);
System.out.println(gameState); System.out.println(gameState);
assertEquals(MoveGenerator.PASS,moveGenerator.genMove(new GameConfig(), gameState, "?")); assertEquals(Policy.PASS,moveGenerator.getAction(new GameConfig(), gameState, "?"));
System.out.println(gameState); System.out.println(gameState);
} }

View File

@@ -1,27 +1,28 @@
package cs6601.p1.generator; package net.woodyfolsom.msproj.policy;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import net.woodyfolsom.msproj.GameBoard;
import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameState;
import net.woodyfolsom.msproj.policy.Policy;
import net.woodyfolsom.msproj.policy.RandomMovePolicy;
import org.junit.Test; import org.junit.Test;
import cs6601.p1.GameBoard;
import cs6601.p1.GameConfig;
import cs6601.p1.GameState;
import cs6601.p1.generator.MoveGenerator;
import cs6601.p1.generator.RandomMoveGenerator;
public class RandomTest { public class RandomTest {
@Test @Test
public void testGenmove() { public void testGenmove() {
MoveGenerator moveGenerator = new RandomMoveGenerator(); Policy moveGenerator = new RandomMovePolicy();
GameState gameState = new GameState(5); GameState gameState = new GameState(5);
moveGenerator.genMove(new GameConfig(), gameState, "b"); moveGenerator.getAction(new GameConfig(), gameState, "b");
gameState = new GameState(5); gameState = new GameState(5);
moveGenerator.genMove(new GameConfig(), gameState, "w"); moveGenerator.getAction(new GameConfig(), gameState, "w");
assertEquals(MoveGenerator.PASS,moveGenerator.genMove(new GameConfig(), gameState, "?")); assertEquals(Policy.PASS,moveGenerator.getAction(new GameConfig(), gameState, "?"));
System.out.println(gameState); System.out.println(gameState);
} }
@@ -46,7 +47,7 @@ public class RandomTest {
assertTrue(gameState.playStone('D', 3, GameBoard.WHITE_STONE)); assertTrue(gameState.playStone('D', 3, GameBoard.WHITE_STONE));
System.out.println(gameState); System.out.println(gameState);
//This is correct - checked vs. MFOG //This is correct - checked vs. MFOG
assertEquals("B3", new RandomMoveGenerator().genMove(new GameConfig(), gameState, "w")); assertEquals("B3", new RandomMovePolicy().getAction(new GameConfig(), gameState, "w"));
System.out.println(gameState); System.out.println(gameState);
} }
} }

View File

@@ -1,14 +1,16 @@
package cs6601.p1.generator; package net.woodyfolsom.msproj.policy;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.util.List; import java.util.List;
import net.woodyfolsom.msproj.GameBoard;
import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameState;
import net.woodyfolsom.msproj.policy.ValidMoveGenerator;
import org.junit.Test; import org.junit.Test;
import cs6601.p1.GameBoard;
import cs6601.p1.GameConfig;
import cs6601.p1.GameState;
public class ValidMoveGeneratorTest { public class ValidMoveGeneratorTest {
@@ -30,7 +32,7 @@ public class ValidMoveGeneratorTest {
gameState.playStone('C', 2, GameBoard.WHITE_STONE); gameState.playStone('C', 2, GameBoard.WHITE_STONE);
assertFalse(gameState.playStone('A', 1, GameBoard.BLACK_STONE)); assertFalse(gameState.playStone('A', 1, GameBoard.BLACK_STONE));
List<String> validMoves = new ValidMoveGenerator().genMoves(new GameConfig(), gameState, "b",0); List<String> validMoves = new ValidMoveGenerator().getActions(new GameConfig(), gameState, "b",0);
assertTrue(validMoves.size() > 0); assertTrue(validMoves.size() > 0);
for (String vm : validMoves) { for (String vm : validMoves) {
System.out.println(vm); System.out.println(vm);