Define a negative fact called Outcome.
fact Outcome {
key:
    Game game;
    Player winner;
}

Add a predicate to Player. A player is active if his game has no outcome.
fact Player {
key:
    User user;
    Game game;

query:
    bool isActive {
        not exists Outcome o : o.game = this.game
    }
}

Use this predicate as a condition in the players query.
fact User {
key:
    string userName;

query:
    Player* players {
        Player p : p.user = this
            where p.isActive
    }
}

Now you can create an Outcome in the Game partial class.
public partial class Game
{
    public void DeclareWinnner(Player player)
    {
        Community.AddFact(new Outcome(this, player));
    }
}
That makes all of the model tests pass. Now let's turn our attention to the view model.

The view model needs a SelectedGame property and a Resign command. The SelectedGame is one of the GameSummaryViewModels from ActiveGames. The user's selection is not persistent. They just use it to navigate through their data. So where does it go? GameListNavigationModel.
private Independent<Player> _selectedPlayer = new Independent<Player>();

public Player SelectedPlayer
{
    get { return _selectedPlayer; }
    set { _selectedPlayer.Value = value; }
}
We're storing the Player, not the GameSummaryViewModel that interprets it for the view. The navigation model should never reference view models. View models reference navigation models.
public GameSummaryViewModel SelectedGame
{
    get
    {
        return _navigation.SelectedPlayer == null
            ? null
            : new GameSummaryViewModel(_navigation.SelectedPlayer);
    }
    set
    {
        _navigation.SelectedPlayer = value.Player;
    }
}
Then we can use this property in the Resign command.
public ICommand Resign
{
    get
    {
        return MakeCommand
            .When(() => _navigation.SelectedPlayer != null)
            .Do(() =>
            {
                Game game = _navigation.SelectedPlayer.Game;
                var opponent = game.Players
                    .FirstOrDefault(p => p != _navigation.SelectedPlayer);
                game.DeclareWinnner(opponent);
            });
    }
}
This makes three of the four unit test pass. The remaining one is telling us that we still have a selected game, even though it is not in the list. To fix that, set the SelectedPlayer to null.
public ICommand Resign
{
    get
    {
        return MakeCommand
            .When(() => _navigation.SelectedPlayer != null)
            .Do(() =>
            {
                Game game = _navigation.SelectedPlayer.Game;
                var opponent = game.Players
                    .FirstOrDefault(p => p != _navigation.SelectedPlayer);
                game.DeclareWinnner(opponent);
                _navigation.SelectedPlayer = null;
            });
    }
}
Now all of the unit tests pass, so we can focus on the user interface.

Log in and challenge another player. The game appears in the list. But when you click on the game, it is not selected. That's because Silverlight can't tell that the SelectedGame is equal to the item in ActiveGames. They are two different instances of GameSummaryViewModel. Fix this by adding Equals() and GetHashCode() methods to GameSummaryViewModel.
public override bool Equals(object obj)
{
    if (obj == this)
        return true;
    GameSummaryViewModel that = obj as GameSummaryViewModel;
    if (that == null)
        return false;
    return this._player == that._player;
}

public override int GetHashCode()
{
    return _player.GetHashCode();
}
Now the UI works correctly. The user can select a game and click "Resign". The game will disappear from the list and the Resign button will be disabled.

Next: Lesson 4a: Publish subscribe

Last edited May 15, 2011 at 5:20 AM by MichaelLPerry1971, version 3

Comments

No comments yet.