package com.charlesmadere.android.classygames.models.games.chess; import com.charlesmadere.android.classygames.models.games.Coordinate; import com.charlesmadere.android.classygames.models.games.GenericBoard; import com.charlesmadere.android.classygames.models.games.GenericPiece; import com.charlesmadere.android.classygames.models.games.Position; import org.json.JSONException; import org.json.JSONObject; import java.util.LinkedList; /** * Class representing a Chess board. This board is made up of a bunch of * positions. Chess is 8 by 8, so that's 64 positions. */ public final class Board extends GenericBoard { private final static byte LENGTH_HORIZONTAL = 8; private final static byte LENGTH_VERTICAL = 8; public final static byte BOARD_NORMAL = 0; public final static byte BOARD_CHECK = 1; public final static byte BOARD_CHECKMATE = 2; /** * The castle move can only be performed once per game. The value of this * variable will be true if it has already been used. Otherwise, this will * only be set when the user actually does the castle technique. */ private boolean hasCastled; /** * Creates a Chess board object. * * @throws JSONException * If a glitch or something happened while trying to create some JSON data * then this JSONException will be thrown. When using this particular * constructor this should never happen. */ public Board() throws JSONException { super(LENGTH_HORIZONTAL, LENGTH_VERTICAL); } /** * Creates a Checkers board object using the given JSON String. * * @param boardJSON * JSONObject that represents the board. * * @throws JSONException * If a glitch or something happened while trying to create this JSONObject * then a JSONException will be thrown. */ public Board(final JSONObject boardJSON) throws JSONException { super(LENGTH_HORIZONTAL, LENGTH_VERTICAL, boardJSON); } @Override protected Piece buildPiece(final byte whichTeam, final int type) { return new Piece(whichTeam, type); } @Override public byte checkValidity() { return (byte) 0; } @Override public byte checkValidity(final GenericBoard board) { return (byte) 0; } @Override protected void initializeDefaultBoard() { // player team getPosition(0, 0).setPiece(new Piece(Piece.TEAM_PLAYER, Piece.TYPE_ROOK)); getPosition(1, 0).setPiece(new Piece(Piece.TEAM_PLAYER, Piece.TYPE_KNIGHT)); getPosition(2, 0).setPiece(new Piece(Piece.TEAM_PLAYER, Piece.TYPE_BISHOP)); getPosition(3, 0).setPiece(new Piece(Piece.TEAM_PLAYER, Piece.TYPE_QUEEN)); getPosition(4, 0).setPiece(new Piece(Piece.TEAM_PLAYER, Piece.TYPE_KING)); getPosition(5, 0).setPiece(new Piece(Piece.TEAM_PLAYER, Piece.TYPE_BISHOP)); getPosition(6, 0).setPiece(new Piece(Piece.TEAM_PLAYER, Piece.TYPE_KNIGHT)); getPosition(7, 0).setPiece(new Piece(Piece.TEAM_PLAYER, Piece.TYPE_ROOK)); getPosition(0, 1).setPiece(new Piece(Piece.TEAM_PLAYER)); getPosition(1, 1).setPiece(new Piece(Piece.TEAM_PLAYER)); getPosition(2, 1).setPiece(new Piece(Piece.TEAM_PLAYER)); getPosition(3, 1).setPiece(new Piece(Piece.TEAM_PLAYER)); getPosition(4, 1).setPiece(new Piece(Piece.TEAM_PLAYER)); getPosition(5, 1).setPiece(new Piece(Piece.TEAM_PLAYER)); getPosition(6, 1).setPiece(new Piece(Piece.TEAM_PLAYER)); getPosition(7, 1).setPiece(new Piece(Piece.TEAM_PLAYER)); // opponent team getPosition(0, 7).setPiece(new Piece(Piece.TEAM_OPPONENT, Piece.TYPE_ROOK)); getPosition(1, 7).setPiece(new Piece(Piece.TEAM_OPPONENT, Piece.TYPE_KNIGHT)); getPosition(2, 7).setPiece(new Piece(Piece.TEAM_OPPONENT, Piece.TYPE_BISHOP)); getPosition(3, 7).setPiece(new Piece(Piece.TEAM_OPPONENT, Piece.TYPE_QUEEN)); getPosition(4, 7).setPiece(new Piece(Piece.TEAM_OPPONENT, Piece.TYPE_KING)); getPosition(5, 7).setPiece(new Piece(Piece.TEAM_OPPONENT, Piece.TYPE_BISHOP)); getPosition(6, 7).setPiece(new Piece(Piece.TEAM_OPPONENT, Piece.TYPE_KNIGHT)); getPosition(7, 7).setPiece(new Piece(Piece.TEAM_OPPONENT, Piece.TYPE_ROOK)); getPosition(0, 6).setPiece(new Piece(Piece.TEAM_OPPONENT)); getPosition(1, 6).setPiece(new Piece(Piece.TEAM_OPPONENT)); getPosition(2, 6).setPiece(new Piece(Piece.TEAM_OPPONENT)); getPosition(3, 6).setPiece(new Piece(Piece.TEAM_OPPONENT)); getPosition(4, 6).setPiece(new Piece(Piece.TEAM_OPPONENT)); getPosition(5, 6).setPiece(new Piece(Piece.TEAM_OPPONENT)); getPosition(6, 6).setPiece(new Piece(Piece.TEAM_OPPONENT)); getPosition(7, 6).setPiece(new Piece(Piece.TEAM_OPPONENT)); } @Override public boolean move(final Position previous, final Position current) { boolean isMoveValid = false; if (isBoardLocked) // If the board is locked, then that means that no piece is allowed to // move around. { return false; } else if (previous.hasPiece() && previous.getPiece().isTeamOpponent()) // If the first position that the user selected has an opponent's piece // on it then this is an invalid move. { return false; } else if (previous.hasPiece() && previous.getPiece().isTeamPlayer()) // This is the one way that a chess move can actually be valid. { final Piece piece = (Piece) previous.getPiece(); switch (piece.getType()) { case Piece.TYPE_BISHOP: isMoveValid = isMoveValidBishop(previous, current); break; case Piece.TYPE_KING: isMoveValid = isMoveValidKing(previous, current); break; case Piece.TYPE_KNIGHT: isMoveValid = isMoveValidKnight(previous, current); break; case Piece.TYPE_PAWN: isMoveValid = isMoveValidPawn(previous, current); break; case Piece.TYPE_QUEEN: isMoveValid = isMoveValidQueen(previous, current); break; case Piece.TYPE_ROOK: isMoveValid = isMoveValidRook(previous, current); break; } if (isMoveValid) { current.removePiece(); current.setPiece(new Piece(piece)); previous.removePiece(); hasMoveBeenMade = true; isBoardLocked = true; } } return isMoveValid; } @Override protected void performGameSpecificJSONChecks(final JSONObject boardJSON) throws JSONException { hasCastled = boardJSON.getBoolean("has_castled"); } @Override protected void resetBoard() { } private void addPositionToSurroundingPositionsIfEmpty(final LinkedList<Position> surroundingPositions, final int x, final int y) { if (isPositionValid(x, y)) { final Position position = getPosition(x, y); if (!position.hasPiece() || position.getPiece().isTeamOpponent()) { surroundingPositions.add(position); } } } /** * Performs a check to see if the user can perform a castle. Note this move * can only be performed once per game. More on the castle technique can be * found here: https://en.wikipedia.org/wiki/Chess#Castling * * @return * Returns true if the user can perform a castle. */ public boolean canCastle() { if (hasCastled) { return false; } else { // TODO // Check to see if the castle technique can be used. return false; } } /** * Searches the game board for the opponent's King piece and then returns * the Position that he's on. Returns null if the King piece cannot be * found... but this should never happen. */ private Position findOpponentKing() { for (byte x = 0; x < getLengthHorizontal(); ++x) { for (byte y = 0; y < getLengthVertical(); ++y) { final Position position = getPosition(x, y); if (position.hasPiece()) { final Piece piece = (Piece) position.getPiece(); if (piece.isTeamOpponent() && piece.isTypeKing()) { return position; } } } } return null; } /** * Creates a LinkedList of all Board positions that contain an opponent's * piece on them. This method will never return null! (But it may return an * empty LinkedList.) */ private LinkedList<Position> findOpponentsPieces() { return findTeamsPieces(GenericPiece.TEAM_OPPONENT); } /** * Creates a LinkedList of all Board positions that contain a player's * piece on them. This method will never return null! (But it may return an * empty LinkedList.) */ private LinkedList<Position> findPlayersPieces() { return findTeamsPieces(GenericPiece.TEAM_PLAYER); } /** * Creates a LinkedList of all Board positions * * @param whichTeam * The team to build the LinkedList of pieces for. * * @return * Returns a LinkedList of all of the Pieces belonging to the given team. * This variable is never null! (But it may be empty.) */ private LinkedList<Position> findTeamsPieces(final byte whichTeam) { final LinkedList<Position> teamsPieces = new LinkedList<Position>(); for (byte x = 0; x < getLengthHorizontal(); ++x) { for (byte y = 0; y < getLengthVertical(); ++y) { final Position position = getPosition(x, y); if (position.hasPiece()) { final Piece piece = (Piece) position.getPiece(); if (piece.isTeam(whichTeam)) { teamsPieces.add(position); } } } } return teamsPieces; } /** * Builds a LinkedList of Positions surrounding the given Position that are * either empty or have an enemy Piece on them. * * @return * Returns a LinkedList of Positions that fulfill the above requirement, * will never return null (but could return an empty LinkedList). */ private LinkedList<Position> findSurroundingPositions(final Position positionToCheck) { final LinkedList<Position> surroundingPositions = new LinkedList<Position>(); final Coordinate coordinate = positionToCheck.getCoordinate(); final int originX = (int) coordinate.getX(); final int originY = (int) coordinate.getY(); // top right addPositionToSurroundingPositionsIfEmpty(surroundingPositions, originX + 1, originY + 1); // bottom right addPositionToSurroundingPositionsIfEmpty(surroundingPositions, originX + 1, originY - 1); // top left addPositionToSurroundingPositionsIfEmpty(surroundingPositions, originX - 1, originY + 1); // bottom left addPositionToSurroundingPositionsIfEmpty(surroundingPositions, originX - 1, originY - 1); // right addPositionToSurroundingPositionsIfEmpty(surroundingPositions, originX + 1, originY); // left addPositionToSurroundingPositionsIfEmpty(surroundingPositions, originX - 1, originY); // top addPositionToSurroundingPositionsIfEmpty(surroundingPositions, originX, originY + 1); // bottom addPositionToSurroundingPositionsIfEmpty(surroundingPositions, originX, originY - 1); return surroundingPositions; } /** * Performs a series of checks on the game board to see if the opponent is * in check or checkmate. https://en.wikipedia.org/wiki/Check_(chess) * https://en.wikipedia.org/wiki/Checkmate * * @return * Returns one of this class's BOARD_* bytes depending on the determined * board condition. So there are three different values that could be * returned: BOARD_NORMAL, BOARD_CHECK, or BOARD_CHECKMATE. */ public byte isBoardInCheckOrCheckmate() { byte boardStatus = BOARD_NORMAL; final Position opponentKingPosition = findOpponentKing(); final LinkedList<Position> playersPositions = findPlayersPieces(); final LinkedList<Position> playerPositionsThatCanHit = new LinkedList<Position>(); for (final Position position : playersPositions) // Finds all of the player's pieces that can hit the opponent's King. { boolean canHit = false; switch (position.getPiece().getType()) { case Piece.TYPE_BISHOP: canHit = isMoveValidBishop(position, opponentKingPosition); break; case Piece.TYPE_KING: canHit = isMoveValidKing(position, opponentKingPosition); break; case Piece.TYPE_KNIGHT: canHit = isMoveValidKnight(position, opponentKingPosition); break; case Piece.TYPE_PAWN: canHit = isMoveValidPawn(position, opponentKingPosition); break; case Piece.TYPE_QUEEN: canHit = isMoveValidQueen(position, opponentKingPosition); break; case Piece.TYPE_ROOK: canHit = isMoveValidRook(position, opponentKingPosition); break; } if (canHit) { playerPositionsThatCanHit.add(position); } } if (!playerPositionsThatCanHit.isEmpty()) // There are player pieces that can hit the opponent's King, so we're // gonna do some further checking to see whether or not the opponent's // King is in check or checkmate. { // build a LinkedList of all of the Positions that have opponent // Pieces on them final LinkedList<Position> opponentsPositions = findOpponentsPieces(); for (final Position position : opponentsPositions) { for (int i = 0; i < playersPositions.size(); ) { final Position playersPosition = playersPositions.get(i); boolean canHit = false; // Loop through all of the players pieces that can hit the // opponent King and see if the enemy team's pieces switch (position.getPiece().getType()) { case Piece.TYPE_BISHOP: canHit = isMoveValidBishop(position, playersPosition, GenericPiece.TEAM_PLAYER); break; case Piece.TYPE_KING: canHit = isMoveValidKing(position, playersPosition, GenericPiece.TEAM_PLAYER); break; case Piece.TYPE_KNIGHT: canHit = isMoveValidKnight(position, playersPosition, GenericPiece.TEAM_PLAYER); break; case Piece.TYPE_PAWN: canHit = isMoveValidPawn(position, playersPosition, GenericPiece.TEAM_PLAYER); break; case Piece.TYPE_QUEEN: canHit = isMoveValidQueen(position, playersPosition, GenericPiece.TEAM_PLAYER); break; case Piece.TYPE_ROOK: canHit = isMoveValidRook(position, playersPosition, GenericPiece.TEAM_PLAYER); break; } if (canHit) { playersPositions.remove(i); } else { ++i; } } } if (!playersPositions.isEmpty()) { // TODO // there are player pieces that can hit the king, check if // there are any enemy pieces that can hit these players } } return boardStatus; } private boolean isMoveValidBishop(final Position previous, final Position current) { return isMoveValidBishop(previous, current, GenericPiece.TEAM_OPPONENT); } private boolean isMoveValidBishop(final Position previous, final Position current, final byte whichTeam) { final Coordinate start = previous.getCoordinate(); final int startX = (int) start.getX(); final int startY = (int) start.getY(); final Coordinate end = current.getCoordinate(); final int endX = (int) end.getX(); final int endY = (int) end.getY(); boolean isMoveValid = false; if (!isMovingThroughPiecesBishop(previous, current) && (Math.abs(startX - endX) == Math.abs(startY - endY))) { if (current.hasPiece()) { final Piece piece = (Piece) current.getPiece(); if (piece.isTeam(whichTeam) && !piece.isTypeKing()) { isMoveValid = true; } } else { isMoveValid = true; } } return isMoveValid; } private boolean isMoveValidKing(final Position previous, final Position current) { return isMoveValidKing(previous, current, GenericPiece.TEAM_OPPONENT); } private boolean isMoveValidKing(final Position previous, final Position current, final byte whichTeam) { final Coordinate start = previous.getCoordinate(); final int startX = (int) start.getX(); final int startY = (int) start.getY(); final Coordinate end = current.getCoordinate(); final int endX = (int) end.getX(); final int endY = (int) end.getY(); boolean isMoveValid = false; if ((Math.abs(startX - endX) == 1 && Math.abs(startY - endY) == 1) || (Math.abs(startX - endX) == 1 && Math.abs(startY - endY) == 0) || (Math.abs(startX - endX) == 0 && Math.abs(startY - endY) == 1)) { if (current.hasPiece()) { final Piece piece = (Piece) current.getPiece(); if (piece.isTeam(whichTeam) && !piece.isTypeKing()) { isMoveValid = true; } } else { isMoveValid = true; } } return isMoveValid; } private boolean isMoveValidKnight(final Position previous, final Position current) { return isMoveValidKnight(previous, current, GenericPiece.TEAM_OPPONENT); } private boolean isMoveValidKnight(final Position previous, final Position current, final byte whichTeam) { final Coordinate start = previous.getCoordinate(); final int startX = (int) start.getX(); final int startY = (int) start.getY(); final Coordinate end = current.getCoordinate(); final int endX = (int) end.getX(); final int endY = (int) end.getY(); boolean isMoveValid = false; if ((Math.abs(startX - endX) == 1 && Math.abs(startY - endY) == 2) || (Math.abs(startX - endX) == 2 && Math.abs(startY - endY) == 1)) { if (current.hasPiece()) { final Piece piece = (Piece) current.getPiece(); if (piece.isTeam(whichTeam) && !piece.isTypeKing()) { isMoveValid = true; } } else { isMoveValid = true; } } return isMoveValid; } private boolean isMoveValidPawn(final Position previous, final Position current) { return isMoveValidPawn(previous, current, GenericPiece.TEAM_OPPONENT); } private boolean isMoveValidPawn(final Position previous, final Position current, final byte whichTeam) { final Coordinate start = previous.getCoordinate(); final int startX = (int) start.getX(); final int startY = (int) start.getY(); final Coordinate end = current.getCoordinate(); final int endX = (int) end.getX(); final int endY = (int) end.getY(); boolean isMoveValid = false; if (endY > startY) { if (startX == endX && !current.hasPiece()) { if (endY - startY == 1) { isMoveValid = true; } else if (startY == 1 && endY - startY == 2 && !isMovingThroughPiecesPawn(previous, current)) { isMoveValid = true; } } else if (Math.abs(endX - startX) == 1 && endY - startY == 1) { if (current.hasPiece()) { final Piece p = (Piece) current.getPiece(); if (p.isTeam(whichTeam) && !p.isTypeKing()) { isMoveValid = true; } } } } return isMoveValid; } private boolean isMoveValidQueen(final Position previous, final Position current) { return isMoveValidQueen(previous, current, GenericPiece.TEAM_OPPONENT); } private boolean isMoveValidQueen(final Position previous, final Position current, final byte whichTeam) { return isMoveValidBishop(previous, current, whichTeam) || isMoveValidRook(previous, current, whichTeam); } private boolean isMoveValidRook(final Position previous, final Position current) { return isMoveValidRook(previous, current, GenericPiece.TEAM_OPPONENT); } private boolean isMoveValidRook(final Position previous, final Position current, final byte whichTeam) { final Coordinate start = previous.getCoordinate(); final int startX = (int) start.getX(); final int startY = (int) start.getY(); final Coordinate end = current.getCoordinate(); final int endX = (int) end.getX(); final int endY = (int) end.getY(); boolean isMoveValid = false; if (!isMovingThroughPiecesRook(previous, current) && (startX == endX && startY != endY) || (startX != endX && startY == endY)) { if (current.hasPiece()) { final Piece piece = (Piece) current.getPiece(); if (piece.isTeam(whichTeam) && !piece.isTypeKing()) { isMoveValid = true; } } else { isMoveValid = true; } } return isMoveValid; } /** * Checks to see if the Piece at Position previous has to move through any * other pieces on the Chess board in order to arrive at Position current. * This algorithm does not check for the existence of a Piece at Position * current. * * @param previous * The Position that the Piece is trying to move from. This absolutely can * not be null and must also have a Piece object associated with it. * * @param current * The Position on the game board that the given Piece is attempting to * travel to. This absolutely can not be null! It's fine if this Position * does or does not have a Piece object associated with it. * * @return * Returns true if the Piece at Position previous has to move through other * pieces on the game board to arrive at Position current. */ private boolean isMovingThroughPiecesBishop(final Position previous, final Position current) { final Coordinate start = previous.getCoordinate(); final byte startX = start.getX(); final byte startY = start.getY(); final Coordinate end = current.getCoordinate(); final byte endX = end.getX(); final byte endY = end.getY(); if (endX > startX) // bishop is moving right { byte currentX = startX; byte currentY = startY; Position p; if (endY > startY) // bishop is moving right-up { while (++currentX < endX && ++currentY < endY) { p = getPosition(currentX, currentY); if (p.hasPiece()) { return true; } } } else if (startY > endY) // bishop is moving right-down { while (++currentX < endX && --currentY > endY) { p = getPosition(currentX, currentY); if (p.hasPiece()) { return true; } } } } else if (startX > endX) // bishop is moving left { byte currentX = startX; byte currentY = startY; Position p; if (endY > startY) // bishop is moving left-up { while (--currentX > endX && ++currentY < endY) { p = getPosition(currentX, currentY); if (p.hasPiece()) { return true; } } } else if (startY > endY) // bishop is moving left-down { while (--currentX > endX && --currentY > endY) { p = getPosition(currentX, currentY); if (p.hasPiece()) { return true; } } } } return false; } /** * Checks to see if the Piece at Position previous has to move through any * other pieces on the Chess board in order to arrive at Position current. * This algorithm does not check for the existence of a Piece at Position * current. * * @param previous * The Position that the Piece is trying to move from. This absolutely can * not be null and must also have a Piece object associated with it. * * @param current * The Position on the game board that the given Piece is attempting to * travel to. This absolutely can not be null! It's fine if this Position * does or does not have a Piece object associated with it. * * @return * Returns true if the Piece at Position previous has to move through other * pieces on the game board to arrive at Position current. */ private boolean isMovingThroughPiecesPawn(final Position previous, final Position current) { final Coordinate start = previous.getCoordinate(); final int startX = (int) start.getX(); final int startY = (int) start.getY(); final Coordinate end = current.getCoordinate(); final int endX = (int) end.getX(); final int endY = (int) end.getY(); boolean isMovingThroughPieces = false; if (startX == endX && endY - startY == 2) { final Position position = getPosition(startX, endY - 1); if (position.hasPiece()) { isMovingThroughPieces = true; } } return isMovingThroughPieces; } /** * Checks to see if the Piece at Position previous has to move through any * other pieces on the Chess board in order to arrive at Position current. * This algorithm does not check for the existence of a Piece at Position * current. * * @param previous * The Position that the Piece is trying to move from. This absolutely can * not be null and must also have a Piece object associated with it. * * @param current * The Position on the game board that the given Piece is attempting to * travel to. This absolutely can not be null! It's fine if this Position * does or does not have a Piece object associated with it. * * @return * Returns true if the Piece at Position previous has to move through other * pieces on the game board to arrive at Position current. */ private boolean isMovingThroughPiecesRook(final Position previous, final Position current) { final Coordinate start = previous.getCoordinate(); final byte startX = start.getX(); final byte startY = start.getY(); final Coordinate end = current.getCoordinate(); final byte endX = end.getX(); final byte endY = end.getY(); if (startX == endX) // rook is moving vertically { byte currentY = startY; Position p; if (startY < endY) // rook is moving up { while (++currentY < endY) { p = getPosition(startX, currentY); if (p.hasPiece()) { return true; } } } else if (startY > endY) // rook is moving down { while (--currentY > endY) { p = getPosition(startX, currentY); if (p.hasPiece()) { return true; } } } } else if (startY == endY) // rook is moving horizontally { byte currentX = startX; Position p; if (startX < endX) // rook is moving right { while (++currentX < endX) { p = getPosition(currentX, startY); if (p.hasPiece()) { return true; } } } else if (startX > endX) // rook is moving left { while (--currentX > endX) { p = getPosition(currentX, startY); if (p.hasPiece()) { return true; } } } } return false; } }