From 7bf4a8d7250c2bf1921a0888bef4f850e79bd17e Mon Sep 17 00:00:00 2001 From: Alexander Klein Date: Sat, 25 Jul 2026 06:58:15 +0200 Subject: [PATCH] first successful run to solve a simple puzzle --- .vscode/launch.json | 15 ++++ board/cell.go | 6 ++ board/field.go | 27 +++++++- board/parser.go | 1 + board/state.go | 18 ++++- data/sudoku-exchange-puzzle-bank/easy3.txt | 2 +- logic/changes.go | 4 -- logic/notes.go | 1 - logic/solver.go | 32 +++++++-- logic/strategies/divers.go | 8 +-- logic/strategies/naked.go | 6 +- logic/strategies/notes.go | 81 ++++++++++++++++++++++ logic/strategies/strategy.go | 24 ++++--- main.go | 1 + todo.md | 3 +- 15 files changed, 201 insertions(+), 28 deletions(-) create mode 100644 .vscode/launch.json delete mode 100644 logic/changes.go delete mode 100644 logic/notes.go create mode 100644 logic/strategies/notes.go diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..608d3c6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${fileDirname}" + } + ] +} \ No newline at end of file diff --git a/board/cell.go b/board/cell.go index 1275d5b..42d7e4e 100644 --- a/board/cell.go +++ b/board/cell.go @@ -4,6 +4,8 @@ package board // NOTES STRUCTURE // // ────────────────────────────────────────────────────────────────────────────── // +import "slices" + type Notes struct { numbers []int } @@ -49,6 +51,10 @@ func (n *Notes) Get() []int { return copySlice } +func (n *Notes) Has(i int) bool { + return slices.Contains(n.numbers, i) +} + // ────────────────────────────────────────────────────────────────────────────── // // CELL STRUCTURE // // ────────────────────────────────────────────────────────────────────────────── // diff --git a/board/field.go b/board/field.go index fba5359..2174844 100644 --- a/board/field.go +++ b/board/field.go @@ -82,10 +82,28 @@ func (f *Field) GetBlock(r, c int) (*Block, error) { 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.rows || c > f.columns { return nil, outOfBound @@ -111,6 +129,13 @@ func (f *Field) ForEachPart(fn func(part Part)) { }) } +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.rows { row, err := f.GetRow(i) diff --git a/board/parser.go b/board/parser.go index ccbfc08..c27ee0a 100644 --- a/board/parser.go +++ b/board/parser.go @@ -80,6 +80,7 @@ func (pb *PuzzleBank) Parse(data []byte) error { return fmt.Errorf("Kann Zeichen nicht in Zahl konvertieren") } field.cells[i][j].number = number + field.cells[i][j].Notes = &Notes{} field.cells[i][j].Pos = &Position{ row: i, column: j, diff --git a/board/state.go b/board/state.go index 63c1beb..40eaa2a 100644 --- a/board/state.go +++ b/board/state.go @@ -1,6 +1,9 @@ package board -import "strings" +import ( + "fmt" + "strings" +) func (f *Field) String() string { //TODO: auf beliebige größen anpassen @@ -45,6 +48,19 @@ func (f *Field) String() string { 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) { diff --git a/data/sudoku-exchange-puzzle-bank/easy3.txt b/data/sudoku-exchange-puzzle-bank/easy3.txt index 6927a27..aa566dd 100644 --- a/data/sudoku-exchange-puzzle-bank/easy3.txt +++ b/data/sudoku-exchange-puzzle-bank/easy3.txt @@ -1,3 +1,3 @@ -0000183b305c 451723860007000800000816000000030000005000100730040086906000204840572093000409000 1.2 +0000183b305c 050703060007000800000816000000030000005000100730040086906000204840572093000409000 1.2 0001d5d6314e 302401809001000300000000000040708010780502036000090000200609003900000008800070005 1.2 000212406270 000823001003000400070000052300960010000102000010038006830000040002000900600789000 1.2 \ No newline at end of file diff --git a/logic/changes.go b/logic/changes.go deleted file mode 100644 index 94fcad2..0000000 --- a/logic/changes.go +++ /dev/null @@ -1,4 +0,0 @@ -package logic - -type Change struct { -} diff --git a/logic/notes.go b/logic/notes.go deleted file mode 100644 index 4c79103..0000000 --- a/logic/notes.go +++ /dev/null @@ -1 +0,0 @@ -package logic diff --git a/logic/solver.go b/logic/solver.go index 007df64..332f0ec 100644 --- a/logic/solver.go +++ b/logic/solver.go @@ -11,6 +11,8 @@ import ( type Solver struct { strategies []strategies.Strategy + returnTo int + field *board.Field conf struct { all bool repeat bool @@ -22,19 +24,37 @@ func (s *Solver) Add(strategy strategies.Strategy) { } func (s *Solver) InitStragies(field *board.Field) { + s.returnTo = 1 + s.field = field for _, strategy := range s.strategies { strategy.Init(field) } } func (s *Solver) Run(i int) bool { - //TODO: clean und Fehlerauffangen - s.strategies[i].SearchProgressableCells() - numberOfChanges := s.strategies[i].ApplyAll() - if numberOfChanges == 0 { - return false + for j := 0; j < len(s.strategies); j++ { + if s.strategies[j].SearchProgressableCells() == 0 { + continue + } + + for _, change := range s.strategies[j].ApplyAll() { + if change.Action != board.ActionSetNumber { + continue + } + s.field.ForEachPartAtPos(change.Cell.Pos, func(part board.Part) { + part.ForEachCell(func(cell *board.Cell) { + cell.Notes.Remove(s.field, cell, change.Value, "remove note after insert of a number", nil) + }) + }) + } + + j = s.returnTo - 1 } - return true + + if s.field.IsSolved() { + return true + } + return false } func (s *Solver) Search() { diff --git a/logic/strategies/divers.go b/logic/strategies/divers.go index 74b0138..8003a2c 100644 --- a/logic/strategies/divers.go +++ b/logic/strategies/divers.go @@ -1,8 +1,6 @@ package strategies import ( - "fmt" - "git.kleiax.de/homepage/board" ) @@ -24,6 +22,8 @@ func (ld *LastDigit) SearchProgressableCells() int { emptyCell = cell } }) + // fmt.Println(part) + // fmt.Printf("Gefundene Zelle: %d/%d - %d, missungNumber: %v, Typ: %T\n", emptyCell.Pos.GetRow(), emptyCell.Pos.GetColumn(), emptyCell.GetNumber(), missingNumbers, part) change := board.ExternalChange{ Cell: emptyCell, Action: board.ActionSetNumber, @@ -34,12 +34,12 @@ func (ld *LastDigit) SearchProgressableCells() int { ld.changes = append(ld.changes, change) } }) - fmt.Printf("LastDigit Changes %d\n", len(ld.changes)) + // fmt.Printf("LastDigit Changes %d\n", len(ld.changes)) return len(ld.changes) } func (ld *LastDigit) getName() string { - return "Last Digit - Letzte Zahl" + return "Last Digit" } // ────────────────────────────────────────────────────────────────────────────── // diff --git a/logic/strategies/naked.go b/logic/strategies/naked.go index 3bc1785..fcb7beb 100644 --- a/logic/strategies/naked.go +++ b/logic/strategies/naked.go @@ -14,7 +14,7 @@ func (ns *NakedSingle) SearchProgressableCells() int { ns.field.ForEachPart(func(part board.Part) { part.ForEachCell(func(cell *board.Cell) { candidates := cell.Notes.Get() - if len(candidates) == 1 { + if len(candidates) == 1 && cell.GetNumber() == 0 { change := board.ExternalChange{ Cell: cell, Action: board.ActionSetNumber, @@ -29,6 +29,10 @@ func (ns *NakedSingle) SearchProgressableCells() int { return len(ns.changes) } +func (ns *NakedSingle) getName() string { + return "Naked Single" +} + // ────────────────────────────────────────────────────────────────────────────── // // NAKED_DOUBLE STRUCTURE // // ────────────────────────────────────────────────────────────────────────────── // diff --git a/logic/strategies/notes.go b/logic/strategies/notes.go new file mode 100644 index 0000000..023bc4a --- /dev/null +++ b/logic/strategies/notes.go @@ -0,0 +1,81 @@ +package strategies + +import ( + "git.kleiax.de/homepage/board" +) + +// ────────────────────────────────────────────────────────────────────────────── // +// NOTES STRUCTURE // +// ────────────────────────────────────────────────────────────────────────────── // + +type Notes struct { + Base +} + +func (n *Notes) SearchProgressableCells() int { + n.field.ForEachRow(func(row *board.Row) { + row.ForEachCell(func(cell *board.Cell) { + column, _ := n.field.GetColumn(cell.Pos.GetColumn()) + block, _ := n.field.GetBlock(cell.Pos.GetBlockRow(), cell.Pos.GetBlockColumn()) + candidates := intersection3(row.GetMissingNumbers(), column.GetMissingNumbers(), block.GetMissingNumbers()) + for _, note := range candidates { + if cell.Notes.Has(note) || cell.GetNumber() != 0 { + continue + } + ch := board.ExternalChange{ + Cell: cell, + Action: board.ActionSetNote, + Value: note, + TriggerdBy: n.getName(), + Marks: nil, + From: 0, + } + n.changes = append(n.changes, ch) + } + }) + }) + return len(n.changes) +} + +func (n *Notes) getName() string { + return "Make Notes" +} + +func intersection3(a, b, c []int) []int { + set := make(map[int]bool) + + for _, v := range a { + set[v] = true + } + + // Nur Werte behalten, die auch in b vorkommen + inB := make(map[int]bool) + for _, v := range b { + inB[v] = true + } + + for v := range set { + if !inB[v] { + delete(set, v) + } + } + + // Nur Werte behalten, die auch in c vorkommen + inC := make(map[int]bool) + for _, v := range c { + inC[v] = true + } + + for v := range set { + if !inC[v] { + delete(set, v) + } + } + + result := make([]int, 0, len(set)) + for v := range set { + result = append(result, v) + } + + return result +} diff --git a/logic/strategies/strategy.go b/logic/strategies/strategy.go index d8aa535..e590656 100644 --- a/logic/strategies/strategy.go +++ b/logic/strategies/strategy.go @@ -12,8 +12,8 @@ import ( type Strategy interface { Init(f *board.Field) - ApplyAll() int - ApplyNext() bool + ApplyAll() []board.ExternalChange + ApplyNext() board.ExternalChange Name() string SearchProgressableCells() int } @@ -40,23 +40,31 @@ func (b *Base) Init(f *board.Field) { b.field = f } -func (b *Base) ApplyAll() int { +func (b *Base) ApplyAll() []board.ExternalChange { + changesCopy := make([]board.ExternalChange, len(b.changes)) + copy(changesCopy, b.changes) + for _, change := range b.changes { b.field.AddChange(&change) } - len := len(b.changes) + b.changes = b.changes[:0] - return len + return changesCopy } -func (b *Base) ApplyNext() bool { +func (b *Base) ApplyNext() board.ExternalChange { if len(b.changes) < 1 { - return false + return board.ExternalChange{} } + + changeCopy := b.changes[0] + b.field.AddChange(&b.changes[0]) + b.changes = b.changes[1:] - return true + + return changeCopy } func (b *Base) getName() string { diff --git a/main.go b/main.go index cf16b3c..140a13d 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( func main() { solver := logic.Solver{} + solver.Add(&strategies.Notes{}) solver.Add(&strategies.LastDigit{}) solver.Add(&strategies.NakedSingle{}) game, err := sudoku.New(&board.PuzzleBank{}, solver, openFile()) diff --git a/todo.md b/todo.md index a3baba9..f41365b 100644 --- a/todo.md +++ b/todo.md @@ -1 +1,2 @@ -- Parser kann eigenes Package sein nur das Interface und die Helfer funktionen im Board lassen, jeder kann einen Parser für sseine Quelle schreiben. Vielleicht ein Standardparser in Board für gängige Typen oder Hilfsfunktionen \ No newline at end of file +- Parser kann eigenes Package sein nur das Interface und die Helfer funktionen im Board lassen, jeder kann einen Parser für sseine Quelle schreiben. Vielleicht ein Standardparser in Board für gängige Typen oder Hilfsfunktionen +- Feld immer wieder auf Validität checken (keine doppelten Zahlen in einem Part) damit keine Fehler bei den Strategien auftauchen können \ No newline at end of file