Building on SimonW's answer, here's an explicit algorithm:
Let squares be an array indexed by the player locations, and containing, for each possible location, either the index of another location or the special value NULL. (You may want to store this as a sparse array.) The possible values of the entries in this array may be interpreted as follows:
- If
squares[S] is NULL, the square S is free to move into.
- If
squares[S] == S, either the player at S cannot or will not move, or two (or more) players tried to move to S at the same time and were both denied.
- Otherwise,
squares[S] will contain the index of the square from which a player wants to move to square S.
On each turn, initialize all entries of squares to NULL and then run the following algorithm:
for each player:
current := the player's current location;
target := the location the player wants to move to (may equal current);
if squares[target] is NULL:
squares[target] := current; // target is free, mark planned move
else
// mark the target square as contested, and if necessary, follow
// the pointers to cancel any moves affected by this:
while not (target is NULL or squares[target] == target):
temp := squares[target];
squares[target] := target;
target := temp;
end while
// mark this player as stationary, and also cancel any moves that
// would require some else to move to this square
while not (current is NULL or squares[current] == current):
temp := squares[current];
squares[current] := current;
current := temp;
end while
end if
end for
After that, loop through the list of players again, and move those which are able to do so:
for each player:
current := the player's current location;
if not squares[current] == current:
move player;
end if
end for
Since each move can only be planned once and cancelled at most once, this algorithm will run in O(n) time for n players even in the worst case.
(Alas, this algorithm won't stop players from switching places or crossing paths diagonally. It might be possible to adapt Gajet's two-step trick to it, but the completely naive way to do so won't work and I'm too tired to figure out a better way just now.)