public void undoLastMove() {
    // Check that we have a move to undo
    if (moves.isEmpty()) {
      throw new InternalError("No move available to undo");
    }

    final Move lastMove = getLastMove();
    final Point from = lastMove.getFrom();
    final Point to = lastMove.getTo();
    final Piece piece = lastMove.getPiece();

    squares[from.getX()][from.getY()].setPiece(piece);

    if (lastMove.isLeftCastling()) {
      squares[to.getX()][to.getY()].setPiece(null);
      if (piece.getColor() == Color.W) {
        setPiece(Piece.WHITE_ROOK, King.W_LEFT_ROOK);
        setPiece(null, to.incrementX(1));
      } else {
        setPiece(Piece.BLACK_ROOK, King.B_LEFT_ROOK);
        setPiece(null, to.incrementX(1));
      }
    } else if (lastMove.isRightCastling()) {
      squares[to.getX()][to.getY()].setPiece(null);
      if (piece.getColor() == Color.W) {
        setPiece(Piece.WHITE_ROOK, King.W_RIGHT_ROOK);
        setPiece(null, to.decrementX(1));
      } else {
        setPiece(Piece.BLACK_ROOK, King.B_RIGHT_ROOK);
        setPiece(null, to.decrementX(1));
      }
    } else {
      final Piece captured = lastMove.getCaptured();

      // Undoing an en passant move?
      if (lastMove.isEnPassant()) {
        if (captured.getColor() == Color.B) {
          squares[to.getX()][to.getY() - 1].setPiece(captured);
        } else {
          squares[to.getX()][to.getY() + 1].setPiece(captured);
        }
        squares[to.getX()][to.getY()].setPiece(null);
      } else {
        squares[to.getX()][to.getY()].setPiece(captured);
      }

      // Keep track of the kings
      if (piece == WHITE_KING) {
        whiteKing = squares[from.getX()][from.getY()];
      } else if (piece == BLACK_KING) {
        blackKing = squares[from.getX()][from.getY()];
      }
    }

    // Remove move from history
    moves.remove(moves.size() - 1);
  }