Universal Windows Platform – 360View App

360View App shows to create an application which will allow you to Open and Play 360 Video also known as Spherical Video Files

Step 1

If not already, follow Setup and Start on how to Install and get Started with Visual Studio 2017 or in Windows 10 choose Start, and then from the Start Menu find and select Visual Studio 2017.

vs2017

Step 2

Once Visual Studio Community 2017 has started, from the Menu choose File, then New then Project…

vs2017-file-new-project

Step 3

From New Project choose Visual C# from Installed, Templates then choose Blank App (Universal Windows) and then type in a Name and select a Location and then select Ok to create the Project
vs2017-new-project-window

Step 4

Then in New Universal Windows Project you need to select the Target Version this should be at least the Windows 10, version 1803 (10.0; Build 17134) which is the April 2018 Update and the Minimum Version to be the same.

vs2017-target-platform

The Target Version will control what features your application can use in Windows 10 so by picking the most recent version you’ll be able to take advantage of those features. To make sure you always have the most recent version, in Visual Studio 2017 select Tools Extensions and Updates… then and then see if there are any Updates

Step 5

Once done select from the Menu, Project, then Add New Item…

vs2017-project-add-new-item

Step 6

From the Add New Item window select Visual C#, then Code from Installed then select Code File from the list, then type in the Name as Library.cs before selecting Add to add the file to the Project

vs2017-add-new-item-library

Step 7

Once in the Code View for Library.cs the following should be entered:

using System;
using System.Numerics;
using Windows.Media.Core;
using Windows.Media.MediaProperties;
using Windows.Media.Playback;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;

public class Library
{
    private const int scroll_step = 2;
    private const int mouse_wheel = 120;
    private const double change_increment = 0.5;

    private MediaPlaybackSphericalVideoProjection _projection;
    private MediaPlayerElement _element = null;
    private MediaPlayer _player;
    private Grid _grid = null;
    private bool _press = false;
    private double _delta = 1.8f;
    private double _centerX = 0;
    private double _centerY = 0;

    private Quaternion GetPitchRoll(double heading, double pitch, double roll)
    {
        double ToRadians(double degree)
        {
            return degree * Math.PI / 180.0;
        }
        Quaternion result;
        double headingPart = ToRadians(heading) * change_increment;
        double sin1 = Math.Sin(headingPart);
        double cos1 = Math.Cos(headingPart);
        double pitchPart = ToRadians(-pitch) * change_increment;
        double sin2 = Math.Sin(pitchPart);
        double cos2 = Math.Cos(pitchPart);
        double rollPart = ToRadians(roll) * change_increment;
        double sin3 = Math.Sin(rollPart);
        double cos3 = Math.Cos(rollPart);
        result.W = (float)(cos1 * cos2 * cos3 - sin1 * sin2 * sin3);
        result.X = (float)(cos1 * cos2 * sin3 + sin1 * sin2 * cos3);
        result.Y = (float)(sin1 * cos2 * cos3 + cos1 * sin2 * sin3);
        result.Z = (float)(cos1 * sin2 * cos3 - sin1 * cos2 * sin3);
        return result;
    }

    private bool IsOpened()
    {
        if (_player != null && _projection != null &&
        _player.PlaybackSession.PlaybackState != MediaPlaybackState.Opening &&
        _player.PlaybackSession.PlaybackState != MediaPlaybackState.None) return true;
        return false;
    }

    private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
    {
        if (e.OriginalSource != _grid && _press)
        {
            double changeX = e.GetCurrentPoint(_element).Position.X - _centerX;
            double changeY = _centerY - e.GetCurrentPoint(_element).Position.Y;
            _projection.ViewOrientation = GetPitchRoll(changeX, changeY, 0);
        }
        e.Handled = true;
    }

    private void OnPointerReleased(object sender, PointerRoutedEventArgs e)
    {
        _press = false;
        e.Handled = true;
    }

    private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
    {
        if (e.OriginalSource != _grid && IsOpened()) _press = true;
        e.Handled = true;
    }

    private void OnPointerWheelChanged(object sender, PointerRoutedEventArgs e)
    {
        if (e.OriginalSource != _grid)
        {
            double value = _projection.HorizontalFieldOfViewInDegrees +
                (scroll_step * e.GetCurrentPoint(_element).Properties.MouseWheelDelta / mouse_wheel);
            if (value > 0 && value <= 180)
            {
                _projection.HorizontalFieldOfViewInDegrees = value;
            }
        }
        e.Handled = true;
    }

    private void PlaybackSession_PlaybackStateChanged(MediaPlaybackSession sender, object args)
    {
        _delta = (_player.PlaybackSession.PlaybackState == 
        MediaPlaybackState.Playing) ? 1.8f : 0.25f;
    }

    private void Player_MediaOpened(MediaPlayer sender, object args)
    {
        _projection = _player.PlaybackSession.SphericalVideoProjection;
        SphericalVideoFrameFormat videoFormat = _projection.FrameFormat;
        if (videoFormat != SphericalVideoFrameFormat.Equirectangular)
        {
            _projection.FrameFormat = SphericalVideoFrameFormat.Equirectangular;
        }
        _projection.IsEnabled = true;
        _projection.HorizontalFieldOfViewInDegrees = 120;
        _player.PlaybackSession.PlaybackStateChanged += PlaybackSession_PlaybackStateChanged;
    }

    public void Layout(ref MediaPlayerElement display)
    {
        if(_element == null) _element = display;
        _centerX = _element.ActualWidth / 2;
        _centerY = _element.ActualHeight / 2;
        if (_grid == null)
        {
            FrameworkElement _root = (FrameworkElement)VisualTreeHelper.GetChild(_element.TransportControls, 0);
            if (_root != null)
            {
                _grid = (Grid)_root.FindName("ControlPanelGrid");
                _grid.PointerPressed += OnPointerPressed;
                _grid.PointerReleased += OnPointerReleased;
                _grid.PointerWheelChanged += OnPointerWheelChanged;
                _element.PointerPressed += OnPointerPressed;
                _element.PointerReleased += OnPointerReleased;
                _element.PointerMoved += OnPointerMoved;
                _element.PointerWheelChanged += OnPointerWheelChanged;
            }
        }
    }

    public async void Open(MediaPlayerElement display)
    {
        try
        {
            FileOpenPicker picker = new FileOpenPicker()
            {
                SuggestedStartLocation = PickerLocationId.PicturesLibrary
            };
            picker.FileTypeFilter.Add(".mp4");
            StorageFile open = await picker.PickSingleFileAsync();
            if (open != null)
            {
                if (_element == null) _element = display;
                _player = new MediaPlayer
                {
                    Source = MediaSource.CreateFromStorageFile(open)
                };
                _player.MediaOpened += Player_MediaOpened;
                _element.SetMediaPlayer(_player);
            }
        }
        finally
        {
            // Ignore Exceptions
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality and in the Library Class has some const values which will help with displaying the Spherical Video and private Members for MediaPlaybackSphericalVideoProjection, MediaPlayerElement and MediaPlayer along with other values which are used as part of navigating the Spherical Video during playback.

In the Library Class there is a GetPitchRoll Method this will be used to calculate the Quaternion Vector based on a passed in heading, pitch and roll. The IsOpened Method is used to determine the MediaPlaybackState of the MediaPlayer as being Opened or Not. The OnPointerMoved Event Handler will be used to set the ViewOrientation of the MediaPlaybackSphericalVideoProjection using the GetPitchRoll Method.

Also in the Library Class there are Event Handlers for OnPointerMoved – which reacts to Mouse Movements or Touch Movements on the Grid, OnPointerReleased which will be when nothing is being Pressed and OnPointerReleased which is when an input is being Pressed and sets the _press Value accordingly. The PointerWheelChanged Event Handler reacts to Mouse Scroll Wheel Events to set the HorizontalFieldOfViewInDegrees of the MediaPlaybackSphericalVideoProjection, the PlaybackSession_PlaybackStateChanged Event Handler will set the relevant _delta Value and Player_MediaOpened will bv triggered when the MediaPlayer Video has been Opened, this will configure the look-and-feel of the Playback Session.

Still in the Library Class there is a Layout Method which is used to get the Grid from the MediaPlayerElement to allow interactions on top of it and will set all the Event Handlers to this and there is an Open Method which has a MediaPlayerElement Parameter passed in, within a try and catch Block to help handle any Exception there is a FileOpenPicker which is used to select a mp4 File it the sets the Source of the MediaPlayer using CreateFromStorageFile and sets up the MediaOpened Event Handler and the calls the SetMediaPlayer Method on the MediaPlayerElement of the MediaPlayer.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-library

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

The Design View will be displayed along with the XAML View and in in this between the Grid and /Grid elements, enter the following XAML:

<MediaPlayerElement Name="Display" AreTransportControlsEnabled="True" 
AutoPlay="True" LayoutUpdated="Display_LayoutUpdated"/>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="OpenFile" Label="Open" Click="Open_Click"/>
</CommandBar>

Within the main Grid Element, the first block of XAML is an MediaPlayerElement Control. The second block of XAML is a CommandBar with AppBarButton for Open which calls Open_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

Once in the Code View, below the end of public MainPage() { … } the following Code should be entered:

Library library = new Library();

private void Display_LayoutUpdated(object sender, object e)
{
	library.Layout(ref Display);
}

private void Open_Click(object sender, RoutedEventArgs e)
{
	library.Open(Display);
}

There is a Display_LayoutUpdated Event Handler which will be triggered when the Layout of the Window is Updated and an Open_Click Event Handler which will will call the Open Method of the Library Class.

Step 13

That completes the Universal Windows Platform Application so Save the Project then in Visual Studio select the Local Machine to run the Application

vs2017-local-machine

Step 14

Once the Application has started running you can tap on the Open Button then you need to select an mp4 Video File that is a Spherical Video Format which will then be displayed within the MediaPlayerElement Control and can be looked-around in with Mouse, Pen or Touch input

uwp-ran-360view-app

You can use the following sample Spherical Video File

Download

Step 15

To Exit the Application select the Close button in the top right of the Application

vs2017-close

This example was inspired from the Microsoft Universal Windows Platform (UWP) app samples on GitHub and in particular the 360 Video Playback Example and the goal of this was to create a simpler implementation of the example available there. The example displayed is by Goutham Gandhi Nadendla from a set examples on GitHub

Creative Commons License

Universal Windows Platform – SvgView App

SvgView App shows how to create an application which will allow you to Open and Display Svg Files

Step 1

If not already, follow Setup and Start on how to Install and get Started with Visual Studio 2017 or in Windows 10 choose Start, and then from the Start Menu find and select Visual Studio 2017.

vs2017

Step 2

Once Visual Studio Community 2017 has started, from the Menu choose File, then New then Project…

vs2017-file-new-project

Step 3

From New Project choose Visual C# from Installed, Templates then choose Blank App (Universal Windows) and then type in a Name and select a Location and then select Ok to create the Project
vs2017-new-project-window

Step 4

Then in New Universal Windows Project you need to select the Target Version this should be at least the Windows 10, version 1803 (10.0; Build 17134) which is the April 2018 Update and the Minimum Version to be the same.

vs2017-target-platform

The Target Version will control what features your application can use in Windows 10 so by picking the most recent version you’ll be able to take advantage of those features. To make sure you always have the most recent version, in Visual Studio 2017 select Tools Extensions and Updates… then and then see if there are any Updates

Step 5

Once done select from the Menu, Project, then Add New Item…

vs2017-project-add-new-item

Step 6

From the Add New Item window select Visual C#, then Code from Installed then select Code File from the list, then type in the Name as Library.cs before selecting Add to add the file to the Project

vs2017-add-new-item-library

Step 7

Once in the Code View for Library.cs the following should be entered:

using System;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;

public class Library
{
    public async void Open(Image display)
    {
        try
        {
            FileOpenPicker picker = new FileOpenPicker()
            {
                SuggestedStartLocation = PickerLocationId.PicturesLibrary
            };
            picker.FileTypeFilter.Add(".svg");
            StorageFile open = await picker.PickSingleFileAsync();
            if (open != null)
            {
                SvgImageSource source = new SvgImageSource()
                {
                    RasterizePixelHeight = display.ActualHeight,
                    RasterizePixelWidth = display.ActualWidth
                };
                if(await source.SetSourceAsync(await open.OpenReadAsync())
                    == SvgImageSourceLoadStatus.Success)
                {              
                    display.Source = source;
                }               
            }
        }
        finally
        {
            // Ignore Exceptions
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality and in the Library Class has an Open Method which has an Image Parameter passed in, within a try and catch Block to help handle any Exception there is a FileOpenPicker which is used to select a png File it then uses SvgImageSource and SetSourceAsync to a file selected with the FileOpenPicker with OpenReadAsync and sets the Source of the passed in Image Control, the RasterizePixelHeight and RasterizePixelWidth Methods are used to set the size of the resulting Image.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-library

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

The Design View will be displayed along with the XAML View and in in this between the Grid and /Grid elements, enter the following XAML:

<Image Margin="50" Name="Display" Width="400" Height="400" 
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="OpenFile" Label="Open" Click="Open_Click"/>
</CommandBar>

Within the main Grid Element, the first block of XAML is an Image Control. The second block of XAML is a CommandBar with AppBarButton for Open which calls Open_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

Once in the Code View, below the end of public MainPage() { … } the following Code should be entered:

Library library = new Library();

private void Open_Click(object sender, RoutedEventArgs e)
{
	library.Open(Display);
}

There is an Open_Click Event Handler which will will call the Open Method of the Library Class.

Step 13

That completes the Universal Windows Platform Application so Save the Project then in Visual Studio select the Local Machine to run the Application

vs2017-local-machine

Step 14

Once the Application has started running you can tap on the Open Button then you need to select a svg file which will then be displayed within the Image Control

uwp-ran-svgview-app

You can use the following sample Scalable Vector Graphics File

Download

Step 15

To Exit the Application select the Close button in the top right of the Application

vs2017-close

SvgView App is a simple application that shows how easy it is to display a svg Image in your application and scale the Image appropriately.

Creative Commons License

Universal Windows Platform – Yatzy Game

Yatzy Game demonstrates how to create a Dice Game based on Yacht or Yahtzee

Step 1

If not already, follow Setup and Start on how to Install and get Started with Visual Studio 2017 or in Windows 10 choose Start, and then from the Start Menu find and select Visual Studio 2017.

vs2017

Step 2

Once Visual Studio Community 2017 has started, from the Menu choose File, then New then Project…

vs2017-file-new-project

Step 3

From New Project choose Visual C# from Installed, Templates then choose Blank App (Universal Windows) and then type in the Name as YatzyGame and select a Location and then select Ok to create the Project
vs2017-new-project-yatzy-game

Step 4

Then in New Universal Windows Project you need to select the Target Version this should be at least the Windows 10, version 1803 (10.0; Build 17134) which is the April 2018 Update and the Minimum Version to be the same.

vs2017-target-platform

The Target Version will control what features your application can use in Windows 10 so by picking the most recent version you’ll be able to take advantage of those features. To make sure you always have the most recent version, in Visual Studio 2017 select Tools Extensions and Updates… then and then see if there are any Updates

Step 5

Once done select from the Menu, Project, then Add New Item…

vs2017-project-add-new-item

Step 6

From the Add New Item window select Visual C#, then Code from Installed then select Code File from the list, then type in the Name as Library.cs before selecting Add to add the file to the Project

vs2017-add-new-item-yatzy-game

Step 7

Once in the Code View for Library.cs the following should be entered:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Input;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;

namespace YatzyGame
{
    public enum YatzyScoreType
    {
        AcesScore, TwosScore, ThreesScore, FoursScore, FivesScore, SixesScore,
        UpperTotalScore, UpperTotalBonusScore, ThreeOfAKindScore, FourOfAKindScore,
        FullHouseScore, SmallStraightScore, LargeStraightScore, YahtzeeScore,
        ChanceScore, YahtzeeBonusScore, LowerTotalScore, TotalScore
    }

    public class CommandHandler : ICommand
    {
        public event EventHandler CanExecuteChanged = null;
        private readonly Action<object> _action;

        public CommandHandler(Action<object> action) { _action = action; }

        public bool CanExecute(object parameter) { return true; }

        public void Execute(object parameter) { _action(parameter); }
    }

    public class BindableBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected bool SetProperty<T>(ref T field, T value,
        [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(propertyName);
            return true;
        }

        public void OnPropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }

    public class YatzyDice : BindableBase
    {
        private ICommand _command;
        private Random _random;
        private int _index;
        private int _value;
        private bool _hold;

        public ICommand Command
        {
            get { return _command; }
            set { SetProperty(ref _command, value); }
        }

        public int Index
        {
            get { return _index; }
            set { SetProperty(ref _index, value); }
        }

        public int Value
        {
            get { return _value; }
            set { SetProperty(ref _value, value); }
        }

        public bool Hold
        {
            get { return _hold; }
            set { SetProperty(ref _hold, value); }
        }

        public YatzyDice(Random random)
        {
            _random = random;
        }

        public void Roll()
        {
            if (!Hold) Value = _random.Next(1, 7);
        }
    }

    public class YatzyCalculate : BindableBase
    {
        private const int diceTotal = 5;

        private int _total;
        private int _upperTotal;
        private int _lowerTotal;
        private bool _upperBonus;

        public void ResetScores()
        {
            _total = 0;
            _upperTotal = 0;
            _lowerTotal = 0;
            _upperBonus = false;
        }

        public void UpdateTotals(int score, bool upperScore)
        {
            if (upperScore)
            {
                _upperTotal += score;
                if (_upperTotal >= 63) _upperBonus = true;
            }
            else
            {
                _lowerTotal += score;
            }
            _total = 0;
            _total += _upperTotal;
            if (_upperBonus == true) _total += 35;
            _total += _lowerTotal;
        }

        public int GetAddUp(ref YatzyDice[] dice, int value)
        {
            int sum = 0;
            for (int i = 0; i < dice.Length; i++)
            {
                if (dice[i].Value == value)
                {
                    sum += value;
                }
            }
            return sum;
        }

        public int GetOfAKind(ref YatzyDice[] dice, int value)
        {
            int sum = 0;
            bool result = false;
            for (int i = 1; i <= 6; i++)
            {
                int count = 0;
                for (int j = 0; j < 5; j++)
                {
                    if (dice[j].Value == i) count++;
                    if (count > value) result = true;
                }
            }
            if (result)
            {
                for (int i = 0; i < dice.Length; i++)
                {
                    sum += dice[i].Value;
                }
            }
            return sum;
        }

        public int GetFullHouse(ref YatzyDice[] dice)
        {
            int sum = 0;
            int[] item = dice.Select(s => s.Value).ToArray();
            Array.Sort(item);
            if ((((item[0] == item[1]) && (item[1] == item[2])) && // Three of a Kind
               (item[3] == item[4]) && // Two of a Kind
               (item[2] != item[3])) ||
               ((item[0] == item[1]) && // Two of a Kind
               ((item[2] == item[3]) && (item[3] == item[4])) && // Three of a Kind
               (item[1] != item[2])))
            {
                sum = 25;
            }
            return sum;
        }

        public int GetSmallStraight(ref YatzyDice[] dice)
        {
            int sort = 0;
            int[] item = dice.Select(s => s.Value).ToArray();
            Array.Sort(item);
            for (int j = 0; j < 4; j++)
            {
                int value = 0;
                if (item[j] == item[j + 1])
                {
                    value = item[j];
                    for (int k = j; k < 4; k++)
                    {
                        item[k] = item[k + 1];
                    }
                    item[4] = value;
                }
            }
            if (((item[0] == 1) && (item[1] == 2) && (item[2] == 3) && (item[3] == 4)) ||
                ((item[0] == 2) && (item[1] == 3) && (item[2] == 4) && (item[3] == 5)) ||
                ((item[0] == 3) && (item[1] == 4) && (item[2] == 5) && (item[3] == 6)) ||
                ((item[1] == 1) && (item[2] == 2) && (item[3] == 3) && (item[4] == 4)) ||
                ((item[1] == 2) && (item[2] == 3) && (item[3] == 4) && (item[4] == 5)) ||
                ((item[1] == 3) && (item[2] == 4) && (item[3] == 5) && (item[4] == 6)))
            {
                sort = 30;
            }
            return sort;
        }

        public int GetLargeStraight(ref YatzyDice[] dice)
        {
            int sum = 0;
            int[] i = dice.Select(s => s.Value).ToArray();
            Array.Sort(i);
            if (((i[0] == 1) && (i[1] == 2) && (i[2] == 3) && (i[3] == 4) && (i[4] == 5)) ||
                ((i[0] == 2) && (i[1] == 3) && (i[2] == 4) && (i[3] == 5) && (i[4] == 6)))
            {
                sum = 40;
            }
            return sum;
        }

        public int GetYahtzee(ref YatzyDice[] dice)
        {
            int sum = 0;
            for (int i = 1; i <= 6; i++)
            {
                int Count = 0;
                for (int j = 0; j < 5; j++)
                {
                    if (dice[j].Value == i) Count++;
                    if (Count > 4) sum = 50;
                }
            }
            return sum;
        }

        public int GetChance(ref YatzyDice[] dice)
        {
            int sum = 0;
            for (int i = 0; i < 5; i++)
            {
                sum += dice[i].Value;
            }
            return sum;
        }

        public int TotalScore
        {
            get { return _total; }
            set { SetProperty(ref _total, value); }
        }

        public int UpperTotalScore
        {
            get { return _upperTotal; }
            set { SetProperty(ref _upperTotal, value); }
        }

        public int LowerTotalScore
        {
            get { return _lowerTotal; }
            set { SetProperty(ref _lowerTotal, value); }
        }

        public bool UpperBonus
        {
            get { return _upperBonus; }
            set { SetProperty(ref _upperBonus, value); }
        }
    }

    public class IntegerToGlyphConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value is int face)
            {
                string[] faces = { null, "\u2680", "\u2681", "\u2682", "\u2683", "\u2684", "\u2685" };
                return faces[face];
            }
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }

    public class YatzyItem : BindableBase
    {
        private int _score;
        private ICommand _command = null;
        private YatzyScoreType _type;

        public int Score
        {
            get { return _score; }
            set { SetProperty(ref _score, value); }
        }

        public ICommand Command
        {
            get { return _command; }
            set { SetProperty(ref _command, value); }
        }

        public YatzyScoreType Type
        {
            get { return _type; }
            set { SetProperty(ref _type, value); }
        }

        public string Content
        {
            get
            {
                IEnumerable<string> GetContent(YatzyScoreType type)
                {
                    string text = Enum.GetName(typeof(YatzyScoreType), type);
                    Regex regex = new Regex(@"\p{Lu}\p{Ll}*");
                    foreach (Match match in regex.Matches(text))
                    {
                        yield return match.Value;
                    }
                }
                return string.Join(" ", GetContent(_type));
            }
        }
    }

    public class YatzyBoard : BindableBase
    {
        private const int total_dice = 5;
        private const string accept = "Do you wish to Accept?";
        private readonly Random random = new Random((int)DateTime.Now.Ticks);

        private YatzyDice[] _dice = new YatzyDice[total_dice]
        {
            null, null, null, null, null
        };
        private YatzyCalculate _calculate = new YatzyCalculate();
        private List<YatzyItem> _items = new List<YatzyItem>();
        private Func<string, Task<bool>> _confirm = null;
        private int _rollCount = 0;
        private int _scoreCount = 0;

        private YatzyItem GetItemByType(YatzyScoreType type)
        {
            return _items.FirstOrDefault(t => t.Type == type);
        }

        private void SetItemScoreByType(YatzyScoreType type, int score)
        {
            YatzyItem item = GetItemByType(type);
            if (item != null)
            {
                item.Score = score;
            }
        }

        private int GetItemScoreByType(YatzyScoreType type)
        {
            YatzyItem item = GetItemByType(type);
            if (item != null)
            {
                return item.Score;
            }
            return 0;
        }

        private async void AddUpDice(YatzyScoreType type, int value)
        {
            int score = GetItemScoreByType(type);
            if (_rollCount > 0 && score == 0)
            {
                int total = _calculate.GetAddUp(ref _dice, value);
                bool result = await _confirm($"Total is {total}. {accept}");
                if (result)
                {
                    SetItemScoreByType(type, total);
                    _calculate.UpdateTotals(total, true);
                    Reset();
                }
            }
        }

        private async void ValueOfAKind(YatzyScoreType type, int value, string name)
        {
            int score = GetItemScoreByType(type);
            if (_rollCount > 0 && score == 0)
            {
                int total = _calculate.GetOfAKind(ref _dice, value - 1);
                if (total != 0)
                {
                    bool result = await _confirm($"Total is {total}. {accept}");
                    if (result)
                    {
                        SetItemScoreByType(type, total);
                        _calculate.UpdateTotals(total, false);
                        Reset();
                    }
                }
                else
                {
                    bool result = await _confirm($"No {name} of a Kind. {accept}");
                    if (result)
                    {
                        SetItemScoreByType(type, 0);
                        _calculate.UpdateTotals(total, false);
                        Reset();
                    }
                }
            }
        }

        private async void ItemScore(YatzyScoreType type, int value, string name)
        {
            int score = GetItemScoreByType(type);
            if ((_rollCount > 0) && (score == 0))
            {
                int total = 0;
                if (type == YatzyScoreType.FullHouseScore)
                {
                    total = _calculate.GetFullHouse(ref _dice);
                }
                else if(type == YatzyScoreType.SmallStraightScore)
                {
                    total = _calculate.GetSmallStraight(ref _dice);
                }
                else if (type == YatzyScoreType.LargeStraightScore)
                {
                    total = _calculate.GetLargeStraight(ref _dice);
                }
                if (total == value)
                {
                    SetItemScoreByType(type, total);
                    _calculate.UpdateTotals(total, false);
                    Reset();
                }
                else
                {
                    bool result = await _confirm($"No {name}. {accept}");
                    if (result)
                    {
                        SetItemScoreByType(type, 0);
                        _calculate.UpdateTotals(total, false);
                        Reset();
                    }
                }
            }
        }

        private async void Yahtzee()
        {
            int score = GetItemScoreByType(YatzyScoreType.YahtzeeScore);
            if ((_rollCount > 0) && (score == 0))
            {
                int total = _calculate.GetYahtzee(ref _dice);
                if (total == 50)
                {
                    SetItemScoreByType(YatzyScoreType.YahtzeeScore, total);
                    _calculate.UpdateTotals(total, false);
                    Reset();
                }
                else
                {
                    bool result = await _confirm($"No Yahtzee. {accept}");
                    if (result)
                    {
                        SetItemScoreByType(YatzyScoreType.YahtzeeScore, 0);
                        SetItemScoreByType(YatzyScoreType.YahtzeeBonusScore, 0);
                        _scoreCount++;
                        _calculate.UpdateTotals(total, true);
                        Reset();
                    }
                }
            }
        }

        private async void Chance()
        {
            int score = GetItemScoreByType(YatzyScoreType.ChanceScore);
            if ((_rollCount > 0) && (score == 0))
            {
                int total = _calculate.GetChance(ref _dice);
                bool result = await _confirm($"Total is {total}. {accept}");
                if (result)
                {
                    SetItemScoreByType(YatzyScoreType.ChanceScore, total);
                    _calculate.UpdateTotals(total, false);
                    Reset();
                }
            }
        }

        private void YahtzeeBonus()
        {
            int yahtzeeScore = GetItemScoreByType(YatzyScoreType.YahtzeeScore);
            int yahtzeeBonusScore = GetItemScoreByType(YatzyScoreType.YahtzeeBonusScore);
            if ((_rollCount > 0) && (yahtzeeScore == 0) && (yahtzeeBonusScore != 0))
            {
                int total = _calculate.GetYahtzee(ref _dice);
                if (total == 50)
                {
                    SetItemScoreByType(YatzyScoreType.YahtzeeBonusScore, 100);
                    _calculate.UpdateTotals(100, false);
                    Reset();
                }
                else
                {
                    SetItemScoreByType(YatzyScoreType.YahtzeeBonusScore, 0);
                    _calculate.UpdateTotals(0, true);
                    Reset();
                }
            }
        }

        private void Hold(int index)
        {
            if (_rollCount != 0)
            {
                _dice[index].Hold = !_dice[index].Hold;
            }
        }

        public YatzyBoard()
        {
            for (int i = 0; i < total_dice; i++)
            {
                _dice[i] = new YatzyDice(random)
                {
                    Index = i,
                    Command = new CommandHandler((param) => Hold((int)param))
                };
            }
            _items.Clear();
            foreach (YatzyScoreType type in Enum.GetValues(typeof(YatzyScoreType)))
            {
                YatzyItem item = new YatzyItem() { Type = type };
                switch (item.Type)
                {
                    case YatzyScoreType.AcesScore:
                        item.Command = new CommandHandler((p) => AddUpDice(item.Type, 1));
                        break;
                    case YatzyScoreType.TwosScore:
                        item.Command = new CommandHandler((p) => AddUpDice(item.Type, 2));
                        break;
                    case YatzyScoreType.ThreesScore:
                        item.Command = new CommandHandler((p) => AddUpDice(item.Type, 3));
                        break;
                    case YatzyScoreType.FoursScore:
                        item.Command = new CommandHandler((p) => AddUpDice(item.Type, 4));
                        break;
                    case YatzyScoreType.FivesScore:
                        item.Command = new CommandHandler((p) => AddUpDice(item.Type, 5));
                        break;
                    case YatzyScoreType.SixesScore:
                        item.Command = new CommandHandler((p) => AddUpDice(item.Type, 6));
                        break;
                    case YatzyScoreType.ThreeOfAKindScore:
                        item.Command = new CommandHandler((p) => ValueOfAKind(item.Type, 3, "Three"));
                        break;
                    case YatzyScoreType.FourOfAKindScore:
                        item.Command = new CommandHandler((p) => ValueOfAKind(item.Type, 4, "Four"));
                        break;
                    case YatzyScoreType.FullHouseScore:
                        item.Command = new CommandHandler((p) => ItemScore(item.Type, 25, "Full House"));
                        break;
                    case YatzyScoreType.SmallStraightScore:
                        item.Command = new CommandHandler((p) => ItemScore(item.Type, 30, "Small Straight"));
                        break;
                    case YatzyScoreType.LargeStraightScore:
                        item.Command = new CommandHandler((p) => ItemScore(item.Type, 40, "Large Straight"));
                        break;
                    case YatzyScoreType.YahtzeeScore:
                        item.Command = new CommandHandler((p) => Yahtzee());
                        break;
                    case YatzyScoreType.ChanceScore:
                        item.Command = new CommandHandler((p) => Chance());
                        break;
                    case YatzyScoreType.YahtzeeBonusScore:
                        item.Command = new CommandHandler((p) => YahtzeeBonus());
                        break;
                }
                _items.Add(item);
            }
        }

        public YatzyBoard(Func<string, Task<bool>> dialog) : this()
        {
            _confirm = dialog;
        }

        public YatzyDice[] Dice
        {
            get { return _dice; }
            set { SetProperty(ref _dice, value); }
        }

        public int RollCount
        {
            get { return _rollCount; }
            set { SetProperty(ref _rollCount, value); }
        }

        public int ScoreCount
        {
            get { return _scoreCount; }
            set { SetProperty(ref _scoreCount, value); }
        }

        public List<YatzyItem> Items
        {
            get { return _items; }
            set { SetProperty(ref _items, value); }
        }

        public void Clear()
        {
            _calculate.ResetScores();
            _rollCount = 0;
            _scoreCount = 0;
            SetItemScoreByType(YatzyScoreType.UpperTotalScore, _calculate.UpperTotalScore);
            SetItemScoreByType(YatzyScoreType.UpperTotalBonusScore, 0);
            SetItemScoreByType(YatzyScoreType.LowerTotalScore, _calculate.LowerTotalScore);
            SetItemScoreByType(YatzyScoreType.TotalScore, _calculate.TotalScore);
            SetItemScoreByType(YatzyScoreType.AcesScore, 0);
            SetItemScoreByType(YatzyScoreType.TwosScore, 0);
            SetItemScoreByType(YatzyScoreType.ThreesScore, 0);
            SetItemScoreByType(YatzyScoreType.FoursScore, 0);
            SetItemScoreByType(YatzyScoreType.FivesScore, 0);
            SetItemScoreByType(YatzyScoreType.SixesScore, 0);
            SetItemScoreByType(YatzyScoreType.ThreeOfAKindScore, 0);
            SetItemScoreByType(YatzyScoreType.FourOfAKindScore, 0);
            SetItemScoreByType(YatzyScoreType.FullHouseScore, 0);
            SetItemScoreByType(YatzyScoreType.SmallStraightScore, 0);
            SetItemScoreByType(YatzyScoreType.LargeStraightScore, 0);
            SetItemScoreByType(YatzyScoreType.YahtzeeScore, 0);
            SetItemScoreByType(YatzyScoreType.ChanceScore, 0);
            SetItemScoreByType(YatzyScoreType.YahtzeeScore, 0);
            int value = 1;
            foreach (YatzyDice die in _dice)
            {
                die.Hold = false;
                die.Value = value++;
            }
        }

        public async void Reset()
        {
            _rollCount = 0;
            _scoreCount++;
            foreach (YatzyDice die in _dice)
            {
                die.Hold = false;
            }
            SetItemScoreByType(YatzyScoreType.UpperTotalScore, _calculate.UpperTotalScore);
            if (_calculate.UpperBonus)
            {
                SetItemScoreByType(YatzyScoreType.UpperTotalBonusScore, 35);
            }
            else
            {
                SetItemScoreByType(YatzyScoreType.UpperTotalBonusScore, 0);
            }
            SetItemScoreByType(YatzyScoreType.LowerTotalScore, _calculate.LowerTotalScore);
            SetItemScoreByType(YatzyScoreType.TotalScore, _calculate.TotalScore);
            if (_scoreCount == 14)
            {
                int total = GetItemScoreByType(YatzyScoreType.TotalScore);
                bool result = await _confirm($"Game Over, Score {total}. Play again?");
                if (result)
                {
                    Clear();
                }
            }
        }

        public void Roll()
        {
            if (_rollCount < 3)
            {
                if (_rollCount == 0)
                {
                    foreach (YatzyDice die in _dice)
                    {
                        die.Hold = false;
                    }
                }
                foreach (YatzyDice die in _dice)
                {
                    die.Roll();
                }
                _rollCount++;
                if (_rollCount == 3)
                {
                    foreach (YatzyDice die in _dice)
                    {
                        die.Hold = true;
                    }
                }
            }
        }
    }

    public class YatzyItemTemplateSelector : DataTemplateSelector
    {
        public DataTemplate ScoreItem { get; set; }
        public DataTemplate TotalItem { get; set; }

        protected override DataTemplate SelectTemplateCore(object value, DependencyObject container)
        {
            if (value is YatzyItem item)
            {
                return item.Command != null ? ScoreItem : TotalItem;
            }
            return null;
        }
    }

    public class Library
    {
        private const string app_title = "Yatzy Game";

        private YatzyBoard _board = null;
        private IAsyncOperation<ContentDialogResult> _dialogResult = null;

        private async Task<bool> ShowDialogAsync(string content)
        {
            return await ShowDialogAsync(content, "Yes", "No");
        }

        private async Task<bool> ShowDialogAsync(string content, string primary = "Ok",
            string close = "Close", string title = app_title)
        {
            try
            {
                if (_dialogResult != null)
                {
                    _dialogResult.Cancel();
                    _dialogResult = null;
                }
                _dialogResult = new ContentDialog
                {
                    Title = title,
                    Content = content,
                    CloseButtonText = close,
                    PrimaryButtonText = primary,
                    DefaultButton = ContentDialogButton.Primary,
                }.ShowAsync();
                return await _dialogResult == ContentDialogResult.Primary;
            }
            catch (TaskCanceledException)
            {
                return false;
            }
        }

        public void Init(ref ItemsControl dice, ref ItemsControl scores)
        {
            _board = new YatzyBoard(ShowDialogAsync);
            dice.ItemsSource = _board.Dice;
            scores.ItemsSource = _board.Items;
            _board.Clear();
        }

        public void Roll()
        {
            _board.Roll();
        }

        public async void New()
        {
            bool result = await ShowDialogAsync("Start a New Game?");
            if (result)
            {
                _board.Clear();
            }
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality. There are enum values for the Game such as YatzyScoreType which contains all the possible Score Types for the Game. There is a CommandHandler Class which Implements ICommand including the CanExecute and Execute Methods.

There is a BindableBase Class which Implements INotifyPropertyChanged there are SetProperty and OnPropertyChanged Methods. The YatzyDice has Members and Properties for the ICommand, and Index, Value and Hold. The Constructor for this sets the Random Member and there is a Roll whcih will select randomised numbers for the YatzyDice.

The YatzyCalculate Class is used to Calculate Scores for the Game, it has int Members for the Totals, it has a ResetScores Method to reset these values and an UpdateTotals Method Method to set them. The GetAddUp Method will add up the values for the YatzyDice, GetOfAKind will add up values for all the Dice of a given kind, passed in as value. The GetFullHouse Method will see if the YatzyDice makes up a Full House, the GetSmallStraight and GetLargeStraight Methods will Calculate those values for the Game and return the Score for those. GetYahtzee will work out if the result of the YatzyDice is a Yahtzee and GetChance will work out the Chance Score. There a Properties for TotalScore, UpperTotalScore, LowerTotalScore and UpperBonus.

There is an IntegerToGlyphConverter Class which will convert an int to the faces of a single Dice or Die, the YatzyItem Class has Properties for int for Score and for an ICommand and YatzyScoreType and there is a Property to return the Content for a YatzyScoreType.

The YatzyBoard Class has const and readonly including for Random to produce randomised values and has Members including a YatzyDice Array, plus YatzyCalculate and List of YatzyItem and a Func of string and Task of bool. There is a Method to GetItemByType which will get a YatzyItem by YatzyScoreType and SetItemScoreByType will set a Score by YatzyScoreType and GetItemScoreByType will get a Score by YatzyScoreType.

Also in the YatzyBoard Class there is a AddUpDice Method which will add up Scores based on YatzyScoreType and will call the YatzyCalculate Method of GetAddUp. The ValueOfAKind will call the Method of GetOfAKind from YatzyCalculate and update the relevant Scores and functionality. The ItemScore Method will work out the FullHouseScore, SmallStraightScore and LargeStraightScore . The Yahtzee Method will be used when there is a Yahtzee in the Game and Chance if there is a Chance in the Game and YahtzeeBonus for a Bonus Score. The Hold Method will set the Hold Property for the YatzyDice.

The YatzyBoard Constructor will set up the YatzyDice and will setup the List of YatzyItem with CommandHandler to bind to Methods for each of the YatzyScoreType. There a Properties for the Array of YatzyDice and ones for RollCount and ScoreCount and List of YatzyItem. The Clear Method is used to reset all the Scores for the Game, the Reset Method is used between rounds of the Game and will also determine when the Game is over and the Roll Method is used to produce the values for the Dice. There is a YatzyItemTemplateSelector Class for selecting the DataTemplate which will be based on the YatzyItem having the Command Property set or Not.

The Library Class has a ShowDialogAsync Method to display a MessageDialog and a Property for the YatzyBoard. There is an Init Method which is used to help with the look-and-feel of the Game, the Roll Method which calls the Roll Method of the YatzyBoard Class and New will begin a Game.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-yatzy-game

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

The Design View will be displayed along with the XAML View and in this above the Grid element, enter the following XAML:

<Page.Resources>
	<DataTemplate x:Key="DiceTemplate" x:DataType="local:YatzyDice">
		<StackPanel>
			<Grid Margin="2" Height="50" Width="50" CornerRadius="2" 
				Background="{ThemeResource AccentButtonBackground}">
				<FontIcon FontSize="60" Margin="0,-8,0,0" FontFamily="Segoe UI Emoji" 
				Foreground="{ThemeResource AccentButtonForeground}"
				Glyph="{x:Bind Value, Mode=OneWay, Converter={StaticResource IntegerToGlyphConverter}}"/>
			</Grid>
			<ToggleButton Margin="2" HorizontalAlignment="Center" Content="Hold" 
			IsChecked="{x:Bind Hold, Mode=OneWay}" Command="{x:Bind Command, Mode=OneWay}" 
			CommandParameter="{x:Bind Index, Mode=OneWay}"/>
		</StackPanel>
	</DataTemplate>
	<DataTemplate x:Key="ScoreTemplate" x:DataType="local:YatzyItem">
		<StackPanel>
			<Grid Margin="2">
				<Grid.ColumnDefinitions>
					<ColumnDefinition Width="*"/>
					<ColumnDefinition Width="Auto"/>
				</Grid.ColumnDefinitions>
				<Button Width="200" Grid.Column="0" HorizontalContentAlignment="Left" 
				Content="{x:Bind Content}" Command="{x:Bind Command}"/>
				<Grid Grid.Column="1" Background="{ThemeResource AccentButtonBackground}">
					<TextBlock Width="75" Text="{x:Bind Score, Mode=TwoWay}"
					TextAlignment="Center" VerticalAlignment="Center"
					Foreground="{ThemeResource AccentButtonForeground}"/>
				</Grid>
			</Grid>
		</StackPanel>
	</DataTemplate>
	<DataTemplate x:Key="TotalTemplate" x:DataType="local:YatzyItem">
		<StackPanel>
			<Grid Margin="2">
				<Grid.ColumnDefinitions>
					<ColumnDefinition Width="*"/>
					<ColumnDefinition Width="Auto"/>
				</Grid.ColumnDefinitions>
				<TextBlock Width="200" Grid.Column="0" TextAlignment="Right" 
				Text="{x:Bind Content}" FontWeight="SemiBold"/>
				<Grid Grid.Column="1" Background="{ThemeResource AccentButtonForeground}">
					<TextBlock Width="75" Text="{x:Bind Score, Mode=TwoWay}" 
					TextAlignment="Center" VerticalAlignment="Center" 
					Foreground="{ThemeResource AccentButtonBackground}"/>
				</Grid>
			</Grid>
		</StackPanel>
	</DataTemplate>
	<local:YatzyItemTemplateSelector x:Key="YatzyItemTemplateSelector" 
		ScoreItem="{StaticResource ScoreTemplate}" TotalItem="{StaticResource TotalTemplate}" />
	<local:IntegerToGlyphConverter x:Key="IntegerToGlyphConverter"/>
</Page.Resources>

Then while still in the XAML View between the Grid and /Grid elements, enter the following XAML:

<Viewbox>
	<StackPanel Margin="50">
		<ItemsControl Name="Dice" HorizontalAlignment="Center" ItemTemplate="{StaticResource DiceTemplate}">
			<ItemsControl.ItemsPanel>
				<ItemsPanelTemplate>
					<StackPanel Orientation="Horizontal"/>
				</ItemsPanelTemplate>
			</ItemsControl.ItemsPanel>
		</ItemsControl>
		<ItemsControl Name="Display" HorizontalAlignment="Center">
			<ItemsControl.ItemTemplate>
				<DataTemplate>
					<ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource YatzyItemTemplateSelector}"/>
				</DataTemplate>
			</ItemsControl.ItemTemplate>
		</ItemsControl>
	</StackPanel>
</Viewbox>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="Page2" Label="New" Click="New_Click"/>
	<AppBarButton Icon="Shuffle" Label="Roll" Click="Roll_Click"/>
</CommandBar>

Within the Page.Resources block of XAML above the Grid Element contains DataTemplate for YatzyDice and for the YatzyItem of ScoreTemplate and TotalTemplate which are the the look-and-feel for the Dice and the Button Controls to be selected for the Scores and TextBlock Controls to display them. There is a YatzyItemTemplateSelector and IntegerToGlyphConverter to help display the correct DataTemplate for the look-and-feel of the Score Board of the Game and to display the Dice.

Within the main Grid Element, the first block of XAML is a Viewbox Control which contains an StackPanel which is where the Board will be displayed with two ItemsControl Controls. The second block of XAML is a CommandBar with AppBarButton for New which calls New_Click and Roll which calls Roll_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

Once in the Code View, below the end of public MainPage() { … } the following Code should be entered:

Library library = new Library();

protected override void OnNavigatedTo(NavigationEventArgs e)
{
	library.Init(ref Dice, ref Display);
}

private void New_Click(object sender, RoutedEventArgs e)
{
	library.New();
}

private void Roll_Click(object sender, RoutedEventArgs e)
{
	library.Roll();
}

There is an OnNavigatedTo Event Handler which will call the Init Method from the Library Class, and New_Click which will call the New Method of the Library Class and Roll_Click will call the Roll Method of the Library Class.

Step 13

That completes the Universal Windows Platform Application so Save the Project then in Visual Studio select the Local Machine to run the Application

vs2017-local-machine

Step 14

Once the Application has started running you can tap on the Roll Button to randomly select the values for the Dice and you can use the other Buttons to total up your Score based on the values of the Dice

uwp-ran-yatzy-game

Step 15

To Exit the Application select the Close button in the top right of the Application

vs2017-close

Yatzy Game is a Dice Game based on Yacht or Yahtzee and uses the Calculation Logic created by Mike Kitchen from their Example A Simple Yahtzee Game, this hopefully and interesting Game not only to create but to play as well.

Creative Commons License

Universal Windows Platform – Tiles Game

Tiles Game demonstrates how to create a Game where you have to tap on all the Black Tiles in the quickest time, however tap any White Tile or a Black Tile in the wrong place then you lose!

Step 1

If not already, follow Setup and Start on how to Install and get Started with Visual Studio 2017 or in Windows 10 choose Start, and then from the Start Menu find and select Visual Studio 2017.

vs2017

Step 2

Once Visual Studio Community 2017 has started, from the Menu choose File, then New then Project…

vs2017-file-new-project

Step 3

From New Project choose Visual C# from Installed, Templates then choose Blank App (Universal Windows) and then type in the Name as TilesGame and select a Location and then select Ok to create the Project
vs2017-new-project-tiles-game

Step 4

Then in New Universal Windows Project you need to select the Target Version this should be at least the Windows 10, version 1803 (10.0; Build 17134) which is the April 2018 Update and the Minimum Version to be the same.

vs2017-target-platform

The Target Version will control what features your application can use in Windows 10 so by picking the most recent version you’ll be able to take advantage of those features. To make sure you always have the most recent version, in Visual Studio 2017 select Tools Extensions and Updates… then and then see if there are any Updates

Step 5

Once done select from the Menu, Project, then Add New Item…

vs2017-project-add-new-item

Step 6

From the Add New Item window select Visual C#, then Code from Installed then select Code File from the list, then type in the Name as Library.cs before selecting Add to add the file to the Project

vs2017-add-new-item-tiles-game

Step 7

Once in the Code View for Library.cs the following should be entered:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;

namespace TilesGame
{
    public enum TilesType
    {
        White, Black, Start, Finish, Correct, Incorrect
    }

    public enum TilesState
    {
        Ready, Started, Lost, Complete
    }

    public class TilesItem
    {
        public int Row { get; set; }
        public TilesType Type { get; set; }

        public TilesItem(int row, TilesType type)
        {
            Row = row;
            Type = type;
        }
    }

    public class TilesBoard
    {
        private const int total = 32;
        private const int offset = 3;
        private const int columns = 4;
        private const int view_height = 400;
        private const int view_width = 200;
        private const int tile_height = view_height / 4;
        private const int tile_width = view_width / 4;
        private readonly Random random = new Random((int)DateTime.Now.Ticks);
        private readonly Color[] colours =
        {
            Colors.White, Colors.Black, Colors.Yellow,
            Colors.ForestGreen, Colors.Gray, Colors.IndianRed
        };

        private List<int> _selected = new List<int>();
        private DateTime _start;
        private double _height;
        private int _current;
        private Grid _layout;

        public delegate void StateChangedEvent(TilesState state);
        public event StateChangedEvent StateChanged;

        public TimeSpan Current { get { return DateTime.Now - _start; } }

        public TimeSpan Best { get; private set; }

        public TimeSpan Time { get; private set; }

        public TilesState State { get; private set; }

        private List<int> Choose()
        {
            List<int> list = new List<int>();
            while ((list.Count < total))
            {
                list.Add(random.Next(0, columns));
            }
            return list;
        }

        private SolidColorBrush GetBrush(TilesType type)
        {
            return new SolidColorBrush(colours[(int)type]);
        }

        private void Grid_Tapped(object sender, TappedRoutedEventArgs e)
        {
            if (State != TilesState.Lost)
            {
                Grid grid = (Grid)sender;
                TilesItem item = (TilesItem)grid.Tag;
                if (item.Type == TilesType.Black)
                {
                    _current--;
                    if (_current == item.Row)
                    {
                        if (State != TilesState.Started)
                        {
                            _start = DateTime.Now;
                            State = TilesState.Started;
                        }
                        grid.Background = GetBrush(TilesType.Correct);
                        _height = _height + tile_height;
                        Canvas.SetTop(_layout, _height);
                        if (_current == offset + 1)
                        {
                            _height = _height + tile_height;
                            Canvas.SetTop(_layout, _height);
                            Time = DateTime.Now - _start;
                            if (Best == TimeSpan.Zero || Time < Best)
                            {
                                Best = Time;
                            }
                            State = TilesState.Complete;
                        }
                        else
                        {
                            State = TilesState.Started;
                        }
                    }
                    else
                    {
                        grid.Background = GetBrush(TilesType.Incorrect);
                        State = TilesState.Lost;
                    }
                }
                else if (item.Type == TilesType.White)
                {
                    grid.Background = GetBrush(TilesType.Incorrect);
                    State = TilesState.Lost;
                }
                StateChanged?.Invoke(State);
            }
        }

        private Grid Add(int row, int column)
        {
            TilesType type = TilesType.White;
            if (row <= offset)
            {
                type = TilesType.Finish;
            }
            else if (row >= total)
            {
                type = TilesType.Start;
            }
            else
            {
                type = (_selected[row] == column) ?
                TilesType.Black : TilesType.White;
            }
            Grid grid = new Grid()
            {
                Background = GetBrush(type),
                Tag = new TilesItem(row, type),
                BorderThickness = new Thickness(1),
                BorderBrush = new SolidColorBrush(Colors.WhiteSmoke)
            };
            Grid.SetRow(grid, row);
            Grid.SetColumn(grid, column);
            grid.Tapped += Grid_Tapped;
            return grid;
        }

        private void Layout(ref Canvas canvas)
        {
            canvas.Children.Clear();
            int rows = total + offset;
            _layout = new Grid()
            {
                VerticalAlignment = VerticalAlignment.Top,
                HorizontalAlignment = HorizontalAlignment.Center
            };
            _layout.Children.Clear();
            _layout.ColumnDefinitions.Clear();
            _layout.RowDefinitions.Clear();
            for (int row = 0; (row < rows); row++)
            {
                _layout.RowDefinitions.Add(new RowDefinition()
                {
                    Height = new GridLength(tile_height)
                });
            }
            for (int column = 0; (column < columns); column++)
            {
                _layout.ColumnDefinitions.Add(new ColumnDefinition()
                {
                    Width = new GridLength(tile_width)
                });
            }
            for (int row = 0; (row < total + offset); row++)
            {
                for (int column = 0; (column < columns); column++)
                {
                    _layout.Children.Add(Add(row, column));
                }
            }
            _height = -tile_height * total + (tile_height * offset);
            Canvas.SetTop(_layout, _height);
            canvas.Children.Add(_layout);
        }

        public void Init(ref Canvas canvas)
        {
            _current = total;
            Time = TimeSpan.Zero;
            State = TilesState.Ready;
            _selected = Choose();
            Layout(ref canvas);
        }
    }

    public class Library
    {
        private const string app_title = "Tiles Game";

        private TilesBoard _board = new TilesBoard();
        private IAsyncOperation<IUICommand> _dialogCommand;
        private DispatcherTimer _timer;

        private async Task<bool> ShowDialogAsync(string content, string title = app_title)
        {
            try
            {
                if (_dialogCommand != null)
                {
                    _dialogCommand.Cancel();
                    _dialogCommand = null;
                }
                _dialogCommand = new MessageDialog(content, title).ShowAsync();
                await _dialogCommand;
                return true;
            }
            catch (TaskCanceledException)
            {
                return false;
            }
        }

        private void SetTextBlock(TimeSpan value, TextBlock time, TextBlock best)
        {
            time.Text = $@"Time:{Environment.NewLine}{value:ss\.fff}";
            best.Text = $@"Best:{Environment.NewLine}{_board.Best:ss\.fff}";
        }

        private async void Board_StateChanged(TilesState state)
        {
            switch (state)
            {
                case TilesState.Lost:
                    await ShowDialogAsync($@"Game Over, You Lost! Best Time: {_board.Best:ss\.fff}");
                    break;
                case TilesState.Complete:
                    await ShowDialogAsync($@"Completion Time: {_board.Time:ss\.fff}, Best Time: {_board.Best:ss\.fff}");
                    break;
            }
        }

        public void Timer(TextBlock time, TextBlock best)
        {
            if (_timer != null)
            {
                _timer.Stop();
            }
            _timer = new DispatcherTimer()
            {
                Interval = TimeSpan.FromMilliseconds(100)
            };
            _timer.Tick += (object sender, object e) =>
            {
                if (_board.State == TilesState.Ready)
                {
                    SetTextBlock(_board.Time, time, best);
                }
                else if (_board.State == TilesState.Started)
                {
                    SetTextBlock(_board.Current, time, best);
                }
                else if (_board.State == TilesState.Complete)
                {
                    SetTextBlock(_board.Time, time, best);
                    _timer.Stop();
                }
                else
                {
                    _timer.Stop();
                }
            };
            _timer.Start();
        }

        public void Init(ref Canvas display, TextBlock time, TextBlock best)
        {
            _board.StateChanged += Board_StateChanged;
            _board.Init(ref display);
            Timer(time, best);
        }

        public void New(ref Canvas display, TextBlock time, TextBlock best)
        {
            _board.Init(ref display);
            Timer(time, best);
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality. There is enum values for the Game for TilesType which are the various Tiles Types, TilesState which are the various Game States. There is a TilesItem Class which has int and TilesType Properties

There is a TilesBoard Class which has various const Values do define the sizes for the look-and feel of the Game, plus readonly for Random for creating randomised values and an Array of Color values for the Game. There are Members for a List of int as well as other values for the Game. There is an event for StateChangedEvent and there are TimeSpan Properties and a TilesState Property.

Also in the TilesBoard Class there are Methods including Choose which is used to select a randomised List of int, GetBrush is used to return a SolidColorBrush from the Array of Color Defined. There is a Grid_Tapped Event Handler which will respond to events from Grid, it will check if the current State is Not Lost. If the TilesType is Black then will begin the game by setting the State to Started and then if Not already set then this is set to the TilesType of Correct and will calculate the offset to move itself within a Canvas by using SetTop and if the selected item was the last one it will calclate the TimeSpan Property Values and set the TilesState to Complete or for any other TilesType of Black will set the TilesState to Started tapping on any other than the Current Black Tile will set the TilesState to Lost and the TilesType of Incorrect – this will also happen if the TilesType is White, any change of State value will trigger the StateChanged Event Handler.

Also while still in TilesBoard Class there is a Add Method which will set the appropriate TilesType and then will create a Grid Element and Position it according to the row and column passed in. The Layout Method helps create the look-and-feel of the Game including the Size of the elements and their position. The Init Merthod is used to configure the Game to start and calls the Choose Method to pick the configuration of the items and then calls Layout Method which will use this to create a randomised set of Tiles.

The Library Class has various const for values for the Game and a Random to select randomised numbers. There is a Member for TilesBoard and DispatcherTimer and a Member for IAsyncOperation of ContentDialogResult. There is ShowDialogAsync Method to display a ContentDialog. The SetTextBlock Method is used to display values from the TimeSpan Properties. The Board_StateChanged Event Handler responds to the TilesState of Lost and Complete and will call ShowDialogAsync to show an appropriate message. The Timer Method is used to configure a DispatcherTimer which will be used to set the various TextBlock Elements to display the current TimeSpan Values. The Init Event is used to initialise the game and call the Init and Timer Methods which are also called by the New Method.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-tiles-game

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

The Design View will be displayed along with the XAML View and in this between the Grid and /Grid elements,

<Viewbox>
	<StackPanel Orientation="Horizontal">
		<TextBlock Name="Time" Width="100" Foreground="{ThemeResource AccentButtonBackground}"
		VerticalAlignment="Center" HorizontalTextAlignment="Center"/>
		<Canvas Name="Display" Height="400" Width="200" 
		VerticalAlignment="Center" HorizontalAlignment="Center"/>
		<TextBlock Name="Best" Width="100" Foreground="{ThemeResource AccentButtonBackground}"
		VerticalAlignment="Center" HorizontalTextAlignment="Center"/>
	</StackPanel>
</Viewbox>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="Page2" Label="New" Click="New_Click"/>
</CommandBar>

Within the main Grid Element, the first block of XAML is a Viewbox Control which contains an StackPanel which is where the Game will appear, it also contains TextBlock Elements to display the values of the TimeSpan Properties. The second block of XAML is a CommandBar with AppBarButton for New which calls New_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

Once in the Code View, below the end of public MainPage() { … } the following Code should be entered:

Library library = new Library();

protected override void OnNavigatedTo(NavigationEventArgs e)
{
	library.Init(ref Display, Time, Best);
}

private void New_Click(object sender, RoutedEventArgs e)
{
	library.New(ref Display, Time, Best);
}

There is an OnNavigatedTo Event Handler which will call the Init Method from the Library Class, New_Click which will call the New Method of the Library Class.

Step 13

That completes the Universal Windows Platform Application so Save the Project then in Visual Studio select the Local Machine to run the Application

vs2017-local-machine

Step 14

Once the Application has started running you can tap on the first Black Tile to begin the Game, tap on them all then you win the game, but tap on any White Tile then you lose the game!

uwp-ran-tiles-game

Step 15

To Exit the Application select the Close button in the top right of the Application

vs2017-close

Tiles Game is a simple game that produces a Random set of Grid Elements that can be tapped to see if you can tap on them all as quickly as possible, this game is sometimes called Piano Game, Piano Tiles or Don’t tap the White Tile and is a simple game to play but a tricky one to master!

Creative Commons License

Universal Windows Platform – Emoji Game

Emoji Game demonstrates how to create a game where you can pick from selection of Emoji to identify which one is the correct one out of a set of three Emoji, get one wrong though and you lose!

Step 1

If not already, follow Setup and Start on how to Install and get Started with Visual Studio 2017 or in Windows 10 choose Start, and then from the Start Menu find and select Visual Studio 2017.

vs2017

Step 2

Once Visual Studio Community 2017 has started, from the Menu choose File, then New then Project…

vs2017-file-new-project

Step 3

From New Project choose Visual C# from Installed, Templates then choose Blank App (Universal Windows) and then type in the Name as EmojiGame and select a Location and then select Ok to create the Project
vs2017-new-project-emoji-game

Step 4

Then in New Universal Windows Project you need to select the Target Version this should be at least the Windows 10, version 1803 (10.0; Build 17134) which is the April 2018 Update and the Minimum Version to be the same.

vs2017-target-platform

The Target Version will control what features your application can use in Windows 10 so by picking the most recent version you’ll be able to take advantage of those features. To make sure you always have the most recent version, in Visual Studio 2017 select Tools Extensions and Updates… then and then see if there are any Updates

Step 5

Once done select from the Menu, Project, then Add New Item…

vs2017-project-add-new-item

Step 6

From the Add New Item window select Visual C#, then Code from Installed then select Code File from the list, then type in the Name as Library.cs before selecting Add to add the file to the Project

vs2017-add-new-item-emoji-game

Step 7

Once in the Code View for Library.cs the following should be entered:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;

namespace EmojiGame
{
    public enum EmojiType
    {
        [Description("\U0001F600")]
        Grinning,
        [Description("\U0001F601")]
        Beaming,
        [Description("\U0001F602")]
        TearsOfJoy,
        [Description("\U0001F606")]
        Squinting,
        [Description("\U0001F609")]
        Winking,
        [Description("\U0001F60B")]
        Savouring,
        [Description("\U0000263A")]
        Smiling,
        [Description("\U0001F917")]
        Hugging,
        [Description("\U0001F914")]
        Thinking,
        [Description("\U0001F928")]
        RaisedEyebrow,
        [Description("\U0001F610")]
        Neutral,
        [Description("\U0001F611")]
        Expressionless,
        [Description("\U0001F644")]
        RollingEyes,
        [Description("\U0001F623")]
        Persevering,
        [Description("\U0001F62E")]
        OpenMouth,
        [Description("\U0001F62F")]
        Hushed,
        [Description("\U0001F62A")]
        Sleepy,
        [Description("\U0001F62B")]
        Tired,
        [Description("\U0001F634")]
        Sleeping,
        [Description("\U0001F60C")]
        Relieved,
        [Description("\U0001F612")]
        Unamused,
        [Description("\U0001F614")]
        Pensive,
        [Description("\U0001F615")]
        Confused,
        [Description("\U0001F632")]
        Astonished,
        [Description("\U00002639")]
        Frowning,
        [Description("\U0001F616")]
        Confounded,
        [Description("\U0001F61E")]
        Disappointed,
        [Description("\U0001F61F")]
        Worried,
        [Description("\U0001F624")]
        Triumph,
        [Description("\U0001F627")]
        Anguished,
        [Description("\U0001F628")]
        Fearful,
        [Description("\U0001F633")]
        Flushed,
        [Description("\U0001F92A")]
        Zany,
        [Description("\U0001F635")]
        Dizzy,
        [Description("\U0001F620")]
        Angry,
        [Description("\U0001F913")]
        Nerdy
    }

    public enum EmojiState
    {
        None, Correct, Incorrect
    }

    public class BindableBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected bool SetProperty<T>(ref T field, T value,
        [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(propertyName);
            return true;
        }

        public void OnPropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }

    public class EmojiItem : BindableBase
    {
        private EmojiType _type;
        private EmojiState _state;
        private bool _correct;

        public EmojiType Type
        {
            get { return _type; }
            set { SetProperty(ref _type, value); }
        }

        public EmojiState State
        {
            get { return _state; }
            set { SetProperty(ref _state, value); }
        }

        public bool Correct
        {
            get { return _correct; }
            set { SetProperty(ref _correct, value); }
        }

        public EmojiItem(EmojiType type, bool correct)
        {
            Type = type;
            Correct = correct;
            State = EmojiState.None;
        }
    }

    public class EmojiBoard : BindableBase
    {
        public const int Rounds = 12;
        public const int Columns = 3;

        private readonly List<EmojiType> types =
        Enum.GetValues(typeof(EmojiType)).Cast<EmojiType>().ToList();
        private readonly Random random = new Random((int)DateTime.Now.Ticks);

        private List<EmojiType> _selected = new List<EmojiType>();
        private List<EmojiType> _options = new List<EmojiType>();
        private int _current = 0;

        private List<EmojiType> ChooseTypes()
        {
            EmojiType selected;
            List<EmojiType> types = new List<EmojiType>();
            while ((types.Count < Rounds))
            {
                selected = (EmojiType)random.Next(0, this.types.Count - 1);
                if ((!types.Contains(selected)) || (types.Count < 1))
                {
                    types.Add(selected);
                }
            }
            return types;
        }

        private List<int> ChooseOptions(List<EmojiType> source)
        {
            int selected;
            List<int> target = new List<int>();
            while ((target.Count < Columns))
            {
                selected = random.Next(0, source.Count - 1);
                if ((!target.Contains(selected)) || (target.Count < 1))
                {
                    target.Add(selected);
                }
            }
            return target;
        }

        private void Shuffle(List<EmojiItem> list)
        {
            int count = list.Count;
            while (count > 1)
            {
                count--;
                int index = random.Next(count + 1);
                EmojiItem value = list[index];
                list[index] = list[count];
                list[count] = value;
            }
        }

        private IEnumerable<string> SplitCapital(string text)
        {
            Regex regex = new Regex(@"\p{Lu}\p{Ll}*");
            foreach (Match match in regex.Matches(text))
            {
                yield return match.Value;
            }
        }

        private string GetText(EmojiType type)
        {
            return string.Join(" ", SplitCapital(Enum.GetName(typeof(EmojiType), type)));
        }

        private string _text;
        private ObservableCollection<EmojiItem> _items = new ObservableCollection<EmojiItem>();

        public string Text
        {
            get { return _text; }
            set { SetProperty(ref _text, value); }
        }

        public ObservableCollection<EmojiItem> Items
        {
            get { return _items; }
            set { SetProperty(ref _items, value); }
        }

        public EmojiBoard()
        {
            _selected = ChooseTypes();
            _options = types.Where(types => !_selected.Any(selected => selected == types)).ToList();
        }

        public bool Next()
        {
            if (_current < Rounds)
            {
                Items.Clear();
                EmojiType answer = _selected[_current];
                List<EmojiItem> selections = new List<EmojiItem>
                {
                    new EmojiItem(answer, true)
                };
                Text = GetText(answer);
                List<int> indexes = ChooseOptions(_options);
                int indexOne = indexes[0];
                int indexTwo = indexes[1];
                EmojiType one = _options[indexOne];
                EmojiType two = _options[indexTwo];
                _options.RemoveAt(indexOne);
                _options.RemoveAt(indexTwo);
                selections.Add(new EmojiItem(one, false));
                selections.Add(new EmojiItem(two, false));
                Shuffle(selections);
                foreach (EmojiItem item in selections)
                {
                    Items.Add(item);
                }
                _current++;
                return true;
            }
            return false;
        }

        public void SetState(EmojiItem selected)
        {
            foreach (EmojiItem item in Items)
            {
                if (selected.Type == item.Type)
                {
                    item.Correct = selected.Correct;
                    item.State = item.Correct ?
                    EmojiState.Correct : EmojiState.Incorrect;
                }
            }
        }
    }

    public class EmojiTypeToGlyphConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value is EmojiType type)
            {
                FieldInfo info = type.GetType().GetField(type.ToString());
                DescriptionAttribute[] attr =
                    (DescriptionAttribute[])info.GetCustomAttributes(typeof(DescriptionAttribute), false);
                return attr[0].Description;
            }
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }

    public class EmojiStateToBrushConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value is EmojiState state)
            {
                Color[] colors = new Color[] { Colors.Transparent, Colors.ForestGreen, Colors.IndianRed };
                return new SolidColorBrush(colors[(int)state]);
            }
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }

    public class Library
    {
        private const string app_title = "Emoji Game";

        private bool _gameOver = false;
        private EmojiItem _selected = null;
        private IAsyncOperation<IUICommand> _dialogCommand;

        private async Task<bool> ShowDialogAsync(string content, string title = app_title)
        {
            try
            {
                if (_dialogCommand != null)
                {
                    _dialogCommand.Cancel();
                    _dialogCommand = null;
                }
                _dialogCommand = new MessageDialog(content, title).ShowAsync();
                await _dialogCommand;
                return true;
            }
            catch (TaskCanceledException)
            {
                return false;
            }
        }

        public EmojiBoard Board { get; set; } = new EmojiBoard();

        public void Init(ref ItemsControl display, ref TextBlock text)
        {
            text.SetBinding(TextBlock.TextProperty, new Binding()
            {
                Source = Board,
                Path = new PropertyPath("Text"),
                Mode = BindingMode.OneWay
            });
            _gameOver = false;
            Board.Next();
            display.ItemsSource = Board.Items;
        }

        public async void Tapped(Button button)
        {
            if (!_gameOver)
            {
                _selected = (EmojiItem)button.Tag;
                Board.SetState(_selected);
                if (_selected.Correct)
                {
                    if (!Board.Next())
                    {
                        await ShowDialogAsync("Game Over, You Won!");
                        _gameOver = true;
                    }
                }
                else
                {
                    await ShowDialogAsync("Incorrect, You Lost!");
                    _gameOver = true;
                }
            }
            else
            {
                await ShowDialogAsync("Game Over!");
            }
        }

        public void New(ref ItemsControl display, ref TextBlock text)
        {
            Board = new EmojiBoard();
            Init(ref display, ref text);
            Board.Next();
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality. There are enum values for the Game such as EmojiType which contains all the possible Emoji values using the Description Attribute to encode their appearance and there is an EmojiState for the States of the Game.

There is a BindableBase Class which implements INotifyPropertyChanged there are SetProperty and OnPropertyChanged Methods. The EmojiItem Class Inherits from BindableBase and has Properties for EmojiType, EmojiState and bool for Correct.

The EmojoBoard Class represents the Board for the Game and has const and readonly Values such as for List of EmojiType which for these also for _selected and _options. The ChooseTypes Method is used to select a randomised set of EmojiType and ChooseOptions is used to select a randomised set of int. The Shuffle Method us used to randomise a List of EmojiItem. SplitCapital and GetText are used to get the Display Name of the EmojiType.

There are Properties for string and ObservableCollection of EmojiItem and a Constructor to call ChooseTypes for _selected and put the remainder in _options. The Next Method will pick set of three EmojiItem which is comprised of two wrong answers and one correct answer and will then randomise this subset. The SetState Method will be used to update the appearance of an EmojiItem.

The EmojiTypeToGlyphConverter Class is used to Convert the EmojiType to then get the Description Attribute to return the Content of the Emoji and the EmojiStateToBrushConverter Class is used to Convert the EmojiState to a SolidColorBrush.

The Library Class has a Member for the Selected EmojiItem and a ShowDialogAsync Method to display a MessageDialog and a Property for the EmojiBoard. There is an Init Method which is used to help with the look-and-feel of the Game the Tapped Method will be used to handle when the Button for an EmojiItem has been tapped and will indicate an incorrect answer with a Message or if the Game is Over and there is a New Method to start a new Game.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-emoji-game

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

The Design View will be displayed along with the XAML View and in this above the Grid element, enter the following XAML:

<Page.Resources>
	<local:EmojiTypeToGlyphConverter x:Key="EmojiTypeToGlyphConverter"/>
	<local:EmojiStateToBrushConverter x:Key="EmojiStateToBrushConverter"/>
	<DataTemplate x:Key="EmojiTemplate" x:DataType="local:EmojiItem">
		<Button Height="130" Width="130" HorizontalAlignment="Center" VerticalAlignment="Center" 
		Background="{x:Bind State, Mode=OneWay, Converter={StaticResource EmojiStateToBrushConverter}}" 
		Tag="{x:Bind}" Tapped="Display_Tapped">
			<Viewbox>
				<TextBlock IsColorFontEnabled="True" FontFamily="Segoe UI Emoji" VerticalAlignment="Center"                           
					Text="{x:Bind Type, Mode=OneWay, Converter={StaticResource EmojiTypeToGlyphConverter}}"/>
			</Viewbox>
		</Button>
	</DataTemplate>
</Page.Resources>

Then while still in the XAML View between the Grid and /Grid elements, enter the following XAML:

<Viewbox Margin="50">
	<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
		<TextBlock Name="Label" TextAlignment="Center" HorizontalAlignment="Center"
		Style="{StaticResource SubtitleTextBlockStyle}" Width="400"/>
		<ItemsControl Name="Display" HorizontalAlignment="Center" Height="400" Width="400" 
			ItemTemplate="{StaticResource EmojiTemplate}">
			<ItemsControl.ItemsPanel>
				<ItemsPanelTemplate>
					<StackPanel HorizontalAlignment="Center" Orientation="Horizontal"/>
				</ItemsPanelTemplate>
			</ItemsControl.ItemsPanel>
		</ItemsControl>
	</StackPanel>
</Viewbox>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="Page2" Label="New" Click="New_Click"/>
</CommandBar>

Within the Page.Resources block of XAML above the Grid Element contains a EmojiTypeToGlyphConverter and EmojiStateToBrushConverter. There is a DataTemplate which is for a EmojiItem which is a Button with its Background Databound to State using the EmojiStateToBrushConverter and with its Content set to a Viewbox with a TextBlock with its Text Databound to the Type using the EmojiTypeToGlyphConverter.

Within the main Grid Element, the first block of XAML is a Viewbox Control which contains an StackPanel which is where the Board will be displayed using the given ItemTemplate. The second block of XAML is a CommandBar with AppBarButton for New which calls New_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

Once in the Code View, below the end of public MainPage() { … } the following Code should be entered:

Library library = new Library();

protected override void OnNavigatedTo(NavigationEventArgs e)
{
	library.Init(ref Display, ref Label);
}

private void Display_Tapped(object sender, TappedRoutedEventArgs e)
{
	library.Tapped((Button)sender);
}

private void New_Click(object sender, RoutedEventArgs e)
{
	library.New(ref Display, ref Label);
}

There is an OnNavigatedTo Event Handler which will call the Init Method from the Library Class, the Display_Tapped Event Handler which will handle when a Button has been Tapped and New_Click which will call the New Method of the Library Class and Play_Click will call the Play Method of the Library Class.

Step 13

That completes the Universal Windows Platform Application so Save the Project then in Visual Studio select the Local Machine to run the Application

vs2017-local-machine

Step 14

Once the Application has started running you can tap on an Emoji that you think is the one being asked for, if you get it right you progress to the next set of three Emoji to pick from – get all nine rounds and you Win, but get any wrong and you Lose the Game

uwp-ran-emoji-game

Step 15

To Exit the Application select the Close button in the top right of the Application

vs2017-close

Emoji Game is a simple game that produces a Random set of Emoji that then can be placed into a set of three to choose from, with nine rounds each one must be correct to win the game, it demonstrates how to use the Description Attribute to store the encoded Emoji Characters, it is a simple game but could be used to create other games such as Trivia and many more!

Creative Commons License

Universal Windows Platform – Racer Game

Racer Game demonstrates how to create a game where you can pick from a Racer from a selection of Cars to see if yours will be the Winner with the quickest time

Step 1

If not already, follow Setup and Start on how to Install and get Started with Visual Studio 2017 or in Windows 10 choose Start, and then from the Start Menu find and select Visual Studio 2017.

vs2017

Step 2

Once Visual Studio Community 2017 has started, from the Menu choose File, then New then Project…

vs2017-file-new-project

Step 3

From New Project choose Visual C# from Installed, Templates then choose Blank App (Universal Windows) and then type in the Name as RacerGame and select a Location and then select Ok to create the Project
vs2017-new-project-racer-game

Step 4

Then in New Universal Windows Project you need to select the Target Version this should be at least the Windows 10, version 1803 (10.0; Build 17134) which is the April 2018 Update and the Minimum Version to be the same.

vs2017-target-platform

The Target Version will control what features your application can use in Windows 10 so by picking the most recent version you’ll be able to take advantage of those features. To make sure you always have the most recent version, in Visual Studio 2017 select Tools Extensions and Updates… then and then see if there are any Updates

Step 5

Once done select from the Menu, Project, then Add New Item…

vs2017-project-add-new-item

Step 6

From the Add New Item window select Visual C#, then Code from Installed then select Code File from the list, then type in the Name as Library.cs before selecting Add to add the file to the Project

vs2017-add-new-item-racer-game

Step 7

Once in the Code View for Library.cs the following should be entered:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation;

namespace RacerGame
{
    public enum RacerOption
    {
        Top, Middle, Bottom
    }

    public enum RacerState
    {
        Select, Ready, Started, Finished
    }

    public class Racer
    {
        public RacerOption Option { get; set; }
        public TimeSpan Time { get; set; }
    }

    public class Library
    {
        private const string app_title = "Racer Game";
        private const int total = 3;
        private const int width = 200;
        private const int height = 10;
        private readonly Random random = new Random((int)DateTime.Now.Ticks);

        private IAsyncOperation<ContentDialogResult> _dialogResult = null;
        private List<Grid> _items = new List<Grid>();

        private RacerState _state;
        private RacerOption _selected;
        private RacerOption _winner;
        private bool _finished;
        private int _count = 0;

        private async Task<bool> ShowDialogAsync(string content, string primary = "Ok",
            string close = "Close", string title = app_title)
        {
            try
            {
                if (_dialogResult != null)
                {
                    _dialogResult.Cancel();
                    _dialogResult = null;
                }
                _dialogResult = new ContentDialog
                {
                    Title = title,
                    Content = content,
                    CloseButtonText = close,
                    PrimaryButtonText = primary,
                    DefaultButton = ContentDialogButton.Primary,
                }.ShowAsync();
                return await _dialogResult == ContentDialogResult.Primary;
            }
            catch (TaskCanceledException)
            {
                return false;
            }
        }

        private List<TimeSpan> Choose(int start, int finish, int total)
        {
            TimeSpan selected;
            List<TimeSpan> selections = new List<TimeSpan>();
            while ((selections.Count < total))
            {
                selected = TimeSpan.FromSeconds(random.Next(start, finish) % finish);
                if ((!selections.Contains(selected)) || (selections.Count < 1))
                {
                    selections.Add(selected);
                }
            }
            return selections;
        }

        private async void Storyboard_Completed(object sender, object e)
        {
            if (_state == RacerState.Started)
            {
                Storyboard storyboard = (Storyboard)sender;
                TimeSpan duration = storyboard.GetCurrentTime();
                Racer racer = (Racer)_items.FirstOrDefault(w => ((Racer)w.Tag).Time == duration).Tag;
                _count++;
                if (_count == 1)
                {
                    _winner = racer.Option;
                    string name = Enum.GetName(typeof(RacerOption), _winner);
                    await ShowDialogAsync($"{name} completed Race in {duration.ToString()}");
                }
                if (_count == total)
                {
                    _state = RacerState.Finished;
                    ShowMessage();
                }
                _finished = true;
            }
        }

        private void Move(Grid grid, double from, double to, TimeSpan duration)
        {
            DoubleAnimation animation = new DoubleAnimation()
            {
                From = from,
                To = to,
                Duration = duration,
                EasingFunction = new ExponentialEase()
                {
                    EasingMode = EasingMode.EaseIn
                }
            };
            Storyboard storyboard = new Storyboard();
            Storyboard.SetTargetProperty(animation, "(Canvas.Left)");
            Storyboard.SetTarget(animation, grid);
            storyboard.Completed += Storyboard_Completed;
            storyboard.Children.Add(animation);
            storyboard.Begin();
        }

        private void Race()
        {
            if (_state != RacerState.Ready)
            {
                ShowMessage();
                return;
            }
            List<TimeSpan> times = Choose(5, 15, total);
            int i = 0;
            foreach (Grid grid in _items)
            {
                Racer racer = (Racer)grid.Tag;
                racer.Time = times[i];
                Move(grid, width, 0, racer.Time);
                i++;
            }
            _state = RacerState.Started;
        }

        private void Start()
        {
            _count = 0;
            _finished = false;
            _state = RacerState.Select;
            ShowMessage();
        }

        private async void ShowMessage()
        {
            switch (_state)
            {
                case RacerState.Select:
                    {
                        await ShowDialogAsync("Select Racer");
                    }
                    break;
                case RacerState.Ready:
                    {
                        string name = Enum.GetName(typeof(RacerOption), _selected);
                        bool result = await ShowDialogAsync($"Selected {name} to Win, select Play to Race", "Play");
                        if (result)
                        {
                            Race();
                        }
                    }
                    break;
                case RacerState.Finished:
                    {
                        string winnerName = Enum.GetName(typeof(RacerOption), _winner);
                        string selectedName = Enum.GetName(typeof(RacerOption), _selected);
                        string content = (_winner == _selected) ?
                        $"Won Race with {winnerName}, select New to Race again!" :
                        $"Racer {selectedName} Lost, {winnerName} Won - select New to try again.";
                        bool result = await ShowDialogAsync(content, "New");
                        if (_finished)
                        {
                            foreach (Grid grid in _items)
                            {
                                Move(grid, 0, width, TimeSpan.FromSeconds(1));
                            }
                            _finished = false;
                        }
                        if (result)
                        {
                            Start();
                        }
                    }
                    break;
            }
        }

        private void Racer_Tapped(object sender, RoutedEventArgs e)
        {
            if (_state == RacerState.Select)
            {
                Grid grid = (Grid)sender;
                Racer racer = (Racer)grid.Tag;
                _selected = racer.Option;
                _state = RacerState.Ready;
                ShowMessage();
            }
        }

        private Grid AddRacer(RacerOption type, int left)
        {
            TextBlock textblock = new TextBlock()
            {
                Text = "\U0001F3CE",
                IsColorFontEnabled = true,
                FontFamily = new FontFamily("Segoe UI Emoji")
            };
            Viewbox viewbox = new Viewbox() { Child = textblock };
            Grid grid = new Grid()
            {
                Tag = new Racer() { Option = type }
            };
            grid.Tapped += Racer_Tapped;
            grid.Children.Add(viewbox);
            Canvas.SetLeft(grid, left);
            return grid;
        }

        private void Add(ref Grid grid, RacerOption type, int row)
        {
            Canvas canvas = new Canvas()
            {
                Width = width,
                Margin = new Thickness(0, 0, 0, 30)
            };
            Grid racer = AddRacer(type, width);
            _items.Add(racer);
            canvas.Children.Add(racer);
            canvas.SetValue(Grid.RowProperty, row);
            grid.Children.Add(canvas);
        }

        private void Layout(ref Grid display)
        {
            _items.Clear();
            display.Children.Clear();
            StackPanel panel = new StackPanel { Orientation = Orientation.Horizontal };
            Grid track = new Grid();
            for (int row = 0; row < total; row++)
            {
                track.RowDefinitions.Add(new RowDefinition());
                Add(ref track, (RacerOption)row, row);
            }
            Grid finish = new Grid()
            {
                Width = 5,
                Background = new SolidColorBrush(Colors.Black)
            };
            panel.Children.Add(finish);
            panel.Children.Add(track);
            display.Children.Add(panel);
        }

        public void Init(ref Grid display)
        {
            _count = 0;
            _state = RacerState.Select;
            Layout(ref display);
        }

        public void New()
        {
            Start();
        }

        public void Play()
        {
            Race();
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality. There is enum values for the Game for RacerOption which are the various Racer Options, RacerState which are the various game States. There is a Racer Class which has RacerOption and TimeSpan Properties

The Library Class has various const for values for the Game and a Random to select randomised numbers. There are Members for IAsyncOperation of ContentDialogResult and List of Grid> for Items in the Game. There are RacerOption for _selected and _winner and a ShowDialogAsync Method to display a ContentDialog. The Choose Method is used to pick a selection of TimeSpan Elements with a randomised duration and return this as a List of TimeSpan

Storyboard_Completed is an Event handler which will be triggered when a Storyboard has Completed – it will check which Racer Finished first and will display a Dialog and/or continue until all have completed. The Move Method is used to trigger a DoubleAnimation with a Storyboard which will Move a Racer along a Canvas by setting the Canvas.Left Property of a Grid.

The Race Method will call the Choose Method if the RacerState is Ready and will loop through all _items and call the Move Method for each and set the RacerState to Started. The Start Method will allow a Racer to be selected and show an appropriate message with the ShowMessage Method which will for each RacerStart perform a Function – for Select it will show a Message, for Ready it will indicate which Racer has been selected and will call Play and the Finished State which is when the race is over will determine which was the winner and which Racer was selected to win and show the appropriate Message.

The Racer_Tapped Event Hander will be triggered when a Racer has been Tapped and is used to pick the Racer that might be the Winner, the AddRacer Method is used to add the elements that make up the look-and-feel of a Racer and is called by the Add Method which is used by the Layout Method to create the look-and-feel of the Game. The Init Method is used to setup the Game initially and calls the Layout Method, New calls the Start Method and Play calls the Race Method

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-racer-game

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

The Design View will be displayed along with the XAML View and in this between the Grid and /Grid elements, enter the following XAML:

<Viewbox>
	<Grid Margin="50" Name="Display" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Viewbox>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="Page2" Label="New" Click="New_Click"/>
	<AppBarButton Icon="Play" Label="Play" Click="Play_Click"/>
</CommandBar>

Within the main Grid Element, the first block of XAML is a Viewbox Control which contains an Grid which is where the Game will appear. The second block of XAML is a CommandBar with AppBarButton for New which calls New_Click and Play which calls Play_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

Once in the Code View, below the end of public MainPage() { … } the following Code should be entered:

Library library = new Library();

protected override void OnNavigatedTo(NavigationEventArgs e)
{
	library.Init(ref Display);
}

private void New_Click(object sender, RoutedEventArgs e)
{
	library.New();
}

private void Play_Click(object sender, RoutedEventArgs e)
{
	library.Play();
}

There is an OnNavigatedTo Event Handler which will call the Init Method from the Library Class, New_Click which will call the New Method of the Library Class and Play_Click will call the Play Method of the Library Class.

Step 13

That completes the Universal Windows Platform Application so Save the Project then in Visual Studio select the Local Machine to run the Application

vs2017-local-machine

Step 14

Once the Application has started running you can tap on any Racer and then tap Play to begin a Race and you can watch and see which one wins, the one that reaches the Finish first will be the Winner – if this is your Racer then you Win!

uwp-ran-racer-game

Step 15

To Exit the Application select the Close button in the top right of the Application

vs2017-close

Racer Game is a simple game that produces a Random set of TimeSpan to indicate how long the Racer will take to reach the Finish and the one with the shortest TimeSpan will win – this Game could be added to with different assets or even different speeds or behaviours to emulate a Horse Race or anything you want!

Creative Commons License

Universal Windows Platform – Mahjong

Mahjong demonstrates how to create a game where you can play a simple game of Mahjong which is a Game where you need to match pairs of Tiles until the board has been cleared.

Step 1

If not already, follow Setup and Start on how to Install and get Started with Visual Studio 2017 or in Windows 10 choose Start, and then from the Start Menu find and select Visual Studio 2017.

vs2017

Step 2

Once Visual Studio Community 2017 has started, from the Menu choose File, then New then Project…

vs2017-file-new-project

Step 3

From New Project choose Visual C# from Installed, Templates then choose Blank App (Universal Windows) and then type in the Name as Mahjong and select a Location and then select Ok to create the Project
vs2017-new-project-mahjong

Step 4

Then in New Universal Windows Project you need to select the Target Version this should be at least the Windows 10, version 1803 (10.0; Build 17134) which is the April 2018 Update and the Minimum Version to be the same.

vs2017-target-platform

The Target Version will control what features your application can use in Windows 10 so by picking the most recent version you’ll be able to take advantage of those features. To make sure you always have the most recent version, in Visual Studio 2017 select Tools Extensions and Updates… then and then see if there are any Updates

Step 5

Once done select from the Menu, Project, then Add New Item…

vs2017-project-add-new-item

Step 6

From the Add New Item window select Visual C#, then Code from Installed then select Code File from the list, then type in the Name as Library.cs before selecting Add to add the file to the Project

vs2017-add-new-item-mahjong

Step 7

Once in the Code View for Library.cs the following should be entered:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Markup;
using Windows.UI.Xaml.Media;

namespace Mahjong
{
    public enum MahjongSelect
    {
        None, Selected, Disabled, Incorrect, Hint
    }

    public enum MahjongState
    {
        DifferentType, NotMove, ValidMove, InvalidMove, NoMoves, Won
    }

    public enum MahjongType
    {
        Joker, RedDragon, GreenDragon, WhiteDragon, EastWind, SouthWind, WestWind, NorthWind,
        Spring, Summer, Autumn, Winter, Plum, Orchid, Chrysanthemum, Bamboo,
        OneOfCircles, TwoOfCircles, ThreeOfCircles, FourOfCircles, FiveOfCircles,
        SixOfCircles, SevenOfCircles, EightOfCircles, NineOfCircles,
        OneOfBamboos, TwoOfBamboos, ThreeOfBamboos, FourOfBamboos, FiveOfBamboos,
        SixOfBamboos, SevenOfBamboos, EightOfBamboos, NineOfBamboos,
        OneOfCharacters, TwoOfCharacters, ThreeOfCharacters, FourOfCharacters, FiveOfCharacters,
        SixOfCharacters, SevenOfCharacters, EightOfCharacters, NineOfCharacters
    }

    public class BindableBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected bool SetProperty<T>(ref T field, T value,
        [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(propertyName);
            return true;
        }

        public void OnPropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }

    public class MahjongPosition : BindableBase
    {
        public int Row { get; set; }
        public int Column { get; set; }
        public int Index { get; set; }

        public MahjongPosition(int column, int row)
        {
            Row = row;
            Column = column;
        }

        public MahjongPosition(int column, int row, int index)
        {
            Row = row;
            Column = column;
            Index = index;
        }
    }

    public class MahjongTile : BindableBase
    {
        private MahjongType? _type;
        private MahjongSelect _select;
        private MahjongPosition _position;

        public MahjongType? Type
        {
            get { return _type; }
            set { SetProperty(ref _type, value); }
        }

        public MahjongSelect Select
        {
            get { return _select; }
            set { SetProperty(ref _select, value); }
        }

        public MahjongPosition Position
        {
            get { return _position; }
            set { SetProperty(ref _position, value); }
        }

        public MahjongTile(MahjongType? type, int column, int row, int index)
        {
            Type = type;
            Position = new MahjongPosition(column, row, index);
        }
    }

    public class MahjongPair : BindableBase
    {
        private static readonly Random random = new Random((int)DateTime.Now.Ticks);
        private MahjongTile _tileOne;
        private MahjongTile _tileTwo;

        public MahjongTile TileOne
        {
            get { return _tileOne; }
            set { SetProperty(ref _tileOne, value); }
        }

        public MahjongTile TileTwo
        {
            get { return _tileTwo; }
            set { SetProperty(ref _tileTwo, value); }
        }

        public MahjongPair(MahjongTile tileOne, MahjongTile tileTwo)
        {
            _tileOne = tileOne;
            _tileTwo = tileTwo;
        }

        public static MahjongPair GetPair(List<MahjongTile> tiles)
        {
            if (tiles.Count < 2) throw new InvalidOperationException();
            int index = random.Next() % tiles.Count;
            MahjongTile tileOne = tiles[index];
            tiles.RemoveAt(index);
            index = random.Next() % tiles.Count;
            MahjongTile tileTwo = tiles[index];
            tiles.RemoveAt(index);
            return new MahjongPair(tileOne, tileTwo);
        }
    }

    public class MahjongBoard : BindableBase
    {
        public const int Rows = 8;
        public const int Columns = 10;
        public const int Indexes = 5;

        private readonly byte[] layout =
        {
            0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
            1, 1, 2, 2, 2, 2, 2, 2, 1, 1,
            1, 1, 2, 3, 4, 4, 3, 2, 1, 1,
            1, 1, 2, 4, 5, 5, 4, 2, 1, 1,
            1, 1, 2, 4, 5, 5, 4, 2, 1, 1,
            1, 1, 2, 3, 4, 4, 3, 2, 1, 1,
            1, 1, 2, 2, 2, 2, 2, 2, 1, 1,
            0, 1, 1, 1, 1, 1, 1, 1, 1, 0
        };
        private readonly List<MahjongType> types =
        Enum.GetValues(typeof(MahjongType)).Cast<MahjongType>().ToList();
        private readonly Random random = new Random((int)DateTime.Now.Ticks);

        private bool _started;
        private MahjongState _state;
        private ObservableCollection<MahjongTile> _tiles;

        private MahjongTile GetTile(int column, int row, int index)
        {
            return _tiles.FirstOrDefault(w =>
            w.Position.Row == row &&
            w.Position.Column == column &&
            w.Position.Index == index);
        }

        private MahjongPosition[] GetPositions(int column, int row)
        {
            MahjongPosition[] positions = new MahjongPosition[Columns * Rows];
            int p = 0;
            for (int c = 0; c < Columns; c++)
            {
                for (int r = 0; r < Rows; r++)
                {
                    positions[p++] = new MahjongPosition(column + c, row + r);
                }
            }
            return positions;
        }

        private bool CanMove(MahjongTile tile, int column, int row, int index)
        {
            MahjongPosition[] positions = GetPositions(tile.Position.Column, tile.Position.Row);
            int i = tile.Position.Index + index;
            for (int p = 0; p < positions.Length; p++)
            {
                int c = positions[p].Column + column;
                int r = positions[p].Row + row;
                MahjongTile found = GetTile(c, r, i);
                if (found != null && tile != found) return false;
            }
            return true;
        }

        private bool CanMoveUp(MahjongTile tile) => CanMove(tile, 0, 0, 1);

        private bool CanMoveRight(MahjongTile tile) => CanMove(tile, 1, 0, 0);

        private bool CanMoveLeft(MahjongTile tile) => CanMove(tile, -1, 0, 0);

        public bool CanMove(MahjongTile tile)
        {
            bool up = CanMoveUp(tile);
            bool leftUp = up && CanMoveLeft(tile);
            bool rightUp = up && CanMoveRight(tile);
            return leftUp || rightUp;
        }

        private bool NextMovePossible()
        {
            List<MahjongTile> removable = new List<MahjongTile>();
            foreach (MahjongTile tile in _tiles)
            {
                if (CanMove(tile)) removable.Add(tile);
            }
            for (int i = 0; i < removable.Count; i++)
            {
                for (int j = 0; j < removable.Count; j++)
                {
                    if (j != i && removable[i].Type == removable[j].Type)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        private MahjongTile AddTile(MahjongType? type, int column, int row, int index)
        {
            byte current = layout[row * Columns + column];
            return (current > 0 && index < current) ?
            new MahjongTile(type, column, row, index) : null;
        }

        private void Add(MahjongTile tile) => _tiles.Add(tile);

        private void Remove(MahjongTile tile) => _tiles.Remove(tile);

        private List<MahjongTile> ExtractRemovableTiles(MahjongBoard board)
        {
            List<MahjongTile> removable = new List<MahjongTile>();
            MahjongTile[] tiles = new MahjongTile[board.Tiles.Count];
            board.Tiles.CopyTo(tiles, 0);
            foreach (MahjongTile tile in tiles)
            {
                if (board.CanMove(tile)) removable.Add(tile);
            }
            foreach (MahjongTile tile in removable)
            {
                board.Remove(tile);
            }
            return removable;
        }

        private void Scramble(MahjongBoard board)
        {
            List<MahjongPair> reversed = new List<MahjongPair>();
            while (board.Tiles.Count > 0)
            {
                List<MahjongTile> removable = new List<MahjongTile>();
                removable.AddRange(ExtractRemovableTiles(board));
                while (removable.Count > 1)
                {
                    reversed.Add(MahjongPair.GetPair(removable));
                }
                foreach (MahjongTile tile in removable)
                {
                    board.Add(tile);
                }
            }
            for (int i = reversed.Count - 1; i >= 0; i--)
            {
                int typeIndex = random.Next() % types.Count;
                reversed[i].TileOne.Type = board.types[typeIndex];
                reversed[i].TileTwo.Type = board.types[typeIndex];
                board.Add(reversed[i].TileOne);
                board.Add(reversed[i].TileTwo);
            }
        }

        private void Structure(MahjongBoard board)
        {
            for (int index = 0; index < Indexes; index++)
            {
                for (int column = 0; column < Columns; column++)
                {
                    for (int row = 0; row < Rows; row++)
                    {
                        MahjongTile tile = AddTile(null, column, row, index);
                        if (tile != null) board.Tiles.Add(tile);
                    }
                }
            }
        }

        private void Generate(MahjongBoard board)
        {
            board.Tiles = new ObservableCollection<MahjongTile>();
            Structure(board);
            Scramble(board);
        }

        private MahjongPair GetHint()
        {
            List<MahjongTile> tiles = new List<MahjongTile>();
            foreach (MahjongTile tile in _tiles)
            {
                if (CanMove(tile)) tiles.Add(tile);
            }
            for (int i = 0; i < tiles.Count; i++)
            {
                for (int j = 0; j < tiles.Count; j++)
                {
                    if (i == j) continue;
                    if (tiles[i].Type == tiles[j].Type)
                    {
                        return new MahjongPair(tiles[i], tiles[j]);
                    }
                }
            }
            return null;
        }

        public MahjongState State
        {
            get { return _state; }
            set { SetProperty(ref _state, value); }
        }

        public ObservableCollection<MahjongTile> Tiles
        {
            get { return _tiles; }
            set { SetProperty(ref _tiles, value); }
        }

        public void Start() => Generate(this);

        public void Shuffle() => Scramble(this);

        public MahjongState Play(MahjongTile tileOne, MahjongTile tileTwo)
        {
            if (!_started) _started = true;
            if (tileOne == tileTwo) return MahjongState.InvalidMove;
            if (tileOne.Type != tileTwo.Type) return MahjongState.DifferentType;
            if (!CanMove(tileOne) || !CanMove(tileTwo)) return MahjongState.NotMove;
            Remove(tileOne);
            Remove(tileTwo);
            if (_tiles.Count == 0) return MahjongState.Won;
            MahjongState result = MahjongState.ValidMove;
            if (!NextMovePossible()) result |= MahjongState.NoMoves;
            return result;
        }

        public void SetHint()
        {
            if (Tiles.Count > 0)
            {
                SetNone();
                MahjongPair pair = GetHint();
                pair.TileOne.Select = MahjongSelect.Hint;
                pair.TileTwo.Select = MahjongSelect.Hint;
            }
        }

        public void SetNone()
        {
            if (Tiles.Count > 0)
            {
                foreach (MahjongTile tile in Tiles)
                {
                    tile.Select = MahjongSelect.None;
                }
            }
        }

        public void SetDisabled()
        {
            if (Tiles.Count > 0)
            {
                foreach (MahjongTile tile in Tiles)
                {
                    tile.Select = CanMove(tile) ?
                    MahjongSelect.None : MahjongSelect.Disabled;
                }
            }
        }
    }

    public class MahjongSelectToBrushConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value is MahjongSelect select)
            {
                switch (select)
                {
                    case MahjongSelect.None:
                        return new SolidColorBrush(Colors.Transparent);
                    case MahjongSelect.Selected:
                        return new SolidColorBrush(Colors.ForestGreen);
                    case MahjongSelect.Disabled:
                        return new SolidColorBrush(Colors.DarkSlateGray);
                    case MahjongSelect.Incorrect:
                        return new SolidColorBrush(Colors.IndianRed);
                    case MahjongSelect.Hint:
                        return new SolidColorBrush(Colors.CornflowerBlue);
                }
            }
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }

    public class MahjongTypeToGlyphConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value is MahjongType type)
            {
                switch (type)
                {
                    case MahjongType.Joker:
                        return "\U0001F02A";
                    case MahjongType.RedDragon:
                        return "\U0001F004";
                    case MahjongType.GreenDragon:
                        return "\U0001F005";
                    case MahjongType.WhiteDragon:
                        return "\U0001F006";
                    case MahjongType.EastWind:
                        return "\U0001F000";
                    case MahjongType.SouthWind:
                        return "\U0001F001";
                    case MahjongType.WestWind:
                        return "\U0001F002";
                    case MahjongType.NorthWind:
                        return "\U0001F003";
                    case MahjongType.Spring:
                        return "\U0001F026";
                    case MahjongType.Summer:
                        return "\U0001F027";
                    case MahjongType.Autumn:
                        return "\U0001F028";
                    case MahjongType.Winter:
                        return "\U0001F029";
                    case MahjongType.Plum:
                        return "\U0001F022";
                    case MahjongType.Orchid:
                        return "\U0001F023";
                    case MahjongType.Chrysanthemum:
                        return "\U0001F025";
                    case MahjongType.Bamboo:
                        return "\U0001F024";
                    case MahjongType.OneOfCircles:
                        return "\U0001F019";
                    case MahjongType.TwoOfCircles:
                        return "\U0001F01A";
                    case MahjongType.ThreeOfCircles:
                        return "\U0001F01B";
                    case MahjongType.FourOfCircles:
                        return "\U0001F01C";
                    case MahjongType.FiveOfCircles:
                        return "\U0001F01D";
                    case MahjongType.SixOfCircles:
                        return "\U0001F01E";
                    case MahjongType.SevenOfCircles:
                        return "\U0001F01F";
                    case MahjongType.EightOfCircles:
                        return "\U0001F020";
                    case MahjongType.NineOfCircles:
                        return "\U0001F021";
                    case MahjongType.OneOfBamboos:
                        return "\U0001F010";
                    case MahjongType.TwoOfBamboos:
                        return "\U0001F011";
                    case MahjongType.ThreeOfBamboos:
                        return "\U0001F012";
                    case MahjongType.FourOfBamboos:
                        return "\U0001F013";
                    case MahjongType.FiveOfBamboos:
                        return "\U0001F014";
                    case MahjongType.SixOfBamboos:
                        return "\U0001F015";
                    case MahjongType.SevenOfBamboos:
                        return "\U0001F016";
                    case MahjongType.EightOfBamboos:
                        return "\U0001F017";
                    case MahjongType.NineOfBamboos:
                        return "\U0001F018";
                    case MahjongType.OneOfCharacters:
                        return "\U0001F007";
                    case MahjongType.TwoOfCharacters:
                        return "\U0001F008";
                    case MahjongType.ThreeOfCharacters:
                        return "\U0001F009";
                    case MahjongType.FourOfCharacters:
                        return "\U0001F00A";
                    case MahjongType.FiveOfCharacters:
                        return "\U0001F00B";
                    case MahjongType.SixOfCharacters:
                        return "\U0001F00C";
                    case MahjongType.SevenOfCharacters:
                        return "\U0001F00D";
                    case MahjongType.EightOfCharacters:
                        return "\U0001F00E";
                    case MahjongType.NineOfCharacters:
                        return "\U0001F00F";
                }
            }
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }

    public class IntToThicknessConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value is int index) return new Thickness(0, 0, 0, index * 3);
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }

    public class MahjongTileStyle : StyleSelector
    {
        public Style Tile { get; set; }

        protected override Style SelectStyleCore(object item, DependencyObject container)
        {
            if (item is MahjongTile tile) return Tile;
            return base.SelectStyleCore(item, container);
        }
    }

    public class Binder
    {
        public static readonly DependencyProperty GridColumnBindingPathProperty =
        DependencyProperty.RegisterAttached("GridColumnBindingPath", typeof(string), typeof(Binder),
        new PropertyMetadata(null, BindingPathPropertyChanged));

        public static readonly DependencyProperty GridRowBindingPathProperty =
        DependencyProperty.RegisterAttached("GridRowBindingPath", typeof(string), typeof(Binder),
        new PropertyMetadata(null, BindingPathPropertyChanged));

        public static readonly DependencyProperty CanvasZIndexBindingPathProperty =
        DependencyProperty.RegisterAttached("CanvasZIndexBindingPath", typeof(string), typeof(Binder),
        new PropertyMetadata(null, BindingPathPropertyChanged));

        public static string GetGridColumnBindingPath(DependencyObject obj)
        {
            return (string)obj.GetValue(GridColumnBindingPathProperty);
        }

        public static void SetGridColumnBindingPath(DependencyObject obj, string value)
        {
            obj.SetValue(GridColumnBindingPathProperty, value);
        }

        public static string GetGridRowBindingPath(DependencyObject obj)
        {
            return (string)obj.GetValue(GridRowBindingPathProperty);
        }

        public static void SetGridRowBindingPath(DependencyObject obj, string value)
        {
            obj.SetValue(GridRowBindingPathProperty, value);
        }

        public static string GetCanvasZIndexBindingPath(DependencyObject obj)
        {
            return (string)obj.GetValue(CanvasZIndexBindingPathProperty);
        }

        public static void SetCanvasZIndexBindingPath(DependencyObject obj, string value)
        {
            obj.SetValue(CanvasZIndexBindingPathProperty, value);
        }

        private static void BindingPathPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue is string path)
            {
                DependencyProperty property = null;
                if (e.Property == GridColumnBindingPathProperty)
                    property = Grid.ColumnProperty;
                else if (e.Property == GridRowBindingPathProperty)
                    property = Grid.RowProperty;
                else if (e.Property == CanvasZIndexBindingPathProperty)
                    property = Canvas.ZIndexProperty;

                BindingOperations.SetBinding(obj, property,
                new Binding { Path = new PropertyPath(path) });
            }
        }
    }

    public class Library
    {
        private const string app_title = "Mahjong";

        private bool _gameOver = false;
        private MahjongTile _selected = null;
        private IAsyncOperation<IUICommand> _dialogCommand;

        private async Task<bool> ShowDialogAsync(string content, string title = app_title)
        {
            try
            {
                if (_dialogCommand != null)
                {
                    _dialogCommand.Cancel();
                    _dialogCommand = null;
                }
                _dialogCommand = new MessageDialog(content, title).ShowAsync();
                await _dialogCommand;
                return true;
            }
            catch (TaskCanceledException)
            {
                return false;
            }
        }

        private ItemsPanelTemplate Layout()
        {
            StringBuilder rows = new StringBuilder();
            StringBuilder columns = new StringBuilder();
            for (int r = 0; r < MahjongBoard.Rows; r++)
            {
                rows.Append($"<RowDefinition Height=\"Auto\"/>");
            }
            for (int c = 0; c < MahjongBoard.Columns; c++)
            {
                columns.Append($"<ColumnDefinition Width=\"Auto\"/>");
            }
            return (ItemsPanelTemplate)
            XamlReader.Load($@"<ItemsPanelTemplate 
            xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' 
            xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
                <Grid>
                    <Grid.ColumnDefinitions>{columns}</Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>{rows}</Grid.RowDefinitions>
                </Grid>
            </ItemsPanelTemplate>");
        }

        public MahjongBoard Board { get; set; } = new MahjongBoard();

        public void Init(ref ItemsControl display)
        {
            _gameOver = false;
            Board.Start();
            display.ItemsPanel = Layout();
            display.ItemsSource = Board.Tiles;
        }

        public async void Tapped(ItemsControl display, ContentPresenter container)
        {
            if (!_gameOver)
            {
                MahjongTile tile = (MahjongTile)display.ItemFromContainer(container);
                if (_selected == null || _selected == tile)
                {
                    Board.SetNone();
                    if (_selected == tile)
                    {
                        tile.Select = MahjongSelect.None;
                        _selected = null;
                    }
                    else
                    {
                        tile.Select = MahjongSelect.Selected;
                        _selected = tile;
                    }
                }
                else
                {
                    Board.State = Board.Play(_selected, tile);
                    switch (Board.State)
                    {
                        case MahjongState.DifferentType:
                            tile.Select = MahjongSelect.Incorrect;
                            break;
                        case MahjongState.NotMove:
                            tile.Select = MahjongSelect.Incorrect;
                            break;
                        case MahjongState.ValidMove:
                            tile.Select = MahjongSelect.None;
                            break;
                        case MahjongState.InvalidMove:
                            tile.Select = MahjongSelect.Incorrect;
                            break;
                        case MahjongState.NoMoves:
                            Board.Shuffle();
                            break;
                        case MahjongState.Won:
                            await ShowDialogAsync("You Won, Game Over!");
                            _gameOver = true;
                            break;
                    }
                    _selected = null;
                }
            }
            else
            {
                await ShowDialogAsync("You Won, Game Over!");
            }
        }

        public void New(ref ItemsControl display)
        {
            _gameOver = false;
            Board.Start();
            display.ItemsSource = Board.Tiles;
        }

        public void Hint() => Board.SetHint();

        public void Show() => Board.SetDisabled();

        public void Shuffle() => Board.Shuffle();
    }
}

In the Code File for Library there are using statements to include the necessary functionality. There is enum values for the Game for MahjongSelect which are the various visual selected States, MahjongState which are the various game States and MahjongType which are the types of Mahjong tile available. There is a BindableBase Class which implements INotifyPropertyChanged there are SetProperty and OnPropertyChanged Methods.

There is a MahjongPosition Class which Inherits from BindableBase and has int Properties for Row, Column and Index. The MahjongTile Class represents a Mahjong Tile and also Inherits from BindableBase and has Properties for MahjongType, MahjongSelect and MahjongPosition and there is a MahjongPair Class which represents a Pair of Tiles and also Inherits from BindableBase with Properties for MahjongTile and a Random to help select randomised numbers, within this Class is a GetPair Method which returns a set of two Tiles from a List of MahjongTile.

The MahjongBoard Class represents the Board of the Game itself and it Inherits from BindableBase. There are const int for Rows, Columns and Indexes and readonly Values for the byte[] Layout and List of MahjongType, Random, MahjongState and an ObservableCollection of MahjongTile. There is a GetTile Method to return a MahjongTile from the ObservableCollection of MahjongTile. The GetPositions Method returns an Array of MahjongPosition which is all the possible Positions. There is a CanMove Method which will return all possible Positions to see if a given Tile can be Moved, this will be called by the CanMoveUp, CanMoveRight, CanMoveLeft and CanMove by MahjongTile Methods. There is a NextMovePossible Method which will check if a Move is possible.

Also in the MahjongBoard Class there is an AddTile Method to help create the layout of the Board, an Add Method which has a Lambda to add to the ObservableCollection of MahjongTile and Remove to remove from it. The ExtractRemovableTiles will determine all the Tiles that can be moved, the Scramble Method will from the ObservableCollection of MahjongTile determine which ones are removable and create a randomised set of MahjongTile for the Board. There is a Structure Method which will create the set of MahjongTile for the Game and is called along with Scramble in the Generate Method. Ther GetHint Method is used to obtain a MahjongPair of Tiles from the Board which can be moved to help the Player.

While still in the MahjongBoard Class there are Properties for MahjongState and the ObservableCollection of MahjongTile, there is a Start Method Lambda which calls the Generate Method and Shuffle Method Lambda which calls the Scramble Method. There is a Play Method which will when a Tile is selected determine which MahjongState currently applies if the Pair of Tiles selected can be moved will call Remove Method and again update the MahjongState accordingly after these have been removed. Ther is a SetHint Method which will set the MahjongSelect of the MahjongPair and set their MahjongSelect to Hint, SetNone will set all Tiles to have the MahjongSelect of None and SetDisabled will set those Tiles that cannot be moved to have the MahjongSelect of Disabled.

There is a MahjongSelectToBrushConverter Class which will Convert a MahjongSelect to a relevant SolidColorBrush, There is a MahjongTypeToGlyphConverter Class which will Convert a MahjongType to the relevant Emoji Character to represent the Tiles. There is a IntToThicknessConverter which will convert a given int Index to a Thickness and there is a MahjongTileStyle is used to determine the Style for the Board. The Binder Class is used to create the Bindings for the DependencyProperty of Grid.ColumnProperty, Grid.RowProperty and Canvas.ZIndexProperty.

The Library Class has an IAsyncOperation of IUICommand for use with the ShowDialogAsync Method which will display a MessageDialog. There is a Layout Method which will be used to create the ItemsPanelTemplate for the display Layout of the Game Board. There is a MahjongBoard Property and an Init Method which will be used to create the look-and-feel of the Game, there is a Tapped Event Handler which will get Tapped MahjongTile and will set any first MahjongTile to have the MahjongSelect of Selected, when a second MahjongTile has been selected the Play Method of the MahjongBoard will be called and either a MahjongSelect will be set for a given MahjongState or the Shuffle Method will be called or ShowDialogAsync to indicate the Game is Over. There is a New Method which will begin a new Game, Hint which will display a hint to the player Show which will indicate which Tiles can be selected and Shuffle which will call the Shuffle Method from MahjongBoard.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-mahjong

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

The Design View will be displayed along with the XAML View and in this above the Grid element, enter the following XAML:

<Page.Resources>
	<Style TargetType="ContentPresenter" x:Key="TileStyle">
		<Setter Property="local:Binder.GridRowBindingPath" Value="Position.Row"/>
		<Setter Property="local:Binder.GridColumnBindingPath" Value="Position.Column"/>
		<Setter Property="local:Binder.CanvasZIndexBindingPath" Value="Position.Index" />
		<Setter Property="Background" Value="Transparent"/>
	</Style>
	<local:MahjongTileStyle x:Key="MahjongTileStyle" Tile="{StaticResource TileStyle}"/>
	<local:MahjongTypeToGlyphConverter x:Key="MahjongTypeToGlyphConverter"/>
	<local:MahjongSelectToBrushConverter x:Key="MahjongSelectToBrushConverter"/>
	<local:IntToThicknessConverter x:Key="IntToThicknessConverter"/>
	<DataTemplate x:Key="MahjongTemplate" x:DataType="local:MahjongTile">
		<Grid IsHitTestVisible="False" HorizontalAlignment="Center" VerticalAlignment="Center"
			 Margin="{x:Bind Position.Index, Mode=OneWay, Converter={StaticResource IntToThicknessConverter}}">
			<Viewbox>
				<TextBlock IsColorFontEnabled="True" FontFamily="Segoe UI Emoji" VerticalAlignment="Center"                           
				Text="{x:Bind Type, Mode=OneWay, Converter={StaticResource MahjongTypeToGlyphConverter}}"/>
			</Viewbox>
			<Rectangle Opacity="0.25" Margin="1,1,1,-1" RadiusX="2" RadiusY="2"
			HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
			Fill="{x:Bind Select, Mode=OneWay, Converter={StaticResource MahjongSelectToBrushConverter}}">
			</Rectangle>
		</Grid>
	</DataTemplate>
</Page.Resources>

Then while still in the XAML View between the Grid and /Grid elements, enter the following XAML:

<Viewbox>
	<ItemsControl Name="Display" Margin="50"
		ItemContainerStyleSelector="{StaticResource MahjongTileStyle}" 
		ItemTemplate="{StaticResource MahjongTemplate}" Tapped="Display_Tapped">
	</ItemsControl>
</Viewbox>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="Page2" Label="New" Click="New_Click"/>
	<AppBarButton Label="Hint" Click="Hint_Click">
		<AppBarButton.Icon>
			<FontIcon FontFamily="Segoe MDL2 Assets" Glyph=""/>
		</AppBarButton.Icon>
	</AppBarButton>
	<AppBarButton Label="Show" Click="Show_Click">
		<AppBarButton.Icon>
			<FontIcon FontFamily="Segoe MDL2 Assets" Glyph=""/>
		</AppBarButton.Icon>
	</AppBarButton>
	<AppBarButton Icon="Shuffle" Label="Shuffle" Click="Shuffle_Click"/>
</CommandBar>

Within the Page.Resources block of XAML above the Grid Element contains Style definitions including a Property for Row, Column and ZIndex. There is also the MahjongTileStyle, MahjongTypeToGlyphConverter, MahjongSelectToBrushConverter and IntToThicknessConverter. There is a DataTemplate which is for a MahjongTile which is a Grid with its Margin Databound to the Index using the IntToThicknessConverter and inside of which is the Viewbox with a TextBlock with its Text Databound to the Type using the MahjongTypeToGlyphConverter, there is a Rectangle with its.Background Databound to the MahjongSelectToBrushConverter.

Within the main Grid Element, the first block of XAML is a Viewbox Control which contains an ItemsControl which is where the Board will be displayed using the given ItemTemplate. The second block of XAML is a CommandBar with AppBarButton for New which calls New_Click, Hint which calls Hint_Click, Show which calls Show_Click and Shuffle which calls Shuffle_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

Once in the Code View, below the end of public MainPage() { … } the following Code should be entered:

Library library = new Library();

protected override void OnNavigatedTo(NavigationEventArgs e)
{
	library.Init(ref Display);
}

private void Display_Tapped(object sender, TappedRoutedEventArgs e)
{
	library.Tapped(sender as ItemsControl, e.OriginalSource as ContentPresenter);
}

private void New_Click(object sender, RoutedEventArgs e)
{
	library.New(ref Display);
}

private void Hint_Click(object sender, RoutedEventArgs e)
{
	library.Hint();
}

private void Show_Click(object sender, RoutedEventArgs e)
{
	library.Show();
}

private void Shuffle_Click(object sender, RoutedEventArgs e)
{
	library.Shuffle();
}

There is an OnNavigatedTo Event Handler which will call the Init Method from the Library Class, Display_Tapped which handles when the ItemsControl has been Tapped and New_Click which will call the New Method of the Library Class, Hint_Click will call the Hint Method, Show_Click will call the Show Method and Shuffle_Click will call the Shuffle Method of the Library Class.

Step 13

That completes the Universal Windows Platform Application so Save the Project then in Visual Studio select the Local Machine to run the Application

vs2017-local-machine

Step 14

Once the Application has started running you can tap on a Tile and then select another Tile that matches to remove it from the Board – if you’re not sure which two to pick then use Hint to indicate a Pair to choose, or use Show to indicate which Tiles can’t be moved or if you can’t pick any Tiles then use Shuffle to move them around and you Win when all the Tiles have been removed!

uwp-ran-mahjong

Step 15

To Exit the Application select the Close button in the top right of the Application

vs2017-close

Mahjong is based upon Mahjong solitaire which is a single-player matching game that uses a set of Mahjong Tiles, there is room for improvment with this example to make the Shuffle more reliable but for a great-looking but simple example of the game it is hopefully an interesting game to create.

Creative Commons License

Universal Windows Platform – Reversi

Reversi demonstrates how to create a game where you can play a simple game of Reversi also known as Othello for two players.

Step 1

If not already, follow Setup and Start on how to Install and get Started with Visual Studio 2017 or in Windows 10 choose Start, and then from the Start Menu find and select Visual Studio 2017.

vs2017

Step 2

Once Visual Studio Community 2017 has started, from the Menu choose File, then New then Project…

vs2017-file-new-project

Step 3

From New Project choose Visual C# from Installed, Templates then choose Blank App (Universal Windows) and then type in the Name as Reversi and select a Location and then select Ok to create the Project
vs2017-new-project-reversi

Step 4

Then in New Universal Windows Project you need to select the Target Version this should be at least the Windows 10, version 1803 (10.0; Build 17134) which is the April 2018 Update and the Minimum Version to be the same.

vs2017-target-platform

The Target Version will control what features your application can use in Windows 10 so by picking the most recent version you’ll be able to take advantage of those features. To make sure you always have the most recent version, in Visual Studio 2017 select Tools Extensions and Updates… then and then see if there are any Updates

Step 5

Once done select from the Menu, Project, then Add New Item…

vs2017-project-add-new-item

Step 6

From the Add New Item window select Visual C#, then Code from Installed then select Code File from the list, then type in the Name as Library.cs before selecting Add to add the file to the Project

vs2017-add-new-item-reversi

Step 7

Once in the Code View for Library.cs the following should be entered:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Markup;

namespace Reversi
{
    public enum ReversiState
    {
        Empty, Black, White
    }

    public class BindableBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected bool SetProperty<T>(ref T field, T value,
        [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(propertyName);
            return true;
        }

        public void OnPropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }

    public class ReversiSquare : BindableBase
    {
        private ReversiState _state;
        private int _row;
        private int _column;

        public ReversiSquare(int row, int column)
        {
            _row = row;
            _column = column;
        }

        public ReversiState State
        {
            get { return _state; }
            set { SetProperty(ref _state, value); }
        }

        public int Row
        {
            get { return _row; }
            set { SetProperty(ref _row, value); }
        }

        public int Column
        {
            get { return _column; }
            set { SetProperty(ref _column, value); }
        }
    }

    public class ReversiBoard : BindableBase
    {
        private const int total = 8;

        delegate void Navigation(ref int row, ref int column);

        private static List<Navigation> _functions = new List<Navigation>()
        {
            new Navigation(delegate (ref int row, ref int column) { row++; }),
            new Navigation(delegate (ref int row, ref int column) { row++; }),
            new Navigation(delegate (ref int row, ref int column) { row--; }),
            new Navigation(delegate (ref int row, ref int column) { row++; column--; }),
            new Navigation(delegate (ref int row, ref int column) { row++; column++; }),
            new Navigation(delegate (ref int row, ref int column) { row--; column--; }),
            new Navigation(delegate (ref int row, ref int column) { row--; column++; }),
            new Navigation(delegate (ref int row, ref int column) { column++; }),
            new Navigation(delegate (ref int row, ref int column) { column--; }),
        };
        private ReversiState _nextMove = ReversiState.White;
        private int _whiteScore = 0;
        private int _blackScore = 0;
        private bool _gameOver = false;

        private ReversiSquare GetSquare(int row, int column)
        {
            return Squares.Single(s => s.Column == column && s.Row == row);
        }

        private IEnumerable<ReversiSquare> NavigateBoard(int row, int column,
            Navigation navigation)
        {
            navigation(ref column, ref row);
            while (row >= 0 && row < total && column >= 0 && column < total)
            {
                yield return GetSquare(row, column);
                navigation(ref column, ref row);
            }
        }

        private ReversiState ToggleState(ReversiState state)
        {
            return state == ReversiState.Black ?
            ReversiState.White : ReversiState.Black;
        }

        private bool IsMoveSurroundsCounters(int row, int column,
            Navigation navigation, ReversiState state)
        {
            int index = 1;
            var squares = NavigateBoard(row, column, navigation);
            foreach (var square in squares)
            {
                ReversiState currentState = square.State;
                if (index == 1)
                {
                    if (currentState != ToggleState(state)) return false;
                }
                else
                {
                    if (currentState == state) return true;
                    if (currentState == ReversiState.Empty) return false;
                }
                index++;
            }
            return false;
        }

        private void FlipOpponentCounters(int row, int column,
            ReversiState state)
        {
            foreach (Navigation navigation in _functions)
            {
                if (!IsMoveSurroundsCounters(row, column, navigation, state)) continue;
                ReversiState opponentState = ToggleState(state);
                IEnumerable<ReversiSquare> squares = NavigateBoard(row, column, navigation);
                foreach (var square in squares)
                {
                    if (square.State == state) break;
                    square.State = state;
                }
            }
        }

        private bool IsValidMove(int row, int col, ReversiState state)
        {
            if (GetSquare(row, col).State != ReversiState.Empty) return false;
            return _functions.Any(nav => IsMoveSurroundsCounters(row, col, nav, state));
        }

        private bool IsValidMove(int row, int col)
        {
            return IsValidMove(row, col, NextMove);
        }

        private bool HasPlayerMove(ReversiState state)
        {
            for (int row = 0; row < total; row++)
            {
                for (int column = 0; column < total; column++)
                {
                    if (IsValidMove(row, column, state)) return true;
                }
            }
            return false;
        }

        private bool IsGameOver()
        {
            return !HasPlayerMove(ReversiState.Black) &&
            !HasPlayerMove(ReversiState.White);
        }

        private void Setup()
        {
            foreach (ReversiSquare square in Squares)
            {
                square.State = ReversiState.Empty;
            }
            GetSquare(3, 4).State = ReversiState.Black;
            GetSquare(4, 3).State = ReversiState.Black;
            GetSquare(4, 4).State = ReversiState.White;
            GetSquare(3, 3).State = ReversiState.White;
            NextMove = ReversiState.Black;
            WhiteScore = 0;
            BlackScore = 0;
        }

        public ReversiBoard()
        {
            Squares = new List<ReversiSquare>();
            for (int row = 0; (row < total); row++)
            {
                for (int column = 0; (column < total); column++)
                {
                    Squares.Add(new ReversiSquare(row, column));
                }
            }
            Setup();
        }

        public int WhiteScore
        {
            get { return _whiteScore; }
            set { SetProperty(ref _whiteScore, value); }
        }

        public int BlackScore
        {
            get { return _blackScore; }
            set { SetProperty(ref _blackScore, value); }
        }

        public bool GameOver
        {
            get { return _gameOver; }
            set { SetProperty(ref _gameOver, value); }
        }
		
        public ReversiState NextMove
        {
            get { return _nextMove; }
            set { SetProperty(ref _nextMove, value); }
        }

        public List<ReversiSquare> Squares { get; }

        public void MakeMove(int row, int col)
        {
            if (!IsValidMove(row, col, NextMove)) return;
            GetSquare(row, col).State = NextMove;
            FlipOpponentCounters(row, col, NextMove);
            NextMove = ToggleState(NextMove);
            if (!HasPlayerMove(NextMove)) NextMove = ToggleState(NextMove);
            GameOver = IsGameOver();
            BlackScore = Squares.Count(s => s.State == ReversiState.Black);
            WhiteScore = Squares.Count(s => s.State == ReversiState.White);
        }

        public void Init()
        {
            Setup();
        }
    }

    public class ReversiStateToGlyphConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value is ReversiState state)
            {
                switch (state)
                {
                    case ReversiState.Black: return "\u26AB";
                    case ReversiState.White: return "\u26AA";
                }
            }
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }

    public class ReversiSquareStyle : StyleSelector
    {
        public Style Tile { get; set; }

        protected override Style SelectStyleCore(object item, DependencyObject container)
        {
            if (item is ReversiSquare square) return Tile;
            return base.SelectStyleCore(item, container);
        }
    }

    public class Binder
    {
        public static readonly DependencyProperty GridColumnBindingPathProperty =
        DependencyProperty.RegisterAttached("GridColumnBindingPath", typeof(string), typeof(Binder),
        new PropertyMetadata(null, GridBindingPathPropertyChanged));

        public static readonly DependencyProperty GridRowBindingPathProperty =
        DependencyProperty.RegisterAttached("GridRowBindingPath", typeof(string), typeof(Binder),
        new PropertyMetadata(null, GridBindingPathPropertyChanged));

        public static string GetGridColumnBindingPath(DependencyObject obj)
        {
            return (string)obj.GetValue(GridColumnBindingPathProperty);
        }

        public static void SetGridColumnBindingPath(DependencyObject obj, string value)
        {
            obj.SetValue(GridColumnBindingPathProperty, value);
        }

        public static string GetGridRowBindingPath(DependencyObject obj)
        {
            return (string)obj.GetValue(GridRowBindingPathProperty);
        }

        public static void SetGridRowBindingPath(DependencyObject obj, string value)
        {
            obj.SetValue(GridRowBindingPathProperty, value);
        }

        private static void GridBindingPathPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue is string path)
            {
                DependencyProperty property = null;
                if (e.Property == GridColumnBindingPathProperty)
                    property = Grid.ColumnProperty;
                else if (e.Property == GridRowBindingPathProperty)
                    property = Grid.RowProperty;

                BindingOperations.SetBinding(obj, property,
                new Binding { Path = new PropertyPath(path) });
            }
        }
    }

    public class Library
    {
        private const string app_title = "Reversi";

        private IAsyncOperation<IUICommand> _dialogCommand;

        private async Task<bool> ShowDialogAsync(string content, string title = app_title)
        {
            try
            {
                if (_dialogCommand != null)
                {
                    _dialogCommand.Cancel();
                    _dialogCommand = null;
                }
                _dialogCommand = new MessageDialog(content, title).ShowAsync();
                await _dialogCommand;
                return true;
            }
            catch (TaskCanceledException)
            {
                return false;
            }
        }

        private ItemsPanelTemplate Layout()
        {
            StringBuilder columns = new StringBuilder();
            StringBuilder rows = new StringBuilder();
            for (int i = 0; i < 8; i++)
            {
                columns.Append("<ColumnDefinition Width=\"*\"/>");
                rows.Append("<RowDefinition Height=\"*\"/>");
            }
            return (ItemsPanelTemplate)
            XamlReader.Load($@"<ItemsPanelTemplate 
            xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' 
            xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
                <Grid>
                    <Grid.ColumnDefinitions>{columns}</Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>{rows}</Grid.RowDefinitions>
                </Grid>
            </ItemsPanelTemplate>");
        }

        public ReversiBoard Board { get; set; } = new ReversiBoard();

        public void Init(ref ItemsControl display, ref TextBlock scores)
        {
            display.ItemsPanel = Layout();
            display.ItemsSource = Board.Squares;
            scores.DataContext = Board;
        }

        public async void Tapped(ItemsControl display, ContentPresenter container)
        {
            if (!Board.GameOver)
            {
                ReversiSquare square = (ReversiSquare)display.ItemFromContainer(container);
                Board.MakeMove(square.Row, square.Column);
            }
            else
            {
                await ShowDialogAsync($"Game Over! White: {Board.WhiteScore}, Black: {Board.BlackScore}");
            }
        }

        public void New(ref ItemsControl display, ref TextBlock scores)
        {
            Board = new ReversiBoard();
            scores.DataContext = Board;
            display.ItemsSource = Board.Squares;
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality. There is enum values for the Game for ReversiState. There is a BindableBase Class which implements INotifyPropertyChanged there are SetProperty and OnPropertyChanged Methods. There is a ReversiSquare Class which represents a position in a Grid with an int Property for “Row” and “Column” and also has a Property for ReveriState.

The ReversiBoard Class represents the Board of the Game itself and it Inherits from BindableBase, it has a const int for the total size of the Board, there is a delegate for use with a List of Navigation Functions and there are Members for ReversiState and int for the Scores and a bool to indicate when the Game is over. There is a Method to return a ReversiSquare by “Row” and “Column” which will be a given Position, there is a NavigateBoard Method which returns an IEnumerable of Navigation for all possible Positions. There is a Method to Toggle the ReversiState, the IsMoveSurroundsCounters Method is used to determine if a given Move surrounds Counters already on the Board. The FlipOpponentCounters will use the IsMoveSurroundsCounters to set the related Counters to the correct ReversiState based on the Positions from NavigateBoard. There are IsValidMove Methods which will check if a given Move is valid using the GetSquare and IsMoveSurroundsCounters Methods. HasPlayerMove is used to check if any Move is possible, the IsGameOver Method is used to check if either Black or White has any possible Moves and the Setup Method is used to help create the layout for the Game.

Also in the ReversiBoard Class there is the Constructor which will populate the List of ReversiSquare, there are int Properties for WhiteScore, BlackScore and a bool for GameOver and for ReversiState. There is the List of ReversiSquare, a Method for MakeMove which checks for a valid Move and will get the next possible move and will Toggle the State of the given Square and will check if the Game is Over and update the Player Scores and there is an Init Method which calls the Setup Method.

There is a ReversiStateToGlyphConverter Class which will Convert a ReversiState to the relevant Emoji Character to represent the Counters for Black and White. There is a ReversiSquareStyle is used to determine the Style for the Board. The Binder Class is used to create the Bindings for the DependencyProperty of Grid.ColumnProperty and Grid.RowProperty.

The Library Class has an IAsyncOperation of IUICommand for use with the ShowDialogAsync Method which will display a MessageDialog. There is a Layout Method which will be used to create the ItemsPanelTemplate for the display Layout of the Game Board. There is a ReversiBoard Property and an Init Method which will be used to create the look-and-feel of the Game, there is a Tapped Event Handler which will get Tapped ReversiSquare and call the MakeMove Method of the ReversiBoard or if GameOver is true then it will use the ShowDialogAsync Method to show a MessageDialog to indicate the Game is Over. There is a New Method which will begin a new Game.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-reversi

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

The Design View will be displayed along with the XAML View and in this above the Grid element, enter the following XAML:

<Page.Resources>
	<Style TargetType="ContentPresenter" x:Key="TileStyle">
		<Setter Property="local:Binder.GridRowBindingPath" Value="Row"/>
		<Setter Property="local:Binder.GridColumnBindingPath" Value="Column"/>
		<Setter Property="Background" Value="ForestGreen"/>
	</Style>
	<local:ReversiSquareStyle x:Key="ReversiSquareStyle" Tile="{StaticResource TileStyle}"/>
	<local:ReversiStateToGlyphConverter x:Key="ReversiStateToGlyphConverter"/>
	<DataTemplate x:Key="ReversiTemplate" x:DataType="local:ReversiSquare">
		<Grid IsHitTestVisible="False" BorderThickness="1" BorderBrush="Black">
			<Viewbox>
				<TextBlock IsColorFontEnabled="True" FontFamily="Segoe UI Emoji"
				Text="{x:Bind State, Mode=OneWay, Converter={StaticResource ReversiStateToGlyphConverter}}"/>
			</Viewbox>
		</Grid>
	</DataTemplate>
</Page.Resources>

Then while still in the XAML View between the Grid and /Grid elements, enter the following XAML:

<Viewbox>
	<StackPanel Margin="50">
		<TextBlock HorizontalAlignment="Center" Name="Scores">
			<Run FontFamily="Segoe UI">Score:</Run>
			<Run FontFamily="Segoe UI Emoji">⚫</Run>
			<Run Text="{Binding BlackScore}" FontFamily="Segoe UI" />
			<Run FontFamily="Segoe UI Emoji">⚪</Run>
			<Run Text="{Binding WhiteScore}" FontFamily="Segoe UI" />
			<Run FontFamily="Segoe UI">Move:</Run>
			<Run Text="{Binding NextMove, Converter={StaticResource ReversiStateToGlyphConverter}}" FontFamily="Segoe UI Emoji" />
		</TextBlock>
		<ItemsControl Name="Display" Width="400" Height="400" 
			ItemContainerStyleSelector="{StaticResource ReversiSquareStyle}" 
			ItemTemplate="{StaticResource ReversiTemplate}" Tapped="Display_Tapped">
		</ItemsControl>
	</StackPanel>
</Viewbox>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="Page2" Label="New" Click="New_Click"/>
</CommandBar>

Within the Page.Resources block of XAML above the Grid Element contains Style definitions including a Property for Row and Column. There is also the ReversiSquareStyle and ReversiStateToGlyphConverter. There is a DataTemplate which is for a ReversiSquare which is a TextBlock within a Viewbox with the Text Databound to the State using the ReversiStateToGlyphConverter.

Within the main Grid Element, the first block of XAML is a Viewbox Control which contains a StackPanel Control with a TextBlock with many Run Elements to help display the Scores, there is an ItemsControl which is where the Board will be displayed using the given ItemTemplate .The second block of XAML is a CommandBar with AppBarButton for New which calls New_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

Once in the Code View, below the end of public MainPage() { … } the following Code should be entered:

Library library = new Library();

protected override void OnNavigatedTo(NavigationEventArgs e)
{
	library.Init(ref Display, ref Scores);
}

private void Display_Tapped(object sender, TappedRoutedEventArgs e)
{
	library.Tapped(sender as ItemsControl, e.OriginalSource as ContentPresenter);
}

private void New_Click(object sender, RoutedEventArgs e)
{
	library.New(ref Display, ref Scores);
}

There is an OnNavigatedTo Event Handler which will call the Init Method from the Library Class, Display_Tapped which handles when the ItemsControl has been Tapped and New_Click which will call the New Method of the Library Class.

Step 13

That completes the Universal Windows Platform Application so Save the Project then in Visual Studio select the Local Machine to run the Application

vs2017-local-machine

Step 14

Once the Application has started running you can tap on the appropriate Square to place either a White or Black Counter on the on the Board to play Reversi until the Game is Over and the Player with the highest Score wins!

uwp-ran-reversi

Step 15

To Exit the Application select the Close button in the top right of the Application

vs2017-close

Reversi is based upon Reversi8 example by Colin Eberhardt and shows how to create a simple two-player Reversi Game including fully represented game logic, unchequered board and counters

Creative Commons License

Universal Windows Platform – Lucky Chess

Lucky Chess demonstrates how to create a game where you can play a simple game of Chess for two players.

Step 1

If not already, follow Setup and Start on how to Install and get Started with Visual Studio 2017 or in Windows 10 choose Start, and then from the Start Menu find and select Visual Studio 2017.

vs2017

Step 2

Once Visual Studio Community 2017 has started, from the Menu choose File, then New then Project…

vs2017-file-new-project

Step 3

From New Project choose Visual C# from Installed, Templates then choose Blank App (Universal Windows) and then type in the Name as LuckyChess and select a Location and then select Ok to create the Project
vs2017-new-project-lucky-chess

Step 4

Then in New Universal Windows Project you need to select the Target Version this should be at least the Windows 10, version 1803 (10.0; Build 17134) which is the April 2018 Update and the Minimum Version to be the same.

vs2017-target-platform

The Target Version will control what features your application can use in Windows 10 so by picking the most recent version you’ll be able to take advantage of those features. To make sure you always have the most recent version, in Visual Studio 2017 select Tools Extensions and Updates… then and then see if there are any Updates

Step 5

Once done select from the Menu, Project, then Add New Item…

vs2017-project-add-new-item

Step 6

From the Add New Item window select Visual C#, then Code from Installed then select Code File from the list, then type in the Name as Library.cs before selecting Add to add the file to the Project

vs2017-add-new-item-lucky-chess

Step 7

Once in the Code View for Library.cs the following should be entered:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Markup;

namespace LuckyChess
{
    public enum ChessBackground
    {
        Light, Dark
    }

    public enum PieceSet
    {
        White, Black
    }

    public enum PieceType
    {
        Pawn, Knight, Bishop, Rook, Queen, King
    }

    public class BindableBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected bool SetProperty<T>(ref T field, T value,
        [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            OnPropertyChanged(propertyName);
            return true;
        }

        public void OnPropertyChanged(string name)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }

    public class ChessCoordinate
    {
        private static string[] chess_ranks = { "8", "7", "6", "5", "4", "3", "2", "1" };
        private static string[] chess_files = { "A", "B", "C", "D", "E", "F", "G", "H" };

        public int Id { get; set; }
        public int Row { get; set; }
        public int Column { get; set; }

        public ChessBackground Background { get; set; }
        public string Notation { get; set; }

        public ChessCoordinate(int id)
        {
            Id = id;
            Row = Id / 8;
            Column = Id % 8;
            Background = (Row + Column) % 2 == 0 ? ChessBackground.Light : ChessBackground.Dark;
            Notation = chess_files[Column] + chess_ranks[Row];
        }
    }

    public class ChessPiece : BindableBase
    {
        private PieceType _type;
        private PieceSet _set;

        public ChessPiece(PieceType type, PieceSet set)
        {
            _type = type;
            _set = set;
        }

        public PieceType Type
        {
            get { return _type; }
            set { SetProperty(ref _type, value); }
        }

        public PieceSet Set
        {
            get { return _set; }
            set { SetProperty(ref _set, value); }
        }
    }

    public class ChessSquare : BindableBase
    {
        private int _id;
        private ChessPiece _piece;
        private ChessCoordinate _coordinate;
        private bool _isSelected;

        public int Id
        {
            get { return _id; }
            set { SetProperty(ref _id, value); }
        }

        public ChessPiece Piece
        {
            get { return _piece; }
            set { SetProperty(ref _piece, value); }
        }

        public ChessCoordinate Coordinate
        {
            get { return _coordinate; }
            set { SetProperty(ref _coordinate, value); }
        }

        public bool IsSelected
        {
            get { return _isSelected; }
            set { SetProperty(ref _isSelected, value); }
        }
    }

    public class ChessPosition : List<ChessPiece>
    {
        public ChessPosition() : base(new ChessPiece[64]) { }

        public ChessPosition(string position) : this()
        {
            int i = 0;
            foreach (char item in position)
            {
                switch (item)
                {
                    case 'p': this[i++] = new ChessPiece(PieceType.Pawn, PieceSet.Black); break;
                    case 'n': this[i++] = new ChessPiece(PieceType.Knight, PieceSet.Black); break;
                    case 'b': this[i++] = new ChessPiece(PieceType.Bishop, PieceSet.Black); break;
                    case 'r': this[i++] = new ChessPiece(PieceType.Rook, PieceSet.Black); break;
                    case 'q': this[i++] = new ChessPiece(PieceType.Queen, PieceSet.Black); break;
                    case 'k': this[i++] = new ChessPiece(PieceType.King, PieceSet.Black); break;
                    case 'P': this[i++] = new ChessPiece(PieceType.Pawn, PieceSet.White); break;
                    case 'N': this[i++] = new ChessPiece(PieceType.Knight, PieceSet.White); break;
                    case 'B': this[i++] = new ChessPiece(PieceType.Bishop, PieceSet.White); break;
                    case 'R': this[i++] = new ChessPiece(PieceType.Rook, PieceSet.White); break;
                    case 'Q': this[i++] = new ChessPiece(PieceType.Queen, PieceSet.White); break;
                    case 'K': this[i++] = new ChessPiece(PieceType.King, PieceSet.White); break;
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8': i += int.Parse(item.ToString()); break;
                    case '/': if (i % 8 != 0) throw new ArgumentException("Invalid FEN"); break;
                    default: throw new ArgumentException($"Invalid FEN Character: '{item}'");
                }
            }
        }
    }

    public class ChessBoard
    {
        public ChessSquare[] ChessSquares { get; set; } = new ChessSquare[64];

        public ChessBoard(string fen)
        {
            ChessPosition position = new ChessPosition(fen);
            for (int i = 0; i < position.Count; i++)
            {
                ChessSquares[i] = new ChessSquare
                {
                    Id = i,
                    Piece = position[i],
                    Coordinate = new ChessCoordinate(i)
                };
            }
        }
    }

    public class ChessSquareStyleSelector : StyleSelector
    {
        public Style Light { get; set; }
        public Style Dark { get; set; }

        protected override Style SelectStyleCore(object item, DependencyObject container)
        {
            if (item is ChessSquare square)
            {
                int row = square.Id / 8;
                int col = square.Id % 8;
                return (row + col) % 2 == 0 ? Light : Dark;
            }
            return base.SelectStyleCore(item, container);
        }
    }

    public class ChessPieceToGlyphConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value is ChessPiece piece)
            {
                if (piece.Set == PieceSet.White)
                {
                    switch (piece.Type)
                    {
                        case PieceType.King: return "\u2654";
                        case PieceType.Queen: return "\u2655";
                        case PieceType.Rook: return "\u2656";
                        case PieceType.Bishop: return "\u2657";
                        case PieceType.Knight: return "\u2658";
                        case PieceType.Pawn: return "\u2659";
                    }
                }
                else
                {
                    switch (piece.Type)
                    {
                        case PieceType.King: return "\u265A";
                        case PieceType.Queen: return "\u265B";
                        case PieceType.Rook: return "\u265C";
                        case PieceType.Bishop: return "\u265D";
                        case PieceType.Knight: return "\u265E";
                        case PieceType.Pawn: return "\u265F";
                    }
                }
            }
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }

    public class BoolToVisibilityConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            bool isVisible = (bool)value;
            return isVisible ? Visibility.Visible : Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }

    public class Binder
    {
        public static readonly DependencyProperty GridColumnBindingPathProperty =
        DependencyProperty.RegisterAttached("GridColumnBindingPath", typeof(string), typeof(Binder),
        new PropertyMetadata(null, GridBindingPathPropertyChanged));

        public static readonly DependencyProperty GridRowBindingPathProperty =
        DependencyProperty.RegisterAttached("GridRowBindingPath", typeof(string), typeof(Binder),
        new PropertyMetadata(null, GridBindingPathPropertyChanged));

        public static string GetGridColumnBindingPath(DependencyObject obj)
        {
            return (string)obj.GetValue(GridColumnBindingPathProperty);
        }

        public static void SetGridColumnBindingPath(DependencyObject obj, string value)
        {
            obj.SetValue(GridColumnBindingPathProperty, value);
        }

        public static string GetGridRowBindingPath(DependencyObject obj)
        {
            return (string)obj.GetValue(GridRowBindingPathProperty);
        }

        public static void SetGridRowBindingPath(DependencyObject obj, string value)
        {
            obj.SetValue(GridRowBindingPathProperty, value);
        }

        private static void GridBindingPathPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue is string path)
            {
                DependencyProperty property = null;
                if (e.Property == GridColumnBindingPathProperty)
                    property = Grid.ColumnProperty;
                else if (e.Property == GridRowBindingPathProperty)
                    property = Grid.RowProperty;

                BindingOperations.SetBinding(obj, property,
                new Binding { Path = new PropertyPath(path) });
            }
        }
    }

    public class Library
    {
        private const string fen_start = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";

        ChessSquare _square = null;

        public ChessBoard Board { get; set; } = new ChessBoard(fen_start);

        private ItemsPanelTemplate Layout()
        {
            StringBuilder columns = new StringBuilder();
            StringBuilder rows = new StringBuilder();
            for (int i = 0; i < 8; i++)
            {
                columns.Append("<ColumnDefinition Width=\"*\"/>");
                rows.Append("<RowDefinition Height=\"*\"/>");
            }
            return (ItemsPanelTemplate)
            XamlReader.Load($@"<ItemsPanelTemplate 
            xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' 
            xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
                <Grid>
                    <Grid.ColumnDefinitions>{columns}</Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>{rows}</Grid.RowDefinitions>
                </Grid>
            </ItemsPanelTemplate>");
        }

        public void Init(ref ItemsControl display)
        {
            display.ItemsPanel = Layout();
            display.ItemsSource = Board.ChessSquares;
        }

        public void Tapped(ItemsControl display, ContentPresenter container)
        {
            ChessSquare square = (ChessSquare)display.ItemFromContainer(container);
            if (_square == null &&
                square.Piece != null)
            {
                square.IsSelected = true;
                _square = square;
            }
            else if (square == _square)
            {
                square.IsSelected = false;
                _square = null;
            }
            else if (_square != null &&
            _square.Piece != null &&
            _square.Piece.Set != square?.Piece?.Set)
            {
                square.Piece = _square.Piece;
                _square.IsSelected = false;
                _square.Piece = null;
                _square = null;
            }
        }

        public void New(ref ItemsControl display)
        {
            Board = new ChessBoard(fen_start);
            display.ItemsSource = Board.ChessSquares;
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality. There are enum values for the Game. There is a BindableBase Class which implements INotifyPropertyChanged there are SetProperty and OnPropertyChanged Methods.

There is a ChessCoordinate Class which represents the possible coordinates for Chess Pieces on a Chess Board such as “A6” there are int Properties for Id, Row and Column. There’s a ChessPiece Class which has Properties for PieceType and PieceSet and Implements BindableBase. The ChessSquare which Implements BindableBase congtains properties for ChessPiece and ChessCoordinate along with an int Property for an identifier and bool Property for selection.

The ChessPosition Class represents a List of ChessPiece and takes a string Parameter of a Chess Layout in Forsyth–Edwards Notation which is what the foreach and switch parts perform. There is a ChessBoard Class which represents a set of ChessSquare for a given Forsyth–Edwards Notation layout.

ChessSquareStyleSelector is used to select the appropriate Style for the squares of a Chess Board and will set the relevant ChessSquare Properties. The ChessPieceToGlyphConverter is used when displaying a ChessPiece Class and to use the relevant PieceSet and PieceType values to return the correct Unicode Character sequence to represent the given Chess Piece and BoolToVisibilityConverter is an IValueConverter to convert a bool to Visibility. The Binder Class is used to help with the Binding of DependencyProperty for the GridRow and GridColumn of the Layout

The Library Class contains a const string which is the Forsyth–Edwards Notation for the start of standard Chess game and there is a ChessBoard Property for the Chess Board itself, the Layout Method returns an ItemsPanelTemplate for the look-and-feel of the Game, the Init Method will initialse the Game and call the Layout Method. The Tapped Event Handler is used on a ChessSquare and will be used to set the “IsSelected” Property and remove any when the relevant conditions are met within the if … else Statements. The New Method is used to start the Game.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-lucky-chess

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

The Design View will be displayed along with the XAML View and in this above the Grid element, enter the following XAML:

<Page.Resources>
	<Style TargetType="ContentPresenter" x:Key="SquareStyle">
		<Setter Property="local:Binder.GridRowBindingPath" Value="Coordinate.Row"/>
		<Setter Property="local:Binder.GridColumnBindingPath" Value="Coordinate.Column"/>
	</Style>
	<Style TargetType="ContentPresenter" BasedOn="{StaticResource SquareStyle}" x:Key="DarkStyle">
		<Setter Property="Background" Value="Peru"/>
		<Setter Property="Foreground" Value="Wheat"/>
	</Style>
	<Style TargetType="ContentPresenter" BasedOn="{StaticResource SquareStyle}" x:Key="LightStyle">
		<Setter Property="Background" Value="Wheat"/>
		<Setter Property="Foreground" Value="Peru"/>
	</Style>
	<local:ChessSquareStyleSelector x:Key="ChessSquareStyleSelector" 
	Dark="{StaticResource DarkStyle}" Light="{StaticResource LightStyle}"/>
	<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
	<local:ChessPieceToGlyphConverter x:Key="ChessPieceToGlyphConverter"/>
	<DataTemplate x:Key="ChessTemplate" x:DataType="local:ChessSquare">
		<Grid IsHitTestVisible="False">
			<Grid.RowDefinitions>
				<RowDefinition Height="*"/>
				<RowDefinition Height="Auto"/>
			</Grid.RowDefinitions>
			<Ellipse Grid.Row="0" Grid.RowSpan="2" Fill="Gray" Opacity="0.75" 
			HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
			Visibility="{x:Bind IsSelected, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
			</Ellipse>
			<Viewbox Grid.Row="0" Grid.RowSpan="2">
				<TextBlock IsColorFontEnabled="True" FontFamily="Segoe UI Emoji"
				Text="{x:Bind Piece, Mode=OneWay, Converter={StaticResource ChessPieceToGlyphConverter}}"/>
			</Viewbox>
			<TextBlock Grid.Row="1" Margin="1" FontSize="4" 
			Text="{x:Bind Coordinate.Notation}"/>
		</Grid>
	</DataTemplate>
</Page.Resources>

Then while still in the XAML View between the Grid and /Grid elements, enter the following XAML:

<Viewbox>
	<ItemsControl Name="Display" Margin="50" Width="400" Height="400" 
		ItemContainerStyleSelector="{StaticResource ChessSquareStyleSelector}" 
		ItemTemplate="{StaticResource ChessTemplate}" Tapped="Display_Tapped">
	</ItemsControl>
</Viewbox>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="Page2" Label="New" Click="New_Click"/>
</CommandBar>

Within the Page.Resources block of XAML above the Grid Element contains Style definitions including a DarkStyle and LightStyle. There is also the ChessSquareStyleSelector, BoolToVisibilityConverter and ChessPieceToGlyphConverter. There is a DataTemplate which is for a ChessSquare which contains a Grid with elements to show the Notation value in a TextBlock including another within a Viewbox for the ChessPiece itself and an Ellipse which will be displayed when the “IsSelected” Property is set to true and hidden when this is false.

Within the main Grid Element, the first block of XAML is a Viewbox Control which contains a ItemsControl Control. The second block of XAML is a CommandBar with AppBarButton for New which calls New_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

Once in the Code View, below the end of public MainPage() { … } the following Code should be entered:

Library library = new Library();

protected override void OnNavigatedTo(NavigationEventArgs e)
{
	library.Init(ref Display);
}

private void Display_Tapped(object sender, TappedRoutedEventArgs e)
{
	library.Tapped(sender as ItemsControl, e.OriginalSource as ContentPresenter);
}

private void New_Click(object sender, RoutedEventArgs e)
{
	library.New(ref Display);
}

There is an OnNavigatedTo Event Handler which will call the Init Method from the Library Class, Display_Tapped which handles when the ItemsControl has been Tapped and New_Click which will call the New Method of the Library Class.

Step 13

That completes the Universal Windows Platform Application so Save the Project then in Visual Studio select the Local Machine to run the Application

vs2017-local-machine

Step 14

Once the Application has started running you can tap on the appropriate Chess Piece and either move it to an Empty square or use to remove an opponent’s Piece from the Board.

uwp-ran-lucky-chess

Step 15

To Exit the Application select the Close button in the top right of the Application

vs2017-close

Lucky Chess is based upon the UwpChess and UniversalChess examples by Chuck Ries and shows how to create a simple two-player Chess Board with a full set of pieces, you could extend this to show more pre-defined layouts or as an advanced exercise have it enforce the rules of Chess!

Creative Commons License

Universal Windows Platform – Lucky Wheel

Lucky Wheel demonstrates how to create a game where you can Spin a Wheel to see how high a score you can get before you Lose.

Step 1

If not already, follow Setup and Start on how to Install and get Started with Visual Studio 2017 or in Windows 10 choose Start, and then from the Start Menu find and select Visual Studio 2017.

vs2017

Step 2

Once Visual Studio Community 2017 has started, from the Menu choose File, then New then Project…

vs2017-file-new-project

Step 3

From New Project choose Visual C# from Installed, Templates then choose Blank App (Universal Windows) and then type in the Name as LuckyWheel and select a Location and then select Ok to create the Project
vs2017-new-project-lucky-wheel

Step 4

Then in New Universal Windows Project you need to select the Target Version this should be at least the Windows 10, version 1803 (10.0; Build 17134) which is the April 2018 Update and the Minimum Version to be the same.

vs2017-target-platform

The Target Version will control what features your application can use in Windows 10 so by picking the most recent version you’ll be able to take advantage of those features. To make sure you always have the most recent version, in Visual Studio 2017 select Tools Extensions and Updates… then and then see if there are any Updates

Step 5

Once done select from the Menu, Project, then Add New Item…

vs2017-project-add-new-item

Step 6

From the Add New Item window select Visual C#, then Code from Installed then select Code File from the list, then type in the Name as Library.cs before selecting Add to add the file to the Project

vs2017-add-new-item-lucky-wheel

Step 7

Once in the Code View for Library.cs the following should be entered:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Popups;
using Windows.UI.Text;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Documents;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Animation;
using Windows.UI.Xaml.Shapes;

namespace LuckyWheel
{
    public class WheelWedge
    {
        public string Value { get; set; }
        public Color Colour { get; set; }
        public double Sector { get; set; }

        public WheelWedge(string value, Color colour)
        {
            Value = value;
            Colour = colour;
        }
    }

    public class WheelBoard
    {
        private const double axis = 180.0;
        private const double circle = 360;
        private const double radius = 200;
        private const double diameter = 400;
        private readonly List<WheelWedge> wedges = new List<WheelWedge>()
        {
            new WheelWedge("1000", Colors.WhiteSmoke),
            new WheelWedge("600", Colors.LightGreen),
            new WheelWedge("500", Colors.Yellow),
            new WheelWedge("300", Colors.Red),
            new WheelWedge("500", Colors.Azure),
            new WheelWedge("800", Colors.Orange),
            new WheelWedge("550", Colors.Violet),
            new WheelWedge("400", Colors.Yellow),
            new WheelWedge("300", Colors.Pink),
            new WheelWedge("900", Colors.Red),
            new WheelWedge("500", Colors.Azure),
            new WheelWedge("300", Colors.LightGreen),
            new WheelWedge("900", Colors.Pink),
            new WheelWedge("LOSE", Colors.Black),
            new WheelWedge("600", Colors.Violet),
            new WheelWedge("400", Colors.Yellow),
            new WheelWedge("300", Colors.Azure),
            new WheelWedge("LOSE", Colors.Black),
            new WheelWedge("800", Colors.Red),
            new WheelWedge("350", Colors.Violet),
            new WheelWedge("450", Colors.Pink),
            new WheelWedge("700", Colors.LightGreen),
            new WheelWedge("300", Colors.Orange),
            new WheelWedge("600", Colors.Violet)
        };
        private readonly Random random = new Random((int)DateTime.Now.Ticks);

        private Storyboard _storyboard;
        private double _selected;
        private double _sector;
        private double _position;
        private Canvas _wheel;
        private int _total;
        private bool _lost;
        private bool _spin;

        public delegate void DialogHandler(string content);
        public event DialogHandler Dialog;

        private int Choose()
        {
            return random.Next(1, (int)circle);
        }

        private Path GetWedgePath(Color fill, ref double angle, double sector)
        {
            double DegreeToRadian(double degree)
            {
                return Math.PI * degree / axis;
            }
            Path path = new Path()
            {
                Fill = new SolidColorBrush(fill),
            };
            PathGeometry geometry = new PathGeometry();
            double x = Math.Cos(angle) * radius + radius;
            double y = Math.Sin(angle) * radius + radius;
            LineSegment lineOne = new LineSegment()
            {
                Point = new Point(x, y)
            };
            angle += DegreeToRadian(sector);
            x = Math.Cos(angle) * radius + radius;
            y = Math.Sin(angle) * radius + radius;
            ArcSegment arc = new ArcSegment()
            {
                RotationAngle = sector,
                Point = new Point(x, y),
                IsLargeArc = sector >= axis,
                Size = new Size(radius, radius),
                SweepDirection = SweepDirection.Clockwise,
            };
            LineSegment lineTwo = new LineSegment()
            {
                Point = new Point(radius, radius)
            };
            PathFigure figure = new PathFigure()
            {
                StartPoint = new Point(radius, radius)
            };
            figure.Segments.Add(lineOne);
            figure.Segments.Add(arc);
            figure.Segments.Add(lineTwo);
            geometry.Figures.Add(figure);
            path.Data = geometry;
            return path;
        }

        private Grid GetWedgeLabel(Color foreground, string value,
        double x, double y, ref double rotate, double sector)
        {
            Grid grid = new Grid()
            {
                Height = radius
            };
            TextBlock textblock = new TextBlock()
            {
                Text = value,
                FontSize = 20,
                Margin = new Thickness(2),
                FontWeight = FontWeights.SemiBold,
                TextAlignment = TextAlignment.Center,
                Foreground = new SolidColorBrush(foreground),
            };
            rotate += sector;
            textblock.Inlines.Clear();
            for (int i = 0; i < value.Length; i++)
            {
                textblock.Inlines.Add(new Run() { Text = value[i] + Environment.NewLine });
            }
            textblock.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
            double width = textblock.DesiredSize.Width;
            double left = y - (width / 2);
            grid.RenderTransform = new RotateTransform()
            {
                Angle = rotate,
                CenterY = radius,
                CenterX = width / 2
            };
            grid.Children.Add(textblock);
            Canvas.SetZIndex(grid, 99);
            Canvas.SetLeft(grid, left);
            Canvas.SetTop(grid, x);
            return grid;
        }

        private Ellipse GetCircle(Color fill, Color stroke,
            double x, double y, double dimension)
        {
            Ellipse ellipse = new Ellipse()
            {
                Width = dimension,
                Height = dimension,
                StrokeThickness = 5,
                Fill = new SolidColorBrush(fill),
                Stroke = new SolidColorBrush(stroke)
            };
            Canvas.SetLeft(ellipse, x - (dimension / 2));
            Canvas.SetTop(ellipse, y - (dimension / 2));
            return ellipse;
        }

        private Polygon GetMarker(Color fill, Color stroke, double dimension)
        {
            Polygon polygon = new Polygon
            {
                Width = dimension,
                Height = dimension,
                StrokeThickness = 5,
                Stretch = Stretch.Uniform,
                Fill = new SolidColorBrush(fill),
                StrokeLineJoin = PenLineJoin.Round,
                Stroke = new SolidColorBrush(stroke),
                VerticalAlignment = VerticalAlignment.Center,
                HorizontalAlignment = HorizontalAlignment.Center,
                Points = { new Point(0, 0), new Point(100, 0), new Point(50, 50) }
            };
            return polygon;
        }

        private WheelWedge GetWedge(double selected)
        {
            double value = circle - selected;
            return wedges.First(w => value >= w.Sector && value < (w.Sector + _sector));
        }

        private void Rotation_Completed(object sender, object e)
        {
            _spin = false;
            WheelWedge wedge = GetWedge(_selected);
            if (int.TryParse(wedge.Value, out int result) && !_lost)
            {
                Dialog?.Invoke($"You Won {result}, Total is {_total}");
                _total += result;
            }
            else
            {
                Dialog?.Invoke($"You Lose, Total was {_total}!");
                _lost = true;
            }
        }

        private void Rotation(ref Canvas wheel, double angle)
        {
            DoubleAnimation animation = new DoubleAnimation()
            {
                From = _position,
                To = (circle * 2) + angle,
                EasingFunction = new QuadraticEase(),
                RepeatBehavior = new RepeatBehavior(1),
                Duration = new Duration(TimeSpan.FromSeconds(5)),
            };
            Storyboard.SetTargetProperty(animation, "(Canvas.RenderTransform).(RotateTransform.Angle)");
            Storyboard.SetTarget(animation, wheel);
            _storyboard = new Storyboard();
            _storyboard.Completed += Rotation_Completed;
            _storyboard.Children.Add(animation);
            _storyboard.Begin();
        }

        private void Reset()
        {
            _total = 0;
            _spin = false;
            _lost = false;
            _selected = 0;
        }

        private void Wheel_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
        {
            if (!_spin)
            {
                _spin = true;
                if (_lost)
                {
                    Dialog?.Invoke($"You Lost, Total was {_total}, Starting New Game");
                    Reset();
                }
                if (!_lost)
                {
                    _position = _selected;
                    _selected = Choose();
                    Rotation(ref _wheel, _selected);
                }
            }
        }

        private Canvas GetWheel()
        {
            double current = 0;
            double total = wedges.Count;
            double angle = -(Math.PI / 2);
            _sector = (360 / total);
            _position = -(_sector / 2);
            double rotate = -(_sector / 2);
            Canvas canvas = new Canvas()
            {
                Width = diameter,
                Height = diameter,
                VerticalAlignment = VerticalAlignment.Center,
                HorizontalAlignment = HorizontalAlignment.Center,
                RenderTransform = new RotateTransform()
                {
                    Angle = rotate,
                    CenterX = radius,
                    CenterY = radius
                }
            };
            canvas.Children.Add(GetCircle(Colors.Transparent, Colors.Gold, radius, radius, diameter + 10));
            foreach (WheelWedge wedge in wedges)
            {
                wedge.Sector = current;
                Path path = GetWedgePath(wedge.Colour, ref angle, _sector);
                Grid label = GetWedgeLabel(wedge.Colour == Colors.Black ? Colors.White : Colors.Black,
                wedge.Value, 0, radius, ref rotate, _sector);
                canvas.Children.Add(path);
                canvas.Children.Add(label);
                current += _sector;
            }
            canvas.Children.Add(GetCircle(Colors.Green, Colors.Gold, radius, radius, 120));
            canvas.Tapped += Wheel_Tapped;
            return canvas;
        }

        public void Layout(ref Grid grid)
        {
            grid.Children.Clear();
            _wheel = GetWheel();
            StackPanel panel = new StackPanel();
            panel.Children.Add(GetMarker(Colors.WhiteSmoke, Colors.Black, 30));
            panel.Children.Add(_wheel);
            grid.Children.Add(panel);
        }

        public void New()
        {
            Reset();
        }
    }

    public class Library
    {
        private const string app_title = "Lucky Wheel";

        private IAsyncOperation<IUICommand> _dialogCommand = null;
        private WheelBoard _board = new WheelBoard();

        private async Task<bool> ShowDialogAsync(string content, string title = app_title)
        {
            try
            {
                if (_dialogCommand != null)
                {
                    _dialogCommand.Cancel();
                    _dialogCommand = null;
                }
                _dialogCommand = new MessageDialog(content, title).ShowAsync();
                await _dialogCommand;
                return true;
            }
            catch (TaskCanceledException)
            {
                return false;
            }
        }

        private async void Board_Dialog(string content)
        {
            await ShowDialogAsync(content);
        }

        public void Init(ref Grid grid)
        {
            _board.Layout(ref grid);
            _board.Dialog += Board_Dialog;
        }

        public void New(ref Grid grid)
        {
            _board.Layout(ref grid);
            _board.New();
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality. There is a WheelWedge Class which has string, Color and double Properties.

There is a WheelBoard Class which contains the private const and private readonly for the Game including Random for selecting randomised numbers. There are also some private Members and an event for Dialog. There are also some private Methods including Choose for selecting randomised numbers between 1 and 360. The GetWedgePath Method is used to create the necessary elements to make up the layout of a Wedge of the Wheel created with a combination of LineSegment and ArcSegment Controls.

Also in the WheelBoard Class is the GetWedgeLabel which is used to return a TextBlock with the required appearance with a RotateTransform. The GetCircle Method is used to return an Ellipse with a given Color for the Stroke and Fill. The GetMarker Method is used to return a Polygon to represent a Marker for use with the Wheel

GetWedge is used to return a WheelWedge with a value between the Sector values in all the Wedges, Rotation_Completed is an Event Handler triggered when the Wheel has finished rotating and will indicate if have Won or Lost. There is a Rotation Method which will perform the rotation of the Wheel with a DoubleAnimation in a Storyboard with a QuadraticEase to make it behave more like a real “Lucky Wheel”. The Reset Method is used to reset Game values, it is also called by the New Method and there is a Wheel_Tapped Event Handler which will be triggered when the Wheel is tapped and GetWheel is used to create the look-and-feel of the Wheel itself made up of various WheelWedge and other Elements and is called from Layout

The Library Class contains a ShowDialogAsync Method to display a MessageDialog and a Board_Dialog Event Handler which will use that Method. The Init Method will initialise the look-and-feel of all the visual elements of the Game and New will start a Game.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-lucky-wheel

Step 9

From the Menu choose View and then Designer

vs2017-view-designer

Step 10

The Design View will be displayed along with the XAML View and in this between the Grid and /Grid elements, enter the following XAML:

<Viewbox>
	<Grid Margin="50" Name="Display" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Viewbox>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="Page2" Label="New" Click="New_Click"/>
</CommandBar>

The first block of XAML is a Viewbox Control which contains a Grid Control. The second block of XAML is a CommandBar with AppBarButton for New which calls New_Click.

Step 11

From the Menu choose View and then Code

vs2017-view-code

Step 12

Once in the Code View, below the end of public MainPage() { … } the following Code should be entered:

Library library = new Library();

protected override void OnNavigatedTo(NavigationEventArgs e)
{
	library.Init(ref Display);
}

private void New_Click(object sender, RoutedEventArgs e)
{
	library.New(ref Display);
}

There is an OnNavigatedTo Event Handler which will call the Init Method from the Library Class and New_Click which will call the New Method of the Library Class.

Step 13

That completes the Universal Windows Platform Application so Save the Project then in Visual Studio select the Local Machine to run the Application

vs2017-local-machine

Step 14

Once the Application has started running you can tap on the Wheel and it will Rotate once its stops on a Value that is the amount you will win, if it lands on LOSE the Game will be over.

uwp-ran-lucky-wheel

Step 15

To Exit the Application select the Close button in the top right of the Application

vs2017-close

This example shows how to create a Fortune Wheel Game which shows how to create the shapes of the Wedge of the Wheel, just like it’s real-life counterpart, it makes a fun game to create and play or just as the basis of a more complex Game.

Creative Commons License