Page 1 of 1

My todays gift : Hanoi towers (windowedscreen + gadgets)

Posted: Mon Jun 28, 2004 11:26 pm
by fweil
Code updated for 5.20+

Here is a nice old school tut reworked with interestin features.

Code: Select all

;
; Hanoi towers in a thread and the drawing in the main program
; =====================================
; Les tours de Hanoï dans un thread et le dessin dans le programme principal
;
; F.Weil 20040628
;
; No API here, full PureBasic, strong and stable code
; Interesting use of gadgets with Spin and TrackBar in Frame3D, the one used with TrackBar being updated when the TrackBar is changed
; Drawing is screen based to have a good window stability (no flickering). I use a windowedscreen with no autostretch and a flipbufers(0)
; for maximum velocity of the screen.
; The towers procedure is independant of the screen refresh. It considers the drawing and the calculation as asynchronous processes.
; Hanoi towers algorithm is an old school workshop which exist in recursive and non recursive writing. I use here an old non recursive.
; ========================================================================================
; Pas d'API utilisée, un code PureBasic solide et stable
; Une utilisation intéressante des gadgets avec un Spin et un TrackBar encadrés par des Frame3D, celui utilisé avec le TrackBar se mettant à jour lorsque l'on change
; l'état du TrackBar.
; Le dessin est effectué mode screen pour assurer une bonne stabilité de la fenêtre (pas de scintillement). J'utilise un windowedscreen sans autostretch
; et un flipbuffers(0) pour une vitesse maximale de raffraichissement de l'écran.
; La procédure des tours et le raffraichissment de l'écran sont indépendants. Le dessin et le calcul sont considérés comme des
; processus asynchrones.
; L'algorithme des tours de Hanoï est un vieux cas d'étude bien connu qui peut être résolu en récursif ou non récursif. J'utilise ici le non récursif.
;
Enumeration
  #Window_Main
  #Gadget_Frame3D1
  #Gadget_TrackBar
  #Gadget_Frame3D2
  #Gadget_Spin
  #Gadget_Button
EndEnumeration

Global WindowXSize, WindowYSize, ScreenXSize, ScreenYSize, ThreadID
Global MaxTowers, NMoves, TotalMoves

MaxTowers = 12
Global Dim Stack(4096)
Global Dim Towers(3, MaxTowers)
Global Dim Colors(MaxTowers)

;
; The procedure calculates positions, moves. Results are stored in arrays used in the main program
; ======================================================================================
; La procedure calcule les positions, les mouvements. Les résultats sont stockés dans des tableaux utilisés dans le programme principal.
;
Procedure HanoiTowers(ThreadParameter)
  Dim Stack(4096)
  Dim Towers(3, MaxTowers)
  StackPointer = 1
  Destination = 2
  Source = 0
  u = 1
  n = 3
  e = 0
  m = 0
  Disk = Val(GetGadgetText(#Gadget_Spin))
  TotalMoves = 1 << (Disk + 1) - 1 << (Disk - 1) - 2
  NMoves = 0
  For i = 1 To Disk
    Towers(1, i) = i
    ;
    ; Don't worry this formula only generates childish colors ! That's all folks !
    ;
    Colors(i) = $80 * (i & 1) + $8000 * (i & 2) / 2 + $800000 * (i & 4) / 4 + $404040 * (i & 8) / 8
  Next
  Stack(StackPointer) = Disk
  Stack(StackPointer + 1) = 1
  Stack(StackPointer + 2) = 3
  Stack(StackPointer + 3) = 0
  StackPointer + 4
  While StackPointer > 1
    If StackPointer <> 0
      StackPointer - 1
      Disk = Stack(StackPointer)
    EndIf
    e = Disk
    If StackPointer <> 0
      StackPointer - 1
      Disk = Stack(StackPointer)
    EndIf
    Destination = Disk
    If StackPointer <> 0
      StackPointer - 1
      Disk = Stack(StackPointer)
    EndIf
    Source = Disk
    If StackPointer <> 0
      StackPointer - 1
      Disk = Stack(StackPointer)
    EndIf
    n = Disk
    m = 6 - Source - Destination
    If e <> 0
      Towers(Source, Disk) = 0
      Towers(Destination, Disk) = Disk
      Stack(StackPointer) = n - 1
      Stack(StackPointer + 1) = m
      Stack(StackPointer + 2) = Destination
      Stack(StackPointer + 3) = 0
      StackPointer + 4
    ElseIf n <> 1
      Stack(StackPointer) = n
      Stack(StackPointer + 1) = Source
      Stack(StackPointer + 2) = Destination
      Stack(StackPointer + 3) = 1
      Stack(StackPointer + 4) = n - 1
      Stack(StackPointer + 5) = Source
      Stack(StackPointer + 6) = m
      Stack(StackPointer + 7) = 0
      StackPointer + 8
    Else
      Towers(Source, Disk) = 0
      Towers(Destination, Disk) = Disk
    EndIf
    Delay(GetGadgetState(#Gadget_TrackBar))
    SetWindowTitle(#Window_Main, "move " + Str(Source) + " --> " + Str(Destination) + " : " + Str(Disk))
    NMoves + 1
  Wend
  ThreadID = 0
EndProcedure

;
; Main manages the window creation and the main event loop
; When the OK button is pushed the game is started in a thread.
; If a game is playing, the main program allow to change parameters. By pressing the OK button again it kills the current thread if one still exists before starting
; a new one.
; ===========
; Le programme principal créé la fenêtre principale et la boucle évènementielle.
; Lorsque l'on appuie sur le bouton OK, le jeu démarre dans un thread
; Si un jeu est en cours, le programme principal permet de modifier les paramètres. En appuyant sur le bouton OK à nouveau on peut relancer le jeu. Si un thread
; est en cours il est détruit avant d'en lancer un autre.
;
WindowXSize = 800
WindowYSize = 600
ScreenXSize = 720
ScreenYSize = 520
Quit = #False
If OpenWindow(#Window_Main, 0, 0, WindowXSize, WindowYSize, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  InitSprite()
  OpenWindowedScreen(WindowID(#Window_Main), 40, 60, ScreenXSize, ScreenYSize, #False, 0, 0)
  AddKeyboardShortcut(#Window_Main, #PB_Shortcut_Escape, #PB_Shortcut_Escape)
  AddKeyboardShortcut(#Window_Main, #PB_Shortcut_Return, #PB_Shortcut_Return)
  FrameGadget(#Gadget_Frame3D1, 200, 10, 130, 40, "Delay 250ms")
  TrackBarGadget(#Gadget_TrackBar, 215, 25, 100, 20, 1, 250)
  SetGadgetState(#Gadget_TrackBar, 250)
  FrameGadget(#Gadget_Frame3D2, 10, 10, 90, 40, "N")
  SpinGadget(#Gadget_Spin, 25, 25, 70, 20, 3, 12)
  SetGadgetState(#Gadget_Spin, 5)
  SetGadgetText(#Gadget_Spin, "5")
  ButtonGadget(#Gadget_Button, 120, 23, 60, 20, "OK")
  
  ThreadID = CreateThread(@HanoiTowers(), 0)
  Repeat
    Select WindowEvent()
      Case #PB_Event_CloseWindow
        Quit = #True
      Case #PB_Event_Menu
        Select EventMenu()
          Case #PB_Shortcut_Escape
            Quit = #True
          Case #PB_Shortcut_Return
            If ThreadID <> 0
              KillThread(ThreadID)
            EndIf
            ThreadID = CreateThread(@HanoiTowers(), 0)
        EndSelect
      Case #PB_Event_Gadget
        Select EventGadget()
          Case #Gadget_Button
            If ThreadID <> 0
              KillThread(ThreadID)
            EndIf
            ThreadID = CreateThread(@HanoiTowers(), 0)
          Case #Gadget_Spin
            SetGadgetText(#Gadget_Spin, Str(GetGadgetState(#Gadget_Spin)))
            WindowEvent()
          Case #Gadget_TrackBar
            FrameGadget(#Gadget_Frame3D1, 200, 10, 130, 40, "Delay " + Str(GetGadgetState(#Gadget_TrackBar)) + "ms")
        EndSelect
    EndSelect
    FlipBuffers()
    ClearScreen(RGB(0, 0, 0))
    DiskSize = ScreenYSize / 30
    StartDrawing(ScreenOutput())
    For i = 1 To 3
      k = 0
      For j = 1 To MaxTowers
        If Towers(i, j) <> 0
          k + 1
        EndIf
      Next
      For j = 1 To MaxTowers
        If  Towers(i, j) <> 0
          ;
          ; If a tile takes place position is calculated and also a shaded color is drawn in nested boxes
          ; =======================================================================
          ; Si une pièce prend place, sa position est calculée et une couleur dégradée est générée par des boites gigognes
          ;
          XPosition = DiskSize * MaxTowers * i - DiskSize * Towers(i, j) / 2
          YPosition = (ScreenYSize - MaxTowers * DiskSize) / 2 + DiskSize * MaxTowers - k * DiskSize
          Width = DiskSize * Towers(i, j)
          Height = DiskSize
          Red = Red(Colors(Towers(i, j)))
          Green = Green(Colors(Towers(i, j)))
          Blue = Blue(Colors(Towers(i, j)))
          For l = 0 To DiskSize / 2
            Color = RGB(Red, Green, Blue)
            Box(XPosition + l, YPosition + l, Width - 2 * l, Height - 2 * l, Color)
            Red - l
            Green - l
            Blue - l
          Next
          k - 1
        EndIf
      Next
    Next
    ;
    ; Stats display
    ; ==============
    ; Affichage des statistiques
    ;
    BackColor(RGB( 0, 0, 0))
    FrontColor(RGB(255, 255, 255))
    DrawingMode(1)
    DrawText(10, 10, "FPS " + Str(FPS))
    DrawText(10, 30, "Moves " + Str(NMoves) + " / " + Str(TotalMoves))
    StopDrawing()
    If ElapsedMilliseconds() - tz => 1000
      FPS = NFrames
      NFrames = 0
      tz = ElapsedMilliseconds()
    EndIf
    NFrames + 1
    Delay(5)
  Until Quit
  If ThreadID <> 0
    KillThread(ThreadID)
  EndIf
  CloseWindow(#Window_Main)
EndIf
End

Posted: Mon Jun 28, 2004 11:43 pm
by thefool
Thanks!

Anyway i found a bug:

Put the N to 8 and delay to 1 and click go.
After some time you get "Array index out of bounds"

Posted: Tue Jun 29, 2004 12:21 am
by fweil
I got the same ... OK

Can't figure out what is wrong but it is an issue coming from the thread and variables that are unstable probably.

If you turn the debugger off, it works without stop and error.

If I fix this bug, I will edit the code ... later.

Rgrds