September 2006 - Posts

Well what can i say except, my apologies to the XAML team.  I was reviewing the XAML code from my last post and there you go, I forgot to set the HorizontalAlignment to Center which was present on the Boo code. After inserting the appropriate setter the problem went away. Here's the correct XAML code for the ColorChart:

<Page x:Class="ColorChart.Page1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Page1"
    >
    <Page.Resources>
        <Style x:Key="CCStyle" >
            <Setter Property="Control.BorderBrush" Value="LightGray"/>
            <Setter Property="Control.BorderThickness" Value="1"/>
            <Setter Property="Control.Margin" Value="3"/>
            <Setter Property="Control.HorizontalAlignment" Value="Center"/>
        </Style>
        <ItemsPanelTemplate x:Key="CCPanel">
            <UniformGrid Columns="6"></UniformGrid>
        </ItemsPanelTemplate>
        <DataTemplate x:Key="CCTemplate">
            <StackPanel>
                <Rectangle     
                    Width="100" 
                    Height="40" 
                    Fill="{Binding Path=Brush}" />
                <TextBlock 
                    FontSize="11" 
                    Text="{Binding Path=Name}" 
                    HorizontalAlignment="Center">
                </TextBlock>
            </StackPanel>
        </DataTemplate>
    </Page.Resources>
    <ListBox 
        Name="listBox1" 
        SelectionMode="Single" 
        ItemsPanel="{StaticResource CCPanel}" 
        ItemTemplate="{StaticResource CCTemplate}" 
        ItemContainerStyle="{StaticResource CCStyle}"/>
</Page>

Now here's the result:

Color Chart XBAP Demo Updated
Posted by smash | with no comments
Filed under:

I was reading with great interest Keith's posts about creating a color chart in ASP.NET and decided to roll out a similar WPF application in pure code first - in Boo ofcourse -  and in XAML second. The former will be a Windows application and the latter will be an XBAP application so I can run it inside a browser - just like Keith's demo. 

The motivation for this came about when I realized that the color chart can easily be rendered using the WPF ListBox.  The trick was to set the ItemsPanel property of the ListBox to a template containing a WPF UniformGrid.

Coding the windows version in Boo was a breeze.  All in all it took only about 20 minutes to get it up and running since Keith had mapped the whole thing already. I used Keith's original method of using Reflection to get the list of predefined colors and adapted it for WPF. 

    class ColorService:
	static def GetBrushes() as System.Collections.IEnumerable:
		for propertyInfo as PropertyInfo in typeof(Brushes).GetProperties():
			memberInfo = cast(MemberInfo, propertyInfo)
			brush = cast(Brush,propertyInfo.GetValue(null,null))
			yield NamedBrush(memberInfo.Name, brush)
			
	class NamedBrush:
		[getter(Name)] _name as string
		[getter(Brush)] _brush as Brush
		
		def constructor(name as string, brush as Brush):
			_name = name
			_brush = brush
	
		override def ToString():
			return _name

Here's the c# version:

   
    public class ColorService
    {
        static public IEnumerable GetBrushes()
        {
            foreach (PropertyInfo propertyInfo in typeof(Brushes).GetProperties())
            {
                MemberInfo memberInfo = (MemberInfo)propertyInfo;
                Brush brushValue = (Brush)propertyInfo.GetValue(null, null);
                yield return new NamedBrush(memberInfo.Name, brushValue);
            }
        }

        public class NamedBrush
        {
            public NamedBrush(string name, Brush brush)
            {
                _name = name;
                _brush = brush;
            }

            public string Name
            {
                get { return _name; }
            }

            public Brush Brush
            {
                get { return _brush; }
            }

            public override string ToString()
            {
                return _name;
            }

            string _name;
            Brush _brush;
        }
    }

Here's how the windows app looks like:

Color Chart Demo

Now converting the app into XBAP took quite some time to finish since I'm not well versed in XAML yet. I had to check the Boo layout code repeatedly to get the XAML finally working.  Heres the XAML layout code:

<Page x:Class="ColorChart.Page1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Page1"
    >
    <Page.Resources>
        <Style x:Key="CCStyle" >
            <Setter Property="Control.BorderBrush" Value="LightGray"/>
            <Setter Property="Control.BorderThickness" Value="1"/>
            <Setter Property="Control.Margin" Value="3"/>
        </Style>
        <ItemsPanelTemplate x:Key="CCPanel">
            <UniformGrid Columns="6"></UniformGrid>
        </ItemsPanelTemplate>
        <DataTemplate x:Key="CCTemplate">
            <StackPanel>
                <Rectangle     
                    Width="100" 
                    Height="40" 
                    Fill="{Binding Path=Brush}" 
                    HorizontalAlignment="Left"/>
                <TextBlock 
                    FontSize="11" 
                    Text="{Binding Path=Name}" 
                    HorizontalAlignment="Center">
                </TextBlock>
            </StackPanel>
        </DataTemplate>
    </Page.Resources>
    <ListBox 
        Name="listBox1" 
        SelectionMode="Single" 
        ItemsPanel="{StaticResource CCPanel}" 
        ItemTemplate="{StaticResource CCTemplate}" 
        ItemContainerStyle="{StaticResource CCStyle}"/>
</Page>

This is the c# code-behind for the XAML above:

namespace ColorChart
{
    public partial class Page1 : System.Windows.Controls.Page
    {
        public Page1()
        {
            InitializeComponent();
            this.listBox1.ItemsSource = ColorService.GetBrushes();
        }
    }
}

And here's the layout code in Boo which does thesame thing as the XAML above:

class CCStyle(Style):
    def constructor():
        s1 = Setter(ListBoxItem.BorderThicknessProperty, Thickness(1))
        s2 = Setter(ListBoxItem.MarginProperty, Thickness(3))
        s3 = Setter(ListBoxItem.BorderBrushProperty, Brushes.LightGray)
        s4 = Setter(ListBoxItem.HorizontalAlignmentProperty, HorizontalAlignment.Center)
        self.Setters.Add(s1)
        self.Setters.Add(s2)
        self.Setters.Add(s3)
        self.Setters.Add(s4)

class CCPanel(ItemsPanelTemplate):
    def constructor():
        root = FrameworkElementFactory(UniformGrid)
        root.SetValue(UniformGrid.ColumnsProperty, 6)
        self.VisualTree = root

class CCTemplate(DataTemplate):
    def constructor():
        root = FrameworkElementFactory(StackPanel)
        ch1 = FrameworkElementFactory(Rectangle)
        ch1.SetValue(Rectangle.WidthProperty, 100.0)
        ch1.SetValue(Rectangle.HeightProperty, 40.0)
        ch1.SetValue(Rectangle.FillProperty, Binding("Brush"))
        ch2 = FrameworkElementFactory(TextBlock)
        ch2.SetValue(TextBlock.TextProperty, Binding("Name"))
        ch2.SetValue(TextBlock.HorizontalAlignmentProperty, HorizontalAlignment.Center)
        root.AppendChild(ch1)
        root.AppendChild(ch2)
        self.VisualTree = root
        
class ColorChartView(ListBox):
    def constructor():
        ItemContainerStyle = CCStyle()
        ItemsPanel = CCPanel()
        ItemTemplate = CCTemplate()
        ItemsSource = ColorService.GetBrushes()        

An XBAP application needs to be compiled and then published to an IIS server first.  After you have done that open your IE browser - or Firefox browser if you have the  IE Tab extension installed - and type in the url of your application.  In my case I typed in http://localhost/winfx/colorchart.xbap and here's what came out:

Color Chart XBAP Demo

Now that was neat. The keyboard navigation and mouse selection works just like in the windows version. But if you will look at it more closely you'll notice that there's something different about the XAML rendered view.

XAML by the way is tokenized into BAML during compilation and is embedded as a resource inside the executable file. Maybe the XAML team needs more time to tweak their engine.

Posted by smash | 3 comment(s)
Filed under: , , , ,

[Edited: I changed the code to use one template this time to emphasize the proper use of styling. I also moved the the definition for the style to a class to make the code cleaner.]

After only a couple of days  hacking away at WPF using the SDK docs and Google as reference, I may finally be gaining a toehold into the vast and exciting world of Avalon.  So many things about WPF are new e.g.  FrameworkElementFactory, DependencyProperty, DataTemplate and the list goes on.  If you are not careful you'll loose yourself following the inheritance heirarchy. 

I finally gave up doing that and decided that the best way to learn WPF was to leave all those fancy stuff first - like transformations, animation, 3D etc. - and work on the normal stuff like take some data from a service object and display the  information on the screen.  So to get the ball rolling, I whipped up a couple of rudimentary classes - Customer and CustomerService.  After spending half a day pounding the keyboard, studying compiler error messages, checking the docs, googling, cursing - here's what i managed to do with the ListBox:

ListBox Demo

Although styling and templating are indisputably easier to accomplish in XAML, I believe programmers must also know how to do these things in pure code. Knowing how things are done under the hood will prove invaluable in the long haul. The days when Web developers would smirk at the sight of our user interfaces are about to end.

import System
import System.Windows
import System.Windows.Controls
import System.Windows.Controls.Primitives
import System.Windows.Data
import System.Windows.Documents
import System.Windows.Input
import System.Windows.Markup
import System.Windows.Media
import System.Windows.Navigation
import System.Windows.Shapes
import System.Globalization

class Customer:
	[property(Name)] _name = ""
	[property(Address)] _addr = ""
	
	def constructor(name as string, addr as string):
		_name = name
		_addr = addr
	
	override def ToString() as string:
		return _name

class CustomerService:
	static def GetCustomers():
		yield Customer("brent","davao")
		yield Customer("manny","gsc")
		yield Customer("robert","cagayan")
		yield Customer("george","cebu")
		yield Customer("thomas","iloilo")
		yield Customer("lelanie","tagum")
		yield Customer("bong","digos")
		yield Customer("hazel","bicol")		
			
class CustomerTemplate(DataTemplate):
	def constructor():
		super(Customer)
		panel = FrameworkElementFactory(StackPanel)		
		child1 = FrameworkElementFactory(TextBlock)
		child1.SetValue(TextBlock.TextProperty, Binding("Name"))		
		child1.SetValue(TextBlock.FontSizeProperty, 20.0)
		child2 = FrameworkElementFactory(Label)
		child2.SetValue(Label.ContentProperty, Binding("Address"))		
		panel.AppendChild(child1)
		panel.AppendChild(child2)	
		self.VisualTree = panel
		
class CustomerStyle(Style):
	def constructor():
		super(ListBoxItem)
		setter1 = Setter(ListBoxItem.HorizontalContentAlignmentProperty, HorizontalAlignment.Stretch)
		setter2 = Setter(ListBoxItem.MarginProperty, Thickness(2))
		setter3 = Setter(ListBoxItem.BorderThicknessProperty, Thickness(1))
		setter4 = Setter(ListBox.BorderBrushProperty, Brushes.Gold)
		setter5 = Setter(ListBox.BackgroundProperty, Brushes.Bisque)
		setter6 = Setter(TextBlock.ForegroundProperty, Brushes.Orange)
		self.Setters.Add(setter1)
		self.Setters.Add(setter2)
		self.Setters.Add(setter3)
		self.Setters.Add(setter4)
		self.Setters.Add(setter5)
		self.Setters.Add(setter6)


class Home(Grid):
	def constructor():	
		self.ColumnDefinitions.Add(ColumnDefinition(Width:GridLength(200.0)))
		self.ColumnDefinitions.Add(ColumnDefinition(Width:GridLength(10.0)))
		self.ColumnDefinitions.Add(ColumnDefinition(Width:GridLength(400.0)))
		self.RowDefinitions.Add(RowDefinition(Height:GridLength(20.0)))
		self.RowDefinitions.Add(RowDefinition(Height:GridLength(100.0)))
		self.RowDefinitions.Add(RowDefinition(Height:GridLength(10.0)))
		self.RowDefinitions.Add(RowDefinition(Height:GridLength(100.0)))
		self.RowDefinitions.Add(RowDefinition(Height:GridLength(10.0)))
		self.RowDefinitions.Add(RowDefinition(Height:GridLength(200.0)))
		self.RowDefinitions.Add(RowDefinition(Height:GridLength(10.0)))
		
		regularListBox = ListBox()
		regularListBox.ItemsSource = CustomerService.GetCustomers()
		Grid.SetColumn(regularListBox,2)
		Grid.SetRow(regularListBox,1)
		
		templatedListBox = ListBox()
		templatedListBox.ItemTemplate = CustomerTemplate()	
		templatedListBox.ItemsSource = CustomerService.GetCustomers()
		Grid.SetColumn(templatedListBox,2)
		Grid.SetRow(templatedListBox,3)
		
		styledListBox = ListBox()			
		styledListBox.ItemContainerStyle = CustomerStyle()
		styledListBox.ItemTemplate = CustomerTemplate()
		styledListBox.ItemsSource = CustomerService.GetCustomers()						
		Grid.SetColumn(styledListBox,2)
		Grid.SetRow(styledListBox,5)
		
		text1 = TextBlock(Run("A Regular ListBox "))
		text1.FontSize = 14
		text1.HorizontalAlignment = HorizontalAlignment.Right
		text2 = TextBlock(Run("A Templated ListBox "))
		text2.FontSize = 14
		text2.HorizontalAlignment = HorizontalAlignment.Right
		text3 = TextBlock(Run("A Styled Templated ListBox "))
		text3.FontSize = 14
		text3.HorizontalAlignment = HorizontalAlignment.Right
		Grid.SetRow(text1,1)
		Grid.SetRow(text2,3)
		Grid.SetRow(text3,5)	
			
		self.Children.Add(regularListBox)
		self.Children.Add(templatedListBox)
		self.Children.Add(styledListBox)
		self.Children.Add(text1)
		self.Children.Add(text2)
		self.Children.Add(text3)				

[STAThread]
def Main():
	# define main window
	win = Window(Title:"ListBox Demo")	
	win.Content = Home()
	# define application
	app = Application()
	app.Startup += { win.Show() }
	app.Run()

Posted by smash | with no comments
Filed under: , ,

Learning the ins and outs of WPF is like climbing Mount Apo for the first time - minus a guide. Oh yes, judging by the sample applications you see on the Internet, Microsoft may have succeeded in creating a very compelling framework that will change the face of computing as we know it today.  The engineers who dared build this thing  has probably thrown in every piece of  computing gobbledygook ever imagined - including the proverbial kitchen sink.

That kitchen sink may very well be the new markup language called XAML, which those bloody engineers lovingly pronounce as zammel.  The fact that it is being hyped in every WPF literature as the de facto standard in writing the next generation UIs leaves me utterly breathless.  WPF is all about this new markup (XAML) and code-behind (C# or VB.NET) - similar to the combo made popular in WebForms.  

Whatever similarities between WebForms and WPF abruptly ends there. Even a six year .NET developer won't have any comfort zones if that's any consolation.  I once had this illusion that since WPF is basically .NET, I will be hitting the ground running by the time I decided to study WPF.  It actually felt more like being hit by a runaway truck.  Microsoft did  err when they labeled WinFX as .NET3. They should have concatenated a higher number -  like .NET5 perhaps. 

There are a thousand ways to skin a cat .  XAML is first translated to raw C# or VB.NET code during the compilation process.  This gives those who are zammel challenged like me, a glimmer of hope that perhaps learning the new mark up can be postponed until a much much later date.  That old aphorism may still apply to programming WPF applications.  Hope indeed has an uncanny way of manifesting itself even during the bleakest of moments!

If you've been watching the events that have been taking place in the .NET sphere of late, you'll probably notice that there seems to be a language renaissance of sorts.  Phython, Ruby, PHP, Smalltalk and other obscure languages, new and old, are in the process or have been successfully ported to .NET.  What was once considered a second rate Java VM copycat is fast becoming the United Nations of programming languages.

Among these new .NET languages, Boo is my favorite.  It has a very compact syntax, yet it does not compromise anything. It belongs to a new breed of smart programming languages designed specifically for .NET that has been slowly emerging from the underground.  The value of Boo becomes evident when you are prototyping something.  And that is exactly what most of us will be doing with WPF between now and next year, writing prototype applications to test the waters so to speak. 

To start writing WPF applications sans XAML with Boo (or even c#), start by creating a new Boo WinForm application and then delete the dummy WinForm file.  Delete all references except Boo.Lang.Useful (you dont need this in c#)  and System, then add references to PresentationCore, PresentationFramework and WindowsBase.  Add a new Empty Boo File then type the following code like so:

namespace Boo.WinFx
import System
import System.Windows
import System.Windows.Navigation

[STAThread]
def Main():
	# create a window
	win = NavigationWindow()
	# create an application
	app = Application()	
	# the first window to be shown becomes the main window
	win.Show()
	# fire the application
	app.Run()	

Empty NavigationWindowHit F5 to run this barebones WinFx Navigation Application. Notice that we did not enclose the Main function inside a class, yet the program did execute as expected.  This is one of the facets of Boo that makes it special.  It adopts very well to procedural style of programming. 

There are two types of windows we can use in our applications - the Window and the NavigationWindow. The Window class looks like the old WinForm but that's where their similarities end. The NavigationWindow class inherits from the Window class and extends it with content navigation support.  With it we now can create desktop applications that mimic the fuctionality of web applications, without really trying. 

Let's say I would like to build an application that displays a button on the first page. If I click on the button, the app will then navigate to the second page. To do this modify the original code like so:

namespace Boo.WinFx
import System
import System.Windows
import System.Windows.Controls
import System.Windows.Navigation

[STAThread]
def Main():
	# create a window host
	win = NavigationWindow()	
	# create a textblock
	para = TextBlock(Text:"Hello World!!!", FontSize:36)
	para.VerticalAlignment = VerticalAlignment.Center
	para.HorizontalAlignment =HorizontalAlignment.Center	
	# create a button 
	button = Button(Content:"Click Me", Height:30, Width:100)
	# display the textblock when the button is clicked
	button.Click += { win.Navigate(para) }
	# display the button first
	win.Navigate(button)
	# create an application host
	app = Application()
	# show the window
	win.Show()
	# fire the application
	app.Run()

first page

second page
Posted by smash | with no comments
Filed under: , ,

i read that phrase in an XML book i bought five long years ago.  it was an amazing declaration that stuck in my head thru all these years.  frankly, i never thought it would happen. but no sooner did i put down that bloody book, MSDN  was showcasing code snippets scripted in the now familiar XML format.  the MSDN article did have an amazing  punchline too - those same scripts were supposed to run on the desktop or in a browser with nary a tweak.  needless to say, i nearly fell off my chair.  

about a week or so, MS released the .NET Framework 3.0 RC1.  today i finally got the thing installed on my PC after almost a week of trying.  it's quite comforting to know that i was not the only one who was scrambling to get this new piece of software with a kickass code name - WinFx. 

Petzolds Clock in XamlPadRC1 brings to the table a simple yet powerful tool in crafting XAML scripts - XamlPad.  unlike the trusty old NotePad which not a few web designers loved to hate, you will probably thank the heavens for this one.  its simplicity is actually a boon to learning this new markup language. 

to see what XamlPad is really all about, i dowloaded Charles Petzold's sample AllXamlClock from his blog and copy pasted it to XamlPad's editor.  oh yes, the bloody thing works.

Petzold's clock is really amazing because there's no C# code-behind to control the movement of the hands, unlike other clock samples you may find on the net.  could this be the fulfillment of that prophecy, that in near future, XML will become a dominant programming language? 

oh well, Petzold's clock is really awesome, wouldn't it be nice to be able to use it on my good ol' Boo apps?  why not, so after a couple of hours tinkering, i got Petzold's clock working on an old school Boo form.

Petzolds Clock side-by-side

Posted by smash | 5 comment(s)
Filed under: ,
when the subject of TDD is brought up, the first thing that comes to my mind is the classic Bowling Game problem. i must admit to be a little bit surprised that the solution the two Bobs came up with did not even make use of a Frame class. i really wonder how the thing would look like if i wrote a solution that is anchored around a Frame class.

to get things going start SharpDevelop and create a new Boo class library project. delete the dummy class and then add a reference to nunit.framework.  create a test called TestGame as shown below.

namespace MyBowlingGame

import System
import NUnit.Framework

[TestFixture]
class TestGame:
    g as Game

    [SetUp]
    def Setup():
        g = Game()

    [Test]
    def TestOneBadThrow():
        g.AddThrow(0)
        assert g.Score() == 0

this wont compile since the Game class doesnt exist yet. create a new class called Game and define the methods Score() and AddThrow() as shown below.

namespace MyBowlingGame

import System
class Game:
    def Score():
        return 0
		
    def AddThrow(pins as int):
        pass

compile the project and run the test. green means success while red means failure. add another test for a single throw.

	
    [Test]
    def TestOneGoodThrow():
        g.AddThrow(7)
        assert g.Score() == 7

now this will also fail since the method Score() always returns a zero.  let me spell out the mechanics of bowling before proceeding. 

a typical bowling game is divided into ten frames each where the bowler is given two chances or throws to clear the ten pins in a frame.  if the bowler clears all the ten pins on his first throw - a strike in bowling parlance - his score for the frame will be 10 plus the combined score of his next two throws in the succeeding frame or frames as bonus.  if the bowler manages to cleanup the remainder of the pins on his second throw - called a spare - he gets a 10 for the frame plus the score of his first throw in  the succedding frame as bonus.  if the bowler doesnt cleanup on his second throw - an open frame - his score for the frame would be the combined result of his two throws on that frame.  during the 10th frame, if a bowler makes a strike he is automatically given two more throws for the bonus points.  if the bowler manages a spare on the 10th frame he gets an extra throw for the bonus.  the maximum score in bowling game is 300 and the maximum number of throws is 22.

the preceding paragraph says that a bowling game is divided into frames. for starters, add a single frame to the Game class.

class Game:
    frame = Frame()
	
    def Score():
        return 0
		
    def AddThrow(pins as int):
        pass

from the bowling game description one can surmise that a Frame has a score property which is the sum of all the pins cleared as a result of a throw or throws.

class Frame:
    _score = 0
    
    def Score():
        return _score
        
    def AddThrow(pins as int):
        _score += pins	

it sure looks like the functionality needed by the Game class are in the Frame class already, so adjust the Game class to just defer to the Frame class for tracking scores.

class Game:
    frame = Frame()
	
    def Score():
        return frame.Score()
		
    def AddThrow(pins as int):
        frame.AddThrow(pins)

let's prove the changes by writing a test for single open frame.

    [Test]
    def TestOpenFrame():
        g.AddThrow(4)
        g.AddThrow(3)
        assert g.Score() == 7

no problem with this test. explore further by adding another open frame to preceding test.

    [Test]
    def TestTwoOpenFrames():
        g.AddThrow(4)
        g.AddThrow(3)
        g.AddThrow(6)
        g.AddThrow(2)
        assert g.Score() == 15

i have a feeling the solution will hold up for open frames. what about validating the score of a particular frame? create a new test as shown below, then insert the ScoreInFrame() method to the Game class.

    [Test]
    def TestTwoOpenFrames():
        g.AddThrow(4)
        g.AddThrow(3)
        g.AddThrow(6)
        g.AddThrow(2)
        assert g.Score() == 15
        assert g.ScoreInFrame(1) == 7
        assert g.ScoreInFrame(2) == 8

class Game:
    frame = Frame()
    
    def Score():
        return frame.Score()

    def ScoreInFrame(index as int):
        return frame.Score()
        
    def AddThrow(pins as int):
        frame.AddThrow(pins)

the test did fail as expected since there's only one Frame in the Game class so far . now get rid of the single frame field and replace it with a list or array of 10 frames. i'll settle for a list, which is populated with 10 Frame objects. to keep track of the current frame, add the _index field to the Game class. modify the Score() method to return the aggregate score from all the frames. [NOTE] we really could have used a generic list of Frame objects here but unfortunately Boo doesnt have any syntax for generics yet - but its coming real soon says Rodrigo Oliveira, its creator. so for convenience add the GetFrame() method to extract the Frame object from the list.

class Game:
    _frames = []
    _index = 0

    def constructor():
        for i in range(10):
            _frames.Add(Frame())
    
    def Score():
        score = 0
        for frame as Frame in _frames:
            score += frame.Score()
        return score

    def ScoreInFrame(index as int):
        return GetFrame(index-1).Score()
        
    def AddThrow(pins as int):
        GetFrame(_index).AddThrow(pins)

    def GetFrame(index as int):
        return _frames[index] as Frame

the test still failed because the code doesnt do anything to advance _index to the next frame after the player has rolled the first two balls. modify the AddThrow method of the Game class as shown below:

        
    def AddThrow(pins as int):
        frame = GetFrame(_index)
        frame.AddThrow(pins)
        _index++ if frame.IsClosed()

or the shortcut version:

    def AddThrow(pins as int):
        (frame = GetFrame(_index)).AddThrow(pins)
        _index++ if frame.IsClosed()

the preceding code says that the value of _index is incremented only if the current frame is already closed i.e. two balls for the frame had already been been rolled. so the Frame class should also track the number of rolls. define the IsClosed() method of the Frame class as shown below.

class Frame:
    _rolls = 0
    _score = 0
    
    def Score():
        return _score
        
    def AddThrow(pins as int):
        _score += pins
        _rolls++
        
    def IsClosed():
        return _rolls == 2

the test succeeds this time. the solution looks very promising. ok lets get some strike on the board by creating the test below.

    [Test]
    def TestStrikeAndOpenFrame():
        g.AddThrow(10)
        g.AddThrow(4)
        g.AddThrow(3)
        assert g.ScoreInFrame(1) == 17
        assert g.ScoreInFrame(2) == 7
        assert g.Score() == 24

again the test fails, so its time for some sleuthing work. lets see, the player makes a strike on the first throw - this effectively closes the  frame since there are no more pins left standing. the IsClosed() method should be revised to include a check for a strike condition i.e.  when a player scores a 10 on the first throw. this means the Frame class should also keep track of the player's score on the first throw for it to be able to tell whether it's a strike  or not. revise the Frame class as shown below.

class Frame:
    _rolls = 0
    _score = 0
    _firstThrow = 0
    
    def Score():
        return _score
        
    def AddThrow(pins as int):
        _firstThrow = pins if _rolls == 0
        _score += pins
        _rolls++
        
    def IsClosed():
        return IsStrike() or _rolls == 2

    def IsStrike():
        return _firstThrow == 10

the test still failed. thats because the bonus pins for the strike frame are not being added to it yet. lets review our Game class.

class Game:
    _frames = []
    _index = 0

    def constructor():
        for i in range(10):
            _frames.Add(Frame())
    
    def Score():
        score = 0
        for frame as Frame in _frames:
            score += frame.Score()
        return score

    def ScoreInFrame(index as int):
        return GetFrame(index-1).Score()
        
    def AddThrow(pins as int):             
        (frame = GetFrame(_index)).AddThrow(pins)
        _index++ if frame.IsClosed()
        
    def GetFrame(index as int) as Frame:
        return _frames[index] as Frame

inside the AddThrow() method of the Game class, the pins are only being added to the score of the current frame. the pins should also be added to the score of the previous strike frame.

        
    def AddThrow(pins as int):             
        GetFrame(_index-1).AddThrow(pins) if _index>0
        (frame = GetFrame(_index)).AddThrow(pins)
        _index++ if frame.IsClosed()

the result of running the test is shown below. the strike test was hurdled but  the open frames test failed.  its quite evident that the code patch above introduced some side effects which led to the failure of the open frames test again.

Image Hosted by ImageShack.us

the problem could be in the behavior of the AddThrow() method of the Frame class. either modify it to handle the bonus points from the free balls or put this functionality in some other method. i'm more inclined to follow the latter approach so as to avoid any further complication. recall that in the event of a strike the next two throws must be credited to the strike frame, no more no less. this means the Frame class should track the number of bonus throws credited to it.

class Frame:
    _rolls = 0
    _score = 0
    _firstThrow = 0
    _freeBall = 0
    
    def Score():
        return _score
        
    def AddThrow(pins as int):
        _firstThrow = pins if _rolls == 0
        _score += pins
        _rolls++

   def AddBonus(pins as int):
        if IsStrike() and _freeBall<2:
            _score += pins
            _freeBall++
        
    def IsClosed():
        return IsStrike() or _rolls == 2

    def IsStrike():
        return _firstThrow == 10

modify the AddThrow() method of the Game class to use the new AddBonus() method of the Frame class to handle the bonus pins.

  def AddThrow(pins as int):                    
        GetFrame(_index-1).AddBonus(pins) if _index>0
        (frame = GetFrame(_index)).AddThrow(pins)
        _index++ if frame.IsClosed()

the open frames test is now cleared. what about two consecutive strikes followed by an open frame.

    [Test]        
    def TestTwoStrikesAndOpenFrame():
        g.AddThrow(10)
        g.AddThrow(10)
        g.AddThrow(4)
        g.AddThrow(3)
        assert g.ScoreInFrame(1) == 24
        assert g.ScoreInFrame(2) == 17
        assert g.ScoreInFrame(3) == 7
        assert g.Score() == 48

Image Hosted by ImageShack.us

the preceding test  fails, but if 24 is replaced by 20 the test will pass. it seems the result of the 2nd free ball is not added to the strike frame. by the 3rd throw, the game is already in its 3rd frame. to reference the 1st frame we simply subtract 2 from _index.

    def AddThrow(pins as int):                 
        GetFrame(_index-2).AddBonus(pins) if _index>1
        GetFrame(_index-1).AddBonus(pins) if _index>0
        (frame = GetFrame(_index)).AddThrow(pins)
        _index++ if frame.IsClosed()

solved. so far, most of the tests are limited to 3 frames or less. i think now is a good time to make a test for a perfect game.  in bowling, a perfect game happens if the bowler makes 12 consecutive strikes - where the last 2 throws are free balls for the 10th frame.

    [Test]
    def TestPerfectGame():
        for i in range(12):
            g.AddThrow(10)    
        assert g.Score() == 300

NUnit reports an IndexOutOfRangeException. some range checking is in order: 

    def AddThrow(pins as int):                 
        GetFrame(_index-2).AddBonus(pins) if _index>1
        GetFrame(_index-1).AddBonus(pins) if _index>0
        if _index<10:
            (frame = GetFrame(_index)).AddThrow(pins)
            _index++ if frame.IsClosed()

Image Hosted by ImageShack.us

that was annoying.  so far the tests prove that the code can handle open frames and strikes, what about spares?  a spare happens if the player cleans up on his second throw.  this means the Frame class must also track the score of the second throw so it can check for a spare. in the event of a spare the next throw is added to the spare frame as bonus. these translates to the changes shown below.

class Frame:
    _rolls = 0
    _score = 0
    _firstThrow = 0
    _secondThrow = 0
    _freeBall = 0
    
    def Score():
        return _score
        
    def AddThrow(pins as int):
        _firstThrow = pins if _rolls == 0
        _secondThrow = pins if _rolls == 1
        _score += pins
        _rolls++

   def AddBonus(pins as int):
        if (IsStrike() and _freeBall<2) or (IsSpare() and _freeBall<1):
            _score += pins
            _freeBall++
        
    def IsClosed():
        return IsStrike() or _rolls == 2

    def IsStrike():
        return _firstThrow == 10

    def IsSpare():
        return (_firstThrow<10) and (_firstThrow+_secondThrow==10)

with the modifications above in place, create a test for spare.

    [Test]
    def TestSpareAndOpenFrame():
        g.AddThrow(6)
        g.AddThrow(4) 
        g.AddThrow(3)
        g.AddThrow(5)
        assert g.ScoreInFrame(1) == 13
        assert g.ScoreInFrame(2) == 8
        assert g.Score() == 21

passed that one. the bowling game article from ObjectMentor included a test for complete game. let's see if our code can pass this one.

    [Test]
    def TestSampleGame():
        g.AddThrow(1)
        g.AddThrow(4)
        g.AddThrow(4)
        g.AddThrow(5)
        g.AddThrow(6)
        g.AddThrow(4)
        g.AddThrow(5)
        g.AddThrow(5)
        g.AddThrow(10)
        g.AddThrow(0)
        g.AddThrow(1)
        g.AddThrow(7)
        g.AddThrow(3)
        g.AddThrow(6)
        g.AddThrow(4)
        g.AddThrow(10)
        g.AddThrow(2)
        g.AddThrow(8)
        g.AddThrow(6)		
        assert g.Score() == 133

Image Hosted by ImageShack.us

no problem with that either.

namespace MyBowlingGame

import System

class Game:
    _frames = []
    _index = 0

    def constructor():
        for i in range(10):
            _frames.Add(Frame())
    
    def Score():
        score = 0
        for frame as Frame in _frames:
            score += frame.Score()
        return score

    def ScoreInFrame(index as int):
        return GetFrame(index-1).Score()
        
    def AddThrow(pins as int):                 
        GetFrame(_index-2).AddBonus(pins) if _index>1
        GetFrame(_index-1).AddBonus(pins) if _index>0
        if _index<10:
            (frame = GetFrame(_index)).AddThrow(pins)
            _index++ if frame.IsClosed()
        
    def GetFrame(index as int) as Frame:
        return _frames[index] as Frame 

class Frame:
    _rolls = 0
    _score = 0
    _firstThrow = 0
    _secondThrow = 0
    _freeBall = 0
    
    def Score():
        return _score
        
    def AddThrow(pins as int):
        _firstThrow = pins if _rolls == 0
        _secondThrow = pins if _rolls == 1
        _score += pins 
        _rolls++ 
        
    def AddBonus(pins as int):
        if (IsStrike() and _freeBall<2) or (IsSpare() and _freeBall<1):
            _score += pins
            _freeBall++
            
    def IsClosed():
        return IsStrike() or _rolls == 2

    def IsStrike():
        return _firstThrow == 10
        
    def IsSpare():
        return (_firstThrow<10) and (_firstThrow+_secondThrow==10)
Posted by smash | with no comments
Filed under: , ,