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
Last edited: