Skip to content


# Hanoi

VTKEx/Python/Visualization/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 (Cxx)

Question

If you have a simple question about this example contact us at VTKExProject If your question is more complex and may require extended discussion, please use the VTK Discourse Forum

### Code

Hanoi.py

    #!/usr/bin/env python

    #  Translated from Hanoi.cxx.

    import vtk

    class GV(object):
        """
        Used to store global variables.
        """

        def __init__(self, numberOfPucks=5, numberOfSteps=5, puckResolution=48, configuration=0):
            self.numberOfPucks = numberOfPucks
            self.numberOfSteps = numberOfSteps
            self.puckResolution = puckResolution
            self.configuration = configuration
            self.gotFigure2 = False  # Used to bail out of recursion if configuration == 2.
            self.L = 1.0  # Puck height.
            self.H = 1.1 * self.numberOfPucks * self.L  # Peg height.
            self.R = 0.5  # Peg radius.
            self.rMin = 4.0 * self.R  # The minimum allowable radius of disks.
            self.rMax = 12.0 * self.R  # The maximum allowable radius of disks
            self.D = 1.1 * 1.25 * self.rMax  # The distance between the pegs.
            self.numberOfMoves = 0

        def update(self, numberOfPucks, numberOfSteps, puckResolution, configuration):
            self.numberOfPucks = numberOfPucks
            self.numberOfSteps = numberOfSteps
            self.puckResolution = puckResolution
            self.configuration = configuration
            self.H = 1.1 * self.numberOfPucks * self.L  # Peg height.

    # Globals
    gv = GV()
    renWin = vtk.vtkRenderWindow()
    """
       For pegStack we use a list of lists where the sublists correspond to the
          source, target and helper pegs.
       Python lists can be used as a stack since they have append() (corresponding
          to push()) and pop().
    """
    pegStack = [[], [], []]

    def hanoi():

        colors = vtk.vtkNamedColors()

        # Create the renderer and render window interactor.
        ren = vtk.vtkRenderer()
        renWin.AddRenderer(ren)
        renWin.SetSize(1200, 750)
        iren = vtk.vtkRenderWindowInteractor()
        iren.SetRenderWindow(renWin)

        ren.SetBackground(colors.GetColor3d("PapayaWhip"))

        camera = vtk.vtkCamera()
        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.
        pegGeometry = vtk.vtkCylinderSource()
        pegGeometry.SetResolution(8)
        pegMapper = vtk.vtkPolyDataMapper()
        pegMapper.SetInputConnection(pegGeometry.GetOutputPort())

        puckGeometry = vtk.vtkCylinderSource()
        puckGeometry.SetResolution(gv.puckResolution)
        puckMapper = vtk.vtkPolyDataMapper()
        puckMapper.SetInputConnection(puckGeometry.GetOutputPort())

        tableGeometry = vtk.vtkPlaneSource()
        tableGeometry.SetResolution(10, 10)
        tableMapper = vtk.vtkPolyDataMapper()
        tableMapper.SetInputConnection(tableGeometry.GetOutputPort())

        # Create the actors: table top, pegs, and pucks
        # The table
        table = vtk.vtkActor()
        ren.AddActor(table)
        table.SetMapper(tableMapper)
        # table.GetProperty().SetColor(0.9569, 0.6431, 0.3765)
        table.GetProperty().SetColor(colors.GetColor3d("SaddleBrown"))
        table.AddPosition(gv.D, 0, 0)
        table.SetScale(4 * gv.D, 2 * gv.D, 3 * gv.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.
        gv.H = 1.1 * gv.numberOfPucks * gv.L
        peg = list()
        for i in range(0, 3):
            peg.append(vtk.vtkActor())
            ren.AddActor(peg[i])
            peg[i].SetMapper(pegMapper)
            # peg[i].GetProperty().SetColor(1, 1, 1)
            peg[i].GetProperty().SetColor(colors.GetColor3d("Lavender"))
            peg[i].AddPosition(i * gv.D, gv.H / 2, 0)
            peg[i].SetScale(1, gv.H, 1)

        # The pucks (using cylinder geometry). Always loaded on peg# 0.
        puck = list()
        randomSequence = vtk.vtkMinimalStandardRandomSequence()
        randomSequence.SetSeed(1)
        for i in range(0, gv.numberOfPucks):
            puck.append(vtk.vtkActor())
            puck[i].SetMapper(puckMapper)
            color = [0, 0, 0]
            for j in range(0, 3):
                color[j] = randomSequence.GetValue()
                randomSequence.Next()
            puck[i].GetProperty().SetColor(*color)
            puck[i].AddPosition(0, i * gv.L + gv.L / 2, 0)
            scale = gv.rMax - i * (gv.rMax - gv.rMin) / (gv.numberOfPucks - 1)
            puck[i].SetScale(scale, 1, scale)
            ren.AddActor(puck[i])
            pegStack[0].append(puck[i])

        # Reset the camera to view all actors.
        renWin.Render()
        renWin.SetWindowName("Towers of Hanoi")

        if gv.configuration == 3:
            WriteImage("hanoi0.png", renWin, rgba=False)

        if gv.configuration != 1:
            # Begin recursion.
            Hanoi(gv.numberOfPucks - 1, 0, 2, 1)
            Hanoi(1, 0, 1, 2)
            if not gv.gotFigure2:
                Hanoi(gv.numberOfPucks - 1, 2, 1, 0)

                renWin.Render()
                if gv.configuration == 3:
                    WriteImage("hanoi2.png", renWin, rgba=False)
            # Report output.
            s = 'Number of moves: {:d}\nPolygons rendered each frame: {:d}\nTotal number of frames: {:d}'
            print(s.format(gv.numberOfMoves, 3 * 8 + 1 + gv.numberOfPucks * (2 + gv.puckResolution),
                           gv.numberOfMoves * 3 * gv.numberOfSteps))

        iren.AddObserver('EndInteractionEvent', OrientationObserver(ren.GetActiveCamera()))

        # Render the image.
        iren.Initialize()
        iren.Start()

    def main():
        maxPucks = 20
        if not verify_parameters(maxPucks):
            return
        hanoi()

    def get_program_parameters():
        import argparse
        description = 'Towers of Hanoi. .'
        epilogue = '''
    Where:  -p specifies the number of pucks.
            -s specifies the number of steps.
            -r specifies the puck resolution.
            -c specifies configuration.
                0 final configuration.
                1 initial configuration.
                2 intermediate configuration.
                3 final configuration and save images
    Defaults:  -p 5 -s 5 -r 48 -c 0
        '''
        parser = argparse.ArgumentParser(description=description, epilog=epilogue,
                                         formatter_class=argparse.RawDescriptionHelpFormatter)
        parser.add_argument('--numberOfPucks', '-p', default=5, type=int, nargs='?', help='The number of pucks.')
        parser.add_argument('--numberOfSteps', '-s', default=5, type=int, nargs='?', help='The number of steps.')
        parser.add_argument('--puckResolution', '-r', default=48, type=int, nargs='?', help='The puck resolution.')
        parser.add_argument('--configuration', '-c', default=0, type=int, nargs='?', help='The configuration.')
        args = parser.parse_args()
        return args.numberOfPucks, args.numberOfSteps, args.puckResolution, args.configuration

    def verify_parameters(maxPucks):
        numberOfPucks, numberOfSteps, puckResolution, configuration = get_program_parameters()
        numberOfPucks = abs(numberOfPucks)
        numberOfSteps = abs(numberOfSteps)
        puckResolution = abs(puckResolution)
        configuration = abs(configuration)
        check = True
        if numberOfPucks < 2:
            print('Please use more pucks!')
            check = False
        if numberOfPucks > maxPucks:
            print('Too many pucks specified! Maximum is', maxPucks)
            check = False
        if numberOfSteps < 3:
            print('Please use more steps!')
            check = False
        if configuration > 3:
            print('0 >= configuration <= 3')
            check = False
        if check:
            gv.update(numberOfPucks, numberOfSteps, puckResolution, configuration)
        return check

    def MovePuck(peg1, peg2):
        """
        This routine is responsible for moving pucks from peg1 to peg2.
        :param peg1: Initial peg.
        :param peg2: Final peg.
        :return:
        """
        gv.numberOfMoves += 1

        # Get the actor to move
        movingActor = pegStack[peg1].pop()

        # Get the distance to move up.
        distance = (gv.H - (gv.L * (len(pegStack[peg1]) - 1)) + gv.rMax) / gv.numberOfSteps

        for i in range(0, gv.numberOfSteps):
            movingActor.AddPosition(0, distance, 0)
            renWin.Render()

        # Get the distance to move across
        distance = (peg2 - peg1) * gv.D / gv.numberOfSteps
        flipAngle = 180.0 / gv.numberOfSteps
        for i in range(0, gv.numberOfSteps):
            movingActor.AddPosition(distance, 0, 0)
            movingActor.RotateX(flipAngle)
            renWin.Render()
            if gv.numberOfMoves == 13 and i == 3:  # for making book image
                if gv.configuration == 3 or gv.configuration == 2:
                    cam = renWin.GetRenderers().GetFirstRenderer().GetActiveCamera()
                    camera1 = vtk.vtkCamera()
                    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 gv.configuration == 3:
                        WriteImage("hanoi1.png", renWin, rgba=False)
                    if gv.configuration == 2:
                        gv.gotFigure2 = True
                        break
                    renWin.GetRenderers().GetFirstRenderer().SetActiveCamera(cam)
                    renWin.Render()
        if gv.gotFigure2:
            pegStack[peg2].append(movingActor)
            return

        # Get the distance to move down.
        distance = ((gv.L * (len(pegStack[peg2]) - 1)) - gv.H - gv.rMax) / gv.numberOfSteps

        for i in range(0, gv.numberOfSteps):
            movingActor.AddPosition(0, distance, 0)
            renWin.Render()
        pegStack[peg2].append(movingActor)

    def Hanoi(n, peg1, peg2, peg3):
        """
        Tower of Hanoi.
        :param n: Number of disks.
        :param peg1: Source
        :param peg2: Target
        :param peg3: Helper
        :return:
        """
        # If gotFigure2 is true, we break out of the recursion.
        if gv.gotFigure2:
            return
        if n != 1:
            Hanoi(n - 1, peg1, peg3, peg2)
            if gv.gotFigure2:
                return
            Hanoi(1, peg1, peg2, peg3)
            Hanoi(n - 1, peg3, peg2, peg1)
        else:
            MovePuck(peg1, peg2)

    class OrientationObserver(object):
        def __init__(self, cam):
            self.cam = cam

        def __call__(self, caller, ev):
            # Just do this to demonstrate who called callback and the event that triggered it.
            print(caller.GetClassName(), "Event Id:", ev)
            # Now print the camera orientation.
            CameraOrientation(self.cam)

    def CameraOrientation(cam):
        fmt1 = "{:>15s}"
        fmt2 = "{:9.6g}"
        print(fmt1.format("Position:"), ', '.join(map(fmt2.format, cam.GetPosition())))
        print(fmt1.format("Focal point:"), ', '.join(map(fmt2.format, cam.GetFocalPoint())))
        print(fmt1.format("Clipping range:"), ', '.join(map(fmt2.format, cam.GetClippingRange())))
        print(fmt1.format("View up:"), ', '.join(map(fmt2.format, cam.GetViewUp())))
        print(fmt1.format("Distance:"), fmt2.format(cam.GetDistance()))

    def WriteImage(fileName, renWin1, rgba=True):
        """
        Write the render window view to an image file.

        Image types supported are:
         BMP, JPEG, PNM, PNG, PostScript, TIFF.
        The default parameters are used for all writers, change as needed.

        :param fileName: The file name, if no extension then PNG is assumed.
        :param renWin1: The render window.
        :param rgba: Used to set the buffer type.
        :return:
        """

        import os

        if fileName:
            # Select the writer to use.
            path, ext = os.path.splitext(fileName)
            ext = ext.lower()
            if not ext:
                ext = '.png'
                fileName = fileName + ext
            if ext == '.bmp':
                writer = vtk.vtkBMPWriter()
            elif ext == '.jpg':
                writer = vtk.vtkJPEGWriter()
            elif ext == '.pnm':
                writer = vtk.vtkPNMWriter()
            elif ext == '.ps':
                if rgba:
                    rgba = False
                writer = vtk.vtkPostScriptWriter()
            elif ext == '.tiff':
                writer = vtk.vtkTIFFWriter()
            else:
                writer = vtk.vtkPNGWriter()

            windowto_image_filter = vtk.vtkWindowToImageFilter()
            windowto_image_filter.SetInput(renWin1)
            windowto_image_filter.SetScale(1)  # image quality
            if rgba:
                windowto_image_filter.SetInputBufferTypeToRGBA()
            else:
                windowto_image_filter.SetInputBufferTypeToRGB()
                # Read from the front buffer.
                windowto_image_filter.ReadFrontBufferOff()
                windowto_image_filter.Update()

            writer.SetFileName(fileName)
            writer.SetInputConnection(windowto_image_filter.GetOutputPort())
            writer.Write()
        else:
            raise RuntimeError('Need a filename.')

    if __name__ == '__main__':
        main()