Skip to main content

A board game in MVVM Part 1 - The Model

This is a continuation of my experience creating a quick board game in silverlight 5
Original Post
Play the Game

I planned on using MVVM so I began the process by breaking down the individual pieces into how they exist in the rules and how they are displayed. I came up with the following:

  • Square
  • Piece
  • Board
  • Game

The board is made up of squares and squares have pieces on them. The game manages the back-and-forth flow of the game. I knew I wanted an AI to be able to use the board, pieces and squares, but wanted it to use separate objects that could potentially reduce overhead so I started by creating interfaces for them defining the properties I knew would be needed. I'm going to use "Square" as a representation of how I went about doing this and why. Here's an example of the interface for Square:

  1. namespace Viking
  2. {
  3.     using System.Collections.Generic;
  4.  
  5.     public interface ISquare
  6.     {
  7.         ISquare Bottom { get; set; }
  8.  
  9.         int Col { get; set; }
  10.  
  11.         System.Windows.Point Coords { get; }
  12.  
  13.         bool IsOccupied { get; }
  14.  
  15.         bool IsRestricted { get; set; }
  16.  
  17.         bool IsStartPosition { get; set; }
  18.  
  19.         bool IsValidMove { get; set; }
  20.  
  21.         ISquare Left { get; set; }
  22.  
  23.         Dictionary<Viking.Square.NeighborDirection, ISquare> Neighbors { get; }
  24.  
  25.         IPiece Occupant { get; set; }
  26.  
  27.         ISquare Right { get; set; }
  28.  
  29.         int Row { get; set; }
  30.  
  31.         ISquare Top { get; set; }
  32.     }
  33. }

I might've made the Square a bit more complicated than it absolutely needs to be. However, what I can do with this is that the data allows squares to be accessed on 3 fronts:

  • Via direction (ie. "mySquare.Left")
  • Via index based on an enum (ie. "mySquare.Neighbors[Viking.Square.Direction.Left]" which is useful for limiting code duplication by being able to pass in a direction to a function)
  • By keeping track of row/column, a board can easily index its squares. "Coords" is for convenience and simply gets a Point(x,y) based on the Row/Col of the square

This should allow reading the code and traversing the squares easier. The other properties are things we want to be able to indicate on the View via Binding. None of these should call anything on the view. Instead, each of these are properties on the square that either the viewmodel can use in its logic, or the view can use to display things differently.

Here's the actual implementation of "Square":

  1. namespace Viking
  2. {
  3.     using System.Collections.Generic;
  4.     using System.Windows;
  5.  
  6.     public class Square : BasePropertyChanged, ISquare
  7.     {
  8.         private ISquare bottom;
  9.  
  10.         private int col;
  11.  
  12.         private bool isRestricted;
  13.  
  14.         private bool isStartPosition;
  15.  
  16.         private bool isValidMove;
  17.  
  18.         private ISquare left;
  19.  
  20.         private Dictionary<NeighborDirection, ISquare> neighbors;
  21.  
  22.         private IPiece occupant;
  23.  
  24.         private ISquare right;
  25.  
  26.         private int row;
  27.  
  28.         private ISquare top;
  29.  
  30.         public Square()
  31.         {
  32.             Neighbors = new Dictionary<NeighborDirection, ISquare>();
  33.             Neighbors.Add(NeighborDirection.Left, Left);
  34.             Neighbors.Add(NeighborDirection.Right, Right);
  35.             Neighbors.Add(NeighborDirection.Top, Top);
  36.             Neighbors.Add(NeighborDirection.Bottom, Bottom);
  37.         }
  38.  
  39.         public enum NeighborDirection
  40.         {
  41.             Left,
  42.  
  43.             Right,
  44.  
  45.             Top,
  46.  
  47.             Bottom
  48.         }
  49.  
  50.         public ISquare Bottom
  51.         {
  52.             get
  53.             {
  54.                 return this.bottom;
  55.             }
  56.  
  57.             set
  58.             {
  59.                 if (value != bottom)
  60.                 {
  61.                     bottom = value;
  62.                     RaisePropertyChanged(() => Bottom);
  63.                     Neighbors[NeighborDirection.Bottom] = value;
  64.                 }
  65.             }
  66.         }
  67.  
  68.         public int Col
  69.         {
  70.             get
  71.             {
  72.                 return this.col;
  73.             }
  74.  
  75.             set
  76.             {
  77.                 if (value != col)
  78.                 {
  79.                     col = value;
  80.                     RaisePropertyChanged(() => Col);
  81.                     RaisePropertyChanged(() => Coords);
  82.                 }
  83.             }
  84.         }
  85.  
  86.         public Point Coords
  87.         {
  88.             get
  89.             {
  90.                 return new Point(Row, Col);
  91.             }
  92.         }
  93.  
  94.         public bool IsOccupied
  95.         {
  96.             get
  97.             {
  98.                 return Occupant != null;
  99.             }
  100.         }
  101.  
  102.         public bool IsRestricted
  103.         {
  104.             get
  105.             {
  106.                 return this.isRestricted;
  107.             }
  108.  
  109.             set
  110.             {
  111.                 if (value != isRestricted)
  112.                 {
  113.                     isRestricted = value;
  114.                     RaisePropertyChanged(() => IsRestricted);
  115.                 }
  116.             }
  117.         }
  118.  
  119.         public bool IsStartPosition
  120.         {
  121.             get
  122.             {
  123.                 return this.isStartPosition;
  124.             }
  125.  
  126.             set
  127.             {
  128.                 if (value != isStartPosition)
  129.                 {
  130.                     isStartPosition = value;
  131.                     RaisePropertyChanged(() => IsStartPosition);
  132.                 }
  133.             }
  134.         }
  135.  
  136.         public bool IsValidMove
  137.         {
  138.             get
  139.             {
  140.                 return this.isValidMove;
  141.             }
  142.  
  143.             set
  144.             {
  145.                 if (value != isValidMove)
  146.                 {
  147.                     isValidMove = value;
  148.                     RaisePropertyChanged(() => IsValidMove);
  149.                 }
  150.             }
  151.         }
  152.  
  153.         public ISquare Left
  154.         {
  155.             get
  156.             {
  157.                 return this.left;
  158.             }
  159.  
  160.             set
  161.             {
  162.                 if (value != left)
  163.                 {
  164.                     left = value;
  165.                     RaisePropertyChanged(() => Left);
  166.                     Neighbors[NeighborDirection.Left] = value;
  167.                 }
  168.             }
  169.         }
  170.  
  171.         public Dictionary<NeighborDirection, ISquare> Neighbors
  172.         {
  173.             get
  174.             {
  175.                 return this.neighbors;
  176.             }
  177.  
  178.             private set
  179.             {
  180.                 if (value != neighbors)
  181.                 {
  182.                     neighbors = value;
  183.                     RaisePropertyChanged(() => Neighbors);
  184.                 }
  185.             }
  186.         }
  187.  
  188.         public IPiece Occupant
  189.         {
  190.             get
  191.             {
  192.                 return this.occupant;
  193.             }
  194.  
  195.             set
  196.             {
  197.                 if (value != occupant)
  198.                 {
  199.                     occupant = value;
  200.                     RaisePropertyChanged(() => Occupant);
  201.                     RaisePropertyChanged(() => IsOccupied);
  202.                 }
  203.             }
  204.         }
  205.  
  206.         public ISquare Right
  207.         {
  208.             get
  209.             {
  210.                 return this.right;
  211.             }
  212.  
  213.             set
  214.             {
  215.                 if (value != right)
  216.                 {
  217.                     right = value;
  218.                     RaisePropertyChanged(() => Right);
  219.                     Neighbors[NeighborDirection.Right] = value;
  220.                 }
  221.             }
  222.         }
  223.  
  224.         public int Row
  225.         {
  226.             get
  227.             {
  228.                 return this.row;
  229.             }
  230.  
  231.             set
  232.             {
  233.                 if (value != row)
  234.                 {
  235.                     row = value;
  236.                     RaisePropertyChanged(() => Row);
  237.                     RaisePropertyChanged(() => Coords);
  238.                 }
  239.             }
  240.         }
  241.  
  242.         public ISquare Top
  243.         {
  244.             get
  245.             {
  246.                 return this.top;
  247.             }
  248.  
  249.             set
  250.             {
  251.                 if (value != top)
  252.                 {
  253.                     top = value;
  254.                     RaisePropertyChanged(() => Top);
  255.                     Neighbors[NeighborDirection.Top] = value;
  256.                 }
  257.             }
  258.         }
  259.     }
  260. }

And here's the AI version that doesn't have the same overhead that this version does in notifying of property changes and such:

  1. namespace Viking
  2. {
  3.     using System.Collections.Generic;
  4.     using System.Windows;
  5.  
  6.     public class DecisionSquare : ISquare
  7.     {
  8.         private ISquare bottom;
  9.  
  10.         private ISquare left;
  11.  
  12.         private ISquare right;
  13.  
  14.         private ISquare top;
  15.  
  16.         public DecisionSquare()
  17.         {
  18.             Neighbors = new Dictionary<Square.NeighborDirection, ISquare>();
  19.             Neighbors.Add(Square.NeighborDirection.Left, Left);
  20.             Neighbors.Add(Square.NeighborDirection.Right, Right);
  21.             Neighbors.Add(Square.NeighborDirection.Top, Top);
  22.             Neighbors.Add(Square.NeighborDirection.Bottom, Bottom);
  23.         }
  24.  
  25.         public ISquare Bottom
  26.         {
  27.             get
  28.             {
  29.                 return bottom;
  30.             }
  31.  
  32.             set
  33.             {
  34.                 bottom = value;
  35.                 Neighbors[Square.NeighborDirection.Bottom] = bottom;
  36.             }
  37.         }
  38.  
  39.         public int Col { get; set; }
  40.  
  41.         public Point Coords
  42.         {
  43.             get
  44.             {
  45.                 return new Point(Row, Col);
  46.             }
  47.         }
  48.  
  49.         public bool IsOccupied
  50.         {
  51.             get
  52.             {
  53.                 return Occupant != null;
  54.             }
  55.         }
  56.  
  57.         public bool IsRestricted { get; set; }
  58.  
  59.         public bool IsStartPosition { get; set; }
  60.  
  61.         public bool IsValidMove { get; set; }
  62.  
  63.         public ISquare Left
  64.         {
  65.             get
  66.             {
  67.                 return left;
  68.             }
  69.  
  70.             set
  71.             {
  72.                 left = value;
  73.                 Neighbors[Square.NeighborDirection.Left] = left;
  74.             }
  75.         }
  76.  
  77.         public Dictionary<Square.NeighborDirection, ISquare> Neighbors { get; set; }
  78.  
  79.         public IPiece Occupant { get; set; }
  80.  
  81.         public ISquare Right
  82.         {
  83.             get
  84.             {
  85.                 return right;
  86.             }
  87.  
  88.             set
  89.             {
  90.                 right = value;
  91.                 Neighbors[Square.NeighborDirection.Right] = right;
  92.             }
  93.         }
  94.  
  95.         public int Row { get; set; }
  96.  
  97.         public ISquare Top
  98.         {
  99.             get
  100.             {
  101.                 return top;
  102.             }
  103.  
  104.             set
  105.             {
  106.                 top = value;
  107.                 Neighbors[Square.NeighborDirection.Top] = top;
  108.             }
  109.         }
  110.     }
  111. }

I've also attached the source for the rest of the "Model". I will be releasing portions of the code in subsequent posts and will release the final source in the last post.

AttachmentSize
Viking Model Source10.74 KB