TileGame in WPF

In this WPF article you will see how to create a TileGame in WPF with Undo and Replay.



Introduction

WPF seems to be hot topic in windows developer community these days , so i wanted to explore and learn it by by writing some application and it appeared like TileGame (See Fig.1) which I used to play as a child seemed perfect fit for this and becuase software provides much more flexibility than plastic case, we can make playing this game a much better experience and thought following requirements would help in it.

  1. Tiles should look like rounded squares than regular squares
  2. When we place mosue on Tile square should change into circle with rainbow colors
  3. When we click on Tile, It should move to empty square with animation
  4. When we all Tiles are in order , computer should congratulate us.
  5. It should be possible to know the number of moves.
  6. It should be possible to do undo
  7. If we win, It should be possible to Replay from beginning.

So with this goals in mind, I wrote the game and used following concepts in WPF.
  1. Layouts in WPF
  2. Attached properties
  3. Styles
  4. Control Templates.
  5. Gradient Brushes
  6. Dispatch Timer

The following sections will explain the concepts in relation to the requirements in Game.

Tile Game in wpf

GameBoard and Layouts in WPF

Designing UI in WPF is quite different from earlier Dialog or Form based applications with introduction of layout panles, in past if we wanted to create a layout as shown in Fig.(1) we will create a dialog and drag buttons on it, however we will have to code to make sure that layout looks good when dialog resizes or maximizes,etc..but in WPF all these problems can be solved using panels, panels let you define how you want things to be arranged, so you starting adding contents(here buttons) to layout and layout automatically handles the measuring, sizing and positioning of the contents in appropriate way.

WPF provides with several builtin panels, some of them are
  1. Canvas
  2. StackPanel
  3. DockPanel
  4. WrapPanel
  5. GridPanel

And Custom panel can be written by deriving from panel class and overriding/implementing MeasureOverride() and ArrangeOverride() functions,However GridPanel arranges contents into a Grid, so this would be perfectly suited for this game and we need not write custom panel.

Tiles with Numbers and Buttons in WPF


Now that Gameboard layout is designed it is time to think about conents of Grid, for conents we need something lookling like squares and when we click on it, it should move to empty squares. click is natural on button, so we will use contents of Grid as buttons with Numbers. (GridContent=>Buttons, ButtonContent=>Number).

Customizing Tiles (Buttons) with Control Templates
  1. Tiles should look like rounded squares than regular squares

    So we have requirement that buttons should appear as white rounded rectangles than having normal look and feel, here again WPF is at its best..it clearly separates controls behaviour from controls look and feel. all the look and feel of control is separated into a "ControlTemplate" and every control comes with a default one and the most fascinating part is we can easily override it.

    So we want to define button which looks like a white rounded rectangle, so first we will have to define a style which applies to all buttons and define a control template.

            <Style TargetType="local:GameButton">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="local:GameButton">
                            <Rectangle x:Name="mainButton" Fill="White">
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>

    This will effect all buttons and they look like WhiteRectangles, but they will neither have content nor behave like button (when we click they reamin same, no normal visual effects), to put the content back on the buttons we need to use special framework element called content presenter , WPF renders content when it encounters content presenter element, but template can hold only one object, so we need to select element which has children, here i have used Grid. and it looks like below

            <Style TargetType="local:GameButton">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="local:GameButton">
                            <Grid>
                                <Rectangle x:Name="mainButton" Fill="White" Stroke="DarkGray" StrokeThickness="5" RadiusX="5" RadiusY="5">
                                    <ContentPresenter Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center" />
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
     
  2. When we place mosue on Tile square should change into circle with rainbow colors

    Control Template provides "Triggers" which allows controls to react to user events such as mouse move , mouse click,etc..so we want to button to change into circle with rainbow colors, so we need to define a rectangle and use radialgradientbrush with various gradientstops to render such rectangle, the code i used is shown below

            <Rectangle x:Name="buttonHoverButton" Opacity="0" RadiusX="5" RadiusY="5">
                <Rectangle.Fill>
                    <RadialGradientBrush>
                        <GradientStop Color="White" Offset="1"/>
                        <GradientStop Color="Indigo" Offset="0.9"/>
                        <GradientStop Color="Blue" Offset="0.8"/>
                        <GradientStop Color="LawnGreen" Offset="0.7"/>
                        <GradientStop Color="Yellow" Offset="0.6"/>
                        <GradientStop Color="Orange" Offset="0.5"/>
                        <GradientStop Color="Violet" Offset="0.4"/>
                        <GradientStop Color="Transparent" Offset="0"/>
                    </RadialGradientBrush>
                </Rectangle.Fill> 

    These are not exactly rainbow colors, but are almost similar exculding red but not in order, but looked enough, that last brush "<GradientStop Color="Transparent" Offset="0"/>" is necessary so that content shows up.

    Now that we have look for button ready, we need to associate this with "MouseEnter" event and for this we need to use triggers provided by control template. we will do it in following way

            <ControlTemplate.Triggers>
                <EventTrigger RoutedEvent="Button.MouseEnter">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetName="buttonHoverButton" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.25" />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>

Storyboard is for animating effect when mouse enters the square, the rectangle will be invisible and will become visible in the duration specified.
Similarly other effects "Button.MouseLeave", "Button.IsPressed" are implemented.

Creating Tiles and Placing in Random Positions

Once buttons are created, random numbers are generated and Tiles are randomly placed into positions genereated by random numbers.

The functions

GenerateRandomNumbersList()
RandomizeTiles()

Have necessary code, code uses attached properties explained in next section.

Number of tiles can be changed in MainWindow class, changing nRows, nCols to 4 will generate 16 squares (15 tiles) Game.

Moving the Tiles and attached properties


Effectively Moving Button from one sqaure to empty square is effectively changing the row and column of grid and it would be nice if these things comes as method on button, so we derive a class called GameButton from Button and implement Move() Method as follows.

        public void Move()
        {           
            if (Math.Abs(emptysquare - position) == diff| Math.Abs(emptysquare - position) == 1)
            {
                lastemptysquare = emptysquare;
               
//valid move //place from position to empty square
                int emptysquarerow = emptysquare / diff;
                int emptysquarecolumn = emptysquare % diff; 
                SetValue(Grid.RowProperty, emptysquarerow);
                SetValue(Grid.ColumnProperty, emptysquarecolumn);
 
               
//swap empty square and position
                int temp = emptysquare;
                emptysquare = position;
//new emptysquere
                position = temp;
               
                PropertyChanged(this, new PropertyChangedEventArgs("MoveComplete"));
            } 
        }

If we want to place a button in Row "0" and Column "1", we would write in XAML as

<Button Grid.Row="0" Grid.Column="1">

But as you see the position(row,col) of Button is not the member of Button, instead layout properties (in this case "Grid") are used and these are called attached properties, becuase they are associated or attached to child (here button) though they belong to control (Grid).

The code equivalent

button.SetValue(Grid.RowProperty, 0)
button.SetValue(Grid.ColumnProperty, 1)


Makes it more clear (notice that it is "Grid.RowProperty" than "Grid.Row").Similar concepts are used in Placing Tiles in Random Positions
It is easy to maintain number of moves using integer variable and number of moves appear on the title bar of game.

Detecting the winning condition

The GameButton has following functionality. It assigns Id to each button and also tracks the position (Row and Column) of button. The Game will be complete if id and position of button match (i.e button 0 should be in location 0, button 1 should be in location 1, etc...).

checkforwinner() has necessary code.

Undo and Replay

  1. Undo:

    Undo is fairly simple becuase of symmetric nature of moment in buttons, forward move is moving button to empty square, and to undo it we simply need to click the same button again and it moves to the old position. so we just need to keep track of button objects in a stack and call move() method to undo the move. because we maintain stack, we can do a repeated undo to reach the original gameboard state, which was there  at beginning of the game.
     
  2. Replay:

    However for replay we need to know which buttons are pressed and in what order to replay it, so we need to know the id and position of class and we need to keep track of this state as game progresses, so GameStateButton class is designed to keep track of id and position of buttons moved and the list of these objects is used to replay.
    (however for replay we need to use DispatchTimer, so that user sees replay gradually than in an instant)

You will have to use ContextMenu (RightClick) to use these features and Replay is Enabled only after winning the Game. (winning is partially dependent on Initial configuration of numbers, sometimes it generates configurations which are not possible to solve, so if you feel game is becoming tough..just close and open a new game)

Conclusion

The concepts explained above clearly shows how WPF makes UI programming an easy job and makes a programmer productive even with minimum knowledge. (writing the same game in MFC would have been rather tedious). The game logic is not dependent on contents and we can set button contnet to images or alphabets and the game logic should probably work (I didnt try though).the program was developed in spare time and I am still learning WPF, so if you find bugs are better implementation , do not blame me :)