Skip to main content

A board game in MVVM Part 3b - Commands and Converters

This is a continuation of my experience creating a quick board game in silverlight 5
Original Post
Part 1 - The Model
Part 2 - The AI
Part 3 - The View
Play the Game

Commands allow the user to interact with the game. The basics of the actual gameplay are in 2 commands.

There's obviously several others but these 2 commands constitute the bulk of the actual gameplay. Activating a piece chooses which to move, and clicking a square that is enabled (which we tell the board to handle when a piece is activated) makes the player's move. It's very useful to be able to dictate the type of the CommandParameter and I find it much more useful than some of the other implementations which always pass in "object".

  1.         public DelegateCommand<Piece> ActivatePieceCommand
  2.         {
  3.             get
  4.             {
  5.                 if (this.activatePieceCommand == null)
  6.                 {
  7.                     this.activatePieceCommand = new DelegateCommand<Piece>(ActivatePiece);
  8.                 }
  9.  
  10.                 return this.activatePieceCommand;
  11.             }
  12.         }
  13.  
  14.         public void ActivatePiece(Piece targetPiece)
  15.         {
  16.             if (Game.IsActive)
  17.             {
  18.                 if (targetPiece.IsMoveable && !targetPiece.IsActive)
  19.                 {
  20.                     targetPiece.IsActive = true;
  21.                     Game.Board.ActivePiece = targetPiece;
  22.                 }
  23.                 else if (targetPiece.IsMoveable)
  24.                 {
  25.                     targetPiece.IsActive = false;
  26.                     Game.Board.ActivePiece = null;
  27.                     Game.Board.SetMoveablePieces(Game.CurrentPlayer);
  28.                 }
  29.             }
  30.         }
  31.  
  32.         public DelegateCommand<Square> MakeMoveCommand
  33.         {
  34.             get
  35.             {
  36.                 if (this.makeMoveCommand == null)
  37.                 {
  38.                     this.makeMoveCommand = new DelegateCommand<Square>(MakeMove);
  39.                 }
  40.  
  41.                 return this.makeMoveCommand;
  42.             }
  43.         }
  44.  
  45.         public void MakeMove(Square targetSquare)
  46.         {
  47.             if (Game.IsActive)
  48.             {
  49.                 Game.MovePiece(targetSquare);
  50.             }
  51.         }

This is the basis of the gameplay and isn't too terribly hard to grasp. The key to a lot of the way we have the board set up is appropriately hiding, showing, enabling, and disabling the various squares and pieces.

There are several converters I find useful in pretty much every project I do. Most of them are very self-explanatory such as:

  1. public class BoolToVisibilityConverter : IValueConverter
  2.     {
  3.         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  4.         {
  5.             return (bool)value ? Visibility.Visible : Visibility.Collapsed;
  6.         }
  7.  
  8.         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  9.         {
  10.             return (Visibility)value == Visibility.Visible;
  11.         }
  12.     }

There are, however, a few that are used that would be much more interesting. If we go back to the view and look at the representation of the player's piece we see this:

  1. <Border Visibility="{Binding Occupant, Converter={StaticResource HideOnNullConverter}}">
  2.     <Button HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Command="{Binding DataContext.ActivatePieceCommand, RelativeSource={RelativeSource AncestorType=local:MainPage}}" CommandParameter="{Binding Occupant}" IsEnabled="{Binding Occupant.IsMoveable}">
  3.         <Button.Template>
  4.             <ControlTemplate>
  5.                 <Ellipse HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke="{Binding Occupant, Converter={StaticResource KingStrokeConverter}}" StrokeThickness="{Binding Occupant, Converter={StaticResource KingStrokeConverter}}" Fill="{Binding Occupant, Converter={StaticResource PlayerColorConverter}}">
  6.                     <Ellipse.Effect>
  7.                         <DropShadowEffect Opacity=".5" ShadowDepth="3" />
  8.                     </Ellipse.Effect>
  9.                 </Ellipse>
  10.             </ControlTemplate>
  11.         </Button.Template>
  12.     </Button>
  13. </Border>

The 2 fun ones are the PlayerColorConverter and the KingStrokeConverter. These bind directly to a piece to convert their properties into what is needed. What I don't see often though in converters is the ability to configure the converter.

Lets look at where I've instantiated the converters in xaml.

  1. <library:PlayerColorConverter x:Key="PlayerColorConverter" DefenderColor="LightBlue" KingColor="LightBlue" AttackerColor="LightSalmon" />
  2. <library:KingStrokeConverter x:Key="KingStrokeConverter" StrokeThickness="10">
  3.     <library:KingStrokeConverter.StrokeColor>
  4.         <RadialGradientBrush>
  5.             <GradientStopCollection>
  6.                 <GradientStop Offset="0.7" Color="LightBlue" />
  7.                 <GradientStop Offset="1" Color="Gold" />
  8.             </GradientStopCollection>
  9.         </RadialGradientBrush>
  10.     </library:KingStrokeConverter.StrokeColor>
  11. </library:KingStrokeConverter>

Notice that my converters inherit DependencyObject and actual have their own DependencyProperties. This allows the view to dictate what should show up.

Lets see it in action:

  1. public class PlayerColorConverter : DependencyObject, IValueConverter
  2.     {
  3.         public static readonly DependencyProperty AttackerColorProperty =
  4.                     DependencyProperty.Register("AttackerColor", typeof(SolidColorBrush), typeof(PlayerColorConverter), null);
  5.  
  6.         public static readonly DependencyProperty DefenderColorProperty =
  7.                     DependencyProperty.Register("DefenderColor", typeof(SolidColorBrush), typeof(PlayerColorConverter), null);
  8.  
  9.         public static readonly DependencyProperty KingColorProperty =
  10.                     DependencyProperty.Register("KingColor", typeof(SolidColorBrush), typeof(PlayerColorConverter), null);
  11.  
  12.         public SolidColorBrush AttackerColor
  13.         {
  14.             get { return (SolidColorBrush)GetValue(AttackerColorProperty); }
  15.             set { SetValue(AttackerColorProperty, value); }
  16.         }
  17.  
  18.         public SolidColorBrush DefenderColor
  19.         {
  20.             get { return (SolidColorBrush)GetValue(DefenderColorProperty); }
  21.             set { SetValue(DefenderColorProperty, value); }
  22.         }
  23.  
  24.         public SolidColorBrush KingColor
  25.         {
  26.             get { return (SolidColorBrush)GetValue(KingColorProperty); }
  27.             set { SetValue(KingColorProperty, value); }
  28.         }
  29.  
  30.         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  31.         {
  32.             SolidColorBrush retVal = new SolidColorBrush(Colors.Magenta);
  33.             if (value is Piece)
  34.             {
  35.                 Piece piece = value as Piece;
  36.                 if (piece != null)
  37.                 {
  38.                     if (piece.Player == Player.Attacker)
  39.                     {
  40.                         retVal = AttackerColor;
  41.                     }
  42.                     else if (!piece.IsKing)
  43.                     {
  44.                         retVal = DefenderColor;
  45.                     }
  46.                     else
  47.                     {
  48.                         retVal = KingColor;
  49.                     }
  50.                 }
  51.             }
  52.             else if (value != null)
  53.             {
  54.                 string stringVal = value.ToString();
  55.                 if (stringVal == Player.Attacker.ToString())
  56.                 {
  57.                     retVal = AttackerColor;
  58.                 }
  59.                 else if (stringVal == Player.Defender.ToString())
  60.                 {
  61.                     retVal = DefenderColor;
  62.                 }
  63.                 else
  64.                 {
  65.                     System.Diagnostics.Debug.WriteLine("Invalid player value passed to PlayerColorConverter: " + stringVal);
  66.                 }
  67.             }
  68.  
  69.             return retVal;
  70.         }
  71.  
  72.         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  73.         {
  74.             throw new NotImplementedException();
  75.         }
  76.     }
  77.  
  78.     public class KingStrokeConverter : DependencyObject, IValueConverter
  79.     {
  80.         public static readonly DependencyProperty StrokeThicknessProperty =
  81.                     DependencyProperty.Register("StrokeThickness", typeof(double), typeof(KingStrokeConverter), null);
  82.  
  83.         public static readonly DependencyProperty StrokeProperty =
  84.                     DependencyProperty.Register("Stroke", typeof(Brush), typeof(KingStrokeConverter), null);
  85.  
  86.         public double StrokeThickness
  87.         {
  88.             get { return (double)GetValue(StrokeThicknessProperty); }
  89.             set { SetValue(StrokeThicknessProperty, value); }
  90.         }
  91.  
  92.         public Brush StrokeColor
  93.         {
  94.             get { return (Brush)GetValue(StrokeProperty); }
  95.             set { SetValue(StrokeProperty, value); }
  96.         }
  97.  
  98.         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  99.         {
  100.             Piece piece = value as Piece;
  101.             object retVal = null;
  102.             if (targetType == typeof(double))
  103.             {
  104.                 if (piece != null && piece.IsKing)
  105.                 {
  106.                     retVal = StrokeThickness;
  107.                 }
  108.                 else
  109.                 {
  110.                     retVal = 0;
  111.                 }
  112.             }
  113.             else
  114.             {
  115.                 if (piece != null && piece.IsKing)
  116.                 {
  117.                     retVal = StrokeColor;
  118.                 }
  119.                 else
  120.                 {
  121.                     retVal = new SolidColorBrush(Colors.Transparent);
  122.                 }
  123.             }
  124.  
  125.             return retVal;
  126.         }
  127.  
  128.         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  129.         {
  130.             throw new NotImplementedException();
  131.         }
  132.     }

This allows me to separate the converter's functionality from the configuration which should rightly happen in the View. You'll also notice that I paid attention to the targetType and reacted accordingly in the KingStrokeConverter, allowing it to handle multiple aspects of "Stroke" without creating an entirely separate converter for it.