Click here to Skip to main content
Click here to Skip to main content

Game Attack Combos : WPF Hybrid Smart Client for Combo Calculations

, 23 May 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
A WPF hybrid smart client for calculating attack combos in the Prince of Persia game.

Estimated read time: about an hour.

Looking for the attack combos necessary to score the “Combo Specialist” achievement/trophy? Feel free to skip to the list of necessary combos that has been repeatedly tested on new user profiles.

Game Attack Combos screenshots

Contents

Introduction

There are a great number of technologies and patterns that programmers can employ today; such as ASP, AJAX, MVC, MVP, MVVM, MSMQ, SOAP, XML, WSE, WCF, WPF, and more. All of these abbreviations and acronyms may leave you asking, “WTF?” Despite the various tutorials, books, blogs, etc. that we can use to learn from, there is something to be said for getting your hands dirty and uncovering hidden gems that are yet to be documented.

Concepts Covered in this Article

This article will demonstrate a complete Windows desktop application written in C# via Windows Presentation Foundation that connects to a Web service via Windows Communication Foundation. There are some key concepts that I will go into with some detail. Most of these concepts are beyond the typical encounters you may find posted elsewhere about these topics. An idea of what will be covered is listed below.

Requirements

You may download the entire solution source code for this demonstration, or download just the standalone application files.

No matter which download you choose, you will need to have the .NET Framework 3.5 with SP1 successfully installed on your computer in order to run the client application. In order to successfully build and run the solution code, you should also make sure you have the following installed and ready.

  • Visual Studio 2008 with SP1 (this is not necessary if you are comfortable building solutions at from the command-line via the SDK and have IIS to host the service)
  • SQL Server 2008 Express (update the web.config file in the ComboServices project to ensure the “GameAttackCombosEntities” connection string is set properly for your instance)

Coders are Often Gamers

As programmers, we code…a lot. Many of us also play video games as a way to unwind and appreciate other coders’ virtual masterpieces. This past holiday, I received a new game that was highly anticipated; Prince of Persia. It is a beautifully cel-rendered game with many acrobatic puzzles to solve and a robust combat system used on the few enemies found throughout.

My Inner Completionist

Soon after I began playing the game, I quickly realized that the completionist in me felt compelled to collect all the trophies/achievements (goals) that are offered. These vary from beating the game in minimal time to performing a perfect 14-hit attack combo. I am not too proud to search online for a guide when I get stumped, but I do like to give things a shot myself first.

I was able to satisfy just about all the requirements for the available goals on my own. However, there was one goal that eluded me (two if you count the fact that the ultimate one was to get all others). The “Combo Specialist” goal is not stated clearly. It simply asks that you uncover all possible attack combos. I studied combinatorics quite a bit in college, and a quick glance at the combo list/tree provided in the game menus raised one of my eyebrows. I knew that there were many hundreds of combinations and found it difficult to believe that the game creators would require such a behemoth task for one of the lower-rated goals.

To make matters worse, there is an error on the combo list screen that the player can reference when performing the various combos in each group, and whether or not the combo will lead to another group; in essence, forming a combo chain. One of the combo groups (Elika’s magic) in the list shows that some of the combos lead to the Aerial group, but the combo tree shows that it should be the Acrobatic group (the tree shows the correct path).

Combo List ScreenCombo Tree Screen
Figure 1: The combo list and combo tree screens found in-game are inconsistent.

Which combinations of the attack combos will actually award you the goal? How many total combinations are there? Why does the official game guide not list the exact combos necessary to get the goal? How will I satisfy my inner completionist when several online searches produce no valid theories on a resolution to my problem?

I decided to write a quick program to answer some of the questions I had about the “Combo Specialist” goal. It turns out that there are 1,602 possible attack combinations.

Guru Games is Born

What if Ubisoft Entertainment decided to contract out development on a smart client application that calculated the combinations for the user and provided a slick interface to hype the game up for the upcoming sequels (and movie)? The application would connect to a service on the Web to get the base combo definitions, user interface skin, images, and other assets. A code provided with a player’s copy of the user’s manual would allow him/her to download the game’s files and unlock the secrets to the fighting system that is planned to be used for the entire trilogy. When the next game in the trilogy is released, the player can enter the code from that manual to get the next game’s files. Updates to the files could be pulled automatically based on version checks with the service to fix any errors, or to add combos/goals for newly available downloadable content (DLC).

The Facts are in the Fiction

I imagine Ubisoft would hire a company like Guru Games. Though a fictional development company that specializes in satellite applications for the gaming industry, Guru Games is the company indicated as the creator of the “Game Attack Combos” application to be discussed hereafter. The lack of a more specific name is to avoid copyright infringement (real and imaginary) and to prevent restricting the application to just the Prince of Persia titles. It is possible that Ubisoft will create other games that use this new and innovative attack combo system.

Suppose in each copy of the Prince of Persia game, a code was printed on the manual that allowed the player to download information for a free Windows client that calculated all the possible combinations and even indicated which were necessary for trophies or achievements. A service on one of the company’s websites would accept connections from the client and send a package of files, when requested, based on a validated code. The client application then skins the user interface with assets found in the package and calculates the permutations of attack combos to display a checklist for the user.

This demonstration application uses Windows Presentation Foundation for the client user interface and connects to a Windows Communication Foundation service over HTTP for game combo package files.

Future-Spoofing

Sometimes, an application begins as a last-minute idea. It may be that the creators want to gauge the popularity of the application before investing more time and money into furthering it. It is times like these that the developers should take care to think about future-proofing the code base. We should always be thinking of how we will refactor our code at a later time. This thought-process will be demonstrated in the Game Attack Combo application here. I will point out where it will be good to implement a different pattern if the program gets an official upgrade or grows into a more complicated beast.

Logically Speaking

Nearly all of the projects in the demo solution reference a common logic library. It is this library that handles the management and manipulation of all things combo and package related. I encourage you to explore the logic library found in the code download at the beginning of this article. There are a number of abstractions at work when parsing the very simple combo definition files to transform them into the many hundreds of actual combinations possible. The only bits I will go into here are some of the issues with regards to packages.

That’s a Nice Package

When I refer to a package, I mean those found in the System.IO.Packaging namespace. If you are not familiar with this namespace, it contains classes that allow you to organize several files into a single container file. The service application in the demo solution uses packaging to deliver the necessary assets over the Web, and the client application needs to get the assets from the requested packages. The client also uses the packages’ metadata to manage the game title, version, and more.

Zip Up Your Package

ZipperEach file in a package may be compressed to reduce the overall size of the resulting file. In fact, the default implementation of the abstract Package class is a ZipPackage. There are a couple of quirks that you may find when working with packages. When you happen upon a runtime exception when testing your code, it can be very difficult to figure out what the problem is due to the embedded streams. Each package part (file in the container) is accessed via a Stream.

Additionally, I discovered that binary files that are added to a package do not compress well. In fact, my tests compressing image files yielded an increase in storage size. Therefore, I only chose to compress text files in packages. I chose to leave all binary files uncompressed (images in this case).

Copy That

Another issue to consider is how to extract a file from a package to be used after the package is closed. We all know that when we open files in our code, we should close the file as soon as possible in order to free the accompanying resources. This basic principle can actually cause trouble for a developer that needs to get at one of the parts inside a package and keep that part around beyond the scope of the package read. Luckily, the solution is simple once identified. The part must be copied to memory from the package before the package is closed. The project provided here copies several parts to MemoryStreams for later use.

Doing a Service to Your Code

Service-oriented programming has come a long way over the past couple years. In particular, Windows Communication Foundation (WCF) has managed to break down quite a few barriers for developers of distributed applications. With it, we can create many different types of services from a common programming model. In this scenario, Guru Games decided to implement a service accessible via HTTP (a Web service). There is much more to learn about WCF than you will find in this article. Please, consult the Microsoft’s Developer Network section on WCF for further learning.

Setting Up the WCF Service Project

The easiest way to setup a new WCF project is to use the project template provided in Visual Studio 2008. Right-click your solution and select Add | New Project.... Select the Web template named, “WCF Service Application”. Name it and click the OK button to get a nice shell for a WCF service in your new project.

Add a new WCF project
Figure 2: Adding a new WCF project is a snap.

A WCF service is made up of two entities. The first is the service contract in the form of an interface. This interface is what a client will use to identify the available operations that the service provides. The Game Attack Combos solution only has a single service with one operation. It offers a way for a client to download a combo package file for a specified game code and current package version on the client, if any.

[ServiceContract(Namespace  = "<a title="Linkification: http://gurugames.com/packages/" href="http://gurugames.com/packages/" class="linkification-ext">http://gurugames.com/packages/</a>")]
public interface IComboPackagesService {

    [OperationContract]
    byte[] DownloadComboPackage(string gameCode, string clientPackageVersion);

}
Listing 1: This project only has a single contract and operation.

With the contract defined, we can get to work on implementing the interface in a class. The class verifies the specified game code and retrieves the corresponding package file name from a database, opens the package file, checks the version against the specified client version, and if newer, loads the file to a binary array for return to the caller. For the complete code of this method, please refer to the code download at the beginning of this article.

[AspNetCompatibilityRequirements(
    RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public byte[] DownloadComboPackage(string gameCode, string clientPackageVersion) {
    // Prepare a binary data array.
    byte[] FileData = null;
    ...
    
    // Get the file name for the specified code.
    string PackageFileName = null;
    if (!string.IsNullOrEmpty(gameCode)) {
        PackageFileName = GetComboPackageFileNameByGameCode(gameCode);
    }
    if (!string.IsNullOrEmpty(PackageFileName)) {
        ...
        
        // Read the entire file into the array.
        using (FileStream PackageFile = File.Open(
            PackageFileName, 
            FileMode.Open, 
            FileAccess.Read, 
            FileShare.ReadWrite
        )) {
            // Open the package to get its version.
            Version CurrentVersion = null;
            using (ComboPackage Package = new ComboPackage(PackageFile)) {
                CurrentVersion = new Version(Package.Version);
            }
    
            // Check the version of the combo package file against the current
            // one specified.
            if (CurrentVersion > ClientVersion) {
                // Copy the combo package file to the data array.
                FileData = StreamHelper.CopyStreamToArray(PackageFile);
            }
        }
    }
    
    // Return the file data.
    return FileData;
}
Listing 2: The service operation implementation to download a package file.

The implementation class also defines an attribute that allows it to act in ASP.NET compatibility mode. This is used here to make it easy to map virtual paths to physical package files stored in a folder with the service.

Service Configuration Changes

Lastly, a small change must be made to the default configuration of the service in order to support larger transfers, like the download of package files. The necessary changes involve defining a new binding where we can indicate the maximum message size and message encoding. Then, the service endpoint must be pointed to the new binding configuration. Also, in order to support the ASP.NET compatibility mode mentioned before, the service must be configured to enable it.

<system.serviceModel>
    <bindings>
        <wsHttpBinding>
            <binding name="wsWithMtom"
                maxReceivedMessageSize="2097152"
                messageEncoding="Mtom" />
        </wsHttpBinding>
    </bindings>
    <services>
        <service ...>
            <endpoint address="" binding="wsHttpBinding" 
                bindingConfiguration="wsWithMtom" 
                contract="GG.GameAttackCombos.Services.IComboPackagesService">
                ...
            </endpoint>
            ...
        </service>
    </services>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
    ...
</system.serviceModel>
Listing 3: Changes to the service configuration are necessary for package download support.

Service Deployment

The service is now ready to be compiled and may be deployed to a location on the Web. If you have Visual Studio 2008 installed, you can run the service from the built-in Web server. The included project in the code download has a static port assigned to the service application to make it easier to connect to from the client. Now it is time to look at the client application.

You’re Such a Smart App

To be a smart application, it needs to do more than just user interface work. The client application in this demo does the heavy lifting of calculating all the possible attack combinations, with the help of the logic library that contains all the appropriate code. As a hybrid application, it still depends on the remote service to get all the necessary files and updates.

Presentation is Everything

Games are the epitome of multimedia entertainment. Therefore, a program that accompanies a game must share similar attributes. The desktop client developed for this solution employs Windows Presentation Foundation (WPF) to provide a better looking application in much less time than other frameworks. There is a plethora of knowledge to take in for WPF, and Microsoft’s Developer Network section on WPF is a good place to start.

I will show you some standard techniques in a developer’s arsenal with WPF. However, I will primarily focus on how to extend some of these techniques into more advanced scenarios that help solve issues that you may actually encounter when writing WPF applications for “real world” problems.

In the Beginning

When the client application is initially launched, it is nothing special to look at. In fact, the default skin that is applied makes it look just like a normal Windows application. That will change once a game combo package is downloaded and opened.

Initial main window
Figure 3: Nothing special here…yet.

Bland Opening

Opening a new game combo package requires the user to click the Open menu item to launch the OpenPackageWindow. From this window, the user is shown any games that are already downloaded to his/her computer. At first, there are no existing games. Notice the watermark for the new game code text box in Figure 4.

Initial open package window
Figure 4: A new game code must be entered first.

A Watermark Text Box

The WatermarkTextBox custom control is created from a very simple code file and a style resource. First, a quick look at the control’s code file reveals a class that descends from TextBox. This new class has a static and read-only field of type DependencyProperty. It is in this field that a new dependency property is registered for the control’s watermark text. I highly recommend that you take some time to learn about dependency properties, if you are a newcomer to WPF. They are one of the pillars that hold the entire WPF framework up high. One of CodeProject’s well known WPF authors gives a very detailed look at dependency properties, if you are interested in digging even deeper.

As a convenience, I have also included a property for WatermarkText that gets and sets the value of the dependency property. This is not a requirement, but is standard protocol when developing custom controls. Such convenience properties are often referred to as dependency property “wrappers”. By offering a property wrapper, the value of the dependency property can be easily set from code. In addition, the XAML compiler depends on these wrapper properties. You will definitely want to include the wrapper properties if you hope to allow developers to set the dependency properties via XAML.

Finally, a static constructor is added that overrides the new control’s default style key. This allows the control to have a default style defined for it. When overriding a WatermarkTextBox’s style in a custom skin later, having this default style to build from will prevent the need to redefine the control’s template.

public class  WatermarkTextBox : TextBox {

    public static readonly DependencyProperty WatermarkTextProperty =
        DependencyProperty.Register(
            "WatermarkText",
            typeof(string),
            typeof(WatermarkTextBox),
            new PropertyMetadata(string.Empty)
        );

    public string WatermarkText {
        get { return (string)GetValue(WatermarkTextProperty); }
        set { SetValue(WatermarkTextProperty, value); }
    }

    static WatermarkTextBox() {
        DefaultStyleKeyProperty.OverrideMetadata(
            typeof(WatermarkTextBox),
            new FrameworkPropertyMetadata(typeof(WatermarkTextBox))
        );
    }

}
Listing 4: The very simple code for our new WatermarkTextBox control.
Note: Normally, such a control would be separated into its own assembly as a custom control library. I chose to keep the control in the client application for demonstration purposes.

All the code in Listing 4 does, is add a dependency property, a wrapper property, and a single-statement static constructor. How will the new text box display the watermark and hide it when it receives focus or text is entered? That is where a default style comes in.

Default Styles and a Generic “Brand”

WPF looks for generic and theme-specific resources in a Themes folder in the root of the project. These resources may be provided to offer a default style for the controls created in your application (or control library). I added a resource dictionary named, “Generic.xaml”, in the Themes folder. It contains the default style for the WatermarkTextBox.

<ResourceDictionary 
    xmlns="<a title="Linkification: http://schemas.microsoft.com/winfx/2006/xaml/presentation" href="http://schemas.microsoft.com/winfx/2006/xaml/presentation" class="linkification-ext">http://schemas.microsoft.com/winfx/2006/xaml/presentation</a>"
    xmlns:x="<a title="Linkification: http://schemas.microsoft.com/winfx/2006/xaml" href="http://schemas.microsoft.com/winfx/2006/xaml" class="linkification-ext">http://schemas.microsoft.com/winfx/2006/xaml</a>"
    xmlns:local="clr-namespace:GG.GameAttackCombos.Client">
Listing 5: The default style for the WatermarkTextBox control.
Note: The style of this new control could be even more robust by including default styles for each of the themes handled by WPF (e.g. Vista’s Aero theme, the Classic theme, etc.).  However, that topic is beyond the scope of this article.

I used one of the few great tools freely available on the Internet to peek into the default templates and styles of a standard TextBox. Reflector comes to the rescue. This time, I used the BAML Viewer add-in with Reflector. With it, I am able to see the BAML (binary XAML) for the resource dictionaries used for theming all the controls in WPF. I used it to deconstruct the TextBox control, so I could add a simple TextBlock that displays the specified watermark text of the custom control. It is essential to use the same names of child controls (parts) used by the default template of a built-in control when overriding it. Doing so allows the control to act as it normally does (e.g. a text box needs to work with a part named “PART_ContentHost” to set the content as needed).

I also added a multi-trigger that will set the watermark text block to be visible only when the control is not focused and has no text entered. In the essence of article length, I leave the remaining template definition for the reader to study.

Note: I have since added an additional property for a WatermarkBrush that allows a control user to specify a brush for the watermark text.

Quality Service is so Hard to Find

After a user enters the fictional code provided with their game manual (e.g. 80c0-9c76-cfc7-440a-9261) and presses the Open button, the open package window attempts to connect to our previously created service to download the requested combo package. A service reference must be added to the project before such an attempt can be made. Right-click your project and select Add Service Reference.... The dialog that appears allows you to enter the address of the service you would like to reference. For this demo, you can simply click the Discover button to add a reference to the service project in the solution. Be sure to enter a meaningful namespace and press the OK button.

Add service reference
Figure 5: Add a service reference to a project in the same solution.

Some changes will need to be made again to the service bindings’ configuration. The changes are similar to those made in the configuration file of the service application. Please, refer to the client application’s App.config file to see the necessary changes. To summarize, we have to create a custom binding so we can change the maxReceivedMessageSize and readerQuotas/maxArrayLength attribute values. This allows for larger messages to be received from the service (i.e. a file download).

The OpenPackageWindow can now perform an asynchronous call to one of the project’s static helper methods (see ComboPackageHelper.DownloadNewPackage for the details) that connects to the service and downloads the combo package for the entered game code. After downloading the file, the package’s metadata is updated with the entered game code as a reference for updates later. It is then saved to the user’s isolated storage.

Note: You can learn more about the WPF threading model on MSDN. In addition, you can study the SelectPackage method of the OpenPackageWindow class in the demo client project.

Processed Files are a Good Thing

After a successful download of the new file, the main window opens the package for processing. First, the name of the file being opened from isolated storage is stored in the application’s properties collection for later reference. Next, the combo definitions XML file is extracted from the package and loaded into a nice class provided by the logic library. The ComboDefinitions class parses the XML in the definition file and generates an object graph that represents the definitions.

The definitions are then flattened into actual combo command sequences and bound to the main list box in the window. Next, the skin resource file is extracted from the package as an in-memory copy and the package is closed. Finally, the skin is loaded into the application’s resources to give the client a totally new look.

Note: The last thing done when a package is opened is to start a timer that will automatically save the state of the checklist provided in the list box that displays the combo sequences. That way the user will not likely lose the state of their checklist if something bad happens.

Put on a New Skin

With an in-memory copy of the skin file read from the package, the client application replaces its default skin (which is provided mainly to avoid errors with styles reference by key on certain controls) with the game-specific one. This is easier than you may first think. All you really need is a stream containing XAML for a resource dictionary that contains all the styles you would like to apply to the user interface. With such a stream, the code is relatively simple.

public void LoadSkin(Stream skinStream) {
    // Create a new resource dictionary for the skin from the specified stream 
    // via an XAML reader.
    ResourceDictionary NewSkin = XamlReader.Load(skinStream) as ResourceDictionary;

    // Replace the last dictionary with the new skin.
    Resources.MergedDictionaries.RemoveAt(Resources.MergedDictionaries.Count - 1);
    Resources.MergedDictionaries.Add(NewSkin);
}
Listing 6: Replacing a resource dictionary to load a new skin is very easy.

Listing 6 shows the very simple LoadSkin method defined on the client’s App class. A quick call to XamlReader.Load, passing the appropriate stream as an argument is all that is needed to initialize a new ResourceDictionary instance. From there, I remove the last dictionary in the application’s resources and add the new one. Believe it or not, that’s it! The user interface is immediately updated to reflect the new style and template definitions present in the skin loaded.

Skinned main window
Figure 6: The main window with the opened package's skin applied.

I slipped one by some of you. Others may be asking, “How in the world did the background image get loaded from thin air?” Those of you intimate with XAML and skinning with WPF know that a background image, like the one in Figure 6, can really only be referenced in XAML by URI. That is, a Web address, physical file path, or resource path is the easiest way to reference an image. The image above is dynamically loaded from the same package file the skin was extracted from.

Dynamically Streamed Images Referenced from XAML

The client application, being a smart app, has a class included for the sole use of loading a resource stream by name from the currently viewed package. The CurrentSkinResource class defines a method for loading a resource with a specific name that is related to the skin in the package.

public Stream LoadResourceByName(string resourceName) {
    ...
    // Open any current combo package.
    using (ComboPackage Package = App.Current.OpenCurrentComboPackage()) {
        if (Package != null) {
            // Open the requested skin resource from the combo package as a copy.
            LastStreamRequested = Package.OpenSkinResourceStream(resourceName, true);
        } else {
            throw new ApplicationException(
                "There is no current combo package being viewed."
            );
        }
    }
    return LastStreamRequested;
}
Listing 7: Loading a skin resource from the current combo package.

The code in Listing 7 opens the currently viewed combo package. Then, it opens the appropriate skin resource from the package into an in-memory stream. Finally, the memory stream is returned; to be consumed by an image source in the calling skin definition.

The skin XAML calls this method by way of an ObjectDataProvider. These are perfect for initializing an instance of a class and calling one of its methods. The catch is how to bind an image source to it properly.

<ObjectDataProvider
    x:Key="LoadSkinResource" 
    ObjectType="{x:Type local:CurrentSkinResource}" 
    MethodName="LoadResourceByName">
    <ObjectDataProvider.MethodParameters>
        <system:String>PrinceOfPersia2008Background.png</system:String>
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<Style x:Key="GameBackground" TargetType="{x:Type Panel}" >
    <Setter Property="Background">
        <Setter.Value>
            <ImageBrush 
                AlignmentX="Left" AlignmentY="Top" 
                Stretch="None" TileMode="None">
                <ImageBrush.ImageSource>
                    <BitmapImage BaseUri="{x:Null}" CacheOption="OnLoad">
                        <BitmapImage.StreamSource>
                            <Binding Source="{StaticResource LoadSkinResource}" />
                        </BitmapImage.StreamSource>
                    </BitmapImage>
                </ImageBrush.ImageSource>
            </ImageBrush>
        </Setter.Value>
    </Setter>
</Style>
Listing 8: XAML snippet from a game skin that binds to an image stream via a method call.

Defining the ObjectDataProvider is simple enough. We give it a key, tell it the type of object to create, indicate the name of the method to call, and provide any parameters for the method. In this case we want to get a stream of the PrinceOfPersia2008Background.png image that is buried deep within the same package the skin was loaded from.

The style referenced by the GameBackground key sets the panel’s Background property. An image brush is necessary for our needs, so one is included with appropriate alignment, stretch and tiling. Setting the ImageSource property of the ImageBrush requires the use of a BitmapImage. These are usually used to load a bitmap image from file. This one uses its StreamSource property instead of UriSource. The StreamSource is primarily set via code, but we do not have that option with our skin definition. Instead, a binding is used with a source that references the ObjectDataProvider defined earlier. There are two very important additional settings that must be present for all this to work properly.

First, BitmapImage’s BaseUri property must be set to null. The documentation states that either UriSource or StreamSource must be set; however, there is apparently a bug present. When I set the StreamSource, no image is loaded. Thanks again to Reflector, a peek into the code of BitmapImage reveals that BaseUri and UriSource must be null in order for StreamSource to be used. The problem is, BaseUri defaults to an empty string (e.g. “”), not null. Therefore, setting it to null is a must in this case. This special case is only an issue from XAML.

Second, BitmapImage’s CacheOption property should be set to “OnLoad”. This ensures that the image source is loaded immediately and the stream is disposed of. Since the LoadResourceByName method is creating a MemoryStream and releasing it into the wild, that stream really should be disposed of instead of left for garbage collection. This setting makes sure of that for us.

It seems like a lot of work, but it is imperative to get our background image loaded from the package via the skin definition. Next, we can finally look at how all those combo sequences are displayed as gaming platform images.

A List Together

List box of combo iconsList boxes are no longer simple windowed views into a basic list of items. Oh, no. They can be much more than that with WPF. In fact, like all WPF controls, you can completely customize its appearance via the control template. However, controls that display a collection of items have an additional template that is used for each item. This is a very good thing for this application. It needs to display attack combos in a meaningful way to the user. Ideally, it should present each sequence of commands as a sequence of button images.

Traditional presentation methods would require custom painting the entire control, or at least attaching custom item draw handlers. With WPF, It is a simple matter of adding a custom item template to the list box.


<Window.Resources>
    <ResourceDictionary>
        ...
        <local:DrawingResourceKeyConverter x:Key="DrawingResourceKeyConverter" />
    </ResourceDictionary>
</Window.Resources>
...
<ListBox Grid.Row="1" Name="lbCombos" IsTextSearchEnabled="False" 
    Style="{DynamicResource ComboList}" KeyUp="lbCombos_KeyUp">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <CheckBox x:Name="chkCompleted" Focusable="False" 
                    Margin="2" VerticalAlignment="Center" 
                    IsChecked="{Binding Path=IsCompleted}" />
                <ItemsControl IsTabStop="False" VerticalAlignment="Center" 
                    ItemsSource="{Binding Path=CommandSequence}">
                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <StackPanel IsItemsHost="True" Orientation="Horizontal" />
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <Image Height="16" Margin="5,2,0,2"
                                ToolTip="{Binding Path=MappedButton.Id}">
                                <Image.Source>
                                    <DrawingImage Drawing="{Binding 
                                        Path=MappedButton.IconKey, Mode=OneWay, 
                                        Converter={StaticResource 
                                            DrawingResourceKeyConverter}}" />
                                </Image.Source>
                            </Image>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
Listing 9: The custom item template for the list box that displays the combos hosts an embedded ItemsControl.

A list box’s ItemTemplate is where you can instruct the list box how to display each item it holds. The one in Listing 9 uses a StackPanel to display a CheckBox and embedded ItemsControl. The check box is bound to each item’s IsCompleted property. An ItemsControl is a very simple control that displays a collection of items; much like a list box, but without the user interactivity. This one is bound to each combo’s CommandSequence list and is instructed to use a horizontal StackPanel to hold those Command items. Similar to its parent, a custom ItemTemplate is provided that displays an Image control. The end effect is a horizontal list of images that correspond to the mapped button of each command in the combo sequence.

Note: The items bound to the list are of type FlattenedCombo; a class that is home to a list of game commands, a flag to indicate user completion, and a property to indicate the combo's additional aspects (e.g. achievements/trophies, longest combo, etc.).

Displaying a Drawing Resource by its Key

There are times when the type of data you are binding to a property is not compatible with that property’s type. An example of this can be seen in Listing 9. The Image control used as the item template of the inner ItemsControl uses a DrawingImage as its source. Furthermore, its Drawing property is bound to “MappedButton.IconKey”. Each Command object is mapped to a Button instance via its MappedButton property. Every Button has a corresponding IconKey that holds the key of a drawing resource included in the application. However, the IconKey is a string and the Drawing property expects…well, it expects a Drawing instance. A value converter is the vital link between these two incompatible types.

Note: The drawing resources for each button are in a resource dictionary named “Icons.xaml” in the Resources folder of the client application.

Value Converters

A value converter simply converts a value to a target type. If necessary, it will convert a value back to its original source type as well. To create a value converter, you must create a class that implements the IValueConverter interface. The interface contracts the Convert and ConvertBack methods. The Drawing’s binding in Listing 9 utilizes the custom DrawingResourceKeyConverter defined in the window’s resources.

[ValueConversion(typeof(string), typeof(Drawing))]
public class DrawingResourceKeyConverter : IValueConverter {

    public object Convert(object value, Type targetType, ...) {
        if (value != null) {
            if (value is string) {
                if (targetType == typeof(Drawing)) {
                    // Get the resource with a key specified as the value.
                    return (Drawing)Application.Current.Resources[value];
                }
                ...
            }
            ...
        } else {
            return null;
        }
    }

    public object ConvertBack(object value, Type targetType, ...) {
        throw new NotImplementedException();
    }

}
Listing 10: The DrawingResourceKeyConverter converts a string key into a Drawing from the application's resources.

The DrawingResourceKeyConverter class converts a string that contains a key into a Drawing after retrieving it from the application’s resources. This makes deeply embedded data bindings that much easier to manipulate with very little code.

Conditionally Selecting a Data Template

Button sets combo boxThe user may select from a list of buttons/keys to change the icons used to draw the combo command sequences. These icon themes are grouped by gaming platform in a ComboBox. This is a great way to assist the user in making a selection. It is also very easy to achieve grouping on the collection before binding it to the control’s ItemsSource.


// Load the button sets.
ButtonSets = ButtonSet.LoadButtonSets(ButtonSetsFile);

// Update the UI with a grouped view of the button sets.
ICollectionView View = CollectionViewSource.GetDefaultView(ButtonSets);
View.GroupDescriptions.Add(new PropertyGroupDescription("Platform"));
cmbButtonSets.ItemsSource = ButtonSets;
Listing 11: The list of button sets are grouped by Platform by way of its default collection view before binding to the ComboBox.

However, some of the icon themes are named the same, but under different platforms. This is fine in the drop-down list, but not in the selection box (the area that shows your current selection when the drop-down list is not visible). If a user chooses “Buttoned” under “Xbox 360”, the selection box would only show “Buttoned”, normally. To better serve the user, a DataTemplateSelector is assigned to the combo box’s ItemTemplateSelector to choose a more expressive data template for the selection box.

<Window.Resources>
    <ResourceDictionary>
        <DataTemplate x:Key="ButtonSetByName">
            <TextBlock Text="{Binding Path=Name}" />
        </DataTemplate>
        <DataTemplate x:Key="ButtonSetByPlatformAndName">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Path=Platform}" />
                <TextBlock Text=": " />
                <TextBlock Text="{Binding Path=Name}" />
            </StackPanel>
        </DataTemplate>

        <local:ButtonSetsTemplateSelector x:Key="ButtonSetsTemplateSelector" />
        ...
    </ResourceDictionary>
</Window.Resources>
...
<ComboBox Grid.Column="1" Name="cmbButtonSets" 
    ItemTemplateSelector="{StaticResource ButtonSetsTemplateSelector}" 
    Width="200" Margin="0,0,0,5" 
    SelectionChanged="cmbButtonSets_SelectionChanged">
    <ComboBox.GroupStyle>
        <GroupStyle>
            <GroupStyle.HeaderTemplate>
                <DataTemplate>
                    <TextBlock 
                        Text="{Binding Path=Name}" 
                        Style="{DynamicResource GroupHeader}" />
                </DataTemplate>
            </GroupStyle.HeaderTemplate>
        </GroupStyle>
    </ComboBox.GroupStyle>
</ComboBox>
Listing 12: The button set combo box assigns a custom data template selector for its items and styles the group header.

First, the two data templates to select from are defined in the window’s resources section. One template simply displays the button set name; the other template stacks the platform and name, separated by a colon. Next, a definition for the ButtonSetsTemplateSelector is added to the resources section also. Finally, the ComboBox’s ItemTemplateSelector property is set to the aforementioned template selector resource.

The custom DataTemplateSelector class overrides the SelectTemplate method. The container argument passed to it is used to base the decision of which data template to return. If the container is a ContentPresenter with a ComboBox as its TemplatedParent, the data template with combined platform and name is returned. In all other cases, the basic template is returned. The content presenter’s TemplatedParent will be a ComboBoxItem for the drop-down list, instead of the actual ComboBox.

public class ButtonSetsTemplateSelector : DataTemplateSelector {

    public override DataTemplate SelectTemplate(object item, DependencyObject container) {
        string ResourceKey = "ButtonSetByName";

        // Get the main window.
        Window Window = Application.Current.MainWindow;

        // Test if the container is a ContentPresenter.
        ContentPresenter Presenter = container as ContentPresenter;
        if (Presenter != null) {
            ComboBox Combo = Presenter.TemplatedParent as ComboBox;
            if (Combo != null) {
                ResourceKey = "ButtonSetByPlatformAndName";
            }
        }

        return Window.FindResource(ResourceKey) as DataTemplate;
    }

}
Listing 13: The ButtonSetsTemplateSelector selects the appropriate data template from the main window's resources.

Grand Opening

Now that the application has a nice new skin from the recently downloaded game combo package, even the old OpenPackageWindow looks a lot better. In fact, after a couple existing games are downloaded, it displays them in a list with a nice icon for the user to select.

Skinned open package window
Figure 7: The OpenPackageWindow displays the existing downloaded games.

The game combo package icons are displayed with the help of another value converter. When the list of existing games is built, the icon is read from the package and stored in a binary array. A list of the titles and icon data are bound to the list box. The array of icon data is directly bound to an Image control with the help of another value converter defined in the window’s resources section.

<Window.Resources>
    <local:BinaryImageConverter x:Key="BinaryImageConverter" />
</Window.Resources>
...
<ListBox Name="lbExistingGames" ...>
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel IsItemsHost="True" Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Margin="5">
                <Border BorderBrush="Black" BorderThickness="1" 
                    Width="72" Margin="0,0,0,5">
                    <Image 
                        Source="{Binding Path=IconData, 
                        Converter={StaticResource BinaryImageConverter}}" />
                </Border>
                <TextBlock Text="{Binding Title}" />
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
Listing 14: The list box binds each item's Image control to the IconData array and uses the BinaryImageConverter for assistance.

Once again, a value converter is implemented to convert the icon data into an image source. This converter loads the byte array into a MemoryStream, creates a new BitmapImage object, sets its StreamSource property to the in-memory stream, and returns the image source as the converted target object.

[ValueConversion(typeof(byte[]), typeof(ImageSource))]
public class BinaryImageConverter : IValueConverter {

    public object Convert(object value, Type targetType, ...) {
        if (value != null) {
            if (value is byte[]) {
                if (targetType == typeof(ImageSource)) {
                    // Create a MemoryStream for the binary image data.
                    byte[] Data = (byte[])value;
                    MemoryStream Stream = new MemoryStream(Data);

                    // Create a BitmapImage to hold the stream data and return it.
                    BitmapImage Image = new BitmapImage();
                    Image.BeginInit();
                    Image.CacheOption = BitmapCacheOption.OnLoad;
                    Image.StreamSource = Stream;
                    Image.EndInit();

                    return Image;
                }
                ...
            }
            ...
        } else {
            return null;
        }
    }

    ...
}
Listing 15: The BinaryImageConverter converts an array of raw bytes into a BitmapImage by way of a MemoryStream.

More Game Skins

I decided to create another game combo package with accompanying combo definitions, skin, and assets. As expected, the user must enter a valid game disc code in order to download the package (e.g. 720d-789a-41e7-97cc-01fc). I would guess this newer version of Prince of Persia will end up being another trilogy, so a third game and package would end up on Guru Game’s servers once the time was right.

Alternately skinned main window
Figure 8: The alleged Prince of Persia 2 game combo package loaded; featuring Elika on a new skin.

Proper Patterns

The client application included in this demo solution does not follow what many consider “proper patterns” (e.g. MVC, MVP, MVVM, DMVVM, etc.). One of the fastest growing patterns to write WPF applications with is the Model-View-ViewModel (MVVM) pattern. I am a big fan of this pattern and believe in the great separation of concerns it offers to developers. I chose to not implement the pattern with this demo solution for a couple of reasons. First, I think it may be a great exercise to refactor the client project to fit the MVVM pattern in another article. Second, I did not want to distract from the information that this article is trying to relay; pure WPF techniques. Last, forgoing a proper pattern in the short term to refactor an application later happens in the “real world” all the time. As long as you are aware of the need to refactor if the application grows, you can design a project that is very easy to update when the time comes.

Other Projects Included in the Solution

The solution found in the code download does contain other projects not mentioned in the article. A project dedicated to the data accessed by the WCF service project contains an ADO.NET Entity Data Model. The service application references this data project in order to query a SQL Server 2008 Express database when a package download is requested by the client.

In addition, a small console application is present. This program makes it easy to generate a package file containing all the necessary assets that are downloaded by the client application. I used this command-line tool to quickly produce the package files that the service application stores and delivers upon request. It handles the tedium of streaming each of the assets into a package structure that the client expects.

The “Combo Specialist” Achievement/Trophy

If you are like many Prince of Persia gamers out there, you have tried tons of different combinations of attack combos in the hopes of finding the right set that will award you that special achievement/trophy; the “Combo Specialist”. The good news is, you do not have to perform all 1,602 possible combos, like the task hint may lead you to believe. You only need to pull off 60 of them. Other guides will tell you 62, 63, or even more, but 60 is the magic number (remember, a combo is actually two or more attacks). I have tested this list at least 10 times with a new user profile each time. There are a few rules to keep in mind when trying to achieve this goal.

  1. A combo cannot kill the enemy. Even the last hit cannot kill your enemy.
  2. A combo cannot be blocked by the enemy. No part of the combo can be blocked for you to succeed.
  3. A combo cannot knock an enemy into a wall or off a ledge. If your combo chain is interrupted by an edge animation, it will not count.
  4. After you successfully complete all 60 combos, you must kill the enemy you are currently fighting before the achievement/trophy is awarded.
A = Acrobatic; G = Gauntlet; M = Magic; S = Sword
  1. A,G
  2. A,G,A,G
  3. A,G,A,M,G
  4. A,G,A,M,M
  5. A,G,A,M,S
  6. A,G,A,S
  7. A,M,G
  8. A,M,M,G
  9. A,M,M,M
  10. A,M,M,S
  11. A,M,S,G
  12. A,M,S,M,G
  13. A,M,S,M,M
  14. A,M,S,M,S
  15. A,M,S,S
  1. A,S,S,S
  2. G,A
  3. G,A,G
  4. G,A,M,G
  5. G,A,M,M
  6. G,A,M,S
  7. G,A,S
  8. G,G
  9. G,M,A
  10. G,M,G
  11. G,M,M,A
  12. G,M,M,G
  13. G,M,M,M
  14. G,M,M,S
  15. G,M,S,A
  1. G,M,S,G
  2. G,M,S,M,A
  3. G,M,S,M,G
  4. G,M,S,M,M
  5. G,M,S,M,S
  6. G,M,S,S
  7. G,S
  8. M,A
  9. M,G
  10. M,M,A
  11. M,M,G
  12. M,M,M
  13. M,M,S
  14. M,S,A
  15. M,S,G
  1. M,S,M,A
  2. M,S,M,G
  3. M,S,M,M
  4. M,S,M,S
  5. M,S,S
  6. S,A
  7. S,G
  8. S,M
  9. S,S,A
  10. S,S,G
  11. S,S,M
  12. S,S,S,A
  13. S,S,S,G
  14. S,S,S,M
  15. S,S,S,S

Calculating “Combo Specialist”

The application has to jump through a few hoops in order to properly calculate the combos necessary to get the “Combo Specialist” award. The theory behind the calculation is to include all the combos that are in the Combo List screen, but find the shortest path to reach each of them.

For example, the Elika/Magic group of combos has a few that lead to the Lift/Gauntlet group. However, getting to the Lift group by way of the Magic group is not the shortest path. Since, you can get to the Lift group from the Normal group in one command, it’s shorter to build combos from there and skip the Magic group altogether. Of course, the Magic group must be included in order to cover all combos, but it can also be reached from the Normal group with one command. In order to include the combos in the Throw group, you can only get to them by way of the Acrobatic group. Therefore, the shortest path to the Throw group is from the Normal group to the Acrobatic group to the Throw group (i.e. A,G,...).

/// <summary>
/// Recursively builds a list of shortest path sequences that are necessary to complete 
/// in order to receive the "Combo Specialist" achievement/trophy.
/// </summary>
/// <param name="group">The initial combo group to calculate from.</param>
/// <param name="startedSequence">Any started sequence to build onto.</param>
private void BuildShortestPathSequences(ComboGroup group, List<Command> startedSequence) {
    // Create a new sequence of commands and initialize it with any started sequence.
    List<Command> Sequence = null;
    if (startedSequence == null) {
        startedSequence = new List<Command>();
    }

    // Traverse each combo in the group, add it to the list of shortest path 
    // combos, and check its next group for the need to continue down that path.
    Dictionary<ComboGroup, AttackCombo> CombosToContinue = 
        new Dictionary<ComboGroup, AttackCombo>();
    foreach (AttackCombo Combo in group.AttackCombos) {
        // Create a new build sequence for this combo from any started sequence
        // plus its own command sequence.
        Sequence = new List<Command>(startedSequence);
        Sequence.AddRange(Combo.CommandSequence);

        // Add this sequence as a new shortest path combo, if it has more than 1 command.
        if (Sequence.Count > 1) {
            ShortestPathCombos.Add(new FlattenedCombo(Sequence));
        }

        if (Combo.NextGroupInChain != null) {
            // Check this combo's next group against any existing ones to continue.
            if (CombosToContinue.ContainsKey(Combo.NextGroupInChain)) {
                // Compare the existing combo to continue for this group with the current
                // combo by their command sequence counts.
                AttackCombo ComboToContinue = CombosToContinue[Combo.NextGroupInChain];
                if (Combo.CommandSequence.Count < ComboToContinue.CommandSequence.Count) {
                    // Swap the combo to continue for this new shorter one.
                    CombosToContinue[Combo.NextGroupInChain] = Combo;
                }
            } else if (!GroupsToFollow.Contains(Combo.NextGroupInChain)) {
                // Add the combo as one to continue for the next group.
                GroupsToFollow.Add(Combo.NextGroupInChain);
                CombosToContinue.Add(Combo.NextGroupInChain, Combo);
            }
        }
    }

    // Build onto the combos to continue recursively.
    foreach (AttackCombo Combo in CombosToContinue.Values) {
        // Create a new build sequence for this combo from any started sequence
        // plus its own command sequence.
        Sequence = new List<Command>(startedSequence);
        Sequence.AddRange(Combo.CommandSequence);

        BuildShortestPathSequences(Combo.NextGroupInChain, Sequence);
    }
}
Listing 16: The recursive method that calculates the "Combo Specialist" command sequences.

The code in Listing 16 uses the already loaded object graph of the combo definitions in order to calculate the necessary combos. The initial call to the BuildShortestPathSequences method uses the StartingComboGroup (which is defined as the Normal group) to start with and no started sequence. From there, it adds all the combos in the group to a global list, and determines if it should follow the next group in that combo’s chain. After processing all the combos in the current group, any that should be continued to their next group are then recursively processed.

Two global lists are maintained that track whether or not a combo should be continued further down the tree. The decision is made based on whether or not a group further up the tree will be processing the same next group. If so, there is no need for this deeper branch to waste time doing so.

Prince of Persia’s History

Prince of Persia - 1989For those that do not know of the Prince of Persia franchise, it started in 1989 as a side-scrolling platformer, featuring a Persian prince pitted against an evil vizier left to rule the land in the sultan’s stead while away at war. There is plenty more information about the franchise on Wikipedia.


Disclaimer

Guru Games is a fictional company and the Game Attack Combos application is for demonstration purposes only. Ubisoft owns all rights to the Prince of Persia game franchise and has not hired me to develop any such application for their games.

This entire development scenario was fabricated for the sole intent of demonstrating several .NET technologies for a hybrid smart client that was actually used to solve a problem I was having acquiring 100% completion of the Prince of Persia game released in 2008. Ubisoft has not announced whether a sequel to the latest Prince of Persia game will use the same combat system used in the first. They have not hinted that such a combat system will be used in any future games. Again, this was all speculation used to justify an application such as the one demonstrated in this article.

Please, do not contact Ubisoft about this application. They have enough to worry about with all the sequels they are currently developing for recent hit games.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Matt Sollars
Web Developer
United States United States
I began programming on my Commodore 64 at around the age of 12. After migrating to DOS and then Windows, I decided to take on the Web. Several languages and platforms later, I have settled in with .NET nicely. I am currently the owner of a software consulting company and lead application developer for a learning-based technology consultation company.
 
The love of a finished application is usually at war with the desire to improve it as soon as it's released (they're never really finished).

Comments and Discussions

 
Generalgood PinmemberSavara20-May-09 7:28 
GeneralRe: good PinmemberMatt Sollars20-May-09 9:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141223.1 | Last Updated 23 May 2009
Article Copyright 2009 by Matt Sollars
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid