Code cleanup; Initial implementation of root parallelization.

This commit is contained in:
2012-11-09 18:40:51 -05:00
parent 33f50fb851
commit b60c176d39
40 changed files with 7046 additions and 7044 deletions

View File

@@ -9,7 +9,6 @@ public class Action {
private char column = 'x'; private char column = 'x';
private int row = 0; private int row = 0;
//private Player player;
private String move; private String move;
public static final Action NONE = new Action("NONE", 'x', 0); public static final Action NONE = new Action("NONE", 'x', 0);

View File

@@ -1,38 +1,40 @@
package net.woodyfolsom.msproj; 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
};
private String[] cmdFields; private String[] cmdFields;
private String text; private String text;
private TYPE type; private TYPE type;
public Command(TYPE type, String text, String[] cmdFields) { public Command(TYPE type, String text, String[] cmdFields) {
this.type = type; this.type = type;
this.text = text; this.text = text;
this.cmdFields = cmdFields; this.cmdFields = cmdFields;
} }
public double getDoubleField(int index) { public double getDoubleField(int index) {
return Double.valueOf(cmdFields[index]); return Double.valueOf(cmdFields[index]);
} }
public int getIntField(int index) { public int getIntField(int index) {
return Integer.valueOf(cmdFields[index]); return Integer.valueOf(cmdFields[index]);
} }
public String getStringField(int index) { public String getStringField(int index) {
return cmdFields[index]; return cmdFields[index];
} }
public String getText() { public String getText() {
return text; return text;
} }
public TYPE getType() { public TYPE getType() {
return type; return type;
} }
public String toString() { public String toString() {
return "[" + type.toString() + "] " + text; return "[" + type.toString() + "] " + text;
} }

View File

@@ -6,8 +6,6 @@ public class CommandParser {
private static final Logger LOGGER = Logger.getLogger(CommandParser.class private static final Logger LOGGER = Logger.getLogger(CommandParser.class
.getName()); .getName());
//private static final String INT_PATTERN = "^(\\d+.*|-\\d+.*)";
/** /**
* This very simple parser converts a small subset of GTP command strings * This very simple parser converts a small subset of GTP command strings
* into Commands. * into Commands.
@@ -17,38 +15,43 @@ public class CommandParser {
*/ */
public static Command parse(String commandString) { public static Command parse(String commandString) {
LOGGER.info("Parsing command: " + commandString); LOGGER.info("Parsing command: " + commandString);
//split on whitespace // split on whitespace
if (commandString == null) { if (commandString == null) {
return new Command(Command.TYPE.INVALID,commandString,new String[0]); return new Command(Command.TYPE.INVALID, commandString,
new String[0]);
} }
String cmdFields[] = commandString.split("\\s+"); String cmdFields[] = commandString.split("\\s+");
if (cmdFields.length == 0) { if (cmdFields.length == 0) {
return new Command(Command.TYPE.INVALID,commandString,new String[0]); return new Command(Command.TYPE.INVALID, commandString,
new String[0]);
} }
if ("boardsize".equals(cmdFields[0])) { if ("boardsize".equals(cmdFields[0])) {
return new Command(Command.TYPE.boardsize,commandString,cmdFields); return new Command(Command.TYPE.boardsize, commandString, cmdFields);
} else if ("clear_board".equals(cmdFields[0])) { } else if ("clear_board".equals(cmdFields[0])) {
return new Command(Command.TYPE.clear_board,commandString,cmdFields); return new Command(Command.TYPE.clear_board, commandString,
cmdFields);
} else if ("final_status_list".equals(cmdFields[0])) { } else if ("final_status_list".equals(cmdFields[0])) {
return new Command(Command.TYPE.final_status_list,commandString,cmdFields); return new Command(Command.TYPE.final_status_list, commandString,
cmdFields);
} else if ("genmove".equals(cmdFields[0])) { } else if ("genmove".equals(cmdFields[0])) {
return new Command(Command.TYPE.genmove,commandString,cmdFields); return new Command(Command.TYPE.genmove, commandString, cmdFields);
} else if ("komi".equals(cmdFields[0])) { } else if ("komi".equals(cmdFields[0])) {
return new Command(Command.TYPE.komi,commandString,cmdFields); return new Command(Command.TYPE.komi, commandString, cmdFields);
} else if ("list_commands".equals(cmdFields[0])) { } else if ("list_commands".equals(cmdFields[0])) {
return new Command(Command.TYPE.list_commands,commandString,cmdFields); return new Command(Command.TYPE.list_commands, commandString,
cmdFields);
} else if ("name".equals(cmdFields[0])) { } else if ("name".equals(cmdFields[0])) {
return new Command(Command.TYPE.name,commandString,cmdFields); return new Command(Command.TYPE.name, commandString, cmdFields);
} else if ("play".equals(cmdFields[0])) { } else if ("play".equals(cmdFields[0])) {
return new Command(Command.TYPE.play,commandString,cmdFields); return new Command(Command.TYPE.play, commandString, cmdFields);
} else if ("quit".equals(cmdFields[0])) { } else if ("quit".equals(cmdFields[0])) {
return new Command(Command.TYPE.quit,commandString,cmdFields); return new Command(Command.TYPE.quit, commandString, cmdFields);
} else if ("version".equals(cmdFields[0])) { } else if ("version".equals(cmdFields[0])) {
return new Command(Command.TYPE.version,commandString,cmdFields); return new Command(Command.TYPE.version, commandString, cmdFields);
} else { } else {
return new Command(Command.TYPE.INVALID,commandString,cmdFields); return new Command(Command.TYPE.INVALID, commandString, cmdFields);
} }
} }
} }

View File

@@ -19,7 +19,7 @@ public class GameBoard {
private char[] board; private char[] board;
private List<Integer> captureList; private List<Integer> captureList;
private List<Long> boardHashHistory = new ArrayList<Long>(); private List<Long> boardHashHistory = new ArrayList<Long>();
private ZobristHashGenerator zobristHashGenerator; private ZobristHashGenerator zobristHashGenerator;
public GameBoard(int size) { public GameBoard(int size) {
@@ -143,11 +143,11 @@ public class GameBoard {
public char getSymbolAt(char colLabel, int rowNumber) { public char getSymbolAt(char colLabel, int rowNumber) {
return getSymbolAt(getColumnIndex(colLabel), rowNumber - 1); return getSymbolAt(getColumnIndex(colLabel), rowNumber - 1);
} }
public char getSymbolAt(int index) { public char getSymbolAt(int index) {
return board[index]; return board[index];
} }
/** /**
* 0-based. * 0-based.
* *
@@ -222,7 +222,7 @@ public class GameBoard {
for (int i = 0; i < board.length; i++) { for (int i = 0; i < board.length; i++) {
if (board[i] == MARKED_GROUP) { if (board[i] == MARKED_GROUP) {
board[i] = opponentSymbol; board[i] = opponentSymbol;
setSymbolAt(i,EMPTY_INTERSECTION); setSymbolAt(i, EMPTY_INTERSECTION);
captureList.add(i); captureList.add(i);
numReplaced++; numReplaced++;
} }
@@ -250,12 +250,13 @@ public class GameBoard {
} }
} }
} }
//TODO change boardHashHistory to stack // TODO change boardHashHistory to stack
public void popHashHistory() { public void popHashHistory() {
boardHashHistory.remove(boardHashHistory.get(boardHashHistory.size() - 1)); boardHashHistory
.remove(boardHashHistory.get(boardHashHistory.size() - 1));
} }
public void pushHashHistory() { public void pushHashHistory() {
boardHashHistory.add(boardHashHistory.get(boardHashHistory.size() - 1)); boardHashHistory.add(boardHashHistory.get(boardHashHistory.size() - 1));
} }
@@ -266,12 +267,14 @@ public class GameBoard {
public void setSymbolAt(int index, char newSymbol) { public void setSymbolAt(int index, char newSymbol) {
char oldSymbol = board[index]; char oldSymbol = board[index];
//TODO marked intersections should really be stored in // TODO marked intersections should really be stored in
//a separate array to ensure that the hash code is always in sync with // a separate array to ensure that the hash code is always in sync with
//an actual or transitional board position // an actual or transitional board position
if (oldSymbol == MARKED_GROUP || newSymbol == MARKED_GROUP || oldSymbol == MARKED_TERRITORY || newSymbol == MARKED_TERRITORY) { if (oldSymbol == MARKED_GROUP || newSymbol == MARKED_GROUP
board[index] = newSymbol; || oldSymbol == MARKED_TERRITORY
|| newSymbol == MARKED_TERRITORY) {
board[index] = newSymbol;
} else { } else {
int hashIndex = boardHashHistory.size() - 1; int hashIndex = boardHashHistory.size() - 1;
long currentHashCode = boardHashHistory.get(hashIndex); long currentHashCode = boardHashHistory.get(hashIndex);

View File

@@ -4,49 +4,49 @@ public class GameConfig {
private double komi; private double komi;
private int size; private int size;
private int timeLimit; private int timeLimit;
public GameConfig(int size, double komi) { public GameConfig(int size, double komi) {
this(size); this(size);
this.komi = komi; this.komi = komi;
} }
public GameConfig(int size) { public GameConfig(int size) {
timeLimit = 0; timeLimit = 0;
this.size = size; this.size = size;
switch(size) { switch (size) {
case 9 : case 9:
komi = 6.5; komi = 6.5;
default : default:
komi = 5.5; komi = 5.5;
} }
} }
public GameConfig(GameConfig that) { public GameConfig(GameConfig that) {
this.komi = that.komi; this.komi = that.komi;
this.size = that.size; this.size = that.size;
this.timeLimit = that.timeLimit; this.timeLimit = that.timeLimit;
} }
public double getKomi() { public double getKomi() {
return komi; return komi;
} }
public int getSize() { public int getSize() {
return size; return size;
} }
public int getTimeLimit(){ public int getTimeLimit() {
return timeLimit; return timeLimit;
} }
public void setKomi(double komi) { public void setKomi(double komi) {
this.komi = komi; this.komi = komi;
} }
public void setSize(int size) { public void setSize(int size) {
this.size = size; this.size = size;
} }
public void setTimeLimit(int timeLimit) { public void setTimeLimit(int timeLimit) {
this.timeLimit = timeLimit; this.timeLimit = timeLimit;
} }

View File

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

View File

@@ -1,25 +1,29 @@
package net.woodyfolsom.msproj; package net.woodyfolsom.msproj;
public class GameResult { public class GameResult {
public static final GameResult BLACK_BY_RESIGNATION = new GameResult(RESULT_TYPE.BLACK_BY_RESIGNATION); public static final GameResult BLACK_BY_RESIGNATION = new GameResult(
RESULT_TYPE.BLACK_BY_RESIGNATION);
public static final GameResult VOID = new GameResult(RESULT_TYPE.VOID); public static final GameResult VOID = new GameResult(RESULT_TYPE.VOID);
public static final GameResult WHITE_BY_RESIGNATION = new GameResult(RESULT_TYPE.WHITE_BY_RESIGNATION); public static final GameResult WHITE_BY_RESIGNATION = new GameResult(
RESULT_TYPE.WHITE_BY_RESIGNATION);
private double komi; private double komi;
private int blackScore; private int blackScore;
private int whiteScore; private int whiteScore;
private int normalizedZeroScore; private int normalizedZeroScore;
public enum RESULT_TYPE { BLACK_BY_RESIGNATION, WHITE_BY_RESIGNATION, IN_PROGRESS, SCORED, VOID} public enum RESULT_TYPE {
BLACK_BY_RESIGNATION, WHITE_BY_RESIGNATION, IN_PROGRESS, SCORED, VOID
}
private RESULT_TYPE resultType; private RESULT_TYPE resultType;
public GameResult(int blackScore, int whiteScore, int size, double komi, boolean finished) { public GameResult(int blackScore, int whiteScore, int size, double komi,
boolean finished) {
this.blackScore = blackScore; this.blackScore = blackScore;
this.komi = komi; this.komi = komi;
this.whiteScore = whiteScore; this.whiteScore = whiteScore;
this.normalizedZeroScore = size * size + ((int)(komi * 2)); this.normalizedZeroScore = size * size + ((int) (komi * 2));
if (finished) { if (finished) {
resultType = RESULT_TYPE.SCORED; resultType = RESULT_TYPE.SCORED;
} else { } else {
@@ -32,10 +36,10 @@ public class GameResult {
blackScore = 0; blackScore = 0;
whiteScore = 0; whiteScore = 0;
normalizedZeroScore = 0; normalizedZeroScore = 0;
this.resultType = resultType; this.resultType = resultType;
} }
public double getBlackScore() { public double getBlackScore() {
return blackScore; return blackScore;
} }
@@ -43,17 +47,20 @@ public class GameResult {
public int getNormalizedZeroScore() { public int getNormalizedZeroScore() {
return normalizedZeroScore; return normalizedZeroScore;
} }
/** /**
* Gets a representation for the game score as an integer. Lower numbers are better for white. * Gets a representation for the game score as an integer. Lower numbers are
* The minimum value is 0 (Chinese scoring - white owns every intersection on 19x19 and has 9 stone komi). * better for white. The minimum value is 0 (Chinese scoring - white owns
* Likewise, the maximum value if 379x2 (black owns every intersection with zero komi). * every intersection on 19x19 and has 9 stone komi). Likewise, the maximum
* value if 379x2 (black owns every intersection with zero komi).
*
* @return * @return
*/ */
public int getNormalizedScore() { public int getNormalizedScore() {
return normalizedZeroScore + 2 * blackScore - ((int)(2 * (whiteScore + komi))); return normalizedZeroScore + 2 * blackScore
- ((int) (2 * (whiteScore + komi)));
} }
public double getScore(Player color) { public double getScore(Player color) {
if (color == Player.BLACK) { if (color == Player.BLACK) {
return getBlackScore(); return getBlackScore();
@@ -63,9 +70,9 @@ public class GameResult {
return 0.0; return 0.0;
} }
} }
public double getWhiteScore() { public double getWhiteScore() {
return (double)whiteScore + komi; return (double) whiteScore + komi;
} }
public boolean isWinner(Player player) { public boolean isWinner(Player player) {
@@ -75,12 +82,12 @@ public class GameResult {
return blackScore > whiteScore + komi; return blackScore > whiteScore + komi;
} }
} }
public String toString() { public String toString() {
switch (resultType) { switch (resultType) {
case BLACK_BY_RESIGNATION : case BLACK_BY_RESIGNATION:
return "B+R"; return "B+R";
case SCORED : case SCORED:
double blackScore = getBlackScore(); double blackScore = getBlackScore();
double whiteScore = getWhiteScore(); double whiteScore = getWhiteScore();
if (blackScore > whiteScore) { if (blackScore > whiteScore) {
@@ -90,12 +97,12 @@ public class GameResult {
} else { } else {
return "DRAW"; return "DRAW";
} }
case WHITE_BY_RESIGNATION : case WHITE_BY_RESIGNATION:
return "W+R"; return "W+R";
case IN_PROGRESS : case IN_PROGRESS:
case VOID : // intentional fall-through case VOID: // intentional fall-through
return "Void"; return "Void";
default : default:
return "?"; return "?";
} }
} }

View File

@@ -10,7 +10,7 @@ public class GameState {
private GameConfig gameConfig; private GameConfig gameConfig;
private Player playerToMove; private Player playerToMove;
private List<Action> moveHistory = new ArrayList<Action>(); private List<Action> moveHistory = new ArrayList<Action>();
public GameState(GameConfig gameConfig) { public GameState(GameConfig gameConfig) {
this.gameConfig = gameConfig; this.gameConfig = gameConfig;
int size = gameConfig.getSize(); int size = gameConfig.getSize();
@@ -40,27 +40,28 @@ public class GameState {
public int getBlackPrisoners() { public int getBlackPrisoners() {
return blackPrisoners; return blackPrisoners;
} }
public GameResult getResult() { public GameResult getResult() {
GameBoard markedBoard; GameBoard markedBoard;
if (gameBoard.isTerritoryMarked()) { if (gameBoard.isTerritoryMarked()) {
markedBoard = gameBoard; markedBoard = gameBoard;
} else { } else {
markedBoard = new GameBoard(gameBoard); markedBoard = new GameBoard(gameBoard);
TerritoryMarker.markTerritory(markedBoard); TerritoryMarker.markTerritory(markedBoard);
} }
int gameLength = moveHistory.size(); int gameLength = moveHistory.size();
//This functionality duplicates isTerminal(), // This functionality duplicates isTerminal(),
//however for the result to be reported, we need to know // however for the result to be reported, we need to know
//how the game was terminated to differentiate between resignation, scored complete // how the game was terminated to differentiate between resignation,
//and (eventually) time out/forfeit. // scored complete
// and (eventually) time out/forfeit.
boolean isFinished = false; boolean isFinished = false;
if (gameLength >= 1) { if (gameLength >= 1) {
if (moveHistory.get(gameLength-1).isResign()) { if (moveHistory.get(gameLength - 1).isResign()) {
if (gameLength % 2 == 1) { if (gameLength % 2 == 1) {
return GameResult.WHITE_BY_RESIGNATION; return GameResult.WHITE_BY_RESIGNATION;
} else { } else {
@@ -68,21 +69,20 @@ public class GameState {
} }
} }
if (gameLength >= 2) { if (gameLength >= 2) {
if (moveHistory.get(gameLength-1).isPass() if (moveHistory.get(gameLength - 1).isPass()
&& moveHistory.get(gameLength-2).isPass()) { && moveHistory.get(gameLength - 2).isPass()) {
isFinished = true; isFinished = true;
} }
} }
} }
int blackScore = gameBoard.countSymbols(GameBoard.BLACK_STONE,GameBoard.BLACK_TERRITORY); int blackScore = gameBoard.countSymbols(GameBoard.BLACK_STONE,
int whiteScore = gameBoard.countSymbols(GameBoard.WHITE_STONE,GameBoard.WHITE_TERRITORY); GameBoard.BLACK_TERRITORY);
int whiteScore = gameBoard.countSymbols(GameBoard.WHITE_STONE,
return new GameResult( blackScore, GameBoard.WHITE_TERRITORY);
whiteScore,
markedBoard.getSize(), return new GameResult(blackScore, whiteScore, markedBoard.getSize(),
gameConfig.getKomi(), gameConfig.getKomi(), isFinished);
isFinished);
} }
public List<String> getEmptyCoords() { public List<String> getEmptyCoords() {
@@ -92,7 +92,8 @@ public class GameState {
for (int rowIndex = 0; rowIndex < gameBoard.getSize(); rowIndex++) { for (int rowIndex = 0; rowIndex < gameBoard.getSize(); rowIndex++) {
if (GameBoard.EMPTY_INTERSECTION == gameBoard.getSymbolAt( if (GameBoard.EMPTY_INTERSECTION == gameBoard.getSymbolAt(
colIndex, rowIndex)) colIndex, rowIndex))
emptyCoords.add(GameBoard.getCoordinate(colIndex, rowIndex)); emptyCoords
.add(GameBoard.getCoordinate(colIndex, rowIndex));
} }
} }
@@ -106,11 +107,11 @@ public class GameState {
public GameConfig getGameConfig() { public GameConfig getGameConfig() {
return gameConfig; return gameConfig;
} }
public Player getPlayerToMove() { public Player getPlayerToMove() {
return playerToMove; return playerToMove;
} }
public int getWhitePrisoners() { public int getWhitePrisoners() {
return whitePrisoners; return whitePrisoners;
} }
@@ -135,13 +136,14 @@ public class GameState {
*/ */
public boolean playStone(Player player, Action action) { public boolean playStone(Player player, Action action) {
if (player != playerToMove) { if (player != playerToMove) {
throw new IllegalArgumentException("Requested " + player + " move, but it is " + playerToMove +"'s turn!"); throw new IllegalArgumentException("Requested " + player
+ " move, but it is " + playerToMove + "'s turn!");
} }
if (player == Player.NONE) { if (player == Player.NONE) {
throw new IllegalArgumentException("Cannot play as " + player); throw new IllegalArgumentException("Cannot play as " + player);
} }
if (action.isPass()) { if (action.isPass()) {
playerToMove = GoGame.getNextPlayer(player); playerToMove = GoGame.getNextPlayer(player);
moveHistory.add(action); moveHistory.add(action);
@@ -153,11 +155,11 @@ public class GameState {
moveHistory.add(action); moveHistory.add(action);
return true; return true;
} }
if (action.isNone()) { if (action.isNone()) {
return false; return false;
} }
char currentStone = gameBoard.getSymbolAt(action.getColumn(), char currentStone = gameBoard.getSymbolAt(action.getColumn(),
action.getRow()); action.getRow());
@@ -165,17 +167,17 @@ public class GameState {
return false; return false;
} }
//assertCorrectHash(); // assertCorrectHash();
gameBoard.pushHashHistory(); gameBoard.pushHashHistory();
gameBoard.clearCaptureList(); gameBoard.clearCaptureList();
// Place stone as requested, then check for (1) captured neighbors and // Place stone as requested, then check for (1) captured neighbors and
// (2) illegal move due to 0 liberties. // (2) illegal move due to 0 liberties.
char stoneSymbol = player.getStoneSymbol(); char stoneSymbol = player.getStoneSymbol();
//Player opponent = GoGame.getNextPlayer(player); // Player opponent = GoGame.getNextPlayer(player);
gameBoard.setSymbolAt(action.getColumn(), action.getRow(), stoneSymbol); gameBoard.setSymbolAt(action.getColumn(), action.getRow(), stoneSymbol);
// look for captured adjacent groups and increment the prisoner counter // look for captured adjacent groups and increment the prisoner counter
@@ -232,13 +234,14 @@ public class GameState {
// Moved test for 0 liberties until after attempting to capture // Moved test for 0 liberties until after attempting to capture
// neighboring groups. // neighboring groups.
// This will only happen if no neighboring groups were capture, hence there is nothing to undo. // This will only happen if no neighboring groups were capture, hence
// there is nothing to undo.
// So return now. // So return now.
if (0 == LibertyCounter.countLiberties(gameBoard, action.getColumn(), if (0 == LibertyCounter.countLiberties(gameBoard, action.getColumn(),
action.getRow(), stoneSymbol)) { action.getRow(), stoneSymbol)) {
gameBoard.removeStone(action.getColumn(), action.getRow()); gameBoard.removeStone(action.getColumn(), action.getRow());
gameBoard.popHashHistory(); gameBoard.popHashHistory();
return false; return false;
} }
@@ -250,18 +253,19 @@ public class GameState {
for (int i : captureList) { for (int i : captureList) {
gameBoard.setSymbolAt(i, opponentSymbol); gameBoard.setSymbolAt(i, opponentSymbol);
} }
//And finally, remove the originally played stone, which was never valid due to ko. // And finally, remove the originally played stone, which was never
// valid due to ko.
gameBoard.removeStone(action.getColumn(), action.getRow()); gameBoard.removeStone(action.getColumn(), action.getRow());
gameBoard.clearCaptureList(); gameBoard.clearCaptureList();
gameBoard.popHashHistory(); gameBoard.popHashHistory();
//assertCorrectHash(); // assertCorrectHash();
return false; return false;
} else { } else {
//assertCorrectHash(); // assertCorrectHash();
playerToMove = GoGame.getNextPlayer(player); playerToMove = GoGame.getNextPlayer(player);
moveHistory.add(action); moveHistory.add(action);
return true; return true;
@@ -271,12 +275,15 @@ public class GameState {
public boolean isTerminal() { public boolean isTerminal() {
int nMoves = moveHistory.size(); int nMoves = moveHistory.size();
if (nMoves < 2) { if (nMoves < 2) {
return false; //Impossible for a game to be over in 1 move unless the first player resigns return false; // Impossible for a game to be over in 1 move unless
//before the first player has played, the game is considered to be 'not over' // the first player resigns
// before the first player has played, the game is considered to be
// 'not over'
} }
return moveHistory.get(nMoves-1).isPass() && moveHistory.get(nMoves-2).isPass(); return moveHistory.get(nMoves - 1).isPass()
&& moveHistory.get(nMoves - 2).isPass();
} }
public String toString() { public String toString() {
int boardSize = gameBoard.getSize(); int boardSize = gameBoard.getSize();
StringBuilder sb = new StringBuilder(" "); StringBuilder sb = new StringBuilder(" ");

View File

@@ -13,8 +13,8 @@ import java.util.Properties;
import net.woodyfolsom.msproj.Command.TYPE; import net.woodyfolsom.msproj.Command.TYPE;
import net.woodyfolsom.msproj.policy.AlphaBeta; import net.woodyfolsom.msproj.policy.AlphaBeta;
import net.woodyfolsom.msproj.policy.Minimax; import net.woodyfolsom.msproj.policy.Minimax;
import net.woodyfolsom.msproj.policy.Policy;
import net.woodyfolsom.msproj.policy.MonteCarloUCT; import net.woodyfolsom.msproj.policy.MonteCarloUCT;
import net.woodyfolsom.msproj.policy.Policy;
import net.woodyfolsom.msproj.policy.RandomMovePolicy; import net.woodyfolsom.msproj.policy.RandomMovePolicy;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;

View File

@@ -1,21 +1,25 @@
package net.woodyfolsom.msproj; 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,
return countLiberties(gameBoard, GameBoard.getColumnIndex(colLabel),rowNum-1,groupColor,false); int rowNum, char groupColor) {
return countLiberties(gameBoard, GameBoard.getColumnIndex(colLabel),
rowNum - 1, groupColor, false);
} }
public static int countLiberties(GameBoard gameBoard, int col, int row, char groupColor, boolean markGroup) { public static int countLiberties(GameBoard gameBoard, int col, int row,
char groupColor, boolean markGroup) {
int liberties = markGroup(gameBoard, col, row, groupColor); int liberties = markGroup(gameBoard, col, row, groupColor);
if (!markGroup) { if (!markGroup) {
gameBoard.unmarkGroup(groupColor); gameBoard.unmarkGroup(groupColor);
} }
return liberties; return liberties;
} }
public static int markGroup(GameBoard gameBoard, int col, int row, char groupColor) { public static int markGroup(GameBoard gameBoard, int col, int row,
char groupColor) {
char stoneSymbol = gameBoard.getSymbolAt(col, row); char stoneSymbol = gameBoard.getSymbolAt(col, row);
if (stoneSymbol == GameBoard.EMPTY_INTERSECTION) { if (stoneSymbol == GameBoard.EMPTY_INTERSECTION) {
return 1; // one liberty return 1; // one liberty
@@ -27,27 +31,30 @@ public class LibertyCounter {
int liberties = 0; int liberties = 0;
gameBoard.setSymbolAt(col, row, GameBoard.MARKED_GROUP); gameBoard.setSymbolAt(col, row, GameBoard.MARKED_GROUP);
if (col > 0) { if (col > 0) {
liberties += markGroup(gameBoard,col-1,row,groupColor); liberties += markGroup(gameBoard, col - 1, row, groupColor);
} }
if (col < gameBoard.getSize() - 1) { if (col < gameBoard.getSize() - 1) {
liberties += markGroup(gameBoard,col+1,row,groupColor); liberties += markGroup(gameBoard, col + 1, row, groupColor);
} }
if (row > 0) { if (row > 0) {
liberties += markGroup(gameBoard,col,row-1,groupColor); liberties += markGroup(gameBoard, col, row - 1, groupColor);
} }
if (row < gameBoard.getSize() - 1) { if (row < gameBoard.getSize() - 1) {
liberties += markGroup(gameBoard,col,row+1,groupColor); liberties += markGroup(gameBoard, col, row + 1, groupColor);
} }
return liberties; return liberties;
} }
if (groupColor == GameBoard.WHITE_STONE && stoneSymbol == GameBoard.BLACK_STONE) { if (groupColor == GameBoard.WHITE_STONE
return 0; //opposing stone - not a liberty && stoneSymbol == GameBoard.BLACK_STONE) {
return 0; // opposing stone - not a liberty
} }
if (groupColor == GameBoard.BLACK_STONE && stoneSymbol == GameBoard.WHITE_STONE) { if (groupColor == GameBoard.BLACK_STONE
return 0; //opposing stone - not a liberty && stoneSymbol == GameBoard.WHITE_STONE) {
return 0; // opposing stone - not a liberty
} }
throw new IllegalStateException("Cannot continue marking group - symbol at current location is not empty intersection, white stone, black stone or group marker"); throw new IllegalStateException(
"Cannot continue marking group - symbol at current location is not empty intersection, white stone, black stone or group marker");
} }
} }

View File

@@ -1,18 +1,21 @@
package net.woodyfolsom.msproj; package net.woodyfolsom.msproj;
public class Player { public class Player {
public static final Player BLACK = new Player("BLACK", GameBoard.BLACK_STONE); public static final Player BLACK = new Player("BLACK",
public static final Player NONE = new Player("NONE", GameBoard.EMPTY_INTERSECTION); GameBoard.BLACK_STONE);
public static final Player WHITE = new Player("WHITE", GameBoard.WHITE_STONE); public static final Player NONE = new Player("NONE",
GameBoard.EMPTY_INTERSECTION);
public static final Player WHITE = new Player("WHITE",
GameBoard.WHITE_STONE);
private char stoneSymbol; private char stoneSymbol;
private String name; private String name;
private Player(String name, char stoneSymbol) { private Player(String name, char stoneSymbol) {
this.name = name; this.name = name;
this.stoneSymbol = stoneSymbol; this.stoneSymbol = stoneSymbol;
} }
public static Player getInstance(String stoneSymbol) { public static Player getInstance(String stoneSymbol) {
if ("b".equals(stoneSymbol)) { if ("b".equals(stoneSymbol)) {
return Player.BLACK; return Player.BLACK;
@@ -22,15 +25,15 @@ public class Player {
return Player.NONE; return Player.NONE;
} }
} }
public char getStoneSymbol() { public char getStoneSymbol() {
return stoneSymbol; return stoneSymbol;
} }
public boolean isNone() { public boolean isNone() {
return "NONE".equals(name); return "NONE".equals(name);
} }
@Override @Override
public String toString() { public String toString() {
return name; return name;

View File

@@ -12,7 +12,7 @@ import net.woodyfolsom.msproj.policy.Policy;
public class Referee { public class Referee {
private Policy blackPolicy; private Policy blackPolicy;
private Policy whitePolicy; private Policy whitePolicy;
public Policy getPolicy(Player player) { public Policy getPolicy(Player player) {
if (Player.BLACK.equals(player)) { if (Player.BLACK.equals(player)) {
return blackPolicy; return blackPolicy;
@@ -37,7 +37,7 @@ public class Referee {
public GameResult play(GameConfig gameConfig) { public GameResult play(GameConfig gameConfig) {
GameState gameState = new GameState(gameConfig); GameState gameState = new GameState(gameConfig);
System.out.println("Game started."); System.out.println("Game started.");
try { try {
@@ -55,9 +55,9 @@ public class Referee {
ex.printStackTrace(); ex.printStackTrace();
return GameResult.VOID; return GameResult.VOID;
} }
GameResult result = gameState.getResult(); GameResult result = gameState.getResult();
System.out.println("Game over. Result: " + result); System.out.println("Game over. Result: " + result);
DateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmssZ"); DateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmssZ");
@@ -84,7 +84,7 @@ public class Referee {
} }
System.out.println("Game finished."); System.out.println("Game finished.");
return result; return result;
} }
} }

View File

@@ -7,22 +7,31 @@ import net.woodyfolsom.msproj.policy.HumanKeyboardInput;
import net.woodyfolsom.msproj.policy.MonteCarloUCT; import net.woodyfolsom.msproj.policy.MonteCarloUCT;
import net.woodyfolsom.msproj.policy.Policy; import net.woodyfolsom.msproj.policy.Policy;
import net.woodyfolsom.msproj.policy.RandomMovePolicy; import net.woodyfolsom.msproj.policy.RandomMovePolicy;
import net.woodyfolsom.msproj.policy.RootParallelization;
public class StandAloneGame { public class StandAloneGame {
enum PLAYER_TYPE { HUMAN, UCT_FAST, UCT_SLOW }; enum PLAYER_TYPE {
HUMAN, ROOT_PAR, UCT_FAST, UCT_SLOW
};
public static void main(String[] args) { public static void main(String[] args) {
if (args.length != 3) { if (args.length != 3) {
System.out.println("Incorrect # of arguments: use StandAloneGame <Player1Type> <Player2Type> <# rounds>"); System.out
System.out.println("For example to play 10 games against MC UCT w/ slow moves: StandAloneGame UCT_SLOW HUMAN 10"); .println("Incorrect # of arguments: use StandAloneGame <Player1Type> <Player2Type> <# rounds>");
System.out
.println("For example to play 10 games against MC UCT w/ slow moves: StandAloneGame UCT_SLOW HUMAN 10");
} }
new StandAloneGame().playGame(parsePlayerType(args[0]), parsePlayerType(args[1]), Integer.valueOf(args[2])); new StandAloneGame().playGame(parsePlayerType(args[0]),
parsePlayerType(args[1]), Integer.valueOf(args[2]));
} }
private static PLAYER_TYPE parsePlayerType(String playerTypeStr) { private static PLAYER_TYPE parsePlayerType(String playerTypeStr) {
if ("UCT_FAST".equalsIgnoreCase(playerTypeStr)) { if ("UCT_FAST".equalsIgnoreCase(playerTypeStr)) {
return PLAYER_TYPE.UCT_FAST; return PLAYER_TYPE.UCT_FAST;
} else if ("UCT_SLOW".equalsIgnoreCase(playerTypeStr) || "UCT".equalsIgnoreCase(playerTypeStr)) { } else if ("ROOT_PAR".equalsIgnoreCase(playerTypeStr)) {
return PLAYER_TYPE.ROOT_PAR;
} else if ("UCT_SLOW".equalsIgnoreCase(playerTypeStr)
|| "UCT".equalsIgnoreCase(playerTypeStr)) {
return PLAYER_TYPE.UCT_SLOW; return PLAYER_TYPE.UCT_SLOW;
} else if ("HUMAN".equalsIgnoreCase(playerTypeStr)) { } else if ("HUMAN".equalsIgnoreCase(playerTypeStr)) {
return PLAYER_TYPE.HUMAN; return PLAYER_TYPE.HUMAN;
@@ -30,38 +39,43 @@ public class StandAloneGame {
throw new RuntimeException("Unknown player type: " + playerTypeStr); throw new RuntimeException("Unknown player type: " + playerTypeStr);
} }
} }
public void playGame(PLAYER_TYPE playerType1, PLAYER_TYPE playerType2, int rounds) { public void playGame(PLAYER_TYPE playerType1, PLAYER_TYPE playerType2,
int rounds) {
Policy player1 = getPolicy(playerType1); Policy player1 = getPolicy(playerType1);
Policy player2 = getPolicy(playerType2); Policy player2 = getPolicy(playerType2);
Referee referee = new Referee(); Referee referee = new Referee();
referee.setPolicy(Player.BLACK, player1); referee.setPolicy(Player.BLACK, player1);
referee.setPolicy(Player.WHITE, player2); referee.setPolicy(Player.WHITE, player2);
List<GameResult> results = new ArrayList<GameResult>(); List<GameResult> results = new ArrayList<GameResult>();
GameConfig gameConfig = new GameConfig(5); GameConfig gameConfig = new GameConfig(5);
for (int round = 0; round < rounds; ) { for (int round = 0; round < rounds; round++) {
results.add(referee.play(gameConfig)); results.add(referee.play(gameConfig));
} }
System.out.println("Cumulative results for 10 games (BLACK=" + playerType1 + ", WHITE=" + playerType2 + ")"); System.out.println("Cumulative results for " + rounds + " games (BLACK="
for (int i = 0; i < rounds; i ++) { + playerType1 + ", WHITE=" + playerType2 + ")");
System.out.println(i +". " + results.get(i)); for (int i = 0; i < rounds; i++) {
System.out.println(i + ". " + results.get(i));
} }
} }
private Policy getPolicy(PLAYER_TYPE playerType) { private Policy getPolicy(PLAYER_TYPE playerType) {
switch (playerType) { switch (playerType) {
case HUMAN : case HUMAN:
return new HumanKeyboardInput(); return new HumanKeyboardInput();
case UCT_SLOW : case ROOT_PAR:
return new MonteCarloUCT(new RandomMovePolicy(), 3000L); return new RootParallelization(3, 4000L);
case UCT_FAST : case UCT_SLOW:
return new MonteCarloUCT(new RandomMovePolicy(), 4000L);
case UCT_FAST:
return new MonteCarloUCT(new RandomMovePolicy(), 1000L); return new MonteCarloUCT(new RandomMovePolicy(), 1000L);
default : default:
throw new IllegalArgumentException("Invalid PLAYER_TYPE: " + playerType); throw new IllegalArgumentException("Invalid PLAYER_TYPE: "
+ playerType);
} }
} }
} }

View File

@@ -5,19 +5,19 @@ public class TerritoryMarker {
public static final char WHITE_TERRITORY = 'o'; public static final char WHITE_TERRITORY = 'o';
public static final char UNOWNED_TERRITORY = '-'; public static final char UNOWNED_TERRITORY = '-';
public static final char TERRITORY_MARKER = '?'; public static final char TERRITORY_MARKER = '?';
public static final int EMPTY = 0; public static final int EMPTY = 0;
public static final int BLACK = 1; public static final int BLACK = 1;
public static final int WHITE = 2; public static final int WHITE = 2;
public static GameBoard markTerritory(GameBoard gameBoard) { public static GameBoard markTerritory(GameBoard gameBoard) {
for (int row = 0; row < gameBoard.getSize(); row++) { for (int row = 0; row < gameBoard.getSize(); row++) {
for (int col = 0; col < gameBoard.getSize(); col++) { for (int col = 0; col < gameBoard.getSize(); col++) {
char symbol = gameBoard.getSymbolAt(col,row); char symbol = gameBoard.getSymbolAt(col, row);
if (symbol != '.') { if (symbol != '.') {
continue; continue;
} }
int ownedBy = findTerritory(gameBoard,col,row); int ownedBy = findTerritory(gameBoard, col, row);
if (ownedBy == BLACK) { if (ownedBy == BLACK) {
gameBoard.markTerritory(BLACK_TERRITORY); gameBoard.markTerritory(BLACK_TERRITORY);
} else if (ownedBy == WHITE) { } else if (ownedBy == WHITE) {
@@ -30,9 +30,9 @@ public class TerritoryMarker {
gameBoard.setTerritoryMarked(true); gameBoard.setTerritoryMarked(true);
return gameBoard; return gameBoard;
} }
public static int getStoneColor(GameBoard gameBoard, int col, int row) { public static int getStoneColor(GameBoard gameBoard, int col, int row) {
char symbol = gameBoard.getSymbolAt(col,row); char symbol = gameBoard.getSymbolAt(col, row);
if (symbol == GameBoard.BLACK_STONE) { if (symbol == GameBoard.BLACK_STONE) {
return BLACK; return BLACK;
} else if (symbol == GameBoard.WHITE_STONE) { } else if (symbol == GameBoard.WHITE_STONE) {
@@ -41,9 +41,9 @@ public class TerritoryMarker {
return EMPTY; return EMPTY;
} }
} }
public static int findTerritory(GameBoard gameBoard, int col, int row) { public static int findTerritory(GameBoard gameBoard, int col, int row) {
char symbol = gameBoard.getSymbolAt(col,row); char symbol = gameBoard.getSymbolAt(col, row);
if (symbol == GameBoard.BLACK_STONE) { if (symbol == GameBoard.BLACK_STONE) {
return BLACK; return BLACK;
@@ -52,23 +52,23 @@ public class TerritoryMarker {
} else if (symbol == TERRITORY_MARKER) { } else if (symbol == TERRITORY_MARKER) {
return EMPTY; return EMPTY;
} }
gameBoard.markTerritory(col,row,TERRITORY_MARKER); gameBoard.markTerritory(col, row, TERRITORY_MARKER);
int borderBits = EMPTY; int borderBits = EMPTY;
if (col > 0) { if (col > 0) {
borderBits |= findTerritory(gameBoard,col-1,row); borderBits |= findTerritory(gameBoard, col - 1, row);
} }
if (col < gameBoard.getSize() - 1) { if (col < gameBoard.getSize() - 1) {
borderBits |= findTerritory(gameBoard,col+1,row); borderBits |= findTerritory(gameBoard, col + 1, row);
} }
if (row > 0) { if (row > 0) {
borderBits |= findTerritory(gameBoard,col,row-1); borderBits |= findTerritory(gameBoard, col, row - 1);
} }
if (row < gameBoard.getSize() - 1) { if (row < gameBoard.getSize() - 1) {
borderBits |= findTerritory(gameBoard,col,row+1); borderBits |= findTerritory(gameBoard, col, row + 1);
} }
return borderBits; return borderBits;
} }
} }

View File

@@ -10,10 +10,10 @@ import net.woodyfolsom.msproj.Player;
public interface ActionGenerator { public interface ActionGenerator {
public static final int ALL_ACTIONS = 0; public static final int ALL_ACTIONS = 0;
public List<Action> getActions(GameConfig gameConfig, GameState gameState, public List<Action> getActions(GameConfig gameConfig, GameState gameState,
Player color, int numActions); Player color, int numActions);
public List<Action> getActions(GameConfig gameConfig, GameState gameState, public List<Action> getActions(GameConfig gameConfig, GameState gameState,
Collection<Action> prohibitedMoves, Player color, int numActions); Collection<Action> prohibitedMoves, Player color, int numActions);
} }

View File

@@ -18,7 +18,7 @@ public class AlphaBeta implements Policy {
private int lookAhead; private int lookAhead;
private int numStateEvaluations = 0; private int numStateEvaluations = 0;
public AlphaBeta() { public AlphaBeta() {
this(DEFAULT_LOOKAHEAD); this(DEFAULT_LOOKAHEAD);
} }
@@ -52,8 +52,8 @@ public class AlphaBeta implements Policy {
GameState gameState = new GameState(node.getGameState()); GameState gameState = new GameState(node.getGameState());
List<Action> validMoves = validMoveGenerator.getActions( List<Action> validMoves = validMoveGenerator.getActions(node
node.getGameState().getGameConfig(), node.getGameState(), player, .getGameState().getGameConfig(), node.getGameState(), player,
ActionGenerator.ALL_ACTIONS); ActionGenerator.ALL_ACTIONS);
boolean terminal = isTerminal(validMoves.size(), recursionLevels); boolean terminal = isTerminal(validMoves.size(), recursionLevels);
@@ -64,9 +64,9 @@ public class AlphaBeta implements Policy {
if (terminal) { if (terminal) {
node.getProperties().setReward( node.getProperties().setReward(
gameState.getResult().getNormalizedScore()); gameState.getResult().getNormalizedScore());
numStateEvaluations++; numStateEvaluations++;
return bestAction; return bestAction;
} else { } else {
@@ -111,8 +111,8 @@ public class AlphaBeta implements Policy {
GameState gameState = new GameState(node.getGameState()); GameState gameState = new GameState(node.getGameState());
List<Action> validMoves = validMoveGenerator.getActions( List<Action> validMoves = validMoveGenerator.getActions(node
node.getGameState().getGameConfig(), node.getGameState(), player, .getGameState().getGameConfig(), node.getGameState(), player,
ActionGenerator.ALL_ACTIONS); ActionGenerator.ALL_ACTIONS);
boolean terminal = isTerminal(validMoves.size(), recursionLevels); boolean terminal = isTerminal(validMoves.size(), recursionLevels);
@@ -123,9 +123,9 @@ public class AlphaBeta implements Policy {
if (terminal) { if (terminal) {
node.getProperties().setReward( node.getProperties().setReward(
gameState.getResult().getNormalizedScore()); gameState.getResult().getNormalizedScore());
numStateEvaluations++; numStateEvaluations++;
return bestAction; return bestAction;
} else { } else {
@@ -164,15 +164,16 @@ public class AlphaBeta implements Policy {
return bestAction; return bestAction;
} }
} }
@Override @Override
public int getNumStateEvaluations() { public int getNumStateEvaluations() {
return numStateEvaluations; return numStateEvaluations;
} }
@Override @Override
public Action getAction(GameConfig gameConfig, GameState gameState, public Action getAction(GameConfig gameConfig, GameState gameState,
Collection<Action> prohibitedActions, Player player) { Collection<Action> prohibitedActions, Player player) {
throw new UnsupportedOperationException("Prohibited actions not supported by this class."); throw new UnsupportedOperationException(
"Prohibited actions not supported by this class.");
} }
} }

View File

@@ -16,17 +16,21 @@ public class HumanKeyboardInput implements Policy {
byte[] inputBytes = new byte[4]; byte[] inputBytes = new byte[4];
Action action = null; Action action = null;
String input = ""; String input = "";
do { do {
try { try {
System.out.println(player + " to move: (enter coord or PASS/RESIGN)"); System.out.println(player
+ " to move: (enter coord or PASS/RESIGN)");
System.in.read(inputBytes); System.in.read(inputBytes);
input = new String(inputBytes).trim().toUpperCase(); input = new String(inputBytes).trim().toUpperCase();
action = Action.getInstance(input); action = Action.getInstance(input);
if (action.isNone()) { if (action.isNone()) {
System.out.println("'" + input +"' is not a valid move. Please enter a coordinate (A1-T19) or 'PASS'."); System.out
.println("'"
+ input
+ "' is not a valid move. Please enter a coordinate (A1-T19) or 'PASS'.");
System.out.println(gameState); System.out.println(gameState);
continue; continue;
} }
@@ -34,27 +38,29 @@ public class HumanKeyboardInput implements Policy {
} catch (IOException ioe) { } catch (IOException ioe) {
System.out.println("IOException: " + ioe.getMessage()); System.out.println("IOException: " + ioe.getMessage());
} catch (IllegalArgumentException iae) { } catch (IllegalArgumentException iae) {
System.out.println("IllegalArgumentException - '"+input+"' is not a valid action or coordinate."); System.out.println("IllegalArgumentException - '" + input
+ "' is not a valid action or coordinate.");
} }
} while (action == null); } while (action == null);
return action; return action;
} }
@Override @Override
public Action getAction(GameConfig gameConfig, GameState gameState, public Action getAction(GameConfig gameConfig, GameState gameState,
Collection<Action> prohibitedActions, Player player) { Collection<Action> prohibitedActions, Player player) {
Action action = getAction(gameConfig,gameState,player); Action action = getAction(gameConfig, gameState, player);
if (!prohibitedActions.contains(action)) { if (!prohibitedActions.contains(action)) {
return action; return action;
} }
do { do {
System.out.println("Sorry, that action is prohibited. Please make another move."); System.out
action = getAction(gameConfig,gameState,player); .println("Sorry, that action is prohibited. Please make another move.");
action = getAction(gameConfig, gameState, player);
} while (prohibitedActions.contains(action)); } while (prohibitedActions.contains(action));
return action; return action;
} }

View File

@@ -18,7 +18,7 @@ public class Minimax implements Policy {
private int lookAhead; private int lookAhead;
private int numStateEvaluations = 0; private int numStateEvaluations = 0;
public Minimax() { public Minimax() {
this(DEFAULT_LOOKAHEAD); this(DEFAULT_LOOKAHEAD);
} }
@@ -51,8 +51,8 @@ public class Minimax implements Policy {
GameState gameState = new GameState(node.getGameState()); GameState gameState = new GameState(node.getGameState());
List<Action> validMoves = validMoveGenerator.getActions( List<Action> validMoves = validMoveGenerator.getActions(node
node.getGameState().getGameConfig(), node.getGameState(), player, .getGameState().getGameConfig(), node.getGameState(), player,
ActionGenerator.ALL_ACTIONS); ActionGenerator.ALL_ACTIONS);
boolean terminal = isTerminal(validMoves.size(), recursionLevels); boolean terminal = isTerminal(validMoves.size(), recursionLevels);
@@ -63,9 +63,9 @@ public class Minimax implements Policy {
if (terminal) { if (terminal) {
node.getProperties().setReward( node.getProperties().setReward(
gameState.getResult().getNormalizedScore()); gameState.getResult().getNormalizedScore());
numStateEvaluations++; numStateEvaluations++;
return bestAction; return bestAction;
} else { } else {
@@ -97,8 +97,8 @@ public class Minimax implements Policy {
GameState gameState = new GameState(node.getGameState()); GameState gameState = new GameState(node.getGameState());
List<Action> validMoves = validMoveGenerator.getActions( List<Action> validMoves = validMoveGenerator.getActions(node
node.getGameState().getGameConfig(), node.getGameState(), player, .getGameState().getGameConfig(), node.getGameState(), player,
ActionGenerator.ALL_ACTIONS); ActionGenerator.ALL_ACTIONS);
boolean terminal = isTerminal(validMoves.size(), recursionLevels); boolean terminal = isTerminal(validMoves.size(), recursionLevels);
@@ -109,9 +109,9 @@ public class Minimax implements Policy {
if (terminal) { if (terminal) {
node.getProperties().setReward( node.getProperties().setReward(
gameState.getResult().getNormalizedScore()); gameState.getResult().getNormalizedScore());
numStateEvaluations++; numStateEvaluations++;
return bestAction; return bestAction;
} else { } else {
@@ -137,7 +137,7 @@ public class Minimax implements Policy {
return bestAction; return bestAction;
} }
} }
@Override @Override
public int getNumStateEvaluations() { public int getNumStateEvaluations() {
return numStateEvaluations; return numStateEvaluations;
@@ -146,6 +146,7 @@ public class Minimax implements Policy {
@Override @Override
public Action getAction(GameConfig gameConfig, GameState gameState, public Action getAction(GameConfig gameConfig, GameState gameState,
Collection<Action> prohibitedActions, Player player) { Collection<Action> prohibitedActions, Player player) {
throw new UnsupportedOperationException("Prohibited actions not supported by this class."); throw new UnsupportedOperationException(
"Prohibited actions not supported by this class.");
} }
} }

View File

@@ -1,7 +1,9 @@
package net.woodyfolsom.msproj.policy; package net.woodyfolsom.msproj.policy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import net.woodyfolsom.msproj.Action; import net.woodyfolsom.msproj.Action;
import net.woodyfolsom.msproj.GameConfig; import net.woodyfolsom.msproj.GameConfig;
@@ -12,88 +14,109 @@ import net.woodyfolsom.msproj.tree.MonteCarloProperties;
public abstract class MonteCarlo implements Policy { public abstract class MonteCarlo implements Policy {
protected static final int ROLLOUT_DEPTH_LIMIT = 100; protected static final int ROLLOUT_DEPTH_LIMIT = 100;
protected int numStateEvaluations = 0; protected int numStateEvaluations = 0;
protected Policy movePolicy; protected Policy movePolicy;
protected long searchTimeLimit; protected long searchTimeLimit;
protected volatile long elapsedTime = 0L; protected volatile long elapsedTime = 0L;
public MonteCarlo(Policy movePolicy, long searchTimeLimit) { public MonteCarlo(Policy movePolicy, long searchTimeLimit) {
this.movePolicy = movePolicy; this.movePolicy = movePolicy;
this.searchTimeLimit = searchTimeLimit; this.searchTimeLimit = searchTimeLimit;
} }
/** /**
* Descend the tree from the specified node and return a list of nodes to grow. * Descend the tree from the specified node and return a list of nodes to
* grow.
* *
* @param node * @param node
* @return * @return
*/ */
public abstract List<GameTreeNode<MonteCarloProperties>> descend(GameTreeNode<MonteCarloProperties> node); public abstract List<GameTreeNode<MonteCarloProperties>> descend(
GameTreeNode<MonteCarloProperties> node);
@Override
public Action getAction(GameConfig gameConfig, GameState gameState, private GameTreeNode<MonteCarloProperties> buildTree(GameConfig gameConfig, GameState gameState, Player player) {
Player player) { System.out.println(player + " is thinking for up to "
System.out.println(player + " is thinking for up to " + (searchTimeLimit / 1000.0) + " seconds..."); + (searchTimeLimit / 1000.0) + " seconds...");
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
if (gameState.getPlayerToMove() != player) { if (gameState.getPlayerToMove() != player) {
throw new RuntimeException("getAction(..." + player +") was requested but GameState.playerToMove was " + gameState.getPlayerToMove()); throw new RuntimeException("getAction(..." + player
+ ") was requested but GameState.playerToMove was "
+ gameState.getPlayerToMove());
} }
//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 GameTreeNode<MonteCarloProperties> rootNode = new GameTreeNode<MonteCarloProperties>(
//result in a win. gameState, new MonteCarloProperties());
GameTreeNode<MonteCarloProperties> rootNode = new GameTreeNode<MonteCarloProperties>(gameState, new MonteCarloProperties());
do { do {
//TODO these return types may need to be lists for some MC methods // TODO these return types may need to be lists for some MC methods
List<GameTreeNode<MonteCarloProperties>> selectedNodes = descend(rootNode); List<GameTreeNode<MonteCarloProperties>> selectedNodes = descend(rootNode);
List<GameTreeNode<MonteCarloProperties>> newLeaves = new ArrayList<GameTreeNode<MonteCarloProperties>>(); List<GameTreeNode<MonteCarloProperties>> newLeaves = new ArrayList<GameTreeNode<MonteCarloProperties>>();
for (GameTreeNode<MonteCarloProperties> selectedNode: selectedNodes) { for (GameTreeNode<MonteCarloProperties> selectedNode : selectedNodes) {
Player playerToMove = selectedNode.getGameState().getPlayerToMove(); Player playerToMove = selectedNode.getGameState()
.getPlayerToMove();
for (GameTreeNode<MonteCarloProperties> newLeaf : grow(gameConfig, selectedNode, playerToMove)) {
for (GameTreeNode<MonteCarloProperties> newLeaf : grow(
gameConfig, selectedNode, playerToMove)) {
newLeaves.add(newLeaf); newLeaves.add(newLeaf);
} }
} }
for (GameTreeNode<MonteCarloProperties> newLeaf : newLeaves) { for (GameTreeNode<MonteCarloProperties> newLeaf : newLeaves) {
int reward = rollout(gameConfig, newLeaf, player); int reward = rollout(gameConfig, newLeaf, player);
update(newLeaf, reward); update(newLeaf, reward);
} }
elapsedTime = System.currentTimeMillis() - startTime; elapsedTime = System.currentTimeMillis() - startTime;
} while (elapsedTime < searchTimeLimit); } while (elapsedTime < searchTimeLimit);
//TODO: for debugging, temporarily specify the number of state evaluations rather than time limit
//} while (numStateEvaluations < searchTimeLimit); return rootNode;
return getBestAction(rootNode);
} }
@Override
public Action getAction(GameConfig gameConfig, GameState gameState,
Player player) {
return getBestAction(buildTree(gameConfig,gameState,player));
}
public Map<Action, MonteCarloProperties> getQvalues(GameConfig gameConfig, GameState gameState,
Player player) {
GameTreeNode<MonteCarloProperties> rootNode = buildTree(gameConfig,gameState,player);
Map<Action,MonteCarloProperties> qValues = new HashMap<Action,MonteCarloProperties>();
for (Action action : rootNode.getActions()) {
qValues.put(action, rootNode.getChild(action).getProperties());
}
return qValues;
}
public long getElapsedTime() { public long getElapsedTime() {
return elapsedTime; return elapsedTime;
} }
public abstract Action getBestAction(GameTreeNode<MonteCarloProperties> node); public abstract Action getBestAction(GameTreeNode<MonteCarloProperties> node);
public abstract List<GameTreeNode<MonteCarloProperties>> grow(GameConfig gameConfig, GameTreeNode<MonteCarloProperties> node, Player player); public abstract List<GameTreeNode<MonteCarloProperties>> grow(
GameConfig gameConfig, GameTreeNode<MonteCarloProperties> node,
public abstract int rollout(GameConfig gameConfig, GameTreeNode<MonteCarloProperties> node, Player player); Player player);
public abstract void update(GameTreeNode<MonteCarloProperties> node, int reward); public abstract int rollout(GameConfig gameConfig,
GameTreeNode<MonteCarloProperties> node, Player player);
public abstract void update(GameTreeNode<MonteCarloProperties> node,
int reward);
public long getSearchTimeLimit() { public long getSearchTimeLimit() {
return searchTimeLimit; return searchTimeLimit;
} }
public int doRollout() { public int doRollout() {
return 0; return 0;
} }
public int getNumStateEvaluations() { public int getNumStateEvaluations() {
return numStateEvaluations; return numStateEvaluations;
} }

View File

@@ -16,46 +16,55 @@ import net.woodyfolsom.msproj.tree.MonteCarloProperties;
public class MonteCarloUCT extends MonteCarlo { public class MonteCarloUCT extends MonteCarlo {
public static final double TUNING_CONSTANT = 0.50; public static final double TUNING_CONSTANT = 0.50;
public MonteCarloUCT(Policy movePolicy, long searchTimeLimit) { public MonteCarloUCT(Policy movePolicy, long searchTimeLimit) {
super(movePolicy, searchTimeLimit); super(movePolicy, searchTimeLimit);
} }
@Override @Override
public List<GameTreeNode<MonteCarloProperties>> descend(GameTreeNode<MonteCarloProperties> node) { public List<GameTreeNode<MonteCarloProperties>> descend(
GameTreeNode<MonteCarloProperties> node) {
double bestScore = Double.NEGATIVE_INFINITY; double bestScore = Double.NEGATIVE_INFINITY;
GameTreeNode<MonteCarloProperties> bestNode = node; GameTreeNode<MonteCarloProperties> bestNode = node;
//What if the optimum leaf node is actually a terminal node? // What if the optimum leaf node is actually a terminal node?
//From Kocsis and Szepesvari, the value of an actual terminal node is 0, so it will never be grown. // From Kocsis and Szepesvari, the value of an actual terminal node is
// 0, so it will never be grown.
double nodeVisits = node.getProperties().getVisits(); double nodeVisits = node.getProperties().getVisits();
Set<Action> actionsExplored = node.getActions(); Set<Action> actionsExplored = node.getActions();
GameState gameState = node.getGameState(); GameState gameState = node.getGameState();
Action randomNewChildAction = new RandomMovePolicy().getAction(node.getGameState().getGameConfig(), gameState, actionsExplored, gameState.getPlayerToMove()); Action randomNewChildAction = new RandomMovePolicy().getAction(node
.getGameState().getGameConfig(), gameState, actionsExplored,
//only descend to the best child if no new actions are available at this node gameState.getPlayerToMove());
// only descend to the best child if no new actions are available at
// this node
if (randomNewChildAction == Action.NONE) { if (randomNewChildAction == Action.NONE) {
for (Action action : actionsExplored) { for (Action action : actionsExplored) {
GameTreeNode<MonteCarloProperties> childNode = node.getChild(action); GameTreeNode<MonteCarloProperties> childNode = node
.getChild(action);
double childScore;
if (childNode.getGameState().isTerminal()) { double childScore;
childScore = 0.0; if (childNode.getGameState().isTerminal()) {
} else { childScore = 0.0;
MonteCarloProperties properties = childNode.getProperties(); } else {
childScore = (double) (properties.getWins() / properties.getVisits()) + (TUNING_CONSTANT * Math.sqrt(Math.log(nodeVisits) / childNode.getProperties().getVisits())); MonteCarloProperties properties = childNode.getProperties();
} childScore = (double) (properties.getWins() / properties
//TODO add random tie breaker? .getVisits())
//otherwise the child that is selected first will be biased + (TUNING_CONSTANT * Math.sqrt(Math.log(nodeVisits)
if (childScore >= bestScore) { / childNode.getProperties().getVisits()));
}
// TODO add random tie breaker?
// otherwise the child that is selected first will be biased
if (childScore >= bestScore) {
bestScore = childScore; bestScore = childScore;
bestNode = childNode; bestNode = childNode;
}
} }
} }
}
if (bestNode == node) { if (bestNode == node) {
List<GameTreeNode<MonteCarloProperties>> bestNodeList = new ArrayList<GameTreeNode<MonteCarloProperties>>(); List<GameTreeNode<MonteCarloProperties>> bestNodeList = new ArrayList<GameTreeNode<MonteCarloProperties>>();
bestNodeList.add(bestNode); bestNodeList.add(bestNode);
@@ -70,76 +79,94 @@ public class MonteCarloUCT extends MonteCarlo {
Action bestAction = Action.NONE; Action bestAction = Action.NONE;
double bestScore = Double.NEGATIVE_INFINITY; double bestScore = Double.NEGATIVE_INFINITY;
GameTreeNode<MonteCarloProperties> bestChild = null; GameTreeNode<MonteCarloProperties> bestChild = null;
for (Action action : node.getActions()) { for (Action action : node.getActions()) {
GameTreeNode<MonteCarloProperties> childNode = node.getChild(action); GameTreeNode<MonteCarloProperties> childNode = node
.getChild(action);
MonteCarloProperties properties = childNode.getProperties(); MonteCarloProperties properties = childNode.getProperties();
double childScore = (double) properties.getWins() / properties.getVisits(); double childScore = (double) properties.getWins()
/ properties.getVisits();
if (childScore >= bestScore) { if (childScore >= bestScore) {
bestScore = childScore; bestScore = childScore;
bestAction = action; bestAction = action;
bestChild = childNode; bestChild = childNode;
} }
} }
if (bestAction == Action.NONE) { if (bestAction == Action.NONE) {
System.out.println("MonteCarloUCT failed - no actions were found for the current game staet (not even PASS)."); System.out
.println("MonteCarloUCT failed - no actions were found for the current game staet (not even PASS).");
} else { } else {
System.out.println("Action " + bestAction + " selected for " + node.getGameState().getPlayerToMove() + " with simulated win ratio of " + (bestScore*100.0 + "%")); System.out.println("Action " + bestAction + " selected for "
System.out.println("It was visited " + bestChild.getProperties().getVisits() + " times out of " + node.getProperties().getVisits() + " rollouts among " + node.getNumChildren() + " valid actions from the current state."); + node.getGameState().getPlayerToMove()
+ " with simulated win ratio of "
+ (bestScore * 100.0 + "%"));
System.out.println("It was visited "
+ bestChild.getProperties().getVisits() + " times out of "
+ node.getProperties().getVisits() + " rollouts among "
+ node.getNumChildren()
+ " valid actions from the current state.");
} }
return bestAction; return bestAction;
} }
@Override @Override
public List<GameTreeNode<MonteCarloProperties>> grow(GameConfig gameConfig, GameTreeNode<MonteCarloProperties> node, Player player) { public List<GameTreeNode<MonteCarloProperties>> grow(GameConfig gameConfig,
GameTreeNode<MonteCarloProperties> node, Player player) {
Policy randomMovePolicy = new RandomMovePolicy(); Policy randomMovePolicy = new RandomMovePolicy();
Set<Action> exploredActions = node.getActions(); Set<Action> exploredActions = node.getActions();
Action action = randomMovePolicy.getAction(gameConfig, node.getGameState(), exploredActions, player); Action action = randomMovePolicy.getAction(gameConfig,
node.getGameState(), exploredActions, player);
if (Action.NONE == action) { if (Action.NONE == action) {
throw new RuntimeException("Unable to grow node - are all actions already explored? Board state: " + node.getGameState() + "\nExplored actions: " + exploredActions); throw new RuntimeException(
"Unable to grow node - are all actions already explored? Board state: "
+ node.getGameState() + "\nExplored actions: "
+ exploredActions);
} }
GameState nextGameState = new GameState(node.getGameState()); GameState nextGameState = new GameState(node.getGameState());
nextGameState.playStone(player, action); nextGameState.playStone(player, action);
List<GameTreeNode<MonteCarloProperties>> newChildren = new ArrayList<GameTreeNode<MonteCarloProperties>>(); List<GameTreeNode<MonteCarloProperties>> newChildren = new ArrayList<GameTreeNode<MonteCarloProperties>>();
GameTreeNode<MonteCarloProperties> newChild = new GameTreeNode<MonteCarloProperties>(nextGameState,new MonteCarloProperties()); GameTreeNode<MonteCarloProperties> newChild = new GameTreeNode<MonteCarloProperties>(
nextGameState, new MonteCarloProperties());
newChildren.add(newChild); newChildren.add(newChild);
node.addChild(action, newChild); node.addChild(action, newChild);
return newChildren; return newChildren;
} }
@Override @Override
/** /**
* Rollout currently depends on the hardcoded ROLLOUT_DEPTH_LIMIT superclass parameter, * Rollout currently depends on the hardcoded ROLLOUT_DEPTH_LIMIT superclass parameter,
* Even with super-ko detection, a rollout might take an unrealistically long time due to unlikely playouts. * Even with super-ko detection, a rollout might take an unrealistically long time due to unlikely playouts.
*/ */
public int rollout(GameConfig gameConfig, GameTreeNode<MonteCarloProperties> node, Player player) { public int rollout(GameConfig gameConfig,
GameTreeNode<MonteCarloProperties> node, Player player) {
Policy randomMovePolicy = new RandomMovePolicy(); Policy randomMovePolicy = new RandomMovePolicy();
Action action; Action action;
int rolloutDepth = 0; int rolloutDepth = 0;
GameState rolloutGameState = new GameState(node.getGameState()); GameState rolloutGameState = new GameState(node.getGameState());
Player currentPlayer = rolloutGameState.getPlayerToMove(); Player currentPlayer = rolloutGameState.getPlayerToMove();
do { do {
rolloutDepth++; rolloutDepth++;
action = randomMovePolicy.getAction(gameConfig, rolloutGameState, currentPlayer); action = randomMovePolicy.getAction(gameConfig, rolloutGameState,
currentPlayer);
if (action != Action.NONE) { if (action != Action.NONE) {
if (!rolloutGameState.playStone(currentPlayer, action)) { if (!rolloutGameState.playStone(currentPlayer, action)) {
throw new RuntimeException("Failed to play move selected by RandomMovePolicy"); throw new RuntimeException(
"Failed to play move selected by RandomMovePolicy");
} }
currentPlayer = GoGame.getNextPlayer(currentPlayer); currentPlayer = GoGame.getNextPlayer(currentPlayer);
} }
} while (action != Action.NONE && rolloutDepth < ROLLOUT_DEPTH_LIMIT); } while (action != Action.NONE && rolloutDepth < ROLLOUT_DEPTH_LIMIT);
numStateEvaluations++; numStateEvaluations++;
GameResult gameScore = rolloutGameState.getResult(); GameResult gameScore = rolloutGameState.getResult();
if (gameScore.isWinner(player)) { if (gameScore.isWinner(player)) {
return 1; return 1;
} else { } else {
@@ -161,9 +188,10 @@ public class MonteCarloUCT extends MonteCarlo {
@Override @Override
public Action getAction(GameConfig gameConfig, GameState gameState, public Action getAction(GameConfig gameConfig, GameState gameState,
Collection<Action> prohibitedActions, Player player) { Collection<Action> prohibitedActions, Player player) {
throw new UnsupportedOperationException("Prohibited actions not supported by this class."); throw new UnsupportedOperationException(
"Prohibited actions not supported by this class.");
} }
@Override @Override
public String toString() { public String toString() {
return "MonteCarloUCT"; return "MonteCarloUCT";

View File

@@ -7,9 +7,12 @@ import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameState; import net.woodyfolsom.msproj.GameState;
import net.woodyfolsom.msproj.Player; import net.woodyfolsom.msproj.Player;
public interface Policy { public interface Policy {
public Action getAction(GameConfig gameConfig, GameState gameState, Player player); public Action getAction(GameConfig gameConfig, GameState gameState,
public Action getAction(GameConfig gameConfig, GameState gameState, Collection<Action> prohibitedActions, Player player); Player player);
public Action getAction(GameConfig gameConfig, GameState gameState,
Collection<Action> prohibitedActions, Player player);
public int getNumStateEvaluations(); public int getNumStateEvaluations();
} }

View File

@@ -9,21 +9,42 @@ import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameState; import net.woodyfolsom.msproj.GameState;
import net.woodyfolsom.msproj.Player; import net.woodyfolsom.msproj.Player;
public class RandomMovePolicy implements Policy, ActionGenerator { public class RandomMovePolicy implements Policy, ActionGenerator {
private final boolean passWhenLosing = false;
/** /**
* Does NOT modify the gameState. * Does NOT modify the gameState.
*/ */
public Action getAction(GameConfig gameConfig, GameState gameState, public Action getAction(GameConfig gameConfig, GameState gameState,
Collection<Action> prohibitedMoves, Player player) { Collection<Action> prohibitedMoves, Player player) {
return getActions(gameConfig, gameState, prohibitedMoves, player, 1).get(0);
List<Action> randomActions = getActions(gameConfig, gameState, prohibitedMoves, player, 1);
//Never randomly generate PASS when losing, because a good opponent will immediately recognize the killer
//move of also passing, thereby causing the current player to lose.
Action firstAction = randomActions.get(0);
//Pass when losing enabled? Just return the first random action;
if (passWhenLosing) {
return firstAction;
}
//But if passing is the only valid move, pass even if losing.
if (firstAction.isPass() && !gameState.getResult().isWinner(player)) {
if (randomActions.size() > 1) {
return randomActions.get(1);
} else {
return firstAction;
}
} else {
return firstAction;
}
} }
/** /**
* Attempts to generate up to nMoves random moves on behalf of the specified * Attempts to generate up to nMoves random moves on behalf of the specified
* player. Will return at least one move, which may be 'NONE' if random * player. Will return at least one move, which may be 'NONE' if random
* search does not succeeed in discovering a valid move. Does NOT modify the * search does not succeed in discovering a valid move. Does NOT modify the
* gameState. * gameState.
* *
* @param gameConfig * @param gameConfig
@@ -35,32 +56,35 @@ public class RandomMovePolicy implements Policy, ActionGenerator {
public List<Action> getActions(GameConfig gameConfig, GameState gameState, public List<Action> getActions(GameConfig gameConfig, GameState gameState,
Collection<Action> prohibitedMoves, Player player, int nMoves) { Collection<Action> prohibitedMoves, Player player, int nMoves) {
if (player != gameState.getPlayerToMove()) { if (player != gameState.getPlayerToMove()) {
throw new IllegalArgumentException("It is not " + player + "'s turn to move!"); throw new IllegalArgumentException("It is not " + player
+ "'s turn to move!");
} }
GameState gameStateCopy = new GameState(gameState); GameState gameStateCopy = new GameState(gameState);
ActionGenerator actionGenerator = new ValidMoveGenerator(); ActionGenerator actionGenerator = new ValidMoveGenerator();
List<Action> possibleActions = actionGenerator.getActions(gameConfig, gameStateCopy, prohibitedMoves, player, ActionGenerator.ALL_ACTIONS); List<Action> possibleActions = actionGenerator.getActions(gameConfig,
gameStateCopy, prohibitedMoves, player,
ActionGenerator.ALL_ACTIONS);
List<Action> randomActions = new ArrayList<Action>(); List<Action> randomActions = new ArrayList<Action>();
while (possibleActions.size() > 0 && randomActions.size() < nMoves) { while (possibleActions.size() > 0 && randomActions.size() < nMoves) {
Action randomAction = possibleActions Action randomAction = possibleActions
.remove((int) (Math.random() * possibleActions.size())); .remove((int) (Math.random() * possibleActions.size()));
randomActions.add(randomAction); randomActions.add(randomAction);
} }
if (randomActions.size() == 0) { if (randomActions.size() == 0) {
randomActions.add(Action.NONE); randomActions.add(Action.NONE);
} }
return randomActions; return randomActions;
} }
/** /**
* RandomMoveGenerator does not evaluate any states, but simply returns elements of * RandomMoveGenerator does not evaluate any states, but simply returns
* a set of uniformly distributed, distinct valid moves. * elements of a set of uniformly distributed, distinct valid moves.
* *
* @return * @return
*/ */
@@ -71,12 +95,13 @@ public class RandomMovePolicy implements Policy, ActionGenerator {
@Override @Override
public List<Action> getActions(GameConfig gameConfig, GameState gameState, public List<Action> getActions(GameConfig gameConfig, GameState gameState,
Player color, int numActions) { Player color, int numActions) {
return getActions(gameConfig, gameState, new ArrayList<Action>(), color, numActions); return getActions(gameConfig, gameState, new ArrayList<Action>(),
color, numActions);
} }
@Override @Override
public Action getAction(GameConfig gameConfig, GameState gameState, public Action getAction(GameConfig gameConfig, GameState gameState,
Player player) { Player player) {
return getActions(gameConfig,gameState,player,1).get(0); return getActions(gameConfig, gameState, player, 1).get(0);
} }
} }

View File

@@ -0,0 +1,145 @@
package net.woodyfolsom.msproj.policy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.woodyfolsom.msproj.Action;
import net.woodyfolsom.msproj.GameConfig;
import net.woodyfolsom.msproj.GameState;
import net.woodyfolsom.msproj.Player;
import net.woodyfolsom.msproj.tree.MonteCarloProperties;
public class RootParallelization implements Policy {
private int numTrees = 1;
private long timeLimit = 1000L;
public RootParallelization(int numTrees, long timeLimit) {
this.numTrees = numTrees;
this.timeLimit = timeLimit;
}
@Override
public Action getAction(GameConfig gameConfig, GameState gameState,
Player player) {
Action bestAction = Action.NONE;
List<PolicyRunner> policyRunners = new ArrayList<PolicyRunner>();
List<Thread> simulationThreads = new ArrayList<Thread>();
for (int i = 0; i < numTrees; i++) {
PolicyRunner policyRunner = new PolicyRunner(new MonteCarloUCT(
new RandomMovePolicy(), timeLimit), gameConfig, gameState,
player);
policyRunners.add(policyRunner);
Thread simThread = new Thread(policyRunner);
simulationThreads.add(simThread);
}
for (Thread simThread : simulationThreads) {
simThread.start();
}
for (Thread simThread : simulationThreads) {
try {
simThread.join();
} catch (InterruptedException ie) {
System.out
.println("Interrupted while waiting for Monte Carlo simulations to finish.");
}
}
Map<Action,Integer> totalReward = new HashMap<Action,Integer>();
Map<Action,Integer> numSims = new HashMap<Action,Integer>();
for (PolicyRunner policyRunner : policyRunners) {
Map<Action, MonteCarloProperties> qValues = policyRunner.getQvalues();
for (Action action : qValues.keySet()) {
if (totalReward.containsKey(action)) {
totalReward.put(action, totalReward.get(action) + qValues.get(action).getWins());
} else {
totalReward.put(action, qValues.get(action).getWins());
}
if (numSims.containsKey(action)) {
numSims.put(action, numSims.get(action) + qValues.get(action).getVisits());
} else {
numSims.put(action, qValues.get(action).getVisits());
}
}
}
double bestValue = 0.0;
int bestWins = 0;
int totalRollouts = 0;
for (Action action : totalReward.keySet())
{
int totalWins = totalReward.get(action);
int totalSims = numSims.get(action);
totalRollouts += totalSims;
double value = ((double)totalWins) / totalSims;
if (bestAction.isNone() || bestValue < value) {
bestAction = action;
bestValue = value;
bestWins = totalWins;
}
}
System.out.println("Action " + bestAction + " selected for "
+ player
+ " with simulated win ratio of "
+ (bestValue * 100.0 + "% among " + numTrees + " parallel simulations."));
System.out.println("It had a value of "
+ bestValue + " out of "
+ totalRollouts + " rollouts among "
+ " valid actions from the current state.");
return bestAction;
}
@Override
public Action getAction(GameConfig gameConfig, GameState gameState,
Collection<Action> prohibitedActions, Player player) {
throw new UnsupportedOperationException(
"Prohibited actions not supported by this class.");
}
@Override
public int getNumStateEvaluations() {
// TODO Auto-generated method stub
return 0;
}
class PolicyRunner implements Runnable {
Map<Action,MonteCarloProperties> qValues;
private GameConfig gameConfig;
private GameState gameState;
private Player player;
private MonteCarlo policy;
public PolicyRunner(MonteCarlo policy, GameConfig gameConfig,
GameState gameState, Player player) {
this.policy = policy;
this.gameConfig = gameConfig;
this.gameState = gameState;
this.player = player;
}
public Map<Action,MonteCarloProperties> getQvalues() {
return qValues;
}
@Override
public void run() {
qValues = policy.getQvalues(gameConfig, gameState, player);
}
}
}

View File

@@ -32,7 +32,7 @@ public class ValidMoveGenerator implements ActionGenerator {
if (!prohibitedMoves.contains(nextMove) if (!prohibitedMoves.contains(nextMove)
&& gameStateCopy.playStone(color, nextMove)) { && gameStateCopy.playStone(color, nextMove)) {
validMoves.add(nextMove); validMoves.add(nextMove);
gameStateCopy = new GameState(gameState); // play successful? gameStateCopy = new GameState(gameState); // play successful?
// regenerate copy // regenerate copy
// of gameState // of gameState
} }

View File

@@ -3,7 +3,7 @@ package net.woodyfolsom.msproj.sgf;
public class SGFCoord { public class SGFCoord {
private char column; private char column;
private char row; private char row;
public SGFCoord(String coords) { public SGFCoord(String coords) {
if (coords == null || coords.length() != 2) { if (coords == null || coords.length() != 2) {
throw new IllegalArgumentException(coords); throw new IllegalArgumentException(coords);
@@ -11,22 +11,22 @@ public class SGFCoord {
column = coords.charAt(0); column = coords.charAt(0);
row = coords.charAt(1); row = coords.charAt(1);
} }
public SGFCoord(char column, char row) { public SGFCoord(char column, char row) {
this.column = column; this.column = column;
this.row = row; this.row = row;
} }
public char getColumn() { public char getColumn() {
return column; return column;
} }
public char getRow() { public char getRow() {
return row; return row;
} }
@Override @Override
public String toString() { public String toString() {
return new String(new char[]{column,row}); return new String(new char[] { column, row });
} }
} }

View File

@@ -9,32 +9,31 @@ import net.woodyfolsom.msproj.Player;
public class SGFGameTree { public class SGFGameTree {
private List<SGFNode> nodeSequence = new ArrayList<SGFNode>(); private List<SGFNode> nodeSequence = new ArrayList<SGFNode>();
private List<SGFGameTree> subTrees = new ArrayList<SGFGameTree>(); private List<SGFGameTree> subTrees = new ArrayList<SGFGameTree>();
public int getNodeCount() { public int getNodeCount() {
return nodeSequence.size(); return nodeSequence.size();
} }
public List<SGFNode> getNodeSequence() { public List<SGFNode> getNodeSequence() {
return Collections.unmodifiableList(nodeSequence); return Collections.unmodifiableList(nodeSequence);
} }
public void setNodeSequence(List<SGFNode> nodeSequence) { public void setNodeSequence(List<SGFNode> nodeSequence) {
this.nodeSequence.clear(); this.nodeSequence.clear();
for(SGFNode node : nodeSequence) { for (SGFNode node : nodeSequence) {
this.nodeSequence.add(node); this.nodeSequence.add(node);
} }
} }
public void addSubTree(SGFGameTree subTree) { public void addSubTree(SGFGameTree subTree) {
subTrees.add(subTree); subTrees.add(subTree);
} }
public int getSubTreeCount() { public int getSubTreeCount() {
return subTrees.size(); return subTrees.size();
} }
public String toLateXmoves(Player player) { public String toLateXmoves(Player player) {
StringBuilder latexSB = new StringBuilder(); StringBuilder latexSB = new StringBuilder();
SGFNode.TYPE nodeType; SGFNode.TYPE nodeType;
@@ -50,7 +49,7 @@ public class SGFGameTree {
} else { } else {
throw new RuntimeException("Invalid player: " + player); throw new RuntimeException("Invalid player: " + player);
} }
boolean firstMove = true; boolean firstMove = true;
int nMoves = 0; int nMoves = 0;
for (SGFNode node : nodeSequence) { for (SGFNode node : nodeSequence) {
@@ -59,7 +58,7 @@ public class SGFGameTree {
} }
SGFValue<?> sgfValue = node.getFirstValue(sgfIdent); SGFValue<?> sgfValue = node.getFirstValue(sgfIdent);
if (sgfValue.isEmpty()) { if (sgfValue.isEmpty()) {
//TODO later this will be the LaTeX igo code for 'Pass'? // TODO later this will be the LaTeX igo code for 'Pass'?
continue; continue;
} }
if (firstMove) { if (firstMove) {
@@ -83,33 +82,36 @@ public class SGFGameTree {
latexSB.append("}\n"); latexSB.append("}\n");
return latexSB.toString(); return latexSB.toString();
} }
public String toLateX() { public String toLateX() {
StringBuilder latexSB = new StringBuilder(); StringBuilder latexSB = new StringBuilder();
//Somewhat convoluted logic here because the grammar does not require all root // Somewhat convoluted logic here because the grammar does not require
//properties to be included in the same node in the tree's node sequence, although they should // all root
//each be unique among all node sequences in the tree. // properties to be included in the same node in the tree's node
// sequence, although they should
// each be unique among all node sequences in the tree.
for (SGFNode node : nodeSequence) { for (SGFNode node : nodeSequence) {
SGFNode.TYPE nodeType = node.getType(); SGFNode.TYPE nodeType = node.getType();
switch (nodeType) { switch (nodeType) {
case ROOT : case ROOT:
latexSB.append("\\gobansize"); latexSB.append("\\gobansize");
latexSB.append("{"); latexSB.append("{");
latexSB.append(node.getFirstValue(SGFIdentifier.SIZE)); latexSB.append(node.getFirstValue(SGFIdentifier.SIZE));
latexSB.append("}\n"); latexSB.append("}\n");
latexSB.append("\\shortstack{\\showfullgoban\\\\"); latexSB.append("\\shortstack{\\showfullgoban\\\\");
SGFResult result = (SGFResult) node.getFirstValue(SGFIdentifier.RESULT).getValue(); SGFResult result = (SGFResult) node.getFirstValue(
latexSB.append(result.getFullText()); SGFIdentifier.RESULT).getValue();
latexSB.append("}\n"); latexSB.append(result.getFullText());
latexSB.append("}\n");
break; break;
default : default:
//ignore // ignore
} }
} }
return latexSB.toString(); return latexSB.toString();
} }
public String toSGF() { public String toSGF() {
StringBuilder sgfFormatString = new StringBuilder("("); StringBuilder sgfFormatString = new StringBuilder("(");
for (SGFNode node : nodeSequence) { for (SGFNode node : nodeSequence) {

View File

@@ -12,13 +12,13 @@ public class SGFIdentifier {
public static final SGFIdentifier RESULT = new SGFIdentifier("RE"); public static final SGFIdentifier RESULT = new SGFIdentifier("RE");
public static final SGFIdentifier SIZE = new SGFIdentifier("SZ"); public static final SGFIdentifier SIZE = new SGFIdentifier("SZ");
public static final SGFIdentifier TIME = new SGFIdentifier("TM"); public static final SGFIdentifier TIME = new SGFIdentifier("TM");
private String text; private String text;
private SGFIdentifier(String value) { private SGFIdentifier(String value) {
this.text = value.toString(); this.text = value.toString();
} }
@Override @Override
public String toString() { public String toString() {
return text; return text;

File diff suppressed because it is too large Load Diff

View File

@@ -4,11 +4,13 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
public class SGFNode { public class SGFNode {
public enum TYPE { ROOT, MOVE_BLACK, MOVE_WHITE, EMPTY } public enum TYPE {
ROOT, MOVE_BLACK, MOVE_WHITE, EMPTY
}
private List<SGFProperty> properties = new ArrayList<SGFProperty>(); private List<SGFProperty> properties = new ArrayList<SGFProperty>();
private TYPE type = TYPE.EMPTY; private TYPE type = TYPE.EMPTY;
public void addProperty(SGFProperty property) { public void addProperty(SGFProperty property) {
if (properties.size() == 0) { if (properties.size() == 0) {
SGFIdentifier sgfIdent = property.getIdentifier(); SGFIdentifier sgfIdent = property.getIdentifier();
@@ -22,37 +24,39 @@ public class SGFNode {
} }
properties.add(property); properties.add(property);
} }
public int getPropertyCount() { public int getPropertyCount() {
return properties.size(); return properties.size();
} }
public TYPE getType() { public TYPE getType() {
return type; return type;
} }
public SGFValue<?> getFirstValue(SGFIdentifier identifier) { public SGFValue<?> getFirstValue(SGFIdentifier identifier) {
for (int i = 0; i < properties.size(); i++) { for (int i = 0; i < properties.size(); i++) {
if (identifier == properties.get(i).getIdentifier()) { if (identifier == properties.get(i).getIdentifier()) {
return properties.get(i).getValues().get(0); return properties.get(i).getValues().get(0);
} }
} }
throw new RuntimeException("SGFNode does not contain a property with identifier '" + identifier +"'"); throw new RuntimeException(
"SGFNode does not contain a property with identifier '"
+ identifier + "'");
} }
public String toSGF() { public String toSGF() {
if (properties.size() == 0) { if (properties.size() == 0) {
throw new RuntimeException("SGFNode contains no properties"); throw new RuntimeException("SGFNode contains no properties");
} }
SGFProperty firstProp = properties.get(0); SGFProperty firstProp = properties.get(0);
String sgfFormatString = ";" + firstProp.toSGF(); String sgfFormatString = ";" + firstProp.toSGF();
for (int i = 1; i < properties.size(); i++) { for (int i = 1; i < properties.size(); i++) {
sgfFormatString += properties.get(i).toSGF(); sgfFormatString += properties.get(i).toSGF();
} }
return sgfFormatString; return sgfFormatString;
} }
} }

View File

@@ -7,32 +7,32 @@ import net.woodyfolsom.msproj.Player;
public class SGFNodeCollection { public class SGFNodeCollection {
private List<SGFGameTree> gameTrees = new ArrayList<SGFGameTree>(); private List<SGFGameTree> gameTrees = new ArrayList<SGFGameTree>();
public void add(SGFGameTree gameTree) { public void add(SGFGameTree gameTree) {
gameTrees.add(gameTree); gameTrees.add(gameTree);
} }
public SGFGameTree getGameTree(int index) { public SGFGameTree getGameTree(int index) {
return gameTrees.get(index); return gameTrees.get(index);
} }
public int getGameTreeCount() { public int getGameTreeCount() {
return gameTrees.size(); return gameTrees.size();
} }
public String toLateX() { public String toLateX() {
StringBuilder latexFormatString = new StringBuilder(""); StringBuilder latexFormatString = new StringBuilder("");
SGFGameTree gameTree = gameTrees.get(0); SGFGameTree gameTree = gameTrees.get(0);
latexFormatString.append(gameTree.toLateXmoves(Player.BLACK)); latexFormatString.append(gameTree.toLateXmoves(Player.BLACK));
latexFormatString.append(gameTree.toLateXmoves(Player.WHITE)); latexFormatString.append(gameTree.toLateXmoves(Player.WHITE));
latexFormatString.append("\\begin{center}\n"); latexFormatString.append("\\begin{center}\n");
latexFormatString.append(gameTree.toLateX()); latexFormatString.append(gameTree.toLateX());
latexFormatString.append("\\end{center}"); latexFormatString.append("\\end{center}");
return latexFormatString.toString(); return latexFormatString.toString();
} }
public String toSGF() { public String toSGF() {
String sgfFormatString = ""; String sgfFormatString = "";
for (SGFGameTree gameTree : gameTrees) { for (SGFGameTree gameTree : gameTrees) {
@@ -40,7 +40,7 @@ public class SGFNodeCollection {
} }
return sgfFormatString; return sgfFormatString;
} }
@Override @Override
public String toString() { public String toString() {
return toSGF(); return toSGF();

File diff suppressed because it is too large Load Diff

View File

@@ -2,27 +2,27 @@ package net.woodyfolsom.msproj.sgf;
public class SGFValue<T> { public class SGFValue<T> {
public static final SGFValue<String> EMPTY = new SGFValue<String>(""); public static final SGFValue<String> EMPTY = new SGFValue<String>("");
private String text; private String text;
private T value; private T value;
public SGFValue(T value) { public SGFValue(T value) {
this.text = value.toString(); this.text = value.toString();
this.value = value; this.value = value;
} }
public boolean isEmpty() { public boolean isEmpty() {
return text.length() == 0; return text.length() == 0;
} }
public String getText() { public String getText() {
return text; return text;
} }
public T getValue() { public T getValue() {
return value; return value;
} }
@Override @Override
public String toString() { public String toString() {
return value.toString(); return value.toString();

View File

@@ -2,7 +2,7 @@ package net.woodyfolsom.msproj.sgf;
public class StrValue { public class StrValue {
public final String value; public final String value;
public StrValue(String value) { public StrValue(String value) {
this.value = value; this.value = value;
} }

View File

@@ -1,22 +1,22 @@
package net.woodyfolsom.msproj.tree; package net.woodyfolsom.msproj.tree;
public class AlphaBetaProperties extends MinimaxProperties { public class AlphaBetaProperties extends MinimaxProperties {
double alpha = Double.NEGATIVE_INFINITY; double alpha = Double.NEGATIVE_INFINITY;
double beta = Double.POSITIVE_INFINITY; double beta = Double.POSITIVE_INFINITY;
public double getAlpha() { public double getAlpha() {
return alpha; return alpha;
} }
public void setAlpha(double d) { public void setAlpha(double d) {
this.alpha = d; this.alpha = d;
} }
public double getBeta() { public double getBeta() {
return beta; return beta;
} }
public void setBeta(double beta) { public void setBeta(double beta) {
this.beta = beta; this.beta = beta;
} }

View File

@@ -6,7 +6,6 @@ import java.util.Set;
import net.woodyfolsom.msproj.Action; import net.woodyfolsom.msproj.Action;
import net.woodyfolsom.msproj.GameState; import net.woodyfolsom.msproj.GameState;
import net.woodyfolsom.msproj.Player;
public class GameTreeNode<T extends GameTreeNodeProperties> { public class GameTreeNode<T extends GameTreeNodeProperties> {
private GameState gameState; private GameState gameState;
@@ -39,7 +38,7 @@ public class GameTreeNode<T extends GameTreeNodeProperties> {
public GameState getGameState() { public GameState getGameState() {
return gameState; return gameState;
} }
public GameTreeNode<T> getParent() { public GameTreeNode<T> getParent() {
return parent; return parent;
} }
@@ -47,7 +46,7 @@ public class GameTreeNode<T extends GameTreeNodeProperties> {
public T getProperties() { public T getProperties() {
return properties; return properties;
} }
public boolean isRoot() { public boolean isRoot() {
return parent == null; return parent == null;
} }

View File

@@ -3,19 +3,19 @@ package net.woodyfolsom.msproj.tree;
public class MonteCarloProperties extends GameTreeNodeProperties { public class MonteCarloProperties extends GameTreeNodeProperties {
int visits = 0; int visits = 0;
int wins = 0; int wins = 0;
public int getVisits() { public int getVisits() {
return visits; return visits;
} }
public void setVisits(int visits) { public void setVisits(int visits) {
this.visits = visits; this.visits = visits;
} }
public int getWins() { public int getWins() {
return wins; return wins;
} }
public void setWins(int wins) { public void setWins(int wins) {
this.wins = wins; this.wins = wins;
} }

View File

@@ -14,8 +14,11 @@ public class AlphaBetaTest {
Policy treeSearch = new AlphaBeta(); Policy treeSearch = new AlphaBeta();
GameConfig gameConfig = new GameConfig(6); GameConfig gameConfig = new GameConfig(6);
GameState gameState = new GameState(gameConfig); GameState gameState = new GameState(gameConfig);
gameState.playStone(Player.BLACK, Action.getInstance("PASS"));
gameState.playStone(Player.WHITE, Action.getInstance("A2")); gameState.playStone(Player.WHITE, Action.getInstance("A2"));
gameState.playStone(Player.BLACK, Action.getInstance("PASS"));
gameState.playStone(Player.WHITE, Action.getInstance("B1")); gameState.playStone(Player.WHITE, Action.getInstance("B1"));
gameState.playStone(Player.BLACK, Action.getInstance("PASS"));
gameState.playStone(Player.WHITE, Action.getInstance("C2")); gameState.playStone(Player.WHITE, Action.getInstance("C2"));
gameState.playStone(Player.BLACK, Action.getInstance("B2")); gameState.playStone(Player.BLACK, Action.getInstance("B2"));
@@ -38,7 +41,9 @@ public class AlphaBetaTest {
GameConfig gameConfig = new GameConfig(6); GameConfig gameConfig = new GameConfig(6);
GameState gameState = new GameState(gameConfig); GameState gameState = new GameState(gameConfig);
gameState.playStone(Player.BLACK, Action.getInstance("A2")); gameState.playStone(Player.BLACK, Action.getInstance("A2"));
gameState.playStone(Player.WHITE, Action.getInstance("PASS"));
gameState.playStone(Player.BLACK, Action.getInstance("B1")); gameState.playStone(Player.BLACK, Action.getInstance("B1"));
gameState.playStone(Player.WHITE, Action.getInstance("PASS"));
gameState.playStone(Player.BLACK, Action.getInstance("C2")); gameState.playStone(Player.BLACK, Action.getInstance("C2"));
gameState.playStone(Player.WHITE, Action.getInstance("B2")); gameState.playStone(Player.WHITE, Action.getInstance("B2"));

View File

@@ -17,8 +17,11 @@ public class MonteCarloUCTTest {
Policy treeSearch = new MonteCarloUCT(new RandomMovePolicy(),10000L); Policy treeSearch = new MonteCarloUCT(new RandomMovePolicy(),10000L);
GameConfig gameConfig = new GameConfig(6); GameConfig gameConfig = new GameConfig(6);
GameState gameState = new GameState(gameConfig); GameState gameState = new GameState(gameConfig);
gameState.playStone(Player.BLACK, Action.getInstance("PASS"));
gameState.playStone(Player.WHITE, Action.getInstance("A2")); gameState.playStone(Player.WHITE, Action.getInstance("A2"));
gameState.playStone(Player.BLACK, Action.getInstance("PASS"));
gameState.playStone(Player.WHITE, Action.getInstance("B1")); gameState.playStone(Player.WHITE, Action.getInstance("B1"));
gameState.playStone(Player.BLACK, Action.getInstance("PASS"));
gameState.playStone(Player.WHITE, Action.getInstance("C2")); gameState.playStone(Player.WHITE, Action.getInstance("C2"));
gameState.playStone(Player.BLACK, Action.getInstance("B2")); gameState.playStone(Player.BLACK, Action.getInstance("B2"));

View File

@@ -3,9 +3,6 @@ package net.woodyfolsom.msproj.sgf;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import org.antlr.runtime.ANTLRInputStream; import org.antlr.runtime.ANTLRInputStream;

View File

@@ -2,7 +2,6 @@ package net.woodyfolsom.msproj.sgf;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import org.antlr.runtime.ANTLRInputStream; import org.antlr.runtime.ANTLRInputStream;
@@ -15,20 +14,21 @@ public class SGFParserTest {
@Test @Test
public void testParse() throws RecognitionException, IOException { public void testParse() throws RecognitionException, IOException {
FileInputStream fis = new FileInputStream(new File("data/games/1334-gokifu-20120916-Gu_Li-Lee_Sedol.sgf")); FileInputStream fis = new FileInputStream(new File(
ANTLRStringStream in = new ANTLRInputStream(fis); "data/games/1334-gokifu-20120916-Gu_Li-Lee_Sedol.sgf"));
SGFLexer lexer = new SGFLexer(in); ANTLRStringStream in = new ANTLRInputStream(fis);
CommonTokenStream tokens = new CommonTokenStream(lexer); SGFLexer lexer = new SGFLexer(in);
SGFParser parser = new SGFParser(tokens); CommonTokenStream tokens = new CommonTokenStream(lexer);
SGFNodeCollection nodeCollection = parser.collection(); SGFParser parser = new SGFParser(tokens);
SGFNodeCollection nodeCollection = parser.collection();
System.out.println("To SGF:");
System.out.println(nodeCollection.toSGF()); System.out.println("To SGF:");
System.out.println(""); System.out.println(nodeCollection.toSGF());
System.out.println("");
System.out.println("To LaTeX:");
System.out.println(nodeCollection.toLateX()); System.out.println("To LaTeX:");
System.out.println(""); System.out.println(nodeCollection.toLateX());
System.out.println("");
} }
} }