Last active
December 13, 2023 13:05
-
-
Save dhaninugraha/e27cee95e0447a1a3f4556cf80214aa9 to your computer and use it in GitHub Desktop.
golang - Tower of Hanoi
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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