Skip to content

Instantly share code, notes, and snippets.

@dhaninugraha
Last active December 13, 2023 13:05
Show Gist options
  • Save dhaninugraha/e27cee95e0447a1a3f4556cf80214aa9 to your computer and use it in GitHub Desktop.
Save dhaninugraha/e27cee95e0447a1a3f4556cf80214aa9 to your computer and use it in GitHub Desktop.
golang - Tower of Hanoi
package main
import (
"errors"
"fmt"
"os"
"reflect"
"strconv"
"strings"
)
type Hanoi struct {
CurrentState [][]int
DesiredState [][]int
StackHeight int
NoOfSuccessfulMoves int
}
const (
defaultNoOfPoles = 3
defaultMinNoOfDisks = 3
defaultMaxNoOfDisks = 10
defaultMargin = 2
)
func (hanoi *Hanoi) DesiredStateAchieved() bool {
if reflect.DeepEqual(hanoi.CurrentState, hanoi.DesiredState) {
return true
}
return false
}
func (hanoi *Hanoi) ShowTower() {
var b strings.Builder
margin := defaultMargin
maxWidth := (hanoi.StackHeight * 2) + 1
/* draw pole # */
for i := 0; i < defaultNoOfPoles; i++ {
fmt.Fprintf(&b, "%s%d%s", strings.Repeat(" ", hanoi.StackHeight+margin), i+1, strings.Repeat(" ", hanoi.StackHeight+margin))
}
b.WriteString("\n")
/* blank space between pole # and exposed pole */
for i := 0; i < defaultNoOfPoles; i++ {
fmt.Fprintf(&b, "%s%s%s", strings.Repeat(" ", hanoi.StackHeight+margin), " ", strings.Repeat(" ", hanoi.StackHeight+margin))
}
b.WriteString("\n")
/* draw exposed pole */
for i := 0; i < defaultNoOfPoles; i++ {
fmt.Fprintf(&b, "%s%s%s", strings.Repeat(" ", hanoi.StackHeight+margin), "\u2588", strings.Repeat(" ", hanoi.StackHeight+margin))
}
b.WriteString("\n")
/* draw stack of disks */
for height := 0; height < hanoi.StackHeight; height++ {
for pole := 0; pole < defaultNoOfPoles; pole++ {
currPole := hanoi.CurrentState[pole]
if currPole[height] == 0 {
fmt.Fprintf(&b, "%s%s%s", strings.Repeat(" ", hanoi.StackHeight+margin), "\u2588", strings.Repeat(" ", hanoi.StackHeight+margin))
} else {
sides := (currPole[height] * 2) + 1
spaces := (maxWidth - sides)
fmt.Fprintf(&b, "%s%s%s%s%s", strings.Repeat(" ", (spaces/2)+margin), strings.Repeat("\u2593", sides/2), "\u2593", strings.Repeat("\u2593", sides/2), strings.Repeat(" ", (spaces/2)+margin))
}
}
b.WriteString("\n")
}
/* draw base */
for i := 0; i < defaultNoOfPoles; i++ {
fmt.Fprintf(&b, "%s%s%s", strings.Repeat("\u2586", hanoi.StackHeight+margin), "\u2586", strings.Repeat("\u2586", hanoi.StackHeight+margin))
}
b.WriteString("\n")
fmt.Println(b.String())
}
func (hanoi *Hanoi) Move(sourcePole int, destPole int) (err error) {
if sourcePole < 1 || sourcePole > defaultNoOfPoles {
err = errors.New(fmt.Sprintf("Invalid source pole specified: %d", sourcePole))
return
}
if destPole < 1 || destPole > defaultNoOfPoles {
err = errors.New(fmt.Sprintf("Invalid destination pole specified: %d", destPole))
return
}
if sourcePole == destPole {
err = errors.New("Source and destination poles cannot be the same")
return
}
sourcePole -= 1
destPole -= 1
emptyPoleReference := make([]int, hanoi.StackHeight)
if reflect.DeepEqual(hanoi.CurrentState[sourcePole], emptyPoleReference) {
err = errors.New("Nothing to move")
return
}
// is this even necessary?
// for i, val := range hanoi.CurrentState[destPole] {
// if i == 0 && val > 0 {
// err = errors.New("Destination pole is full")
// return
// }
// }
var currDisk int
var sourceIdx int
var destIdx int
for i, val := range hanoi.CurrentState[sourcePole] {
if val > 0 {
currDisk = val
sourceIdx = i
hanoi.CurrentState[sourcePole][i] = 0
break
}
}
for i, val := range hanoi.CurrentState[destPole] {
if val == 0 {
destIdx = i
}
if i+1 < hanoi.StackHeight {
if hanoi.CurrentState[destPole][i+1] == 0 {
continue
} else {
break
}
}
}
if destIdx == hanoi.StackHeight-1 {
hanoi.CurrentState[destPole][destIdx] = currDisk
} else {
if currDisk < hanoi.CurrentState[destPole][destIdx+1] {
hanoi.CurrentState[destPole][destIdx] = currDisk
} else {
hanoi.CurrentState[sourcePole][sourceIdx] = currDisk
err = errors.New("Cannot move a disk that is larger than the destination's top disk")
return
}
}
hanoi.NoOfSuccessfulMoves += 1
return
}
func (hanoi *Hanoi) SetDestPole(destPole int) (err error) {
if destPole < 1 || destPole > defaultNoOfPoles {
err = errors.New(fmt.Sprintf("Invalid destination pole specified: %d", destPole))
return
}
destPole -= 1
for diskNo := 0; diskNo < hanoi.StackHeight; diskNo++ {
hanoi.DesiredState[destPole][diskNo] = diskNo + 1
}
return
}
func NewHanoi(noOfDisks int) (hanoi Hanoi, err error) {
if noOfDisks < defaultMinNoOfDisks || noOfDisks > defaultMaxNoOfDisks {
err = errors.New(fmt.Sprintf("Invalid number of disks: %d", noOfDisks))
return
}
hanoi.CurrentState = make([][]int, defaultNoOfPoles, defaultNoOfPoles)
hanoi.DesiredState = make([][]int, defaultNoOfPoles, defaultNoOfPoles)
hanoi.StackHeight = noOfDisks
for poleNo := 0; poleNo < defaultNoOfPoles; poleNo++ {
hanoi.CurrentState[poleNo] = make([]int, hanoi.StackHeight, hanoi.StackHeight)
hanoi.DesiredState[poleNo] = make([]int, hanoi.StackHeight, hanoi.StackHeight)
if poleNo == 0 {
for diskNo := 0; diskNo < hanoi.StackHeight; diskNo++ {
hanoi.CurrentState[poleNo][diskNo] = diskNo + 1
}
}
}
return
}
func main() {
var backToMainMenu bool
for {
fmt.Println("\033[H\033[2J")
fmt.Println("Welcome to The Tower of Hanoi.")
fmt.Println("Your options are:")
fmt.Println(" (P)lay")
fmt.Println(" (E)xit")
fmt.Printf("Please enter a selection: ")
kbInput := ""
fmt.Scanln(&kbInput)
switch strings.ToLower(kbInput) {
case "":
continue
case "e":
fmt.Println("\033[H\033[2J")
fmt.Println("Goodbye.\n")
os.Exit(0)
case "p":
for {
if backToMainMenu {
backToMainMenu = false
break
}
fmt.Println("\033[H\033[2J")
fmt.Printf("Enter # of disks (%d..%d): ", defaultMinNoOfDisks, defaultMaxNoOfDisks)
kbInput := ""
fmt.Scanln(&kbInput)
noOfDisks, err := strconv.Atoi(kbInput)
if err != nil {
fmt.Printf("%q is not a valid number.\n", kbInput)
fmt.Printf("Press any key to retry...")
fmt.Scanln()
continue
}
hanoi, err := NewHanoi(noOfDisks)
if err != nil {
fmt.Println(err)
fmt.Printf("Press any key to retry...")
fmt.Scanln()
continue
}
fmt.Printf("Enter destination pole (2..3): ")
kbInput = ""
fmt.Scanln(&kbInput)
destPole, err := strconv.Atoi(kbInput)
if err != nil {
fmt.Printf("%q is not a valid number.\n", kbInput)
fmt.Printf("Press any key to retry...")
fmt.Scanln()
continue
}
err = hanoi.SetDestPole(destPole)
if err != nil {
fmt.Println(err)
fmt.Printf("Press any key to retry...")
fmt.Scanln()
continue
}
hanoi.ShowTower()
fmt.Printf("You have chosen a stack of %d disks to be moved to pole %d.\n", noOfDisks, destPole)
fmt.Printf("Press any key to begin...")
fmt.Scanln()
for {
fmt.Println("\033[H\033[2J")
hanoi.ShowTower()
fmt.Printf("# of moves: %d\n", hanoi.NoOfSuccessfulMoves)
if hanoi.DesiredStateAchieved() {
fmt.Println("Congratulations! You've finished the tower in", hanoi.NoOfSuccessfulMoves, "steps!")
fmt.Printf("Press any key to go back to the main menu...")
backToMainMenu = true
fmt.Scanln()
break
}
fmt.Printf("Move disk from pole: ")
kbInput := ""
fmt.Scanln(&kbInput)
fromPole, errFromPole := strconv.Atoi(kbInput)
if errFromPole != nil {
fmt.Printf("%q is not a valid pole.\n")
fmt.Printf("Press any key to retry...")
fmt.Scanln()
continue
}
fmt.Printf("...to pole: ")
kbInput = ""
fmt.Scanln(&kbInput)
toPole, errToPole := strconv.Atoi(kbInput)
if errToPole != nil {
fmt.Printf("%q is not a valid pole.\n")
fmt.Printf("Press any key to retry...")
fmt.Scanln()
continue
}
err := hanoi.Move(fromPole, toPole)
if err != nil {
fmt.Println("Illegal move:", err)
fmt.Printf("Press any key to retry...")
fmt.Scanln()
continue
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment