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 }