Creating Multi-Player Experiences Using NFC and Wi-Fi* Direct On Intel® Atom™ Processor-Based Tablets

Creating Multi-Player Experiences Using NFC and Wi-Fi* Direct On Intel® Atom™ Processor-Based Tablets (PDF 509 KB)

Abstract

This article intends to serve as a tutorial on how to use near field communications (NFC) and Wi-Fi Direct in developing multi-device and multi-user application experiences on Intel® Atom™ processor-based tablets running Microsoft Windows 8*. A simple game app is used as an example to describe how to enable the proximity capabilities in the new Windows Store apps.

1 NFC and Wi-Fi Direct on Intel Atom Processor-based Tablets

Intel Atom processor-based tablets running Windows 8 support Near Field Communications (NFC) and Wi-Fi Direct through Windows.Networking.Proximity namespace API.

NFC is a set of communication standards that allows peer-to-peer data exchanges between two closely held devices. If two devices are held closely to each other (less than 4 centimeters) or are simply “tapped” together, the NFC components on the devices can discover each other and the operating systems can set up connection between the two devices to exchange data.

Wi-Fi Direct is a technology that allows Wi-Fi devices to directly connect to each other without connecting to a wireless access point. Using Wi-Fi Direct, a device can browse for other devices within the Wi-Fi radio range, and request direct connection with these devices.

Based on the Windows Proximity API on NFC and Wi-Fi Direct, we can create multi-device, multi-user experiences on tablets with Intel Atom processors.

2 Windows Proximity API

On Windows 8, the Windows.Networking.Proximity namespace API provides access to NFC and Wi-Fi Direct hardware features. In the following sections, we will go through the process of developing a “Tic-Tac-Toe” game app (Figure 1) using Microsoft Visual Studio 2012* and C# to show how to use Windows Proximity API to create connections between two proximity devices.

An important note is this app must be run on two devices equipped with a NFC component and/or a Wi-Fi adapter that supports Wi-Fi Direct.


Figure 1 The Tic-Tac-Toe game

3 Developing a Multi-Player Game Using Windows Proximity API

To start the Tic-Tac-Toe game app development, we assume you have already installed and set up Visual Studio 2012 on your Windows 8 development system, and you have basic knowledge of the C# programming language and XAML. To learn using C# and XAML to develop Windows Store apps, please go to the link http://msdn.microsoft.com/en-us/library/windows/apps/hh974581.aspx.

3.1 Create a C# Project and Enable Proximity Capabilities

Let’s start with creating the C# project. Open Visual 2012, select File->New->Project…, then select “Visual C#” on the left pane, and “Blank App (XAML)” on the right pane. In the project name field, enter “TicTacToe” (Figure 2), and click “OK”.


Figure 2 Creating a new C# project

Now we have the project created. On the “Solution Explorer” window we can see a file named “Package.appxmanifest”. Double click the file to open it, and select the “Capabilities” tab within the view of the file, and check the “Proximity” capability item (Figure 3).


Figure 3 Adding proximity in the application capabilities

3.2 Add the UI XAML Code

Now let’s define the Tic-Tac-Toe game UI in XAML. On Solution Explorer screen, double click the file “MainPage.xaml” to open it. Copy the contents in Figure 4 and paste to overwrite and replace the original MainPage.xaml file content.

<Page
    x:Class="TicTacToe.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:TicTacToe"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid x:Name="LayoutRoot"  >
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <StackPanel Height="Auto" VerticalAlignment="Top" HorizontalAlignment="Left" Grid.Column="0">
            <GridView x:Name="TicTacToeGridView" Width="Auto" Height="Auto" Background="Green"
                        BorderBrush="LightGray"
                BorderThickness="2" VerticalAlignment="Stretch" ScrollViewer.VerticalScrollBarVisibility="Auto"
                ScrollViewer.HorizontalScrollBarVisibility="Auto" IsItemClickEnabled="True"
                >
                <GridView.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapGrid Orientation="Horizontal" MaximumRowsOrColumns="3"/>
                    </ItemsPanelTemplate>
                </GridView.ItemsPanel>
                <Button x:Name="Grid_Button00" Click="HandleClick_Button00" Height="200" Width="200" />
                <Button x:Name="Grid_Button01" Click="HandleClick_Button01" Height="200" Width="200" />
                <Button x:Name="Grid_Button02" Click="HandleClick_Button02" Height="200" Width="200" />
                <Button x:Name="Grid_Button03" Click="HandleClick_Button03" Height="200" Width="200" />
                <Button x:Name="Grid_Button04" Click="HandleClick_Button04" Height="200" Width="200" />
                <Button x:Name="Grid_Button05" Click="HandleClick_Button05" Height="200" Width="200" />
                <Button x:Name="Grid_Button06" Click="HandleClick_Button06" Height="200" Width="200" />
                <Button x:Name="Grid_Button07" Click="HandleClick_Button07" Height="200" Width="200" />
                <Button x:Name="Grid_Button08" Click="HandleClick_Button08" Height="200" Width="200" />
            </GridView>
        </StackPanel>
        <StackPanel Orientation="Vertical"  HorizontalAlignment="Left" Margin="10,10,10,10" Grid.Column="1"  >
            <Button x:Name="StartPlayGameButton" Content="Start Game" Background="Blue" Visibility="Collapsed" Margin="0,0,10,0"/>
            <Button x:Name="FindPlayersButton" Content="Find Players" Background ="Blue" Visibility="Collapsed" Margin="0,0,10,0"/>
            <Button x:Name="InvitPlayerButton" Content="Invite A Player" Background="Blue" Visibility="Collapsed" Margin="0,0,10,0"/>
            <Button x:Name="AcceptButton" Content="Accept The Invitation" Background="Blue" Visibility="Collapsed" Margin="0,0,10,0"/>
            <Button x:Name="EndGameButton" Content="End This Game" Background="Blue" Visibility="Collapsed" Margin="0,0,10,0"/>
            <Button x:Name="ReplayGameButton" Content="Play Again" Background="Blue" Visibility="Collapsed" Margin="0,0,10,0"/>
            <TextBlock x:Name="OutputTextBlock"  FontSize="20" Margin="0,0,10,0" />
            <ListBox x:Name="ProximityDeviceList" Background="LightBlue" >
               
            </ListBox>
        </StackPanel>
    </Grid>
</Page>

Figure 4 The MainPage.xaml file (**)

You will see the Tic-Tac-Toe basic UI appear on the design view (Figure 5).


Figure 5 The MainPage.xaml Design view

3.3 Attach PeerFinder Event Handlers and Start PeerFinder

In this game app, we support setting up device connections using either NFC tap gestures or Wi-Fi Direct peer browsing. This is done by handling the PeerFinder.TriggeredConnectionStateChanged and PeerFinder.ConnectionRequested events. We attach these event handlers in the “Start Game” button click event handler. We then start PeerFinder. Figure 6 shows the “Start Game” button click event handler code, which is in the C# file named MainPage.xaml.cs.

        void StartPlayGame(object sender, RoutedEventArgs e)
        {
            if (!_peerFinderStarted)
            {
                // tap triggered state change handler
                PeerFinder.TriggeredConnectionStateChanged += new TypedEventHandler<object, TriggeredConnectionStateChangedEventArgs>(TriggeredConnectionStateChangedEventHandler);
                // ConnectionRequested event handler
                PeerFinder.ConnectionRequested += new TypedEventHandler<object, ConnectionRequestedEventArgs>(ConnectionRequestedEventHandler);

                // start the proximate device peer finder
                PeerFinder.Start();
                _peerFinderStarted = true;

            }
        }

Figure 6 Attaching the PeerFinder event handlers (**)

3.4 Handle PeerFinder Events

Now let’s implement the two PeerFinder event handlers. In the connection triggered by tap gestures, after the connection is set up, we start data read and write using the Proximity stream socket created during the connection setting up. If the connection is initiated using Wi-Fi Direct peer browsing, we will display an “Accept” button to let the user confirm the connection.

        async private void TriggeredConnectionStateChangedEventHandler(object sender, TriggeredConnectionStateChangedEventArgs eArgs)
        {
             if (eArgs.State == TriggeredConnectState.PeerFound)
            {
                // devices tap and detect each other, socket connection set is setup.
                DisplayConsoleMessage("Starting the connection ...");
            }

            if (eArgs.State == TriggeredConnectState.Completed)
            {
                DisplayConsoleMessage("Connect has been set up");
                // get the socket which has been set up
                _connectionSocket = eArgs.Socket;
                await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                {
                    this.StartSocketReadWrite();
                });

            }

            if (eArgs.State == TriggeredConnectState.Failed)
            {
                DisplayConsoleMessage("Failed to set up socket connection.");
            }
        }

        private async void ConnectionRequestedEventHandler(object sender, ConnectionRequestedEventArgs args)
        {
            _invitedPlayerDevice = args.PeerInformation;
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                DisplayConsoleMessage("Another player is available on  " + args.PeerInformation.DisplayName);

                this.AcceptButton.Visibility = Visibility.Visible;
            });
        }

Figure 7 The implementation of the PeerFinder event handlers (**)

3.5 Exchange Messages Using the Proximity Stream Socket

After the connection between the two devices is set up, the two devices can exchange messages using the StreamSocket object created by the Proximity API (Figure 8). This game app uses these back-and-forth messages to inform the other player about the game moves and synchronize the current state of the game.

        void StartSocketReadWrite()
        {
            StartPlayGameButton.Visibility = Visibility.Collapsed;
            InvitPlayerButton.Visibility = Visibility.Collapsed;
            AcceptButton.Visibility = Visibility.Collapsed;
            ProximityDeviceList.Visibility = Visibility.Collapsed;
            FindPlayersButton.Visibility = Visibility.Collapsed;
            _reader = new DataReader(_connectionSocket.InputStream);
            _writer = new DataWriter(_connectionSocket.OutputStream);
            _connectionSocketClosed = false;
            ReadSocketMessage();
        }

        async void ReadSocketMessage()
        {
            try
            {
                uint byteCount = await _reader.LoadAsync(sizeof(uint));
                if (byteCount > 0)
                {
                    uint len = (uint)_reader.ReadUInt32();
                    byteCount = await _reader.LoadAsync(len);
                    if (byteCount > 0)
                    {
                        String message = _reader.ReadString(len);
                        ProcessReadMessage(message);
                        // get the move of the other player, now is my turn to play
                        _turnToPlay = true;

                        ReadSocketMessage(); // waiting for the next socket read
                    }
                    else
                    {
                        HandleSocketError("The socket reading failed.");
                    }
                }
                else
                {
                    HandleSocketError("The socket reading failed.");
                }
            }
            catch (Exception e)
            {
                if (!_connectionSocketClosed)
                {
                    HandleSocketError("The socket reading failed with the reason " + e.Message);
                }
            }
        }

        async private void SendMessage(String msg)
        {
            DisplayConsoleMessage("");
            String message = msg;
            if (!_connectionSocketClosed)
            {
                if (message.Length > 0)
                {
                    try
                    {
                        uint len = _writer.MeasureString(message);
                        _writer.WriteUInt32(len);
                        _writer.WriteString(message);
                        uint byteCount = await _writer.StoreAsync();
                        if (byteCount > 0)
                        {
                            DisplayConsoleMessage("Send the move to the other player: " + message + " byte count: " + byteCount);

                        }
                        else
                        {
                            HandleSocketError("The communication with the other player interrupted.");
                        }
                    }
                    catch (Exception err)
                    {
                        if (!_connectionSocketClosed)
                        {
                            HandleSocketError("Communication with the other player error: " + err.Message);
                        }
                    }
                }
            }
            else
            {
                HandleSocketError("The communication with the other player device is closed.");
            }

        }

Figure 8 Using the Proximity StreamSocket to exchange messages (**)

3.6 Other Game Logic

Besides the logic used to set up the connection between the two devices in proximity, MainPage.xaml.cs includes code that implements the Tic-Tac-Toe game logic, for example, to determine which player’s turn it is to play, if the game ends, etc. We attach the completed MainPage.xaml.cs file here. On the Solution Explorer screen, you may double click the MainPage.xaml.cs file to open it, and copy the contents of Figure 9 and paste to overwrite the contents in your original MainPage.xaml.cs file.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Core;

using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;

using Windows.Networking.Proximity;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;
using System.Diagnostics;


// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238

namespace TicTacToe
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        private IReadOnlyList<PeerInformation> _deviceList;
        private PeerInformation _invitedPlayerDevice;
        private StreamSocket _connectionSocket = null;
        private bool _connectionSocketClosed = true;
        private bool _peerFinderStarted = false;
        private DataWriter _writer;
        private DataReader _reader;
        private bool _triggeredSupported = false;
        private bool _browseSupported = false;
        bool _launchedActivatedByPeerFinder = false;
       
        //stores the current state of a game cell, 0 - untaken 1-taken by player 1, 2-taken by plaer 2
        private int[] _grid;

        // 1 - player 1, 2- payer 2
        private int _playerID;
 
        //my turn to play
        private bool _turnToPlay = false;

        //the game has reached an end game state, one side wins or the game draws
        private bool _gameEnds = false;
        private Button[] _buttons;

        // the messages used to exchange the current state of the game
        private static string _iwin = "IWIN";
        private static string _gameDraw = "DRAW";
        private static string _replayRequest = "REPLAY";
        private static string _endGameRequest = "ENDGAME";
       
        public MainPage()
        {
            this.InitializeComponent();

            _buttons = new Button[9];
            _buttons[0] = Grid_Button00;
            _buttons[1] = Grid_Button01;
            _buttons[2] = Grid_Button02;
            _buttons[3] = Grid_Button03;
            _buttons[4] = Grid_Button04;
            _buttons[5] = Grid_Button05;
            _buttons[6] = Grid_Button06;
            _buttons[7] = Grid_Button07;
            _buttons[8] = Grid_Button08;

            _grid = new int[9];
            for (int ix = 0; ix < 9; ix++)
            {
                _grid[ix] = 0;
            }
           
            _playerID = 0;
           
            _triggeredSupported = (PeerFinder.SupportedDiscoveryTypes & PeerDiscoveryTypes.Triggered) ==
                                              PeerDiscoveryTypes.Triggered;
            _browseSupported = (PeerFinder.SupportedDiscoveryTypes & PeerDiscoveryTypes.Browse) ==
                                      PeerDiscoveryTypes.Browse;
            if (_triggeredSupported || _browseSupported)
            {
                StartPlayGameButton.Click += new RoutedEventHandler(StartPlayGame);
                FindPlayersButton.Click += new RoutedEventHandler(FindPlayers);
                InvitPlayerButton.Click += new RoutedEventHandler(InvitePlayer);
                AcceptButton.Click += new RoutedEventHandler(Accept);
                ReplayGameButton.Click += new RoutedEventHandler(Replay);
                EndGameButton.Click += new RoutedEventHandler(EndGame);
                StartPlayGameButton.Visibility = Visibility.Visible;
                DisplayConsoleMessage("Press the Start Game to start a new game.");
            }
        }

        void StartPlayGame(object sender, RoutedEventArgs e)
        {
            DisplayConsoleMessage("");
            if (!_peerFinderStarted)
            {
                // tap triggered state change handler
                PeerFinder.TriggeredConnectionStateChanged += new TypedEventHandler<object, TriggeredConnectionStateChangedEventArgs>(TriggeredConnectionStateChangedEventHandler);
                // ConnectionRequested event handler
                PeerFinder.ConnectionRequested += new TypedEventHandler<object, ConnectionRequestedEventArgs>(ConnectionRequestedEventHandler);

                // start the proximate device peer finder
                PeerFinder.Start();
                _peerFinderStarted = true;
                if (_triggeredSupported && _browseSupported)
                {
                    DisplayConsoleMessage("Tap two player devices together, or click Find Players button.");
                    FindPlayersButton.Visibility = Visibility.Visible;
                }
                else if (_triggeredSupported)
                {
                    DisplayConsoleMessage("Tap two player devices together to set up connection.");
                }
                else if (_browseSupported)
                {
                    DisplayConsoleMessage("Click Find Players button.");
                }
            }
        }
 
        async private void TriggeredConnectionStateChangedEventHandler(object sender, TriggeredConnectionStateChangedEventArgs eArgs)
        {
             if (eArgs.State == TriggeredConnectState.PeerFound)
            {
                // devices tap and detect each other, socket connection set is setup.
                DisplayConsoleMessage("Starting the connection ...");
            }

            if (eArgs.State == TriggeredConnectState.Completed)
            {
                DisplayConsoleMessage("Connect has been set up");
                // get the socket which has been set up
                _connectionSocket = eArgs.Socket;
                await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                {
                    this.StartSocketReadWrite();
                });

            }

            if (eArgs.State == TriggeredConnectState.Failed)
            {
                DisplayConsoleMessage("Failed to set up socket connection.");
            }
        }

        private async void ConnectionRequestedEventHandler(object sender, ConnectionRequestedEventArgs args)
        {
            _invitedPlayerDevice = args.PeerInformation;
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                DisplayConsoleMessage("Another player is available on  " + args.PeerInformation.DisplayName + ", press Accept button to play a game.");

                this.AcceptButton.Visibility = Visibility.Visible;
            });
        }

        // Start the reader and the writer to talk to the other player device
        void StartSocketReadWrite()
        {
            StartPlayGameButton.Visibility = Visibility.Collapsed;
            InvitPlayerButton.Visibility = Visibility.Collapsed;
            AcceptButton.Visibility = Visibility.Collapsed;
            ProximityDeviceList.Visibility = Visibility.Collapsed;
            FindPlayersButton.Visibility = Visibility.Collapsed;
            _reader = new DataReader(_connectionSocket.InputStream);
            _writer = new DataWriter(_connectionSocket.OutputStream);
            _connectionSocketClosed = false;
            ReadSocketMessage();
        }

        async void ReadSocketMessage()
        {
            try
            {
                uint byteCount = await _reader.LoadAsync(sizeof(uint));
                if (byteCount > 0)
                {
                    uint len = (uint)_reader.ReadUInt32();
                    byteCount = await _reader.LoadAsync(len);
                    if (byteCount > 0)
                    {
                        String message = _reader.ReadString(len);
                        ProcessReadMessage(message);
                        if (_gameEnds != true)
                        {
                            DisplayConsoleMessage("Received the move from the other player, your turn to play.");
                        }

                        // get the move of the other player, now is my turn to play
                        _turnToPlay = true;

                        ReadSocketMessage(); // waiting for the next socket read
                    }
                    else
                    {
                        HandleSocketError("The socket reading failed.");
                    }
                }
                else
                {
                    HandleSocketError("The socket reading failed.");
                }
            }
            catch (Exception e)
            {
                if (!_connectionSocketClosed)
                {
                    HandleSocketError("The socket reading failed with the reason " + e.Message);
                }
            }
        }

        private void ProcessReadMessage(String msg)
        {
            char[] delimiters = { '|', '\t' };
            String[] elems = msg.Split(delimiters);
            if (elems.Length == 1)
            {
                if (elems[0].Equals(_replayRequest, StringComparison.OrdinalIgnoreCase))
                {
                    ResetGame();
                    DisplayConsoleMessage("The other player has requested to play again.");
                }
                else if (elems[0].Equals(_endGameRequest, StringComparison.OrdinalIgnoreCase))
                {
                    _gameEnds = true;
                    ReplayGameButton.Visibility = Visibility.Visible;
                    DisplayConsoleMessage("The other player has requested to end this game.");
                }

            }
            if (elems.Length > 1)
            {
                int rivalPlayer = Convert.ToInt32(elems[0]);
                int pos = Convert.ToInt32(elems[1]);
                MakeThePlay(rivalPlayer, pos);
                if (elems.Length > 2)
                {
                    if (elems[2].Equals(_iwin, StringComparison.OrdinalIgnoreCase))
                    {
                        _gameEnds = true;
                        HandleGameEnds(GameEndsType.OTHERSIDEWON);
                    }
                    else if (elems[2].Equals(_gameDraw, StringComparison.OrdinalIgnoreCase))
                    {
                        _gameEnds = true;
                        HandleGameEnds(GameEndsType.DRAW);
                    }
                }
            }

        }

        private void HandleSocketError(String errMessage)
        {
            DisplayConsoleMessage(errMessage);
            StartPlayGameButton.Visibility = Visibility.Visible;
            if (_browseSupported)
            {
                FindPlayersButton.Visibility = Visibility.Visible;
            }
            if (!_connectionSocketClosed)
            {
                _connectionSocketClosed = true;
                _connectionSocket.Dispose();

                _connectionSocket = null;
            }
        }

        async void FindPlayers(object sender, RoutedEventArgs e)
        {
            DisplayConsoleMessage("Looking for other players...");
            try
            {
                _deviceList = await PeerFinder.FindAllPeersAsync();
            }
            catch (Exception ex)
            {
                DisplayConsoleMessage("Looking for other players encountered problem: " + ex.Message);
            }

            if (_deviceList.Count > 0)
            {
                ProximityDeviceList.Items.Clear();
                for (int i = 0; i < _deviceList.Count; i++)
                {
                    ListBoxItem lbItem = new ListBoxItem();
                    lbItem.Content = _deviceList[i].DisplayName;
                    ProximityDeviceList.Items.Add(lbItem);
                }
                InvitPlayerButton.Visibility = Visibility.Visible;
                ProximityDeviceList.Visibility = Visibility.Visible;
                DisplayConsoleMessage("Other players are available, press the Invite A Player button to invite a player to play.");
            }
            else
            {
                DisplayConsoleMessage("No other player is available");
                InvitPlayerButton.Visibility = Visibility.Collapsed;
                ProximityDeviceList.Visibility = Visibility.Collapsed;
            }
        }

        async void InvitePlayer(object sender, RoutedEventArgs e)
        {
            DisplayConsoleMessage("");
            PeerInformation playerInvted = null;
            try
            {
                // by default, select the first player
                if (ProximityDeviceList.SelectedIndex == -1)
                {
                    playerInvted = _deviceList[0];
                }
                else
                {
                    playerInvted = _deviceList[ProximityDeviceList.SelectedIndex];
                }

                DisplayConsoleMessage("Inviting the play on " + playerInvted.DisplayName + " to play...");
                _connectionSocket = await PeerFinder.ConnectAsync(playerInvted);
                DisplayConsoleMessage("The game is set up. Your turn to play.");
                StartSocketReadWrite();
                _playerID = 1;
                _turnToPlay = true;
                EndGameButton.Visibility = Visibility.Visible;
            }
            catch (Exception err)
            {
                DisplayConsoleMessage("Failed to set up the game connection with player on " + playerInvted.DisplayName + "  reason: " + err.Message);
            }
        }

        async private void Accept(object sender, RoutedEventArgs e)
        {
            _playerID = 2;
            _turnToPlay = false;
            DisplayConsoleMessage("Setting up the game with player on " + _invitedPlayerDevice.DisplayName + "...");
            AcceptButton.Visibility = Visibility.Collapsed;
            EndGameButton.Visibility = Visibility.Visible;
            try
            {
                _connectionSocket = await PeerFinder.ConnectAsync(_invitedPlayerDevice);
                DisplayConsoleMessage("The game connection was set up successfully. The other player's turn to play.");
                StartSocketReadWrite();
            }
            catch (Exception err)
            {
                DisplayConsoleMessage("Setting up game connection with player on " + _invitedPlayerDevice.DisplayName + " failed: " + err.Message);
            }
        }

        private void Replay(object sender, RoutedEventArgs e)
        {
            ResetGame();
            SendMessage(_replayRequest);
        }

        private void EndGame(object sender, RoutedEventArgs e)
        {
            _gameEnds = true;
            SendMessage(_endGameRequest);
            ReplayGameButton.Visibility = Visibility.Visible;
            DisplayConsoleMessage("The game was stopped.");
        }

        private void ResetGame()
        {
            DisplayConsoleMessage("");
            for (int ix = 0; ix < 9; ix++)
            {
                MakeThePlay(0, ix);
            }
            _gameEnds = false;
            ReplayGameButton.Visibility = Visibility.Collapsed;
            EndGameButton.Visibility = Visibility.Visible;
        }

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            LaunchActivatedEventArgs launchActivatedEventArgs = e.Parameter as LaunchActivatedEventArgs;

            if ((launchActivatedEventArgs != null) && (launchActivatedEventArgs.Arguments == "Windows.Networking.Proximity.PeerFinder:StreamSocket"))
            {
                _launchedActivatedByPeerFinder = true;
            }
           
            if (_triggeredSupported || _browseSupported)
            {
                StartPlayGameButton.Visibility = Visibility.Visible;
                FindPlayersButton.Visibility = Visibility.Collapsed;
                InvitPlayerButton.Visibility = Visibility.Collapsed;
                ProximityDeviceList.Visibility = Visibility.Collapsed;
                AcceptButton.Visibility = Visibility.Collapsed;
                EndGameButton.Visibility = Visibility.Collapsed;
                if (_launchedActivatedByPeerFinder)
                {
                    DisplayConsoleMessage("This game was started by player on a proximity device");
                    _launchedActivatedByPeerFinder = false;
                    StartPlayGame(null, null);
                }
            }
            else
            {
                DisplayConsoleMessage("Proximity API is not supported on this device. This app can not run.");
            }
        }


        // Invoked when the main page navigates to a different scenario
        protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
        {
            if (_peerFinderStarted)
            {
                PeerFinder.TriggeredConnectionStateChanged -= new TypedEventHandler<object, TriggeredConnectionStateChangedEventArgs>(TriggeredConnectionStateChangedEventHandler);
                PeerFinder.ConnectionRequested -= new TypedEventHandler<object, ConnectionRequestedEventArgs>(ConnectionRequestedEventHandler);
                PeerFinder.Stop();
                if (_connectionSocket != null)
                {
                    _connectionSocketClosed = true;
                    _connectionSocket.Dispose();
                    _connectionSocket = null;
                }
                _peerFinderStarted = false;
            }
        }

        public async void DisplayConsoleMessage(string strMessage)
        {
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                OutputTextBlock.Text = "\n" + strMessage;
            });
        }

        void HandleClick_Button00(object sender, RoutedEventArgs e)
        {
            HandleClick(0, sender, e);
        }

        void HandleClick_Button01(object sender, RoutedEventArgs e)
        {
            HandleClick(1, sender, e);
        }

        void HandleClick_Button02(object sender, RoutedEventArgs e)
        {
            HandleClick(2, sender, e);
        }

        void HandleClick_Button03(object sender, RoutedEventArgs e)
        {
            HandleClick(3, sender, e);
        }
        void HandleClick_Button04(object sender, RoutedEventArgs e)
        {
            HandleClick(4, sender, e);
        }

        void HandleClick_Button05(object sender, RoutedEventArgs e)
        {
            HandleClick(5, sender, e);
        }

        void HandleClick_Button06(object sender, RoutedEventArgs e)
        {
            HandleClick(6, sender, e);
        }

        void HandleClick_Button07(object sender, RoutedEventArgs e)
        {
            HandleClick(7, sender, e);
        }

        void HandleClick_Button08(object sender, RoutedEventArgs e)
        {
            HandleClick(8, sender, e);
        }

        void HandleClick(int btnIndex, object sender, RoutedEventArgs e)
        {
            Button srcButton = sender as Button;
            if ((_gameEnds == false) && (_turnToPlay == true) && (_grid[btnIndex] == 0))
            {
                MakeThePlay(_playerID, btnIndex);
                GameEndsType moveResult = CheckIfGameEnds(_playerID, btnIndex);
                String msg = _playerID.ToString();
                msg += "|";
                msg += btnIndex.ToString();
                if (moveResult == GameEndsType.IWON)
                {
                    _gameEnds = true;
                    msg += "|";
                    msg += _iwin;
                }
                else if (moveResult == GameEndsType.DRAW)
                {
                    _gameEnds = true;
                    msg += "|";
                    msg += _gameDraw;
                }
                SendMessage(msg);
                if (_gameEnds != true)
                {
                    DisplayConsoleMessage("The move was sent to the other player, now is the other player's turn to play.");
                }
                _turnToPlay = false;

                if (_gameEnds == true)
                {
                    HandleGameEnds(moveResult);
                }
            }
        }

        private void MakeThePlay(int _playerID, int btnIndex)
        {
            if (_playerID == 1)
            {
                _buttons[btnIndex].Background = new ImageBrush { ImageSource = new BitmapImage(new Uri("ms-appx:///Assets/ximage.png")) };
            }
            else if (_playerID == 2)
            {
                _buttons[btnIndex].Background = new ImageBrush { ImageSource = new BitmapImage(new Uri("ms-appx:///Assets/oimage.png")) };
            }
            else if (_playerID == 0)
            {
                _buttons[btnIndex].Background = new ImageBrush { ImageSource = new BitmapImage(new Uri("ms-appx:///Assets/initialimage.png")) };
            }

            _grid[btnIndex] = _playerID;
        }

        private GameEndsType CheckIfGameEnds(int player, int btnIdx)
        {
            GameEndsType res = GameEndsType.CONTINUE;

            switch (btnIdx)
            {
                case 0:
                    if (((_grid[1] == _grid[0]) && (_grid[2] == _grid[0])) ||
                        ((_grid[3] == _grid[0]) && (_grid[6] == _grid[0])) ||
                        ((_grid[4] == _grid[0]) && (_grid[8] == _grid[0])))
                    {
                        res = GameEndsType.IWON;
                    }
                    break;
                case 1:
                    if (((_grid[0] == _grid[1]) && (_grid[2] == _grid[1])) ||
                        ((_grid[4] == _grid[1]) && (_grid[7] == _grid[1])))
                    {
                        res = GameEndsType.IWON;
                    }
                    break;
                case 2:
                    if (((_grid[0] == _grid[2]) && (_grid[1] == _grid[2])) ||
                        ((_grid[5] == _grid[2]) && (_grid[8] == _grid[2])) ||
                        ((_grid[4] == _grid[2]) && (_grid[6] == _grid[2])))
                    {
                        res = GameEndsType.IWON;
                    }
                    break;
                case 3:
                    if (((_grid[4] == _grid[3]) && (_grid[5] == _grid[3])) ||
                        ((_grid[0] == _grid[3]) && (_grid[6] == _grid[3])))
                    {
                        res = GameEndsType.IWON;
                    }
                    break;
                case 4:
                    if(((_grid[1] == _grid[4]) && (_grid[7] == _grid[4])) ||
                        ((_grid[3] == _grid[4]) && (_grid[5] == _grid[4]))||
                        ((_grid[0] == _grid[4]) && (_grid[8] == _grid[4])) ||
                        ((_grid[2] == _grid[4]) && (_grid[6] == _grid[4])))
                    {
                        res = GameEndsType.IWON;
                    }
                    break;
                case 5:
                    if (((_grid[3] == _grid[5]) && (_grid[4] == _grid[5])) ||
                        ((_grid[2] == _grid[5]) && (_grid[8] == _grid[5])))
                    {
                        res = GameEndsType.IWON;
                    }
                    break;
                case 6:
                    if (((_grid[7] == _grid[6]) && (_grid[8] == _grid[6])) ||
                        ((_grid[0] == _grid[6]) && (_grid[3] == _grid[6])) ||
                        ((_grid[4] == _grid[6]) && (_grid[2] == _grid[6])))
                    {
                        res = GameEndsType.IWON;
                    }
                    break;
                case 7:
                    if (((_grid[6] == _grid[7]) && (_grid[8] == _grid[7])) ||
                        ((_grid[1] == _grid[7]) && (_grid[4] == _grid[7])))
                    {
                        res = GameEndsType.IWON;
                    }
                    break;
                case 8:
                    if (((_grid[6] == _grid[8]) && (_grid[7] == _grid[8])) ||
                        ((_grid[2] == _grid[8]) && (_grid[5] == _grid[8])) ||
                        ((_grid[4] == _grid[8]) && (_grid[0] == _grid[8])))
                    {
                        res = GameEndsType.IWON;
                    }
                    break;
            }
            if (res != GameEndsType.IWON)
            {
                bool gameTies = true;
                for (int ix = 0; ix < 9; ix++)
                {
                    if (_grid[ix] == 0)
                    {
                        gameTies = false;
                    }
                }
                if (gameTies == true)
                {
                    res = GameEndsType.DRAW;
                }
            }
            return res;
        }

        private void HandleGameEnds(GameEndsType type)
        {
            switch (type)
            {
                case GameEndsType.IWON:
                    DisplayConsoleMessage("I Won!");
                    break;
                case GameEndsType.OTHERSIDEWON:
                    DisplayConsoleMessage("The Other Side Won!");
                    break;
                case GameEndsType.DRAW:
                    DisplayConsoleMessage("A Draw!");
                    break;
            }
            ReplayGameButton.Visibility = Visibility.Visible;
        }

        async private void SendMessage(String msg)
        {
            DisplayConsoleMessage("");
            String message = msg;
            if (!_connectionSocketClosed)
            {
                if (message.Length > 0)
                {
                    try
                    {
                        uint len = _writer.MeasureString(message);
                        _writer.WriteUInt32(len);
                        _writer.WriteString(message);
                        uint byteCount = await _writer.StoreAsync();
                        if (byteCount > 0)
                        {
                        }
                        else
                        {
                            HandleSocketError("The communication with the other player interrupted.");
                        }
                    }
                    catch (Exception err)
                    {
                        if (!_connectionSocketClosed)
                        {
                            HandleSocketError("Communication with the other player error: " + err.Message);
                        }
                    }
                }
            }
            else
            {
                HandleSocketError("The communication with the other player device is closed.");
            }

        }
    }

    public enum GameEndsType
    {
        IWON,
        OTHERSIDEWON,
        DRAW,
        CONTINUE
    };

}

Figure 9 The MainPage.xaml.cs file (**)

3.7 Run the App

After you finish the changes described in the above sections and you successfully build the solution, you can deploy the app on two tablets that support NFC and/or Wi-Fi Direct peer browsing. You can play the game in action on the two devices (Figure 10).

Let’s start with creating the C# project. Open Visual 2012, select File->New->Project…, then select “Visual C#” on the left pane, and “Blank App (XAML)” on the right pane. In the project name field, enter “TicTacToe” (Figure 2), and click “OK”.


Figure 10 The Tic-Tac-Toe game start screen

4 Summary

On Intel Atom processor-based tablets running Windows 8, by using Windows Proximity API, we can create connections between two devices with a simple tap gesture, or through Wi-Fi Direct peer browsing. The connected devices can then exchange data with each other without connecting to a network. The Windows Proximity API enables application developers to enhance their apps and create multi-player application experiences.

About the Author

Miao Wei works as a software engineer with Intel Corporation’s Software and Services Group.

Notices

INFORMATION IN THIS DOCUMENT IS PROVIDED IN CONNECTION WITH INTEL PRODUCTS. NO LICENSE, EXPRESS OR IMPLIED, BY ESTOPPEL OR OTHERWISE, TO ANY INTELLECTUAL PROPERTY RIGHTS IS GRANTED BY THIS DOCUMENT. EXCEPT AS PROVIDED IN INTEL'S TERMS AND CONDITIONS OF SALE FOR SUCH PRODUCTS, INTEL ASSUMES NO LIABILITY WHATSOEVER AND INTEL DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY, RELATING TO SALE AND/OR USE OF INTEL PRODUCTS INCLUDING LIABILITY OR WARRANTIES RELATING TO FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR INFRINGEMENT OF ANY PATENT, COPYRIGHT OR OTHER INTELLECTUAL PROPERTY RIGHT.

UNLESS OTHERWISE AGREED IN WRITING BY INTEL, THE INTEL PRODUCTS ARE NOT DESIGNED NOR INTENDED FOR ANY APPLICATION IN WHICH THE FAILURE OF THE INTEL PRODUCT COULD CREATE A SITUATION WHERE PERSONAL INJURY OR DEATH MAY OCCUR.

Intel may make changes to specifications and product descriptions at any time, without notice. Designers must not rely on the absence or characteristics of any features or instructions marked "reserved" or "undefined." Intel reserves these for future definition and shall have no responsibility whatsoever for conflicts or incompatibilities arising from future changes to them. The information here is subject to change without notice. Do not finalize a design with this information.

The products described in this document may contain design defects or errors known as errata which may cause the product to deviate from published specifications. Current characterized errata are available on request.

Contact your local Intel sales office or your distributor to obtain the latest specifications and before placing your product order.

Copies of documents which have an order number and are referenced in this document, or other Intel literature, may be obtained by calling 1-800-548-4725, or go to: http://www.intel.com/design/literature.htm

Intel, the Intel logo and Atom are trademarks of Intel Corporation in the U.S. and other countries.

*Other names and brands may be claimed as the property of others

**This sample source code is released under the Intel Sample Source Code License Agreement

Copyright© 2012 Intel Corporation. All rights reserved.

For more complete information about compiler optimizations, see our Optimization Notice.