Universal Windows Platform – Pomodoro App

Pomodoro App shows how to create a simple application that can help with time management using a timer to help break down tasks with a 25 Minute Task Timer and then have a Short 5 Minute or Long 15 Minute Break inbetween.

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 PomodoroApp and select a Location and then select Ok to create the Project
vs2017-new-project-pomodoro-app

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-pomodoro-app

Step 7

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

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

namespace PomodoroApp
{
    public enum PomodoroType
    {
        [Description("\U0001F345")]
        TaskTimer = 25,
        [Description("\U00002615")]
        ShortBreak = 1,
        [Description("\U0001F34F")]
        LongBreak = 15
    }

    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 PomodoroItem : BindableBase
    {
        private PomodoroType _type;
        private Color _light;
        private Color _dark;

        private string GetGlyph(PomodoroType type)
        {
            FieldInfo info = type.GetType().GetField(type.ToString());
            DescriptionAttribute[] attr =
            (DescriptionAttribute[])info.GetCustomAttributes
            (typeof(DescriptionAttribute), false);
            return attr[0].Description;
        }

        private string GetId(PomodoroType type)
        {
            return Enum.GetName(typeof(PomodoroType), type);
        }

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

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

        public Color Dark
        {
            get { return _dark; }
            set { SetProperty(ref _dark, value); }
        }

        public Color Light
        {
            get { return _light; }
            set { SetProperty(ref _light, value); }
        }

        public string Id => GetId(_type);

        public string Glyph => GetGlyph(_type);

        public string Name => string.Join(" ", GetName(_type));

        public TimeSpan TimeSpan => TimeSpan.FromMinutes((double)_type);

        public PomodoroItem(PomodoroType type, Color dark, Color light)
        {
            Type = type;
            Dark = dark;
            Light = light;
        }
    }

    public class PomodoroSetup : BindableBase
    {
        private bool _started;
        private string _display;
        private DateTime _start;
        private DateTime _finish;
        private TimeSpan _current;
        private PomodoroItem _item;
        private List<PomodoroItem> _items;
        private DispatcherTimer _timer;
        private ToastNotifier _notifier = ToastNotificationManager.CreateToastNotifier();

        private ScheduledToastNotification GetToast()
        {
            return _notifier.GetScheduledToastNotifications().FirstOrDefault();
        }

        private void ClearToast()
        {
            foreach (ScheduledToastNotification toast in _notifier.GetScheduledToastNotifications())
            {
                _notifier.RemoveFromSchedule(toast);
            }
        }

        private void AddToast(string id, string name, string glyph, DateTime start, DateTime finish)
        {
            XmlDocument xml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02);
            xml.GetElementsByTagName("text")[0].InnerText = $"{glyph} {name}";
            xml.GetElementsByTagName("text")[1].InnerText = $"Start {start:HH:mm} Finish {finish:HH:mm}";
            ScheduledToastNotification toast = new ScheduledToastNotification(xml, finish) { Id = id };
            _notifier.AddToSchedule(toast);
        }

        private string GetDisplay(TimeSpan timeSpan) => timeSpan.ToString(@"mm\:ss");

        private void Start()
        {
            _started = true;
            if (_timer == null)
            {
                _timer = new DispatcherTimer()
                {
                    Interval = TimeSpan.FromMilliseconds(250)
                };
                _timer.Tick += (object s, object args) =>
                {
                    TimeSpan difference = _start - DateTime.UtcNow;
                    string display = GetDisplay(_current + difference);
                    if (_started && display != GetDisplay(TimeSpan.Zero))
                    {
                        Display = display;
                    }
                    else
                    {
                        _current = TimeSpan.Zero;
                        Display = GetDisplay(_current);
                        _timer.Stop();
                        _started = false;
                    }
                };
            }
            _timer.Start();
        }

        private void Stop()
        {
            if (_timer != null) _timer.Stop();
           _started = false;
            Select(_item);
        }

        public PomodoroItem Item
        {
            get { return _item; }
            set { SetProperty(ref _item, value); }
        }

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

        public string Display
        {
            get { return _display; }
            set { SetProperty(ref _display, value); }
        }

        public bool Started
        {
            get { return _started; }
            set { SetProperty(ref _started, value); }
        }

        public void Select(PomodoroItem item)
        {
            Item = item;
            _current = Item.TimeSpan;
            Display = GetDisplay(_current);
        }

        public void Init()
        {
            if (Items == null)
            {
                Items = new List<PomodoroItem>
                {
                    new PomodoroItem(PomodoroType.TaskTimer, 
                    Color.FromArgb(255, 240, 58, 23), 
                    Color.FromArgb(255, 239, 105, 80)),
                    new PomodoroItem(PomodoroType.ShortBreak,
                    Color.FromArgb(255, 131, 190, 236), 
                    Color.FromArgb(255, 179, 219, 212)),
                    new PomodoroItem(PomodoroType.LongBreak,
                    Color.FromArgb(255, 186, 216, 10), 
                    Color.FromArgb(255, 228, 245, 119)),
                };
            }
            ScheduledToastNotification toast = GetToast();
            PomodoroItem item = Items[0];
            if (toast != null)
            {
                item = Items.FirstOrDefault(f => f.Id == toast.Id);
                _start = toast.DeliveryTime.UtcDateTime - item.TimeSpan;
                _finish = toast.DeliveryTime.UtcDateTime;
                Start();
            }
            Select(item);
        }

        public void Toggle()
        {
            _start = DateTime.UtcNow;
            _finish = _start.Add(TimeSpan.FromMinutes((double)Item.Type));
            if(_started)
            {
                ClearToast();
                Stop();
            }
            else
            {
                AddToast(_item.Id, _item.Name, _item.Glyph, _start, _finish);
                _current = Item.TimeSpan;
                Start();
            }
        }
    }

    public class Library
    {
        private const string app_title = "Pomodoro App";

        private IAsyncOperation<IUICommand> _dialogCommand;
        private PomodoroSetup _setup = new PomodoroSetup();

        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 Button_Click(object sender, RoutedEventArgs e)
        {
            AppBarButton button = (AppBarButton)sender;
            if(_setup.Started)
            {
                await ShowDialogAsync($"You will need to Stop {_setup.Item.Name} Timer to Switch");
            }
            else
            {
                _setup.Select((PomodoroItem)button.Tag);
            }
        }

        public void Init(ref CommandBar command, ref Grid display)
        {
            _setup.Init();
            if (command.PrimaryCommands.Count == 1)
            {
                foreach (PomodoroItem item in _setup.Items)
                {
                    AppBarButton button = new AppBarButton()
                    {
                        Tag = item,
                        Content = item.Name,
                        Icon = new FontIcon()
                        {
                            Glyph = item.Glyph,
                            Margin = new Thickness(0, -5, 0, 0),
                            FontFamily = new FontFamily("Segoe UI Emoji")
                        }
                    };
                    button.Click += Button_Click;
                    command.PrimaryCommands.Add(button);
                }
            }
            display.DataContext = _setup;
        }

        public void Toggle()
        {
            _setup.Toggle();
        }
    }      
}

In the Code File for Library there are using statements to include the necessary functionality. There is an enum for PomodoroApp which has values for use within the Application and also includes a Description Attribute. There is a BindableBase Class which implements INotifyPropertyChanged there are SetProperty and OnPropertyChanged Methods.

The PomodoroItem Class Inherits from BindableBase and has members for PomodoroType and Color. The GetGlyph Method is used to read the DescriptionAttribute value, GetId Method is used to get the Name of the PomodoroType Value the and GetName Method is used to return a friendly Name for the PomodoroType. There are Properties for the PomodoroType, for Color, and some string Properties and for TimeSpan.

The PomodoroSetup Class

also Inherits from BindableBase and has various Members including ones for DateTime, PomodoroItem, DispatcherTimer and ToastNotifier. There is a GetToast Method which will return the first ScheduledToastNotification or default, as null. The ClearToast Method is used to remove all ScheduledToastNotification from the ToastNotificationManager. The AddToast Method is used to create a ScheduledToastNotification with a given ToastTemplateType. The GetDisplay Method uses a Lambda to return a Formatted TimeSpan.

Also in the PomodoroSetup Class there is a Start Method which will create a DispatcherTimer which will be used to Display a Current value which involves Calculating a time difference to use as a Countdown. The Stop Method will Stop the DispatcherTimer and call a Select Method. The Select Method is used to set the PomodoroItem to use and get the Current TimeSpan for this. The Init Method is used to help create the look-and-feel of the Application and will Populate the List of PomodoroItem with the different PomodoroType Values, it will get any existing ScheduledToastNotification to retrieve an already running Timer or call the Select Method. The Toggle Method is used to either Start or Stop a Timer.

The Library Class has Members for IAsyncOperation of IUICommand for use with the ShowDialogAsync Method which will display a MessageDialog and for PomodoroSetup. There is a Button_Click Event Handler which will call the Select Method to pick a Timer or if one is Started it will display a message with ShowDialogAsync. The Init Method is used to create the Layout of the Application and add additional AppBarButton for each PomodoroItem and there is a Toggle Method which will call the Toggle Method in the PomodoroSetup Class.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-pomodoro-app

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:

<Grid Name="Display" Margin="50" HorizontalAlignment="Center">
	<Grid.RowDefinitions>
		<RowDefinition Height="50*"/>
		<RowDefinition Height="50*"/>
	</Grid.RowDefinitions>
	<Grid Grid.Row="0">
		<Grid.Background>
			<SolidColorBrush Color="{Binding Path=Item.Light, Mode=OneWay}"/>
		</Grid.Background>
		<Viewbox>
			<TextBlock Text="{Binding Path=Item.Glyph, Mode=OneWay}" FontFamily="Segoe UI Emoji"/>
		</Viewbox>
	</Grid>
	<Grid Grid.Row="1">
		<Grid.Background>
			<SolidColorBrush Color="{Binding Path=Item.Dark, Mode=OneWay}"/>
		</Grid.Background>
		<Viewbox>
			<TextBlock Margin="5" Text="{Binding Path=Display, Mode=OneWay}" Foreground="WhiteSmoke"/>
		</Viewbox>
	</Grid>
</Grid>
<CommandBar Name="Command" VerticalAlignment="Bottom">
	<AppBarButton Name="Timer" Icon="Clock" Label="Timer" Click="Timer_Click"/>
</CommandBar>

Within the main Grid Element, the first block of XAML is a Grid Control which has two Rows, in the first Row is a TextBlock which will display the Glyph for a PomodoroItem and is within a Grid which has a Background set to the Light Property and in the second Row is another Grid with another TextBlock set to the Display Property and is within a Grid with its Background set to the Dark Property. The second block of XAML is a CommandBar with AppBarButton for Time which calls Timer_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 Command, ref Display);
}

private void Timer_Click(object sender, RoutedEventArgs e)
{
	library.Toggle();
}

There is an OnNavigatedTo Event Handler which calls the Init Method in the Library Class and Timer_Click which calls the Toggle 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 select the Timer Button to Start or Stop the Task Timer and then do some work or select the Short Break or Long Break to rest – at the end of the time a Toast Notification will appear

uwp-ran-pomodoro-app

Step 15

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

vs2017-close

This example shows how to implement the Pomodoro Technique created by Francesco Cirillo which involves deciding on a Task to perform then setting a Timer to 25 Minutes, then once done have a Short Break of 5 Minutes, then every Four Tasks performed then take a Long Break of 15 Minutes and then repeat the process again. This is a useful working technique and also is a great way on how to use ScheduledToastNotification and other features to create an application like this.

Creative Commons License

Universal Windows Platform – ZuneView App

ZuneView App shows how to create a simple application that can display any Microsoft Zune with the Devices and Colours that were available and Save an Image of the selected Device and Colour.

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 ZuneViewApp and select a Location and then select Ok to create the Project
vs2017-new-project-zuneview-app

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-zuneview-app

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.Runtime.InteropServices.WindowsRuntime;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;

namespace ZuneViewApp
{
    public enum ZuneViewType
    {
        Keel, Draco, Scorpius, Pavo
    }

    public class ZuneViewStyle
    {
        public string Name { get; set; }
        public Color Colour { get; set; }
        public ZuneViewType[] Types { get; set; }

        public ZuneViewStyle(string name, Color colour, ZuneViewType[] types)
        {
            Name = name;
            Types = types;
            Colour = colour;
        }
    }

    public class ZuneViewDevice
    {
        public string Name { get; set; }
        public ZuneViewType Type { get; set; }
        public Brush Fill { get; set; } // Change to Style

        public ZuneViewDevice(ZuneViewType type, Brush fill)
        {
            Type = type;
            Fill = fill;
        }

        public ZuneViewDevice(string name, ZuneViewType type, Brush fill)
        {
            Name = name;
            Type = type;
            Fill = fill;
        }
    }

    public class ZuneViewDeviceTemplateSelector : DataTemplateSelector
    {
        public DataTemplate Keel { get; set; }
        public DataTemplate Draco { get; set; }
        public DataTemplate Scorpius { get; set; }
        public DataTemplate Pavo { get; set; }

        protected override DataTemplate SelectTemplateCore(object value, DependencyObject container)
        {
            if (value is ZuneViewDevice item)
            {
                switch (item.Type)
                {
                    case ZuneViewType.Keel:
                        return Keel;
                    case ZuneViewType.Draco:
                        return Draco;
                    case ZuneViewType.Scorpius:
                        return Scorpius;
                    case ZuneViewType.Pavo:
                        return Pavo;
                }
            }
            return null;
        }
    }

    public class Library
    {
        private const int scale_size = 1000;
        private const double image_dpi = 96.0;
        private const string file_extension = ".png";
        private readonly Color brown = Color.FromArgb(255, 68, 48, 22);
        private readonly Color black = Color.FromArgb(255, 28, 28, 28);
        private readonly Color red = Color.FromArgb(255, 132, 20, 44);
        private readonly Color platinum = Color.FromArgb(255, 220, 220, 220);

        private List<ZuneViewStyle> _styles = new List<ZuneViewStyle>();
        private List<ZuneViewDevice> _devices = new List<ZuneViewDevice>();

        private void AddStyle(string name, Color colour, params ZuneViewType[] types)
        {
            _styles.Add(new ZuneViewStyle(name, colour, types));
        }

        private void SetStyles()
        {
            AddStyle("Brown", brown, ZuneViewType.Keel);
            AddStyle("Halo", Color.FromArgb(255, 70, 90, 90), ZuneViewType.Keel);
            AddStyle("Red", Color.FromArgb(255, 234, 48, 104), ZuneViewType.Keel);
            AddStyle("Pink", Color.FromArgb(255, 228, 182, 194), ZuneViewType.Keel);
            AddStyle("Magenta", Color.FromArgb(255, 230, 62, 100), ZuneViewType.Keel);
            AddStyle("Blue", Color.FromArgb(255, 14, 84, 150), ZuneViewType.Keel);
            AddStyle("Orange", Color.FromArgb(255, 246, 116, 0), ZuneViewType.Keel);
            AddStyle("Black", Color.FromArgb(255, 16, 24, 22), ZuneViewType.Keel);
            AddStyle("White", Color.FromArgb(255, 235, 235, 235),
            ZuneViewType.Keel, ZuneViewType.Draco, ZuneViewType.Scorpius);
            AddStyle("Black", black, ZuneViewType.Draco, ZuneViewType.Scorpius);
            AddStyle("Red", red, ZuneViewType.Draco, ZuneViewType.Scorpius);
            AddStyle("Blue", Color.FromArgb(255, 2, 70, 130),
            ZuneViewType.Draco, ZuneViewType.Scorpius);
            AddStyle("Green", Color.FromArgb(255, 140, 132, 66), ZuneViewType.Scorpius);
            AddStyle("Pink", Color.FromArgb(255, 246, 164, 196), ZuneViewType.Scorpius);
            AddStyle("Citron", Color.FromArgb(255, 210, 198, 6), ZuneViewType.Scorpius);
            AddStyle("Platinum", platinum, ZuneViewType.Pavo);
            AddStyle("Onyx", Color.FromArgb(255, 24, 42, 44), ZuneViewType.Pavo);
            AddStyle("Red", Color.FromArgb(255, 124, 30, 54), ZuneViewType.Pavo);
            AddStyle("Blue", Color.FromArgb(255, 30, 104, 146), ZuneViewType.Pavo);
            AddStyle("Green", Color.FromArgb(255, 166, 172, 92), ZuneViewType.Pavo);
            AddStyle("Pink", Color.FromArgb(255, 192, 162, 182), ZuneViewType.Pavo);
            AddStyle("Magenta", Color.FromArgb(255, 140, 88, 140), ZuneViewType.Pavo);
            AddStyle("Purple", Color.FromArgb(255, 94, 90, 120), ZuneViewType.Pavo);
            AddStyle("Atomic", Color.FromArgb(255, 186, 24, 0), ZuneViewType.Pavo);
        }

        private void AddDevice(string name, ZuneViewType type, Color colour)
        {
            _devices.Add(new ZuneViewDevice(name, type, new SolidColorBrush(colour)));
        }

        private void SetDevices()
        {
            AddDevice("Zune 30", ZuneViewType.Keel, brown);
            AddDevice("Zune 80 120", ZuneViewType.Draco, black);
            AddDevice("Zune 4 8 16", ZuneViewType.Scorpius, red);
            AddDevice("Zune HD", ZuneViewType.Pavo, platinum);
        }

        private void SetItem(ref ItemsControl display, ref ComboBox devices, ref ComboBox styles)
        {
            ZuneViewDevice device = (ZuneViewDevice)devices.SelectedItem ?? _devices.First();
            ZuneViewStyle style = (ZuneViewStyle)styles.SelectedItem ?? _styles.First();
            display.ItemsSource = new List<ZuneViewDevice>()
            {
                new ZuneViewDevice(device.Type, new SolidColorBrush(style.Colour))
            };
        }

        private async void Render(UIElement element, StorageFile file)
        {
            using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite))
            {
                BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
                RenderTargetBitmap target = new RenderTargetBitmap();
                await target.RenderAsync(element, scale_size, 0);
                IBuffer buffer = await target.GetPixelsAsync();
                encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied,
                (uint)target.PixelWidth, (uint)target.PixelHeight, image_dpi, image_dpi, buffer.ToArray());
                await encoder.FlushAsync();
                target = null;
                buffer = null;
                encoder = null;
            }
        }

        public Library()
        {
            SetDevices();
            SetStyles();
        }

        public void Init(ref ItemsControl display, ref ComboBox devices, ref ComboBox styles)
        {
            devices.ItemsSource = _devices;
            styles.ItemsSource = _styles;
            devices.SelectedIndex = 0;
            styles.SelectedIndex = 0;
            SetItem(ref display, ref devices, ref styles);
        }

        public void Style(ref ItemsControl display, ref ComboBox devices, ref ComboBox styles)
        {
            SetItem(ref display, ref devices, ref styles);
        }

        public void Device(ref ItemsControl display, ref ComboBox devices, ref ComboBox styles)
        {
            ZuneViewDevice selected = (ZuneViewDevice)devices.SelectedItem;
            IEnumerable<ZuneViewStyle> list = _styles.Where(w => w.Types.Contains(selected.Type));
            styles.ItemsSource = list;
            styles.SelectedIndex = 0;
            SetItem(ref display, ref devices, ref styles);
        }

        public async void Save(UIElement element, ComboBox devices)
        {
            try
            {
                ZuneViewDevice selected = (ZuneViewDevice)devices.SelectedItem;
                FileSavePicker picker = new FileSavePicker
                {
                    DefaultFileExtension = file_extension,
                    SuggestedFileName = selected.Name,
                    SuggestedStartLocation = PickerLocationId.PicturesLibrary
                };
                picker.FileTypeChoices.Add("Image", new List<string>() { file_extension });
                StorageFile file = await picker.PickSaveFileAsync();
                if (file != null)
                {
                    Render(element, file);
                }
            }
            finally
            {
                // Ignore Exceptions
            }
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality. There is an enum for ZuneViewType for the different types of Zune Device.

The ZuneViewStyle Class has Properties for the Name of the Style, Color and an Array of ZuneViewType. Thee ZuneViewDevice Class has Properties for the Name of the Device, the ZuneViewType and Brush. The ZuneViewDeviceTemplateSelector is used to return the appropriate DataTemplate based on ZuneViewDevice.

The Library Class has various const and readonly Values including Color for examples for each Type of Device. There are List of ZuneViewStyle and List if ZuneViewDevice Members. There is a SetStyles Method which uses the AddStyle Method to add the Names, Colours and Devices for each Style and there is a SetDevices Method which uses the AddDevice Method to add the Names, Types and Example Colours of each Device. There is a Render Method which takes a UIElement and StorageFile – with an IRandomAccessStream from this it will use a BitmapEncoder with RenderTargetBitmap to create the Image to be Rendered.

Also in the Library Class there is a Constructor which calls the SetDevices and SetStyles Methods. The Init Method helps set up the look-and-feel of the Application, Style and Device will help set up the required items for the given Device that has been selected. The Save Method is used with a FileSavePicker to then call the Render Method with the selected file from PickSaveFileAsync.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-zuneview-app

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>
	<LinearGradientBrush x:Key="ZuneKeel">
		<GradientStop Offset="0" Color="#020202"/>
		<GradientStop Offset="1" Color="#424242"/>
	</LinearGradientBrush>
	<LinearGradientBrush x:Key="ZunePad">
		<GradientStop Offset="0" Color="#66000000"/>
		<GradientStop Offset="1" Color="#22000000"/>
	</LinearGradientBrush>
	<LinearGradientBrush x:Key="ZunePadOuter">
		<GradientStop Offset="0" Color="#66FFFFFF"/>
		<GradientStop Offset="1" Color="#22FFFFFF"/>
	</LinearGradientBrush>
	<LinearGradientBrush x:Key="ZuneScreen">
		<GradientStop Offset="1" Color="#231F20"/>
		<GradientStop Offset="0" Color="#524F4F"/>
		<LinearGradientBrush.Transform>
			<RotateTransform Angle="68" CenterX="0.5" CenterY="0.5"/>
		</LinearGradientBrush.Transform>
	</LinearGradientBrush>
	<DataTemplate x:Key="KeelTemplate" x:DataType="local:ZuneViewDevice">
		<Canvas Width="21" Height="36">
			<Rectangle Height="36" Width="21" Fill="{x:Bind Path=Fill, Mode=OneWay}" 
			Canvas.Left="1" Canvas.Top="0" RadiusX="1" RadiusY="1">
			</Rectangle>
			<Rectangle Canvas.Left="2" Canvas.Top="1" Fill="{StaticResource ZuneScreen}" 
			Height="24" Stroke="#5D5D5D" Width="19" />
			<Ellipse Canvas.Left="6" Canvas.Top="24" Fill="{StaticResource ZuneKeel}" 
			Stroke="#5D5D5D" StrokeThickness="1.5" Height="11" Width="11"/>
		</Canvas>
	</DataTemplate>
	<DataTemplate x:Key="DracoTemplate" x:DataType="local:ZuneViewDevice">
		<Canvas Width="21" Height="36">
			<Rectangle Height="36" Width="20" Fill="{x:Bind Path=Fill, Mode=OneWay}" 
			Canvas.Left="1" Canvas.Top="0" RadiusX="1" RadiusY="1">
			</Rectangle>
			<Rectangle Fill="{StaticResource ZuneScreen}" Canvas.Left="2" Canvas.Top="1" 
			Height="24" Stroke="#191616" Width="18"/>
			<Rectangle Canvas.Left="6.5" Canvas.Top="25" Height="9" Width="9" RadiusX="3" RadiusY="3" 
			Fill="{StaticResource ZunePad}" Stroke="{StaticResource ZunePadOuter}"/>
		</Canvas>
	</DataTemplate>
	<DataTemplate x:Key="ScorpiusTemplate" x:DataType="local:ZuneViewDevice">
		<Canvas Width="21" Height="36">
			<Rectangle Height="35" Width="16" Fill="{x:Bind Path=Fill, Mode=OneWay}" 
			Canvas.Left="4" Canvas.Top="0"/>
			<Rectangle Canvas.Left="5" Canvas.Top="1" Fill="{StaticResource ZuneScreen}" 
			Height="18" Stroke="#191616" Width="14" />
			<Rectangle Canvas.Left="7" Canvas.Top="22" Fill="{StaticResource ZunePad}" 
			Stroke="{StaticResource ZunePadOuter}" Height="10" Width="10" RadiusX="3" RadiusY="3"/>
		</Canvas>
	</DataTemplate>
	<DataTemplate x:Key="PavoTemplate" x:DataType="local:ZuneViewDevice">
		<Canvas Width="21" Height="36">
			<Rectangle Height="32" Width="16" Fill="{x:Bind Path=Fill, Mode=OneWay}" Canvas.Left="4" Canvas.Top="2" 
			RadiusX="1.5" RadiusY="1.5">
			</Rectangle>
			<Rectangle Fill="{StaticResource ZuneScreen}" Canvas.Left="5" Canvas.Top="3.25" Height="24.5" 
			Width="14" Stroke="Black" StrokeThickness="1" StrokeMiterLimit="1"/>
			<Rectangle Fill="Black" Canvas.Left="5" Canvas.Top="27" Height="2" Width="14"/>
			<Polygon Points="0,0 8,0 6,1.5 2,1.5 0,0" Canvas.Left="8" Canvas.Top="29" 
			Height="1.5" Width="8">
				<Polygon.Fill>
					<LinearGradientBrush>
						<GradientStop Offset="0" Color="#FF162025"/>
						<GradientStop Offset="1" Color="#FF0C1417"/>
					</LinearGradientBrush>
				</Polygon.Fill>
			</Polygon>
		</Canvas>
	</DataTemplate>
	<DataTemplate x:Key="DevicesTemplate" x:DataType="local:ZuneViewDevice">
		<StackPanel Orientation="Horizontal">
			<Viewbox Margin="2" Width="{StaticResource ControlContentThemeFontSize}"
			Height="{StaticResource ControlContentThemeFontSize}">
				<ContentControl Content="{Binding}"
				ContentTemplateSelector="{StaticResource ZuneViewDeviceTemplateSelector}"/>
			</Viewbox>
			<TextBlock Text="{x:Bind Path=Name, Mode=OneWay}"/>
		</StackPanel>
	</DataTemplate>
	<DataTemplate x:Key="StylesTemplate" x:DataType="local:ZuneViewStyle">
		<StackPanel Orientation="Horizontal">
			<Grid Margin="2" Width="{StaticResource ControlContentThemeFontSize}" 
			Height="{StaticResource ControlContentThemeFontSize}">
				<Grid.Background>
					<SolidColorBrush Color="{x:Bind Path=Colour, Mode=OneWay}"/>
				</Grid.Background>
			</Grid>
			<TextBlock Text="{x:Bind Path=Name, Mode=OneWay}"/>
		</StackPanel>
	</DataTemplate>
	<DataTemplate x:Key="ContentTemplate">
		<ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource ZuneViewDeviceTemplateSelector}"/>
	</DataTemplate>
	<local:ZuneViewDeviceTemplateSelector x:Key="ZuneViewDeviceTemplateSelector" 
	Keel="{StaticResource KeelTemplate}" Draco="{StaticResource DracoTemplate}" 
	Scorpius="{StaticResource ScorpiusTemplate}" Pavo="{StaticResource PavoTemplate}"/>
</Page.Resources>

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

<Grid Margin="50">
	<Grid.RowDefinitions>
		<RowDefinition Height="Auto"/>
		<RowDefinition Height="*"/>
	</Grid.RowDefinitions>
	<Grid.ColumnDefinitions>
		<ColumnDefinition Width="50*"/>
		<ColumnDefinition Width="50*"/>
	</Grid.ColumnDefinitions>
	<ComboBox Name="Devices" Grid.Row="0" Grid.Column="0" Margin="5"
	HorizontalAlignment="Stretch" SelectionChanged="Devices_SelectionChanged" 
	ItemTemplate="{StaticResource DevicesTemplate}"/>
	<ComboBox Name="Styles" Grid.Row="0" Grid.Column="1" Margin="5"
	HorizontalAlignment="Stretch" SelectionChanged="Styles_SelectionChanged"
	ItemTemplate="{StaticResource StylesTemplate}"/>
	<Grid Margin="50" Grid.Row="1" Grid.ColumnSpan="2" HorizontalAlignment="Center">
		<Viewbox>
			<ItemsControl Name="Display" ItemTemplate="{StaticResource ContentTemplate}"/>
		</Viewbox>
	</Grid>
</Grid>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="Save" Label="Save" Click="Save_Click"/>
</CommandBar>

Within the Page.Resources block of XAML above the Grid Element contains various Resources including LinearGradientBrush to help create the look-and-feel of the Devices and a DataTemplate for each ZuneViewDevice which is made up of Canvas, Rectangle and Ellipse Elements. There is also the ZuneViewDeviceTemplateSelector to help display the correct ZuneViewDevice Template.

Within the main Grid Element, the first block of XAML is a Grid Control which has two Rows and Columns, in the first Row are two ComboBox Controls in a Column each for Devices and Styles and in the second Row is a ItemsControl within a Viewbox which will be used to show the selected Device. The second block of XAML is a CommandBar with AppBarButton for Save which calls Save_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 Devices, ref Styles);
}

private void Devices_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
	library.Device(ref Display, ref Devices, ref Styles);
}

private void Styles_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
	library.Style(ref Display, ref Devices, ref Styles);
}

private void Save_Click(object sender, RoutedEventArgs e)
{
	library.Save(Display, Devices);
}

There is an OnNavigatedTo Event Handler which calls the Init Method in the Library Class, there is a Devices_SelectionChanged and Styles_SelectionChanged Event Handlers to call the Device and Style Methods in the Library Class respectively and Save_Click which calls the Save 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 ComboBox to select a Microsoft Zune and the second ComboBox to select a Colour for it and then can Save an Image of the selected Device in the selected Style

uwp-ran-zuneview-app

Step 15

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

vs2017-close

This example allows you to view Microsoft Zune of the Devices and Colours that were available to show how to use RenderTargetBitmap to Save an Image of a UIElement. The Assets used were originally created for ZuneCardr which was a Zune Social application that could read and export the Zune Card with other images and originally also allowed a Zune to be displayed also in an older version and make a great, nostalgic and hopefully fun way of learning how to create Images from XAML.

Keel was the code name of the original Zune, Draco was the code name of the follow-up the Zune 80 which was also later available in a 120GB Version, Scorpius was the code name of the smaller Zune and Pavo the code name for the Zune HD. Zune and all associated image designs and names are the trademarks and/or property of Microsoft and are shared here for educational purposes only.

Creative Commons License

Universal Windows Platform – FileView App

FileView App shows how to create a simple application that uses the FolderPicker and TreeView Control

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 FileViewApp and select a Location and then select Ok to create the Project
vs2017-new-project-fileview-app

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-fileview-app

Step 7

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

using System;
using System.Collections.Generic;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.UI.Xaml.Controls;

namespace FileViewApp
{
    public class FileViewItem
    {
        public string Name { get; set; }
        public string Path { get; set; }
    }

    public class Library
    {
        private async void FillNode(TreeViewNode node)
        {
            StorageFolder folder = null;
            if (node.Content is StorageFolder && node.HasUnrealizedChildren)
            {
                folder = (StorageFolder)node.Content;
            }
            else
            {
                return;
            }
            IReadOnlyList<IStorageItem> list = await folder.GetItemsAsync();
            if (list.Count == 0) return;
            foreach (IStorageItem item in list)
            {
                TreeViewNode itemNode = new TreeViewNode
                {
                    Content = item
                };
                if (item is StorageFolder)
                {
                    itemNode.HasUnrealizedChildren = true;
                }
                node.Children.Add(itemNode);
            }
            node.HasUnrealizedChildren = false;
        }

        public void Expanding(TreeViewNode node)
        {
            if (node.HasChildren) FillNode(node);
        }

        public void Collapsed(TreeViewNode node)
        {
            node.Children.Clear();
            node.HasUnrealizedChildren = true;
        }

        public FileViewItem Invoked(TreeViewNode node)
        {
            FileViewItem item = null;
            if (node.Content is IStorageItem storageItem)
            {
                item = new FileViewItem()
                {
                    Name = storageItem.Name,
                    Path = storageItem.Path
                };
                if (node.Content is StorageFolder)
                {
                    node.IsExpanded = !node.IsExpanded;
                }
            }
            return item;
        }

        public async void Folder(TreeView tree)
        {
            try
            {
                FolderPicker picker = new FolderPicker()
                {
                    SuggestedStartLocation = PickerLocationId.PicturesLibrary
                };
                picker.FileTypeFilter.Add("*");
                IStorageFolder folder = await picker.PickSingleFolderAsync();
                if (folder != null)
                {
                    tree.RootNodes.Clear();
                    TreeViewNode node = new TreeViewNode
                    {
                        Content = folder,
                        IsExpanded = true,
                        HasUnrealizedChildren = true
                    };
                    tree.RootNodes.Add(node);
                    FillNode(node);
                }
            }
            finally
            {
                // Ignore Exceptions
            }
        }
    }
}

In the Code File for Library there are using statements to include the necessary functionality. There is a FileViewItem with Properties for Name and Path

The Library Class has a FillNode Method which will help populate the TreeView Control by going through any Folder Childen as StorageFolder to find more of them until all the IStorageItem for all StorageFolder have been found. The Expanding Method is used to call the FillNode Method and Collapsed will be used to remove Children from a TreeViewNode. The Invoked Method is used to set a FileViewItem based on a TreeViewNode and the Folder Method is used with a FolderPicker to select an IStorageFolder with PickSingleFolderAsync and then it will set a Root TreeViewNode as the starting point for the TreeView and then call the FillNode Method.

Step 8

In the Solution Explorer select MainPage.xaml

vs2017-mainpage-fileview-app

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="TreeViewItemTemplate">
		<TextBlock Text="{Binding Content.DisplayName}"
		HorizontalAlignment="Left" VerticalAlignment="Center"
		Style="{ThemeResource BodyTextBlockStyle}"/>
	</DataTemplate>
	<Style TargetType="TreeView">
		<Setter Property="Template">
			<Setter.Value>
				<ControlTemplate TargetType="TreeView">
					<TreeViewList x:Name="ListControl"
					ItemTemplate="{StaticResource TreeViewItemTemplate}"
					ItemContainerStyle="{StaticResource TreeViewItemStyle}"
					CanDragItems="True" AllowDrop="True" CanReorderItems="False">
					</TreeViewList>
				</ControlTemplate>
			</Setter.Value>
		</Setter>
	</Style>
</Page.Resources>

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

<SplitView IsPaneOpen="True" DisplayMode="Inline">
	<SplitView.Pane>
		<TreeView x:Name="Display" SelectionMode="Single"
		Expanding="TreeView_Expanding" Collapsed="TreeView_Collapsed"
		ItemInvoked="TreeView_ItemInvoked"/>
	</SplitView.Pane>
	<StackPanel Margin="10">
		<TextBlock Text="Name:"/>
		<TextBlock Name="FileName" FontWeight="SemiBold"/>
		<TextBlock Text="Path:"/>
		<TextBlock Name="FilePath" FontWeight="SemiBold"/>
	</StackPanel>
</SplitView>
<CommandBar VerticalAlignment="Bottom">
	<AppBarButton Icon="OpenLocal" Label="Folder" Click="Folder_Click"/>
</CommandBar>

Within the Page.Resources block of XAML above the Grid Element contains DataTemplate for the TreeView which contains a TextBlock and a Style for the TreeView which contains a ControlTemplate to set the look-and-feel of the TreeView accordingly.

Within the main Grid Element, the first block of XAML is a SplitView Control which contains an TreeView within the Pane annd a StackPanel in the main content which contains TextBlock Controls to display information about a selected File or Folder. The second block of XAML is a CommandBar with AppBarButton for Folder which calls Folder_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 TreeView_Expanding(TreeView sender, TreeViewExpandingEventArgs args)
{
	library.Expanding(args.Node);
}

private void TreeView_Collapsed(TreeView sender, TreeViewCollapsedEventArgs args)
{
	library.Collapsed(args.Node);
}

private void TreeView_ItemInvoked(TreeView sender, TreeViewItemInvokedEventArgs args)
{
	FileViewItem item = library.Invoked((TreeViewNode)args.InvokedItem);
	FileName.Text = item.Name;
	FilePath.Text = item.Path;
}

private void Folder_Click(object sender, RoutedEventArgs e)
{
	library.Folder(Display);
}

There is an TreeView_Expanding, TreeView_Collapsed and TreeView_ItemInvoked Event Handler which will handle different Events from the TreeView Control along with TreeView_ItemInvoked which will get the FileViewItem to be displayed. There is a Folder_Click Event Handler which calls the Folder Method in 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 to to select any Folder which itself and any Child Folders will be displayed in the TreeView along with some basic information about any selected Files or Folders of the TreeView will be displayed also

uwp-ran-fileview-app

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 TreeView example from docs.microsoft.com which has been expanded to allow the selection of any Folder using a FolderPicker

Creative Commons License

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