I wrote a computer program and it showed that $18$ moves is the optimum.
Here is one such solution:
bbbb bbbb .bbb .bbb .bb. .bb.
.... .... b... bw.. bw.. b..w
.... w... w... w... w..b w..b
wwww .www .www ..ww ..ww ..ww
..b. ..bw .b.w .bww .bww .bww
b..w b... b... b... b... b...
w..b w..b w..b w..b w..b w..b
.bww .bww .bww .b.w b..w b.w.
..ww .w.w .w.w .www .www wwww wwww
b... b... b... b... ..b. ..b. ....
w..b w..b w... w... w... .... ....
bbw. bbw. bbwb bb.b bb.b bb.b bbbb
Oddly enough, even if you relax the condition of alternating white and black moves, it cannot be done in fewer moves.
For $3\times3$ the optimal number of moves is $16$.
bbb bbb bb. bb. .b. .b. ... w..
... w.. w.b .wb .wb w.b wbb .bb
www .ww .ww .ww bww bww bww bww
w.. ww. ww. www www ww. ww. www www
b.b b.b bb. bb. bb. bbw b.w b.. ...
bww b.w b.w b.. ..b ..b .bb .bb bbb
Without the need to alternate moves the optimum is $14$ moves, for example just by doing the above solution excluding white's last two moves.
Here is the C# source code that I wrote.
using System;
using System.Collections.Generic;
namespace test
{
class Rooks
{
static void Main()
{
Calc(true,4);
}
static void Calc(bool alternateMoves, int n )
{
int[] dirs = {0, 1, 0, -1, 1, 0, -1, 0};
List<String> list = new List<String>();
Dictionary<String, String> dict = new Dictionary<String, String>();
string start = new string('b', n) + new string('.', n * (n - 2)) + new string('w', n);
if (alternateMoves) start += '0';
string goal = new string('w', n) + new string('.', n * (n - 2)) + new string('b', n);
list.Add(start);
dict.Add(start, "");
int n1 = list.Count;
int n2 = 0;
int len = 0;
while (list.Count > 0)
{
String p = list[0];
list.RemoveAt(0);
n1--;
String gen = dict[p];
char player = alternateMoves ? (p[n * n] == '0' ? 'w' : 'b') : '.';
for (int y = 0; y < n; y++)
{
for (int x = 0; x < n; x++)
{
if (!alternateMoves ^ p[y * n + x] == player)
{
for (int d = 0; d < 4; d++)
{
int dx = dirs[d + d];
int dy = dirs[d + d + 1];
int x2 = x;
int y2 = y;
while (true)
{
x2 += dx;
y2 += dy;
if (y2 < 0 || x2 < 0 || y2 >= n || x2 >= n || p[y2 * n + x2] != '.') break;
string q = SwapPieces(p, y * n + x, y2 * n + x2);
if(alternateMoves) q = q.Substring(0, n * n) + (char) (q[n * n] ^ 1);
if (!dict.ContainsKey(q))
{
list.Add(q);
string gen2 = gen + " " + (char)('A' + x) + (char)('1' + y) + (char)('A' + x2) + (char)('1' + y2);
dict.Add(q, gen2);
if (q.StartsWith(goal))
{
Console.WriteLine(q + " " + gen2);
}
n2++;
}
}
}
}
}
}
if (n1 == 0)
{
len++;
Console.WriteLine("{0}: {1}",len,n2);
n1 = n2;
n2 = 0;
}
}
}
static String SwapPieces(String input, int i1, int i2)
{
if (i1 > i2) return SwapPieces(input, i2, i1);
return input.Substring(0, i1) + input.Substring(i2, 1) + input.Substring(i1 + 1, i2 - i1 - 1) + input.Substring(i1, 1) + input.Substring(i2 + 1);
}
}
}