Towers of Hannoi

This is the source code of the mathematical puzzle game written in python2, where its graphical appearence is based on the gtk+2 library. It’s a simple GUI application, which lets you play this game with a different number of disks.
snapshot of the game
toh.py:

#!/usr/bin/env python2
# -*- coding: utf-8 -*-

import gtk
import sys
import cairo
from gtk import gdk

class Towers( gtk.DrawingArea ):
  def __init__( self ): 
    gtk.DrawingArea.__init__( self )
    self.set_size_request( 300, 200 )
    self.connect( "expose-event", self.on_expose )
    self.connect( "button-press-event", self.on_click )

    self.set_events( gdk.BUTTON_PRESS_MASK )

    self.set_disc_num( 3 )

  def on_expose( self, widget, event ):
    cr = self.window.cairo_create( )
    
    cr.rectangle( event.area.x, event.area.y, 
                  event.area.width, event.area.height )
    cr.clip( )
		
    self.draw( cr )
    return False

  def on_click( self, widget, event ):
    okey = False
    om = int( ( event.x - self.x ) // ( self.w / 3.0 ) )
    
    if event.button == 1:
      to = om + 1
      okey = True
    elif event.button == 3:
      to = om + 2
      okey = True
    if okey: self.move_disc( om, to % 3 )
    return True

  def move_disc( self, om, to ):
    def find_top( num ):
      for i in range( self.disc_num - 1, -1, -1 ):
        if self.tower[om] & ( 1 << i ):
          return i
    if not self.tower[om]: return
    top_disc = 1 << find_top( self.tower[om] )
    if top_disc < self.tower[to]: return
          
    self.tower[om] ^= top_disc 
    self.tower[to] |= top_disc
        
    self.queue_draw( )
          
  def draw( self, cr ):
    rect = self.get_allocation( )
    self.w = min( rect.width, 3.0 / 2.0 * rect.height )
    self.h = min( rect.height, 2.0 / 3.0 * self.w )
    self.w = 3.0 / 2.0 * self.h
    self.x = ( rect.width - self.w ) / 2.0
    self.y = ( rect.height - self.h ) / 2.0 

    #cr.set_source_rgb( 0, 0, 0 )
    #cr.rectangle( 0, 0, self.w, self.h )
    #cr.stroke( )

    #cr.set_source_rgb( 1, 1, 1 )
    #cr.rectangle( 0, 0, rect.width, rect.height )
    #cr.stroke( )

    for n in range( 3 ):
      self.draw_tower( cr, n )

  def draw_tower( self, cr, n ):
    cr.set_source_rgb( 0.375, 0.25, 0.125 )
    cr.save( )

    lw = 5 * cr.get_line_width( )
    cr.set_line_width( lw )
    ofs = 10
		
    cr.move_to( self.x + ofs + n * self.w / 3.0, self.y + self.h - ofs )
    cr.line_to( self.x + ( n + 1 ) * self.w / 3.0 - ofs, self.y + self.h - ofs )
		
    cr.move_to( self.x + ( n + 1.0 / 2.0 ) * self.w / 3.0, self.y + self.h - ofs )
    cr.line_to( self.x + ( n + 1.0 / 2.0 ) * self.w / 3.0, self.y + ofs )

    cr.stroke( )
      
    ofs1 = ofs + 1.0 / 5.0 * self.w / 6.0
    lw1 = 4 * lw 
    fac = 10
    cr.set_line_cap( cairo.LINE_CAP_ROUND )
    cr.set_line_width( lw1 )
    j = 0
    for i in range( self.disc_num ):
      cr.set_source_rgb( 0.04 * i, 0.17 * i, 0.13 * i )
      if self.tower[n] & ( 1 << i ):
        cr.move_to( self.x + ofs1 + fac * i + n * self.w / 3.0,
                    self.y + self.h - ofs - ( lw + lw1 ) / 2.0 - j * lw1 )
        cr.line_to( self.x + ( n + 1 ) * self.w / 3.0 - ofs1 - fac * i,
                    self.y + self.h - ofs - ( lw + lw1 ) / 2.0 - j * lw1 )
        cr.stroke( )
        j = j + 1
          
    cr.restore( )

  def set_disc_num( self, num ):
    self.disc_num = num

    self.tower = [ ( 1 << num ) - 1, 0, 0 ]
    
    self.queue_draw( )


class Toh( gtk.Window ):
  def __init__( self ):
    gtk.Window.__init__( self )
    
    self.set_title( "The Towers of Hannoi" )
    self.set_border_width( 3 )
    self.connect( "destroy", gtk.main_quit )
    
    label = gtk.Label( "Set up the number of discs" )
    towers = Towers( )
		
    adjustment = gtk.Adjustment( 3.0, 1.0, 6.0, 1.0 )
    spin = gtk.SpinButton( adjustment, 0, 0 )
    spin.connect( "value-changed", self.on_click, towers )
    
    hbox = gtk.HBox( False, 5 )
    hbox.pack_start( label, False, False )
    hbox.pack_start( spin, False, False )
    
    vbox = gtk.VBox( False, 5 )
    vbox.pack_start( hbox, False, False )
    vbox.pack_start( towers )
    
    self.add( vbox )
    self.show_all( )

  def on_click( self, spin, towers ):
    towers.set_disc_num( spin.get_value_as_int( ) )


def main( ):
  Toh( )
  gtk.main( )


if __name__ == "__main__":
  try:
    main( )
  except( KeyboardInterrupt ):
    sys.exit( 0 )
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s