- Reorganized packages so that the computer agents are inside the model package.

- Added support for the player model inside Referee.java; high scores should now persist over a single execution of the program.
- Refactored PlayerModel.java to support game logging. All games are now logged so that we can track overall progress.
- Added scaffolding to allow saving and importing of PlayerModel.java. It is not yet functional, but it will be with two function implementations and then the appropriate calls.
This commit is contained in:
Marshall
2012-04-28 19:18:28 -04:00
parent 1a4c6d865b
commit a6af6df132
18 changed files with 123 additions and 52 deletions

View File

@@ -1,13 +1,13 @@
package model;
import model.Board.TileColor;
import model.comPlayer.AlphaBetaComPlayer;
import model.comPlayer.HumanPlayer;
import model.comPlayer.Player;
import model.playerModel.PlayerModel;
import org.apache.log4j.Logger;
import player.AlphaBetaComPlayer;
import player.HumanPlayer;
import player.Player;
import view.BoardPanel;
import view.HighScoreDialog;
import view.MainFrame;
@@ -27,10 +27,18 @@ public class Referee implements Runnable {
private Player computerPlayer;
private final HumanPlayer humanPlayer = new HumanPlayer();
private final MainFrame mf;
private final PlayerModel playerModel;
public Referee(MainFrame mnFrm) {
if (PlayerModel.TRY_LOAD && PlayerModel.exists()) {
playerModel = PlayerModel.load();
}
else {
playerModel = new PlayerModel();
}
mf = mnFrm;
initGame();
}
@@ -75,7 +83,8 @@ public class Referee implements Runnable {
initGame();
mf.updateBoard();
play();
new HighScoreDialog(mf, new PlayerModel());
playerModel.logGame(getPlayerScore());
new HighScoreDialog(mf, playerModel);
}
}

View File

@@ -0,0 +1,25 @@
package model.comPlayer;
import model.Board;
import model.Move;
import model.comPlayer.generator.AlphaBetaMoveGenerator;
import model.comPlayer.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
}
}

View File

@@ -0,0 +1,82 @@
package model.comPlayer;
import model.Board;
import model.CellPointer;
import model.Move;
import model.Board.TileColor;
public class HumanPlayer implements Player {
private CellPointer cell = CellPointer.NONE;
private boolean ready = false;
private TileColor color = TileColor.BLUE;
@Override
public void denyMove() {
ready = false;
}
public TileColor getColor() {
return color;
}
@Override
public Move getMove(Board board) {
ready = false;
return new Move(cell, color);
}
public void decrementColor() {
TileColor[] colors = Board.TileColor.values();
int currentColor = -1;
for (int i = 0; i < colors.length; i++) {
if (colors[i] == color) {
currentColor = i;
break;
}
}
if (currentColor == -1) {
throw new RuntimeException("Color not found: " + color + "!");
}
currentColor = (currentColor + colors.length - 1) % colors.length;
if (colors[currentColor] == TileColor.NONE) {
currentColor = (currentColor + colors.length - 1) % colors.length;
}
color = colors[currentColor];
}
public void incrementColor() {
TileColor[] colors = Board.TileColor.values();
int currentColor = -1;
for (int i = 0; i < colors.length; i++) {
if (colors[i] == color) {
currentColor = i;
break;
}
}
if (currentColor == -1) {
throw new RuntimeException("Color not found: " + color + "!");
}
currentColor = (currentColor + 1) % colors.length;
if (colors[currentColor] == TileColor.NONE) {
currentColor = (currentColor + 1) % colors.length;
}
color = colors[currentColor];
}
@Override
public boolean isReady() {
return ready;
}
public void setCell(int row, int col) {
cell = new CellPointer(row, col);
ready = true;
}
public void setColor(TileColor clr) {
color = clr;
}
}

View File

@@ -0,0 +1,25 @@
package model.comPlayer;
import model.Board;
import model.Move;
import model.comPlayer.generator.MinimaxMoveGenerator;
import model.comPlayer.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

@@ -0,0 +1,57 @@
package model.comPlayer;
import java.util.Random;
import model.Board;
import model.CellPointer;
import model.Move;
import model.Board.TileColor;
public class MonteCarloComPlayer implements Player {
private final Random rand = new Random();
@Override
public Move getMove(Board board) {
return getRandomMove(board,true);
}
public Move getRandomMove(Board board, boolean isCompTurn) {
TileColor tile = TileColor.BLUE;
int r = -1;
int c = -1;
while (tile != TileColor.NONE) {
r = rand.nextInt(Board.NUM_ROWS);
c = rand.nextInt(Board.NUM_COLS);
tile = board.getTile(r, c);
}
switch (rand.nextInt(4)) {
case 0:
tile = TileColor.BLUE;
break;
case 1:
tile = TileColor.GREEN;
break;
case 2:
tile = TileColor.RED;
break;
case 3:
tile = TileColor.YELLOW;
}
return new Move(new CellPointer(r, c), tile);
}
@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

@@ -0,0 +1,23 @@
package model.comPlayer;
import model.Board;
import model.Move;
public interface Player {
/**
* Instructs the Player to retract the last move.
*/
public void denyMove();
/**
* Gets the player's move given the board state. Blocks if player is not ready.
* @param board
* @return
*/
public Move getMove(Board board);
/**
* Returns true if the player is ready to make a move (getMove() does not block).
* @return
*/
public boolean isReady();
}

View File

@@ -0,0 +1,53 @@
package model.comPlayer;
import java.util.Random;
import model.Board;
import model.CellPointer;
import model.Move;
import model.Board.TileColor;
public class RandomComPlayer implements Player {
private final Random rand = new Random();
@Override
public Move getMove(Board board) {
TileColor tile = TileColor.BLUE;
int r = -1;
int c = -1;
while (tile != TileColor.NONE) {
r = rand.nextInt(Board.NUM_ROWS);
c = rand.nextInt(Board.NUM_COLS);
tile = board.getTile(r, c);
}
switch (rand.nextInt(4)) {
case 0:
tile = TileColor.BLUE;
break;
case 1:
tile = TileColor.GREEN;
break;
case 2:
tile = TileColor.RED;
break;
case 3:
tile = TileColor.YELLOW;
}
return new Move(new CellPointer(r, c), tile);
}
@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

@@ -0,0 +1,126 @@
package model.comPlayer.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 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();
@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;
} else {
return getMinValue(board, asHuman, DEFAULT_RECURSIVE_PLAYS * 2, Integer.MIN_VALUE,
Integer.MAX_VALUE).move;
}
}
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) {
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.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) {
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);
bestResult = getMin(bestResult,searchResult);
if (bestResult.score <= alpha) {
return bestResult;
}
beta = Math.min(beta, bestResult.score);
}
return bestResult;
}
/**
* 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

@@ -0,0 +1,26 @@
package model.comPlayer.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;
}
}

View File

@@ -0,0 +1,105 @@
package model.comPlayer.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

@@ -0,0 +1,13 @@
package model.comPlayer.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;
}
}

View File

@@ -0,0 +1,13 @@
package model.comPlayer.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<Move> genMoves(Board board, boolean asHuman, int nMoves);
}

View File

@@ -0,0 +1,44 @@
package model.comPlayer.generator;
import java.util.ArrayList;
import java.util.Collections;
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<Move> genMoves(Board board, boolean asHuman, int nMoves) {
List<Move> validMoves = new ArrayList<Move>();
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()) {
if (color == TileColor.NONE) {
continue;
}
validMoves.add(new Move(color, i, j));
}
}
}
}
Collections.shuffle(validMoves);
return validMoves;
}
}

View File

@@ -0,0 +1,32 @@
package model.playerModel;
public class GameLog implements Comparable<GameLog> {
public static boolean SORT_BY_SCORE = true;
private final int gameNum;
private final int score;
public GameLog(int scr, int gmNm) {
score = scr;
gameNum = gmNm;
}
@Override
public int compareTo(GameLog o) {
if (SORT_BY_SCORE) {
return o.getScore() - getScore();
}
else {
return getGameNum() - o.getGameNum();
}
}
public int getGameNum() {
return gameNum;
}
public int getScore() {
return score;
}
}

View File

@@ -1,19 +1,44 @@
package model.playerModel;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
import model.Board;
import model.playerModel.node.InputNode;
import model.playerModel.node.SigmoidNode;
public class PlayerModel {
public class PlayerModel implements Serializable {
public static final Random rand = new Random();
public static final String PLAYER_MODEL_PATH = "playerModel.dat"; // Path to
// the
// stored
// player
// model.
public static final Random rand = new Random(); // Randomizer object.
public static final boolean TRY_LOAD = true; // Set to false if any existing
// player model should be
// discarded and the next
// game should begin a new
// sequence.
private static final long serialVersionUID = 1L;
public static boolean exists() {
return (new File(PLAYER_MODEL_PATH)).exists();
}
public static PlayerModel load() {
// TODO Import the PlayerModel from a file.
return new PlayerModel();
}
private int GAME_COUNTER = 0;
private final SigmoidNode[] hiddenLayer;
private final int[] highScore = new int[10];
private int highScoresAchieved = 0;
// One node for each tile-color combination, plus one for each upcoming
// tile-color combination.
private final InputNode[] inputNode = new InputNode[(Board.NUM_COLS
@@ -34,11 +59,9 @@ public class PlayerModel {
// should be placed.
private final SigmoidNode[] outputNode = new SigmoidNode[(Board.NUM_COLS * Board.NUM_ROWS) + 4];
public PlayerModel() {
for (int i = 0; i < highScore.length; i++) {
highScore[i] = -1;
}
private final ArrayList<GameLog> scores = new ArrayList<GameLog>();
public PlayerModel() {
hiddenLayer = new SigmoidNode[inputNode.length
+ ((inputNode.length * 2) / 3)];
@@ -64,7 +87,23 @@ public class PlayerModel {
}
public int[] getHighScores() {
return highScore;
GameLog.SORT_BY_SCORE = true;
Collections.sort(scores);
int[] highScores = new int[10];
for (int i = 0; i < highScores.length; i++) {
if (i >= scores.size()) {
highScores[i] = -1;
}
else {
highScores[i] = scores.get(i).getScore();
}
}
return highScores;
}
public boolean[] getPrediction(boolean[] input) {
@@ -124,25 +163,15 @@ public class PlayerModel {
}
public void logGame(int score) {
if (highScore[0] == -1) {
highScore[0] = score;
}
else {
loop: for (int i = 0; i < highScore.length; i++) {
if (score > highScore[i]) {
for (int j = highScore.length - 2; j >= i; j--) {
highScore[j + 1] = highScore[j];
}
highScore[i] = score;
break loop;
}
}
}
highScoresAchieved += (score == highScore[0]) ? 1 : 0;
scores.add(new GameLog(score, GAME_COUNTER));
GAME_COUNTER++;
nextHighInGames--;
highScoresAchieved += (score == getHighScore()) ? 1 : 0;
}
public boolean save() {
// TODO Implement saving.
return false;
}
public void train(boolean[] example) {
@@ -154,6 +183,6 @@ public class PlayerModel {
}
private int getHighScore() {
return highScore[0];
return getHighScores()[0];
}
}