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 //
// ────────────────────────────────────────────────────────────────────────────── //
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 //
// ────────────────────────────────────────────────────────────────────────────── //
+3 -1
View File
@@ -1,6 +1,8 @@
package board
import "image/color"
import (
"image/color"
)
// ────────────────────────────────────────────────────────────────────────────── //
// MARK STRUCTURE //
+57 -18
View File
@@ -25,27 +25,38 @@ type Field struct {
// GETTER //
// ────────────────────────────────────────────────────────────────────────────── //
func (f *Field) GetRow(r int) (*Line, error) {
func (f *Field) GetRow(r int) (*Row, error) {
if r >= f.rows || r < 0 {
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]
}
func (f *Field) GetColumn(c int) (*Line, error) {
return &Row{
Line: Line{
cells: cellPtrs,
},
}, nil
}
func (f *Field) GetColumn(c int) (*Column, error) {
if c >= f.columns || c < 0 {
return nil, outOfBound
}
// Performance
result := &Line{
cells: make([]Cell, 0, f.rows),
result := &Column{
Line: Line{
cells: make([]*Cell, 0, f.rows),
},
}
// Copying
for _, row := range f.cells {
result.cells = append(result.cells, row[c])
result.cells = append(result.cells, &row[c])
}
return result, nil
@@ -60,16 +71,37 @@ func (f *Field) GetBlock(r, c int) (*Block, error) {
startCol := c * f.blockSizeColumn
block := &Block{
cells: make([][]Cell, f.blockSizeRow),
cells: make([][]*Cell, f.blockSizeRow),
}
for row := range block.cells {
// Effizientes Kopieren der Zeile
block.cells[row] = make([]Cell, f.blockSizeColumn)
copy(block.cells[row], f.cells[startRow+row][startCol:startCol+f.blockSizeColumn])
block.cells[row] = make([]*Cell, f.blockSizeColumn)
for column := range block.cells[row] {
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) {
@@ -84,12 +116,12 @@ func (f *Field) GetCell(r, c int) (*Cell, error) {
// ────────────────────────────────────────────────────────────────────────────── //
func (f *Field) ForEachPart(fn func(part Part)) {
f.ForEachRow(func(line *Line) {
fn(line)
f.ForEachRow(func(row *Row) {
fn(row)
})
f.ForEachColumn(func(line *Line) {
fn(line)
f.ForEachColumn(func(column *Column) {
fn(column)
})
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 {
line, err := f.GetRow(i)
row, err := f.GetRow(i)
if err != nil {
fmt.Println(err.Error())
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 {
column, err := f.GetColumn(i)
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")
}
field.cells[i][j].number = number
field.cells[i][j].Notes = &Notes{}
field.cells[i][j].Pos = &Position{
row: i,
column: j,
+61 -4
View File
@@ -1,10 +1,16 @@
package board
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
}
// ────────────────────────────────────────────────────────────────────────────── //
@@ -12,7 +18,7 @@ type Part interface {
// ────────────────────────────────────────────────────────────────────────────── //
type Line struct {
cells []Cell
cells []*Cell
}
func (l *Line) IsSolved() bool {
@@ -22,7 +28,7 @@ func (l *Line) IsSolved() bool {
func (l *Line) ForEachCell(fn func(cell *Cell)) {
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")
}
// ────────────────────────────────────────────────────────────────────────────── //
// 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
cells [][]*Cell
}
func (b *Block) IsSolved() bool {
@@ -52,7 +86,7 @@ func (b *Block) IsSolved() bool {
func (b *Block) ForEachCell(fn func(cell *Cell)) {
for _, row := range b.cells {
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 //
// ────────────────────────────────────────────────────────────────────────────── //
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)
+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
+25 -5
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,20 +24,38 @@ 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
}
if s.field.IsSolved() {
return true
}
return false
}
func (s *Solver) Search() {
+7 -3
View File
@@ -1,6 +1,8 @@
package strategies
import "git.kleiax.de/homepage/board"
import (
"git.kleiax.de/homepage/board"
)
// ────────────────────────────────────────────────────────────────────────────── //
// LAST_DIGIT STRUCTURE //
@@ -20,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,
@@ -30,12 +34,12 @@ func (ld *LastDigit) SearchProgressableCells() int {
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)
}
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
}
+18 -7
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,20 +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)
}
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 {
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