Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Tic Tac Toe Game for Windows Phone 7

DZone's Guide to

Tic Tac Toe Game for Windows Phone 7

· Mobile Zone
Free Resource

Discover how to focus on operators for Reactive Programming and how they are essential to react to data in your application.  Brought to you in partnership with Wakanda

This time I am going to develop a simple Tic Tac Toe (Noughts and Crosses) game for Windows Phone 7. I will begin with Custom User Control that will be used as X/O buttons (using Visual States) and then create a Gameboard. In case you don’t have any idea what is a this game about, find more information at Wikipedia.

eugenedotnet tic tac toe for windows phone 7 example

Source code

Latest version of Tic Tac Toe game is located on CodePlex website.

Step 1: Creating new project

First of all you need to create a new project. To do so open Visual Studio 2010 -> File -> New Project -> select Windows Phone Application there as it is shown on picture bellow.

eugenedotnet creating windows phone 7 project for application bar

 

Step 2: Creating a Custom UserControl for X/O buttons

I have created a video describing how did I created a custom X/O button (Video was recorded while I’ve had Visual Studio for Windows Phone CTP version intalled):

[HD] Windows Phone 7 Custom Control for Tic Tac Toe by EugeneDotnet from Eugene Dotnet on Vimeo.



Here is a XAML markup for TicTacToeButton user control (pay attention to the Visual States):

 

<UserControl x:Class="WindowsPhoneTicTacToe.TicTacToeButton"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
            d:DesignWidth="60" d:DesignHeight="60" Cursor="Hand">
    <Grid x:Name="LayoutRoot" Width="60" Height="60" Opacity="1" Background="Transparent">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="TicTacToeButtonStates">
                <VisualState x:Name="Default"/>
                <VisualState x:Name="Cross">
                    <Storyboard>
                        <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="path" d:IsOptimized="True"/>
                        <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="path1" d:IsOptimized="True"/>
                        <ColorAnimation Duration="0" To="White" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)" Storyboard.TargetName="path1" d:IsOptimized="True"/>
                        <DoubleAnimation Duration="0" To="2" Storyboard.TargetProperty="(Shape.StrokeThickness)" Storyboard.TargetName="path1" d:IsOptimized="True"/>
                        <DoubleAnimation Duration="0" To="1.024" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="path1" d:IsOptimized="True"/>
                        <DoubleAnimation Duration="0" To="0.5" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="path1" d:IsOptimized="True"/>
                        <ColorAnimation Duration="0" To="White" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)" Storyboard.TargetName="path" d:IsOptimized="True"/>
                        <DoubleAnimation Duration="0" To="2" Storyboard.TargetProperty="(Shape.StrokeThickness)" Storyboard.TargetName="path" d:IsOptimized="True"/>
                        <DoubleAnimation Duration="0" To="1.048" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="path" d:IsOptimized="True"/>
                        <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="path" d:IsOptimized="True"/>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="Nought">
                    <Storyboard>
                        <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="path2" d:IsOptimized="True"/>
                        <ColorAnimation Duration="0" To="White" Storyboard.TargetProperty="(Shape.Stroke).(SolidColorBrush.Color)" Storyboard.TargetName="path2" d:IsOptimized="True"/>
                        <DoubleAnimation Duration="0" To="2" Storyboard.TargetProperty="(Shape.StrokeThickness)" Storyboard.TargetName="path2" d:IsOptimized="True"/>
                        <DoubleAnimation Duration="0" To="1.084" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="path2" d:IsOptimized="True"/>
                        <DoubleAnimation Duration="0" To="-3.083" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="path2" d:IsOptimized="True"/>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Path x:Name="path" Data="M-1.5894572E-07,-3.5527137E-15 C4.1656914,4.3908639 15.52839,10.918503 17.333334,16.333334 C22.318741,17.330414 26.620918,23.479309 29.333334,28 C31.796015,32.104469 36.741779,36.356804 40.333332,39.666668" Fill="{x:Null}" Margin="7.333,12,11.333,7.333" Opacity="0" StrokeStartLineCap="Flat" StrokeEndLineCap="Flat" Stroke="Black" StrokeThickness="1" StrokeMiterLimit="10" StrokeLineJoin="Miter" UseLayoutRounding="False" RenderTransformOrigin="0.5,0.5">
            <Path.RenderTransform>
                <CompositeTransform/>
            </Path.RenderTransform>
        </Path>
        <Path x:Name="path1" Data="M40.666668,-3.5527137E-15 C35.157913,3.7949185 27.844921,7.0956602 23.666666,12.666667 C20.548512,16.824207 16.592587,20.476198 13.333333,24.666666 C8.6571426,30.678911 4.3249264,36.545231 0,42.666668" Fill="{x:Null}" Margin="6,12,12.333,4.333" Opacity="0" StrokeStartLineCap="Flat" StrokeEndLineCap="Flat" Stroke="Black" StrokeThickness="1" StrokeMiterLimit="10" StrokeLineJoin="Miter" UseLayoutRounding="False" RenderTransformOrigin="0.5,0.5">
            <Path.RenderTransform>
                <CompositeTransform/>
            </Path.RenderTransform>
        </Path>
        <Path x:Name="path2" Data="M21.386688,0.66666698 C18.104256,0.75869775 17.090931,-0.16339432 14.386687,2.0000002 C12.42321,3.5707819 10.433279,3.7392514 8.7200203,5.666667 C6.4274411,8.2458191 5.5025487,7.6524167 4.3866873,11 C3.5323884,13.562898 3.0594554,15.975595 2.3866875,18.666666 C1.0282598,24.100378 -1.137174,26.809351 0.72002077,33 C1.3220947,35.006912 3.1331961,37.458286 4.0533543,39.666668 C5.114325,42.212997 6.5792308,43.366936 9.7200203,44.333332 C15.411503,46.08456 20.752144,46 27.053354,46 C31.401215,46 34.719112,42.44838 36.053352,36.666668 C37.463272,30.557018 38.386688,25.955296 38.386688,19.333334 C38.386688,12.770806 34.890102,10.843581 33.053352,5.3333335 C29.067465,4.004704 25.056402,1.8348579 21.386688,3.1789145E-07 C21.386688,0.22222254 21.386688,0.44444478 21.386688,0.66666698 z" Fill="{x:Null}" Margin="7.28,9.333,13.333,3.666" Opacity="0" StrokeStartLineCap="Flat" StrokeEndLineCap="Flat" Stroke="Black" StrokeThickness="1" StrokeMiterLimit="10" StrokeLineJoin="Miter" UseLayoutRounding="False" RenderTransformOrigin="0.5,0.5">
            <Path.RenderTransform>
                <CompositeTransform/>
            </Path.RenderTransform>
        </Path>
    </Grid>
</UserControl>

Add the following code to codebehind of control that allows to switch between Visual States:

private bool? isCross = null;
public bool? IsCross
{
   get
   {
      return isCross;
   }
   set
   {
      VisualStateManager.GoToState(this, (value.HasValue ? (value.Value ? "Cross" : "Nought") : "Default" ) , false);
      isCross = value;
    }
}

Changing this property will make TicTacToeButton change it’s visual state to ”’cross”’, ”’nought”’ or ”’default”’. If value in setter is “null” then button has “Default” state applied (meaning nothing is shown), if value is “true” then state of button will change to “Cross” and, finally, if value is “false” then “Nought”. For example, if TicTacToeButton’s state is “Default” then neither of players have not pushed that button yet. Also make sure that LayoutRoot grid in TicTacToeButton had Opacity set to 1.

Step 3: Changing default task

After new project is created we can change the default XAML page from MainPage.xaml to Gameboard.xaml. It can be done within WMAppManifest.xml file (Properties package).

windows phone changing default xaml page

There you need to change the NavigationPage value of DefaultTask element:

<Tasks>
   <DefaultTask  Name ="_default" NavigationPage="Gameboard.xaml"/>
</Tasks>

Step 4: Creating a gameboard

Now let’s move on to our Gameboard. Create a new Windows Phone portrait page and call it Gameboard. There we need to change ContentGrid in XAML. We will add a grid for a gameboard with 9 instances of TicTacToeButton, 2 horizontal lines and 2 vertical lines as inner borders, a textblock displaying current status of application and a “Play again?” button. My advice to you is to use ”’Microsoft Expression Blend 4”’ (with Silverlight 4 support), for example, for drawing custom lines as grid borders to make them look more realistic. ContentGrid XAML will now look like that:

<Grid x:Name="ContentGrid" Grid.Row="1">
 
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
 
    <TextBlock Height="53" HorizontalAlignment="Left" Margin="72,78,0,0" Name="tbStatus" Text="TextBlock" VerticalAlignment="Top" Width="340" FontSize="32" TextAlignment="Center" Grid.Row="0" />
    <Canvas x:Name="canvasGameboard" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="1" Height="200" Width="200">
        <Grid x:Name="gridGameboard">
 
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="10"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="10"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
 
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="10"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="10"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
 
            <Path Grid.Column="1" Data="M64.333336,4.6666665 C62.924431,20.164593 64.127037,36.65147 65.333336,52.333332 C66.558273,68.257599 63.498032,84.145035 65,100.66666 C66.361046,115.63819 64.666664,132.64523 64.666664,148.33333 C64.666664,156.89328 64.333336,165.07155 64.333336,173.66667 C64.333336,182.08456 67.718994,189.40503 66,198 C65.777779,197.55556 65.623878,197.06978 65.333336,196.66667 C65.241455,196.5392 65.090111,196.46207 65,196.33333 C64.909889,196.20461 64.758545,196.12747 64.666664,196 C64.521393,195.79845 64.534637,195.18771 64.333336,195.33333" Margin="3.365,4.667,2.513,1" Grid.RowSpan="5" StrokeStartLineCap="Flat" Stretch="Fill" StrokeEndLineCap="Flat" Stroke="White" StrokeThickness="2" StrokeMiterLimit="10" StrokeLineJoin="Miter" UseLayoutRounding="False"/>
            <Path Grid.Column="3" Data="M134.33333,4.3333335 C132.37093,20.567755 133.83511,35.186302 135.33333,51.666668 C136.78621,67.648277 134,82.84716 134,99 C134,115.52766 130.90732,134.11053 133.66667,150.66667 C135.1199,159.38612 133.33333,166.49176 133.33333,175 C133.33333,183.39162 131.75618,189.55441 134,197.66667" Margin="1.521,3.333,3.247,1.333" Grid.RowSpan="5" StrokeStartLineCap="Flat" Stretch="Fill" StrokeEndLineCap="Flat" Stroke="White" StrokeThickness="2" StrokeMiterLimit="10" StrokeLineJoin="Miter" UseLayoutRounding="False"/>
            <Path Grid.ColumnSpan="5" Data="M195.66667,65.333336 C180.5721,67.724548 165.25514,67.598076 149.66667,65 C132.7099,62.173874 116.64419,66.333336 99.666664,66.333336 C67.673195,66.333336 35.10807,66.211624 3,65" Margin="2,3.016,3.333,1.958" Grid.Row="1" StrokeStartLineCap="Flat" Stretch="Fill" StrokeEndLineCap="Flat" Stroke="White" StrokeThickness="2" StrokeMiterLimit="10" StrokeLineJoin="Miter" UseLayoutRounding="False"/>
            <Path Grid.ColumnSpan="5" Data="M2.3333333,136.66667 C10.924497,136.66667 18.328968,134.33333 26.666666,134.33333 C34.2099,134.33333 40.833973,135.33333 48.333332,135.33333 C63.764233,135.33333 79.204323,135.33333 94.666664,135.33333 C128.60835,135.33333 161.59547,137.31277 195.33333,134.66667" Margin="1.333,3.333,3.667,2.333" Grid.Row="3" StrokeStartLineCap="Flat" Stretch="Fill" StrokeEndLineCap="Flat" Stroke="White" StrokeThickness="2" StrokeMiterLimit="10" StrokeLineJoin="Miter" UseLayoutRounding="False"/>
            <local:TicTacToeButton HorizontalAlignment="Left" x:Name="ticTacToeButton1" VerticalAlignment="Top" Height="60" Width="60" MouseLeftButtonDown="button_MouseLeftButtonDown" />
            <local:TicTacToeButton Grid.Column="2" HorizontalAlignment="Left" Margin="1,0,0,0" x:Name="ticTacToeButton2" VerticalAlignment="Top" Height="60" Width="60" MouseLeftButtonDown="button_MouseLeftButtonDown" />
            <local:TicTacToeButton Grid.Column="4" HorizontalAlignment="Left" Margin="2,0,0,0" x:Name="ticTacToeButton3" VerticalAlignment="Top" Height="60" Width="60" MouseLeftButtonDown="button_MouseLeftButtonDown" />
            <local:TicTacToeButton Grid.ColumnSpan="2" Grid.Row="2" HorizontalAlignment="Left" Margin="2,1,0,0" x:Name="ticTacToeButton4" VerticalAlignment="Top" Height="60" Width="60" MouseLeftButtonUp="button_MouseLeftButtonDown" />
            <local:TicTacToeButton Grid.Column="2" Grid.Row="2" HorizontalAlignment="Left" Margin="1,0,0,0" x:Name="ticTacToeButton5" VerticalAlignment="Top" Height="60" Width="60" MouseLeftButtonUp="button_MouseLeftButtonDown" />
            <local:TicTacToeButton Grid.Column="4" Grid.Row="2" HorizontalAlignment="Left" Margin="2,1,0,0" x:Name="ticTacToeButton6" VerticalAlignment="Top" Height="60" Width="60" MouseLeftButtonUp="button_MouseLeftButtonDown" />
            <local:TicTacToeButton Grid.ColumnSpan="2" Grid.Row="4" HorizontalAlignment="Left" Margin="2,0,0,0" x:Name="ticTacToeButton7" VerticalAlignment="Top" Height="60" Width="60" MouseLeftButtonUp="button_MouseLeftButtonDown" />
            <local:TicTacToeButton Grid.Column="2" Grid.Row="4" HorizontalAlignment="Left" Margin="1,0,0,0" x:Name="ticTacToeButton8" VerticalAlignment="Top" Height="60" Width="60" MouseLeftButtonUp="button_MouseLeftButtonDown" />
            <local:TicTacToeButton  Grid.Column="4" Grid.Row="4" VerticalAlignment="Top" MouseLeftButtonUp="button_MouseLeftButtonDown" HorizontalAlignment="Left" Height="60" Width="60" Margin="2,0,0,0" x:Name="ticTacToeButton9"  />
        </Grid>
    </Canvas>
    <Button Content="Play again?" Height="70" HorizontalAlignment="Right" Margin="0,20,123,0" Name="btnReplay" VerticalAlignment="Top" Width="230" Click="btnReplay_Click" Grid.Row="2" />
 
</Grid>

Step 5: Game logic

I will not describe each part of code file in my lesson, you can view the full code of application on CodePlex(link above). Definitely, most important method is a mouse click event on one of TicTacToe buttons. This method checks if the game is still active (if there are some unpressed buttons and none of player has won), changes a state of a pressed TicTacToeButton, checks if the current player has won and if the game is over. Here is a code for that method:

void button_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
      if (IsActive)
      {
         TicTacToeButton button = sender as TicTacToeButton;
         // if button haven't been pressed before
         if (button != null && !button.IsCross.HasValue)
         {
            button.IsCross = IsCross;
            if (!CheckGameboard())
            {
               // change visual state of a button
               IsCross = !IsCross;
            }
            else
            {
               // game over
               IsActive = false;
            }
         }
      }
}

Here is a video demonstration:




Learn how divergent branches can appear in your repository and how to better understand why they are called “branches".  Brought to you in partnership with Wakanda

Topics:

Published at DZone with permission of Jevgeni Tšaikin. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}