first successful run to solve a simple puzzle

This commit is contained in:
2026-07-25 06:58:15 +02:00
parent 8e99087a30
commit 7bf4a8d725
15 changed files with 201 additions and 28 deletions
+15
View File
@@ -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}"
}
]
}
+6
View File
@@ -4,6 +4,8 @@ package board
// NOTES STRUCTURE // // NOTES STRUCTURE //
// ────────────────────────────────────────────────────────────────────────────── // // ────────────────────────────────────────────────────────────────────────────── //
import "slices"
type Notes struct { type Notes struct {
numbers []int numbers []int
} }
@@ -49,6 +51,10 @@ func (n *Notes) Get() []int {
return copySlice return copySlice
} }
func (n *Notes) Has(i int) bool {
return slices.Contains(n.numbers, i)
}
// ────────────────────────────────────────────────────────────────────────────── // // ────────────────────────────────────────────────────────────────────────────── //
// CELL STRUCTURE // // CELL STRUCTURE //
// ────────────────────────────────────────────────────────────────────────────── // // ────────────────────────────────────────────────────────────────────────────── //
+26 -1
View File
@@ -82,10 +82,28 @@ func (f *Field) GetBlock(r, c int) (*Block, error) {
block.cells[row][column] = &f.cells[startRow+row][startCol+column] block.cells[row][column] = &f.cells[startRow+row][startCol+column]
} }
} }
return block, nil 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) { func (f *Field) GetCell(r, c int) (*Cell, error) {
if r > f.rows || c > f.columns { if r > f.rows || c > f.columns {
return nil, outOfBound 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)) { func (f *Field) ForEachRow(fn func(row *Row)) {
for i := range f.rows { for i := range f.rows {
row, err := f.GetRow(i) row, err := f.GetRow(i)
+1
View File
@@ -80,6 +80,7 @@ func (pb *PuzzleBank) Parse(data []byte) error {
return fmt.Errorf("Kann Zeichen nicht in Zahl konvertieren") return fmt.Errorf("Kann Zeichen nicht in Zahl konvertieren")
} }
field.cells[i][j].number = number field.cells[i][j].number = number
field.cells[i][j].Notes = &Notes{}
field.cells[i][j].Pos = &Position{ field.cells[i][j].Pos = &Position{
row: i, row: i,
column: j, column: j,
+17 -1
View File
@@ -1,6 +1,9 @@
package board package board
import "strings" import (
"fmt"
"strings"
)
func (f *Field) String() string { func (f *Field) String() string {
//TODO: auf beliebige größen anpassen //TODO: auf beliebige größen anpassen
@@ -45,6 +48,19 @@ func (f *Field) String() string {
return sb.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 { func (f *Field) IsSolved() bool {
result := true result := true
f.ForEachCell(func(cell *Cell) { f.ForEachCell(func(cell *Cell) {
+1 -1
View File
@@ -1,3 +1,3 @@
0000183b305c 451723860007000800000816000000030000005000100730040086906000204840572093000409000 1.2 0000183b305c 050703060007000800000816000000030000005000100730040086906000204840572093000409000 1.2
0001d5d6314e 302401809001000300000000000040708010780502036000090000200609003900000008800070005 1.2 0001d5d6314e 302401809001000300000000000040708010780502036000090000200609003900000008800070005 1.2
000212406270 000823001003000400070000052300960010000102000010038006830000040002000900600789000 1.2 000212406270 000823001003000400070000052300960010000102000010038006830000040002000900600789000 1.2
-4
View File
@@ -1,4 +0,0 @@
package logic
type Change struct {
}
-1
View File
@@ -1 +0,0 @@
package logic
+25 -5
View File
@@ -11,6 +11,8 @@ import (
type Solver struct { type Solver struct {
strategies []strategies.Strategy strategies []strategies.Strategy
returnTo int
field *board.Field
conf struct { conf struct {
all bool all bool
repeat bool repeat bool
@@ -22,20 +24,38 @@ func (s *Solver) Add(strategy strategies.Strategy) {
} }
func (s *Solver) InitStragies(field *board.Field) { func (s *Solver) InitStragies(field *board.Field) {
s.returnTo = 1
s.field = field
for _, strategy := range s.strategies { for _, strategy := range s.strategies {
strategy.Init(field) strategy.Init(field)
} }
} }
func (s *Solver) Run(i int) bool { func (s *Solver) Run(i int) bool {
//TODO: clean und Fehlerauffangen for j := 0; j < len(s.strategies); j++ {
s.strategies[i].SearchProgressableCells() if s.strategies[j].SearchProgressableCells() == 0 {
numberOfChanges := s.strategies[i].ApplyAll() continue
if numberOfChanges == 0 {
return false
} }
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
}
if s.field.IsSolved() {
return true return true
} }
return false
}
func (s *Solver) Search() { func (s *Solver) Search() {
+4 -4
View File
@@ -1,8 +1,6 @@
package strategies package strategies
import ( import (
"fmt"
"git.kleiax.de/homepage/board" "git.kleiax.de/homepage/board"
) )
@@ -24,6 +22,8 @@ func (ld *LastDigit) SearchProgressableCells() int {
emptyCell = cell 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{ change := board.ExternalChange{
Cell: emptyCell, Cell: emptyCell,
Action: board.ActionSetNumber, Action: board.ActionSetNumber,
@@ -34,12 +34,12 @@ func (ld *LastDigit) SearchProgressableCells() int {
ld.changes = append(ld.changes, change) 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) return len(ld.changes)
} }
func (ld *LastDigit) getName() string { func (ld *LastDigit) getName() string {
return "Last Digit - Letzte Zahl" return "Last Digit"
} }
// ────────────────────────────────────────────────────────────────────────────── // // ────────────────────────────────────────────────────────────────────────────── //
+5 -1
View File
@@ -14,7 +14,7 @@ func (ns *NakedSingle) SearchProgressableCells() int {
ns.field.ForEachPart(func(part board.Part) { ns.field.ForEachPart(func(part board.Part) {
part.ForEachCell(func(cell *board.Cell) { part.ForEachCell(func(cell *board.Cell) {
candidates := cell.Notes.Get() candidates := cell.Notes.Get()
if len(candidates) == 1 { if len(candidates) == 1 && cell.GetNumber() == 0 {
change := board.ExternalChange{ change := board.ExternalChange{
Cell: cell, Cell: cell,
Action: board.ActionSetNumber, Action: board.ActionSetNumber,
@@ -29,6 +29,10 @@ func (ns *NakedSingle) SearchProgressableCells() int {
return len(ns.changes) return len(ns.changes)
} }
func (ns *NakedSingle) getName() string {
return "Naked Single"
}
// ────────────────────────────────────────────────────────────────────────────── // // ────────────────────────────────────────────────────────────────────────────── //
// NAKED_DOUBLE STRUCTURE // // NAKED_DOUBLE STRUCTURE //
// ────────────────────────────────────────────────────────────────────────────── // // ────────────────────────────────────────────────────────────────────────────── //
+81
View File
@@ -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
}
+16 -8
View File
@@ -12,8 +12,8 @@ import (
type Strategy interface { type Strategy interface {
Init(f *board.Field) Init(f *board.Field)
ApplyAll() int ApplyAll() []board.ExternalChange
ApplyNext() bool ApplyNext() board.ExternalChange
Name() string Name() string
SearchProgressableCells() int SearchProgressableCells() int
} }
@@ -40,23 +40,31 @@ func (b *Base) Init(f *board.Field) {
b.field = f 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 { for _, change := range b.changes {
b.field.AddChange(&change) b.field.AddChange(&change)
} }
len := len(b.changes)
b.changes = b.changes[:0] 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 { if len(b.changes) < 1 {
return false return board.ExternalChange{}
} }
changeCopy := b.changes[0]
b.field.AddChange(&b.changes[0]) b.field.AddChange(&b.changes[0])
b.changes = b.changes[1:] b.changes = b.changes[1:]
return true
return changeCopy
} }
func (b *Base) getName() string { func (b *Base) getName() string {
+1
View File
@@ -13,6 +13,7 @@ import (
func main() { func main() {
solver := logic.Solver{} solver := logic.Solver{}
solver.Add(&strategies.Notes{})
solver.Add(&strategies.LastDigit{}) solver.Add(&strategies.LastDigit{})
solver.Add(&strategies.NakedSingle{}) solver.Add(&strategies.NakedSingle{})
game, err := sudoku.New(&board.PuzzleBank{}, solver, openFile()) game, err := sudoku.New(&board.PuzzleBank{}, solver, openFile())
+1
View File
@@ -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 - 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