Refactor Sudoku field and solver implementation
This commit is contained in:
+166
-40
@@ -1,13 +1,11 @@
|
||||
package field
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// MARK STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type Mark struct {
|
||||
Cell *Cell
|
||||
Change *Change
|
||||
@@ -15,94 +13,222 @@ type Mark struct {
|
||||
}
|
||||
|
||||
func (m *Mark) GetColor() color.Color {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return m.color
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// CHANGE_ACTION TYPE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type ChangeAction int
|
||||
|
||||
const (
|
||||
ActionSetNumber = iota
|
||||
ActionSetNumber ChangeAction = iota
|
||||
ActionSetNote
|
||||
ActionRemoveNumber
|
||||
ActionRemoveNote
|
||||
)
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// CHANGE STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type ExternalChange struct {
|
||||
Cell *Cell
|
||||
Marks []Mark
|
||||
Action ChangeAction
|
||||
Value int
|
||||
From int
|
||||
TriggerdBy string //strategy or manuell
|
||||
TriggerdBy string
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// CHANGE STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type Change struct {
|
||||
Cell *Cell
|
||||
marks []Mark
|
||||
action ChangeAction
|
||||
value int
|
||||
from int
|
||||
triggerdBy string //strategy or manuell
|
||||
triggerdBy string
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// GETTER //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
func (c *Change) GetMarks() []Mark {
|
||||
return c.marks
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return slices.Clone(c.marks)
|
||||
}
|
||||
|
||||
func (c *Change) GetAction() ChangeAction {
|
||||
if c == nil {
|
||||
return ActionSetNumber
|
||||
}
|
||||
return c.action
|
||||
}
|
||||
|
||||
func (c *Change) GetTo() int {
|
||||
if c == nil {
|
||||
return 0
|
||||
}
|
||||
return c.value
|
||||
}
|
||||
|
||||
func (c *Change) GetFrom() int {
|
||||
if c == nil {
|
||||
return 0
|
||||
}
|
||||
return c.from
|
||||
}
|
||||
|
||||
func (c *Change) GetTriggeredBy() string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
return c.triggerdBy
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// ??? //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
func (c *Change) do() {
|
||||
switch c.action {
|
||||
func (f *Field) AddChange(external *ExternalChange) error {
|
||||
if err := f.validateChange(external); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cell := external.Cell
|
||||
switch external.Action {
|
||||
case ActionSetNumber:
|
||||
c.Cell.number = c.value
|
||||
|
||||
if cell.number == external.Value {
|
||||
return nil
|
||||
}
|
||||
cell.number = external.Value
|
||||
f.recordChange(external)
|
||||
f.clearNotes(cell, external.TriggerdBy)
|
||||
f.removePeerNote(cell, external.Value, external.TriggerdBy)
|
||||
case ActionSetNote:
|
||||
c.Cell.Notes.numbers = append(c.Cell.Notes.numbers, c.value)
|
||||
|
||||
if cell.notes.Has(external.Value) {
|
||||
return nil
|
||||
}
|
||||
cell.notes.numbers = append(cell.notes.numbers, external.Value)
|
||||
slices.Sort(cell.notes.numbers)
|
||||
f.recordChange(external)
|
||||
case ActionRemoveNumber:
|
||||
c.Cell.number = 0
|
||||
|
||||
if cell.number == 0 {
|
||||
return nil
|
||||
}
|
||||
cell.number = 0
|
||||
f.recordChange(external)
|
||||
case ActionRemoveNote:
|
||||
for i, existing := range c.Cell.Notes.numbers {
|
||||
if existing == c.from {
|
||||
c.Cell.Notes.numbers = append(c.Cell.Notes.numbers[:i], c.Cell.Notes.numbers[i+1:]...)
|
||||
return
|
||||
for i, existing := range cell.notes.numbers {
|
||||
if existing == external.From {
|
||||
cell.notes.numbers = slices.Delete(cell.notes.numbers, i, i+1)
|
||||
f.recordChange(external)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Field) AddChanges(changes []ExternalChange) error {
|
||||
if f == nil || !f.IsValid() {
|
||||
return ErrInvalidField
|
||||
}
|
||||
state := f.snapshot()
|
||||
for i := range changes {
|
||||
if err := f.AddChange(&changes[i]); err != nil {
|
||||
f.restore(state)
|
||||
return fmt.Errorf("apply change %d: %w", i, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Field) GetChanges() []ExternalChange {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
result := make([]ExternalChange, len(f.changes))
|
||||
for i := range f.changes {
|
||||
result[i] = ExternalChange{Cell: f.changes[i].Cell, Marks: slices.Clone(f.changes[i].marks), Action: f.changes[i].action, Value: f.changes[i].value, From: f.changes[i].from, TriggerdBy: f.changes[i].triggerdBy}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (f *Field) validateChange(change *ExternalChange) error {
|
||||
if f == nil || !f.IsValid() {
|
||||
return ErrInvalidField
|
||||
}
|
||||
if change == nil || change.Cell == nil || !f.ownsCell(change.Cell) {
|
||||
return fmt.Errorf("%w: cell does not belong to field", ErrInvalidChange)
|
||||
}
|
||||
cell := change.Cell
|
||||
if cell.notes == nil || cell.pos == nil {
|
||||
return fmt.Errorf("%w: incomplete cell", ErrInvalidChange)
|
||||
}
|
||||
|
||||
switch change.Action {
|
||||
case ActionSetNumber:
|
||||
if change.Value < 1 || change.Value > f.props.Rows {
|
||||
return fmt.Errorf("%w: number %d is out of range", ErrInvalidChange, change.Value)
|
||||
}
|
||||
if change.From != cell.number {
|
||||
return fmt.Errorf("%w: expected previous value %d, got %d", ErrInvalidChange, cell.number, change.From)
|
||||
}
|
||||
if f.hasPeerNumber(cell, change.Value) {
|
||||
return fmt.Errorf("%w: number %d already exists in a peer", ErrInvalidChange, change.Value)
|
||||
}
|
||||
case ActionSetNote:
|
||||
if cell.number != 0 {
|
||||
return fmt.Errorf("%w: cannot add a note to a filled cell", ErrInvalidChange)
|
||||
}
|
||||
if change.Value < 1 || change.Value > f.props.Rows || f.hasPeerNumber(cell, change.Value) {
|
||||
return fmt.Errorf("%w: note %d is not a candidate", ErrInvalidChange, change.Value)
|
||||
}
|
||||
case ActionRemoveNumber:
|
||||
if change.From != cell.number {
|
||||
return fmt.Errorf("%w: expected previous value %d, got %d", ErrInvalidChange, cell.number, change.From)
|
||||
}
|
||||
case ActionRemoveNote:
|
||||
if change.From < 1 || change.From > f.props.Rows {
|
||||
return fmt.Errorf("%w: note %d is out of range", ErrInvalidChange, change.From)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%w: unknown action %d", ErrInvalidChange, change.Action)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Field) recordChange(external *ExternalChange) {
|
||||
f.changes = append(f.changes, Change{Cell: external.Cell, marks: slices.Clone(external.Marks), action: external.Action, value: external.Value, from: external.From, triggerdBy: external.TriggerdBy})
|
||||
}
|
||||
|
||||
func (f *Field) clearNotes(cell *Cell, trigger string) {
|
||||
for _, note := range slices.Clone(cell.notes.numbers) {
|
||||
cell.notes.numbers = removeNumber(cell.notes.numbers, note)
|
||||
f.recordChange(&ExternalChange{Cell: cell, Action: ActionRemoveNote, From: note, TriggerdBy: trigger})
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Field) removePeerNote(cell *Cell, number int, trigger string) {
|
||||
seen := make(map[*Cell]struct{})
|
||||
parts, err := f.GetEachPartAtPos(cell.pos)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, part := range parts {
|
||||
part.ForEachCell(func(peer *Cell) {
|
||||
if peer == cell {
|
||||
return
|
||||
}
|
||||
if _, exists := seen[peer]; exists {
|
||||
return
|
||||
}
|
||||
seen[peer] = struct{}{}
|
||||
if peer.notes.Has(number) {
|
||||
peer.notes.numbers = removeNumber(peer.notes.numbers, number)
|
||||
f.recordChange(&ExternalChange{Cell: peer, Action: ActionRemoveNote, From: number, TriggerdBy: trigger})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func removeNumber(numbers []int, number int) []int {
|
||||
for i, existing := range numbers {
|
||||
if existing == number {
|
||||
return slices.Delete(numbers, i, i+1)
|
||||
}
|
||||
}
|
||||
return numbers
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user