Share My Creation Tower of Hanoi

Hi, this could do with a bit more polish to finish it off but it's playable and took me quite a bit of time to process, so feel free to amend to your personal taste, if you do please send me a copy :) . Here's the code; no additional libraries are needed

B4X:
#Region  Project Attributes
#ApplicationLabel: DITL Hanoi
#VersionCode: 1
#VersionName: 1.0
#SupportedOrientations: portrait
#CanInstallToExternalStorage: False
#End Region

#Region  Activity Attributes
#FullScreen: False
#IncludeTitle: True
#End Region

Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
End Sub

Sub Globals
    Private pnlMain As Panel
    Private pnlGameBoard As Panel
    Private lblTitle As Label
    Private btnNewGame As Button
    Private btnAutoSolve As Button
    Private spnDisks As Spinner
    Private lblMoves As Label
    Private lblMinMoves As Label
    Private lblTime As Label
    ' Game Variables
    Private towers(3) As List ' Three towers
    Private diskViews As List ' Visual disk panels
    Private diskCount As Int = 5
    Private moveCount As Int = 0
    Private selectedDisk As Int = -1
    Private selectedTower As Int = -1
    Private gameStartTime As Long
    Private timer As Timer
    Private isAnimating As Boolean = False
    Private autoSolving As Boolean = False
    ' Animation
    Private animationTimer As Timer
    Private animatingDisk As Panel
    Private animStartX As Float
    Private animStartY As Float
    Private animEndX As Float
    Private animEndY As Float
    Private animSteps As Int
    Private animCurrentStep As Int
    Private animCallback As String
    ' Display Constants
    Private TOWER_HEIGHT As Int = 600
    Private TOWER_WIDTH As Int = 10
    Private DISK_HEIGHT As Int = 80
    Private BASE_WIDTH As Int = 150
    Private BASE_HEIGHT As Int = 20
    Private BOARD_WIDTH As Int
    Private BOARD_HEIGHT As Int = 700
    ' Colors (RGB values)
    Private diskColors() As Int = Array As Int(0xFFFF6B6B, 0xFF4ECDC4, 0xFF45B7D1, 0xFF96CEB4, 0xFFFECA57, 0xFFFF9FF3, 0xFFA29BFE)
    Private backgroundColor As Int = 0xFF2D3748
    Private towerColor As Int = 0xFF718096
    Private baseColor As Int = 0xFF4A5568
End Sub

Sub Activity_Create(FirstTime As Boolean)
    BOARD_WIDTH = 100%x
    CreateUI
    CallSubDelayed(Me, "DelayedInitialize")
End Sub

Sub DelayedInitialize
    InitializeGame
    StartTimer
End Sub

Sub CreateUI
    ' Main panel
    pnlMain.Initialize("")
    Activity.AddView(pnlMain, 0, 0, 100%x, 100%y)
    pnlMain.Color = 0xFF667EEA
    ' Title
    lblTitle.Initialize("")
    lblTitle.Text = "🗼 Tower of Hanoi"
    lblTitle.TextSize = 24
    lblTitle.TextColor = Colors.White
    lblTitle.Gravity = Gravity.CENTER
    lblTitle.Typeface = Typeface.DEFAULT_BOLD
    pnlMain.AddView(lblTitle, 0, 10dip, 100%x, 60dip)
    ' Controls panel
    Dim pnlControls As Panel
    pnlControls.Initialize("")
    pnlControls.Color = Colors.Transparent
    pnlMain.AddView(pnlControls, 10dip, 80dip, 100%x - 20dip, 100dip)
    ' Disk count spinner
    spnDisks.Initialize("spnDisks")
    Dim diskOptions As List
    diskOptions.Initialize
    For i = 3 To 7
        diskOptions.Add(i & " Disks")
    Next
    spnDisks.AddAll(diskOptions)
    spnDisks.SelectedIndex = 2
    pnlControls.AddView(spnDisks, 10dip, 10dip, 120dip, 40dip)
    ' New Game button
    btnNewGame.Initialize("btnNewGame")
    btnNewGame.Text = "New Game"
    btnNewGame.TextColor = Colors.White
    btnNewGame.Color = 0xFF4299E1
    pnlControls.AddView(btnNewGame, 140dip, 10dip, 100dip, 40dip)
    ' Auto Solve button
    btnAutoSolve.Initialize("btnAutoSolve")
    btnAutoSolve.Text = "Auto Solve"
    btnAutoSolve.TextColor = Colors.White
    btnAutoSolve.Color = 0xFF38A169
    pnlControls.AddView(btnAutoSolve, 250dip, 10dip, 100dip, 40dip)
    ' Stats panel
    Dim pnlStats As Panel
    pnlStats.Initialize("")
    pnlStats.Color = Colors.Transparent
    pnlMain.AddView(pnlStats, 0, 190dip, 100%x, 60dip)
    lblMoves.Initialize("")
    lblMoves.Text = "Moves: 0"
    lblMoves.TextColor = Colors.White
    lblMoves.TextSize = 16
    lblMoves.Gravity = Gravity.CENTER
    pnlStats.AddView(lblMoves, 10dip, 10dip, 100dip, 40dip)
    lblMinMoves.Initialize("")
    lblMinMoves.Text = "Min: 31"
    lblMinMoves.TextColor = Colors.White
    lblMinMoves.TextSize = 16
    lblMinMoves.Gravity = Gravity.CENTER
    pnlStats.AddView(lblMinMoves, 120dip, 10dip, 100dip, 40dip)
    lblTime.Initialize("")
    lblTime.Text = "Time: 00:00"
    lblTime.TextColor = Colors.White
    lblTime.TextSize = 16
    lblTime.Gravity = Gravity.CENTER
    pnlStats.AddView(lblTime, 230dip, 10dip, 100dip, 40dip)
    ' Game board
    pnlGameBoard.Initialize("pnlGameBoard")
    pnlGameBoard.Color = backgroundColor
    pnlMain.AddView(pnlGameBoard, 10dip, 260dip, 100%x - 20dip, BOARD_HEIGHT)
    ' Animation timer
    animationTimer.Initialize("animationTimer", 16) ' ~60fps
    CreateGameBoard
End Sub

Sub CreateGameBoard
    ' Clear existing towers and bases
    pnlGameBoard.RemoveAllViews
    Dim towerSpacing As Int = (BOARD_WIDTH - 60dip) / 3
    ' Create towers and bases
    For i = 0 To 2
        Dim towerX As Int = 30dip + i * towerSpacing + towerSpacing/2 - TOWER_WIDTH/2
        Dim baseX As Int = 30dip + i * towerSpacing + towerSpacing/2 - BASE_WIDTH/2
        ' Tower pole
        Dim pnlTower As Panel
        pnlTower.Initialize("tower" & i)
        pnlTower.Color = towerColor
        pnlTower.Tag = i
        pnlGameBoard.AddView(pnlTower, towerX, BOARD_HEIGHT - TOWER_HEIGHT - BASE_HEIGHT, TOWER_WIDTH, TOWER_HEIGHT)
        ' Base
        Dim pnlBase As Panel
        pnlBase.Initialize("base" & i)
        pnlBase.Color = baseColor
        pnlBase.Tag = i
        pnlGameBoard.AddView(pnlBase, baseX, BOARD_HEIGHT - BASE_HEIGHT, BASE_WIDTH, BASE_HEIGHT)
    Next
End Sub

Sub InitializeGame
    ' Initialize towers
    For i = 0 To 2
        towers(i).Initialize
    Next
    If diskViews.IsInitialized = False Then
        diskViews.Initialize
    End If
    diskCount = spnDisks.SelectedIndex + 3
    For i = diskCount To 1 Step -1
        towers(0).Add(i)
    Next
    ' Initialize game state
    moveCount = 0
    selectedDisk = -1
    selectedTower = -1
    isAnimating = False
    autoSolving = False
    gameStartTime = DateTime.Now
    UpdateDisplay
    UpdateStats
End Sub

Sub UpdateDisplay
    If pnlGameBoard.IsInitialized = False Then
        Return
    End If
    Try
        pnlGameBoard.RemoveAllViews
    Catch
        Log("Ignoring any errors during removal")
    End Try
    CreateGameBoard
    If diskViews.IsInitialized = False Then
        diskViews.Initialize
    End If
    diskViews.Clear
    Dim towerSpacing As Int = (BOARD_WIDTH - 60dip) / 3
    ' Create visual disks
    For towerIndex = 0 To 2
        Dim tower As List = towers(towerIndex)
        Dim towerCenterX As Int = 30dip + towerIndex * towerSpacing + towerSpacing/2
        For diskIndex = 0 To tower.Size - 1
            Dim diskSize As Int = tower.Get(diskIndex)
            Dim diskWidth As Int = 60dip + diskSize * 15dip
            Dim pnlDisk As Panel
            pnlDisk.Initialize("disk")
            pnlDisk.Color = diskColors(diskSize - 1)
            pnlDisk.Tag = CreateMap("size": diskSize, "tower": towerIndex, "index": diskIndex)
            Dim diskX As Int = towerCenterX - diskWidth/2
            Dim diskY As Int = BOARD_HEIGHT - BASE_HEIGHT - DISK_HEIGHT * (diskIndex + 1)
            pnlGameBoard.AddView(pnlDisk, diskX, diskY, diskWidth, DISK_HEIGHT)
            diskViews.Add(pnlDisk)
            ' Highlight selected disk
            If selectedTower = towerIndex And diskIndex = tower.Size - 1 Then
                pnlDisk.Color = 0xFFFFD700 ' Gold color for selection
            End If
        Next
    Next
End Sub

Sub UpdateStats
    lblMoves.Text = "Moves: " & moveCount
    lblMinMoves.Text = "Min: " & (Power(2, diskCount) - 1)
    Dim elapsed As Long = (DateTime.Now - gameStartTime) / 1000
    Dim minutes As Int = elapsed / 60
    Dim seconds As Int = elapsed Mod 60
    lblTime.Text = "Time: " & NumberFormat2(minutes, 2, 0, 0, False) & ":" & NumberFormat2(seconds, 2, 0, 0, False)
End Sub

Sub StartTimer
    timer.Initialize("timer", 1000)
    timer.Enabled = True
End Sub

Sub timer_Tick
    If autoSolving = False Then UpdateStats
End Sub

Sub pnlGameBoard_Touch (Action As Int, X As Float, Y As Float)
    If isAnimating Or autoSolving Then Return
    ' Determine which tower was clicked based on X position
    Dim towerSpacing As Int = (BOARD_WIDTH - 60dip) / 3
    Dim clickedTower As Int = -1
    ' Calculate which tower was clicked
    If X < towerSpacing Then
        clickedTower = 0
    Else If X < towerSpacing * 2 Then
        clickedTower = 1
    Else
        clickedTower = 2
    End If
    ' Handle tower selection and moves
    If selectedTower = -1 Then
        ' Select a disk from clicked tower
        If towers(clickedTower).Size > 0 Then
            selectedTower = clickedTower
            selectedDisk = towers(clickedTower).Get(towers(clickedTower).Size - 1)
            UpdateDisplay
        End If
    Else
        ' Try to move selected disk to clicked tower
        If clickedTower = selectedTower Then
            ' Deselect if clicking same tower
            selectedTower = -1
            selectedDisk = -1
            UpdateDisplay
        Else
            ' Attempt to move disk
            If IsValidMove(selectedTower, clickedTower) Then
                MoveDisk(selectedTower, clickedTower)
            Else
                ' Invalid move - deselect
                selectedTower = -1
                selectedDisk = -1
                UpdateDisplay
            End If
        End If
    End If
End Sub

Sub IsValidMove(fromTower As Int, toTower As Int) As Boolean
    If towers(fromTower).Size = 0 Then Return False
    If towers(toTower).Size = 0 Then Return True
    Dim movingDisk As Int = towers(fromTower).Get(towers(fromTower).Size - 1)
    Dim targetDisk As Int = towers(toTower).Get(towers(toTower).Size - 1)
    Return movingDisk < targetDisk
End Sub

Sub MoveDisk(fromTower As Int, toTower As Int)
    isAnimating = True
    ' Move disk logically
    Dim disk As Int
    disk = towers(fromTower).Get(towers(fromTower).Size - 1)
    towers(fromTower).RemoveAt(towers(fromTower).Size - 1)
    towers(toTower).Add(disk)
    moveCount = moveCount + 1
    selectedTower = -1
    selectedDisk = -1
    ' Animate the move
    AnimateMove(disk, fromTower, toTower, "MoveComplete")
End Sub

Sub AnimateMove(diskSize As Int, fromTower As Int, toTower As Int, callback As String)
    ' Find the disk panel to animate
    For Each pnl As Panel In diskViews
        Dim diskInfo As Map = pnl.Tag
        If diskInfo.Get("size") = diskSize Then
            animatingDisk = pnl
            Exit
        End If
    Next
    If animatingDisk = Null Then
        CallSub(Me, callback)
        Return
    End If
    ' Calculate path
    Dim towerSpacing As Int = (BOARD_WIDTH - 60dip) / 3
    Dim fromX As Int = 30dip + fromTower * towerSpacing + towerSpacing/2 - animatingDisk.Width/2
    Dim toX As Int = 30dip + toTower * towerSpacing + towerSpacing/2 - animatingDisk.Width/2
    Dim upY As Int = 50dip ' Move up position
    Dim downY As Int = BOARD_HEIGHT - BASE_HEIGHT - DISK_HEIGHT * towers(toTower).Size
    animStartX = fromX
    animStartY = animatingDisk.Top
    animEndX = fromX
    animEndY = upY
    animSteps = 20
    animCurrentStep = 0
    animCallback = callback
    animationTimer.Enabled = True
End Sub

Sub animationTimer_Tick
    animCurrentStep = animCurrentStep + 1
    If animCurrentStep <= animSteps Then
        ' Interpolate position
        Dim progress As Float = animCurrentStep / animSteps
        Dim currentX As Int = animStartX + (animEndX - animStartX) * progress
        Dim currentY As Int = animStartY + (animEndY - animStartY) * progress
        animatingDisk.Left = currentX
        animatingDisk.Top = currentY
    Else
        ' Animation phase complete
        animationTimer.Enabled = False
        isAnimating = False
        UpdateDisplay
        CheckWin
        CallSub(Me, animCallback)
    End If
End Sub

Sub MoveComplete
    '
End Sub

Sub CheckWin
    If towers(2).Size = diskCount Then
        timer.Enabled = False
        Msgbox("🎉 Congratulations! You solved the Tower of Hanoi! 🎉", "Victory!")
    End If
End Sub

Sub btnNewGame_Click
    InitializeGame
End Sub

Sub btnAutoSolve_Click
    If autoSolving = False Then
        autoSolving = True
        btnAutoSolve.Enabled = False
        AutoSolve(diskCount, 0, 2, 1)
    End If
End Sub

Sub AutoSolve(n As Int, fromTower As Int, toTower As Int, auxTower As Int)
    If n = 1 Then
        If IsValidMove(fromTower, toTower) Then
            MoveDisk(fromTower, toTower)
        End If
        Return
    End If
    ' Move n-1 disks to  tower
    AutoSolve(n - 1, fromTower, auxTower, toTower)
    ' Move the bottom disk to destination
    If IsValidMove(fromTower, toTower) Then
        MoveDisk(fromTower, toTower)
    End If
    ' Move n-1 disks from current to destination
    AutoSolve(n - 1, auxTower, toTower, fromTower)
    autoSolving = False
    btnAutoSolve.Enabled = True
End Sub

Sub spnDisks_ItemClick (Position As Int, Value As Object)
    InitializeGame
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)
    timer.Enabled = False
    animationTimer.Enabled = False
End Sub
 

Attachments

  • Screenshot_20250821-100614.png
    Screenshot_20250821-100614.png
    54 KB · Views: 270
  • Screenshot_20250821-100914.png
    Screenshot_20250821-100914.png
    81 KB · Views: 37
  • Screenshot_20250821-100950.png
    Screenshot_20250821-100950.png
    55.1 KB · Views: 35
Last edited:
Top