Files
Sudoku/parser/parser.go
T

107 lines
2.6 KiB
Go

package parser
import (
"errors"
"fmt"
"slices"
"strings"
"git.kleiax.de/homepage/field"
)
var (
ErrFieldIndex = errors.New("field index out of range")
ErrInvalidInput = errors.New("invalid puzzle input")
ErrNoPuzzles = errors.New("input contains no puzzles")
)
func ClassicProperties() field.Properties {
return field.Properties{
Rows: 9,
Columns: 9,
BlockRows: 3,
BlockColumns: 3,
BlockSizeRow: 3,
BlockSizeColumn: 3,
}
}
type Parser interface {
Parse([]byte) error
GetField(int) (*field.Field, error)
GetAllFields() []*field.Field
}
type ParserHelper struct {
fields []*field.Field
}
func (ph *ParserHelper) GetField(index int) (*field.Field, error) {
if ph == nil || index < 0 || index >= len(ph.fields) {
return nil, fmt.Errorf("%w: %d", ErrFieldIndex, index)
}
return ph.fields[index], nil
}
func (ph *ParserHelper) GetAllFields() []*field.Field {
if ph == nil {
return nil
}
return slices.Clone(ph.fields)
}
func classicField(digits string, rating float64) (*field.Field, error) {
props := ClassicProperties()
if len(digits) != props.Rows*props.Columns {
return nil, fmt.Errorf("%w: puzzle has %d characters, expected 81", ErrInvalidInput, len(digits))
}
props.Rating = rating
cells := make([][]field.Cell, props.Rows)
for row := 0; row < props.Rows; row++ {
cells[row] = make([]field.Cell, props.Columns)
for column := 0; column < props.Columns; column++ {
char := digits[row*props.Columns+column]
if char < '0' || char > '9' {
return nil, fmt.Errorf("%w: puzzle contains invalid character %q at position %d", ErrInvalidInput, char, row*props.Columns+column)
}
position := field.NewPosition(
row,
column,
row/props.BlockSizeRow,
column/props.BlockSizeColumn,
row%props.BlockSizeRow,
column%props.BlockSizeColumn,
)
cells[row][column] = *field.NewCell(int(char-'0'), position)
}
}
result, err := field.New(props, cells)
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrInvalidInput, err)
}
return result, nil
}
// PuzzleString parses one classic Sudoku represented by exactly 81 digits.
// Whitespace around the complete string is ignored; 0 denotes an empty cell.
type PuzzleString struct {
ParserHelper
}
func (p *PuzzleString) Parse(data []byte) error {
if p == nil {
return errors.New("nil puzzle string parser")
}
digits := strings.TrimSpace(string(data))
if digits == "" {
return ErrNoPuzzles
}
parsed, err := classicField(digits, 0)
if err != nil {
return fmt.Errorf("parse puzzle string: %w", err)
}
p.fields = []*field.Field{parsed}
return nil
}