A really simply slider puzzle in WPF

Within WPF there it is possible to create a Brush based on an Image, this is known as an ImageBrush. You are then able to use this ImageBrush anywhere that you would be able to use another Brush. One of the less documented features of the ImageBrush is its ViewBox property. Using the ViewBox property you are able to grab certain portions of the original image. You may have seen this in use in the Blendables zoom box, where a small box shows a zoomed section of the full image.

I was thinking about this the other day, and was a little miffed with the world, so decided to write a small puzzle in WPF. I used the ImageBrush and its ViewBox property to create a small 9 square slider puzzle just like the ones you got for MXAS in your stocking (cracker if you were very lucky).

You know like this.

image

And here is all the code that makes it work

 

XAML

   1:  <Window x:Class="SlideGame.Window1"
   2:      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:      Title="Window1" Height="300" Width="300">
   5:      <DockPanel LastChildFill="True">
   6:   
   7:   
   8:          <Button x:Name="btnPickImage" Content="Pick Image" 
   9:                  Click="btnPickImage_Click" DockPanel.Dock="Top"/>
  10:          <!-- Puzzle -->    
  11:          <Grid x:Name="gridMain">
  12:              <Grid.ColumnDefinitions>
  13:                  <ColumnDefinition Width="*"/>
  14:                  <ColumnDefinition Width="*"/>
  15:                  <ColumnDefinition Width="*"/>
  16:              </Grid.ColumnDefinitions>
  17:   
  18:              <Grid.RowDefinitions>
  19:                  <RowDefinition Height="*"/>
  20:                  <RowDefinition Height="*"/>
  21:                  <RowDefinition Height="*"/>
  22:              </Grid.RowDefinitions>
  23:   
  24:          </Grid>
  25:   
  26:   
  27:      </DockPanel>
  28:  </Window>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

 

C# Code behind

 

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Windows;
   6:  using System.Windows.Controls;
   7:  using System.Windows.Data;
   8:  using System.Windows.Documents;
   9:  using System.Windows.Input;
  10:  using System.Windows.Media;
  11:  using System.Windows.Media.Imaging;
  12:  using System.Windows.Navigation;
  13:  using System.Windows.Shapes;
  14:   
  15:  namespace SlideGame
  16:  {
  17:      /// <summary>
  18:      /// A simple 9 square sliding puzzle using an 
  19:      /// image that the user picks
  20:      /// </summary>
  21:      public partial class Window1 : Window
  22:      {
  23:          #region Data
  24:          BitmapImage image;
  25:          Image img;
  26:          List<Rectangle> initialUnallocatedParts = new List<Rectangle>();
  27:          List<Rectangle> allocatedParts = new List<Rectangle>();
  28:          #endregion
  29:   
  30:          #region Ctor
  31:          /// <summary>
  32:          /// Creates a new Window
  33:          /// </summary>
  34:          public Window1()
  35:          {
  36:              InitializeComponent();
  37:          }
  38:          #endregion
  39:   
  40:          #region Private Methods
  41:   
  42:          /// <summary>
  43:          /// Randomizes the tiles such that they are not
  44:          /// show in the order they were created
  45:          /// </summary>
  46:          private void RandomizeTiles()
  47:          {
  48:              Random rand = new Random(15);
  49:              int allocated = 0;
  50:              while (allocated != 8)
  51:              {
  52:                  int index = 0;
  53:                  if (initialUnallocatedParts.Count > 1)
  54:                  {
  55:                      index = (int)(rand.NextDouble() * 
  56:                          initialUnallocatedParts.Count);
  57:                  }
  58:                  allocatedParts.Add(initialUnallocatedParts[index]);
  59:                  initialUnallocatedParts.RemoveAt(index);
  60:                  allocated++;
  61:              }
  62:          }
  63:   
  64:          /// <summary>
  65:          /// Creates all the puzzles squares, which will either
  66:          /// be a portion of the original image or will be the
  67:          /// single blank puzzle square
  68:          /// </summary>
  69:          private void CreatePuzzleForImage()
  70:          {
  71:              initialUnallocatedParts.Clear();
  72:              allocatedParts.Clear();
  73:   
  74:              //row0
  75:              CreateImagePart(0, 0, 0.33333, 0.33333);
  76:              CreateImagePart(0.33333, 0, 0.33333, 0.33333);
  77:              CreateImagePart(0.66666, 0, 0.33333, 0.33333);
  78:              //row1
  79:              CreateImagePart(0, 0.33333, 0.33333, 0.33333);
  80:              CreateImagePart(0.33333, 0.33333, 0.33333, 0.33333);
  81:              CreateImagePart(0.66666, 0.33333, 0.33333, 0.33333);
  82:              //row2
  83:              CreateImagePart(0, 0.66666, 0.33333, 0.33333);
  84:              CreateImagePart(0.33333, 0.66666, 0.33333, 0.33333);
  85:              RandomizeTiles();
  86:              CreateBlankRect();
  87:   
  88:              int index = 0;
  89:              for (int i = 0; i < 3; i++)
  90:              {
  91:                  for (int j = 0; j < 3; j++)
  92:                  {
  93:                      allocatedParts[index].SetValue(Grid.RowProperty, i);
  94:                      allocatedParts[index].SetValue(Grid.ColumnProperty, j);
  95:                      gridMain.Children.Add(allocatedParts[index]);
  96:                      index++;
  97:                  }
  98:              }
  99:          }
 100:   
 101:          /// <summary>
 102:          /// Creates the single blank Rectangle for the puzzle
 103:          /// </summary>
 104:          private void CreateBlankRect()
 105:          {
 106:              Rectangle rectPart = new Rectangle();
 107:              rectPart.Fill = new SolidColorBrush(Colors.White);
 108:              rectPart.Margin = new Thickness(0);
 109:              rectPart.HorizontalAlignment = HorizontalAlignment.Stretch;
 110:              rectPart.VerticalAlignment = VerticalAlignment.Stretch;
 111:              allocatedParts.Add(rectPart);
 112:          }
 113:   
 114:          /// <summary>
 115:          /// Creates a ImageBrush using x/y/width/height params
 116:          /// to create a Viewbox to only show a portion of original
 117:          /// image. This ImageBrush is then used to fill a Rectangle
 118:          /// which is added to the internal list of unallocated
 119:          /// Rectangles
 120:          /// </summary>
 121:          /// <param name="x">x position in the original image</param>
 122:          /// <param name="y">y position in the original image</param>
 123:          /// <param name="width">the width to use</param>
 124:          /// <param name="height">the hieght to use</param>
 125:          private void CreateImagePart(double x, double y, double width, double height)
 126:          {
 127:              ImageBrush ib = new ImageBrush();
 128:              ib.Stretch = Stretch.UniformToFill;
 129:              ib.ImageSource = image;
 130:              ib.Viewport = new Rect(0, 0, 1.0, 1.0);
 131:              //grab image portion
 132:              ib.Viewbox = new Rect(x, y, width, height); 
 133:              ib.ViewboxUnits = BrushMappingMode.RelativeToBoundingBox;
 134:              ib.TileMode = TileMode.None;
 135:   
 136:              Rectangle rectPart = new Rectangle();
 137:              rectPart.Fill = ib;
 138:              rectPart.Margin = new Thickness(0);
 139:              rectPart.HorizontalAlignment = HorizontalAlignment.Stretch;
 140:              rectPart.VerticalAlignment = VerticalAlignment.Stretch;
 141:              rectPart.MouseDown += new MouseButtonEventHandler(rectPart_MouseDown);
 142:              initialUnallocatedParts.Add(rectPart);
 143:          }
 144:   
 145:          /// <summary>
 146:          /// Swaps the blank puzzle square with the square clicked if its a
 147:          /// valid move
 148:          /// </summary>
 149:          private void rectPart_MouseDown(object sender, MouseButtonEventArgs e)
 150:          {
 151:              //get the source Rectangle, and the blank Rectangle
 152:              //NOTE : Blank Rectangle never moves, its always the last Rectangle
 153:              //in the allocatedParts List, but it gets re-allocated to 
 154:              //different Gri Row/Column
 155:              Rectangle rectCurrent = sender as Rectangle;
 156:              Rectangle rectBlank = allocatedParts[allocatedParts.Count - 1];
 157:   
 158:              //get current grid row/col for clicked Rectangle and Blank one
 159:              int currentTileRow = (int)rectCurrent.GetValue(Grid.RowProperty);
 160:              int currentTileCol = (int)rectCurrent.GetValue(Grid.ColumnProperty);
 161:              int currentBlankRow = (int)rectBlank.GetValue(Grid.RowProperty);
 162:              int currentBlankCol = (int)rectBlank.GetValue(Grid.ColumnProperty);
 163:   
 164:              //create possible valid move positions
 165:              List<PossiblePositions> posibilities = new List<PossiblePositions>();
 166:              posibilities.Add(new PossiblePositions 
 167:                  { Row = currentBlankRow - 1, Col = currentBlankCol });
 168:              posibilities.Add(new PossiblePositions 
 169:                  { Row = currentBlankRow + 1, Col = currentBlankCol });
 170:              posibilities.Add(new PossiblePositions 
 171:                  { Row = currentBlankRow, Col = currentBlankCol-1 });
 172:              posibilities.Add(new PossiblePositions 
 173:                  { Row = currentBlankRow, Col = currentBlankCol + 1 });
 174:   
 175:              //check for valid move
 176:              bool validMove = false;
 177:              foreach (PossiblePositions position in posibilities)
 178:                  if (currentTileRow == position.Row && currentTileCol == position.Col)
 179:                      validMove = true;
 180:   
 181:              //only allow valid move
 182:              if (validMove)
 183:              {
 184:                  rectCurrent.SetValue(Grid.RowProperty, currentBlankRow);
 185:                  rectCurrent.SetValue(Grid.ColumnProperty, currentBlankCol);
 186:   
 187:                  rectBlank.SetValue(Grid.RowProperty, currentTileRow);
 188:                  rectBlank.SetValue(Grid.ColumnProperty, currentTileCol);
 189:              }
 190:              else
 191:                  return;
 192:          }
 193:   
 194:          /// <summary>
 195:          /// Allows user to pick a new image to create a puzzle for
 196:          /// </summary>
 197:          private void btnPickImage_Click(object sender, RoutedEventArgs e)
 198:          {
 199:              Microsoft.Win32.OpenFileDialog ofd = new Microsoft.Win32.OpenFileDialog();
 200:              ofd.Filter = "Image Files(*.BMP;*.JPG;*.GIF;*.PNG)|*.BMP;*.JPG;*.GIF;*.PNG" +
 201:                          "|All Files (*.*)|*.*";
 202:              ofd.Multiselect = false;
 203:              if (ofd.ShowDialog() == true)
 204:              {
 205:                  try
 206:                  {
 207:                      image = new BitmapImage(new Uri(ofd.FileName, 
 208:                          UriKind.RelativeOrAbsolute));
 209:                      img = new Image { Source = image };
 210:                      CreatePuzzleForImage();
 211:                  }
 212:                  catch
 213:                  {
 214:                      MessageBox.Show("Couldnt load the image file " + 
 215:                          ofd.FileName);
 216:                  }
 217:              }
 218:          }
 219:          #endregion
 220:      }
 221:   
 222:      #region PossiblePositions STRUCT
 223:      /// <summary>
 224:      /// Simply struct to store Row/Column data
 225:      /// </summary>
 226:      struct PossiblePositions
 227:      {
 228:          public int Row { get; set; }
 229:          public int Col { get; set; }
 230:      }
 231:      #endregion
 232:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

 

 

I have written a more detail account of how this works in this article at codeproject so if you want to download the code you can do it from there.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: