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 //
// ────────────────────────────────────────────────────────────────────────────── //
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 //
// ────────────────────────────────────────────────────────────────────────────── //
+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]
}
}
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)
+1
View File
@@ -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,
+17 -1
View File
@@ -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) {
+1 -1
View File
@@ -1,3 +1,3 @@
0000183b305c 451723860007000800000816000000030000005000100730040086906000204840572093000409000 1.2
0000183b305c 050703060007000800000816000000030000005000100730040086906000204840572093000409000 1.2
0001d5d6314e 302401809001000300000000000040708010780502036000090000200609003900000008800070005 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 {
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() {
+4 -4
View File
@@ -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"
}
// ────────────────────────────────────────────────────────────────────────────── //
+5 -1
View File
@@ -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 //
// ────────────────────────────────────────────────────────────────────────────── //
+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 {
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 {
+1
View File
@@ -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())
+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
- Feld immer wieder auf Validität checken (keine doppelten Zahlen in einem Part) damit keine Fehler bei den Strategien auftauchen können