107 lines
2.6 KiB
Go
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
|
|
}
|