Refactor Sudoku field and solver implementation
This commit is contained in:
+90
-14
@@ -1,30 +1,106 @@
|
||||
package parser
|
||||
|
||||
import "git.kleiax.de/homepage/field"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
//TODO: const für klassik sudoku board.Props
|
||||
//TODO: funktion für [][]Cell generierung
|
||||
"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(data []byte) error
|
||||
GetField(i int) *field.Field
|
||||
GetAllFields() []field.Field
|
||||
Parse([]byte) error
|
||||
GetField(int) (*field.Field, error)
|
||||
GetAllFields() []*field.Field
|
||||
}
|
||||
|
||||
type ParserHelper struct {
|
||||
fields []field.Field
|
||||
fields []*field.Field
|
||||
}
|
||||
|
||||
func (ph *ParserHelper) GetField(i int) *field.Field {
|
||||
if i < 0 || i >= len(ph.fields) {
|
||||
return &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[i]
|
||||
return ph.fields[index], nil
|
||||
}
|
||||
|
||||
func (ph *ParserHelper) GetAllFields() []field.Field {
|
||||
if len(ph.fields) == 0 {
|
||||
func (ph *ParserHelper) GetAllFields() []*field.Field {
|
||||
if ph == nil {
|
||||
return nil
|
||||
}
|
||||
return ph.fields
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user