Introduction
From wiki http://en.wikipedia.org/wiki/Hanoi_tower
The
Tower of Hanoi (also called the
Tower of Brahma or
Lucas' Tower,
[1] and sometimes pluralised) is a
mathematical game or
puzzle. It consists of three rods, and a number of disks of different sizes which can slide onto any rod. The puzzle starts with the disks in a neat stack in ascending order of size on one rod, the smallest at the top, thus making a conical shape.
The objective of the puzzle is to move the entire stack to another rod, obeying the following rules:
- Only one disk must be moved at a time.
- Each move consists of taking the upper disk from one of the rods and sliding it onto another rod, on top of the other disks that may already be present on that rod.
- No disk may be placed on top of a smaller disk.
With three disks, the puzzle can be solved in seven moves.
This article is not talking about the algorithm but the real
game. This is WPF based and present a friendly GUI and control. You can use
keyboard to move and drop disks. Let’s see how fast you can solve the Hanoi
tower. I found that we already had some Hanoi projects here in past 8 years implemented
by C++, VB.NET etc, however, this is the first WPF version and it even contains
human voice! (TTS may not work in OS under Windows Vista) J
Have fun.
Background
I am interest in the 2D/Animation/Movie part. WPF structure is totally different from C# Winform, I encountered many problems when I try to apply my winform experience
on WPF application. They are:
1. Where is the Left, Top properties?
How can I move a form control in WPF?
We need to use
Canvas.SetLeft/SetTop methods, Canvas is kind of root panel which supports
absolutely location for controls (Elements).
2. How to add an image to an Image
control programmingly?
In WPF, it calls uri. We need to set
the image uri to Image.Source property.
3. Dispatcher Timer vs Timer
Dispatcher Timer, in a word, it
can invoke UI Controls in the same thread. But original Timer can’t.
Using the code
The code has been refactored from the original mess up. So I will show some code points here since the source code is simple to read. I think...
I create a class to describe the Tower which contains disks.
Here I use Stack<int> to simulate it. Int represents the disk size.
Stack<int> Tower1 = new Stack<int>();
To move the disk between two columns, I compare the int numbers and use Stack.Pop and Stack.Push to simulate the moving. Atually Stack is really suitable because it is FILO like the real Hanoi Disks moving policy.
public bool Move(Towers from, Towers to)
{
int fromStep = 0;
int toStep = 0;
Stack<int> fromTower = GetTower(from); ;
Stack<int> toTower = GetTower(to); ;
if (fromTower.Count < 1) return false;
fromStep = fromTower.Peek();
if (toTower.Count < 1) toStep = 10;
else toStep = toTower.Peek();
if (fromStep < toStep)
{
toTower.Push(fromTower.Pop());
return true;
}
else if (fromStep == toStep)
{
return true;
}
else
{
return false;
}
}
I try to decouple the logic from GUI, so I run the logic inside HanoiTowerClass and draw GUI everytime anything changes.
private void RedrawTower(List<int> tower, int columns)
{
int j = 0;
for (int i = tower.Count - 1; i >= 0; i--)
{
Canvas.SetTop(rects[tower[i] - 1], Constant.BottomTop - Constant.StepThickness * ++j);
Canvas.SetLeft(rects[tower[i] - 1], Constant.ColumnWidth * columns - rects[tower[i] - 1].Width / 2 + Constant.Offset);
}
}
private void RedrawEnvironment()
{
List<int> towerLeft = tower.GetTowerLeft();
List<int> towerMiddle = tower.GetTowerMiddle();
List<int> towerRight = tower.GetTowerRight();
RedrawTower(towerLeft, 1);
RedrawTower(towerMiddle, 2);
RedrawTower(towerRight, 3);
Canvas.SetLeft(hand, Constant.ColumnWidth * (handPosition + 1));
Canvas.SetTop(hand, Constant.HandTop);
if (holdStep > 0)
{
Canvas.SetTop(rects[holdStep - 1], Constant.HandTop);
Canvas.SetLeft(rects[holdStep - 1], (handPosition + 1) * Constant.ColumnWidth - rects[holdStep - 1].Width / 2);
}
}
In Windows_KeyDown events, we can catch the user input and do some job:
if (e.Key == Key.A || e.Key == Key.Left)
{
if (handPosition > 0)
handPosition--;
else
handPosition = 2;
}
else if (e.Key == Key.D || e.Key == Key.Right)
{
if (handPosition > 1)
handPosition = 0;
else
handPosition++;
}
else if (e.Key == Key.J || e.Key == Key.S || e.Key == Key.Down)
{
if (!timer.IsEnabled)
timer.Start();
if (holdStep == 0)
{
fromPosition = handPosition;
holdStep = tower.GetStep(GetTowerFromPos(fromPosition));
}
else if (holdStep > 0)
{
bool successMove = tower.Move(GetTowerFromPos(fromPosition), GetTowerFromPos(handPosition));
if (successMove)
{
holdStep = 0;
}
else
{
speaker.SpeakAsyncCancelAll();
speaker.SpeakAsync("Can not drop there.");
}
}
}
else if (e.Key == Key.D3)
{
StartNewGame(3);
}
The last point is the timer, I use it to calculate how fast the player complete the puzzle.
private void timer_Tick(object sender, EventArgs e)
{
miliseconds++;
if (miliseconds == 100)
{
if (seconds < 1000)
seconds++;
miliseconds = 0;
}
if (seconds > 999)
lblTime.Content = "More than 999s";
else
lblTime.Content = string.Format("TIME: {0}:{1}", seconds, miliseconds);
}
Points of Interest
Let your application speak!
I invoke the TTS engine to say some words in this application, it's easy to use.