/** Copyright by Barry G. Becker, 2000-2011. Licensed under MIT License: http://www.opensource.org/licenses/MIT */
package com.barrybecker4.game.twoplayer.blockade.board;
import com.barrybecker4.common.geometry.Location;
import com.barrybecker4.game.common.AbstractGameProfiler;
import com.barrybecker4.game.common.GameProfiler;
import com.barrybecker4.game.common.Move;
import com.barrybecker4.game.common.board.BoardPosition;
import com.barrybecker4.game.twoplayer.blockade.board.analysis.BoardAnalyzer;
import com.barrybecker4.game.twoplayer.blockade.board.analysis.PossibleMoveAnalyzer;
import com.barrybecker4.game.twoplayer.blockade.board.move.BlockadeMove;
import com.barrybecker4.game.twoplayer.blockade.board.move.wall.BlockadeWall;
import com.barrybecker4.game.twoplayer.blockade.board.move.wall.WallPlacementValidator;
import com.barrybecker4.game.twoplayer.blockade.board.path.PathList;
import com.barrybecker4.game.twoplayer.blockade.board.path.PlayerPathLengths;
import com.barrybecker4.game.twoplayer.common.TwoPlayerBoard;
import java.util.List;
import java.util.Set;
/**
* Defines the structure of the blockade board and the pieces on it.
* Each BlockadeBoardPosition can contain a piece and south and east walls.
*
* @author Barry Becker
*/
public class BlockadeBoard extends TwoPlayerBoard {
/** Home base positions for both players. */
private Homes homes;
private BoardAnalyzer boardAnalyzer;
private WallPlacementValidator wallValidator;
/**
* Constructor.
* @param numRows number of rows in the board grid.
* @param numCols number of rows in the board grid.
*/
public BlockadeBoard(int numRows, int numCols) {
homes = new Homes();
setSize(numRows, numCols);
boardAnalyzer = new BoardAnalyzer(this);
wallValidator = new WallPlacementValidator(this);
}
/** copy constructor */
private BlockadeBoard(BlockadeBoard b) {
super(b);
boardAnalyzer = new BoardAnalyzer(this);
homes = new Homes(b.homes);
}
@Override
public BlockadeBoard copy() {
return new BlockadeBoard(this);
}
@Override
public BlockadeBoardPosition getPosition(int row, int col) {
return (BlockadeBoardPosition) super.getPosition(row, col);
}
@Override
public final BlockadeBoardPosition getPosition( Location loc ) {
return (BlockadeBoardPosition) super.getPosition(loc.getRow(), loc.getCol());
}
/**
* reset the board to its initial state.
*/
@Override
public void reset() {
super.reset();
if (homes == null) {
homes = new Homes();
}
homes.init(getNumRows(), getNumCols());
for (int i=0; i<Homes.NUM_HOMES; i++) {
setPosition(homes.getPlayerHomes(true)[i]);
setPosition(homes.getPlayerHomes(false)[i]);
}
}
@Override
protected BoardPosition getPositionPrototype() {
return new BlockadeBoardPosition(1, 1);
}
/**
* If the Blockade game has more than this many moves, then we assume it is a draw.
* We make this number big, because in blockade it is impossible to have a draw.
* I haven't proved it, but I think it is impossible for the number of moves to exceed
* the rows*cols.
* @return assumed maximum number of moves.
*/
@Override
public int getMaxNumMoves() {
return Integer.MAX_VALUE;
}
/**
* @return player1's home bases.
*/
public BoardPosition[] getPlayerHomes(boolean player1) {
return homes.getPlayerHomes(player1);
}
/**
* Blockade pieces can move 1 or 2 spaces in any direction.
* However, only in rare cases would you ever want to move only 1 space.
* For example, move 1 space to land on a home base, or in preparation to jump an opponent piece.
* They may jump over opponent pieces that are in the way (but they do not capture it).
* The wall is ignored for the purposes of this method.
* Moves are only allowed if the candidate position is unoccupied (unless a home base) and if
* it has not been visited already. The visited part is only significant when we are doing a traversal
* such as when we are finding the shortest paths to home bases.
* <pre>
* # There are at most 12 moves from this position
* #*# (some of course may be blocked by walls)
* #*O*# The most common being marked with #'s.
* #*#
* #
* </pre>
*
* We only add the one space moves if
* 1. jumping 2 spaces in that direction would land on an opponent pawn,
* 2. or moving one space moves lands on an opponent home base.
*
* @param position we are moving from
* @param op1 true if opposing player is player1; false if player2.
* @return a list of legal piece movements
*/
public List<BlockadeMove> getPossibleMoveList(BlockadeBoardPosition position, boolean op1) {
return new PossibleMoveAnalyzer(this).getPossibleMoveList(position, op1);
}
/**
* @param player1 the last player to make a move.
* @return all the opponent's shortest paths to your home bases.
*/
public PathList findAllOpponentShortestPaths(boolean player1) {
return boardAnalyzer.findAllOpponentShortestPaths(player1);
}
/**
* Find the shortest paths from the specified position to opponent homes.
* We use DefaultMutableTreeNodes to represent nodes in the path.
* If the number of paths returned by this method is less than NUM_HOMES,
* then there has been an illegal wall placement, since according to the rules
* of the game there must always be paths from all pieces to all opponent homes.
* If a pawn has reached an opponent home then the path magnitude is 0 and that player won.
*
* @param position position to check shortest paths for.
* @return the NUM_HOMES shortest paths from toPosition.
*/
public PathList findShortestPaths( BlockadeBoardPosition position ) {
return boardAnalyzer.findShortestPaths(position);
}
/*
* It is illegal to place a wall at a position that overlaps
* or intersects another wall, or if the wall prevents one of the pawns from reaching an
* opponent home.
* @param wall to place. has not been placed yet.
* @param location where the wall is to be placed.
* @return an error string if the wall is not a legal placement on the board.
*/
public String checkLegalWallPlacement(BlockadeWall wall, Location location) {
return wallValidator.checkLegalWallPlacement(wall, location, boardAnalyzer);
}
/**
* find all the paths from each player's pawn to each opponent base.
* @return the lengths of all the paths from each player's pawn to each opponent base.
*/
public PlayerPathLengths findPlayerPathLengths() {
return boardAnalyzer.findPlayerPathLengths();
}
public void addWall(BlockadeWall wall) {
showWall(wall, true);
}
public void removeWall(BlockadeWall wall) {
showWall(wall, false);
}
/**
* Shows or hides this wall on the game board.
* @param show whether to show or hide the wall.
*/
private void showWall(BlockadeWall wall, boolean show) {
Set<BlockadeBoardPosition> positions = wall.getPositions();
assert positions.isEmpty() || (positions.size() == 2) : "positions="+positions;
for (BlockadeBoardPosition position : positions) {
// since p may be from a different board, we need to make sure that we set the wall for this board.
BlockadeBoardPosition otherPos = getPosition(position.getLocation());
if (wall.isVertical()) {
assert (!show || !otherPos.isEastBlocked()) : "East blocked already!";
otherPos.setEastWall(show ? wall : null);
} else {
assert (!show || !otherPos.isSouthBlocked()) : "South blocked already!";
otherPos.setSouthWall(show ? wall : null);
}
}
}
private AbstractGameProfiler getProfiler() {
return GameProfiler.getInstance();
}
/**
* Given a move specification, execute it on the board.
* This places the players symbol at the position specified by move,
* and then also places a wall somewhere.
* @return true if the move was made successfully
*/
@Override
protected boolean makeInternalMove( Move move ) {
getProfiler().startMakeMove();
BlockadeMove m = (BlockadeMove) move;
BlockadeBoardPosition toPos = getPosition(m.getToLocation());
// in the rare event that we capture an opponent on his base, remember it so it can be undone.
if (toPos.isOccupiedHomeBase(!m.isPlayer1())) {
m.capturedOpponentPawn = toPos.getPiece();
}
toPos.setPiece(m.getPiece());
// we also need to place a wall.
if (m.getWall() != null) {
addWall(m.getWall());
}
getPosition(m.getFromRow(), m.getFromCol()).clear();
getProfiler().stopMakeMove();
return true;
}
/**
* for Blockade, undoing a move means moving the piece back and
* restoring any captures. It is very rare that there was a capture.
* It can only happen on the final winning move.
*/
@Override
protected void undoInternalMove( Move move ) {
getProfiler().startUndoMove();
BlockadeMove m = (BlockadeMove) move;
BoardPosition startPos = getPosition(m.getFromRow(), m.getFromCol());
startPos.setPiece( m.getPiece() );
BlockadeBoardPosition toPos = getPosition(m.getToLocation());
toPos.clear();
if (m.capturedOpponentPawn != null) {
toPos.setPiece(m.capturedOpponentPawn);
m.capturedOpponentPawn = null;
}
// remove the wall that was placed by this move.
if (m.getWall() != null) {
removeWall(m.getWall());
}
getProfiler().stopUndoMove();
}
/**
* Num different states.
* There are 12 unique states for a position. 4 ways the walls can be arranged around the position.
* @return number of different states this position can have.
*/
@Override
public int getNumPositionStates() {
return 12;
}
/**
* The index of the state for this position.
* @return The index of the state for this position.
*/
@Override
public int getStateIndex(BoardPosition pos) {
return ((BlockadeBoardPosition) pos).getStateIndex();
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder(100);
// print just the walls and pawns
for ( int i = 1; i <= getNumRows(); i++ ) {
for ( int j = 1; j <= getNumCols(); j++ ) {
BlockadeBoardPosition pos = getPosition(i, j);
if (pos.isOccupied())
buf.append(pos).append("\n");
if (pos.getEastWall() != null)
buf.append("East wall at: ").append(i).append(' ').append(j).append('\n');
if (pos.getSouthWall() != null)
buf.append("South wall at: ").append(i).append(' ').append(j).append('\n');
}
}
return buf.toString();
}
}