To read a board back - and to check a solver's output - you need to turn a grid of numbers into a string. Today you write the printer, giving you a parse-print round trip and the first usable surface of the library.
Render a grid back to an 81-character string, using a dot for each blank.
Printing is the mirror image of parsing: walk the 81 cells in order and emit one
character each, a . for a blank and the digit itself otherwise. Choosing .
(rather than 0) for blanks gives a canonical form, so a puzzle written with
zeros and the same puzzle written with dots print identically once parsed. That
canonical string is what you will assert solutions against for the rest of the
project.
With parse and print in place the library already does something useful end to end: read a puzzle, hold it as numbers, hand it back as text. Every solver you write from here takes a grid and returns a grid, and this printer is how you will read the answer.
// inverse of Parse: 0 -> '.', a digit -> its characterfunc GridString(g [81]int) string {b := make([]byte, 81)for i, v := range g {if v == 0 { b[i] = '.' } else { b[i] = byte('0' + v) }}return string(b)}