Refactor Sudoku field and solver implementation

This commit is contained in:
2026-09-16 22:52:44 +02:00
parent 8a5ae8c640
commit f7f9c16184
24 changed files with 1574 additions and 603 deletions
+54 -36
View File
@@ -1,65 +1,83 @@
package logic
import (
"errors"
"fmt"
"git.kleiax.de/homepage/field"
"git.kleiax.de/homepage/logic/strategies"
)
// ────────────────────────────────────────────────────────────────────────────── //
// SOLVER STRUCTURE //
// ────────────────────────────────────────────────────────────────────────────── //
var ErrNoProgress = errors.New("no strategy can solve the puzzle")
type Solver struct {
strategies []strategies.Strategy
returnTo int
field *field.Field
conf struct {
all bool
repeat bool
}
}
func (s *Solver) Add(strategy strategies.Strategy) {
s.strategies = append(s.strategies, strategy)
}
func (s *Solver) InitStragies(field *field.Field) {
s.returnTo = 1
s.field = field
for _, strategy := range s.strategies {
strategy.Init(field)
if strategy != nil {
s.strategies = append(s.strategies, strategy)
}
}
func (s *Solver) Run(i int) bool {
for j := 0; j < len(s.strategies); j++ {
if s.strategies[j].SearchProgressableCells() == 0 {
continue
func (s *Solver) InitStrategies(f *field.Field) error {
if f == nil || !f.IsValid() {
return field.ErrInvalidField
}
s.field = f
for _, strategy := range s.strategies {
strategy.Init(f)
}
return nil
}
// InitStragies is kept for compatibility. New code should use InitStrategies.
func (s *Solver) InitStragies(f *field.Field) error {
return s.InitStrategies(f)
}
// Run applies strategies from the requested index until the field is solved or
// no strategy can make progress. It returns false without an error for a valid
// but currently unsolved field.
func (s *Solver) Run(start int) (bool, error) {
if s.field == nil || !s.field.IsValid() {
return false, field.ErrInvalidField
}
if start < 0 || start > len(s.strategies) {
return false, fmt.Errorf("strategy index %d out of range", start)
}
for {
if s.field.IsSolved() {
return true, nil
}
for _, change := range s.strategies[j].ApplyAll() {
if change.Action != field.ActionSetNumber {
progress := false
for index := start; index < len(s.strategies); index++ {
strategy := s.strategies[index]
if strategy.SearchProgressableCells() == 0 {
continue
}
s.field.ForEachPartAtPos(change.Cell.Pos, func(part field.Part) {
part.ForEachCell(func(cell *field.Cell) {
cell.Notes.Remove(s.field, cell, change.Value, "remove note after insert of a number", nil)
})
})
changes, err := strategy.ApplyAll()
if err != nil {
return false, fmt.Errorf("apply strategy %q: %w", strategy.Name(), err)
}
if len(changes) == 0 {
continue
}
progress = true
break
}
j = s.returnTo - 1
if !progress {
return false, nil
}
start = 0
}
if s.field.IsSolved() {
return true
}
return false
}
func (s *Solver) Search() {
}
func (s *Solver) Search() {}
func (s *Solver) GetSolutionPath() []field.ExternalChange {
return nil