WPF 101 – Deal or no Deal
Code for this post can be found at
www.dashpoint.com/downloads/DealOrNoDealWPF.ZIP
Monday nights in the Paddock home are fun. At 8:00pm we watch Deal or No Deal. Right after that we watch “Heroes” (one of the best TV shows this season). One of things that is most fun about watching Deal or No Deal is the concept of the banker’s offer.
The basic concept of the game is this… There are 26 cases with amounts ranging from one cent to 1 million dollars. The player selects one case in advance which goes on a podium. The player then selects a pre specified number of cases in (n) number of rounds. The first round they are required to select 6 cases. The banker then makes an offer… The player then decides whether that offer is a good and then makes a decision to take the deal or slam down a little plexi glass cover yelling “No Deal!!!”. Then it starts again. This time the player must select 5 cases… then a bankers offer… decision… 4 cases…offer…3 cases… offer…2 cases… offer, and so on…..
The part I like and debate with my wife is the amount the deal should be. In general I am pretty good guessing the amount relatively closely. This comes from being a total geek and saying that the “Banker” is a dude looking at a computer screen which calculates an amount. So this week I got to thinking,”Hmm, I wonder what the algorithm is used to calculate the offers.” So I did what all good geeks do: consulted the Oracle (www.google.com) to see if anyone figured it out. I came across this blog topic
http://www.pearsonified.com/2006/03/deal_or_no_deal_the_real_deal.php
Basically this author’s premise is that the Deal or No Deal calculation is a mean: a basic average of the remaining cases not yet used up. Well now I was onto something….
Getting even geekier I decided I would write a program that would do these calculations to test this hypothesis. I probably could have done this with a simple Excel spreadsheet or quick and dirty macro but what fun would that be. I decided to write a small application where during the show I could mark off cases chosen and calculate the deal amount. What better way to do this that using WPF. I could closely mimic the game board and learn something in the process…
So here you have it… WPF--- Deal or No Deal
Creating the App
The first thing I did (after installing .NET 3.0 of course) was to create a new Windows Application (WPF) from the Visual Studio Designer.
Custom Classes
During the development of the game I created two custom classes. The first is a called BoardItem this class has two simple member variables. The amount attached to the item and whether it is still in play. The class second is called BoardButton. The board button inherits from the WPF Button class and has a single additional member variable called BoardItem which is a link to a board item. The code for these classes is as follows:
Public Class BoardItem
Public ItemValue As Decimal = 0.0
Public InPlay As Boolean = True
Sub New(ByVal ItemValue As Decimal)
Me.ItemValue = ItemValue
End Sub
End Class
Public Class BoardButton
Inherits Button
Public BoardItem As BoardItem
End Class
Creating the Game Board
The next step was to create a game board with 26 board items. In the interest of time (ITIOT) I decided to make this as simple as possible. I created an array with 2 columns and 13 rows (space for 26 items) I then populated the array with games pieces representing the correct amounts:
NOTE: All code except for the classes was added to the default window page created by Visual Studio.
Dim aBoardValues(1, 12) As BoardItem
Sub PopulateGameValues()
Me.aBoardValues(0, 0) = New BoardItem(0.01)
Me.aBoardValues(0, 1) = New BoardItem(1.0)
Me.aBoardValues(0, 2) = New BoardItem(5.0)
…
Me.aBoardValues(1, 10) = New BoardItem(500000)
Me.aBoardValues(1, 11) = New BoardItem(750000)
Me.aBoardValues(1, 12) = New BoardItem(1000000)
End Sub
After populating the array with the correct values I went to work populating my screen with the 26 buttons. Before showing the code to populate the game board you need to look at the XAML for the basic window:
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WPF Deal or No Deal" Width="600" Height="500">
<Grid Name="grdBoard" >
</Grid>
</Window>
This is 100% of the XAML for our window definition. By default Visual Studio creates a simple window with a grid control in it. I changed the name of the grid to “grdBoard” and some basic layout properties.
The only other XAML I added to this project was some basic style information. I added this to the App.XAML file created by Visual Studio. My App.XAML file looks like this:
<Application x:Class="App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml">
<Application.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Control.FontFamily" Value="Comic Sans MS"></Setter>
<Setter Property="Control.FontSize" Value="14"></Setter>
</Style>
</Application.Resources>
</Application>
The cool thing about this is that it applies font characteristics to the classes specified by the TargetType attribute. So for our application all buttons will be styled using the Comic Sans MS 14pt font.
So that’s it. That’s 100% of the XAML code for this project. Now back to our regularly scheduled program…
The next step was to dynamically generate the game board from our array of board items. The first set of code to look at is LoadGameBoard. The job this code performs is to add a number of rows and columns to the Grid control. The following snippet shows how to add rows and columns to a WPF grid.
For nColumnKount As Integer = 0 To Me.aBoardValues.GetUpperBound(0) + 1
Dim oNewCol As New ColumnDefinition
Me.grdBoard.ColumnDefinitions.Add(oNewCol)
Next
For nRowKount As Integer = 0 To Me.aBoardValues.GetUpperBound(1)
Dim oNewRow As New RowDefinition
oNewRow.Height = New System.Windows.GridLength(25)
Me.grdBoard.RowDefinitions.Add(New RowDefinition)
Next
After setup the playing field you can go to work populating that playing field with BoardButtons attached to the BoardItems contained in the array. The following code demonstrates the process of adding buttons to the playing grid.
For nColumnKount As Integer = 0 To Me.aBoardValues.GetUpperBound(0)
For nRowKount As Integer = 0 To Me.aBoardValues.GetUpperBound(1)
Dim oNewButton As BoardButton =
Me.GetGameButton(Me.aBoardValues(nColumnKount, nRowKount))
Me.grdBoard.Children.Add(oNewButton)
Grid.SetRow(oNewButton, nRowKount)
Grid.SetColumn(oNewButton, nColumnKount)
Next
Next
Basically this code calls the factory function GetGameButton which accepts a parameter of a GameItem. It then adds that button to the Grid via the Grids Children.Add() method. Finally it sets the Row and Column positions of that game piece via the Grid control’s shared methods SetRow() and SetColumn()
Let’s take a look at the GetGameButton function now. The job of this function is to generate the visual button class that will be shown on our gameboard. The code for generating a button is shown below:
Function GetGameButton(ByVal oBoardItem As BoardItem) As BoardButton
Dim oNewButton As New BoardButton
oNewButton.BoardItem = oBoardItem
Dim oButtonGrid As New Grid
Dim col1 As New ColumnDefinition
col1.Width = New GridLength(1, GridUnitType.Star)
oButtonGrid.ColumnDefinitions.Add(col1)
Dim col2 As New ColumnDefinition
col2.Width = New GridLength(1, GridUnitType.Star)
oButtonGrid.ColumnDefinitions.Add(col2)
oNewButton.Content = oButtonGrid
oNewButton.HorizontalContentAlignment = Windows.HorizontalAlignment.Stretch
Dim oDollarLabel As New Label
oDollarLabel.Content = "$"
oButtonGrid.Children.Add(oDollarLabel)
Dim oDataLabel As New Label
oDataLabel.Content = Me.ReturnFormattedDollarAmount(oBoardItem.ItemValue)
Grid.SetColumn(oDataLabel, 1)
oDataLabel.HorizontalContentAlignment = Windows.HorizontalAlignment.Right
oButtonGrid.Children.Add(oDataLabel)
oNewButton.Background = Me.oInplayBrush
AddHandler oNewButton.Click, AddressOf Me.ProcessGameButton
Return oNewButton
End Function
This code does a number of unique things, things VERY UNIQUE to WPF. Basically here’s the story with this code. I could have just specified the Content (aka Text) property of the button as a string formatted as currency. The problem I saw with this is that the gameboard for Deal or No Deal doesn’t look like that. Basically a game piece in Deal or No Deal shows a “$” symbol flush and the dollar amount flush right on the game piece. So how to do this in a button I wanted to keep using a button as they look good in WPF and they provide the interactivity that I am looking for (i.e. clicking does something)
This is where WPF is cool… I know from all the goofy Chris Anderson/Don Box demos I have seen over the last 3+ years I could simply add a grid control to a button control and WPF would take care of the magic.
So this is what I did. I fabricated a new grid control, added a 2 columns to that grid. Then I set the Content property of the Button to the grid. Now I have a button containing a grid. I can now add other controls to the cells in this grid. After creating my grid I created two labels one with the “$” symbol in it and the other with a formatted dollar amount in it.
There was one significant issue I had to overcome here. For some reason I couldn’t get the grid to align the 1st column left and the 2nd column right. This was because the Grid control didn’t size itself properly inside the button. So I pinged my friend Markus Egger (http://www.markusegger.com/Blog/) for some help. He figured out that the grid needed some sizing attributes set. The following code tells the columns to use the available space provided to the grid to size itself. This is much like setting the width property of an HTML <TD> tag to 100%
col1.Width = New GridLength(1, GridUnitType.Star)
Attempting to be WPF compliant (having cool UI<G>) I wanted to shade my buttons with a gradient. The following code assigned a gradient to the BackGround property of the button
oNewButton.Background = Me.oInplayBrush
I defined gradients member variables using the following code:
Dim oInplayBrush As New LinearGradientBrush(Colors.Gray, Colors.Yellow, 90)
Dim oPlayedBrush As New LinearGradientBrush(Colors.Black, Colors.LightYellow, 90)
Basically these gradients are faded at a 90 degree angle from Gray to Yellow and Black to Light Yellow respectively.
After adding the labels to the button I attached an event handler to the Click event of the button. This event calls the ProcessGameButton sub routine.
Process Game Button
The job of the process game button is pretty simple. When clicked a board button is turned on or off (i.e its InPlay status is changed). The event then calls the CalulationTest() subroutine which does a brute force trip through the array and calculates the proper mean (offer) for the game and displays it in another button:
Private Sub ProcessGameButton(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs)
With CType(sender, BoardButton)
If .BoardItem.InPlay = True Then
CType(sender, BoardButton).Background = Me.oPlayedBrush
CType(sender, BoardButton).BoardItem.InPlay = False
Else
CType(sender, BoardButton).Background = Me.oInplayBrush
CType(sender, BoardButton).BoardItem.InPlay = True
End If
End With
Me.CalculationTest()
End Sub
Sub CalculationTest()
Dim nRetVal As Decimal = 0
Dim nPiecesLeft As Integer = 0
For nColumnKount As Integer = 0 To Me.aBoardValues.GetUpperBound(0)
For nRowKount As Integer = 0 To Me.aBoardValues.GetUpperBound(1)
If Me.aBoardValues(nColumnKount, nRowKount).InPlay = True Then
nRetVal += Me.aBoardValues(nColumnKount, nRowKount).ItemValue
nPiecesLeft += 1
End If
Next
Next
Me.cmdDealAmount.Content = FormatCurrency(nRetVal / nPiecesLeft, 0)
End Sub
Code for this post can be found at:
www.dashpoint.com/downloads/DealOrNoDealWPF.ZIP
Now with all of this code you can run the game which looks like:
