In the first part, we saw how to recursively solve the Towers of Hanoi problem in C#. In this post I want to show a graphic view of the solution. This is a starting position with 7 discs:

This is how it looks when the problem is solved:

In between, the discs move with animation from pole to pole, as the solution dictates. Options include speeding up the process (with the slider, very useful), pausing the animation and resetting to the initial state. Here’s something in the middle:

The Poles
The poles are built as thick lines, with a gradient brush. They are set inside a Canvas, so their location can be set explicitly. Resizing the window causes everything to be resized appropriately. This neat trick is achieved with one of WPF’s useful Decorators – the ViewBox. The ViewBox effectively allows creation of a custom coordinate system that makes placing objects, calculating distances and whatnot easy:
- <Viewbox Stretch="Fill">
- <Canvas x:Name="_canvas" Background="White" Width="1200" Height="1200" >
- <Line Stroke="{StaticResource poleFill}" StrokeThickness="40" X1="200" Y1="120" X2="200" Y2="1200" />
- <Line Stroke="{StaticResource poleFill}" StrokeThickness="40" X1="600" Y1="120" X2="600" Y2="1200" />
- <Line Stroke="{StaticResource poleFill}" StrokeThickness="40" X1="1000" Y1="120" X2="1000" Y2="1200" />
- </Canvas>
- </Viewbox>
The ViewBox child should be set a Width and Height explicitly – this sets the coordinate system. In this case, 1200x1200 units. This means that no matter the actual Canvas size – its center point (e.g.) is always (600,600).
The Discs
Each disc is a simple user control that only adds one dependency property, Text, to allow customization of the written text on top of a rectangle. Here’s the basic XAML:
- <UserControl x:Class="TowersOfHanoi.DiscControl"
- d:DesignHeight="300" d:DesignWidth="300" x:Name="ctl">
- <UserControl.Resources>
- <LinearGradientBrush x:Key="discBrush" EndPoint="0,.5" SpreadMethod="Reflect">
- <GradientStop Color="DarkRed" Offset="0" />
- <GradientStop Color="Red" Offset="1" />
- </LinearGradientBrush>
- </UserControl.Resources>
- <Grid>
- <Rectangle Stroke="Black" StrokeThickness="2" Fill="{StaticResource discBrush}"
- RadiusX="10" RadiusY="15" />
- <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
- Text="{Binding Text, ElementName=ctl}" />
- </Grid>
- </UserControl>
What we have is a rounded Rectangle filled with a gradient brush. On top of that sits a TextBlock with its Text property bound to the Text property exposes by the user control.
The Application
The application is built around a semi-MVVM implementation. The HanoiViewModel class implements INotifyPropertyChanged and a bunch of commands (Solve, Reset, Pause) that the main window consumes. I will not describe MVVM here in detail, as this is available in so many sources. The implementation is very basic, so don’t expect too much with that – I was focusing on the graphical aspects…
Initialization
First, the discs must be created and placed in the correct position before a solving attempt can be made. The Init method is responsible for setting things up. This is the part that creates the discs and adds them to the Canvas:
- double width = 250, left = 200 - width / 2, bottom = 0;
- _poles = new PoleInfo[3];
- for(int i = 0; i < _vm.Discs; i++) {
- var disc = new DiscControl {
- Text = (_vm.Discs - i).ToString(),
- FontSize = 30,
- Width = width,
- Height = DiscHeight,
- Foreground = Brushes.White
- };
- _canvas.Children.Add(disc);
- Canvas.SetLeft(disc, left);
- Canvas.SetBottom(disc, bottom);
- width -= 10;
- bottom += DiscHeight;
- left += 5;
- _poles[0].Add(disc);
- }
Within a loop over all discs, a DiscControl is created and set up with the correct properties including is Width and Height. The Width is decremented by 10 units every iteration. Then the disc is added to the Canvas and placed in the correct location, that is also adjusted accordingly before the iteration ends. The last interesting detail here is an array of PoleInfo structures, that keep track of the discs on each pole. This is necessary when an animation should start for determining the moving disc and its target location in the target pole.
Animation
The animation that’s required here is a Path based animation. That is, an animation the follows a path laid out by a PathGeometry, which is the most powerful Geometry WPF provides, and can be used to create virtually any desired set of 2D curves. In this case, it should contain a PolyLineSegment that traces a route connecting 3 points of a rectangle – from some pole to another pole.
The basic geometry and the Storyboard that uses it is defined within the Resources collection of the main window:
- <PathGeometry x:Key="animGeometry">
- <PathFigure IsClosed="False" >
- <PolyLineSegment Points="150,300 150,450 30,450" />
- </PathFigure>
- </PathGeometry>
- <Storyboard x:Key="moveStoryboard" SpeedRatio="{Binding Speed}">
- <DoubleAnimationUsingPath Duration="0:0:3"
- Storyboard.TargetProperty="(Canvas.Left)"
- PathGeometry="{StaticResource animGeometry}" Source="X" />
- <DoubleAnimationUsingPath Duration="0:0:3"
- Storyboard.TargetProperty="(Canvas.Bottom)"
- PathGeometry="{StaticResource animGeometry}" Source="Y"/>
- </Storyboard>
We have 2 animations running simultaneously, animating the Canvas.Left and Canvas.Bottom properties. The former getting the X position of the point and the latter getting the Y.
When an animation should start, the PolyLineSegment is adjusted to the correct path based on the disc to be moved:
- private void MakeMove(HanoiMove move) {
- int from = move.From - 1, to = move.To - 1;
- var disc = _movingDisc = _poles[from].Top;
- var start = GetPositionInPole(disc, from);
- var end = GetPositionInPole(disc, to);
- var g = FindResource("animGeometry") as PathGeometry;
- g.Figures[0].StartPoint = start;
- // update geometry
- var poly = g.Figures[0].Segments[0] as PolyLineSegment;
- poly.Points[0] = new Point(start.X, 1100);
- poly.Points[1] = new Point(end.X, 1100);
- poly.Points[2] = end;
The GetPositionInPole helper method returns a Point that is the location of a given disc in a given pole.
Then the Storyboard.Completed event is registered with an event handler, to be notified of a finished move - to advance to the next move. Then the animation actually begins:
- EventHandler completed = null;
- completed = (s, e) => {
- _animation.Completed -= completed;
- if(_vm.Moves != null) {
- _poles[to].Add(disc);
- _poles[from].Remove();
- Canvas.SetLeft(disc, Canvas.GetLeft(disc));
- Canvas.SetBottom(disc, Canvas.GetBottom(disc));
- // next move
- if(_currentMoveEnum != null)
- if(_currentMoveEnum.MoveNext())
- MakeMove(_currentMoveEnum.Current);
- else {
- _vm.MovesDone();
- CommandManager.InvalidateRequerySuggested();
- }
-
- }
- };
- _animation.Completed += completed;
- _vm.MakeMove();
- // start a controllable animation
- _animation.Begin(disc, true);
The event handler uses a lambda expression that is first assigned to a variable. This is necessary, because part of the handling is unregistering from that same event. The last line of the preceding code actually starts the animation. Note the true parameter there; it’s essential if pausing the animation is required (as it is in our case). If the simpler Begin method is used, no call to Storyboard.Pause would have any effect.
Since the moves are provided as IEnumerable<HanoiMove>, a simple foreach is not possible, as we cannot block after an animation begins. So, _currentMoveEnum is actually using the GetEnumerator method (when initialized) and calls MoveNext and the Current property as foreach internally does – but only after a move is complete.
The are other parts of this app that may be of interest – just browse the source code. The above explanations should make it easier to follow.
In the next (and final) part, we’ll try to port this to Silverlight running on Windows Phone 7.