Refactor Sudoku solver architecture

This commit is contained in:
2026-09-10 23:11:34 +02:00
parent 7bf4a8d725
commit a5ec338277
21 changed files with 475 additions and 260 deletions
+123
View File
@@ -0,0 +1,123 @@
package field
// ────────────────────────────────────────────────────────────────────────────── //
// NOTES STRUCTURE //
// ────────────────────────────────────────────────────────────────────────────── //
import "slices"
type Notes struct {
numbers []int
}
func (n *Notes) Add(field *Field, cell *Cell, note int, trigger string, marks []Mark) {
if slices.Contains(n.numbers, note) {
return // Note already exists
}
field.changes = append(field.changes, Change{
Cell: cell,
marks: marks,
action: ActionSetNote,
value: note,
from: 0,
triggerdBy: trigger,
})
n.numbers = append(n.numbers, note)
}
func (n *Notes) Remove(field *Field, cell *Cell, note int, trigger string, marks []Mark) {
for i, existing := range n.numbers {
if existing == note {
field.changes = append(field.changes, Change{
Cell: cell,
marks: marks,
action: ActionRemoveNote,
value: 0,
from: note,
triggerdBy: trigger,
})
n.numbers = append(n.numbers[:i], n.numbers[i+1:]...)
return
}
}
}
func (n *Notes) Get() []int {
copySlice := make([]int, len(n.numbers))
copy(copySlice, n.numbers)
return copySlice
}
func (n *Notes) Has(i int) bool {
return slices.Contains(n.numbers, i)
}
// ────────────────────────────────────────────────────────────────────────────── //
// CELL STRUCTURE //
// ────────────────────────────────────────────────────────────────────────────── //
type Cell struct {
number int
Notes *Notes
Pos *Position
}
func NewCell(number int, pos *Position) *Cell {
cell := Cell{
number: number,
Pos: pos,
Notes: &Notes{},
}
return &cell
}
func (c *Cell) SetNumber(field *Field, n int, trigger string, marks []Mark) {
if c.number == n {
return
}
if field == nil {
return
}
if n <= 0 || n > field.props.Rows || n > field.props.Columns {
return
}
field.changes = append(field.changes, Change{
Cell: c,
marks: marks,
action: ActionSetNumber,
value: n,
from: c.number,
triggerdBy: trigger,
})
c.number = n
}
func (c *Cell) RemoveNumber(field *Field, trigger string, marks []Mark) {
if c.number == 0 {
return
}
if field == nil {
return
}
field.changes = append(field.changes, Change{
action: ActionSetNumber,
Cell: c,
value: 0,
from: c.number,
marks: marks,
triggerdBy: trigger,
})
c.number = 0
}
func (c *Cell) GetNumber() int {
return c.number
}
+108
View File
@@ -0,0 +1,108 @@
package field
import (
"image/color"
)
// ────────────────────────────────────────────────────────────────────────────── //
// MARK STRUCTURE //
// ────────────────────────────────────────────────────────────────────────────── //
type Mark struct {
Cell *Cell
Change *Change
color color.Color
}
func (m *Mark) GetColor() color.Color {
return m.color
}
// ────────────────────────────────────────────────────────────────────────────── //
// CHANGE_ACTION TYPE //
// ────────────────────────────────────────────────────────────────────────────── //
type ChangeAction int
const (
ActionSetNumber = iota
ActionSetNote
ActionRemoveNumber
ActionRemoveNote
)
// ────────────────────────────────────────────────────────────────────────────── //
// CHANGE STRUCTURE //
// ────────────────────────────────────────────────────────────────────────────── //
type ExternalChange struct {
Cell *Cell
Marks []Mark
Action ChangeAction
Value int
From int
TriggerdBy string //strategy or manuell
}
// ────────────────────────────────────────────────────────────────────────────── //
// CHANGE STRUCTURE //
// ────────────────────────────────────────────────────────────────────────────── //
type Change struct {
Cell *Cell
marks []Mark
action ChangeAction
value int
from int
triggerdBy string //strategy or manuell
}
// ────────────────────────────────────────────────────────────────────────────── //
// GETTER //
// ────────────────────────────────────────────────────────────────────────────── //
func (c *Change) GetMarks() []Mark {
return c.marks
}
func (c *Change) GetAction() ChangeAction {
return c.action
}
func (c *Change) GetTo() int {
return c.value
}
func (c *Change) GetFrom() int {
return c.from
}
func (c *Change) GetTriggeredBy() string {
return c.triggerdBy
}
// ────────────────────────────────────────────────────────────────────────────── //
// ??? //
// ────────────────────────────────────────────────────────────────────────────── //
func (c *Change) do() {
switch c.action {
case ActionSetNumber:
c.Cell.number = c.value
case ActionSetNote:
c.Cell.Notes.numbers = append(c.Cell.Notes.numbers, c.value)
case ActionRemoveNumber:
c.Cell.number = 0
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
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
package field
import "errors"
var (
outOfBound = errors.New("invalid coordinates")
invalidField = errors.New("can not parse Field")
)
+307
View File
@@ -0,0 +1,307 @@
package field
import (
"fmt"
"strings"
)
// ────────────────────────────────────────────────────────────────────────────── //
// META STRUCTURE //
// ────────────────────────────────────────────────────────────────────────────── //
type Properties struct {
Rows int
Columns int
BlockRows int
BlockColumns int
BlockSizeRow int
BlockSizeColumn int
Rating float64
}
// ────────────────────────────────────────────────────────────────────────────── //
// FIELD STRUCTURE //
// ────────────────────────────────────────────────────────────────────────────── //
type Field struct {
props *Properties
cells [][]Cell
changes []Change
}
func New(props Properties, cells [][]Cell) *Field {
field := Field{
props: &props,
cells: cells,
}
return &field
}
// ────────────────────────────────────────────────────────────────────────────── //
// GETTER //
// ────────────────────────────────────────────────────────────────────────────── //
func (f *Field) GetRow(r int) (*Row, error) {
if r >= f.props.Rows || r < 0 {
return nil, outOfBound
}
cellPtrs := make([]*Cell, len(f.cells[r]))
for i := range f.cells[r] {
cellPtrs[i] = &f.cells[r][i]
}
return &Row{
Line: Line{
cells: cellPtrs,
},
}, nil
}
func (f *Field) GetColumn(c int) (*Column, error) {
if c >= f.props.Columns || c < 0 {
return nil, outOfBound
}
// Performance
result := &Column{
Line: Line{
cells: make([]*Cell, 0, f.props.Rows),
},
}
// Copying
for _, row := range f.cells {
result.cells = append(result.cells, &row[c])
}
return result, nil
}
func (f *Field) GetBlock(r, c int) (*Block, error) {
if r < 0 || r >= f.props.BlockRows || c < 0 || c >= f.props.BlockColumns {
return nil, outOfBound
}
startRow := r * f.props.BlockSizeRow
startCol := c * f.props.BlockSizeColumn
block := &Block{
cells: make([][]*Cell, f.props.BlockSizeRow),
}
for row := range block.cells {
// Effizientes Kopieren der Zeile
block.cells[row] = make([]*Cell, f.props.BlockSizeColumn)
for column := range block.cells[row] {
block.cells[row][column] = &f.cells[startRow+row][startCol+column]
}
}
return block, nil
}
func (f *Field) GetEachPartAtPos(pos *Position) []Part {
row, err := f.GetRow(pos.row)
if err != nil {
return nil
}
column, err := f.GetColumn(pos.column)
if err != nil {
return nil
}
block, err := f.GetBlock(pos.blockRow, pos.blockColumn)
if err != nil {
return nil
}
return append([]Part{}, row, column, block)
}
func (f *Field) GetCell(r, c int) (*Cell, error) {
if r > f.props.Rows || c > f.props.Columns {
return nil, outOfBound
}
return &f.cells[r][c], nil
}
func (f *Field) GetRating() float64 {
return f.props.Rating
}
// ────────────────────────────────────────────────────────────────────────────── //
// FOREACH FUNCTIONS //
// ────────────────────────────────────────────────────────────────────────────── //
func (f *Field) ForEachPart(fn func(part Part)) {
f.ForEachRow(func(row *Row) {
fn(row)
})
f.ForEachColumn(func(column *Column) {
fn(column)
})
f.ForEachBlock(func(block *Block) {
fn(block)
})
}
func (f *Field) ForEachPartAtPos(pos *Position, fn func(part Part)) {
parts := f.GetEachPartAtPos(pos)
for _, part := range parts {
fn(part)
}
}
func (f *Field) ForEachRow(fn func(row *Row)) {
for i := range f.props.Rows {
row, err := f.GetRow(i)
if err != nil {
fmt.Println(err.Error())
return
}
fn(row)
}
}
func (f *Field) ForEachColumn(fn func(column *Column)) {
for i := range f.props.Columns {
column, err := f.GetColumn(i)
if err != nil {
fmt.Println(err.Error())
return
}
fn(column)
}
}
func (f *Field) ForEachBlock(fn func(block *Block)) {
for r := range f.props.BlockRows {
for c := range f.props.BlockColumns {
block, err := f.GetBlock(r, c)
if err != nil {
fmt.Println(err.Error())
return
}
fn(block)
}
}
}
func (f *Field) ForEachCell(fn func(cell *Cell)) {
for _, row := range f.cells {
for _, cell := range row {
fn(&cell)
}
}
}
// ────────────────────────────────────────────────────────────────────────────── //
// MODIFIER //
// ────────────────────────────────────────────────────────────────────────────── //
func (f *Field) AddChange(eChange *ExternalChange) {
change := Change{
Cell: eChange.Cell,
marks: eChange.Marks,
action: eChange.Action,
value: eChange.Value,
from: eChange.From,
triggerdBy: eChange.TriggerdBy,
}
change.do()
f.changes = append(f.changes, change)
}
func (f *Field) SetRating(rating float64) {
f.props.Rating = rating
}
// ────────────────────────────────────────────────────────────────────────────── //
// STATE //
// ────────────────────────────────────────────────────────────────────────────── //
func (f *Field) String() string {
//TODO: auf beliebige größen anpassen
var sb strings.Builder
// Oberer Rahmen
sb.WriteString("╔═══════╤═══════╤═══════╗\n")
for i := range f.cells {
sb.WriteString("║ ") // Linke Rahmenseite
for j, cell := range f.cells[i] {
// Wert ausgeben oder Punkt für 0
val := cell.number
if val == 0 {
sb.WriteString("·")
} else {
sb.WriteString(string('0' + val))
}
// Trennlinien zwischen Blöcken und Zellen
if (j+1)%3 == 0 {
if j < 8 {
sb.WriteString(" │ ")
} else {
sb.WriteString(" ║\n") // Rechte Rahmenseite + Zeilenumbruch
}
} else {
sb.WriteString(" ")
}
}
// Horizontale Trennlinien nach jeder 3. Zeile
if (i+1)%3 == 0 && i < 8 {
sb.WriteString("╟───────┼───────┼───────╢\n")
}
}
// Unterer Rahmen
sb.WriteString("╚═══════╧═══════╧═══════╝")
return sb.String()
}
func (f *Field) StringNotesForNumber(n int) string {
return "StringNotesForNumber is not implementet"
}
func (f *Field) StringNotes() string {
var str strings.Builder
fmt.Fprintf(&str, "Notes:\n")
f.ForEachCell(func(cell *Cell) {
fmt.Fprintf(&str, "Pos: %d/%d - Notes: %v\n", cell.Pos.row, cell.Pos.column, cell.Notes.numbers)
})
return str.String()
}
func (f *Field) IsSolved() bool {
result := true
f.ForEachCell(func(cell *Cell) {
if cell.number == 0 {
result = false
}
})
/* TODO:
if !f.isValid() {
return false
}
*/
return result
}
func (f *Field) IsValid() bool {
//In jedem Part gibt es jede Zahl max ein mal
//Die Zahl kommt nicht in intersecting parts vor
return false
}
+148
View File
@@ -0,0 +1,148 @@
package field
import (
"fmt"
"strings"
)
type Part interface {
IsSolved() bool
ForEachCell(fn func(cell *Cell))
GetMissingNumbers() []int
RemoveNote(field *Field, note int, trigger string, marks []Mark)
String() string
}
// ────────────────────────────────────────────────────────────────────────────── //
// LINE STRUCTURE //
// ────────────────────────────────────────────────────────────────────────────── //
type Line struct {
cells []*Cell
}
func (l *Line) IsSolved() bool {
//TODO
return false
}
func (l *Line) ForEachCell(fn func(cell *Cell)) {
for _, cell := range l.cells {
fn(cell)
}
}
func (l *Line) GetMissingNumbers() []int {
return getMissingNumbersHelper(len(l.cells), l.ForEachCell)
}
func (l *Line) RemoveNote(field *Field, note int, trigger string, marks []Mark) {
l.ForEachCell(func(cell *Cell) {
cell.Notes.Remove(field, cell, note, trigger, marks)
})
}
func (l *Line) String() string {
return lineString(l, "Line")
}
// ────────────────────────────────────────────────────────────────────────────── //
// ROW STRUCTURE //
// ────────────────────────────────────────────────────────────────────────────── //
type Row struct {
Line
}
func (r *Row) String() string {
return lineString(r, "Row")
}
// ────────────────────────────────────────────────────────────────────────────── //
// COLUMN STRUCTURE //
// ────────────────────────────────────────────────────────────────────────────── //
type Column struct {
Line
}
func (c *Column) String() string {
return lineString(c, "Column")
}
// ────────────────────────────────────────────────────────────────────────────── //
// BLOCK STRUCTURE //
// ────────────────────────────────────────────────────────────────────────────── //
type Block struct {
cells [][]*Cell
}
func (b *Block) IsSolved() bool {
//TODO
return false
}
func (b *Block) ForEachCell(fn func(cell *Cell)) {
for _, row := range b.cells {
for _, cell := range row {
fn(cell)
}
}
}
func (b *Block) GetMissingNumbers() []int {
max := len(b.cells) * len(b.cells[0])
return getMissingNumbersHelper(max, b.ForEachCell)
}
func (b *Block) RemoveNote(field *Field, note int, trigger string, marks []Mark) {
b.ForEachCell(func(cell *Cell) {
cell.Notes.Remove(field, cell, note, trigger, marks)
})
}
func (b *Block) String() string {
var str strings.Builder
str.WriteString("Block:\t")
lastLine := 0
b.ForEachCell(func(cell *Cell) {
fmt.Fprintf(&str, "%d ", cell.number)
if lastLine == cell.Pos.inBlockRow {
}
})
return str.String()
}
// ────────────────────────────────────────────────────────────────────────────── //
// HELPER //
// ────────────────────────────────────────────────────────────────────────────── //
func lineString(part Part, name string) string {
var str strings.Builder
fmt.Fprintf(&str, "%s:\t", name)
part.ForEachCell(func(cell *Cell) {
fmt.Fprintf(&str, "%d ", cell.number)
})
return str.String()
}
func getMissingNumbersHelper(max int, iterate func(fn func(cell *Cell))) []int {
present := make(map[int]bool, max)
iterate(func(cell *Cell) {
if cell.number != 0 {
present[cell.number] = true
}
})
var missing []int
for i := 1; i <= max; i++ {
if !present[i] {
missing = append(missing, i)
}
}
return missing
}
+58
View File
@@ -0,0 +1,58 @@
package field
type Position struct {
row int
column int
blockRow int
blockColumn int
inBlockRow int
inBlockColumn int
}
func NewPosition(row, column, blockRow, blockColumn, inBlockRow, inBlockColumn int) *Position {
pos := Position{
row: row,
column: column,
blockRow: blockRow,
blockColumn: blockColumn,
inBlockRow: inBlockRow,
inBlockColumn: inBlockColumn,
}
return &pos
}
func (p *Position) GetRow() int {
return p.row
}
func (p *Position) GetColumn() int {
return p.column
}
func (p *Position) GetBlockRow() int {
return p.blockRow
}
func (p *Position) GetBlockColumn() int {
return p.blockColumn
}
func (p *Position) GetInBlockRow() int {
return p.inBlockRow
}
func (p *Position) GetInBlockColumn() int {
return p.inBlockColumn
}
func (p *Position) GetCoords() (int, int) {
return p.row, p.column
}
func (p *Position) GetBlockCoords() (int, int) {
return p.blockRow, p.blockColumn
}
func (p *Position) GetInBlockCoords() (int, int) {
return p.inBlockRow, p.inBlockColumn
}