From e012f17b33669dec34c8c8c239f6979837d7eb11 Mon Sep 17 00:00:00 2001 From: Marshall Date: Sun, 29 Apr 2012 17:11:11 -0400 Subject: [PATCH 1/3] - Changed the rules. Now tiles can only be placed next to existing tiles. Unless the board is empty. --- src/model/Board.java | 78 +++++++- .../generator/AlphaBetaMoveGenerator.java | 172 +++++++++--------- .../generator/ValidMoveGenerator.java | 13 +- 3 files changed, 168 insertions(+), 95 deletions(-) diff --git a/src/model/Board.java b/src/model/Board.java index 3fa08fe..c86c246 100644 --- a/src/model/Board.java +++ b/src/model/Board.java @@ -12,6 +12,66 @@ public class Board { public static final int NUM_ROWS = 5; public static final int ROW_REMOVAL_SIZE = 3; + public static boolean isLegal(Board brd, CellPointer cp) { + if (cp.r < 0 || cp.r >= Board.NUM_ROWS || cp.c < 0 + || cp.c >= Board.NUM_COLS) { + return false; + } + + boolean legal = (brd.getTile(cp.r, cp.c) == TileColor.NONE); + + boolean rowUp = (cp.r - 1) >= 0; + boolean rowDown = (cp.r + 1) < Board.NUM_ROWS; + boolean colUp = (cp.c - 1) >= 0; + boolean colDown = (cp.c + 1) < Board.NUM_COLS; + + // r-1 / c-1 + if (rowUp && colUp + && (brd.getTile(cp.r - 1, cp.c - 1) != TileColor.NONE)) { + return legal && true; + } + + // r-1 / c + if (rowUp && (brd.getTile(cp.r - 1, cp.c) != TileColor.NONE)) { + return legal && true; + } + + // r-1 / c+1 + if (rowUp && colDown + && (brd.getTile(cp.r - 1, cp.c + 1) != TileColor.NONE)) { + return legal && true; + } + + // r / c-1 + if (colUp && (brd.getTile(cp.r, cp.c - 1) != TileColor.NONE)) { + return legal && true; + } + + // r / c+1 + if (colDown && (brd.getTile(cp.r, cp.c + 1) != TileColor.NONE)) { + return legal && true; + } + + // r+1 / c-1 + if (rowDown && colUp + && (brd.getTile(cp.r + 1, cp.c - 1) != TileColor.NONE)) { + return legal && true; + } + + // r+1 / c + if (rowDown && (brd.getTile(cp.r + 1, cp.c) != TileColor.NONE)) { + return legal && true; + } + + // r+1 / c+1 + if (rowDown && colDown + && (brd.getTile(cp.r + 1, cp.c + 1) != TileColor.NONE)) { + return legal && true; + } + + return brd.isEmpty(); + } + private final TileColor[][] board; // a Ply is a play by 1 side. Increments each time setTile() is called. @@ -62,7 +122,7 @@ public class Board { } public int getTurn() { - return (numPlies + 1)/ 2; + return (numPlies + 1) / 2; } @Override @@ -90,8 +150,8 @@ public class Board { } public boolean playTile(CellPointer cp, TileColor tile) { - if (board[cp.r][cp.c] != TileColor.NONE) { - return false; // illegal move + if (!isLegal(this, cp)) { + return false; // Illegal move. } board[cp.r][cp.c] = tile; @@ -247,4 +307,16 @@ public class Board { } return sb1.toString(); } + + private boolean isEmpty() { + for (int r = 0; r < Board.NUM_ROWS; r++) { + for (int c = 0; c < Board.NUM_COLS; c++) { + if (getTile(r, c) != TileColor.NONE) { + return false; + } + } + } + + return true; + } } \ No newline at end of file diff --git a/src/model/comPlayer/generator/AlphaBetaMoveGenerator.java b/src/model/comPlayer/generator/AlphaBetaMoveGenerator.java index 1eb36db..4034cf5 100644 --- a/src/model/comPlayer/generator/AlphaBetaMoveGenerator.java +++ b/src/model/comPlayer/generator/AlphaBetaMoveGenerator.java @@ -11,104 +11,24 @@ import model.SearchResult; import org.apache.log4j.Logger; public class AlphaBetaMoveGenerator implements MoveGenerator { + private static final int DEFAULT_RECURSIVE_PLAYS = 1; private static final Logger LOGGER = Logger .getLogger(AlphaBetaMoveGenerator.class.getName()); - private static final int DEFAULT_RECURSIVE_PLAYS = 2; private final BoardScorer scorer = new BoardScorer(); private final ValidMoveGenerator validMoveGenerator = new ValidMoveGenerator(); @Override public Move genMove(Board board, boolean asHuman) { - + if (!asHuman) { - return getMaxValue(board, asHuman, DEFAULT_RECURSIVE_PLAYS * 2, Integer.MIN_VALUE, - Integer.MAX_VALUE).move; + return getMaxValue(board, asHuman, DEFAULT_RECURSIVE_PLAYS * 2, + Integer.MIN_VALUE, Integer.MAX_VALUE).move; } else { - return getMinValue(board, asHuman, DEFAULT_RECURSIVE_PLAYS * 2, Integer.MIN_VALUE, - Integer.MAX_VALUE).move; + return getMinValue(board, asHuman, DEFAULT_RECURSIVE_PLAYS * 2, + Integer.MIN_VALUE, Integer.MAX_VALUE).move; } } - - private SearchResult getMaxValue(Board board, boolean asHuman, int recursionLevel, - int alpha, int beta) { - if (recursionLevel < 1) { - return new SearchResult(Move.NONE,scorer.getScore(board)); - } - - List validMoves = validMoveGenerator.genMoves(board, asHuman, - MoveGenerator.ALL_MOVES); - - SearchResult bestResult = new SearchResult(Move.NONE,Integer.MIN_VALUE); - - if (validMoves.size() == 0) { - return bestResult; - } - - for (Move nextMove : validMoves) { - Board nextBoard = new Board(board); - - if (!nextBoard.playTile(nextMove.getCell(), nextMove.getColor())) { - throw new RuntimeException( - "Illegal move attempted during search!"); - } - - SearchResult searchResult = new SearchResult(nextMove,getMinValue(nextBoard, !asHuman, recursionLevel - 1, - alpha, beta).score); - - if (searchResult.compareTo(bestResult) > 0) { - bestResult = searchResult; - } - - if (bestResult.score >= beta) { - return bestResult; - } - - alpha = Math.max(alpha, bestResult.score); - } - - return bestResult; - } - - private SearchResult getMinValue(Board board, boolean asHuman, int recursionLevel, - int alpha, int beta) { - if (recursionLevel < 1) { - return new SearchResult(Move.NONE,scorer.getScore(board)); - } - - List validMoves = validMoveGenerator.genMoves(board, asHuman, - MoveGenerator.ALL_MOVES); - - SearchResult bestResult = new SearchResult(Move.NONE,Integer.MAX_VALUE); - - if (validMoves.size() == 0) { - return bestResult; - } - - for (Move nextMove : validMoves) { - Board nextBoard = new Board(board); - - if (!nextBoard.playTile(nextMove.getCell(), nextMove.getColor())) { - throw new RuntimeException( - "Illegal move attempted during search!"); - } - - SearchResult searchResult = new SearchResult(nextMove,getMaxValue(nextBoard, !asHuman, recursionLevel - 1, - alpha, beta).score); - - if (searchResult.compareTo(bestResult) < 0) { - bestResult = searchResult; - } - - if (bestResult.score <= alpha) { - return bestResult; - } - - beta = Math.min(beta, bestResult.score); - } - - return bestResult; - } /** * AlphaBetaMoveGenerator2 does not support this method. @@ -119,4 +39,84 @@ public class AlphaBetaMoveGenerator implements MoveGenerator { LOGGER.info("Minimax genMoves() stub returning []"); return Arrays.asList(doNothing); } + + private SearchResult getMaxValue(Board board, boolean asHuman, + int recursionLevel, int alpha, int beta) { + if (recursionLevel < 1) { + return new SearchResult(Move.NONE, scorer.getScore(board)); + } + + List validMoves = validMoveGenerator.genMoves(board, asHuman, + MoveGenerator.ALL_MOVES); + + SearchResult bestResult = new SearchResult(Move.NONE, Integer.MIN_VALUE); + + if (validMoves.size() == 0) { + return bestResult; + } + + for (Move nextMove : validMoves) { + Board nextBoard = new Board(board); + + if (!nextBoard.playTile(nextMove.getCell(), nextMove.getColor())) { + throw new RuntimeException( + "Illegal move attempted during search!"); + } + + SearchResult searchResult = new SearchResult(nextMove, getMinValue( + nextBoard, !asHuman, recursionLevel - 1, alpha, beta).score); + + if (searchResult.compareTo(bestResult) > 0) { + bestResult = searchResult; + } + + if (bestResult.score >= beta) { + return bestResult; + } + + alpha = Math.max(alpha, bestResult.score); + } + + return bestResult; + } + + private SearchResult getMinValue(Board board, boolean asHuman, + int recursionLevel, int alpha, int beta) { + if (recursionLevel < 1) { + return new SearchResult(Move.NONE, scorer.getScore(board)); + } + + List validMoves = validMoveGenerator.genMoves(board, asHuman, + MoveGenerator.ALL_MOVES); + + SearchResult bestResult = new SearchResult(Move.NONE, Integer.MAX_VALUE); + + if (validMoves.size() == 0) { + return bestResult; + } + + for (Move nextMove : validMoves) { + Board nextBoard = new Board(board); + + if (!nextBoard.playTile(nextMove.getCell(), nextMove.getColor())) { + throw new RuntimeException( + "Illegal move attempted during search!"); + } + + SearchResult searchResult = new SearchResult(nextMove, getMaxValue( + nextBoard, !asHuman, recursionLevel - 1, alpha, beta).score); + + if (searchResult.compareTo(bestResult) < 0) { + bestResult = searchResult; + } + + if (bestResult.score <= alpha) { + return bestResult; + } + + beta = Math.min(beta, bestResult.score); + } + + return bestResult; + } } \ No newline at end of file diff --git a/src/model/comPlayer/generator/ValidMoveGenerator.java b/src/model/comPlayer/generator/ValidMoveGenerator.java index c9a7da2..957fc8e 100644 --- a/src/model/comPlayer/generator/ValidMoveGenerator.java +++ b/src/model/comPlayer/generator/ValidMoveGenerator.java @@ -6,6 +6,7 @@ import java.util.List; import model.Board; import model.Board.TileColor; +import model.CellPointer; import model.Move; import org.apache.log4j.Logger; @@ -25,25 +26,25 @@ public class ValidMoveGenerator implements MoveGenerator { List validMoves = new ArrayList(); - for (int i = 0; i < Board.NUM_ROWS; i++) { - for (int j = 0; j < Board.NUM_COLS; j++) { - if (board.getTile(i, j) == TileColor.NONE) { + for (int r = 0; r < Board.NUM_ROWS; r++) { + for (int c = 0; c < Board.NUM_COLS; c++) { + if (Board.isLegal(board, new CellPointer(r, c))) { for (TileColor color : TileColor.values()) { if (color == TileColor.NONE) { continue; } - validMoves.add(new Move(color, i, j)); + validMoves.add(new Move(color, r, c)); } } } } Collections.shuffle(validMoves); - + if (nMoves == MoveGenerator.ALL_MOVES) { return validMoves; } else { - return validMoves.subList(0, Math.min(validMoves.size(),nMoves)); + return validMoves.subList(0, Math.min(validMoves.size(), nMoves)); } } } From 8de42a356268734c8757b8b82e87849699642c42 Mon Sep 17 00:00:00 2001 From: Marshall Date: Sun, 29 Apr 2012 20:54:34 -0400 Subject: [PATCH 2/3] - I created a ComboPlayer.java agent. It sucks and doesn't really work, but I created it. Now I'm putting it down to work on other things. --- src/model/Referee.java | 10 +- src/model/comPlayer/AlphaBetaComPlayer.java | 7 ++ src/model/comPlayer/ComboPlayer.java | 44 +++++++++ src/model/comPlayer/HumanPlayer.java | 6 ++ src/model/comPlayer/MinimaxComPlayer.java | 7 ++ src/model/comPlayer/MonteCarloComPlayer.java | 19 ++-- src/model/comPlayer/NeuralNetworkPlayer.java | 72 +++----------- src/model/comPlayer/Player.java | 3 + src/model/comPlayer/RandomComPlayer.java | 6 ++ .../generator/NeuralNetworkMoveGenerator.java | 98 +++++++++++++++++++ src/model/playerModel/PlayerModel.java | 21 +++- src/model/playerModel/node/SigmoidNode.java | 4 +- src/view/ParsedArgs.java | 25 +++-- 13 files changed, 239 insertions(+), 83 deletions(-) create mode 100644 src/model/comPlayer/ComboPlayer.java create mode 100644 src/model/comPlayer/generator/NeuralNetworkMoveGenerator.java diff --git a/src/model/Referee.java b/src/model/Referee.java index 55d4748..e192ef7 100644 --- a/src/model/Referee.java +++ b/src/model/Referee.java @@ -128,7 +128,8 @@ public class Referee implements Runnable { int tile = 0; for (int r = 0; r < Board.NUM_ROWS; r++) { for (int c = 0; c < Board.NUM_COLS; c++) { - move[tile] = (mv.getCell().r == r && mv.getCell().c == c); + move[tile + 4] = (mv.getCell().r == r && mv.getCell().c == c); + tile++; } } @@ -163,12 +164,13 @@ public class Referee implements Runnable { System.out .println("Interrupted while waiting for human to move!"); } else { + getPlayerModel().getOutputNodes(getBoardState(board)); + Move mv = humanPlayer.getMove(board, playerModel); if (board.getTile(mv.getCell().r, mv.getCell().c) == TileColor.NONE) { playToken(humanPlayer.getMove(board, playerModel)); - getPlayerModel().train(getBoardState(board), - getMoveArray(mv)); + getPlayerModel().train(getMoveArray(mv)); } else { humanPlayer.denyMove(); @@ -177,7 +179,7 @@ public class Referee implements Runnable { } else { Move mv = computerPlayer.getMove(board, getPlayerModel()); playToken(mv); - + // This is the call that gets a prediction of a user's move. // Some changes will probably be necessary to put it in the // right place and also to get the node weights. But... all in diff --git a/src/model/comPlayer/AlphaBetaComPlayer.java b/src/model/comPlayer/AlphaBetaComPlayer.java index aee56f7..0e965de 100644 --- a/src/model/comPlayer/AlphaBetaComPlayer.java +++ b/src/model/comPlayer/AlphaBetaComPlayer.java @@ -4,6 +4,7 @@ import model.Board; import model.Move; import model.comPlayer.generator.AlphaBetaMoveGenerator; import model.comPlayer.generator.MoveGenerator; +import model.playerModel.GameGoal; import model.playerModel.PlayerModel; public class AlphaBetaComPlayer implements Player { @@ -24,6 +25,12 @@ public class AlphaBetaComPlayer implements Player { return true; // always ready to play a random valid move } + @Override + public void setGameGoal(GameGoal target) { + // TODO Auto-generated method stub + + } + @Override public String toString() { return "Alpha-Beta ComPlayer"; diff --git a/src/model/comPlayer/ComboPlayer.java b/src/model/comPlayer/ComboPlayer.java new file mode 100644 index 0000000..c141d84 --- /dev/null +++ b/src/model/comPlayer/ComboPlayer.java @@ -0,0 +1,44 @@ +package model.comPlayer; + +import model.Board; +import model.Move; +import model.comPlayer.generator.AlphaBetaMoveGenerator; +import model.comPlayer.generator.NeuralNetworkMoveGenerator; +import model.playerModel.GameGoal; +import model.playerModel.PlayerModel; + +public class ComboPlayer implements Player { + + private final AlphaBetaMoveGenerator abGen = new AlphaBetaMoveGenerator(); + private NeuralNetworkMoveGenerator nnGen = null; + + @Override + public void denyMove() { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public Move getMove(Board board, PlayerModel player) { + if (player.getHighScores()[2] == -1) { + return abGen.genMove(board, false); + } + + else { + if (nnGen == null) { + nnGen = new NeuralNetworkMoveGenerator(player); + } + return nnGen.genMove(board, false); + } + } + + @Override + public boolean isReady() { + // TODO Auto-generated method stub + return false; + } + + @Override + public void setGameGoal(GameGoal target) { + // Nothing yet. + } +} diff --git a/src/model/comPlayer/HumanPlayer.java b/src/model/comPlayer/HumanPlayer.java index f86c472..18e8840 100644 --- a/src/model/comPlayer/HumanPlayer.java +++ b/src/model/comPlayer/HumanPlayer.java @@ -4,6 +4,7 @@ import model.Board; import model.Board.TileColor; import model.CellPointer; import model.Move; +import model.playerModel.GameGoal; import model.playerModel.PlayerModel; public class HumanPlayer implements Player { @@ -79,4 +80,9 @@ public class HumanPlayer implements Player { public void setColor(TileColor clr) { color = clr; } + + @Override + public void setGameGoal(GameGoal target) { + // Do nothing. + } } \ No newline at end of file diff --git a/src/model/comPlayer/MinimaxComPlayer.java b/src/model/comPlayer/MinimaxComPlayer.java index 81c6fdc..6189a6d 100644 --- a/src/model/comPlayer/MinimaxComPlayer.java +++ b/src/model/comPlayer/MinimaxComPlayer.java @@ -4,6 +4,7 @@ import model.Board; import model.Move; import model.comPlayer.generator.MinimaxMoveGenerator; import model.comPlayer.generator.MoveGenerator; +import model.playerModel.GameGoal; import model.playerModel.PlayerModel; public class MinimaxComPlayer implements Player { @@ -28,6 +29,12 @@ public class MinimaxComPlayer implements Player { return true; // always ready to play a random valid move } + @Override + public void setGameGoal(GameGoal target) { + // TODO Auto-generated method stub + + } + @Override public String toString() { return "Minimax ComPlayer"; diff --git a/src/model/comPlayer/MonteCarloComPlayer.java b/src/model/comPlayer/MonteCarloComPlayer.java index fca3b52..2b78c73 100644 --- a/src/model/comPlayer/MonteCarloComPlayer.java +++ b/src/model/comPlayer/MonteCarloComPlayer.java @@ -4,26 +4,33 @@ import model.Board; import model.Move; import model.comPlayer.generator.MonteCarloMoveGenerator; import model.comPlayer.generator.MoveGenerator; +import model.playerModel.GameGoal; import model.playerModel.PlayerModel; public class MonteCarloComPlayer implements Player { - private MoveGenerator moveGenerator = new MonteCarloMoveGenerator(); - - @Override - public Move getMove(Board board, PlayerModel playerModel) { - return moveGenerator.genMove(board, false); - } + private final MoveGenerator moveGenerator = new MonteCarloMoveGenerator(); @Override public void denyMove() { throw new UnsupportedOperationException("Not implemented"); } + @Override + public Move getMove(Board board, PlayerModel playerModel) { + return moveGenerator.genMove(board, false); + } + @Override public boolean isReady() { return true; // always ready to play a random valid move } + @Override + public void setGameGoal(GameGoal target) { + // TODO Auto-generated method stub + + } + @Override public String toString() { return "Monte Carlo ComPlayer"; diff --git a/src/model/comPlayer/NeuralNetworkPlayer.java b/src/model/comPlayer/NeuralNetworkPlayer.java index 78b3a6f..d4c5c71 100644 --- a/src/model/comPlayer/NeuralNetworkPlayer.java +++ b/src/model/comPlayer/NeuralNetworkPlayer.java @@ -1,25 +1,14 @@ package model.comPlayer; import model.Board; -import model.Board.TileColor; import model.Move; -import model.Referee; -import model.playerModel.Node; +import model.comPlayer.generator.NeuralNetworkMoveGenerator; +import model.playerModel.GameGoal; import model.playerModel.PlayerModel; public class NeuralNetworkPlayer implements Player { - public static int getSmallest(double[] list) { - int index = 0; - - for (int i = 0; i < list.length; i++) { - if (list[index] < list[i]) { - index = i; - } - } - - return index; - } + private NeuralNetworkMoveGenerator nnGen = null; @Override public void denyMove() { @@ -28,55 +17,11 @@ public class NeuralNetworkPlayer implements Player { @Override public Move getMove(Board board, PlayerModel player) { - Move mv = null; - - Node[] nodes = player.getOutputNodes(Referee.getBoardState(board)); - - TileColor color = TileColor.BLUE; - - double[] colorStrengths = new double[4]; - colorStrengths[0] = nodes[0].strength(); - colorStrengths[1] = nodes[1].strength(); - colorStrengths[2] = nodes[2].strength(); - colorStrengths[3] = nodes[3].strength(); - - switch (getSmallest(colorStrengths)) { - case 1: - color = TileColor.GREEN; - break; - case 2: - color = TileColor.RED; - break; - case 3: - color = TileColor.YELLOW; - break; - case 0: - default: - color = TileColor.BLUE; + if (nnGen == null) { + nnGen = new NeuralNetworkMoveGenerator(player); } - int index = 4; - for (int i = 4; i < nodes.length; i++) { - if (nodes[i].strength() > nodes[index].strength()) { - index = i; - } - } - - int i = 4; - loop: for (int r = 0; r < Board.NUM_ROWS; r++) { - for (int c = 0; c < Board.NUM_COLS; c++) { - if (i == index) { - mv = new Move(color, r, c); - break loop; - } - - else { - i++; - } - } - } - - return mv; + return nnGen.genMove(board, false); } @Override @@ -84,6 +29,11 @@ public class NeuralNetworkPlayer implements Player { return true; } + @Override + public void setGameGoal(GameGoal target) { + // Do nothing. + } + @Override public String toString() { return "Neural Network Player"; diff --git a/src/model/comPlayer/Player.java b/src/model/comPlayer/Player.java index b3c37bb..793c2cc 100644 --- a/src/model/comPlayer/Player.java +++ b/src/model/comPlayer/Player.java @@ -2,6 +2,7 @@ package model.comPlayer; import model.Board; import model.Move; +import model.playerModel.GameGoal; import model.playerModel.PlayerModel; public interface Player { @@ -26,4 +27,6 @@ public interface Player { * @return */ public boolean isReady(); + + public void setGameGoal(GameGoal target); } diff --git a/src/model/comPlayer/RandomComPlayer.java b/src/model/comPlayer/RandomComPlayer.java index 4ed1b41..c2cd83c 100644 --- a/src/model/comPlayer/RandomComPlayer.java +++ b/src/model/comPlayer/RandomComPlayer.java @@ -6,6 +6,7 @@ import model.Board; import model.Board.TileColor; import model.CellPointer; import model.Move; +import model.playerModel.GameGoal; import model.playerModel.PlayerModel; public class RandomComPlayer implements Player { @@ -51,6 +52,11 @@ public class RandomComPlayer implements Player { return true; // always ready to play a random valid move } + @Override + public void setGameGoal(GameGoal target) { + // TODO Auto-generated method stub + } + @Override public String toString() { return "Random ComPlayer"; diff --git a/src/model/comPlayer/generator/NeuralNetworkMoveGenerator.java b/src/model/comPlayer/generator/NeuralNetworkMoveGenerator.java new file mode 100644 index 0000000..1eb59e3 --- /dev/null +++ b/src/model/comPlayer/generator/NeuralNetworkMoveGenerator.java @@ -0,0 +1,98 @@ +package model.comPlayer.generator; + +import java.util.List; + +import model.Board; +import model.Board.TileColor; +import model.CellPointer; +import model.Move; +import model.Referee; +import model.playerModel.Node; +import model.playerModel.PlayerModel; + +public class NeuralNetworkMoveGenerator implements MoveGenerator { + + public static int getSmallest(double[] list) { + int index = 0; + + for (int i = 0; i < list.length; i++) { + if (list[index] < list[i]) { + index = i; + } + } + + return index; + } + + PlayerModel player; + + public NeuralNetworkMoveGenerator(PlayerModel pm) { + player = pm; + } + + @Override + public Move genMove(Board board, boolean asHuman) { + Move mv = null; + + Node[] nodes = player.getOutputNodes(Referee.getBoardState(board)); + + TileColor color = TileColor.BLUE; + + double[] colorStrengths = new double[4]; + colorStrengths[0] = nodes[0].strength(); + colorStrengths[1] = nodes[1].strength(); + colorStrengths[2] = nodes[2].strength(); + colorStrengths[3] = nodes[3].strength(); + + switch (getSmallest(colorStrengths)) { + case 1: + color = TileColor.GREEN; + break; + case 2: + color = TileColor.RED; + break; + case 3: + color = TileColor.YELLOW; + break; + case 0: + default: + color = TileColor.BLUE; + } + + int index = 4; + for (int i = 4; i < nodes.length; i++) { + if (nodes[i].strength() > nodes[index].strength()) { + index = i; + } + } + + int i = 4; + loop: for (int r = 0; r < Board.NUM_ROWS; r++) { + for (int c = 0; c < Board.NUM_COLS; c++) { + if (i == index) { + mv = new Move(color, r, c); + break loop; + } + + else { + i++; + } + } + } + + while (!Board.isLegal(board, mv.getCell())) { + mv = new Move(mv.getColor(), new CellPointer( + PlayerModel.rand.nextInt(Board.NUM_ROWS), + PlayerModel.rand.nextInt(Board.NUM_COLS))); + } + + return mv; + } + + @Override + public List genMoves(Board board, boolean asHuman, int nMoves) { + // Do nothing. + return null; + } + +} \ No newline at end of file diff --git a/src/model/playerModel/PlayerModel.java b/src/model/playerModel/PlayerModel.java index 2570d1d..22240ca 100644 --- a/src/model/playerModel/PlayerModel.java +++ b/src/model/playerModel/PlayerModel.java @@ -134,6 +134,10 @@ public class PlayerModel implements Serializable { public Node[] getOutputNodes(boolean[] input) { if (input.length == inputNode.length) { + for (int i = 0; i < input.length; i++) { + inputNode[i].setStimulation(input[i]); + } + return outputNode; } @@ -203,12 +207,13 @@ public class PlayerModel implements Serializable { } } - public void train(boolean[] boardState, boolean[] example) { - getOutputNodes(boardState); + public void train(boolean[] example) { + boolean[] hold = getOutputActivations(); + System.out.println("TRAIN"); if (example.length == outputNode.length) { for (int i = 0; i < outputNode.length; i++) { - outputNode[i].learn(example[i]); + outputNode[i].learn(example[i] == hold[i]); } } } @@ -216,4 +221,14 @@ public class PlayerModel implements Serializable { private int getHighScore() { return getHighScores()[0]; } + + private boolean[] getOutputActivations() { + boolean[] acts = new boolean[outputNode.length]; + + for (int i = 0; i < acts.length; i++) { + acts[i] = outputNode[i].axon(); + } + + return acts; + } } diff --git a/src/model/playerModel/node/SigmoidNode.java b/src/model/playerModel/node/SigmoidNode.java index c19c02a..60af9f9 100644 --- a/src/model/playerModel/node/SigmoidNode.java +++ b/src/model/playerModel/node/SigmoidNode.java @@ -9,7 +9,7 @@ public class SigmoidNode implements Node { private static final long serialVersionUID = 1L; // Training rate. - private final double A = .15; + private final double A = .05; private final Hashtable dendrites = new Hashtable(); @@ -36,6 +36,8 @@ public class SigmoidNode implements Node { n.learn(correct); } } + + System.out.println(strength()); } @Override diff --git a/src/view/ParsedArgs.java b/src/view/ParsedArgs.java index 17c547d..e42ed91 100644 --- a/src/view/ParsedArgs.java +++ b/src/view/ParsedArgs.java @@ -1,20 +1,24 @@ package view; import model.comPlayer.AlphaBetaComPlayer; +import model.comPlayer.ComboPlayer; import model.comPlayer.MinimaxComPlayer; import model.comPlayer.MonteCarloComPlayer; +import model.comPlayer.NeuralNetworkPlayer; import model.comPlayer.Player; import model.comPlayer.RandomComPlayer; public class ParsedArgs { - public static final String COM_RANDOM = "RANDOM"; - public static final String COM_MINIMAX = "MINIMAX"; public static final String COM_ALPHABETA = "ALPHABETA"; - public static final String COM_MONTECARLO = "MONTECARLO"; + public static final String COM_ANN = "NEURALNET"; + public static final String COM_COMBO = "COMBO"; public static final String COM_DEFAULT = COM_ALPHABETA; - + public static final String COM_MINIMAX = "MINIMAX"; + public static final String COM_MONTECARLO = "MONTECARLO"; + public static final String COM_RANDOM = "RANDOM"; + private String comPlayer = COM_DEFAULT; - + public Player getComPlayer() { if (COM_RANDOM.equalsIgnoreCase(comPlayer)) { return new RandomComPlayer(); @@ -24,13 +28,18 @@ public class ParsedArgs { return new AlphaBetaComPlayer(); } else if (COM_MONTECARLO.equalsIgnoreCase(comPlayer)) { return new MonteCarloComPlayer(); + } else if (COM_ANN.equalsIgnoreCase(comPlayer)) { + return new NeuralNetworkPlayer(); + } else if (COM_COMBO.equalsIgnoreCase(comPlayer)) { + return new ComboPlayer(); } else { - System.out.println("Unrecognized comPlayer '" + comPlayer +"', using default: " + COM_DEFAULT); + System.out.println("Unrecognized comPlayer '" + comPlayer + + "', using default: " + COM_DEFAULT); return new AlphaBetaComPlayer(); } } - - public void setComPlayer(String comPlayer) { + + public void setComPlayer(String comPlayer) { this.comPlayer = comPlayer; } } \ No newline at end of file From 0724d44ec4243fc0ac12dfbde24f9d1bfe785a62 Mon Sep 17 00:00:00 2001 From: Marshall Date: Mon, 30 Apr 2012 05:18:02 -0400 Subject: [PATCH 3/3] - Created a new ComPlayer. It targets a particular score, and does it pretty well. It does this by counting the number of empty spaces and the number of empty spaces that can potentially be created by removing tiles then either playing to block or playing to extend the game. - Created DumpResults.java, which simply outputs all scores in a player model so that they can be graphed. --- src/model/Board.java | 13 + src/model/Referee.java | 4 + src/model/comPlayer/CountingPlayer.java | 299 ++++++++++++++++++++ src/model/playerModel/PlayerModel.java | 9 +- src/model/playerModel/node/SigmoidNode.java | 2 +- src/view/ParsedArgs.java | 4 + test/model/DumpResults.java | 35 +++ 7 files changed, 364 insertions(+), 2 deletions(-) create mode 100644 src/model/comPlayer/CountingPlayer.java create mode 100644 test/model/DumpResults.java diff --git a/src/model/Board.java b/src/model/Board.java index c86c246..bb91d0e 100644 --- a/src/model/Board.java +++ b/src/model/Board.java @@ -117,6 +117,19 @@ public class Board { return true; } + public int getEmptySpaces() { + int count = 0; + for (int r = 0; r < NUM_ROWS; r++) { + for (int c = 0; c < NUM_COLS; c++) { + if (getTile(r, c) == TileColor.NONE) { + count++; + } + } + } + + return count; + } + public TileColor getTile(int r, int c) { return board[r][c]; } diff --git a/src/model/Referee.java b/src/model/Referee.java index e192ef7..b1efdf1 100644 --- a/src/model/Referee.java +++ b/src/model/Referee.java @@ -3,6 +3,7 @@ package model; import model.Board.TileColor; import model.comPlayer.HumanPlayer; import model.comPlayer.Player; +import model.playerModel.GameGoal; import model.playerModel.PlayerModel; import org.apache.log4j.Logger; @@ -102,6 +103,9 @@ public class Referee implements Runnable { while (true) { initGame(); mf.updateBoard(); + GameGoal goal = playerModel.getTargetScore(); + System.out.println("Target: " + goal.getTargetScore()); + computerPlayer.setGameGoal(goal); play(); getPlayerModel().logGame(getPlayerScore()); diff --git a/src/model/comPlayer/CountingPlayer.java b/src/model/comPlayer/CountingPlayer.java new file mode 100644 index 0000000..201b91c --- /dev/null +++ b/src/model/comPlayer/CountingPlayer.java @@ -0,0 +1,299 @@ +package model.comPlayer; + +import java.util.ArrayList; + +import model.Board; +import model.Board.TileColor; +import model.CellPointer; +import model.Move; +import model.playerModel.GameGoal; +import model.playerModel.PlayerModel; + +public class CountingPlayer implements Player { + + private static boolean causesConnection(Board board, Move m) { + + CellPointer cell = m.getCell(); + TileColor color = m.getColor(); + boolean causes = false; + + boolean oneUBlank = (cell.r - 1 >= 0) + && board.getTile(cell.r - 1, cell.c) == TileColor.NONE; + boolean oneUColor = (cell.r - 1 >= 0) + && board.getTile(cell.r - 1, cell.c) == color; + boolean twoUBlank = (cell.r - 2 >= 0) + && board.getTile(cell.r - 2, cell.c) == TileColor.NONE; + boolean twoUColor = (cell.r - 2 >= 0) + && board.getTile(cell.r - 2, cell.c) == color; + + boolean oneDBlank = (cell.r + 1 < Board.NUM_ROWS) + && board.getTile(cell.r + 1, cell.c) == TileColor.NONE; + boolean oneDColor = (cell.r + 1 < Board.NUM_ROWS) + && board.getTile(cell.r + 1, cell.c) == color; + boolean twoDBlank = (cell.r + 2 < Board.NUM_ROWS) + && board.getTile(cell.r + 2, cell.c) == TileColor.NONE; + boolean twoDColor = (cell.r + 2 < Board.NUM_ROWS) + && board.getTile(cell.r + 2, cell.c) == color; + + boolean oneLBlank = (cell.c - 1 >= 0) + && board.getTile(cell.r, cell.c - 1) == TileColor.NONE; + boolean oneLColor = (cell.c - 1 >= 0) + && board.getTile(cell.r, cell.c - 1) == color; + boolean twoLBlank = (cell.c - 2 >= 0) + && board.getTile(cell.r, cell.c - 2) == TileColor.NONE; + boolean twoLColor = (cell.c - 2 >= 0) + && board.getTile(cell.r, cell.c - 2) == color; + + boolean oneRBlank = (cell.c + 1 < Board.NUM_ROWS) + && board.getTile(cell.r, cell.c + 1) == TileColor.NONE; + boolean oneRColor = (cell.c + 1 < Board.NUM_ROWS) + && board.getTile(cell.r, cell.c + 1) == color; + boolean twoRBlank = (cell.c + 2 < Board.NUM_ROWS) + && board.getTile(cell.r, cell.c + 2) == TileColor.NONE; + boolean twoRColor = (cell.c + 2 < Board.NUM_ROWS) + && board.getTile(cell.r, cell.c + 2) == color; + + causes = (oneUBlank && twoUColor) || (oneDBlank && twoDColor) + || (oneLBlank && twoLColor) || (oneRBlank && twoRColor) + || (oneDColor && (oneUBlank || twoDBlank)) + || (oneUColor && (oneDBlank || twoUBlank)) + || (oneLColor && (oneRBlank || twoLBlank)) + || (oneRColor && (oneLBlank || twoRBlank)); + + return causes; + } + + private static boolean updateRemovals(Board board, Move m, + boolean[][] canBeRemoved) { + ArrayList remove = new ArrayList(); + ArrayList hold; + CellPointer cp = m.getCell(); + TileColor tile = m.getColor(); + int count; + + // Check up-and-down. + count = 1; + hold = new ArrayList(); + loop: for (int row = cp.r - 1; row >= 0; row--) { + if (board.getTile(row, cp.c) == tile) { + hold.add(new CellPointer(row, cp.c)); + count++; + } else { + break loop; + } + } + + loop: for (int row = cp.r + 1; row < Board.NUM_ROWS; row++) { + if (board.getTile(row, cp.c) == tile) { + hold.add(new CellPointer(row, cp.c)); + count++; + } else { + break loop; + } + } + + if (count >= Board.ROW_REMOVAL_SIZE) { + remove.addAll(hold); + } + + // Check left-and-right. + count = 1; + hold = new ArrayList(); + loop: for (int col = cp.c - 1; col >= 0; col--) { + if (board.getTile(cp.r, col) == tile) { + hold.add(new CellPointer(cp.r, col)); + count++; + } else { + break loop; + } + } + + loop: for (int col = cp.c + 1; col < Board.NUM_COLS; col++) { + if (board.getTile(cp.r, col) == tile) { + hold.add(new CellPointer(cp.r, col)); + count++; + } else { + break loop; + } + } + + if (count >= Board.ROW_REMOVAL_SIZE) { + remove.addAll(hold); + } + + for (CellPointer c : remove) { + canBeRemoved[c.r][c.c] = true; + } + + canBeRemoved[cp.r][cp.c] = false; + + return (remove.size() > 0); + } + + GameGoal goal = null; + + @Override + public void denyMove() { + throw new UnsupportedOperationException("Not implemented"); + } + + @Override + public Move getMove(Board board, PlayerModel player) { + int remainingPlays = goal.getTargetScore() - board.getTurn(); + int prediction = board.getEmptySpaces(); + + ArrayList moves = getLegalMoves(board); + Object[] movesHolder; + + boolean[][] canBeRemoved = new boolean[Board.NUM_ROWS][Board.NUM_COLS]; + int[][] blockScore = new int[Board.NUM_ROWS][Board.NUM_COLS]; + + for (int r = 0; r < canBeRemoved.length; r++) { + for (int c = 0; c < canBeRemoved[0].length; c++) { + canBeRemoved[r][c] = false; + blockScore[r][c] = 0; + } + } + + movesHolder = moves.toArray(); + Move m; + for (Object o : movesHolder) { + m = (Move) o; + if (updateRemovals(board, m, canBeRemoved)) { + // blockScore[m.getCell().r][m.getCell().c] += + // !causesConnection( + // board, m) ? 2 : 1; + blockScore[m.getCell().r][m.getCell().c] += 2; + moves.remove(m); + } + + else if (causesConnection(board, m) + && (remainingPlays <= prediction)) { + moves.remove(m); + } + + else if (causesConnection(board, m)) { + blockScore[m.getCell().r][m.getCell().c]++; + } + + // else if (!causesConnection(board, m)) { + // blockScore[m.getCell().r][m.getCell().c]++; + // } + } + + for (int r = 0; r < canBeRemoved.length; r++) { + for (int c = 0; c < canBeRemoved[0].length; c++) { + prediction += (canBeRemoved[r][c]) ? 1 : 0; + } + } + + // Okay. So now... + // We have a number of plays we WANT to go to and a number of plays we + // EXPECT to go to. If we want to go to a greater number than we expect + // to, then we should pick a crap move. If we want to go to a smaller or + // equal number, we should block. + + if (moves.size() == 0) { + moves = getLegalMoves(board); + return moves.get(PlayerModel.rand.nextInt(moves.size())); + } + + else if (remainingPlays > prediction) { + for (int i = 0;; i++) { + ArrayList choices = new ArrayList(); + for (int r = 0; r < Board.NUM_ROWS; r++) { + for (int c = 0; c < Board.NUM_COLS; c++) { + if (blockScore[r][c] == i) { + for (Move mv : moves) { + if (mv.getCell().r == r && mv.getCell().c == c) { + choices.add(mv); + } + } + } + } + } + + if (choices.size() > 0) { + return choices + .get(PlayerModel.rand.nextInt(choices.size())); + } + } + } + + else { + int bestScore; + while (true) { + bestScore = 0; + ArrayList choices = new ArrayList(); + for (int r = 0; r < Board.NUM_ROWS; r++) { + for (int c = 0; c < Board.NUM_COLS; c++) { + if (blockScore[r][c] > bestScore) { + bestScore = blockScore[r][c]; + choices = new ArrayList(); + } + + if (blockScore[r][c] == bestScore) { + movesHolder = moves.toArray(); + + for (Object o : movesHolder) { + m = (Move) o; + if (m.getCell().r == r && m.getCell().c == c) { + choices.add(m); + moves.remove(m); + } + } + } + + } + } + + if (choices.size() > 0) { + return choices + .get(PlayerModel.rand.nextInt(choices.size())); + } + + else { + for (int r = 0; r < Board.NUM_ROWS; r++) { + for (int c = 0; c < Board.NUM_COLS; c++) { + if (blockScore[r][c] == bestScore) { + blockScore[r][c] = 0; + } + } + } + } + } + } + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setGameGoal(GameGoal target) { + goal = target; + } + + @Override + public String toString() { + return "Counting ComPlayer"; + } + + private ArrayList getLegalMoves(Board board) { + ArrayList moves = new ArrayList(); + + for (int r = 0; r < Board.NUM_ROWS; r++) { + for (int c = 0; c < Board.NUM_COLS; c++) { + if (Board.isLegal(board, new CellPointer(r, c))) { + moves.add(new Move(TileColor.BLUE, r, c)); + moves.add(new Move(TileColor.GREEN, r, c)); + moves.add(new Move(TileColor.RED, r, c)); + moves.add(new Move(TileColor.YELLOW, r, c)); + } + } + } + + return moves; + } +} diff --git a/src/model/playerModel/PlayerModel.java b/src/model/playerModel/PlayerModel.java index 22240ca..c35d988 100644 --- a/src/model/playerModel/PlayerModel.java +++ b/src/model/playerModel/PlayerModel.java @@ -144,6 +144,13 @@ public class PlayerModel implements Serializable { return null; } + public ArrayList getScores() { + GameLog.SORT_BY_SCORE = false; + Collections.sort(scores); + + return scores; + } + public GameGoal getTargetScore() { GameGoal goal; int targetScore; @@ -210,7 +217,7 @@ public class PlayerModel implements Serializable { public void train(boolean[] example) { boolean[] hold = getOutputActivations(); - System.out.println("TRAIN"); + // System.out.println("TRAIN"); if (example.length == outputNode.length) { for (int i = 0; i < outputNode.length; i++) { outputNode[i].learn(example[i] == hold[i]); diff --git a/src/model/playerModel/node/SigmoidNode.java b/src/model/playerModel/node/SigmoidNode.java index 60af9f9..ce78c51 100644 --- a/src/model/playerModel/node/SigmoidNode.java +++ b/src/model/playerModel/node/SigmoidNode.java @@ -37,7 +37,7 @@ public class SigmoidNode implements Node { } } - System.out.println(strength()); + // System.out.println(strength()); } @Override diff --git a/src/view/ParsedArgs.java b/src/view/ParsedArgs.java index e42ed91..dcdeac5 100644 --- a/src/view/ParsedArgs.java +++ b/src/view/ParsedArgs.java @@ -2,6 +2,7 @@ package view; import model.comPlayer.AlphaBetaComPlayer; import model.comPlayer.ComboPlayer; +import model.comPlayer.CountingPlayer; import model.comPlayer.MinimaxComPlayer; import model.comPlayer.MonteCarloComPlayer; import model.comPlayer.NeuralNetworkPlayer; @@ -12,6 +13,7 @@ public class ParsedArgs { public static final String COM_ALPHABETA = "ALPHABETA"; public static final String COM_ANN = "NEURALNET"; public static final String COM_COMBO = "COMBO"; + public static final String COM_COUNTING = "COUNTING"; public static final String COM_DEFAULT = COM_ALPHABETA; public static final String COM_MINIMAX = "MINIMAX"; public static final String COM_MONTECARLO = "MONTECARLO"; @@ -32,6 +34,8 @@ public class ParsedArgs { return new NeuralNetworkPlayer(); } else if (COM_COMBO.equalsIgnoreCase(comPlayer)) { return new ComboPlayer(); + } else if (COM_COUNTING.equalsIgnoreCase(comPlayer)) { + return new CountingPlayer(); } else { System.out.println("Unrecognized comPlayer '" + comPlayer + "', using default: " + COM_DEFAULT); diff --git a/test/model/DumpResults.java b/test/model/DumpResults.java new file mode 100644 index 0000000..e7f197e --- /dev/null +++ b/test/model/DumpResults.java @@ -0,0 +1,35 @@ +package model; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.ObjectInputStream; + +import model.playerModel.GameLog; +import model.playerModel.PlayerModel; + +public class DumpResults { + public static void main(String[] args) { + FileInputStream fin = null; + ObjectInputStream oin = null; + + try { + fin = new FileInputStream(PlayerModel.getPlayerPath("marshall")); + + oin = new ObjectInputStream(fin); + PlayerModel pm = (PlayerModel) oin.readObject(); + oin.close(); + + for (GameLog gl : pm.getScores()) { + System.out.println(gl.getScore()); + } + + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } +}