Reputation: 11006
I am writing a card game and have been using the following method to get the next Player who's turn it is
There is a direction to the game which could be forwards or backwards, and it needs to respect this too
private Player GetNextPlayer()
{
int currentPlayerIndex = Players.FindIndex(o => o.IsThisPlayersTurn);
Player nextPlayer;
if (_direction.Equals(Direction.Forwards))
{
nextPlayer = currentPlayerIndex == Players.Count - 1 ? Players[0] : Players[currentPlayerIndex + 1];
}
else
{
nextPlayer = currentPlayerIndex == 0 ? Players[Players.Count - 1] : Players[currentPlayerIndex - 1];
}
return nextPlayer;
}
This works fine until a player has finished the game. Then it can potentially return a player who is no longer in the game.
When a player has finished the game their PlayerState is HasNoCards
So I changed it to this, but it seems to be buggy in certain cases
public Player GetNextPlayer()
{
var players = Players.Where(o => o.PlayerState != PlayerState.HasNoCards);
if (Direction.Equals(Direction.Backwards))
{
players = players.Reverse();
}
bool selectNextPlayer = false;
foreach (Player player in players)
{
if (selectNextPlayer)
{
return player;
}
if (player.IsThisPlayersTurn)
{
selectNextPlayer = true;
}
}
return players.First();
}
I reckon there must be a smart way with linq to say "get the next player , where the Player.PlayerState is not PlayerState.HasNoCards"
Any ideas?
I should add that I can't remove the player from the list to solve the problem as it would screw my databinding
EDIT
I have a failing unit test for the scenario that the second method can't handle. It is when a player plays their last card when the direction is backwards. As I immediately filter the current player from the list, with
var players = Players.Where(o => o.PlayerState != PlayerState.HasNoCards);
Upvotes: 1
Views: 6875
Reputation: 18286
public Player GetNextPlayer()
{
int currentPlayerIndex = Players.FindIndex(o => o.IsThisPlayersTurn);
int next = _direction.Equals(Direction.Forwards) ? 1 : -1;
int nextPlayerIndex = currentPlayerIndex;
do
{
nextPlayerIndex = (nextPlayerIndex + next + Players.Count) % Players.Count;
}while(Players[nextPlayerIndex].HasNoCards && nextPlayerIndex != currentPlayerIndex);
return Players[nextPlayerIndex];
}
Upvotes: 2
Reputation: 15207
The trick with LINQ is to carefully design your starting sequence so that it contains all possible output values in logical order. In this case, you want the starting sequence to be all the other players, in turn order, starting with the player following the current player. Once you can express that, it is trivial to handle cases like backwards direction, or players who have no cards.
private Player GetNextPlayer() {
if (!Players.Any()) throw new InvalidOperationException("No players.");
if (Players.Count(p => p.IsThisPlayersTurn) != 1) {
throw new InvalidOperationException(
"It must be one--and only one--player's turn.");
}
var current = Players.Single(p => p.IsThisPlayersTurn);
var subsequent = Players.Concat(Players)
.SkipWhile(p => p != current)
.Skip(1) // skip current player
.TakeWhile(p => p != current);
if (_direction == Direction.Backwards) {
subsequent = subsequent.Reverse();
}
return subsequent
.FirstOrDefault(p => p.PlayerState != PlayerState.HasNoCards);
}
Upvotes: 2
Reputation: 11006
Have come up with the following, which works, but would be interested in any more elegant solutions
private Player GetNextPlayer()
{
var players = Players.AsEnumerable();
if (Direction.Equals(Direction.Backwards))
{
players = players.Reverse();
}
bool selectNextPlayer = false;
foreach (Player player in players)
{
if (selectNextPlayer && !player.PlayerState.Equals(PlayerState.HasNoCards))
{
return player;
}
if (player.IsThisPlayersTurn)
{
selectNextPlayer = true;
}
}
return players.First(o => !o.PlayerState.Equals(PlayerState.HasNoCards));
}
Upvotes: 0