Alpha-Beta move generator can look 2 plays (4 plies) ahead on a 4x4 board and blocks every possible attempt by the player to connect 3.

It should be possible to play on larger boards when the computers 'move' is changed from playing a tile to picking the player's next available color.
This commit is contained in:
Woody Folsom
2012-04-14 15:36:02 -04:00
parent 74b8eb4622
commit d9ec72d0fb
23 changed files with 396 additions and 69 deletions

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -1,81 +0,0 @@
package model;
import model.Board.TileColor;
public class HumanPlayer implements Player {
private final CellPointer cell = new CellPointer(0, 0);
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.r = row;
cell.c = col;
ready = true;
}
public void setColor(TileColor clr) {
color = clr;
}
}

View File

@@ -1,54 +0,0 @@
package model;
import java.util.Random;
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

@@ -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;

View File

@@ -1,20 +0,0 @@
package model;
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

@@ -1,50 +0,0 @@
package model;
import java.util.Random;
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

@@ -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;
}
}