diff --git a/src/model/Board.java b/src/model/Board.java index 3fa08fe..bb91d0e 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. @@ -57,12 +117,25 @@ 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]; } public int getTurn() { - return (numPlies + 1)/ 2; + return (numPlies + 1) / 2; } @Override @@ -90,8 +163,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 +320,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/Referee.java b/src/model/Referee.java index 55d4748..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()); @@ -128,7 +132,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 +168,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 +183,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/AdaptiveComPlayer.java b/src/model/comPlayer/AdaptiveComPlayer.java index efd2995..5413f1b 100644 --- a/src/model/comPlayer/AdaptiveComPlayer.java +++ b/src/model/comPlayer/AdaptiveComPlayer.java @@ -6,6 +6,7 @@ import model.Move; import model.comPlayer.generator.AlphaBetaMoveGenerator; import model.comPlayer.generator.MonteCarloMoveGenerator; import model.comPlayer.generator.MoveGenerator; +import model.playerModel.GameGoal; import model.playerModel.PlayerModel; import aima.core.environment.gridworld.GridCell; import aima.core.environment.gridworld.GridWorld; @@ -24,6 +25,7 @@ public class AdaptiveComPlayer implements Player { private BoardScorer boardScorer = new BoardScorer(); private boolean calculatePolicy = true; + private GameGoal target = null; private GridWorld gw = null; private MarkovDecisionProcess, GridWorldAction> mdp = null; private Policy, GridWorldAction> policy = null; @@ -42,7 +44,7 @@ public class AdaptiveComPlayer implements Player { // take 10 turns to place 6 tiles double defaultPenalty = -0.25; - int maxScore = player.getTargetScore().getTargetScore(); + int maxScore = target.getTargetScore(); int maxTiles = Board.NUM_COLS * Board.NUM_ROWS; gw = GridWorldFactory.createGridWorldForTileGame(maxTiles, @@ -56,7 +58,7 @@ public class AdaptiveComPlayer implements Player { policy = pi.policyIteration(mdp); System.out.println("Optimum policy calculated."); - + for (int j = maxScore; j >= 1; j--) { StringBuilder sb = new StringBuilder(); for (int i = 1; i <= maxTiles; i++) { @@ -65,7 +67,7 @@ public class AdaptiveComPlayer implements Player { } System.out.println(sb.toString()); } - + calculatePolicy = false; } else { System.out.println("Using pre-calculated policy"); @@ -75,22 +77,27 @@ public class AdaptiveComPlayer implements Player { GridWorldAction action = policy.action(state); if (action == null || state == null) { - System.out.println("Board state outside of parameters of MDP. Reverting to failsafe behavior."); + System.out + .println("Board state outside of parameters of MDP. Reverting to failsafe behavior."); action = GridWorldAction.RandomMove; } - System.out.println("Performing action " + action + " at state " + state + " per policy."); + System.out.println("Performing action " + action + " at state " + state + + " per policy."); switch (action) { case AddTile: - //System.out.println("Performing action #" + GridWorldAction.AddTile.ordinal()); + // System.out.println("Performing action #" + + // GridWorldAction.AddTile.ordinal()); return abMoveGenerator.genMove(board, false); case CaptureThree: - //System.out.println("Performing action #" + GridWorldAction.CaptureThree.ordinal()); + // System.out.println("Performing action #" + + // GridWorldAction.CaptureThree.ordinal()); return mcMoveGenerator.genMove(board, false); case RandomMove: - //System.out.println("Performing action #" + GridWorldAction.None.ordinal()); + // System.out.println("Performing action #" + + // GridWorldAction.None.ordinal()); return mcMoveGenerator.genMove(board, false); default: - //System.out.println("Performing failsafe action"); + // System.out.println("Performing failsafe action"); return mcMoveGenerator.genMove(board, false); } } @@ -98,7 +105,7 @@ public class AdaptiveComPlayer implements Player { private GridCell getState(Board board) { return gw.getCellAt(board.getTurn(), boardScorer.getScore(board)); } - + @Override public boolean isReady() { return true; // always ready to play a random valid move @@ -106,6 +113,11 @@ public class AdaptiveComPlayer implements Player { @Override public String toString() { - return "Alpha-Beta ComPlayer"; + return "Adaptive ComPlayer"; + } + + @Override + public void setGameGoal(GameGoal target) { + this.target = target; } } \ No newline at end of file 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/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/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/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/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/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)); } } } diff --git a/src/model/playerModel/PlayerModel.java b/src/model/playerModel/PlayerModel.java index 2570d1d..c35d988 100644 --- a/src/model/playerModel/PlayerModel.java +++ b/src/model/playerModel/PlayerModel.java @@ -134,12 +134,23 @@ 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; } return null; } + public ArrayList getScores() { + GameLog.SORT_BY_SCORE = false; + Collections.sort(scores); + + return scores; + } + public GameGoal getTargetScore() { GameGoal goal; int targetScore; @@ -203,12 +214,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 +228,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..ce78c51 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 210895a..3dfe4e1 100644 --- a/src/view/ParsedArgs.java +++ b/src/view/ParsedArgs.java @@ -2,21 +2,27 @@ package view; import model.comPlayer.AdaptiveComPlayer; import model.comPlayer.AlphaBetaComPlayer; +import model.comPlayer.ComboPlayer; +import model.comPlayer.CountingPlayer; 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_ADAPTIVE = "ADAPTIVE"; 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"; public static final String COM_RANDOM = "RANDOM"; - public static final String COM_DEFAULT = COM_ALPHABETA; - + private String comPlayer = COM_DEFAULT; - + public Player getComPlayer() { if (COM_ADAPTIVE.equalsIgnoreCase(comPlayer)) { return new AdaptiveComPlayer(); @@ -28,13 +34,20 @@ 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 if (COM_COUNTING.equalsIgnoreCase(comPlayer)) { + return new CountingPlayer(); } 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 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(); + } + } +}