Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Hanoi Tower

4.77/5 (8 votes)
18 Oct 2013CPOL3 min read 32.8K   1.8K  
A full functional Hanoi Tower game base on WPF

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.

Image 1


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.

Image 2

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.

C#
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.

C#
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.

C#
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:

C#
if (e.Key == Key.A || e.Key == Key.Left) //move left
   {
    if (handPosition > 0)
     handPosition--;
    else
     handPosition = 2;
   }
   else if (e.Key == Key.D || e.Key == Key.Right) //move right
   {
    if (handPosition > 1)
     handPosition = 0;
    else
     handPosition++;
   }
   else if (e.Key == Key.J || e.Key == Key.S || e.Key == Key.Down) // hold/drop 
   {
    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.

C#
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.   

License

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