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.)