Click here to Skip to main content
15,867,686 members
Articles / Desktop Programming / WPF

Simple slide game using ViewBox

Rate me:
Please Sign up or sign in to vote.
4.80/5 (29 votes)
9 Aug 2008CPOL3 min read 97.5K   2.2K   63   22
A simple 9 peice puzzle using ImageBrush.ViewBox.

Introduction

This is a very simple article that really just demonstrates the various capabilities of the WPF ImageBrush. The basic idea is that the attached demo emulates one of those 9 square puzzles you used to get in Christmas stockings.

Where a square is blank, and the image portions in the other 8 squares are randomly scattered, and you had to try and recreate the whole image, by sliding the image portion squares into the blank space, and repeating this until you had the whole image again.

In a nutshell, that's all this article does.

So How Does It Work

Well, it's based in WPF land, so we can use a convenient layout manager called a Grid which has rows/columns. We can imagine that each row/column of the Grid will either hold a square which holds a section of the original image, or will be the single blank square.

And that's exactly what we do.

The basic steps are as follows:

  1. Load an image
  2. Split the image into equal portions
  3. Randomly place the 8 sequentially obtained image portions
  4. Allow the user to click one of the image portion squares

That's all there is to it.

Let's look at each of these steps in a bit more detail.

Step 1: Load an Image

Loading an image is done by the button click event that allows the user to grab a new image; the code is as follows:

C#
Microsoft.Win32.OpenFileDialog ofd = new Microsoft.Win32.OpenFileDialog();
ofd.Filter = "Image Files(*.BMP;*.JPG;*.GIF;*.PNG)|*.BMP;*.JPG;*.GIF;*.PNG" +
            "|All Files (*.*)|*.*";
ofd.Multiselect = false;
if (ofd.ShowDialog() == true)
{
    try
    {
        image = new BitmapImage(new Uri(ofd.FileName, UriKind.RelativeOrAbsolute));
        img = new Image { Source = image };
        CreatePuzzleForImage();
    }
    catch
    {
        MessageBox.Show("Couldnt load the image file " + ofd.FileName);
    }
}

Step 2: Split the Image Into Equal Portions

We need to grab sequential portions of the original image, but only grab 8, as we need the 9th square to be a blank. This is done using the following code:

C#
//row0
CreateImagePart(0, 0, 0.33333, 0.33333);
CreateImagePart(0.33333, 0, 0.33333, 0.33333);
CreateImagePart(0.66666, 0, 0.33333, 0.33333);
//row1
CreateImagePart(0, 0.33333, 0.33333, 0.33333);
CreateImagePart(0.33333, 0.33333, 0.33333, 0.33333);
CreateImagePart(0.66666, 0.33333, 0.33333, 0.33333);
//row2
CreateImagePart(0, 0.66666, 0.33333, 0.33333);
CreateImagePart(0.33333, 0.66666, 0.33333, 0.33333);

Where the CreateImagePart() method looks like:

C#
private void CreateImagePart(double x, double y, double width, double height)
{
    ImageBrush ib = new ImageBrush();
    ib.Stretch = Stretch.UniformToFill;
    ib.ImageSource = image;
    ib.Viewport = new Rect(0, 0, 1.0, 1.0);
    //grab image portion
    ib.Viewbox = new Rect(x, y, width, height); 
    ib.ViewboxUnits = BrushMappingMode.RelativeToBoundingBox;
    ib.TileMode = TileMode.None;

    Rectangle rectPart = new Rectangle();
    rectPart.Fill = ib;
    rectPart.Margin = new Thickness(0);
    rectPart.HorizontalAlignment = HorizontalAlignment.Stretch;
    rectPart.VerticalAlignment = VerticalAlignment.Stretch;
    rectPart.MouseDown += new MouseButtonEventHandler(rectPart_MouseDown);
    initialUnallocatedParts.Add(rectPart);
}

The most important parts of this is the line that sets ImageBrush ViewPort, ib.Viewport = new Rect(0, 0, 1.0, 1.0). This means, start at 0,0, and end at the end of the image. In WPF, 0,0 means top left, and 1.0,1.0 means bottom right.

The other important part is where we grab the ImageBrush Viewbox, which is where we grab only the portion of the image we want for the ImageBrush. This simply uses the input parameters to grab the relevant section of the original image. Neat, I think.

Step 3: Randomly Place the First 8 Sequentially Obtained Image Portions

Now, it wouldn't be much of a puzzle if we laid the squares out in the order that they were obtained, so we need to shake things up a bit. We need some randomness. This is achieved by the following method, where we randomly grab the 8 sequentially obtained unallocated image portions and allocate them to an allocated List<Rectangle>.

C#
private void RandomizeTiles()
{
    Random rand = new Random();
    int allocated = 0;
    while (allocated != 8)
    {
        int index = 0;
        if (initialUnallocatedParts.Count > 1)
        {
            index = (int)(rand.NextDouble() * initialUnallocatedParts.Count);
        }
        allocatedParts.Add(initialUnallocatedParts[index]);
        initialUnallocatedParts.RemoveAt(index);
        allocated++;
    }
}

We then need to fill the Grid with these random image portions and a single blank Rectangle which was added to the end of the allocated List<Rectangle>.

C#
int index = 0;
for (int i = 0; i < 3; i++)
{
    for (int j = 0; j < 3; j++)
    {
        allocatedParts[index].SetValue(Grid.RowProperty, i);
        allocatedParts[index].SetValue(Grid.ColumnProperty, j);
        gridMain.Children.Add(allocatedParts[index]);
        index++;
    }
}

Step 4: Allow the User to Click One of the Image Portion Squares

As we cunningly used Rectangle objects to put into the Grid, we are able to use RoutedEvents. This is because Rectangle is a full blown WPF element, so has routed events. We simply hook into the MouseDown RoutedEvent for each Rectangle. And then, see if the Rectangle clicked is allowed to be swapped with the current blank Rectangle. If it can be swapped, the two Rectangles are swapped (reallocated to a different Grid row/column).

This is done as follows:

C#
private void rectPart_MouseDown(object sender, MouseButtonEventArgs e)
{
    //get the source Rectangle, and the blank Rectangle
    //NOTE : Blank Rectangle never moves, its always the last Rectangle
    //in the allocatedParts List, but it gets re-allocated to 
    //different Gri Row/Column
    Rectangle rectCurrent = sender as Rectangle;
    Rectangle rectBlank = allocatedParts[allocatedParts.Count - 1];


    //get current grid row/col for clicked Rectangle and Blank one
    int currentTileRow = (int)rectCurrent.GetValue(Grid.RowProperty);
    int currentTileCol = (int)rectCurrent.GetValue(Grid.ColumnProperty);
    int currentBlankRow = (int)rectBlank.GetValue(Grid.RowProperty);
    int currentBlankCol = (int)rectBlank.GetValue(Grid.ColumnProperty);


    //create possible valid move positions
    List<PossiblePositions> posibilities = 
                          new List<PossiblePositions>();
    posibilities.Add(new PossiblePositions 
        { Row = currentBlankRow - 1, Col = currentBlankCol });
    posibilities.Add(new PossiblePositions 
        { Row = currentBlankRow + 1, Col = currentBlankCol });
    posibilities.Add(new PossiblePositions 
        { Row = currentBlankRow, Col = currentBlankCol-1 });
    posibilities.Add(new PossiblePositions 
        { Row = currentBlankRow, Col = currentBlankCol + 1 });

    //check for valid move
    bool validMove = false;
    foreach (PossiblePositions position in posibilities)
        if (currentTileRow == position.Row && currentTileCol == position.Col)
            validMove = true;

    //only allow valid move
    if (validMove)
    {
        rectCurrent.SetValue(Grid.RowProperty, currentBlankRow);
        rectCurrent.SetValue(Grid.ColumnProperty, currentBlankCol);

        rectBlank.SetValue(Grid.RowProperty, currentTileRow);
        rectBlank.SetValue(Grid.ColumnProperty, currentTileCol);
    }
    else
        return;
}

And that's it. Really simply, as I said in the intro, but you never know you may have just learnt a bit about the WPF ImageBrush. If you have ever seen the Blendables zoom box, it is based on the ImageBrush concepts covered in this article.

Anyway, why not try your luck. Have a go.

Though I must say I have not been able to complete a puzzle at all as yet. I never was much good at these damn puzzles. Grrrrr.

History

  • v1.0: 25/04/08.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
QuestionRandomization leads to unsolvable state Pin
Member 97964446-May-13 6:23
Member 97964446-May-13 6:23 
GeneralSomething's not quite right with the math... Pin
Rei Miyasaka28-Sep-09 0:33
Rei Miyasaka28-Sep-09 0:33 
GeneralGreat article. Pin
pathurun20-Jul-09 5:51
pathurun20-Jul-09 5:51 
GeneralRe: Great article. Pin
Sacha Barber20-Jul-09 6:06
Sacha Barber20-Jul-09 6:06 
GeneralCompatibility Pin
cherishnews18-Dec-08 20:30
cherishnews18-Dec-08 20:30 
GeneralRe: Compatibility Pin
Sacha Barber18-Dec-08 21:54
Sacha Barber18-Dec-08 21:54 
GeneralAgain Thanks Pin
Abhijit Jana11-Aug-08 18:01
professionalAbhijit Jana11-Aug-08 18:01 
GeneralRe: Again Thanks Pin
Sacha Barber11-Aug-08 21:51
Sacha Barber11-Aug-08 21:51 
GeneralPuzzle Solution Pin
Cal Schrotenboer30-Jul-08 10:02
Cal Schrotenboer30-Jul-08 10:02 
GeneralRe: Puzzle Solution Pin
Sacha Barber30-Jul-08 22:04
Sacha Barber30-Jul-08 22:04 
GeneralSimple and neat, very nice! Pin
ravinamballa7-May-08 6:15
ravinamballa7-May-08 6:15 
GeneralRe: Simple and neat, very nice! Pin
Sacha Barber7-May-08 21:10
Sacha Barber7-May-08 21:10 
GeneralWindows Puzzle Foundation Pin
Daniel Vaughan5-May-08 1:31
Daniel Vaughan5-May-08 1:31 
GeneralRe: Windows Puzzle Foundation Pin
Sacha Barber5-May-08 4:15
Sacha Barber5-May-08 4:15 
GeneralExpression Blend Capabilities Pin
BlitzPackage3-May-08 2:24
BlitzPackage3-May-08 2:24 
GeneralRe: Expression Blend Capabilities Pin
Sacha Barber4-May-08 20:29
Sacha Barber4-May-08 20:29 
GeneralFifteen puzzle Pin
Bert delaVega29-Apr-08 6:46
Bert delaVega29-Apr-08 6:46 
GeneralRe: Fifteen puzzle Pin
Sacha Barber30-Apr-08 2:57
Sacha Barber30-Apr-08 2:57 
QuestionWhy didn't you use Web 2.0 AJAX? Pin
Josh Smith29-Apr-08 2:34
Josh Smith29-Apr-08 2:34 
AnswerRe: Why didn't you use Web 2.0 AJAX? Pin
Sacha Barber29-Apr-08 2:39
Sacha Barber29-Apr-08 2:39 
GeneralNice Pin
User 27100929-Apr-08 1:13
User 27100929-Apr-08 1:13 
Cool stuff from the cool guy!

Is there a way to provide a visual clue that the user can click the tile when they mouse over it? Something like the border of the rectange turns red?

It's nice to write a fun article, I need to write some like this, but...

Cheers, Karl

» CodeProject 2008 MVP
» Microsoft MVP - Client App Dev

My Blog | Mole's Home Page | MVP Profile

Just a grain of sand on the worlds beaches.



modified 27-Feb-21 21:01pm.

GeneralRe: Nice Pin
Sacha Barber29-Apr-08 1:36
Sacha Barber29-Apr-08 1:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.