diff --git a/src/net/woodyfolsom/msproj/Action.java b/src/net/woodyfolsom/msproj/Action.java index 3f1f7cd..97b728f 100644 --- a/src/net/woodyfolsom/msproj/Action.java +++ b/src/net/woodyfolsom/msproj/Action.java @@ -12,34 +12,48 @@ public class Action { //private Player player; private String move; - public static final Action NONE = new Action("NONE", /*Player.NONE,*/ 'x', 0); - public static final Action PASS = new Action("PASS", /*Player.NONE,*/ 'x', 0); + public static final Action NONE = new Action("NONE", 'x', 0); + public static final Action PASS = new Action("PASS", 'x', 0); + public static final Action RESIGN = new Action("RESIGN", 'x', 0); - private Action(String move, /*Player player,*/ char column, int row) { + private Action(String move, char column, int row) { this.move = move; - //this.player = player; this.column = column; this.row = row; } public static final Action getInstance(String move) { - if (move == null || "NONE".equals(move)) { - throw new IllegalArgumentException("Illegal move: " + move); + if (move.length() < 2) { + return Action.NONE; + } + + if ("NONE".equals(move)) { + return Action.NONE; } if ("PASS".equals(move)) { return Action.PASS; } + if ("RESIGN".equals(move)) { + return Action.RESIGN; + } + if (actionMap.containsKey(move)) { return actionMap.get(move); } - char col = move.charAt(0); - int row = Integer.valueOf(move.substring(1)); + Action action = Action.NONE; - Action action = new Action(move, col, row); - actionMap.put(move, action); + try { + char col = move.charAt(0); + int row = Integer.valueOf(move.substring(1)); + + action = new Action(move, col, row); + actionMap.put(move, action); + } catch (NumberFormatException nfe) { + return Action.NONE; + } return action; } @@ -60,6 +74,10 @@ public class Action { return this == Action.PASS; } + public boolean isResign() { + return this == Action.RESIGN; + } + @Override public String toString() { return move; diff --git a/src/net/woodyfolsom/msproj/GameResult.java b/src/net/woodyfolsom/msproj/GameResult.java new file mode 100644 index 0000000..64506d5 --- /dev/null +++ b/src/net/woodyfolsom/msproj/GameResult.java @@ -0,0 +1,100 @@ +package net.woodyfolsom.msproj; + + +public class GameResult { + public static final GameResult BLACK_BY_RESIGNATION = new GameResult(RESULT_TYPE.BLACK_BY_RESIGNATION); + public static final GameResult WHITE_BY_RESIGNATION = new GameResult(RESULT_TYPE.WHITE_BY_RESIGNATION); + + private double komi; + private int blackScore; + private int whiteScore; + private int normalizedZeroScore; + + public enum RESULT_TYPE { BLACK_BY_RESIGNATION, WHITE_BY_RESIGNATION, IN_PROGRESS, SCORED} + + private RESULT_TYPE resultType; + + public GameResult(int blackScore, int whiteScore, int size, double komi, boolean finished) { + this.blackScore = blackScore; + this.komi = komi; + this.whiteScore = whiteScore; + this.normalizedZeroScore = size * size + ((int)(komi * 2)); + if (finished) { + resultType = RESULT_TYPE.SCORED; + } else { + resultType = RESULT_TYPE.IN_PROGRESS; + } + } + + private GameResult(RESULT_TYPE resultType) { + komi = 0.0; + blackScore = 0; + whiteScore = 0; + normalizedZeroScore = 0; + + this.resultType = resultType; + } + + public double getBlackScore() { + return blackScore; + } + + public int getNormalizedZeroScore() { + return normalizedZeroScore; + } + + /** + * Gets a representation for the game score as an integer. Lower numbers are better for white. + * The minimum value is 0 (Chinese scoring - white owns every intersection on 19x19 and has 9 stone komi). + * Likewise, the maximum value if 379x2 (black owns every intersection with zero komi). + * @return + */ + public int getNormalizedScore() { + return normalizedZeroScore + 2 * blackScore - ((int)(2 * (whiteScore + komi))); + } + + public double getScore(Player color) { + if (color == Player.BLACK) { + return getBlackScore(); + } else if (color == Player.WHITE) { + return getWhiteScore(); + } else { + return 0.0; + } + } + + public double getWhiteScore() { + return (double)whiteScore + komi; + } + + public boolean isWinner(Player player) { + if (Player.WHITE == player) { + return whiteScore + komi > blackScore; + } else { + return blackScore > whiteScore + komi; + } + } + + public String toString() { + switch (resultType) { + case BLACK_BY_RESIGNATION : + return "B+R"; + case SCORED : + double blackScore = getBlackScore(); + double whiteScore = getWhiteScore(); + if (blackScore > whiteScore) { + return "B+" + (whiteScore - blackScore); + } else if (whiteScore > blackScore) { + return "W+" + (whiteScore - blackScore); + } else { + return "DRAW"; + } + case WHITE_BY_RESIGNATION : + return "W+R"; + case IN_PROGRESS : + return "Void"; + default : + return "?"; + } + } +} \ No newline at end of file diff --git a/src/net/woodyfolsom/msproj/GameScore.java b/src/net/woodyfolsom/msproj/GameScore.java deleted file mode 100644 index 1515f0e..0000000 --- a/src/net/woodyfolsom/msproj/GameScore.java +++ /dev/null @@ -1,79 +0,0 @@ -package net.woodyfolsom.msproj; - - -public class GameScore { - private double komi; - private int blackScore; - private int whiteScore; - private int normalizedZeroScore; - - public GameScore(int size) { - } - - public GameScore(int blackScore, int whiteScore, int size, double komi) { - this.blackScore = blackScore; - this.komi = komi; - this.whiteScore = whiteScore; - this.normalizedZeroScore = size * size + ((int)(komi * 2)); - } - - public double getBlackScore() { - return (double)blackScore - komi; - } - - public int getNormalizedZeroScore() { - return normalizedZeroScore; - } - /** - * Gets a representation for the game score as an integer. Lower numbers are better for white. - * The minimum value is 0 (Chinese scoring - white owns every intersection on 19x19 and has 9 stone komi). - * Likewise, the maximum value if 379x2 (black owns every intersection with zero komi). - * @return - */ - public int getAggregateScore() { - return normalizedZeroScore + 2 * blackScore - ((int)(2 * (whiteScore + komi))); - } - - public double getScore(Player color) { - if (color == Player.BLACK) { - return getBlackScore(); - } else if (color == Player.WHITE) { - return getWhiteScore(); - } else { - return 0.0; - } - } - - public double getWhiteScore() { - return (double)whiteScore + komi; - } - - public boolean isWinner(Player player) { - if (Player.WHITE == player) { - return whiteScore + komi > blackScore; - } else { - return blackScore > whiteScore + komi; - } - } - - public String toString() { - return "B: " + blackScore + "W: "+ whiteScore+"K:" + komi; - } - - public String getScoreReport() { - double blackScore = getBlackScore(); - double whiteScore = getWhiteScore(); - - boolean gameTied = Math.abs(blackScore - whiteScore) < 0.5; - - if (gameTied) { - return "Black +0 (tie)"; - } else if (blackScore > whiteScore) { - return "Black +" - + (blackScore - whiteScore - komi); - } else { - return "White +" - + (whiteScore + komi - blackScore); - } - } -} \ No newline at end of file diff --git a/src/net/woodyfolsom/msproj/GameState.java b/src/net/woodyfolsom/msproj/GameState.java index 88d683f..bcf476a 100644 --- a/src/net/woodyfolsom/msproj/GameState.java +++ b/src/net/woodyfolsom/msproj/GameState.java @@ -40,6 +40,50 @@ public class GameState { public int getBlackPrisoners() { return blackPrisoners; } + + public GameResult getResult() { + GameBoard markedBoard; + + if (gameBoard.isTerritoryMarked()) { + markedBoard = gameBoard; + } else { + markedBoard = new GameBoard(gameBoard); + TerritoryMarker.markTerritory(markedBoard); + } + + int gameLength = moveHistory.size(); + + //This functionality duplicates isTerminal(), + //however for the result to be reported, we need to know + //how the game was terminated to differentiate between resignation, scored complete + //and (eventually) time out/forfeit. + boolean isFinished = false; + + if (gameLength >= 1) { + if (moveHistory.get(gameLength-1).isResign()) { + if (gameLength % 2 == 1) { + return GameResult.WHITE_BY_RESIGNATION; + } else { + return GameResult.BLACK_BY_RESIGNATION; + } + } + if (gameLength >= 2) { + if (moveHistory.get(gameLength-1).isPass() + && moveHistory.get(gameLength-2).isPass()) { + isFinished = true; + } + } + } + + int blackScore = gameBoard.countSymbols(GameBoard.BLACK_STONE,GameBoard.BLACK_TERRITORY); + int whiteScore = gameBoard.countSymbols(GameBoard.WHITE_STONE,GameBoard.WHITE_TERRITORY); + + return new GameResult( blackScore, + whiteScore, + markedBoard.getSize(), + gameConfig.getKomi(), + isFinished); + } public List getEmptyCoords() { List emptyCoords = new ArrayList(); @@ -100,11 +144,16 @@ public class GameState { if (action.isPass()) { playerToMove = GoGame.getNextPlayer(player); - //TODO will need to record player as well moveHistory.add(action); return true; } + if (action.isResign()) { + playerToMove = Player.NONE; + moveHistory.add(action); + return true; + } + if (action.isNone()) { return false; } @@ -218,25 +267,6 @@ public class GameState { return true; } } - - /* - @Deprecated - private void assertCorrectHash() { - long hashFromHistory = gameBoard.getZobristHash(); - - int boardSize = gameBoard.getSize(); - ZobristHashGenerator zhg = ZobristHashGenerator.getInstance(boardSize); - long recalculatedHash = zhg.getEmptyBoardHash(); - - for (int i = 0; i < boardSize * boardSize; i ++) { - recalculatedHash ^= zhg.getHashCode(i, GameBoard.EMPTY_INTERSECTION); - recalculatedHash ^= zhg.getHashCode(i, gameBoard.getSymbolAt(i)); - } - - if (hashFromHistory != recalculatedHash) { - throw new RuntimeException("Zobrist hash code mismatch"); - } - }*/ public boolean isTerminal() { int nMoves = moveHistory.size(); diff --git a/src/net/woodyfolsom/msproj/GoGame.java b/src/net/woodyfolsom/msproj/GoGame.java index a1646e1..411472f 100644 --- a/src/net/woodyfolsom/msproj/GoGame.java +++ b/src/net/woodyfolsom/msproj/GoGame.java @@ -137,7 +137,7 @@ public class GoGame implements Runnable { break; case final_status_list: System.out.println("Final game score: "); - System.out.println(new StateEvaluator(gameConfig).scoreGame(gameState)); + System.out.println(gameState.getResult()); System.out.println(); localOutput.println("=\n"); break; @@ -159,7 +159,7 @@ public class GoGame implements Runnable { player); gameState.playStone(player, nextMove); - LOGGER.info(new StateEvaluator(gameConfig).scoreGame(gameState)); + LOGGER.info(gameState.getResult()); localOutput.println("=" + nextMove.toString() + "\n"); @@ -198,7 +198,7 @@ public class GoGame implements Runnable { LOGGER.info("GameState: " + gameState); localOutput.println("?" + errMsg + "\n"); } - LOGGER.info(new StateEvaluator(gameConfig).scoreGame(gameState)); + LOGGER.info(gameState.getResult()); break; case version: localOutput.println("= 0.1\n"); diff --git a/src/net/woodyfolsom/msproj/Referee.java b/src/net/woodyfolsom/msproj/Referee.java index 3125059..0ce5e86 100644 --- a/src/net/woodyfolsom/msproj/Referee.java +++ b/src/net/woodyfolsom/msproj/Referee.java @@ -1,5 +1,75 @@ package net.woodyfolsom.msproj; -public class Referee { +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import net.woodyfolsom.msproj.policy.Policy; + +public class Referee { + private GameConfig gameConfig = new GameConfig(9); + private GameState gameState = new GameState(gameConfig); + private Policy blackPolicy; + private Policy whitePolicy; + + public Policy getPolicy(Player player) { + if (Player.BLACK.equals(player)) { + return blackPolicy; + } else if (Player.WHITE.equals(player)) { + return whitePolicy; + } else { + throw new IllegalArgumentException("Player must be one of {Player.WHITE, Player.BLACK}."); + } + } + + public void setPolicy(Player player, Policy policy) { + if (Player.BLACK.equals(player)) { + blackPolicy = policy; + } else if (Player.WHITE.equals(player)) { + whitePolicy = policy; + } else { + throw new IllegalArgumentException("Player must be one of {Player.WHITE, Player.BLACK}."); + } + } + + public void play() { + System.out.println("Game started."); + + Player currentPlayer = Player.BLACK; + + while (!gameState.isTerminal()) { + System.out.println(gameState); + + Action action = getPolicy(currentPlayer).getAction(gameConfig, gameState, currentPlayer); + gameState.playStone(currentPlayer, action); + currentPlayer = GoGame.getNextPlayer(currentPlayer); + } + + System.out.println("Game over. Result: " + gameState.getResult()); + + DateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmssZ"); + + try { + + File sgfFile = new File("gogame-" + dateFormat.format(new Date())+ ".sgf"); + FileOutputStream fos = new FileOutputStream(sgfFile); + try { + SGFWriter.writeSGF(gameState, fos); + System.out.println("Game saved as " + sgfFile.getAbsolutePath()); + } finally { + try { + fos.close(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + } catch(IOException ioe) { + System.out.println("Unable to save game file due to IOException: " + ioe.getMessage()); + } + + System.out.println("Game finished."); + } } diff --git a/src/net/woodyfolsom/msproj/SGFWriter.java b/src/net/woodyfolsom/msproj/SGFWriter.java new file mode 100644 index 0000000..e14f2bb --- /dev/null +++ b/src/net/woodyfolsom/msproj/SGFWriter.java @@ -0,0 +1,22 @@ +package net.woodyfolsom.msproj; + +import java.io.IOException; +import java.io.OutputStream; + +import net.woodyfolsom.msproj.sgf.SGFIdentifier; + +public class SGFWriter { + + public static void writeSGF(GameState gameState, OutputStream os) + throws IOException { + writeNode(os, SGFIdentifier.FILE_FORMAT, Integer.valueOf(4)); + } + + private static void writeNode(OutputStream os, SGFIdentifier sgfIdent, + Object nodeValue) throws IOException { + os.write(sgfIdent.toString().getBytes()); + os.write("[".getBytes()); + os.write(nodeValue.toString().getBytes()); + os.write("];".getBytes()); + } +} \ No newline at end of file diff --git a/src/net/woodyfolsom/msproj/StandAloneGame.java b/src/net/woodyfolsom/msproj/StandAloneGame.java new file mode 100644 index 0000000..4ff939a --- /dev/null +++ b/src/net/woodyfolsom/msproj/StandAloneGame.java @@ -0,0 +1,24 @@ +package net.woodyfolsom.msproj; + +import net.woodyfolsom.msproj.policy.HumanKeyboardInput; +import net.woodyfolsom.msproj.policy.MonteCarloUCT; +import net.woodyfolsom.msproj.policy.Policy; +import net.woodyfolsom.msproj.policy.RandomMovePolicy; + +public class StandAloneGame { + + /** + * @param args + */ + public static void main(String[] args) { + Policy player1 = new HumanKeyboardInput(); + Policy player2 = new MonteCarloUCT(new RandomMovePolicy(), 5000L); + + Referee referee = new Referee(); + referee.setPolicy(Player.BLACK, player1); + referee.setPolicy(Player.WHITE, player2); + + referee.play(); + } + +} diff --git a/src/net/woodyfolsom/msproj/StateEvaluator.java b/src/net/woodyfolsom/msproj/StateEvaluator.java deleted file mode 100644 index 80d957c..0000000 --- a/src/net/woodyfolsom/msproj/StateEvaluator.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.woodyfolsom.msproj; - -public class StateEvaluator { - private final GameConfig gameConfig; - - public StateEvaluator(GameConfig gameConfig) { - this.gameConfig = gameConfig; - } - - public GameConfig getGameConfig() { - return gameConfig; - } - - public GameScore scoreGame(GameState gameState) { - GameBoard gameBoard; - if (gameState.getGameBoard().isTerritoryMarked()) { - gameBoard = gameState.getGameBoard(); - } else { - gameBoard = new GameBoard(gameState.getGameBoard()); - TerritoryMarker.markTerritory(gameBoard); - } - - //TODO include komi from gameConfig - return new GameScore(gameBoard.countSymbols(GameBoard.BLACK_STONE,GameBoard.BLACK_TERRITORY), - gameBoard.countSymbols(GameBoard.WHITE_STONE,GameBoard.WHITE_TERRITORY),gameState.getGameBoard().getSize(),gameConfig.getKomi()); - } -} \ No newline at end of file diff --git a/src/net/woodyfolsom/msproj/policy/AlphaBeta.java b/src/net/woodyfolsom/msproj/policy/AlphaBeta.java index 0ca4b2b..4b69223 100644 --- a/src/net/woodyfolsom/msproj/policy/AlphaBeta.java +++ b/src/net/woodyfolsom/msproj/policy/AlphaBeta.java @@ -8,7 +8,6 @@ import net.woodyfolsom.msproj.GameConfig; import net.woodyfolsom.msproj.GameState; import net.woodyfolsom.msproj.GoGame; import net.woodyfolsom.msproj.Player; -import net.woodyfolsom.msproj.StateEvaluator; import net.woodyfolsom.msproj.tree.AlphaBetaProperties; import net.woodyfolsom.msproj.tree.GameTreeNode; @@ -33,16 +32,14 @@ public class AlphaBeta implements Policy { Player player) { numStateEvaluations = 0; - - StateEvaluator stateEvaluator = new StateEvaluator(gameConfig); GameTreeNode rootNode = new GameTreeNode( gameState, new AlphaBetaProperties()); if (player == Player.BLACK) { - return getMax(lookAhead * 2, stateEvaluator, rootNode, player); + return getMax(lookAhead * 2, rootNode, player); } else { - return getMin(lookAhead * 2, stateEvaluator, rootNode, player); + return getMin(lookAhead * 2, rootNode, player); } } @@ -50,13 +47,13 @@ public class AlphaBeta implements Policy { return recursionLevels == 0 || nValidMoves == 0; } - private Action getMax(int recursionLevels, StateEvaluator stateEvaluator, + private Action getMax(int recursionLevels, GameTreeNode node, Player player) { GameState gameState = new GameState(node.getGameState()); List validMoves = validMoveGenerator.getActions( - stateEvaluator.getGameConfig(), node.getGameState(), player, + node.getGameState().getGameConfig(), node.getGameState(), player, ActionGenerator.ALL_ACTIONS); boolean terminal = isTerminal(validMoves.size(), recursionLevels); @@ -66,7 +63,7 @@ public class AlphaBeta implements Policy { if (terminal) { node.getProperties().setReward( - stateEvaluator.scoreGame(gameState).getAggregateScore()); + gameState.getResult().getNormalizedScore()); numStateEvaluations++; @@ -85,7 +82,7 @@ public class AlphaBeta implements Policy { node.getProperties().getBeta()); node.addChild(nextMove, childNode); - getMin(recursionLevels - 1, stateEvaluator, childNode, + getMin(recursionLevels - 1, childNode, GoGame.getNextPlayer(player)); double gameScore = childNode.getProperties().getReward(); @@ -109,13 +106,13 @@ public class AlphaBeta implements Policy { } } - private Action getMin(int recursionLevels, StateEvaluator stateEvaluator, + private Action getMin(int recursionLevels, GameTreeNode node, Player player) { GameState gameState = new GameState(node.getGameState()); List validMoves = validMoveGenerator.getActions( - stateEvaluator.getGameConfig(), node.getGameState(), player, + node.getGameState().getGameConfig(), node.getGameState(), player, ActionGenerator.ALL_ACTIONS); boolean terminal = isTerminal(validMoves.size(), recursionLevels); @@ -125,7 +122,7 @@ public class AlphaBeta implements Policy { if (terminal) { node.getProperties().setReward( - stateEvaluator.scoreGame(gameState).getAggregateScore()); + gameState.getResult().getNormalizedScore()); numStateEvaluations++; @@ -144,7 +141,7 @@ public class AlphaBeta implements Policy { node.getProperties().getBeta()); node.addChild(nextMove, childNode); - getMax(recursionLevels - 1, stateEvaluator, childNode, + getMax(recursionLevels - 1, childNode, GoGame.getNextPlayer(player)); double gameScore = childNode.getProperties().getReward(); diff --git a/src/net/woodyfolsom/msproj/policy/HumanKeyboardInput.java b/src/net/woodyfolsom/msproj/policy/HumanKeyboardInput.java new file mode 100644 index 0000000..61655fa --- /dev/null +++ b/src/net/woodyfolsom/msproj/policy/HumanKeyboardInput.java @@ -0,0 +1,67 @@ +package net.woodyfolsom.msproj.policy; + +import java.io.IOException; +import java.util.Collection; + +import net.woodyfolsom.msproj.Action; +import net.woodyfolsom.msproj.GameConfig; +import net.woodyfolsom.msproj.GameState; +import net.woodyfolsom.msproj.Player; + +public class HumanKeyboardInput implements Policy { + + @Override + public Action getAction(GameConfig gameConfig, GameState gameState, + Player player) { + byte[] inputBytes = new byte[4]; + Action action = null; + String input = ""; + + do { + try { + System.out.println(player + " to move: (enter coord or PASS/RESIGN)"); + System.in.read(inputBytes); + input = new String(inputBytes).trim().toUpperCase(); + + action = Action.getInstance(input); + + if (action.isNone()) { + System.out.println("'" + input +"' is not a valid move. Please enter a coordinate (A1-T19) or 'PASS'."); + System.out.println(gameState); + continue; + } + + } catch (IOException ioe) { + System.out.println("IOException: " + ioe.getMessage()); + } catch (IllegalArgumentException iae) { + System.out.println("IllegalArgumentException - '"+input+"' is not a valid action or coordinate."); + } + } while (action == null); + + return action; + } + + @Override + public Action getAction(GameConfig gameConfig, GameState gameState, + Collection prohibitedActions, Player player) { + Action action = getAction(gameConfig,gameState,player); + + if (!prohibitedActions.contains(action)) { + return action; + } + + do { + System.out.println("Sorry, that action is prohibited. Please make another move."); + action = getAction(gameConfig,gameState,player); + } while (prohibitedActions.contains(action)); + + return action; + } + + @Override + public int getNumStateEvaluations() { + // TODO Auto-generated method stub + return 1; + } + +} diff --git a/src/net/woodyfolsom/msproj/policy/Minimax.java b/src/net/woodyfolsom/msproj/policy/Minimax.java index 278b95c..7ae4df5 100644 --- a/src/net/woodyfolsom/msproj/policy/Minimax.java +++ b/src/net/woodyfolsom/msproj/policy/Minimax.java @@ -8,7 +8,6 @@ import net.woodyfolsom.msproj.GameConfig; import net.woodyfolsom.msproj.GameState; import net.woodyfolsom.msproj.GoGame; import net.woodyfolsom.msproj.Player; -import net.woodyfolsom.msproj.StateEvaluator; import net.woodyfolsom.msproj.tree.GameTreeNode; import net.woodyfolsom.msproj.tree.MinimaxProperties; @@ -32,16 +31,14 @@ public class Minimax implements Policy { public Action getAction(GameConfig gameConfig, GameState gameState, Player player) { numStateEvaluations = 0; - - StateEvaluator stateEvaluator = new StateEvaluator(gameConfig); GameTreeNode rootNode = new GameTreeNode( gameState, new MinimaxProperties()); if (player == Player.BLACK) { - return getMax(lookAhead * 2, stateEvaluator, rootNode, player); + return getMax(lookAhead * 2, rootNode, player); } else { - return getMin(lookAhead * 2, stateEvaluator, rootNode, player); + return getMin(lookAhead * 2, rootNode, player); } } @@ -49,13 +46,13 @@ public class Minimax implements Policy { return recursionLevels == 0 || nValidMoves == 0; } - private Action getMax(int recursionLevels, StateEvaluator stateEvaluator, + private Action getMax(int recursionLevels, GameTreeNode node, Player player) { GameState gameState = new GameState(node.getGameState()); List validMoves = validMoveGenerator.getActions( - stateEvaluator.getGameConfig(), node.getGameState(), player, + node.getGameState().getGameConfig(), node.getGameState(), player, ActionGenerator.ALL_ACTIONS); boolean terminal = isTerminal(validMoves.size(), recursionLevels); @@ -65,7 +62,7 @@ public class Minimax implements Policy { if (terminal) { node.getProperties().setReward( - stateEvaluator.scoreGame(gameState).getAggregateScore()); + gameState.getResult().getNormalizedScore()); numStateEvaluations++; @@ -79,7 +76,7 @@ public class Minimax implements Policy { nextState, new MinimaxProperties()); node.addChild(nextMove, childNode); - getMin(recursionLevels - 1, stateEvaluator, childNode, + getMin(recursionLevels - 1, childNode, GoGame.getNextPlayer(player)); double gameScore = childNode.getProperties().getReward(); @@ -95,13 +92,13 @@ public class Minimax implements Policy { } } - private Action getMin(int recursionLevels, StateEvaluator stateEvaluator, + private Action getMin(int recursionLevels, GameTreeNode node, Player player) { GameState gameState = new GameState(node.getGameState()); List validMoves = validMoveGenerator.getActions( - stateEvaluator.getGameConfig(), node.getGameState(), player, + node.getGameState().getGameConfig(), node.getGameState(), player, ActionGenerator.ALL_ACTIONS); boolean terminal = isTerminal(validMoves.size(), recursionLevels); @@ -111,7 +108,7 @@ public class Minimax implements Policy { if (terminal) { node.getProperties().setReward( - stateEvaluator.scoreGame(gameState).getAggregateScore()); + gameState.getResult().getNormalizedScore()); numStateEvaluations++; @@ -125,7 +122,7 @@ public class Minimax implements Policy { nextState, new MinimaxProperties()); node.addChild(nextMove, childNode); - getMax(recursionLevels - 1, stateEvaluator, childNode, + getMax(recursionLevels - 1, childNode, GoGame.getNextPlayer(player)); double gameScore = childNode.getProperties().getReward(); diff --git a/src/net/woodyfolsom/msproj/policy/MonteCarlo.java b/src/net/woodyfolsom/msproj/policy/MonteCarlo.java index a54274a..44b53d1 100644 --- a/src/net/woodyfolsom/msproj/policy/MonteCarlo.java +++ b/src/net/woodyfolsom/msproj/policy/MonteCarlo.java @@ -7,7 +7,6 @@ import net.woodyfolsom.msproj.Action; import net.woodyfolsom.msproj.GameConfig; import net.woodyfolsom.msproj.GameState; import net.woodyfolsom.msproj.Player; -import net.woodyfolsom.msproj.StateEvaluator; import net.woodyfolsom.msproj.tree.GameTreeNode; import net.woodyfolsom.msproj.tree.MonteCarloProperties; @@ -46,7 +45,7 @@ public abstract class MonteCarlo implements Policy { //result in a win. GameTreeNode rootNode = new GameTreeNode(gameState, new MonteCarloProperties()); - StateEvaluator stateEvaluator = new StateEvaluator(gameConfig); + do { //TODO these return types may need to be lists for some MC methods @@ -62,7 +61,7 @@ public abstract class MonteCarlo implements Policy { } for (GameTreeNode newLeaf : newLeaves) { - int reward = rollout(gameConfig, stateEvaluator, newLeaf, player); + int reward = rollout(gameConfig, newLeaf, player); update(newLeaf, reward); } @@ -82,7 +81,7 @@ public abstract class MonteCarlo implements Policy { public abstract List> grow(GameConfig gameConfig, GameTreeNode node, Player player); - public abstract int rollout(GameConfig gameConfig, StateEvaluator stateEvaluator, GameTreeNode node, Player player); + public abstract int rollout(GameConfig gameConfig, GameTreeNode node, Player player); public abstract void update(GameTreeNode node, int reward); diff --git a/src/net/woodyfolsom/msproj/policy/MonteCarloUCT.java b/src/net/woodyfolsom/msproj/policy/MonteCarloUCT.java index 1418ba7..429d779 100644 --- a/src/net/woodyfolsom/msproj/policy/MonteCarloUCT.java +++ b/src/net/woodyfolsom/msproj/policy/MonteCarloUCT.java @@ -7,11 +7,10 @@ import java.util.Set; import net.woodyfolsom.msproj.Action; import net.woodyfolsom.msproj.GameConfig; -import net.woodyfolsom.msproj.GameScore; +import net.woodyfolsom.msproj.GameResult; import net.woodyfolsom.msproj.GameState; import net.woodyfolsom.msproj.GoGame; import net.woodyfolsom.msproj.Player; -import net.woodyfolsom.msproj.StateEvaluator; import net.woodyfolsom.msproj.tree.GameTreeNode; import net.woodyfolsom.msproj.tree.MonteCarloProperties; @@ -119,7 +118,7 @@ public class MonteCarloUCT extends MonteCarlo { * 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. */ - public int rollout(GameConfig gameConfig, StateEvaluator stateEvaluator, GameTreeNode node, Player player) { + public int rollout(GameConfig gameConfig, GameTreeNode node, Player player) { Policy randomMovePolicy = new RandomMovePolicy(); Action action; @@ -139,18 +138,11 @@ public class MonteCarloUCT extends MonteCarlo { numStateEvaluations++; - GameScore gameScore = stateEvaluator.scoreGame(rolloutGameState); + GameResult gameScore = rolloutGameState.getResult(); + if (gameScore.isWinner(player)) { - //System.out.println(); - //System.out.println("Win for " + player + ":\n" + rolloutGameState); - //System.out.println(gameScore.getScoreReport()); - //System.out.println(); return 1; } else { - //System.out.println(); - //System.out.println("Loss for " + player + ":\n" + rolloutGameState); - //System.out.println(gameScore.getScoreReport()); - //System.out.println(); return 0; } } diff --git a/src/net/woodyfolsom/msproj/sgf/SGFReader.java b/src/net/woodyfolsom/msproj/sgf/SGFReader.java deleted file mode 100644 index 0ad808d..0000000 --- a/src/net/woodyfolsom/msproj/sgf/SGFReader.java +++ /dev/null @@ -1,5 +0,0 @@ -package net.woodyfolsom.msproj.sgf; - -public class SGFReader { - -} diff --git a/test/net/woodyfolsom/msproj/CaptureTest.java b/test/net/woodyfolsom/msproj/CaptureTest.java index 5d02c89..16dc9ed 100644 --- a/test/net/woodyfolsom/msproj/CaptureTest.java +++ b/test/net/woodyfolsom/msproj/CaptureTest.java @@ -54,8 +54,7 @@ public class CaptureTest { System.out.println(gameState); - GameScore gameScore = new StateEvaluator(gameConfig).scoreGame(gameState); - System.out.println(gameScore.getScoreReport()); + System.out.println(gameState.getResult()); } @Test diff --git a/test/net/woodyfolsom/msproj/GameScoreTest.java b/test/net/woodyfolsom/msproj/GameScoreTest.java index 2dc91f1..02adbe6 100644 --- a/test/net/woodyfolsom/msproj/GameScoreTest.java +++ b/test/net/woodyfolsom/msproj/GameScoreTest.java @@ -2,7 +2,7 @@ package net.woodyfolsom.msproj; import static org.junit.Assert.assertEquals; -import net.woodyfolsom.msproj.GameScore; +import net.woodyfolsom.msproj.GameResult; import org.junit.Test; @@ -10,19 +10,19 @@ public class GameScoreTest { @Test public void testGetAggregateScoreZero() { - GameScore gameScore = new GameScore(0,0,19,0); - assertEquals(gameScore.getNormalizedZeroScore(), gameScore.getAggregateScore()); + GameResult gameScore = new GameResult(0,0,19,0, true); + assertEquals(gameScore.getNormalizedZeroScore(), gameScore.getNormalizedScore()); } @Test public void testGetAggregateScoreBlackWinsNoKomi() { - GameScore gameScore = new GameScore(25,2,19,0); - assertEquals(425, gameScore.getAggregateScore()); + GameResult gameScore = new GameResult(25,2,19,0, true); + assertEquals(407, gameScore.getNormalizedScore()); } @Test public void testGetAggregateScoreWhiteWinsWithKomi() { - GameScore gameScore = new GameScore(10,12,19,6.5); - assertEquals(362, gameScore.getAggregateScore()); + GameResult gameScore = new GameResult(10,12,19,6.5, true); + assertEquals(357, gameScore.getNormalizedScore()); } } \ No newline at end of file diff --git a/test/net/woodyfolsom/msproj/StateEvaluatorTest.java b/test/net/woodyfolsom/msproj/StateEvaluatorTest.java deleted file mode 100644 index a41f1e2..0000000 --- a/test/net/woodyfolsom/msproj/StateEvaluatorTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package net.woodyfolsom.msproj; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - -public class StateEvaluatorTest { - - @Test - public void testScoreEmptyBoard() { - GameConfig gameConfig = new GameConfig(5); - GameState gameState = new GameState(gameConfig); - GameScore gameScore = new StateEvaluator(gameConfig) - .scoreGame(gameState); - - assertEquals(0.0, gameScore.getWhiteScore(), 0.5); - assertEquals(0.0, gameScore.getBlackScore(), 0.5); - } - - @Test - public void testScoreFirstMove() { - GameConfig gameConfig = new GameConfig(5); - GameState gameState = new GameState(gameConfig); - gameState.playStone(Player.BLACK, Action.getInstance("B3")); - - GameScore gameScore = new StateEvaluator(gameConfig) - .scoreGame(gameState); - - System.out.println(gameScore.getScoreReport()); - - assertEquals(0.0, gameScore.getWhiteScore(), 0.5); - assertEquals(25.0, gameScore.getBlackScore(), 0.5); - } - - @Test - public void testScoreTiedAtMove2() { - GameConfig gameConfig = new GameConfig(5); - GameState gameState = new GameState(gameConfig); - - gameState.playStone(Player.BLACK, Action.getInstance("B3")); - gameState.playStone(Player.WHITE, Action.getInstance("A1")); - GameScore gameScore = new StateEvaluator(gameConfig) - .scoreGame(gameState); - - System.out.println(gameScore.getScoreReport()); - - assertEquals(1.0, gameScore.getWhiteScore(), 0.5); - assertEquals(1.0, gameScore.getBlackScore(), 0.5); - } - - @Test - public void testScoreTerritory() { - GameConfig gameConfig = new GameConfig(5); - GameState gameState = new GameState(gameConfig); - - gameState.playStone(Player.BLACK, Action.getInstance("A2")); - gameState.playStone(Player.BLACK, Action.getInstance("B3")); - gameState.playStone(Player.BLACK, Action.getInstance("C2")); - gameState.playStone(Player.BLACK, Action.getInstance("B1")); - gameState.playStone(Player.WHITE, Action.getInstance("E5")); - - System.out.println(gameState); - GameScore gameScore = new StateEvaluator(gameConfig) - .scoreGame(gameState); - - System.out.println(gameScore.getScoreReport()); - - assertEquals(1.0, gameScore.getWhiteScore(), 0.5); - assertEquals(6.0, gameScore.getBlackScore(), 0.5); - // Black should be up by 5 if Black's territory at A1 & B2 is scored - // correctly. - } - - @Test - public void testCaptureAggScore() { - GameConfig gameConfig = new GameConfig(9); - GameState gameState = new GameState(gameConfig); - gameState.playStone(Player.WHITE, Action.getInstance("A2")); - gameState.playStone(Player.WHITE, Action.getInstance("B1")); - gameState.playStone(Player.WHITE, Action.getInstance("C2")); - gameState.playStone(Player.BLACK, Action.getInstance("B2")); - - GameState moveToA1 = new GameState(gameState); - GameState capAtB3 = new GameState(gameState); - - moveToA1.playStone(Player.WHITE, Action.getInstance("A1")); - capAtB3.playStone(Player.WHITE, Action.getInstance("B3")); - - System.out.println(moveToA1); - System.out.println(capAtB3); - - StateEvaluator eval = new StateEvaluator(gameConfig); - int scoreA1 = eval.scoreGame(moveToA1).getAggregateScore(); - int scoreB3 = eval.scoreGame(capAtB3).getAggregateScore(); - - System.out.println("Score at A1: " + scoreA1); - System.out.println("Score at B3: " + scoreB3); - // moving as white, lower is better - assertTrue(scoreA1 > scoreB3); - } -} \ No newline at end of file