;***********************************************************************
;*                                                                     *
;* HANOI.ASP  (C) 1990 DATASTORM TECHNOLOGIES, INC.                    *
;*                                                                     *
;* ------------------------------------------------------------------- *
;*                                                                     *
;*                                                                     *
;*  This is a 'Towers of Hanoi' program written in ASPECT to           *
;*  demonstrate many features of the new PROCOMM PLUS 2.0              *
;*  ASPECT language.  THIS SCRIPT REQUIRES PROCOMM PLUS 2.0 -          *
;*  DO NOT ATTEMPT TO RUN IT UNDER ANY PREVIOUS VERSION!               *
;*                                                                     *
;*  HANOI.ASP is also a good demonstration of recursive processes.     *
;*  A recursive process is one that CALLs itself to divide the         *
;*  work to be done.  Recursive processing is a useful way to          *
;*  solve certain types of problems where the work is repetitive,      *
;*  can be easily divided and intermediate results don't depend        *
;*  on each other.                                                     *
;*                                                                     *
;*  In 'Towers of Hanoi', there are 3 poles and a given number of      *
;*  disks; you must move each of the disks from the leftmost pole      *
;*  (pole 1) to the rightmost pole (pole 3).  You must move one        *
;*  disk at a time without ever placing a larger disk on top of a      *
;*  smaller disk.  To solve the problem, you must use pole 2 as a      *
;*  temporary 'holding area'.                                          *
;*                                                                     *
;*  This program allows you to set the number of disks that will       *
;*  be used, and will allow you to either try and solve the            *
;*  Towers of Hanoi, or will provide a demonstration for you.          *
;*                                                                     *
;*  Written by Rob Greenberg on 9/25/90.                               *
;*                                                                     *
;***********************************************************************

define SOLID    7
define BLANK    0
define FALSE    0
define TRUE     1
define FROM     1
define TO       2
define NORMAL   7
define REV    112
define ESC     27

;----------------------------------------------------
;  Store our representation of "poles" in strings.  -
;----------------------------------------------------
string pole1="00000000", pole2="00000000", pole3="00000000", bar
integer number_of_disks, move_number, keypress, termcolors, demo, exithanoi

;------------------------------------------------------------------------
;  The main procedure -- calls opening screen and the HANOI procedure.  -
;------------------------------------------------------------------------
proc main
integer diskval1, diskval2, done, counter, from_pole_num, to_pole_num
integer high_disk1, high_disk2, high_pos, valid
string  fm_pole_string, to_pole_string

   call setup
   setjmp 0 exithanoi
   if exithanoi == -1
      call cleanup
   endif
   call opening_screen

   ;-------------------------------------------
   ;  If the user chose 'demo', do the demo.  -
   ;-------------------------------------------
   if demo == TRUE
      call hanoi with number_of_disks, &pole1, &pole2, &pole3, 1, 2, 3
   else

      ;--------------------------------------------
      ;  The user wants to play, so stay in this  -
      ;  loop until the user finishes the game.   -
      ;--------------------------------------------
      while TRUE

         ;--------------------------------------
         ;  See if poles 1 and 2 are empty...  -
         ;--------------------------------------
         done = TRUE
         for counter = 7 downto 0
            strpeek pole1 counter diskval1
            strpeek pole2 counter diskval2
            if (diskval1 != 48) || (diskval2 != 48)
               done = FALSE
               exitfor
            endif
         endfor

         ;------------------------------
         ;  If we're done, then exit.  -
         ;------------------------------
         if done == TRUE
            exitwhile
         else
            valid = FALSE
            while valid == FALSE
               ;-------------------------------------------
               ;  Get the poles to move 'from' and 'to',  -
               ;  and the addresses of their strings.     -
               ;-------------------------------------------
               scroll 0 21 20 22 60 NORMAL
               call get_pole with &from_pole_num, &fm_pole_string, FROM
               call get_pole with &to_pole_num,   &to_pole_string, TO

               ;--------------------------------------
               ;  Where are the disks on each pole?  -
               ;--------------------------------------
               call get_highest_disk with &high_disk1, high_pos, fm_pole_string
               call get_highest_disk with &high_disk2, high_pos, to_pole_string

               ;--------------------------
               ;  Is this a valid move?  -
               ;--------------------------
               if ((high_disk1 <= 0) || ((high_disk1 >= high_disk2) && high_disk2 > 0))
                  scroll 0 21 20 22 60 NORMAL
                  sound 1000,40
                  atsay 22 21 NORMAL "Invalid move!  Press a key to try again."
                  keyget keypress
                  scroll 0 21 20 22 60 NORMAL
               else
                  valid = TRUE
               endif
            endwhile

            ;----------------------------------------------
            ;  Move whatever disk the user said to move.  -
            ;----------------------------------------------
            call move_disk with &fm_pole_string, &to_pole_string, from_pole_num, to_pole_num

            ;----------------------------------------------
            ;  Update our master copies of our 'poles'.   -
            ;----------------------------------------------
            call update_string with from_pole_num, fm_pole_string
            call update_string with to_pole_num,   to_pole_string
         endif
      endwhile
      scroll 0 21 20 22 60 NORMAL
   endif
   call cleanup
endproc

;-------------------------------------------------------------------
;  The recursive function that subdivides work by calling itself.  -
;-------------------------------------------------------------------
proc hanoi
intparm h_number_of_disks
strparm h_pole1_string, h_pole2_string, h_pole3_string
intparm pole1_num, pole2_num, pole3_num
integer disks_to_move

   if h_number_of_disks == 1
      call move_disk with &h_pole1_string, &h_pole3_string, pole1_num, pole3_num
   else
      disks_to_move = h_number_of_disks - 1
      call hanoi with disks_to_move, &h_pole1_string, &h_pole3_string, &h_pole2_string, pole1_num, pole3_num, pole2_num
      call move_disk  with &h_pole1_string, &h_pole3_string, pole1_num, pole3_num
      call hanoi with disks_to_move, &h_pole2_string, &h_pole1_string, &h_pole3_string, pole2_num, pole1_num, pole3_num
   endif
endproc



;===========================================
;                                          =
;  "Worker" functions to support "Hanoi".  =
;                                          =
;===========================================

;-------------------------------------------------
;  The function that will 'move a disk' for us.  -
;-------------------------------------------------
proc move_disk
strparm fm_pole_string, to_pole_string
intparm fm_pole_num,  to_pole_num
integer fm_high_disk, to_high_disk, fm_high_pos, to_high_pos, disk_display
integer temp_disk,  sound_value

   move_number++

  ;----------------------------------------------
  ;  Find the highest 'disk' on 'source' pole.  -
  ;----------------------------------------------
   call get_highest_disk with &fm_high_disk, &fm_high_pos, fm_pole_string

  ;-----------------------------------------
  ;  Convert character disk # to decimal.  -
  ;-----------------------------------------
   disk_display = fm_high_disk - 48
   if demo == TRUE
      fatsay 21 21 NORMAL "Move %d -- disk %d from pole %d to pole %d" move_number,disk_display,fm_pole_num,to_pole_num
   endif

  ;-----------------------------------------
  ;  Erase highest disk on 'source' pole.  -
  ;-----------------------------------------
   strpoke fm_pole_string fm_high_pos '0'
   temp_disk   = fm_high_pos + 1
   sound_value = (disk_display * 50) - 20
   sound sound_value, 12
   call display_disk with fm_pole_num, temp_disk, disk_display, BLANK

  ;---------------------------------------------------
  ;  Find the highest 'disk' on 'destination' pole.  -
  ;---------------------------------------------------
   call get_highest_disk with &to_high_disk, &to_high_pos, to_pole_string

  ;-----------------------------------------------------
  ;  Move disk into empty slot on 'destination' pole.  -
  ;-----------------------------------------------------
   to_high_pos++
   strpoke to_pole_string to_high_pos fm_high_disk
   temp_disk = to_high_pos + 1
   call display_disk with to_pole_num, temp_disk, disk_display, SOLID

   if demo == TRUE
      keyget keypress
      if keypress == ESC
         longjmp 0 -1
      endif
   endif
endproc

;----------------------------------------------------------------
;  This function will return the highest disk on a given pole.  -
;  If there are no disks on the pole, it will return a -1.      -
;----------------------------------------------------------------
proc get_highest_disk
intparm high_disk, high_pos
strparm search_pole
integer counter

   strlen search_pole counter
   counter--
   high_pos = 99

   for counter downto 0
      strpeek search_pole counter high_disk
      if high_disk != 48
         high_pos   = counter
         exitfor
      endif
   endfor

   if high_pos  == 99
      high_pos  = -1
      high_disk = -1
   endif
endproc

;---------------------------------------------------
;  Given a pole, position, and disk size, this     -
;  function displays the disk.  It also erases a   -
;  given disk if the attribute is set to 'BLANK'.  -
;---------------------------------------------------
proc display_disk
intparm bar_number, disk_position, disk_size, attribute
integer row, column, counter, position, display_num

   row = 19 - (disk_position * 2)
   column      = (bar_number * 20) - disk_size
   display_num = disk_size
   disk_size   = (disk_size * 2) + 1

   if attribute != BLANK
      attribute  = display_num + 8
   endif

   for counter = 1 upto disk_size
      position = column + counter - 1
      atsay row position attribute "±"
   endfor

  ;-----------------------------------------------
  ;  If we're erasing a disk, fixup the pole.    -
  ;  If we're drawing a disk, print the number.  -
  ;-----------------------------------------------
   column = bar_number * 20
   if attribute == BLANK
      atsay row column SOLID "Û"
   else
      fatsay row column SOLID "%d" display_num
   endif
endproc

;---------------------------------------------------
;  Prompt the user for a pole to move, and return  -
;  a copy of the pole "string" in POLE_STRING.     -
;---------------------------------------------------
proc get_pole
intparm pole_num
strparm pole_string
intparm direction

   while TRUE
      if direction == FROM
         scroll 0 21 20 21 60 NORMAL
         atsay 21 22 NORMAL "Move a disk FROM which pole? (1-3):"
         atget 21 58 NORMAL 1 pole_num
         if failure
            longjmp 0 -1
         endif
      else
         scroll 0 22 20 22 60 NORMAL
         atsay 22 22 NORMAL "Move a disk TO which pole? (1-3):"
         atget 22 58 NORMAL 1 pole_num
         if failure
            longjmp 0 -1
         endif
      endif

      switch pole_num
         case 1
            pole_string = pole1
            exitwhile
         endcase

         case 2
            pole_string = pole2
            exitwhile
         endcase

         case 3
            pole_string = pole3
            exitwhile
         endcase

         default
            scroll 0 23 20 23 60 NORMAL
            sound 1000, 40
            atsay 23 22 NORMAL "You must choose 1-3.  Press a key..."
            keyget keypress
            scroll 0 23 20 23 60 NORMAL
         endcase
      endswitch
   endwhile
endproc

;-----------------------------------------
;  Given a 'local' copy of a string,     -
;  update the master copy of the string. -
;-----------------------------------------
proc update_string
intparm polenum
strparm pole_string

   switch polenum
      case 1
         pole1 = pole_string
      endcase

      case 2
         pole2 = pole_string
      endcase

      case 3
         pole3 = pole_string
      endcase
   endswitch
endproc

;=================================================
;                                                =
;  Routines for the opening and closing screens  =
;                                                =
;=================================================

;-------------------------------------------
; Setup Hanoi...                           -
;-------------------------------------------
proc setup
   strset bar 223 60
   strpoke bar 61 0
   move_number = 0
   demo = TRUE
   set keys on
   fetch termnorm termcolors
   set statline off
   curoff
endproc

;----------------------------------------------------
;  This function displays the opening screen, asks  -
;  the user how many disks to use, displays them,   -
;  and asks the user if they want to play or demo.  -
;----------------------------------------------------
proc opening_screen
integer counter1, counter2, column, disk_position, disk_size, offset, temp
string  choice

   clear NORMAL
   atsay 0 25 REV " T O W E R S   O F   H A N O I "
   atsay 2  8 NORMAL "*****************************************************************"
   atsay 3  8 NORMAL "* This is a 'Towers of Hanoi' program written in ASPECT to      *"
   atsay 4  8 NORMAL "* demonstrate recursive processing.                             *"
   atsay 5  8 NORMAL "*                                                               *"
   atsay 6  8 NORMAL "* A recursive process is one that CALLs itself to divide the    *"
   atsay 7  8 NORMAL "* work to be done.  Recursive processing is a useful way to     *"
   atsay 8  8 NORMAL "* solve certain types of problems where the work is repetitive, *"
   atsay 9  8 NORMAL "* can be easily divided and intermediate results don't depend   *"
   atsay 10 8 NORMAL "* on each other.                                                *"
   atsay 11 8 NORMAL "*                                                               *"
   atsay 12 8 NORMAL "* In 'Towers of Hanoi', there are 3 poles and a given number of *"
   atsay 13 8 NORMAL "* disks; you must move each of the disks from the leftmost pole *"
   atsay 14 8 NORMAL "* (pole 1) to the rightmost pole (pole 3).  You must move one   *"
   atsay 15 8 NORMAL "* disk at a time without ever placing a larger disk on top of a *"
   atsay 16 8 NORMAL "* smaller disk.  To solve the problem, you must use pole 2 as a *"
   atsay 17 8 NORMAL "* temporary 'holding area'.                                     *"
   atsay 18 8 NORMAL "*                                                               *"
   atsay 19 8 NORMAL "* This program allows you to set the number of disks that will  *"
   atsay 20 8 NORMAL "* be used, and will allow you to either try and solve the       *"
   atsay 21 8 NORMAL "* Towers of Hanoi, or will provide a demonstration for you.     *"
   atsay 22 8 NORMAL "*****************************************************************"
   atsay 24 28 REV "Hit any key to continue."
   keyget keypress

   clear NORMAL
   atsay 1 25 REV " T O W E R S   O F   H A N O I "

  ;----------------------
  ;  Draw the "poles".  -
  ;----------------------
   atsay 19 10 NORMAL bar

   for counter1 = 1 upto 3
      column = counter1 * 20
      for counter2 = 3 upto 18
         atsay counter2 column NORMAL "Û"
      endfor
   endfor

  ;-----------------------------
  ;  Get the number of disks.  -
  ;-----------------------------
   while TRUE
      atsay 21 20 NORMAL "Enter the number of disks to use (1-8):"
      atget 21 60 NORMAL 1 number_of_disks
      if failure
         longjmp 0 -1
      endif
      if (number_of_disks < 1) || (number_of_disks > 8)
         sound 1000,40
         atsay 22 25 NORMAL "Invalid selection.  Try again!"
      else
         exitwhile
      endif
   endwhile

  ;-----------------------------------
  ;  'Place' the disks onto pole 1.  -
  ;-----------------------------------
   for counter1 = number_of_disks downto 1
      offset = number_of_disks - counter1
      temp   = counter1 + 48
      strpoke pole1 offset temp
   endfor

  ;-------------------------------------
  ;  Display the disks on the screen.  -
  ;-------------------------------------
   for disk_position = 1 upto number_of_disks
      disk_size = number_of_disks + 1 - disk_position
      call display_disk with 1, disk_position, disk_size, SOLID
   endfor

  ;--------------------------------------------
  ;  Ask user if they want to play or watch.  -
  ;--------------------------------------------
   scroll 0 21 20 22 60 NORMAL

   atsay 21 22 NORMAL "Play the game or see the demo (P/D)?"
   atget 21 59 REV 1 choice
   strupr choice
   if failure
      longjmp 0 -1
   endif
   find choice "P"
   if found
      demo = FALSE
   endif

  ;--------------------------------------------
  ;  Erase any text that might be left over.  -
  ;--------------------------------------------
   scroll 0 21 10 22 70 NORMAL
   atsay 22 17 NORMAL "Hit [ENTER] to begin the game.  [ESC] will quit."
   keyget keypress
   scroll 0 22 10 23 70 NORMAL
   if demo == TRUE
      atsay 23 22 NORMAL "Hit [ENTER] to continue, [ESC] to quit"
   endif
endproc



;----------------------------------------------
;  Prepare for the return to Procomm Plus...  -
;----------------------------------------------
proc cleanup
   if exithanoi
      scroll 0 21 20 23 70 NORMAL
   else
      scroll 0 22 20 23 70 NORMAL
      if demo == FALSE
         fatsay 21 22 NORMAL "Congratulations!  You won in %d moves!" move_number
      endif
   endif
   atsay 23 22 NORMAL "Hit [ENTER] to return to Procomm Plus"
   kflush
   keyget keypress

  ;----------------------------------------------------------
  ;  Restore status line, cursor, colors for Procomm Plus.  -
  ;----------------------------------------------------------
   set statline on
   curon
   clear termcolors
   exit
endproc

