From 0724d44ec4243fc0ac12dfbde24f9d1bfe785a62 Mon Sep 17 00:00:00 2001 From: Marshall Date: Mon, 30 Apr 2012 05:18:02 -0400 Subject: [PATCH] - 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(); + } + } +}