tags:

views:

171

answers:

3

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);
+1  A: 
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];
}
Itay
Assume:nextPlayerIndex = 0; next = -1;Your loop will do nextPlayerIndex = (0-1)%Players.Count which will be -1, which will throw an exception when you will try to access an array.
Ravadre
thanks for taking the time - but that goes into an infinite loop
Christo Fur
@Ravadre: My bad - In a mathematical manner (-1 % x) = x-1. (I guess is different in c#). I'll fix it.
Itay
@Christo Fur: I do not see how it can go into an infinite loop. After one iteration over all player (where `nextPlayerIndex == currentPlayerIndex`) The loop must end.
Itay
well, it never returns when I ran my unit tests on it - maybe you meant this line : while(Players[currentPlayerIndex] to be : while(Players[nextPlayerIndex] ? when I change it to that I get an index outof range exception, as Ravadre points out
Christo Fur
@Christo Fur: I have made a little change so you will not have the OutOfRangeException, Try it out.
Itay
sorry, but that doesn't work. as an example, when the current player is 2nd in the list and the direction is backwards, it selects the 2nd player as the next player (i.e. the current player). Also, are you sure your while statement is correct? as you are always checking against the currentplayerindex, which never changes
Christo Fur
If current index is 1 (second player) and the direction is backwards the first time the line `nextPlayerIndex += (nextPlayerIndex + next + Players.Count) % Players.Count;` will run it will set nextPlayerIndex to be 0. the only case it will return the same player again is if all other players have no cards. (The check `nextPlayerIndex != currentPlayerIndex` is to avoid infinite loop in case that no one has cards.
Itay
no, that's not the case. e.g. player list size = 6. Current Index = 1. (1 - 1 + 6) % 6 = 0. and so nextPlayerIndex stays as 1. i.e. the same index. I have stepped through it with the debugger and I can assure you that is what is happening
Christo Fur
woops... will be fixed in a minute :)
Itay
exactly. nextPlayerIndex += 0, results in the same index
Christo Fur
sorry, it should be `=` and not `+=`. Fixed. :)
Itay
that works, if you change the condition in the while statement from while(Players[currentPlayerIndex] to while(Players[nextPlayerIndex] - thanks
Christo Fur
Yes - also that :), This is what I meant to anyway
Itay
A: 

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));
        }
Christo Fur
A: 

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);
}
Michael Kropat