Merge branch 'master' of woodyfolsom.net:/opt/git/cs8803p4

This commit is contained in:
Marshall
2012-04-26 11:18:06 -04:00
12 changed files with 297 additions and 102 deletions

View File

@@ -26,7 +26,6 @@ public class BoardPanelMouseListener implements MouseListener, MouseWheelListene
public void mouseClicked(MouseEvent e) { public void mouseClicked(MouseEvent e) {
Tile tile = (Tile) e.getComponent(); Tile tile = (Tile) e.getComponent();
humanPlayer.setCell(tile.getRow(), tile.getCol()); humanPlayer.setCell(tile.getRow(), tile.getCol());
System.out.println("mouseClicked() " + (clickNum++));
} }
@Override @Override

View File

@@ -8,8 +8,8 @@ public class Board {
BLUE, GREEN, NONE, RED, YELLOW BLUE, GREEN, NONE, RED, YELLOW
} }
public static final int NUM_COLS = 4; public static final int NUM_COLS = 5;
public static final int NUM_ROWS = 4; public static final int NUM_ROWS = 5;
public static final int ROW_REMOVAL_SIZE = 3; public static final int ROW_REMOVAL_SIZE = 3;
private final TileColor[][] board; private final TileColor[][] board;
@@ -20,9 +20,9 @@ public class Board {
public Board(Board that) { public Board(Board that) {
board = new TileColor[NUM_ROWS][NUM_COLS]; board = new TileColor[NUM_ROWS][NUM_COLS];
for (int i = 0; i < NUM_COLS; i++) { for (int r = 0; r < NUM_ROWS; r++) {
for (int j = 0; j < NUM_ROWS; j++) { for (int c = 0; c < NUM_COLS; c++) {
this.board[j][i] = that.board[j][i]; this.board[r][c] = that.board[r][c];
} }
} }
} }
@@ -230,4 +230,18 @@ public class Board {
numPlies++; numPlies++;
return true; return true;
} }
@Override
public String toString() {
StringBuilder sb1 = new StringBuilder();
for (int r = 0; r < NUM_ROWS; r++) {
StringBuilder sb2 = new StringBuilder();
for (int c = 0; c < NUM_COLS; c++) {
sb2.append(board[r][c].toString().charAt(0));
}
sb1.append(sb2);
sb1.append("\n");
}
return sb1.toString();
}
} }

View File

@@ -1,11 +1,18 @@
package model; package model;
public class CellPointer { public class CellPointer {
public static final CellPointer NONE = new CellPointer(-1,-1);
public final int c;
public final int r;
public CellPointer(int row, int col) { public CellPointer(int row, int col) {
r = row; this.r = row;
c = col; this.c = col;
} }
public int r; @Override
public int c; public String toString() {
} return "(" + r + "," + c +")";
}
}

View File

@@ -28,4 +28,9 @@ public class Move {
public TileColor getColor() { public TileColor getColor() {
return color; return color;
} }
@Override
public String toString() {
return "Play " + color + "@" + cp;
}
} }

View File

@@ -1,14 +1,16 @@
package model; package model;
import model.Board.TileColor;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import player.AlphaBetaComPlayer; import player.AlphaBetaComPlayer;
import player.HumanPlayer; import player.HumanPlayer;
import player.MinimaxComPlayer;
import player.Player; import player.Player;
import view.BoardPanel; import view.BoardPanel;
import view.MessagePanel; import view.MessagePanel;
import view.ScorePanel; import view.ScorePanel;
import model.Board.TileColor;
public class Referee implements Runnable { public class Referee implements Runnable {

View File

@@ -0,0 +1,16 @@
package model;
public class SearchResult {
public final Move move;
public final int score;
public SearchResult(Move move, int score) {
this.move = move;
this.score = score;
}
@Override
public String toString() {
return move + ", score: " + score;
}
}

View File

@@ -7,7 +7,7 @@ import model.Board.TileColor;
public class HumanPlayer implements Player { public class HumanPlayer implements Player {
private final CellPointer cell = new CellPointer(0, 0); private CellPointer cell = CellPointer.NONE;
private boolean ready = false; private boolean ready = false;
private TileColor color = TileColor.BLUE; private TileColor color = TileColor.BLUE;
@@ -72,9 +72,7 @@ public class HumanPlayer implements Player {
} }
public void setCell(int row, int col) { public void setCell(int row, int col) {
cell.r = row; cell = new CellPointer(row, col);
cell.c = col;
ready = true; ready = true;
} }

View File

@@ -1,5 +1,25 @@
package player; package player;
public class MinimaxComPlayer { import model.Board;
import model.Move;
import player.generator.MinimaxMoveGenerator;
import player.generator.MoveGenerator;
public class MinimaxComPlayer implements Player{
private MoveGenerator moveGenerator = new MinimaxMoveGenerator();
@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
}
} }

View File

@@ -6,46 +6,90 @@ import java.util.List;
import model.Board; import model.Board;
import model.BoardScorer; import model.BoardScorer;
import model.Move; import model.Move;
import model.SearchResult;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
public class AlphaBetaMoveGenerator implements MoveGenerator { public class AlphaBetaMoveGenerator implements MoveGenerator {
private static final Logger LOGGER = Logger private static final Logger LOGGER = Logger
.getLogger(AlphaBetaMoveGenerator.class.getName()); .getLogger(AlphaBetaMoveGenerator.class.getName());
private static final int DEFAULT_RECURSIVE_PLAYS = 3; private static final int DEFAULT_RECURSIVE_PLAYS = 2;
private final BoardScorer scorer = new BoardScorer(); private final BoardScorer scorer = new BoardScorer();
private final ValidMoveGenerator validMoveGenerator = new ValidMoveGenerator(); private final ValidMoveGenerator validMoveGenerator = new ValidMoveGenerator();
private Move bestPick = Move.NONE;
@Override @Override
public Move genMove(Board board, boolean asHuman) { public Move genMove(Board board, boolean asHuman) {
int alpha = Integer.MIN_VALUE;
int beta = Integer.MAX_VALUE;
if (!asHuman) { if (!asHuman) {
getMaxValue(board, asHuman, DEFAULT_RECURSIVE_PLAYS * 2, alpha, return getMaxValue(board, asHuman, DEFAULT_RECURSIVE_PLAYS * 2, Integer.MIN_VALUE,
beta); Integer.MAX_VALUE).move;
} else { } else {
getMinValue(board, asHuman, DEFAULT_RECURSIVE_PLAYS * 2, alpha, return getMinValue(board, asHuman, DEFAULT_RECURSIVE_PLAYS * 2, Integer.MIN_VALUE,
beta); Integer.MAX_VALUE).move;
} }
return bestPick;
} }
private int getMaxValue(Board board, boolean asHuman, int recursionLevel, private SearchResult getMax(SearchResult sr1, SearchResult sr2) {
if (sr1.score >= sr2.score) {
return sr1;
} else {
return sr2;
}
}
private SearchResult getMin(SearchResult sr1, SearchResult sr2) {
if (sr1.score <= sr2.score) {
return sr1;
} else {
return sr2;
}
}
private SearchResult getMaxValue(Board board, boolean asHuman, int recursionLevel,
int alpha, int beta) { int alpha, int beta) {
if (terminalTest(recursionLevel)) { if (recursionLevel < 1) {
return getUtility(board); return new SearchResult(Move.NONE,scorer.getScore(board));
} }
List<Move> validMoves = validMoveGenerator.genMoves(board, asHuman, List<Move> validMoves = validMoveGenerator.genMoves(board, asHuman,
MoveGenerator.ALL_MOVES); MoveGenerator.ALL_MOVES);
int value = Integer.MIN_VALUE; SearchResult bestResult = new SearchResult(Move.NONE,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!");
}
SearchResult searchResult = new SearchResult(nextMove,getMinValue(nextBoard, !asHuman, recursionLevel - 1,
alpha, beta).score);
bestResult = getMax(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<Move> validMoves = validMoveGenerator.genMoves(board, asHuman,
MoveGenerator.ALL_MOVES);
SearchResult bestResult = new SearchResult(Move.NONE,Integer.MAX_VALUE);
for (Move nextMove : validMoves) { for (Move nextMove : validMoves) {
Board nextBoard = new Board(board); Board nextBoard = new Board(board);
@@ -55,69 +99,19 @@ public class AlphaBetaMoveGenerator implements MoveGenerator {
"Illegal move attempted during search!"); "Illegal move attempted during search!");
} }
int minValue = getMinValue(nextBoard, !asHuman, recursionLevel - 1, SearchResult searchResult = new SearchResult(nextMove,getMaxValue(nextBoard, !asHuman, recursionLevel - 1,
alpha, beta); alpha, beta).score);
if (minValue > value) { bestResult = getMin(bestResult,searchResult);
value = minValue;
if (recursionLevel == DEFAULT_RECURSIVE_PLAYS * 2) {
bestPick = nextMove;
}
}
if (value >= beta) { if (bestResult.score <= alpha) {
return value; return bestResult;
} }
alpha = Math.max(alpha, value);
beta = Math.min(beta, bestResult.score);
} }
return value; return bestResult;
}
private int getMinValue(Board board, boolean asHuman, int recursionLevel,
int alpha, int beta) {
if (terminalTest(recursionLevel)) {
return getUtility(board);
}
List<Move> 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);
} }
/** /**
@@ -129,4 +123,4 @@ public class AlphaBetaMoveGenerator implements MoveGenerator {
LOGGER.info("Minimax genMoves() stub returning []"); LOGGER.info("Minimax genMoves() stub returning []");
return Arrays.asList(doNothing); return Arrays.asList(doNothing);
} }
} }

View File

@@ -0,0 +1,105 @@
package player.generator;
import java.util.Arrays;
import java.util.List;
import model.Board;
import model.BoardScorer;
import model.Move;
import model.SearchResult;
import org.apache.log4j.Logger;
public class MinimaxMoveGenerator implements MoveGenerator {
private static final Logger LOGGER = Logger
.getLogger(MinimaxMoveGenerator.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) {
//asHuman may appear redundant, but is for later use when the com and human
//have different types of moves.
SearchResult searchResult;
if (!asHuman) {
searchResult = getMaxValue(board, Move.NONE, asHuman, DEFAULT_RECURSIVE_PLAYS * 2);
} else {
searchResult = getMinValue(board, Move.NONE, asHuman, DEFAULT_RECURSIVE_PLAYS * 2);
}
System.out.println(searchResult);
return searchResult.move;
}
private SearchResult getMaxValue(Board board, Move lastMove, boolean asHuman, int recursionLevel) {
if (terminalTest(recursionLevel)) {
return new SearchResult(lastMove,scorer.getScore(board));
}
List<Move> validMoves = validMoveGenerator.genMoves(board, asHuman,
MoveGenerator.ALL_MOVES);
SearchResult bestResult = new SearchResult(Move.NONE,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!");
}
SearchResult searchResult = getMinValue(nextBoard, nextMove, !asHuman, recursionLevel - 1);
if (searchResult.score > bestResult.score) {
bestResult = new SearchResult(nextMove,searchResult.score);
}
}
return bestResult;
}
private SearchResult getMinValue(Board board, Move lastMove, boolean asHuman, int recursionLevel) {
if (terminalTest(recursionLevel)) {
return new SearchResult(lastMove,scorer.getScore(board));
}
List<Move> validMoves = validMoveGenerator.genMoves(board, asHuman,
MoveGenerator.ALL_MOVES);
SearchResult bestResult = new SearchResult(Move.NONE,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!");
}
SearchResult searchResult = getMaxValue(nextBoard, nextMove,!asHuman, recursionLevel - 1);
if (searchResult.score < bestResult.score) {
bestResult = new SearchResult(nextMove,searchResult.score);
}
}
return bestResult;
}
private boolean terminalTest(int recursionLevel) {
return recursionLevel < 1;
}
/**
* AlphaBetaMoveGenerator2 does not support this method.
*/
@Override
public List<Move> genMoves(Board board, boolean asHuman, int nMoves) {
Move[] doNothing = new Move[] { Move.NONE };
LOGGER.info("Minimax genMoves() stub returning []");
return Arrays.asList(doNothing);
}
}

View File

@@ -1,6 +1,7 @@
package player.generator; package player.generator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import model.Board; import model.Board;
@@ -10,8 +11,9 @@ import model.Move;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
public class ValidMoveGenerator implements MoveGenerator { public class ValidMoveGenerator implements MoveGenerator {
private static final Logger LOGGER = Logger.getLogger(ValidMoveGenerator.class.getName()); private static final Logger LOGGER = Logger
.getLogger(ValidMoveGenerator.class.getName());
@Override @Override
public Move genMove(Board board, boolean asHuman) { public Move genMove(Board board, boolean asHuman) {
LOGGER.info("ValidMoveGenerator genMove() stub returning NONE"); LOGGER.info("ValidMoveGenerator genMove() stub returning NONE");
@@ -20,22 +22,23 @@ public class ValidMoveGenerator implements MoveGenerator {
@Override @Override
public List<Move> genMoves(Board board, boolean asHuman, int nMoves) { public List<Move> genMoves(Board board, boolean asHuman, int nMoves) {
List<Move> validMoves = new ArrayList<Move>(); List<Move> validMoves = new ArrayList<Move>();
for (int i = 0; i < Board.NUM_ROWS; i++) { for (int i = 0; i < Board.NUM_ROWS; i++) {
for (int j = 0; j < Board.NUM_COLS; j++) { for (int j = 0; j < Board.NUM_COLS; j++) {
if (board.getTile(i, j) == TileColor.NONE) { if (board.getTile(i, j) == TileColor.NONE) {
for (TileColor color : TileColor.values()) { for (TileColor color : TileColor.values()) {
if (color == TileColor.NONE) { if (color == TileColor.NONE) {
continue; continue;
} }
validMoves.add(new Move(color, i, j)); validMoves.add(new Move(color, i, j));
} }
} }
} }
} }
Collections.shuffle(validMoves);
return validMoves; return validMoves;
} }
} }

View File

@@ -0,0 +1,32 @@
package model;
import static org.junit.Assert.assertEquals;
import model.Board.TileColor;
import org.junit.Test;
public class BoardScorerTest {
@Test
public void testScore() {
Board board = new Board();
board.playTile(new CellPointer(0,0), TileColor.BLUE);
board.playTile(new CellPointer(0,1), TileColor.GREEN);
board.playTile(new CellPointer(1,0), TileColor.BLUE);
board.playTile(new CellPointer(2,0), TileColor.GREEN);
board.playTile(new CellPointer(1,1), TileColor.BLUE);
board.playTile(new CellPointer(0,2), TileColor.BLUE);
board.playTile(new CellPointer(3,0), TileColor.BLUE);
board.playTile(new CellPointer(0,3), TileColor.BLUE);
BoardScorer boardScorer = new BoardScorer();
System.out.println("Board 1: ");
System.out.println(board);
assertEquals(8,boardScorer.getScore(board));
board.playTile(new CellPointer(1,2), TileColor.BLUE);
System.out.println("Board 2: ");
System.out.println(board);
assertEquals(6,boardScorer.getScore(board));
}
}