Compare commits

...
2 Commits
Author SHA1 Message Date
kleiax 7bf4a8d725 first successful run to solve a simple puzzle 2026-07-25 06:58:15 +02:00
kleiax 8e99087a30 fix part slices were no pinters 2026-07-21 09:25:45 +02:00
17 changed files with 302 additions and 49 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 //
// ────────────────────────────────────────────────────────────────────────────── // // ────────────────────────────────────────────────────────────────────────────── //
+3 -1
View File
@@ -1,6 +1,8 @@
package board package board
import "image/color" import (
"image/color"
)
// ────────────────────────────────────────────────────────────────────────────── // // ────────────────────────────────────────────────────────────────────────────── //
// MARK STRUCTURE // // MARK STRUCTURE //
+57 -18
View File
@@ -25,27 +25,38 @@ type Field struct {
// GETTER // // GETTER //
// ────────────────────────────────────────────────────────────────────────────── // // ────────────────────────────────────────────────────────────────────────────── //
func (f *Field) GetRow(r int) (*Line, error) { func (f *Field) GetRow(r int) (*Row, error) {
if r >= f.rows || r < 0 { if r >= f.rows || r < 0 {
return nil, outOfBound return nil, outOfBound
} }
return &Line{cells: append([]Cell(nil), f.cells[r]...)}, nil 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) (*Line, error) { func (f *Field) GetColumn(c int) (*Column, error) {
if c >= f.columns || c < 0 { if c >= f.columns || c < 0 {
return nil, outOfBound return nil, outOfBound
} }
// Performance // Performance
result := &Line{ result := &Column{
cells: make([]Cell, 0, f.rows), Line: Line{
cells: make([]*Cell, 0, f.rows),
},
} }
// Copying // Copying
for _, row := range f.cells { for _, row := range f.cells {
result.cells = append(result.cells, row[c]) result.cells = append(result.cells, &row[c])
} }
return result, nil return result, nil
@@ -60,16 +71,37 @@ func (f *Field) GetBlock(r, c int) (*Block, error) {
startCol := c * f.blockSizeColumn startCol := c * f.blockSizeColumn
block := &Block{ block := &Block{
cells: make([][]Cell, f.blockSizeRow), cells: make([][]*Cell, f.blockSizeRow),
} }
for row := range block.cells { for row := range block.cells {
// Effizientes Kopieren der Zeile // Effizientes Kopieren der Zeile
block.cells[row] = make([]Cell, f.blockSizeColumn) block.cells[row] = make([]*Cell, f.blockSizeColumn)
copy(block.cells[row], f.cells[startRow+row][startCol:startCol+f.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
} }
return block, 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) {
@@ -84,12 +116,12 @@ func (f *Field) GetCell(r, c int) (*Cell, error) {
// ────────────────────────────────────────────────────────────────────────────── // // ────────────────────────────────────────────────────────────────────────────── //
func (f *Field) ForEachPart(fn func(part Part)) { func (f *Field) ForEachPart(fn func(part Part)) {
f.ForEachRow(func(line *Line) { f.ForEachRow(func(row *Row) {
fn(line) fn(row)
}) })
f.ForEachColumn(func(line *Line) { f.ForEachColumn(func(column *Column) {
fn(line) fn(column)
}) })
f.ForEachBlock(func(block *Block) { f.ForEachBlock(func(block *Block) {
@@ -97,18 +129,25 @@ func (f *Field) ForEachPart(fn func(part Part)) {
}) })
} }
func (f *Field) ForEachRow(fn func(line *Line)) { 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 { for i := range f.rows {
line, err := f.GetRow(i) row, err := f.GetRow(i)
if err != nil { if err != nil {
fmt.Println(err.Error()) fmt.Println(err.Error())
return return
} }
fn(line) fn(row)
} }
} }
func (f *Field) ForEachColumn(fn func(line *Line)) { func (f *Field) ForEachColumn(fn func(column *Column)) {
for i := range f.columns { for i := range f.columns {
column, err := f.GetColumn(i) column, err := f.GetColumn(i)
if err != nil { if err != nil {
+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,
+62 -5
View File
@@ -1,10 +1,16 @@
package board package board
import (
"fmt"
"strings"
)
type Part interface { type Part interface {
IsSolved() bool IsSolved() bool
ForEachCell(fn func(cell *Cell)) ForEachCell(fn func(cell *Cell))
GetMissingNumbers() []int GetMissingNumbers() []int
RemoveNote(field *Field, note int, trigger string, marks []Mark) RemoveNote(field *Field, note int, trigger string, marks []Mark)
String() string
} }
// ────────────────────────────────────────────────────────────────────────────── // // ────────────────────────────────────────────────────────────────────────────── //
@@ -12,7 +18,7 @@ type Part interface {
// ────────────────────────────────────────────────────────────────────────────── // // ────────────────────────────────────────────────────────────────────────────── //
type Line struct { type Line struct {
cells []Cell cells []*Cell
} }
func (l *Line) IsSolved() bool { func (l *Line) IsSolved() bool {
@@ -22,7 +28,7 @@ func (l *Line) IsSolved() bool {
func (l *Line) ForEachCell(fn func(cell *Cell)) { func (l *Line) ForEachCell(fn func(cell *Cell)) {
for _, cell := range l.cells { for _, cell := range l.cells {
fn(&cell) fn(cell)
} }
} }
@@ -36,12 +42,40 @@ func (l *Line) RemoveNote(field *Field, note int, trigger string, marks []Mark)
}) })
} }
func (l *Line) String() string {
return lineString(l, "Line")
}
// ────────────────────────────────────────────────────────────────────────────── // // ────────────────────────────────────────────────────────────────────────────── //
// BLOCK STRUCTURE // // 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 { type Block struct {
cells [][]Cell cells [][]*Cell
} }
func (b *Block) IsSolved() bool { func (b *Block) IsSolved() bool {
@@ -52,7 +86,7 @@ func (b *Block) IsSolved() bool {
func (b *Block) ForEachCell(fn func(cell *Cell)) { func (b *Block) ForEachCell(fn func(cell *Cell)) {
for _, row := range b.cells { for _, row := range b.cells {
for _, cell := range row { for _, cell := range row {
fn(&cell) fn(cell)
} }
} }
} }
@@ -68,10 +102,33 @@ func (b *Block) RemoveNote(field *Field, note int, trigger string, marks []Mark)
}) })
} }
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 // // 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 { func getMissingNumbersHelper(max int, iterate func(fn func(cell *Cell))) []int {
present := make(map[int]bool, max) present := make(map[int]bool, max)
+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
+26 -6
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,19 +24,37 @@ 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
} }
return true
if s.field.IsSolved() {
return true
}
return false
} }
func (s *Solver) Search() { func (s *Solver) Search() {
+7 -3
View File
@@ -1,6 +1,8 @@
package strategies package strategies
import "git.kleiax.de/homepage/board" import (
"git.kleiax.de/homepage/board"
)
// ────────────────────────────────────────────────────────────────────────────── // // ────────────────────────────────────────────────────────────────────────────── //
// LAST_DIGIT STRUCTURE // // LAST_DIGIT STRUCTURE //
@@ -20,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,
@@ -30,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", 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
}
+18 -7
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,20 +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)
} }
return len(b.changes)
b.changes = b.changes[:0]
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())
+2 -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