diff --git a/.classpath b/.classpath
index 371ffc0..ae23745 100644
--- a/.classpath
+++ b/.classpath
@@ -5,5 +5,6 @@
+
diff --git a/lib/log4j-1.2.16.jar b/lib/log4j-1.2.16.jar
new file mode 100644
index 0000000..3f9d847
Binary files /dev/null and b/lib/log4j-1.2.16.jar differ
diff --git a/src/controller/BoardPanelMouseListener.java b/src/controller/BoardPanelMouseListener.java
index 9e0e76d..b9602a7 100644
--- a/src/controller/BoardPanelMouseListener.java
+++ b/src/controller/BoardPanelMouseListener.java
@@ -5,7 +5,8 @@ import java.awt.event.MouseListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
-import model.HumanPlayer;
+import player.HumanPlayer;
+
import view.Tile;
import view.TileSelectionPanel;
diff --git a/src/controller/TSPMouseListener.java b/src/controller/TSPMouseListener.java
index 04b931e..c92cd63 100644
--- a/src/controller/TSPMouseListener.java
+++ b/src/controller/TSPMouseListener.java
@@ -3,9 +3,10 @@ package controller;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
+import player.HumanPlayer;
+
import view.TileSelectionPanel;
-import model.HumanPlayer;
import model.Board.TileColor;
public class TSPMouseListener implements MouseListener{
diff --git a/src/model/Board.java b/src/model/Board.java
index 2b4d3ca..32666bb 100644
--- a/src/model/Board.java
+++ b/src/model/Board.java
@@ -8,12 +8,15 @@ public class Board {
BLUE, GREEN, NONE, RED, YELLOW
}
- public static final int NUM_COLS = 5;
- public static final int NUM_ROWS = 5;
+ public static final int NUM_COLS = 4;
+ public static final int NUM_ROWS = 4;
public static final int ROW_REMOVAL_SIZE = 3;
private final TileColor[][] board;
+ // a Ply is a play by 1 side. Increments each time setTile() is called.
+ private int numPlies = 0;
+
public Board(Board that) {
this();
for (int i = 0; i < NUM_COLS; i++) {
@@ -22,17 +25,16 @@ public class Board {
}
}
}
-
+
public Board() {
board = new TileColor[NUM_ROWS][NUM_COLS];
for (int r = 0; r < NUM_ROWS; r++) {
for (int c = 0; c < NUM_COLS; c++) {
- setTile(new CellPointer(r, c), TileColor.NONE);
+ board[r][c] = TileColor.NONE;
}
}
}
-
@Override
public int hashCode() {
final int prime = 31;
@@ -50,7 +52,7 @@ public class Board {
if (getClass() != obj.getClass())
return false;
Board other = (Board) obj;
-
+
for (int r = 0; r < NUM_ROWS; r++) {
for (int c = 0; c < NUM_COLS; c++) {
if (this.board[r][c] != other.board[r][c]) {
@@ -58,7 +60,7 @@ public class Board {
}
}
}
-
+
return true;
}
@@ -66,7 +68,31 @@ public class Board {
return board[r][c];
}
- public void setTile(CellPointer cp, TileColor tile) {
+ public int getTurn() {
+ return numPlies / 2;
+ }
+
+ public boolean isPlayerTurn() {
+ return numPlies % 2 == 0;
+ }
+
+ public boolean isTerminalState() {
+ for (int r = 0; r < Board.NUM_ROWS; r++) {
+ for (int c = 0; c < Board.NUM_COLS; c++) {
+ if (board[r][c] == TileColor.NONE) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public boolean playTile(CellPointer cp, TileColor tile) {
+ if (board[cp.r][cp.c] != TileColor.NONE) {
+ return false; //illegal move
+ }
+
board[cp.r][cp.c] = tile;
if (tile != TileColor.NONE) {
@@ -199,5 +225,8 @@ public class Board {
board[cell.r][cell.c] = TileColor.NONE;
}
}
+
+ numPlies++;
+ return true;
}
-}
+}
\ No newline at end of file
diff --git a/src/model/BoardScorer.java b/src/model/BoardScorer.java
new file mode 100644
index 0000000..a63edb5
--- /dev/null
+++ b/src/model/BoardScorer.java
@@ -0,0 +1,24 @@
+package model;
+
+/**
+ * Scorer for use by various ComPlayer implementations.
+ *
+ * @author Woody
+ *
+ */
+public class BoardScorer {
+
+ public int getMaxScore(Board board) {
+ return Board.NUM_ROWS * Board.NUM_COLS;
+ }
+
+ public int getScore(Board board) {
+ int score = 0;
+ for (int r = 0; r < Board.NUM_ROWS; r++) {
+ for (int c = 0; c < Board.NUM_COLS; c++) {
+ score += board.getTile(r, c) == Board.TileColor.NONE ? 0 : 1;
+ }
+ }
+ return score;
+ }
+}
\ No newline at end of file
diff --git a/src/model/Move.java b/src/model/Move.java
index db586d6..539b5a9 100644
--- a/src/model/Move.java
+++ b/src/model/Move.java
@@ -3,9 +3,19 @@ package model;
import model.Board.TileColor;
public class Move {
+ /**
+ * Used when genMove() is called but no valid move exists for that player.
+ */
+ public static final Move NONE = new Move(TileColor.NONE, -1, -1);
+
private final TileColor color;
private final CellPointer cp;
+ public Move(TileColor color, int row, int column) {
+ cp = new CellPointer(row,column);
+ this.color = color;
+ }
+
public Move(CellPointer cllPntr, TileColor tlClr) {
cp = cllPntr;
color = tlClr;
diff --git a/src/model/Referee.java b/src/model/Referee.java
index 61457f1..98fe4c2 100644
--- a/src/model/Referee.java
+++ b/src/model/Referee.java
@@ -1,7 +1,13 @@
package model;
+import org.apache.log4j.Logger;
+
+import player.AlphaBetaComPlayer;
+import player.HumanPlayer;
+import player.Player;
import view.BoardPanel;
import view.MessagePanel;
+import view.ScorePanel;
import model.Board.TileColor;
public class Referee implements Runnable {
@@ -10,26 +16,28 @@ public class Referee implements Runnable {
public static final String GAME_OVER = "Game over!";
public static final String PLAYER_TURN = "Waiting for the player's move.";
+ public static final Logger LOGGER = Logger.getLogger(Referee.class
+ .getName());
+
private final Board board;
private final HumanPlayer humanPlayer = new HumanPlayer();
- private final Player cc;
+ private final Player computerPlayer;
- private boolean playerTurn;
private BoardPanel boardPanel;
- private int score = 0;
private MessagePanel messagePanel;
+ private ScorePanel scorePanel;
public Referee() {
board = new Board();
- cc = new RandomComPlayer();
- playerTurn = true;
+ computerPlayer = new AlphaBetaComPlayer();
}
@Override
public void run() {
int plies = 0;
while (!isOver()) {
- if (playerTurn) {
+ scorePanel.updateScore(getPlayerScore());
+ if (board.isPlayerTurn()) {
boolean wasInterrupted = false;
while (!humanPlayer.isReady()) {
try {
@@ -39,7 +47,8 @@ public class Referee implements Runnable {
}
}
if (wasInterrupted) {
- System.out.println("Interrupted while waiting for human to move!");
+ System.out
+ .println("Interrupted while waiting for human to move!");
} else {
Move mv = humanPlayer.getMove(board);
if (board.getTile(mv.getCell().r, mv.getCell().c) == TileColor.NONE) {
@@ -49,79 +58,65 @@ public class Referee implements Runnable {
}
}
} else {
- Move mv = cc.getMove(board);
+ Move mv = computerPlayer.getMove(board);
playToken(mv);
}
-
+
messagePanel.updateMessage(getMessage());
boardPanel.updateIcons();
- System.out.println("plies: " + plies++);
+ LOGGER.info("plies: " + plies++);
try {
Thread.sleep(1000L);
} catch (InterruptedException ie) {
- System.out.println("Interrupted while waiting for human view ply!");
+ System.out
+ .println("Interrupted while waiting for human view ply!");
}
}
}
public Player getComputerPlayer() {
- return cc;
+ return computerPlayer;
}
public HumanPlayer getHumanPlayer() {
return humanPlayer;
}
-
+
public String getMessage() {
if (isOver()) {
return GAME_OVER;
- } else if (isPlayersTurn()) {
+ } else if (board.isPlayerTurn()) {
return PLAYER_TURN;
} else {
return COM_TURN;
}
}
-
- public boolean isOver() {
- for (int r = 0; r < Board.NUM_ROWS; r++) {
- for (int c = 0; c < Board.NUM_COLS; c++) {
- if (board.getTile(r, c) == TileColor.NONE) {
- return false;
- }
- }
- }
- return true;
+ public int getPlayerScore() {
+ return board.getTurn();
}
-
- private boolean isPlayersTurn() {
- return playerTurn;
- }
-
- public int getScore() {
- return score;
- }
-
+
public TileColor getTile(int r, int c) {
return board.getTile(r, c);
}
+ public boolean isOver() {
+ return board.isTerminalState();
+ }
+
public void playToken(Move move) {
-
- board.setTile(move.getCell(), move.getColor());
-
- if (playerTurn) {
- score++;
- }
-
- playerTurn = !playerTurn;
+ board.playTile(move.getCell(), move.getColor());
}
public void setBoardPanel(BoardPanel boardPanel) {
this.boardPanel = boardPanel;
}
-
+
public void setMessagePanel(MessagePanel messagePanel) {
this.messagePanel = messagePanel;
}
-}
+
+ public void setScorePanel(ScorePanel scorePanel) {
+ this.scorePanel = scorePanel;
+ }
+}
\ No newline at end of file
diff --git a/src/player/AlphaBetaComPlayer.java b/src/player/AlphaBetaComPlayer.java
new file mode 100644
index 0000000..79a51ed
--- /dev/null
+++ b/src/player/AlphaBetaComPlayer.java
@@ -0,0 +1,25 @@
+package player;
+
+import model.Board;
+import model.Move;
+import player.generator.AlphaBetaMoveGenerator;
+import player.generator.MoveGenerator;
+
+public class AlphaBetaComPlayer implements Player {
+ private MoveGenerator moveGenerator = new AlphaBetaMoveGenerator();
+
+ @Override
+ public Move getMove(Board board) {
+ return moveGenerator.genMove(board, false);
+ }
+
+ @Override
+ public void denyMove() {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public boolean isReady() {
+ return true; // always ready to play a random valid move
+ }
+}
\ No newline at end of file
diff --git a/src/model/HumanPlayer.java b/src/player/HumanPlayer.java
similarity index 95%
rename from src/model/HumanPlayer.java
rename to src/player/HumanPlayer.java
index 5119ab0..ab30ff7 100644
--- a/src/model/HumanPlayer.java
+++ b/src/player/HumanPlayer.java
@@ -1,5 +1,8 @@
-package model;
+package player;
+import model.Board;
+import model.CellPointer;
+import model.Move;
import model.Board.TileColor;
diff --git a/src/player/MinimaxComPlayer.java b/src/player/MinimaxComPlayer.java
new file mode 100644
index 0000000..9f6d5c1
--- /dev/null
+++ b/src/player/MinimaxComPlayer.java
@@ -0,0 +1,5 @@
+package player;
+
+public class MinimaxComPlayer {
+
+}
diff --git a/src/model/MonteCarloComPlayer.java b/src/player/MonteCarloComPlayer.java
similarity index 92%
rename from src/model/MonteCarloComPlayer.java
rename to src/player/MonteCarloComPlayer.java
index d98f87a..061393a 100644
--- a/src/model/MonteCarloComPlayer.java
+++ b/src/player/MonteCarloComPlayer.java
@@ -1,7 +1,10 @@
-package model;
+package player;
import java.util.Random;
+import model.Board;
+import model.CellPointer;
+import model.Move;
import model.Board.TileColor;
diff --git a/src/model/Player.java b/src/player/Player.java
similarity index 87%
rename from src/model/Player.java
rename to src/player/Player.java
index f59d61b..625a77e 100644
--- a/src/model/Player.java
+++ b/src/player/Player.java
@@ -1,4 +1,7 @@
-package model;
+package player;
+
+import model.Board;
+import model.Move;
public interface Player {
diff --git a/src/model/RandomComPlayer.java b/src/player/RandomComPlayer.java
similarity index 91%
rename from src/model/RandomComPlayer.java
rename to src/player/RandomComPlayer.java
index 6143c20..cf791ac 100644
--- a/src/model/RandomComPlayer.java
+++ b/src/player/RandomComPlayer.java
@@ -1,7 +1,10 @@
-package model;
+package player;
import java.util.Random;
+import model.Board;
+import model.CellPointer;
+import model.Move;
import model.Board.TileColor;
diff --git a/src/player/generator/AlphaBetaMoveGenerator.java b/src/player/generator/AlphaBetaMoveGenerator.java
new file mode 100644
index 0000000..8da6ab2
--- /dev/null
+++ b/src/player/generator/AlphaBetaMoveGenerator.java
@@ -0,0 +1,132 @@
+package player.generator;
+
+import java.util.Arrays;
+import java.util.List;
+
+import model.Board;
+import model.BoardScorer;
+import model.Move;
+
+import org.apache.log4j.Logger;
+
+public class AlphaBetaMoveGenerator implements MoveGenerator {
+ 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();
+
+ private Move bestPick = Move.NONE;
+
+ @Override
+ public Move genMove(Board board, boolean asHuman) {
+
+ int alpha = Integer.MIN_VALUE;
+ int beta = Integer.MAX_VALUE;
+
+ if (!asHuman) {
+ getMaxValue(board, asHuman, DEFAULT_RECURSIVE_PLAYS * 2, alpha,
+ beta);
+ } else {
+ getMinValue(board, asHuman, DEFAULT_RECURSIVE_PLAYS * 2, alpha,
+ beta);
+ }
+
+ return bestPick;
+ }
+
+ private int getMaxValue(Board board, boolean asHuman, int recursionLevel,
+ int alpha, int beta) {
+ if (terminalTest(recursionLevel)) {
+ return getUtility(board);
+ }
+
+ List validMoves = validMoveGenerator.genMoves(board, asHuman,
+ MoveGenerator.ALL_MOVES);
+
+ int value = Integer.MIN_VALUE;
+
+ for (Move nextMove : validMoves) {
+ Board nextBoard = new Board(board);
+
+ if (!nextBoard.playTile(nextMove.getCell(), nextMove.getColor())) {
+ throw new RuntimeException(
+ "Illegal move attempted during search!");
+ }
+
+ int minValue = getMinValue(nextBoard, !asHuman, recursionLevel - 1,
+ alpha, beta);
+
+ if (minValue > value) {
+ value = minValue;
+ if (recursionLevel == DEFAULT_RECURSIVE_PLAYS * 2) {
+ bestPick = nextMove;
+ }
+ }
+
+ if (value >= beta) {
+ return value;
+ }
+ alpha = Math.max(alpha, value);
+ }
+
+ return value;
+ }
+
+ private int getMinValue(Board board, boolean asHuman, int recursionLevel,
+ int alpha, int beta) {
+ if (terminalTest(recursionLevel)) {
+ return getUtility(board);
+ }
+
+ List validMoves = validMoveGenerator.genMoves(board, asHuman,
+ MoveGenerator.ALL_MOVES);
+
+ int value = Integer.MAX_VALUE;
+
+ for (Move nextMove : validMoves) {
+ Board nextBoard = new Board(board);
+
+ if (!nextBoard.playTile(nextMove.getCell(), nextMove.getColor())) {
+ throw new RuntimeException(
+ "Illegal move attempted during search!");
+ }
+
+ int maxValue = getMaxValue(nextBoard, !asHuman, recursionLevel - 1,
+ alpha, beta);
+
+ if (maxValue < value) {
+ value = maxValue;
+ if (recursionLevel == 2 * DEFAULT_RECURSIVE_PLAYS) {
+ bestPick = nextMove;
+ }
+ }
+
+ if (value <= alpha) {
+ return value;
+ }
+ beta = Math.min(beta, value);
+ }
+
+ return value;
+ }
+
+ private boolean terminalTest(int recursionLevel) {
+ return recursionLevel < 1;
+ }
+
+ private int getUtility(Board board) {
+ return scorer.getScore(board);
+ }
+
+ /**
+ * AlphaBetaMoveGenerator2 does not support this method.
+ */
+ @Override
+ public List genMoves(Board board, boolean asHuman, int nMoves) {
+ Move[] doNothing = new Move[] { Move.NONE };
+ LOGGER.info("Minimax genMoves() stub returning []");
+ return Arrays.asList(doNothing);
+ }
+}
diff --git a/src/player/generator/GameScore.java b/src/player/generator/GameScore.java
new file mode 100644
index 0000000..e99c281
--- /dev/null
+++ b/src/player/generator/GameScore.java
@@ -0,0 +1,26 @@
+package player.generator;
+
+public class GameScore {
+ boolean terminal;
+ int maxScore;
+ int score;
+
+ public GameScore(boolean terminal, int maxScore, int score) {
+ super();
+ this.terminal = terminal;
+ this.maxScore = maxScore;
+ this.score = score;
+ }
+
+ public boolean isTerminal() {
+ return terminal;
+ }
+
+ public int getMaxScore() {
+ return maxScore;
+ }
+
+ public int getScore() {
+ return score;
+ }
+}
\ No newline at end of file
diff --git a/src/player/generator/MoveCandidate.java b/src/player/generator/MoveCandidate.java
new file mode 100644
index 0000000..562a0d4
--- /dev/null
+++ b/src/player/generator/MoveCandidate.java
@@ -0,0 +1,13 @@
+package player.generator;
+
+import model.Move;
+
+public class MoveCandidate {
+ public final Move move;
+ public final GameScore score;
+
+ public MoveCandidate(Move move, GameScore score) {
+ this.move = move;
+ this.score = score;
+ }
+}
\ No newline at end of file
diff --git a/src/player/generator/MoveGenerator.java b/src/player/generator/MoveGenerator.java
new file mode 100644
index 0000000..ee64b91
--- /dev/null
+++ b/src/player/generator/MoveGenerator.java
@@ -0,0 +1,13 @@
+package player.generator;
+
+import java.util.List;
+
+import model.Board;
+import model.Move;
+
+public interface MoveGenerator {
+ public static final int ALL_MOVES = 0;
+
+ public Move genMove(Board board, boolean asHuman);
+ public List genMoves(Board board, boolean asHuman, int nMoves);
+}
\ No newline at end of file
diff --git a/src/player/generator/ValidMoveGenerator.java b/src/player/generator/ValidMoveGenerator.java
new file mode 100644
index 0000000..cc10c05
--- /dev/null
+++ b/src/player/generator/ValidMoveGenerator.java
@@ -0,0 +1,38 @@
+package player.generator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import model.Board;
+import model.Board.TileColor;
+import model.Move;
+
+import org.apache.log4j.Logger;
+
+public class ValidMoveGenerator implements MoveGenerator {
+ private static final Logger LOGGER = Logger.getLogger(ValidMoveGenerator.class.getName());
+
+ @Override
+ public Move genMove(Board board, boolean asHuman) {
+ LOGGER.info("ValidMoveGenerator genMove() stub returning NONE");
+ return Move.NONE;
+ }
+
+ @Override
+ public List genMoves(Board board, boolean asHuman, int nMoves) {
+
+ 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 (TileColor color : TileColor.values()) {
+ validMoves.add(new Move(color, i, j));
+ }
+ }
+ }
+ }
+
+ return validMoves;
+ }
+}
diff --git a/src/view/MainFrame.java b/src/view/MainFrame.java
index e44320a..ef5fa19 100644
--- a/src/view/MainFrame.java
+++ b/src/view/MainFrame.java
@@ -35,8 +35,9 @@ public class MainFrame extends JFrame {
private void init() {
ScorePanel sp = new ScorePanel(referee);
- TileSelectionPanel tp = new TileSelectionPanel(referee.getHumanPlayer());
+ referee.setScorePanel(sp);
+ TileSelectionPanel tp = new TileSelectionPanel(referee.getHumanPlayer());
BoardPanel bp = new BoardPanel(referee,tp);
referee.setBoardPanel(bp);
@@ -82,7 +83,7 @@ public class MainFrame extends JFrame {
add(vWrapper,BorderLayout.CENTER);
//To ensure correct size, pre-populate the score and message panels with text.
- sp.updateScore();
+ sp.updateScore(0);
mp.updateMessage("Loading new game...");
pack();
diff --git a/src/view/ScorePanel.java b/src/view/ScorePanel.java
index 9e0aa8e..df638df 100644
--- a/src/view/ScorePanel.java
+++ b/src/view/ScorePanel.java
@@ -11,17 +11,15 @@ public class ScorePanel extends JPanel {
private static final long serialVersionUID = 1L;
private final JLabel message = new JLabel();
- private final Referee referee;
-
+
public ScorePanel(Referee ref) {
- referee = ref;
add(message);
}
- public void updateScore() {
+ public void updateScore(final int score) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
- message.setText("Score: " + referee.getScore());
+ message.setText("Score: " + score);
}
});
}
diff --git a/src/view/TileSelectionPanel.java b/src/view/TileSelectionPanel.java
index 852b682..425e09e 100644
--- a/src/view/TileSelectionPanel.java
+++ b/src/view/TileSelectionPanel.java
@@ -9,9 +9,10 @@ import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
+import player.HumanPlayer;
+
import controller.TSPMouseListener;
-import model.HumanPlayer;
import model.Board.TileColor;
public class TileSelectionPanel extends JPanel {
diff --git a/test/model/BoardTest.java b/test/model/BoardTest.java
index d713f71..cb111bc 100644
--- a/test/model/BoardTest.java
+++ b/test/model/BoardTest.java
@@ -11,7 +11,9 @@ public class BoardTest {
@Test
public void testConstructor() {
- new Board();
+ Board board = new Board();
+ assertTrue(board.isPlayerTurn());
+ assertFalse(board.isTerminalState());
}
@Test
@@ -19,10 +21,10 @@ public class BoardTest {
Board board = new Board();
Board copy = new Board(board);
- board.setTile(new CellPointer(1,2), TileColor.BLUE);
+ board.playTile(new CellPointer(1,2), TileColor.BLUE);
assertFalse(board.equals(copy));
- copy.setTile(new CellPointer(1,2),TileColor.BLUE);
+ copy.playTile(new CellPointer(1,2),TileColor.BLUE);
assertTrue(board.equals(copy));
}
}