From ca37280ed8fbaba51eabc52c68fcb6c8d6d90e78 Mon Sep 17 00:00:00 2001 From: Woody Folsom Date: Thu, 15 Nov 2012 15:00:40 -0500 Subject: [PATCH] Beginning to implement neural net training. Fixed bugs in SGF/LaTeX export. --- lib/encog-engine-2.5.0.jar | Bin 0 -> 69246 bytes src/net/woodyfolsom/msproj/Action.java | 42 +++++++ src/net/woodyfolsom/msproj/GameResult.java | 23 ++++ src/net/woodyfolsom/msproj/LatexWriter.java | 46 ++++++++ src/net/woodyfolsom/msproj/Referee.java | 28 +++++ src/net/woodyfolsom/msproj/SGFWriter.java | 4 +- .../woodyfolsom/msproj/StandAloneGame.java | 110 +++++++++++++++--- .../msproj/ann/NeuralNetLearner.java | 15 +++ .../woodyfolsom/msproj/ann/PassLearner.java | 40 ++----- .../woodyfolsom/msproj/ann/XORLearner.java | 42 +++++++ .../woodyfolsom/msproj/sgf/SGFGameTree.java | 98 +++++----------- .../msproj/sgf/SGFNodeCollection.java | 24 ++-- src/net/woodyfolsom/msproj/sgf/SGFResult.java | 10 -- test/net/woodyfolsom/msproj/RefereeTest.java | 42 +++++++ .../msproj/ann/NeuralNetLearnerTest.java | 86 ++++++++++++++ .../msproj/sgf/CollectionTest.java | 6 +- .../woodyfolsom/msproj/sgf/SGFParserTest.java | 10 -- 17 files changed, 470 insertions(+), 156 deletions(-) create mode 100644 lib/encog-engine-2.5.0.jar create mode 100644 src/net/woodyfolsom/msproj/LatexWriter.java create mode 100644 src/net/woodyfolsom/msproj/ann/NeuralNetLearner.java create mode 100644 src/net/woodyfolsom/msproj/ann/XORLearner.java create mode 100644 test/net/woodyfolsom/msproj/RefereeTest.java create mode 100644 test/net/woodyfolsom/msproj/ann/NeuralNetLearnerTest.java diff --git a/lib/encog-engine-2.5.0.jar b/lib/encog-engine-2.5.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..12d9d6322aa1e4f70950e191127f50c16d733527 GIT binary patch literal 69246 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 literal 0 HcmV?d00001 diff --git a/src/net/woodyfolsom/msproj/Action.java b/src/net/woodyfolsom/msproj/Action.java index 7dd22a6..ed3465c 100644 --- a/src/net/woodyfolsom/msproj/Action.java +++ b/src/net/woodyfolsom/msproj/Action.java @@ -77,6 +77,48 @@ public class Action { return this == Action.RESIGN; } + public static Action parseSGF(String sgfCoord, int boardSize) { + if (sgfCoord.length() == 0) { + return Action.PASS; + } + StringBuilder sb = new StringBuilder(); + char column = sgfCoord.charAt(0); + if (column >= 'i') { + sb.append((char) (column + 1)); + } else { + sb.append(column); + } + char row = sgfCoord.charAt(1); + sb.append(boardSize - row + 'a'); + return Action.getInstance(sb.toString().toUpperCase()); + } + + public String toLatex(int boardSize) { + if (isPass() || isNone() || isResign()) { + throw new UnsupportedOperationException("Invalid Action for toLatex() call: " + this); + } + String latex = new String(new char[] {column}).toLowerCase(); + latex += row; + return latex; + } + + public String toSGF(int boardSize) { + if (isNone() || isResign()) { + throw new UnsupportedOperationException("Invalid Action for toLatex() call: " + this); + } + if (isPass()) { + return ""; // or 'tt' in old format + } + char[] latex = new char[2]; + if (column >= 'J') { + latex[0] = (char) (column - 1); + } else { + latex[0] = column; + } + latex[1] = (char)('a' + (boardSize - row)); + return new String(latex).toLowerCase(); + } + @Override public String toString() { return move; diff --git a/src/net/woodyfolsom/msproj/GameResult.java b/src/net/woodyfolsom/msproj/GameResult.java index 6f99c26..2562713 100644 --- a/src/net/woodyfolsom/msproj/GameResult.java +++ b/src/net/woodyfolsom/msproj/GameResult.java @@ -44,6 +44,29 @@ public class GameResult { return blackScore; } + public String getFullText() { + double blackScore = getBlackScore(); + double whiteScore = getWhiteScore(); + + switch (resultType) { + case BLACK_BY_RESIGNATION: + return "Black wins by resignation"; + case WHITE_BY_RESIGNATION: + return "White wins by resignation"; + case IN_PROGRESS: + case VOID: + return "Game in progress"; + case SCORED: + if (blackScore > whiteScore) { + return "Black wins by " + (blackScore - whiteScore); + } else { + return "White wins by " + (whiteScore - blackScore); + } + default: + return "Unknown game result"; + } + } + public int getNormalizedZeroScore() { return normalizedZeroScore; } diff --git a/src/net/woodyfolsom/msproj/LatexWriter.java b/src/net/woodyfolsom/msproj/LatexWriter.java new file mode 100644 index 0000000..76690b0 --- /dev/null +++ b/src/net/woodyfolsom/msproj/LatexWriter.java @@ -0,0 +1,46 @@ +package net.woodyfolsom.msproj; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; + +public class LatexWriter { + + public static void write(OutputStream os, GameRecord gameRecord, + int turnNumber) throws IOException { + OutputStreamWriter writer = new OutputStreamWriter(os); + + int boardSize = gameRecord.getGameConfig().getSize(); + + writer.write("\\begin{figure}[h]\n"); + writer.write("\\black{"); + boolean firstMove = true; + for (int turn = 1; turn <= gameRecord.getNumTurns(); turn+=2) { + if (!firstMove) { + writer.write(","); + } + writer.write(gameRecord.getMove(turn).toLatex(boardSize)); + firstMove = false; + } + writer.write("}\n"); + + writer.write("\\white{"); + firstMove = true; + for (int turn = 2; turn <= gameRecord.getNumTurns(); turn+=2) { + if (!firstMove) { + writer.write(","); + } + writer.write(gameRecord.getMove(turn).toLatex(boardSize)); + firstMove = false; + } + writer.write("}\n"); + + writer.write("\\begin{center}\n"); + writer.write("\\gobansize{" + boardSize + "}\n"); + writer.write("\\shortstack{\\showfullgoban\\\\" + + gameRecord.getResult().getFullText() + "}\n"); + writer.write("\\end{center}\n"); + writer.write("\\end{figure}\n"); + writer.flush(); + } +} \ No newline at end of file diff --git a/src/net/woodyfolsom/msproj/Referee.java b/src/net/woodyfolsom/msproj/Referee.java index c719f2a..782add9 100644 --- a/src/net/woodyfolsom/msproj/Referee.java +++ b/src/net/woodyfolsom/msproj/Referee.java @@ -3,11 +3,20 @@ package net.woodyfolsom.msproj; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; +import org.antlr.runtime.ANTLRInputStream; +import org.antlr.runtime.ANTLRStringStream; +import org.antlr.runtime.CommonTokenStream; +import org.antlr.runtime.RecognitionException; + import net.woodyfolsom.msproj.policy.Policy; +import net.woodyfolsom.msproj.sgf.SGFLexer; +import net.woodyfolsom.msproj.sgf.SGFNodeCollection; +import net.woodyfolsom.msproj.sgf.SGFParser; public class Referee { private Policy blackPolicy; @@ -35,6 +44,25 @@ public class Referee { } } + public static GameRecord replay(InputStream sgfInputStream) throws IOException, RecognitionException { + ANTLRStringStream in = new ANTLRInputStream(sgfInputStream); + SGFLexer lexer = new SGFLexer(in); + CommonTokenStream tokens = new CommonTokenStream(lexer); + SGFParser parser = new SGFParser(tokens); + SGFNodeCollection nodeCollection = parser.collection(); + + //parse sgf header + GameConfig gameConfig = nodeCollection.getGameConfig(); + GameRecord gameRecord = new GameRecord(gameConfig); + + //replay sgf moves, throw exception if moves are illegal + for (Action action : nodeCollection.getMoves(gameConfig.getSize())) { + gameRecord.play(gameRecord.getPlayerToMove(), action); + } + + return gameRecord; + } + public GameResult play(GameConfig gameConfig) { GameRecord gameRecord = new GameRecord(gameConfig); diff --git a/src/net/woodyfolsom/msproj/SGFWriter.java b/src/net/woodyfolsom/msproj/SGFWriter.java index 356baf1..e2dd23c 100644 --- a/src/net/woodyfolsom/msproj/SGFWriter.java +++ b/src/net/woodyfolsom/msproj/SGFWriter.java @@ -33,9 +33,7 @@ public class SGFWriter { if (action.isPass()) { sgfCoord = ""; } else { - sgfCoord = action.toString().toLowerCase().substring(0,1); - char row = (char) ('a' + gameConfig.getSize() - Integer.valueOf(action.toString().substring(1)).intValue()); - sgfCoord = sgfCoord + row; + sgfCoord = action.toSGF(gameConfig.getSize()); } writer.write(sgfCoord + "]"); } diff --git a/src/net/woodyfolsom/msproj/StandAloneGame.java b/src/net/woodyfolsom/msproj/StandAloneGame.java index ba20d68..c69f22b 100644 --- a/src/net/woodyfolsom/msproj/StandAloneGame.java +++ b/src/net/woodyfolsom/msproj/StandAloneGame.java @@ -1,6 +1,14 @@ package net.woodyfolsom.msproj; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Date; import java.util.List; import net.woodyfolsom.msproj.gui.Goban; @@ -12,12 +20,15 @@ import net.woodyfolsom.msproj.policy.RandomMovePolicy; import net.woodyfolsom.msproj.policy.RootParallelization; public class StandAloneGame { + private static final int DEFAULT_TURN_LENGTH = 2; // default turn is 2 + // seconds; + private static final double DEFAULT_KOMI = 5.5; private static final int DEFAULT_NUM_GAMES = 1; private static final int DEFAULT_SIZE = 9; - + enum PLAYER_TYPE { - HUMAN, HUMAN_GUI, ROOT_PAR, UCT_FAST, UCT_SLOW + HUMAN, HUMAN_GUI, ROOT_PAR, UCT_FAST, UCT_SLOW, RANDOM }; public static void main(String[] args) { @@ -30,7 +41,7 @@ public class StandAloneGame { int nGames = DEFAULT_NUM_GAMES; int size = DEFAULT_SIZE; double komi = DEFAULT_KOMI; - + switch (args.length) { case 5: nGames = Integer.valueOf(args[4]); @@ -40,7 +51,14 @@ public class StandAloneGame { size = Integer.valueOf(args[2]); break; default: - System.out.println("Arguments #3-5 not specified. Using default size=" + size +", komi = " + komi +", nGames=" + nGames +"."); + System.out + .println("Arguments #3-5 not specified. Using default size=" + + size + + ", komi = " + + komi + + ", nGames=" + + nGames + + "."); } new StandAloneGame().playGame(parsePlayerType(args[0]), parsePlayerType(args[1]), size, komi, nGames); @@ -58,6 +76,8 @@ public class StandAloneGame { return PLAYER_TYPE.HUMAN; } else if ("HUMAN_GUI".equalsIgnoreCase(playerTypeStr)) { return PLAYER_TYPE.HUMAN_GUI; + } else if ("RANDOM".equalsIgnoreCase(playerTypeStr)) { + return PLAYER_TYPE.RANDOM; } else { throw new RuntimeException("Unknown player type: " + playerTypeStr); } @@ -70,34 +90,90 @@ public class StandAloneGame { gameConfig.setKomi(komi); Referee referee = new Referee(); - referee.setPolicy(Player.BLACK, getPolicy(playerType1, gameConfig, Player.BLACK)); - referee.setPolicy(Player.WHITE, getPolicy(playerType2, gameConfig, Player.WHITE)); + referee.setPolicy(Player.BLACK, + getPolicy(playerType1, gameConfig, Player.BLACK)); + referee.setPolicy(Player.WHITE, + getPolicy(playerType2, gameConfig, Player.WHITE)); + + List round1results = new ArrayList(); - List results = new ArrayList(); - for (int round = 0; round < rounds; round++) { - results.add(referee.play(gameConfig)); + round1results.add(referee.play(gameConfig)); } - System.out.println("Cumulative results for " + rounds + " games (BLACK=" - + playerType1 + ", WHITE=" + playerType2 + ")"); - for (int i = 0; i < rounds; i++) { - System.out.println(i + ". " + results.get(i)); + List round2results = new ArrayList(); + + referee.setPolicy(Player.BLACK, + getPolicy(playerType2, gameConfig, Player.BLACK)); + referee.setPolicy(Player.WHITE, + getPolicy(playerType1, gameConfig, Player.WHITE)); + for (int round = 0; round < rounds; round++) { + round2results.add(referee.play(gameConfig)); } + + DateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmssZ"); + + try { + + File txtFile = new File("gotournament-" + dateFormat.format(new Date()) + + ".txt"); + FileWriter writer = new FileWriter(txtFile); + + try { + logResults(writer, round1results, playerType1.toString(), playerType2.toString()); + logResults(writer, round2results, playerType2.toString(), playerType1.toString()); + + System.out + .println("Game tournament saved as " + txtFile.getAbsolutePath()); + } finally { + try { + writer.close(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + } catch (IOException ioe) { + System.out.println("Unable to save game file due to IOException: " + + ioe.getMessage()); + } + } - private Policy getPolicy(PLAYER_TYPE playerType, GameConfig gameConfig, Player player) { + private void logResults(FileWriter writer, List results, String player1, String player2) throws IOException { + String header = "Cumulative results for " + results.size() + + " games (BLACK=" + player1 + ", WHITE=" + player2 + + ")"; + + System.out.println(header); + writer.write(header); + writer.write("\n"); + + for (int i = 0; i < results.size(); i++) { + String resultLine = (i+1) + ". " + results.get(i); + System.out.println(resultLine); + writer.write(resultLine); + writer.write("\n"); + } + writer.flush(); + } + + private Policy getPolicy(PLAYER_TYPE playerType, GameConfig gameConfig, + Player player) { switch (playerType) { case HUMAN: return new HumanKeyboardInput(); case HUMAN_GUI: return new HumanGuiInput(new Goban(gameConfig, player)); case ROOT_PAR: - return new RootParallelization(4, 6000L); + return new RootParallelization(4, DEFAULT_TURN_LENGTH * 1000L * 3); case UCT_SLOW: - return new MonteCarloUCT(new RandomMovePolicy(), 4000L); + return new MonteCarloUCT(new RandomMovePolicy(), + DEFAULT_TURN_LENGTH * 1000L * 3); case UCT_FAST: - return new MonteCarloUCT(new RandomMovePolicy(), 1000L); + return new MonteCarloUCT(new RandomMovePolicy(), + DEFAULT_TURN_LENGTH * 1000L); + case RANDOM: + return new RandomMovePolicy(); default: throw new IllegalArgumentException("Invalid PLAYER_TYPE: " + playerType); diff --git a/src/net/woodyfolsom/msproj/ann/NeuralNetLearner.java b/src/net/woodyfolsom/msproj/ann/NeuralNetLearner.java new file mode 100644 index 0000000..42e9e59 --- /dev/null +++ b/src/net/woodyfolsom/msproj/ann/NeuralNetLearner.java @@ -0,0 +1,15 @@ +package net.woodyfolsom.msproj.ann; + +import org.neuroph.core.NeuralNetwork; +import org.neuroph.core.learning.SupervisedTrainingElement; +import org.neuroph.core.learning.TrainingSet; + +public interface NeuralNetLearner { + void learn(TrainingSet trainingSet); + + void reset(); + + NeuralNetwork getNeuralNetwork(); + + void setNeuralNetwork(NeuralNetwork neuralNetwork); +} diff --git a/src/net/woodyfolsom/msproj/ann/PassLearner.java b/src/net/woodyfolsom/msproj/ann/PassLearner.java index f339e66..1aca3db 100644 --- a/src/net/woodyfolsom/msproj/ann/PassLearner.java +++ b/src/net/woodyfolsom/msproj/ann/PassLearner.java @@ -6,6 +6,8 @@ import java.io.FileNotFoundException; import java.io.FilenameFilter; import java.io.IOException; +import net.woodyfolsom.msproj.GameRecord; +import net.woodyfolsom.msproj.Referee; import net.woodyfolsom.msproj.sgf.SGFLexer; import net.woodyfolsom.msproj.sgf.SGFNodeCollection; import net.woodyfolsom.msproj.sgf.SGFParser; @@ -40,40 +42,18 @@ public class PassLearner { } public void parseSGF(File sgfFile) { - FileInputStream fis; + FileInputStream sgfInputStream; try { - fis = new FileInputStream(sgfFile); - ANTLRStringStream in; - try { - in = new ANTLRInputStream(fis); - SGFLexer lexer = new SGFLexer(in); - CommonTokenStream tokens = new CommonTokenStream(lexer); - SGFParser parser = new SGFParser(tokens); - SGFNodeCollection nodeCollection; - try { - nodeCollection = parser.collection(); - - System.out.println("To SGF:"); - System.out.println(nodeCollection.toSGF()); - System.out.println(""); - - System.out.println("To LaTeX:"); - System.out.println(nodeCollection.toLateX()); - System.out.println(""); - } catch (RecognitionException re) { - re.printStackTrace(); - } - } catch (IOException ioe) { - ioe.printStackTrace(); - } - try { - fis.close(); - } catch (IOException ioe) { - System.out.println("Error closing input stream for file" + sgfFile.getPath()); - } + sgfInputStream = new FileInputStream(sgfFile); + GameRecord gameRecord = Referee.replay(sgfInputStream); + //... } catch (FileNotFoundException fnfe) { fnfe.printStackTrace(); + } catch (RecognitionException re) { + re.printStackTrace(); + } catch (IOException ioe) { + ioe.printStackTrace(); } } } diff --git a/src/net/woodyfolsom/msproj/ann/XORLearner.java b/src/net/woodyfolsom/msproj/ann/XORLearner.java new file mode 100644 index 0000000..64fcde9 --- /dev/null +++ b/src/net/woodyfolsom/msproj/ann/XORLearner.java @@ -0,0 +1,42 @@ +package net.woodyfolsom.msproj.ann; + +import org.neuroph.core.NeuralNetwork; +import org.neuroph.core.learning.SupervisedTrainingElement; +import org.neuroph.core.learning.TrainingSet; +import org.neuroph.nnet.MultiLayerPerceptron; +import org.neuroph.util.TransferFunctionType; + +/** + * Based on sample code from http://neuroph.sourceforge.net + * + * @author Woody + * + */ +public class XORLearner implements NeuralNetLearner { + private NeuralNetwork neuralNetwork; + + public XORLearner() { + reset(); + } + + @Override + public NeuralNetwork getNeuralNetwork() { + return neuralNetwork; + } + + @Override + public void learn(TrainingSet trainingSet) { + this.neuralNetwork.learn(trainingSet); + } + + @Override + public void reset() { + this.neuralNetwork = new MultiLayerPerceptron( + TransferFunctionType.TANH, 2, 3, 1); + } + + @Override + public void setNeuralNetwork(NeuralNetwork neuralNetwork) { + this.neuralNetwork = neuralNetwork; + } +} \ No newline at end of file diff --git a/src/net/woodyfolsom/msproj/sgf/SGFGameTree.java b/src/net/woodyfolsom/msproj/sgf/SGFGameTree.java index a10d8df..2766416 100644 --- a/src/net/woodyfolsom/msproj/sgf/SGFGameTree.java +++ b/src/net/woodyfolsom/msproj/sgf/SGFGameTree.java @@ -4,7 +4,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import net.woodyfolsom.msproj.Player; +import net.woodyfolsom.msproj.Action; public class SGFGameTree { private List nodeSequence = new ArrayList(); @@ -30,60 +30,32 @@ public class SGFGameTree { subTrees.add(subTree); } + public List getMoves(int boardSize) { + List moves = new ArrayList(); + + for (SGFNode node : nodeSequence) { + SGFValue sgfValue; + switch (node.getType()) { + case MOVE_BLACK : + sgfValue = node.getFirstValue(SGFIdentifier.MOVE_BLACK); + break; + case MOVE_WHITE : + sgfValue = node.getFirstValue(SGFIdentifier.MOVE_WHITE); + break; + default : + continue; + } + + moves.add(Action.parseSGF(sgfValue.getText(), boardSize)); + } + + return moves; + } + public int getSubTreeCount() { return subTrees.size(); } - public String toLateXmoves(Player player, int boardSize) { - StringBuilder latexSB = new StringBuilder(); - SGFNode.TYPE nodeType; - SGFIdentifier sgfIdent; - if (player == Player.WHITE) { - nodeType = SGFNode.TYPE.MOVE_WHITE; - sgfIdent = SGFIdentifier.MOVE_WHITE; - latexSB.append("\\white{"); - } else if (player == Player.BLACK) { - nodeType = SGFNode.TYPE.MOVE_BLACK; - sgfIdent = SGFIdentifier.MOVE_BLACK; - latexSB.append("\\black{"); - } else { - throw new RuntimeException("Invalid player: " + player); - } - - boolean firstMove = true; - int nMoves = 0; - for (SGFNode node : nodeSequence) { - if (node.getType() != nodeType) { - continue; - } - SGFValue sgfValue = node.getFirstValue(sgfIdent); - if (sgfValue.isEmpty()) { - // TODO later this will be the LaTeX igo code for 'Pass'? - continue; - } - if (firstMove) { - firstMove = false; - } else { - latexSB.append(","); - } - SGFCoord sgfCoord = (SGFCoord) sgfValue.getValue(); - char column = sgfCoord.getColumn(); - if (column >= 'i') { - latexSB.append((char) (column + 1)); - } else { - latexSB.append(column); - } - char row = sgfCoord.getRow(); - latexSB.append(boardSize - row + 'a'); - nMoves++; - } - if (nMoves == 0) { - return ""; - } - latexSB.append("}\n"); - return latexSB.toString(); - } - public int getBoardSize() { for (SGFNode node : nodeSequence) { SGFNode.TYPE nodeType = node.getType(); @@ -97,35 +69,19 @@ public class SGFGameTree { throw new RuntimeException("Cannot get board size: SGFNode with identifier SZ was not found in any nodeSequence of type ROOT."); } - public String toLateX(int boardSize) { - StringBuilder latexSB = new StringBuilder(); - - // Somewhat convoluted logic here because the grammar does not require - // all root - // 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. + public double getKomi() { for (SGFNode node : nodeSequence) { SGFNode.TYPE nodeType = node.getType(); switch (nodeType) { case ROOT: - latexSB.append("\\gobansize"); - latexSB.append("{"); - latexSB.append(boardSize); - latexSB.append("}\n"); - latexSB.append("\\shortstack{\\showfullgoban\\\\"); - SGFResult result = (SGFResult) node.getFirstValue( - SGFIdentifier.RESULT).getValue(); - latexSB.append(result.getFullText()); - latexSB.append("}\n"); - break; + return Double.parseDouble(node.getFirstValue(SGFIdentifier.KOMI).getText()); default: // ignore } } - return latexSB.toString(); + throw new RuntimeException("Cannot get board size: SGFNode with identifier KM was not found in any nodeSequence of type ROOT."); } - + public String toSGF() { StringBuilder sgfFormatString = new StringBuilder("("); for (SGFNode node : nodeSequence) { diff --git a/src/net/woodyfolsom/msproj/sgf/SGFNodeCollection.java b/src/net/woodyfolsom/msproj/sgf/SGFNodeCollection.java index c77b590..ad637b3 100644 --- a/src/net/woodyfolsom/msproj/sgf/SGFNodeCollection.java +++ b/src/net/woodyfolsom/msproj/sgf/SGFNodeCollection.java @@ -3,7 +3,8 @@ package net.woodyfolsom.msproj.sgf; import java.util.ArrayList; import java.util.List; -import net.woodyfolsom.msproj.Player; +import net.woodyfolsom.msproj.Action; +import net.woodyfolsom.msproj.GameConfig; public class SGFNodeCollection { private List gameTrees = new ArrayList(); @@ -16,22 +17,21 @@ public class SGFNodeCollection { return gameTrees.get(index); } + public GameConfig getGameConfig() { + SGFGameTree gameTree = gameTrees.get(0); + int boardSize = gameTree.getBoardSize(); + double komi = gameTree.getKomi(); + + return new GameConfig(boardSize,komi); + } + public int getGameTreeCount() { return gameTrees.size(); } - public String toLateX() { - StringBuilder latexFormatString = new StringBuilder(""); + public List getMoves(int boardSize) { SGFGameTree gameTree = gameTrees.get(0); - - int boardSize = gameTree.getBoardSize(); - latexFormatString.append(gameTree.toLateXmoves(Player.BLACK, boardSize)); - latexFormatString.append(gameTree.toLateXmoves(Player.WHITE, boardSize)); - latexFormatString.append("\\begin{center}\n"); - latexFormatString.append(gameTree.toLateX(boardSize)); - latexFormatString.append("\\end{center}"); - - return latexFormatString.toString(); + return gameTree.getMoves(boardSize); } /** diff --git a/src/net/woodyfolsom/msproj/sgf/SGFResult.java b/src/net/woodyfolsom/msproj/sgf/SGFResult.java index 490eead..b118860 100644 --- a/src/net/woodyfolsom/msproj/sgf/SGFResult.java +++ b/src/net/woodyfolsom/msproj/sgf/SGFResult.java @@ -18,16 +18,6 @@ public class SGFResult { } } - public String getFullText() { - if (resignation == false && tie == false) { - return winner.getColor() + " wins by " + score; - } else if (resignation == true && tie == false) { - return winner.getColor() + " wins by resignation"; - } else { - throw new UnsupportedOperationException("Not implemented"); - } - } - @Override public String toString() { if (resignation == false && tie == false) { diff --git a/test/net/woodyfolsom/msproj/RefereeTest.java b/test/net/woodyfolsom/msproj/RefereeTest.java new file mode 100644 index 0000000..f62f040 --- /dev/null +++ b/test/net/woodyfolsom/msproj/RefereeTest.java @@ -0,0 +1,42 @@ +package net.woodyfolsom.msproj; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.antlr.runtime.RecognitionException; +import org.junit.Test; + +public class RefereeTest { + //private static final String FILENAME = "data/tourney1/gogame-121115115720-0500.sgf"; + private static final String FILENAME = "data/games/1334-gokifu-20120916-Gu_Li-Lee_Sedol.sgf"; + + @Test + public void testReplay() throws IOException, RecognitionException { + File sgfFile = new File(FILENAME); + InputStream fis = new FileInputStream(sgfFile); + + GameRecord gameRecord = Referee.replay(fis); + + fis.close(); + + //assertEquals(9, gameRecord.getGameConfig().getSize()); + assertEquals(19, gameRecord.getGameConfig().getSize()); + + //assertEquals(5.5, gameRecord.getGameConfig().getKomi(), 0.1); + assertEquals(7.5, gameRecord.getGameConfig().getKomi(), 0.1); + + //assertEquals(74, gameRecord.getNumTurns()); + assertEquals(214, gameRecord.getNumTurns()); + + System.out.println("Final board state (LaTeX): "); + LatexWriter.write(System.out, gameRecord, 214); + + System.out.println("Final board state (SGF): "); + SGFWriter.write(System.out, gameRecord); + + } +} diff --git a/test/net/woodyfolsom/msproj/ann/NeuralNetLearnerTest.java b/test/net/woodyfolsom/msproj/ann/NeuralNetLearnerTest.java new file mode 100644 index 0000000..aa4d260 --- /dev/null +++ b/test/net/woodyfolsom/msproj/ann/NeuralNetLearnerTest.java @@ -0,0 +1,86 @@ +package net.woodyfolsom.msproj.ann; + +import java.io.File; +import java.util.Arrays; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.neuroph.core.NeuralNetwork; +import org.neuroph.core.learning.SupervisedTrainingElement; +import org.neuroph.core.learning.TrainingSet; + +public class NeuralNetLearnerTest { + private static final String FILENAME = "myMlPerceptron.nnet"; + + @AfterClass + public static void deleteNewNet() { + File file = new File(FILENAME); + if (file.exists()) { + file.delete(); + } + } + + @BeforeClass + public static void deleteSavedNet() { + File file = new File(FILENAME); + if (file.exists()) { + file.delete(); + } + } + + @Test + public void testLearnSaveLoad() { + NeuralNetLearner nnLearner = new XORLearner(); + + // create training set (logical XOR function) + TrainingSet trainingSet = new TrainingSet( + 2, 1); + for (int x = 0; x < 1000; x++) { + trainingSet.addElement(new SupervisedTrainingElement(new double[] { 0, + 0 }, new double[] { 0 })); + trainingSet.addElement(new SupervisedTrainingElement(new double[] { 0, + 1 }, new double[] { 1 })); + trainingSet.addElement(new SupervisedTrainingElement(new double[] { 1, + 0 }, new double[] { 1 })); + trainingSet.addElement(new SupervisedTrainingElement(new double[] { 1, + 1 }, new double[] { 0 })); + } + + nnLearner.learn(trainingSet); + NeuralNetwork nnet = nnLearner.getNeuralNetwork(); + + TrainingSet valSet = new TrainingSet( + 2, 1); + valSet.addElement(new SupervisedTrainingElement(new double[] { 0, + 0 }, new double[] { 0 })); + valSet.addElement(new SupervisedTrainingElement(new double[] { 0, + 1 }, new double[] { 1 })); + valSet.addElement(new SupervisedTrainingElement(new double[] { 1, + 0 }, new double[] { 1 })); + valSet.addElement(new SupervisedTrainingElement(new double[] { 1, + 1 }, new double[] { 0 })); + + System.out.println("Output from eval set (learned network):"); + testNetwork(nnet, valSet); + + nnet.save(FILENAME); + nnet = NeuralNetwork.load(FILENAME); + + System.out.println("Output from eval set (learned network):"); + testNetwork(nnet, valSet); + } + + private void testNetwork(NeuralNetwork nnet, TrainingSet trainingSet) { + for (SupervisedTrainingElement trainingElement : trainingSet.elements()) { + + nnet.setInput(trainingElement.getInput()); + nnet.calculate(); + double[] networkOutput = nnet.getOutput(); + System.out.print("Input: " + + Arrays.toString(trainingElement.getInput())); + System.out.println(" Output: " + Arrays.toString(networkOutput)); + + } + } +} \ No newline at end of file diff --git a/test/net/woodyfolsom/msproj/sgf/CollectionTest.java b/test/net/woodyfolsom/msproj/sgf/CollectionTest.java index 8bdc5d1..ddd1fc4 100644 --- a/test/net/woodyfolsom/msproj/sgf/CollectionTest.java +++ b/test/net/woodyfolsom/msproj/sgf/CollectionTest.java @@ -1,6 +1,7 @@ package net.woodyfolsom.msproj.sgf; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -15,6 +16,7 @@ public class CollectionTest { public static final String TEST_SGF = "(;FF[4]SZ[9]KM[5.5]RE[W+6.5]"+ ";W[ee];B[];W[])"; + public static final String TEST_LATEX = "\\white{e5}\n" + "\\begin{center}\n" + @@ -48,8 +50,6 @@ public class CollectionTest { CommonTokenStream tokens = new CommonTokenStream(lexer); SGFParser parser = new SGFParser(tokens); SGFNodeCollection nodeCollection = parser.collection(); - - String actualLaTeX = nodeCollection.toLateX(); - assertEquals(TEST_LATEX, actualLaTeX); + assertNotNull(nodeCollection); } } diff --git a/test/net/woodyfolsom/msproj/sgf/SGFParserTest.java b/test/net/woodyfolsom/msproj/sgf/SGFParserTest.java index 62cfbc7..c4b2a63 100644 --- a/test/net/woodyfolsom/msproj/sgf/SGFParserTest.java +++ b/test/net/woodyfolsom/msproj/sgf/SGFParserTest.java @@ -25,11 +25,6 @@ public class SGFParserTest { System.out.println("To SGF:"); System.out.println(nodeCollection.toSGF()); System.out.println(""); - - System.out.println("To LaTeX:"); - System.out.println(nodeCollection.toLateX()); - System.out.println(""); - } @Test @@ -45,10 +40,5 @@ public class SGFParserTest { System.out.println("To SGF:"); System.out.println(nodeCollection.toSGF()); System.out.println(""); - - System.out.println("To LaTeX:"); - System.out.println(nodeCollection.toLateX()); - System.out.println(""); - } }