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:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/model/BoardScorer.java
Normal file
24
src/model/BoardScorer.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user