Skip to content

Hanoi

Repository source: Hanoi


Description

This is three-dimensional implementation of the Towers of Hanoi.

Here we visualize the operation of the recursive Towers of Hanoi puzzle. In this puzzle there are three pegs. In the initial position there are one or more disks(or pucks) of varying diameter on the pegs. The disks are sorted according to disk diameter, so that the largest disk is on the bottom, followed by the next largest, and so on. The goal of the puzzle is to move the disks from one peg to another, moving the disks one at a time, and never placing a larger disk on top of a smaller disk.

Here we first set up the scene with the table, pegs and pucks. Then we use a function called Hanoi() to begin the recursion. A second function MovePuck() moves the puck from one peg to another.

To give a pleasing visual effect we move the disk in small, user-specified increments, flipping the disc over as it moves from one peg to the next. Option -s controls the user-defined increments. The option -c 2 freezes a disk in mid air moving from one peg to another.

Other languages

See (Python), (PythonicAPI)

Question

If you have a question about this example, please use the VTK Discourse Forum

Code

Hanoi.cxx

/*

Translated from:
   http://www.new-npac.org/projects/sv2all/sv2/vtk/graphics/examplesCxx/Hanoi.cxx


Hanoi - application example does 3D towers of hanoi.
Usage:
 Hanoi -p # -s # -r # -c #
 where -p is the number of starting pucks on the peg;
       -s is the number of steps to take during animation;
       -r is the resolution of each puck
       -c controls output of the program.
*/

#include <vtkActor.h>
#include <vtkCallbackCommand.h>
#include <vtkCamera.h>
#include <vtkCylinderSource.h>
#include <vtkMinimalStandardRandomSequence.h>
#include <vtkNamedColors.h>
#include <vtkNew.h>
#include <vtkPNGWriter.h>
#include <vtkPlaneSource.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkRendererCollection.h>
#include <vtkSmartPointer.h>
#include <vtkVersion.h>
#include <vtkWindowToImageFilter.h>

#include <algorithm>
#include <array>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <sstream>
#include <stack>
#include <string>
#include <vector>

namespace {
void PrintCameraOrientation(vtkCamera* cam);

/**
Here we inherit from vtkCallbackCommand and set pointers to any
client and/or call data as needed.
When the class is implemented, it becomes the callback function.
*/
class CameraModifiedCallback : public vtkCallbackCommand
{
public:
  static CameraModifiedCallback* New()
  {
    return new CameraModifiedCallback;
  }
  // Here we Create a vtkCallbackCommand and reimplement it.
  void Execute(vtkObject* caller, unsigned long evId, void*) override
  {
    // Note the use of reinterpret_cast to cast the caller to the expected type.
    vtkRenderWindowInteractor* interactor =
        reinterpret_cast<vtkRenderWindowInteractor*>(caller);
    // Just do this to demonstrate who called callback and the event that
    // triggered it.
    std::cout << interactor->GetClassName() << "  Event Id: " << evId
              << std::endl;

    // Now print the camera orientation.
    PrintCameraOrientation(this->cam);
  }
  CameraModifiedCallback() : cam(nullptr)
  {
  }
  // Set pointers to any clientData or callData here.
  vtkCamera* cam;

private:
  CameraModifiedCallback(const CameraModifiedCallback&) = delete;
  void operator=(const CameraModifiedCallback&) = delete;
};

// Find command line parameters.
std::vector<std::string>::iterator FindParameter(std::string const& p,
                                                 std::vector<std::string>& v);

typedef std::array<std::stack<vtkSmartPointer<vtkActor>>, 3> PegArray;

/**
 * This routine is responsible for moving pucks from peg1 to peg2.
 *
 * @param peg1 Initial peg.
 * @param peg2 Final peg.
 */
void MovePuck(vtkRenderWindow* renWin, PegArray& pegStack, int peg1, int peg2);

/**
* Tower of Hanoi.
*
* @param n Number of disks.
* @param peg1 Source.
* @param peg2 Target.
* @param peg3 Helper.

*/
void Hanoi(vtkRenderWindow* renWin, PegArray& pegStack, int n, int peg1,
           int peg2, int peg3);

// Save a screenshot.
void Screenshot(std::string const& fileName, vtkRenderWindow* renWin);

// Default values.
auto numberOfPucks = 5;
auto numberOfSteps = 5;
auto puckResolution = 48;
auto configuration = 0;  // Selecting output.
auto gotFigure2 = false; // Used to bail out of recursion if configuration == 2.
auto L = 1.0;            // Puck height.
auto H = 1.1 * numberOfPucks * L; // Peg height.
auto R = 0.5;                     // Peg radius.
auto rMin = 4.0 * R;              // The minimum allowable radius of disks.
auto rMax = 12.0 * R;             // The maximum allowable radius of disks
auto D = 1.1 * 1.25 * rMax;       // The distance between the pegs.
auto numberOfMoves = 0;
auto const maxPucks = 20;
} // namespace

int main(int argc, char* argv[])
{
  // Here we parse the command line.
  std::vector<std::string> parameters;
  for (auto i = 1; i < argc; ++i)
  {
    parameters.push_back(argv[i]);
  }
  auto p = FindParameter(std::string("?"), parameters);
  if (p != parameters.end())
  {
    std::cerr << "Usage:   " << argv[0] << " [-p #] [-s #] [r #] [-c #]"
              << std::endl;
    std::cerr << "Where:        -p specifies the number of pucks." << std::endl;
    std::cerr << "              -s specifies the number of steps." << std::endl;
    std::cerr << "              -r specifies the puck resolution." << std::endl;
    std::cerr << "              -c specifies configuration." << std::endl;
    std::cerr << "                 0 final configuration." << std::endl;
    std::cerr << "                 1 initial configuration." << std::endl;
    std::cerr << "                 2 intermediate configuration." << std::endl;
    std::cerr << "                 3 final configuration and save images"
              << std::endl;
    std::cerr << "Defaults:  -p 5 -s 5 -r 48 -c 0" << std::endl;
    return EXIT_FAILURE;
  }
  p = FindParameter(std::string("-p"), parameters);
  if (p != parameters.end() && std::next(p) != parameters.end())
  {
    numberOfPucks = std::abs(atoi(std::next(p)->c_str()));
  }
  p = FindParameter(std::string("-s"), parameters);
  if (p != parameters.end() && std::next(p) != parameters.end())
  {
    numberOfSteps = std::abs(atoi(std::next(p)->c_str()));
  }
  p = FindParameter(std::string("-r"), parameters);
  if (p != parameters.end() && std::next(p) != parameters.end())
  {
    puckResolution = std::abs(atoi(std::next(p)->c_str()));
  }
  p = FindParameter(std::string("-c"), parameters);
  if (p != parameters.end() && std::next(p) != parameters.end())
  {
    configuration = std::abs(atoi(std::next(p)->c_str()));
  }

  // Initialize variables and check input.
  if (numberOfPucks < 2)
  {
    std::cerr << "Please use more pucks!\n";
    return EXIT_FAILURE;
  }

  if (numberOfPucks > maxPucks)
  {
    std::cerr << "Too many pucks specified! Maximum is " << maxPucks << "\n";
    return EXIT_FAILURE;
  }

  if (numberOfSteps < 3)
  {
    std::cerr << "Please use more steps!\n";
    return EXIT_FAILURE;
  }

  if (configuration > 3)
  {
    std::cerr << "0>= configuration <= 3\n";
    return EXIT_FAILURE;
  }

  vtkNew<vtkNamedColors> colors;

  // Our rendering window.
  vtkNew<vtkRenderWindow> renWin;

  // Create renderer and render window interactor.
  vtkNew<vtkRenderer> ren;
  renWin->AddRenderer(ren);
  renWin->SetSize(1200, 750);
  vtkNew<vtkRenderWindowInteractor> iren;
  iren->SetRenderWindow(renWin);

  ren->SetBackground(colors->GetColor3d("PapayaWhip").GetData());

  vtkNew<vtkCamera> camera;
  camera->SetPosition(41.0433, 27.9637, 30.442);
  camera->SetFocalPoint(11.5603, -1.51931, 0.95899);
  camera->SetClippingRange(18.9599, 91.6042);
  camera->SetViewUp(0, 1, 0);

  ren->SetActiveCamera(camera);

  // Create geometry: table, pegs, and pucks.
  vtkNew<vtkCylinderSource> pegGeometry;
  pegGeometry->SetResolution(8);
  vtkNew<vtkPolyDataMapper> pegMapper;
  pegMapper->SetInputConnection(pegGeometry->GetOutputPort());

  vtkNew<vtkCylinderSource> puckGeometry;
  puckGeometry->SetResolution(puckResolution);
  vtkNew<vtkPolyDataMapper> puckMapper;
  puckMapper->SetInputConnection(puckGeometry->GetOutputPort());

  vtkNew<vtkPlaneSource> tableGeometry;
  tableGeometry->SetResolution(10, 10);
  vtkNew<vtkPolyDataMapper> tableMapper;
  tableMapper->SetInputConnection(tableGeometry->GetOutputPort());

  // Create the actors: table top, pegs, and pucks
  // The table
  vtkNew<vtkActor> table;
  ren->AddActor(table);
  table->SetMapper(tableMapper);
  // table->GetProperty()->SetColor(0.9569, 0.6431, 0.3765);
  table->GetProperty()->SetColor(colors->GetColor3d("SaddleBrown").GetData());
  table->AddPosition(D, 0, 0);
  table->SetScale(4 * D, 2 * D, 3 * D);
  table->RotateX(90);

  // The pegs (using cylinder geometry).  Note that the pegs have to translated
  // in the  y-direction because the cylinder is centered about the origin.
  H = 1.1 * numberOfPucks * L;
  std::vector<vtkSmartPointer<vtkActor>> peg;
  for (auto i = 0; i < 3; ++i)
  {
    peg.push_back(vtkSmartPointer<vtkActor>::New());
    ren->AddActor(peg[i]);
    peg[i]->SetMapper(pegMapper);
    // peg[i]->GetProperty()->SetColor(1, 1, 1);
    peg[i]->GetProperty()->SetColor(colors->GetColor3d("Lavender").GetData());
    peg[i]->AddPosition(i * D, H / 2, 0);
    peg[i]->SetScale(1, H, 1);
  }

  // Three pegs, each a stack of a vector of actors.
  PegArray pegStack;

  // The pucks (using cylinder geometry). Always loaded on peg# 0.
  std::vector<vtkSmartPointer<vtkActor>> puck;
  vtkNew<vtkMinimalStandardRandomSequence> randomSequence;
  randomSequence->SetSeed(1);
  for (auto i = 0; i < numberOfPucks; ++i)
  {
    puck.push_back(vtkSmartPointer<vtkActor>::New());
    puck[i]->SetMapper(puckMapper);
    std::array<double, 3> color{{0, 0, 0}};
    for (auto j = 0; j < 3; ++j)
    {
      color[j] = randomSequence->GetValue();
      randomSequence->Next();
    }
    puck[i]->GetProperty()->SetColor(color.data());
    puck[i]->AddPosition(0, i * L + L / 2, 0);
    auto scale = rMax - i * (rMax - rMin) / (numberOfPucks - 1);
    puck[i]->SetScale(scale, 1, scale);
    ren->AddActor(puck[i]);
    pegStack[0].push(puck[i]);
  }

  // Reset the camera to view all actors.
  renWin->Render();
  renWin->SetWindowName("Hanoi");

  if (configuration == 3)
  {
    Screenshot("hanoi0.png", renWin);
  }
  if (configuration != 1)
  {
    // Begin recursion.
    Hanoi(renWin, pegStack, numberOfPucks - 1, 0, 2, 1);
    Hanoi(renWin, pegStack, 1, 0, 1, 2);
    if (!gotFigure2)
    {
      Hanoi(renWin, pegStack, numberOfPucks - 1, 2, 1, 0);

      renWin->Render();
      if (configuration == 3)
      {
        Screenshot("hanoi2.png", renWin);
      }
    }
    // Report output.
    cout << "Number of moves: " << numberOfMoves << "\n"
         << "Polygons rendered each frame: "
         << 3 * 8 + 1 + numberOfPucks * (2 + puckResolution) << "\n"
         << "Total number of frames: " << numberOfMoves * 3 * numberOfSteps
         << "\n";
  }
  vtkNew<CameraModifiedCallback> getOrientation;
  // Set the camera to use.
  getOrientation->cam = ren->GetActiveCamera();
  iren->AddObserver(vtkCommand::EndInteractionEvent, getOrientation);

  // Render the image.
  iren->Initialize();
  iren->Start();
  return EXIT_SUCCESS;
}

namespace {

std::vector<std::string>::iterator FindParameter(std::string const& p,
                                                 std::vector<std::string>& v)
{
  return std::find(v.begin(), v.end(), p);
}

void MovePuck(vtkRenderWindow* renWin, PegArray& pegStack, int peg1, int peg2)
{
  numberOfMoves++;

  // Get the actor to move
  vtkActor* movingActor = pegStack[peg1].top();
  pegStack[peg1].pop();

  // Get the distance to move up.
  auto distance =
      (H - (L * (static_cast<int>(pegStack[peg1].size()) - 1)) + rMax) /
      numberOfSteps;
  for (auto i = 0; i < numberOfSteps; i++)
  {
    movingActor->AddPosition(0, distance, 0);
    renWin->Render();
  }

  // get the distance to move across
  distance = (peg2 - peg1) * D / numberOfSteps;
  auto flipAngle = 180.0 / numberOfSteps;
  for (auto i = 0; i < numberOfSteps; i++)
  {
    movingActor->AddPosition(distance, 0, 0);
    movingActor->RotateX(flipAngle);
    renWin->Render();
    if (numberOfMoves == 13 && i == 3) // for making book image
    {
      if (configuration == 3 || configuration == 2)
      {
        vtkCamera* cam =
            renWin->GetRenderers()->GetFirstRenderer()->GetActiveCamera();
        vtkNew<vtkCamera> camera1;
        camera1->SetPosition(54.7263, 41.6467, 44.125);
        camera1->SetFocalPoint(11.5603, -1.51931, 0.95899);
        camera1->SetClippingRange(42.4226, 115.659);
        camera1->SetViewUp(0, 1, 0);
        renWin->GetRenderers()->GetFirstRenderer()->SetActiveCamera(camera1);
        renWin->Render();
        if (configuration == 3)
        {
          Screenshot("hanoi1.png", renWin);
        }
        if (configuration == 2)
        {
          gotFigure2 = true;
          break;
        }
        renWin->GetRenderers()->GetFirstRenderer()->SetActiveCamera(cam);
        renWin->Render();
      }
    }
  }

  if (gotFigure2)
  {
    pegStack[peg2].push(movingActor);
    return;
  }

  // Get the distance to move down.
  distance = ((L * (static_cast<int>(pegStack[peg2].size()) - 1)) - H - rMax) /
      numberOfSteps;

  for (auto i = 0; i < numberOfSteps; i++)
  {
    movingActor->AddPosition(0, distance, 0);
    renWin->Render();
  }

  pegStack[peg2].push(movingActor);
}

void Hanoi(vtkRenderWindow* renWin, PegArray& pegStack, int n, int peg1,
           int peg2, int peg3)
{
  // If gotFigure2 is true, we break out of the recursion.
  if (gotFigure2)
  {
    return;
  }
  if (n != 1)
  {
    Hanoi(renWin, pegStack, n - 1, peg1, peg3, peg2);
    if (gotFigure2)
    {
      return;
    }
    Hanoi(renWin, pegStack, 1, peg1, peg2, peg3);
    Hanoi(renWin, pegStack, n - 1, peg3, peg2, peg1);
  }
  else
  {
    MovePuck(renWin, pegStack, peg1, peg2);
  }
  return;
}

void Screenshot(std::string const& fileName, vtkRenderWindow* renWin)
{
  vtkNew<vtkWindowToImageFilter> windowToImageFilter;
  windowToImageFilter->SetInput(renWin);

#if VTK_MAJOR_VERSION >= 8 || VTK_MAJOR_VERSION == 8 && VTK_MINOR_VERSION >= 90
  windowToImageFilter->SetScale(1); // image quality
#else
  windowToImageFilter->SetMagnification(1); // image quality
#endif

  // We are not recording the alpha (transparency) channel.
  // windowToImageFilter->SetInputBufferTypeToRGBA();
  windowToImageFilter->SetInputBufferTypeToRGB();
  // Read from the front buffer.
  windowToImageFilter->ReadFrontBufferOff();
  windowToImageFilter->Update();

  vtkNew<vtkPNGWriter> writer;
  writer->SetFileName(fileName.c_str());
  writer->SetInputConnection(windowToImageFilter->GetOutputPort());
  writer->Write();
}

/**
Get a comma separated list.
*/
template <typename T> std::string CommaSeparatedList(std::vector<T> v)
{
  std::ostringstream os;
  std::copy(v.begin(), v.end() - 1, std::ostream_iterator<T>(os, ", "));
  os << v.back();
  return os.str();
}

/**
Print the camera orientation.
*/
void PrintCameraOrientation(vtkCamera* cam)
{
  auto width = 16;
  double pos[3];
  cam->GetPosition(pos);
  double fp[3];
  cam->GetFocalPoint(fp);
  double vu[3];
  cam->GetViewUp(vu);
  double cr[2];
  cam->GetClippingRange(cr);
  std::cout << setw(width) << "Position: "
            << CommaSeparatedList(std::vector<double>(pos, pos + 3))
            << std::endl;
  std::cout << setw(width) << "Focal point: "
            << CommaSeparatedList(std::vector<double>(fp, fp + 3)) << std::endl;
  std::cout << setw(width) << "Clipping range: "
            << CommaSeparatedList(std::vector<double>(cr, cr + 2)) << std::endl;
  std::cout << setw(width) << "View up: "
            << CommaSeparatedList(std::vector<double>(vu, vu + 3)) << std::endl;
  std::cout << setw(width) << "Distance: " << cam->GetDistance() << std::endl;
};
} // namespace

CMakeLists.txt

cmake_minimum_required(VERSION 3.12 FATAL_ERROR)

project(Hanoi)

find_package(VTK COMPONENTS 
  CommonColor
  CommonCore
  FiltersSources
  IOImage
  InteractionStyle
  RenderingContextOpenGL2
  RenderingCore
  RenderingFreeType
  RenderingGL2PSOpenGL2
  RenderingOpenGL2
)

if (NOT VTK_FOUND)
  message(FATAL_ERROR "Hanoi: Unable to find the VTK build folder.")
endif()

# Prevent a "command line is too long" failure in Windows.
set(CMAKE_NINJA_FORCE_RESPONSE_FILE "ON" CACHE BOOL "Force Ninja to use response files.")
add_executable(Hanoi MACOSX_BUNDLE Hanoi.cxx )
  target_link_libraries(Hanoi PRIVATE ${VTK_LIBRARIES}
)
# vtk_module_autoinit is needed
vtk_module_autoinit(
  TARGETS Hanoi
  MODULES ${VTK_LIBRARIES}
)

Download and Build Hanoi

Click here to download Hanoi and its CMakeLists.txt file. Once the tarball Hanoi.tar has been downloaded and extracted,

cd Hanoi/build

If VTK is installed:

cmake ..

If VTK is not installed but compiled on your system, you will need to specify the path to your VTK build:

cmake -DVTK_DIR:PATH=/home/me/vtk_build ..

Build the project:

make

and run it:

./Hanoi

WINDOWS USERS

Be sure to add the VTK bin directory to your path. This will resolve the VTK dll's at run time.