umma.dev

Sudoku in CLI

Here I explain how to build a sudoku app with go-lang.

  • The aim of this application is to create an algorithm to fill in the cells of the puzzle with numbers, while ensuring rows and columns in a 3x3 grid contains numbers between 1 to 9 without repetition. And enable to the user to input values in different rows and columns, and check if this is correct within the 3x3 sub-grid as well as the whole grid.

  • Create a Backtracking Algorithm This fills the cells of the puzzle with numbers in each row, column and the sub-grid of 3x3, with digitals 1-9 that are no repeated within this sub-grid.

  • Create a function that takes in user’s input

  • Display puzzle to user in CLI and show progress

  • Create a Main function

Example Code

package main

import (
	"fmt"
	"math/rand"
	"time"
)

const (
	boardSize = 9
	boxSize   = 3
)

type board [boardSize][boardSize]int

func (b *board) print() {
	for i := 0; i < boardSize; i++ {
		for j := 0; j < boardSize; j++ {
			fmt.Printf("%d ", b[i][j])
		}
		fmt.Println()
	}
}

func (b *board) isSafe(row, col, num int) bool {
	// Check if the number is already in the same row, col or box
	for x := 0; x < boardSize; x++ {
		if b[row][x] == num || b[x][col] == num {
			return false
		}
	}
	// Check if the number is already in the same box
	startRow, startCol := row-row%boxSize, col-col%boxSize
	for i := 0; i < boxSize; i++ {
		for j := 0; j < boxSize; j++ {
			if b[i+startRow][j+startCol] == num {
				return false
			}
		}
	}
	return true
}

func (b *board) generate() {
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < boardSize; i++ {
		for j := 0; j < boardSize; j++ {
			num := rand.Intn(9) + 1
			for !b.isSafe(i, j, num) {
				num = rand.Intn(9) + 1
			}
			b[i][j] = num
		}
	}
}

func main() {
	var b board
	b.generate()
	b.print()
}

Another Take

Recently I asked Bard to provide me with a solution. In this solution I specified it to use Charm CLI packages where possible. Here’s the code it provided.

package main

import (
    "fmt"
    "github.com/charmbracelet/charm/cli"
    "math/rand"
)

func main() {
    app := cli.App("sudoku", "A CLI sudoku game")

    app.Action(func(c *cli.Context) error {
        board := generateBoard()
        printBoard(board)

        for {
            fmt.Println("Enter the row and column of the cell you want to change:")
            row, col, err := getRowCol(c)
            if err != nil {
                fmt.Println(err)
                continue
            }

            fmt.Println("Enter the new value for the cell:")
            value, err := getNewValue(c)
            if err != nil {
                fmt.Println(err)
                continue
            }

            board[row][col] = value
            printBoard(board)
        }

        return nil
    })

    app.Run(os.Args)
}

func generateBoard() [][]int {
    board := make([][]int, 9)
    for i := range board {
        board[i] = make([]int, 9)
        for j := range board[i] {
            board[i][j] = rand.Intn(9) + 1
        }
    }

    return board
}

func printBoard(board [][]int) {
    for i := range board {
        for j := range board[i] {
            fmt.Printf("%d ", board[i][j])
        }
        fmt.Println()
    }
}

func getRowCol(c *cli.Context) (int, int, error) {
    rowStr := c.Args()[0]
    colStr := c.Args()[1]

    row, err := strconv.Atoi(rowStr)
    if err != nil {
        return -1, -1, fmt.Errorf("invalid row: %s", rowStr)
    }

    col, err := strconv.Atoi(colStr)
    if err != nil {
        return -1, -1, fmt.Errorf("invalid column: %s", colStr)
    }

    return row, col, nil
}

func getNewValue(c *cli.Context) (int, error) {
    valueStr := c.Args()[2]

    value, err := strconv.Atoi(valueStr)
    if err != nil {
        return -1, fmt.Errorf("invalid value: %s", valueStr)
    }

    return value, nil
}