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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s