Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Tic Tac Toe Game using Microsoft Silverlight

0.00/5 (No votes)
29 May 2014 1  
This tip shows how we can use Silverlight cool features to make a tic-tac-toe game in the web.

Introduction

I'm using Microsoft Silverlight to develop Tic Tac Toe game. I am going to show how to programmatically develop an interactive simple game in a web environment and extend this game in a cool way.

Using the Code

I'm not going to spend time to develop a fancy code; however, I strongly recommend you guys to follow the code convention and probably a more modular code if you can.

The first thing you need to do is create a Silverlight project, please remember if Visual Studio complains about creating such a project, you might have installed a wrong version. I had this issue as Visual Studio automatically referred me to a 32bit version while my machine is a 64bit. Also, I realized if you install ReSharper 8.2.0.2160, which can be used for refactoring your coding in Visual Studio, it may mess up with Silverlight and you need to re-install Silverlight.

When you are good to go with your Silverlight application, change the XAML code of your user control, i.e., MainPage.XAML, as follows:

<Grid ShowGridLines="False" 
        x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="70" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="150" />
        </Grid.ColumnDefinitions>
</Grid>  

The above code makes the grid on your user control as shown in Figure 1.

Figure 1. The initial grid designed for our user control.

The next step is to add the Tik-Tak-Toe board. I use the following code to make the board:

<Grid ShowGridLines="False" x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="70" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="150" />
        </Grid.ColumnDefinitions>
        <Grid Grid.Row="1" Grid.Column="1" ShowGridLines="True" Background="BlanchedAlmond" Name="GridBoard">
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Button Width="30" Height="30" Content="" 
            Grid.Row="0" Grid.Column="0" Name="btn00"/>
            <Button Width="30" Height="30" Content="" 
            Grid.Row="0" Grid.Column="1" Name="btn01"/>
            <Button Width="30" Height="30" Content="" 
            Grid.Row="0" Grid.Column="2" Name="btn02"/>
            <Button Width="30" Height="30" Content="" 
            Grid.Row="2" Grid.Column="0" Name="btn20"/>
            <Button Width="30" Height="30" Content="" 
            Grid.Row="2" Grid.Column="1" Name="btn21"/>               
            <Button Width="30" Height="30" Content="" 
            Grid.Row="2" Grid.Column="2" Name="btn22"/>            
            <Button Width=" 30" Height="30" Content="" 
            Grid.Row="1" Grid.Column="0" Name="btn10"/>
            <Button Width=" 30" Height="30" Content="" 
            Grid.Row="1" Grid.Column="1" Name="btn11"/>
            <Button Width=" 30" Height="30" Content="" 
            Grid.Row="1" Grid.Column="2" Name="btn12"/>          
            </Grid>
</Grid> 

The result is shown in Figure 2. You can change the "Width" and "Height" properties in any Row/ColumnDefinition to change the size/flexibility of board cells.

Figure 2. The game board.

After making the board, I add a method to the code behind as follows and assign this method to be run for Click event on each Button on the board.

private void btn_Click(object sender, RoutedEventArgs e)
        {
            if (game_ended)
                return;
            if ((sender as Button).Content != "X" && (sender as Button).Content != "O")
            {
                (sender as Button).Content = m_turn == 1 ? "X" : "O";
                int len = (sender as Button).Name.Length;
                int x = int.Parse((sender as Button).Name[len - 2].ToString());
                int y = int.Parse((sender as Button).Name[len - 1].ToString());
                if (m_board[x, y] == 0)
                {
                    m_board[x, y] = m_turn;
                    moveNo++;
                    if (CheckWinner(x, y))
                    {
                        MessageBox.Show(string.Format("The winner is Player {0}", m_turn));
                        game_ended = true;
                    }
                    else
                    {
                        if (moveNo == MaxMoveNo)
                        {
                            MessageBox.Show("Game result is a draw");
                            game_ended = true;
                        }
                        m_turn = (m_turn % 2) + 1;
                    }
                }
            }
        } 

In the above method, I have a global variable called game_ended which is used to stop the game if it is already ended. After this checking, I examine if the caller of this method is a Button with a label not equal to 'X' and 'O', which means this button is not played before. If the condition is true, I check the player turn using a global variable called m_turn, then I find out the index of the button in the grid, please remember we named the buttons as btn00, btn01, ..., btn22, so I use a trick to parse the name of the button to get those two digits at its end which can associate the coordinate of the button. After finding the coordinate of the clicked button in the grid, I check if a global variable m_board which represents the actual game, initially contains 0s, at that index given by x and y is not taken before, if so I change the value of that element with the value of m_turn. moveNo is another global variable which includes the total number of moves up to now, if it exceeds the MaxMoveNo which equals the number of elements in the board, we should stop the game with a draw result. Later, I will assign this general behavior for all the buttons so I avoid defining different methods for each click event on each button.

The game GUI is shown in Figure 3.

Figure 3. The sample game.

I have a CheckWinner method as follows which checks if the current player wins:

private bool CheckWinner(int x, int y)
        {
            int n = MaxRow;
            // check the row
            for (int i = 0; i < n; i++)
            {
                if (m_board[i, y] != m_turn)
                    break;
                if (i == n - 1)
                    return true; //already win
            }
            // check the col
            for (int i = 0; i < n; i++)
            {
                if (m_board[x, i] != m_turn)
                    break;
                if (i == n - 1)
                    return true; //already win
            }
            if (x == y)
            {
                // check the diag
                for (int i = 0; i < n; i++)
                {
                    if (m_board[i, i] != m_turn)
                        break;
                    if (i == n - 1)
                        return true; //already win
                }
                // check the anti diag
                for (int i = 0; i < n; i++)
                {
                    if (m_board[i, n - i - 1] != m_turn)
                        break;
                    if (i == n - 1)
                        return true; //already win
                }
            }
            return false;
        } 

CheckWinner method checks all possible ways of winning including the rows, columns, and (anti/)diagonals. A scenario when user 2 wins is shown in Figure 4.

Figure 4. The winner notification.

Finally, I use some tricks to generalize the game for a bigger/smaller board size using a button called btnAddButton where by clicking on that, I hide the board and show another grid where the user can define the new board size.

GridBoard.Visibility = System.Windows.Visibility.Collapsed;
gridInput.Visibility = System.Windows.Visibility.Visible; 

gridInput is defined as follows:

<Grid Grid.Column="2" Grid.Row="1" Height="239" 
HorizontalAlignment="Left" Margin="9,8,0,0" 
Name="gridInput" VerticalAlignment="Top" 
Width="139" Visibility="Visible" 
BindingValidationError="gridInput_BindingValidationError">
            <TextBox Height="23" HorizontalAlignment="Left" 
            Margin="52,20,0,0" Name="txtRows" 
            VerticalAlignment="Top" Width="58" 
            Text="{Binding Rows, Mode=TwoWay, ValidatesOnExceptions=True, 
            NotifyOnValidationError=True, UpdateSourceTrigger=Explicit}"/>
            <TextBox Height="23" HorizontalAlignment="Left" 
            Margin="52,57,0,0" Name="txtCols" 
            VerticalAlignment="Top" Width="58" />
            <TextBlock Height="23" HorizontalAlignment="Left" 
            Margin="16,24,0,0" Name="textBlock1" 
            Text="Rows" VerticalAlignment="Top" />
            <TextBlock Height="23" HorizontalAlignment="Left" 
            Margin="16,61,0,0" Name="textBlock2" 
            Text="Cols" VerticalAlignment="Top" />
            <Button Content="Set" Height="23" 
            HorizontalAlignment="Left" Margin="35,95,0,0" 
            Name="btnSet" VerticalAlignment="Top" 
            Width="75" Click="btnSet_Click" />
            <Button Content="Cancel" Height="23" 
            HorizontalAlignment="Left" Margin="35,136,0,0" 
            Name="btnCancel" VerticalAlignment="Top" 
            Width="75" Click="btnCancel_Click" />
</Grid> 

By clicking on btnSet button, btnSet_Click gets called which contains the following code:

 private void btnSet_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                txtRows.GetBindingExpression(TextBox.TextProperty).UpdateSource();
            }
            catch(Exception ex)
            {
                return;
            }
            if (_rows == 0)
                return;
            GridBoard.Children.Clear();
            GridBoard.RowDefinitions.Clear();
            GridBoard.ColumnDefinitions.Clear();
            MaxRow = _rows;//int.Parse(txtRows.Text);
            for (int i = 0; i < MaxRow; i++)
            {
                GridBoard.RowDefinitions.Add(new RowDefinition());
                GridBoard.ColumnDefinitions.Add(new ColumnDefinition());
            }
            for (int i = 0; i < MaxRow; i++)
                for (int j = 0; j < MaxRow; j++)
                {
                    Button btn = new Button();
                    btn.Width = 30;
                    btn.Height = 30;
                    btn.Content = "";
                    btn.SetValue(Grid.RowProperty, i);
                    btn.SetValue(Grid.ColumnProperty, j);
                    btn.Name = string.Format("btn{0}{1}", i, j);
                    btn.Click += btn_Click;
                    GridBoard.Children.Add(btn);
                }
            ResetBoard();
            gridInput.Visibility = System.Windows.Visibility.Collapsed;
            GridBoard.Visibility = System.Windows.Visibility.Visible;
        } 

A scenario when I created a 5x5 board is shown in Figure 5 and 6.

Figure 5. Creating a new board.

Figure 6. 5x5 board.

First, btnSet_Click method checks if the value entered in the txtRows number of rows in the board, is correct. Then _rows which is a global variable contains the number of rows and previously associated to them which is a control to enter the desired txtRows.

 public int Rows
        {
            get { return _rows; }
            set
            {
                if (value > 9 || value < 1)
                {
                    _rows = 0;
                    throw new Exception("Please enter a value between 1 - 11.");
                }
                _rows = value;
            }
        } 

and in the XAML side, we need to add:

<TextBox Height="23" HorizontalAlignment="Left" Margin="52,20,0,0" 
        Name="txtRows" VerticalAlignment="Top" Width="58" 
        Text="{Binding Rows, Mode=TwoWay, ValidatesOnExceptions=True, 
        NotifyOnValidationError=True, UpdateSourceTrigger=Explicit}"/> 

The error message when trying to create a 10x10 board is shown in Figure 7.

Figure 7. Error while making a wrong sized board.

The whole code is attached.

Good luck with your coding.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here