Refactor Sudoku solver architecture
This commit is contained in:
@@ -25,3 +25,5 @@ go.work.sum
|
|||||||
# env file
|
# env file
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
# Local instructions for coding agents
|
||||||
|
AGENTS.md
|
||||||
|
|||||||
@@ -1,3 +1,114 @@
|
|||||||
# Sudoku
|
# Sudoku
|
||||||
|
|
||||||
Sudoku-Puzzle-Löser mit Lösungsweg
|
Ein in Go geschriebener Sudoku-Löser, der Rätsel mit nachvollziehbaren,
|
||||||
|
menschlichen Lösungsstrategien bearbeiten soll. Neben dem gelösten Feld soll
|
||||||
|
langfristig auch der Lösungsweg mit Kandidaten, Änderungen und Markierungen
|
||||||
|
verfügbar sein.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Das Projekt befindet sich in einer frühen Entwicklungsphase. Der aktuelle
|
||||||
|
> Prüflauf ist wegen eines Vet-Fehlers noch nicht grün und die Demo kann während des
|
||||||
|
> Lösens abstürzen. Siehe [Bekannte Einschränkungen](#bekannte-einschränkungen).
|
||||||
|
|
||||||
|
## Voraussetzungen
|
||||||
|
|
||||||
|
- Go 1.25.10 oder neuer (siehe `go.mod`)
|
||||||
|
- keine externen Go-Abhängigkeiten
|
||||||
|
|
||||||
|
## Ausführen
|
||||||
|
|
||||||
|
Die Demo in `main.go` liest derzeit fest das dritte Rätsel aus
|
||||||
|
`data/sudoku-exchange-puzzle-bank/easy3.txt`, zeigt das Ausgangsfeld an und
|
||||||
|
startet anschließend den Solver:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go run .
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Pfad und die Rätselauswahl sind momentan noch nicht über
|
||||||
|
Kommandozeilenargumente konfigurierbar.
|
||||||
|
|
||||||
|
## Entwicklung
|
||||||
|
|
||||||
|
Alle Pakete bauen und testen:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go test ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
Code formatieren und statisch prüfen:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gofmt -w main.go field/*.go parser/*.go logic/*.go logic/strategies/*.go sudoku/*.go
|
||||||
|
go vet ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
Aktuell gibt es noch keine automatisierten Tests. Neue Funktionalität sollte
|
||||||
|
nach Möglichkeit mit paketnahen `*_test.go`-Tests ergänzt werden.
|
||||||
|
|
||||||
|
## Architektur
|
||||||
|
|
||||||
|
| Pfad | Aufgabe |
|
||||||
|
| --- | --- |
|
||||||
|
| `main.go` | Kleine Demo und Zusammenbau von Parser, Solver und Strategien |
|
||||||
|
| `field/` | Spielfeld, Zellen, Positionen, Zeilen, Spalten, Blöcke, Kandidaten und Änderungsprotokoll |
|
||||||
|
| `parser/` | Parser-Schnittstelle und Import des Sudoku-Exchange-Puzzle-Bank-Formats |
|
||||||
|
| `logic/` | Ablaufsteuerung des Solvers |
|
||||||
|
| `logic/strategies/` | Lösungsstrategien und gemeinsame Strategie-Basis |
|
||||||
|
| `sudoku/` | Fassade, die Parser, Feld und Solver zu einem Spiel verbindet |
|
||||||
|
| `data/` | Mitgelieferte Beispielrätsel samt eigener Herkunfts- und Lizenzhinweise |
|
||||||
|
| `todo.md` | Offene technische Ideen und Aufgaben |
|
||||||
|
|
||||||
|
Der Datenfluss der Demo ist:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Puzzle-Bank-Datei -> parser.PuzzleBank -> field.Field
|
||||||
|
-> sudoku.Game -> logic.Solver
|
||||||
|
-> Strategien -> Änderungen am Feld
|
||||||
|
```
|
||||||
|
|
||||||
|
Eine Strategie implementiert `strategies.Strategy`. `SearchProgressableCells`
|
||||||
|
sammelt mögliche Änderungen, `ApplyAll` oder `ApplyNext` übernimmt sie in das
|
||||||
|
Feld. Wird eine Zahl gesetzt, entfernt der Solver den entsprechenden Kandidaten
|
||||||
|
aus der zugehörigen Zeile, Spalte und dem Block.
|
||||||
|
|
||||||
|
Derzeit in `main.go` aktiv:
|
||||||
|
|
||||||
|
- Kandidaten eintragen (`Notes`)
|
||||||
|
- letzte fehlende Zahl eines Bereichs (`LastDigit`)
|
||||||
|
- einzelner Kandidat einer Zelle (`NakedSingle`)
|
||||||
|
- nur einmal vorkommender Kandidat eines Bereichs (`HiddenSingle`)
|
||||||
|
|
||||||
|
Weitere Strategietypen sind bereits als Gerüste angelegt, aber noch nicht
|
||||||
|
implementiert.
|
||||||
|
|
||||||
|
## Eingabedaten
|
||||||
|
|
||||||
|
`parser.PuzzleBank` erwartet das Format der
|
||||||
|
[Sudoku Exchange Puzzle Bank](data/sudoku-exchange-puzzle-bank/README.md): pro
|
||||||
|
Zeile einen 12-stelligen Hash, 81 Ziffern für das Rätsel und eine
|
||||||
|
Schwierigkeitsbewertung. `0` steht für ein leeres Feld. Leere Zeilen werden
|
||||||
|
übersprungen; alle eingelesenen Rätsel werden im Parser gespeichert.
|
||||||
|
|
||||||
|
Die Datensätze unter `data/sudoku-exchange-puzzle-bank/` stehen unter der dort
|
||||||
|
beiliegenden separaten Lizenz.
|
||||||
|
|
||||||
|
## Bekannte Einschränkungen
|
||||||
|
|
||||||
|
- `field.Field.IsValid` und die `IsSolved`-Prüfungen einzelner Bereiche sind
|
||||||
|
noch nicht implementiert. Ein vollständig belegtes, aber ungültiges Feld kann
|
||||||
|
daher als gelöst gelten.
|
||||||
|
- `logic.Solver.GetSolutionPath` liefert noch keinen Lösungsweg.
|
||||||
|
- Mehrere fortgeschrittene Strategien sind nur als leere Typen vorhanden.
|
||||||
|
- `go test ./...` meldet aktuell in `field/field.go`, dass eine Ganzzahl direkt
|
||||||
|
in einen String konvertiert wird.
|
||||||
|
- Die Demo kann in `LastDigit.SearchProgressableCells` auf eine leere Zelle
|
||||||
|
zugreifen und dadurch abstürzen.
|
||||||
|
- Fehlertexte und Bezeichner sind derzeit teilweise deutsch, teilweise
|
||||||
|
englisch; einige öffentliche Namen enthalten noch Tippfehler.
|
||||||
|
|
||||||
|
## Lizenz
|
||||||
|
|
||||||
|
Der Programmcode steht unter der [GNU General Public License v3.0](LICENSE).
|
||||||
|
Für die mitgelieferten Rätseldaten gelten die Hinweise im jeweiligen
|
||||||
|
Unterverzeichnis.
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
package board
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Parser interface {
|
|
||||||
Parse(data []byte) error
|
|
||||||
GetField(i int) *Field
|
|
||||||
GetAllFields() []Field
|
|
||||||
}
|
|
||||||
|
|
||||||
type parserHelper struct {
|
|
||||||
fields []Field
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ph *parserHelper) GetField(i int) *Field {
|
|
||||||
if i < 0 || i >= len(ph.fields) {
|
|
||||||
return &Field{}
|
|
||||||
}
|
|
||||||
return &ph.fields[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ph *parserHelper) GetAllFields() []Field {
|
|
||||||
if len(ph.fields) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ph.fields
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/grantm/sudoku-exchange-puzzle-bank/tree/master
|
|
||||||
type PuzzleBank struct {
|
|
||||||
parserHelper
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pb *PuzzleBank) Parse(data []byte) error {
|
|
||||||
|
|
||||||
lines := bytes.Split(data, []byte("\n"))
|
|
||||||
for _, line := range lines {
|
|
||||||
if len(line) == 0 { // Überspringe leere Zeile
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(line) != 99 {
|
|
||||||
return fmt.Errorf("Zeile hat die falsche länge. soll: 100, ist: %d", len(line))
|
|
||||||
}
|
|
||||||
|
|
||||||
//siehe Readme in github repo
|
|
||||||
sha1Hash := bytes.TrimSpace(line[0:12])
|
|
||||||
sudokuStr := string(line[13:94])
|
|
||||||
ratingStr := string(line[96:99])
|
|
||||||
//fmt.Println(sudokuStr)
|
|
||||||
|
|
||||||
var rating float64
|
|
||||||
_, err := fmt.Sscanf(ratingStr, "%f", &rating)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("can nor parse raiting")
|
|
||||||
}
|
|
||||||
|
|
||||||
field := Field{
|
|
||||||
rows: 9,
|
|
||||||
columns: 9,
|
|
||||||
blockRows: 3,
|
|
||||||
blockColumns: 3,
|
|
||||||
blockSizeRow: 3,
|
|
||||||
blockSizeColumn: 3,
|
|
||||||
rating: rating,
|
|
||||||
sha1: sha1Hash,
|
|
||||||
}
|
|
||||||
|
|
||||||
field.cells = make([][]Cell, field.rows)
|
|
||||||
for i := range field.cells {
|
|
||||||
field.cells[i] = make([]Cell, field.columns)
|
|
||||||
for j := range field.cells[i] {
|
|
||||||
number, err := strconv.Atoi(string(sudokuStr[i*9+j]))
|
|
||||||
if err != nil {
|
|
||||||
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,
|
|
||||||
blockRow: i / field.blockRows,
|
|
||||||
blockColumn: j / field.blockColumns,
|
|
||||||
inBlockRow: i % field.blockSizeRow,
|
|
||||||
inBlockColumn: j % field.blockSizeColumn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//fmt.Println(field.String())
|
|
||||||
pb.fields = append(pb.fields, field)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package board
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f *Field) String() string {
|
|
||||||
//TODO: auf beliebige größen anpassen
|
|
||||||
var sb strings.Builder
|
|
||||||
|
|
||||||
// Oberer Rahmen
|
|
||||||
sb.WriteString("╔═══════╤═══════╤═══════╗\n")
|
|
||||||
|
|
||||||
for i := range f.cells {
|
|
||||||
sb.WriteString("║ ") // Linke Rahmenseite
|
|
||||||
|
|
||||||
for j, cell := range f.cells[i] {
|
|
||||||
// Wert ausgeben oder Punkt für 0
|
|
||||||
val := cell.number
|
|
||||||
if val == 0 {
|
|
||||||
sb.WriteString("·")
|
|
||||||
} else {
|
|
||||||
sb.WriteString(string('0' + val))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trennlinien zwischen Blöcken und Zellen
|
|
||||||
if (j+1)%3 == 0 {
|
|
||||||
if j < 8 {
|
|
||||||
sb.WriteString(" │ ")
|
|
||||||
} else {
|
|
||||||
sb.WriteString(" ║\n") // Rechte Rahmenseite + Zeilenumbruch
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sb.WriteString(" ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Horizontale Trennlinien nach jeder 3. Zeile
|
|
||||||
if (i+1)%3 == 0 && i < 8 {
|
|
||||||
sb.WriteString("╟───────┼───────┼───────╢\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unterer Rahmen
|
|
||||||
sb.WriteString("╚═══════╧═══════╧═══════╝")
|
|
||||||
|
|
||||||
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) {
|
|
||||||
if cell.number == 0 {
|
|
||||||
result = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/* TODO:
|
|
||||||
if !f.isValid() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Field) IsValid() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package board
|
package field
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
// NOTES STRUCTURE //
|
// NOTES STRUCTURE //
|
||||||
@@ -11,10 +11,8 @@ type Notes struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notes) Add(field *Field, cell *Cell, note int, trigger string, marks []Mark) {
|
func (n *Notes) Add(field *Field, cell *Cell, note int, trigger string, marks []Mark) {
|
||||||
for _, existing := range n.numbers {
|
if slices.Contains(n.numbers, note) {
|
||||||
if existing == note {
|
return // Note already exists
|
||||||
return // Note already exists
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
field.changes = append(field.changes, Change{
|
field.changes = append(field.changes, Change{
|
||||||
Cell: cell,
|
Cell: cell,
|
||||||
@@ -65,6 +63,15 @@ type Cell struct {
|
|||||||
Pos *Position
|
Pos *Position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewCell(number int, pos *Position) *Cell {
|
||||||
|
cell := Cell{
|
||||||
|
number: number,
|
||||||
|
Pos: pos,
|
||||||
|
Notes: &Notes{},
|
||||||
|
}
|
||||||
|
return &cell
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Cell) SetNumber(field *Field, n int, trigger string, marks []Mark) {
|
func (c *Cell) SetNumber(field *Field, n int, trigger string, marks []Mark) {
|
||||||
if c.number == n {
|
if c.number == n {
|
||||||
return
|
return
|
||||||
@@ -74,7 +81,7 @@ func (c *Cell) SetNumber(field *Field, n int, trigger string, marks []Mark) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if n <= 0 || n > field.rows || n > field.columns {
|
if n <= 0 || n > field.props.Rows || n > field.props.Columns {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package board
|
package field
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image/color"
|
"image/color"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package board
|
package field
|
||||||
|
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
+131
-24
@@ -1,24 +1,40 @@
|
|||||||
package board
|
package field
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
|
// META STRUCTURE //
|
||||||
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
|
|
||||||
|
type Properties struct {
|
||||||
|
Rows int
|
||||||
|
Columns int
|
||||||
|
BlockRows int
|
||||||
|
BlockColumns int
|
||||||
|
BlockSizeRow int
|
||||||
|
BlockSizeColumn int
|
||||||
|
Rating float64
|
||||||
|
}
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
// FIELD STRUCTURE //
|
// FIELD STRUCTURE //
|
||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
|
|
||||||
type Field struct {
|
type Field struct {
|
||||||
rows int
|
props *Properties
|
||||||
columns int
|
cells [][]Cell
|
||||||
blockRows int
|
changes []Change
|
||||||
blockColumns int
|
}
|
||||||
blockSizeRow int
|
|
||||||
blockSizeColumn int
|
func New(props Properties, cells [][]Cell) *Field {
|
||||||
cells [][]Cell
|
field := Field{
|
||||||
changes []Change
|
props: &props,
|
||||||
sha1 []byte
|
cells: cells,
|
||||||
rating float64
|
}
|
||||||
|
return &field
|
||||||
}
|
}
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
@@ -26,7 +42,7 @@ type Field struct {
|
|||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
|
|
||||||
func (f *Field) GetRow(r int) (*Row, error) {
|
func (f *Field) GetRow(r int) (*Row, error) {
|
||||||
if r >= f.rows || r < 0 {
|
if r >= f.props.Rows || r < 0 {
|
||||||
return nil, outOfBound
|
return nil, outOfBound
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,14 +59,14 @@ func (f *Field) GetRow(r int) (*Row, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Field) GetColumn(c int) (*Column, error) {
|
func (f *Field) GetColumn(c int) (*Column, error) {
|
||||||
if c >= f.columns || c < 0 {
|
if c >= f.props.Columns || c < 0 {
|
||||||
return nil, outOfBound
|
return nil, outOfBound
|
||||||
}
|
}
|
||||||
|
|
||||||
// Performance
|
// Performance
|
||||||
result := &Column{
|
result := &Column{
|
||||||
Line: Line{
|
Line: Line{
|
||||||
cells: make([]*Cell, 0, f.rows),
|
cells: make([]*Cell, 0, f.props.Rows),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,20 +79,20 @@ func (f *Field) GetColumn(c int) (*Column, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Field) GetBlock(r, c int) (*Block, error) {
|
func (f *Field) GetBlock(r, c int) (*Block, error) {
|
||||||
if r < 0 || r >= f.blockRows || c < 0 || c >= f.blockColumns {
|
if r < 0 || r >= f.props.BlockRows || c < 0 || c >= f.props.BlockColumns {
|
||||||
return nil, outOfBound
|
return nil, outOfBound
|
||||||
}
|
}
|
||||||
|
|
||||||
startRow := r * f.blockSizeRow
|
startRow := r * f.props.BlockSizeRow
|
||||||
startCol := c * f.blockSizeColumn
|
startCol := c * f.props.BlockSizeColumn
|
||||||
|
|
||||||
block := &Block{
|
block := &Block{
|
||||||
cells: make([][]*Cell, f.blockSizeRow),
|
cells: make([][]*Cell, f.props.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.props.BlockSizeColumn)
|
||||||
|
|
||||||
for column := range block.cells[row] {
|
for column := range block.cells[row] {
|
||||||
block.cells[row][column] = &f.cells[startRow+row][startCol+column]
|
block.cells[row][column] = &f.cells[startRow+row][startCol+column]
|
||||||
@@ -105,12 +121,16 @@ func (f *Field) GetEachPartAtPos(pos *Position) []Part {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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.props.Rows || c > f.props.Columns {
|
||||||
return nil, outOfBound
|
return nil, outOfBound
|
||||||
}
|
}
|
||||||
return &f.cells[r][c], nil
|
return &f.cells[r][c], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Field) GetRating() float64 {
|
||||||
|
return f.props.Rating
|
||||||
|
}
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
// FOREACH FUNCTIONS //
|
// FOREACH FUNCTIONS //
|
||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
@@ -137,7 +157,7 @@ func (f *Field) ForEachPartAtPos(pos *Position, fn func(part 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.props.Rows {
|
||||||
row, err := f.GetRow(i)
|
row, err := f.GetRow(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err.Error())
|
fmt.Println(err.Error())
|
||||||
@@ -148,7 +168,7 @@ func (f *Field) ForEachRow(fn func(row *Row)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Field) ForEachColumn(fn func(column *Column)) {
|
func (f *Field) ForEachColumn(fn func(column *Column)) {
|
||||||
for i := range f.columns {
|
for i := range f.props.Columns {
|
||||||
column, err := f.GetColumn(i)
|
column, err := f.GetColumn(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err.Error())
|
fmt.Println(err.Error())
|
||||||
@@ -160,8 +180,8 @@ func (f *Field) ForEachColumn(fn func(column *Column)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Field) ForEachBlock(fn func(block *Block)) {
|
func (f *Field) ForEachBlock(fn func(block *Block)) {
|
||||||
for r := range f.blockRows {
|
for r := range f.props.BlockRows {
|
||||||
for c := range f.blockColumns {
|
for c := range f.props.BlockColumns {
|
||||||
block, err := f.GetBlock(r, c)
|
block, err := f.GetBlock(r, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err.Error())
|
fmt.Println(err.Error())
|
||||||
@@ -198,3 +218,90 @@ func (f *Field) AddChange(eChange *ExternalChange) {
|
|||||||
|
|
||||||
f.changes = append(f.changes, change)
|
f.changes = append(f.changes, change)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Field) SetRating(rating float64) {
|
||||||
|
f.props.Rating = rating
|
||||||
|
}
|
||||||
|
|
||||||
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
|
// STATE //
|
||||||
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
|
|
||||||
|
func (f *Field) String() string {
|
||||||
|
//TODO: auf beliebige größen anpassen
|
||||||
|
var sb strings.Builder
|
||||||
|
|
||||||
|
// Oberer Rahmen
|
||||||
|
sb.WriteString("╔═══════╤═══════╤═══════╗\n")
|
||||||
|
|
||||||
|
for i := range f.cells {
|
||||||
|
sb.WriteString("║ ") // Linke Rahmenseite
|
||||||
|
|
||||||
|
for j, cell := range f.cells[i] {
|
||||||
|
// Wert ausgeben oder Punkt für 0
|
||||||
|
val := cell.number
|
||||||
|
if val == 0 {
|
||||||
|
sb.WriteString("·")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(string('0' + val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trennlinien zwischen Blöcken und Zellen
|
||||||
|
if (j+1)%3 == 0 {
|
||||||
|
if j < 8 {
|
||||||
|
sb.WriteString(" │ ")
|
||||||
|
} else {
|
||||||
|
sb.WriteString(" ║\n") // Rechte Rahmenseite + Zeilenumbruch
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sb.WriteString(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Horizontale Trennlinien nach jeder 3. Zeile
|
||||||
|
if (i+1)%3 == 0 && i < 8 {
|
||||||
|
sb.WriteString("╟───────┼───────┼───────╢\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unterer Rahmen
|
||||||
|
sb.WriteString("╚═══════╧═══════╧═══════╝")
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if cell.number == 0 {
|
||||||
|
result = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/* TODO:
|
||||||
|
if !f.isValid() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Field) IsValid() bool {
|
||||||
|
//In jedem Part gibt es jede Zahl max ein mal
|
||||||
|
//Die Zahl kommt nicht in intersecting parts vor
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package board
|
package field
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package board
|
package field
|
||||||
|
|
||||||
type Position struct {
|
type Position struct {
|
||||||
row int
|
row int
|
||||||
@@ -9,6 +9,18 @@ type Position struct {
|
|||||||
inBlockColumn int
|
inBlockColumn int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewPosition(row, column, blockRow, blockColumn, inBlockRow, inBlockColumn int) *Position {
|
||||||
|
pos := Position{
|
||||||
|
row: row,
|
||||||
|
column: column,
|
||||||
|
blockRow: blockRow,
|
||||||
|
blockColumn: blockColumn,
|
||||||
|
inBlockRow: inBlockRow,
|
||||||
|
inBlockColumn: inBlockColumn,
|
||||||
|
}
|
||||||
|
return &pos
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Position) GetRow() int {
|
func (p *Position) GetRow() int {
|
||||||
return p.row
|
return p.row
|
||||||
}
|
}
|
||||||
+7
-7
@@ -1,7 +1,7 @@
|
|||||||
package logic
|
package logic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.kleiax.de/homepage/board"
|
"git.kleiax.de/homepage/field"
|
||||||
"git.kleiax.de/homepage/logic/strategies"
|
"git.kleiax.de/homepage/logic/strategies"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
type Solver struct {
|
type Solver struct {
|
||||||
strategies []strategies.Strategy
|
strategies []strategies.Strategy
|
||||||
returnTo int
|
returnTo int
|
||||||
field *board.Field
|
field *field.Field
|
||||||
conf struct {
|
conf struct {
|
||||||
all bool
|
all bool
|
||||||
repeat bool
|
repeat bool
|
||||||
@@ -23,7 +23,7 @@ func (s *Solver) Add(strategy strategies.Strategy) {
|
|||||||
s.strategies = append(s.strategies, strategy)
|
s.strategies = append(s.strategies, strategy)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Solver) InitStragies(field *board.Field) {
|
func (s *Solver) InitStragies(field *field.Field) {
|
||||||
s.returnTo = 1
|
s.returnTo = 1
|
||||||
s.field = field
|
s.field = field
|
||||||
for _, strategy := range s.strategies {
|
for _, strategy := range s.strategies {
|
||||||
@@ -38,11 +38,11 @@ func (s *Solver) Run(i int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, change := range s.strategies[j].ApplyAll() {
|
for _, change := range s.strategies[j].ApplyAll() {
|
||||||
if change.Action != board.ActionSetNumber {
|
if change.Action != field.ActionSetNumber {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
s.field.ForEachPartAtPos(change.Cell.Pos, func(part board.Part) {
|
s.field.ForEachPartAtPos(change.Cell.Pos, func(part field.Part) {
|
||||||
part.ForEachCell(func(cell *board.Cell) {
|
part.ForEachCell(func(cell *field.Cell) {
|
||||||
cell.Notes.Remove(s.field, cell, change.Value, "remove note after insert of a number", nil)
|
cell.Notes.Remove(s.field, cell, change.Value, "remove note after insert of a number", nil)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -61,6 +61,6 @@ func (s *Solver) Search() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Solver) GetSolutionPath() []board.ExternalChange {
|
func (s *Solver) GetSolutionPath() []field.ExternalChange {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package strategies
|
package strategies
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.kleiax.de/homepage/board"
|
"git.kleiax.de/homepage/field"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
@@ -13,20 +13,20 @@ type LastDigit struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ld *LastDigit) SearchProgressableCells() int {
|
func (ld *LastDigit) SearchProgressableCells() int {
|
||||||
ld.field.ForEachPart(func(part board.Part) {
|
ld.field.ForEachPart(func(part field.Part) {
|
||||||
missingNumbers := part.GetMissingNumbers()
|
missingNumbers := part.GetMissingNumbers()
|
||||||
if len(missingNumbers) == 1 {
|
if len(missingNumbers) == 1 {
|
||||||
var emptyCell *board.Cell
|
var emptyCell *field.Cell
|
||||||
part.ForEachCell(func(cell *board.Cell) {
|
part.ForEachCell(func(cell *field.Cell) {
|
||||||
if cell.GetNumber() == 0 {
|
if cell.GetNumber() == 0 {
|
||||||
emptyCell = cell
|
emptyCell = cell
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// fmt.Println(part)
|
// 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)
|
// 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 := field.ExternalChange{
|
||||||
Cell: emptyCell,
|
Cell: emptyCell,
|
||||||
Action: board.ActionSetNumber,
|
Action: field.ActionSetNumber,
|
||||||
Value: missingNumbers[0],
|
Value: missingNumbers[0],
|
||||||
From: emptyCell.GetNumber(),
|
From: emptyCell.GetNumber(),
|
||||||
TriggerdBy: ld.getName(),
|
TriggerdBy: ld.getName(),
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package strategies
|
package strategies
|
||||||
|
|
||||||
|
import "git.kleiax.de/homepage/field"
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
// HIDDEN_SINGLE STRUCTURE //
|
// HIDDEN_SINGLE STRUCTURE //
|
||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
@@ -8,6 +10,46 @@ type HiddenSingle struct {
|
|||||||
Base
|
Base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (hs *HiddenSingle) SearchProgressableCells() int {
|
||||||
|
hs.field.ForEachPart(func(part field.Part) {
|
||||||
|
m := make(map[int]int)
|
||||||
|
|
||||||
|
part.ForEachCell(func(cell *field.Cell) {
|
||||||
|
notes := cell.Notes.Get()
|
||||||
|
for note := range notes {
|
||||||
|
m[note]++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var hiddenSingles []int
|
||||||
|
for num, count := range m {
|
||||||
|
if count == 1 {
|
||||||
|
hiddenSingles = append(hiddenSingles, num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for num := range hiddenSingles {
|
||||||
|
part.ForEachCell(func(cell *field.Cell) {
|
||||||
|
if cell.Notes.Has(num) {
|
||||||
|
change := field.ExternalChange{
|
||||||
|
Cell: cell,
|
||||||
|
Action: field.ActionSetNumber,
|
||||||
|
Value: num,
|
||||||
|
From: cell.GetNumber(),
|
||||||
|
TriggerdBy: hs.getName(),
|
||||||
|
}
|
||||||
|
hs.changes = append(hs.changes, change)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return len(hs.changes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hs *HiddenSingle) getName() string {
|
||||||
|
return "Hidden Single"
|
||||||
|
}
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
// HIDDEN_PAIR STRUCTURE //
|
// HIDDEN_PAIR STRUCTURE //
|
||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package strategies
|
package strategies
|
||||||
|
|
||||||
import "git.kleiax.de/homepage/board"
|
import "git.kleiax.de/homepage/field"
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
// NAKED_SINGLE STRUCTURE //
|
// NAKED_SINGLE STRUCTURE //
|
||||||
@@ -11,13 +11,13 @@ type NakedSingle struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ns *NakedSingle) SearchProgressableCells() int {
|
func (ns *NakedSingle) SearchProgressableCells() int {
|
||||||
ns.field.ForEachPart(func(part board.Part) {
|
ns.field.ForEachPart(func(part field.Part) {
|
||||||
part.ForEachCell(func(cell *board.Cell) {
|
part.ForEachCell(func(cell *field.Cell) {
|
||||||
candidates := cell.Notes.Get()
|
candidates := cell.Notes.Get()
|
||||||
if len(candidates) == 1 && cell.GetNumber() == 0 {
|
if len(candidates) == 1 && cell.GetNumber() == 0 {
|
||||||
change := board.ExternalChange{
|
change := field.ExternalChange{
|
||||||
Cell: cell,
|
Cell: cell,
|
||||||
Action: board.ActionSetNumber,
|
Action: field.ActionSetNumber,
|
||||||
Value: candidates[0],
|
Value: candidates[0],
|
||||||
From: cell.GetNumber(),
|
From: cell.GetNumber(),
|
||||||
TriggerdBy: ns.getName(),
|
TriggerdBy: ns.getName(),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package strategies
|
package strategies
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.kleiax.de/homepage/board"
|
"git.kleiax.de/homepage/field"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
@@ -13,8 +13,8 @@ type Notes struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notes) SearchProgressableCells() int {
|
func (n *Notes) SearchProgressableCells() int {
|
||||||
n.field.ForEachRow(func(row *board.Row) {
|
n.field.ForEachRow(func(row *field.Row) {
|
||||||
row.ForEachCell(func(cell *board.Cell) {
|
row.ForEachCell(func(cell *field.Cell) {
|
||||||
column, _ := n.field.GetColumn(cell.Pos.GetColumn())
|
column, _ := n.field.GetColumn(cell.Pos.GetColumn())
|
||||||
block, _ := n.field.GetBlock(cell.Pos.GetBlockRow(), cell.Pos.GetBlockColumn())
|
block, _ := n.field.GetBlock(cell.Pos.GetBlockRow(), cell.Pos.GetBlockColumn())
|
||||||
candidates := intersection3(row.GetMissingNumbers(), column.GetMissingNumbers(), block.GetMissingNumbers())
|
candidates := intersection3(row.GetMissingNumbers(), column.GetMissingNumbers(), block.GetMissingNumbers())
|
||||||
@@ -22,9 +22,9 @@ func (n *Notes) SearchProgressableCells() int {
|
|||||||
if cell.Notes.Has(note) || cell.GetNumber() != 0 {
|
if cell.Notes.Has(note) || cell.GetNumber() != 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ch := board.ExternalChange{
|
ch := field.ExternalChange{
|
||||||
Cell: cell,
|
Cell: cell,
|
||||||
Action: board.ActionSetNote,
|
Action: field.ActionSetNote,
|
||||||
Value: note,
|
Value: note,
|
||||||
TriggerdBy: n.getName(),
|
TriggerdBy: n.getName(),
|
||||||
Marks: nil,
|
Marks: nil,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package strategies
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.kleiax.de/homepage/board"
|
"git.kleiax.de/homepage/field"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
@@ -11,9 +11,9 @@ import (
|
|||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
|
|
||||||
type Strategy interface {
|
type Strategy interface {
|
||||||
Init(f *board.Field)
|
Init(f *field.Field)
|
||||||
ApplyAll() []board.ExternalChange
|
ApplyAll() []field.ExternalChange
|
||||||
ApplyNext() board.ExternalChange
|
ApplyNext() field.ExternalChange
|
||||||
Name() string
|
Name() string
|
||||||
SearchProgressableCells() int
|
SearchProgressableCells() int
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@ type Strategy interface {
|
|||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
|
|
||||||
type StrategyVisualization interface {
|
type StrategyVisualization interface {
|
||||||
pointOut(f *board.Field) []board.Mark
|
pointOut(f *field.Field) []field.Mark
|
||||||
}
|
}
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────────────────────────── //
|
// ────────────────────────────────────────────────────────────────────────────── //
|
||||||
@@ -32,16 +32,16 @@ type StrategyVisualization interface {
|
|||||||
|
|
||||||
type Base struct {
|
type Base struct {
|
||||||
name string
|
name string
|
||||||
field *board.Field
|
field *field.Field
|
||||||
changes []board.ExternalChange
|
changes []field.ExternalChange
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Base) Init(f *board.Field) {
|
func (b *Base) Init(f *field.Field) {
|
||||||
b.field = f
|
b.field = f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Base) ApplyAll() []board.ExternalChange {
|
func (b *Base) ApplyAll() []field.ExternalChange {
|
||||||
changesCopy := make([]board.ExternalChange, len(b.changes))
|
changesCopy := make([]field.ExternalChange, len(b.changes))
|
||||||
copy(changesCopy, b.changes)
|
copy(changesCopy, b.changes)
|
||||||
|
|
||||||
for _, change := range b.changes {
|
for _, change := range b.changes {
|
||||||
@@ -53,9 +53,9 @@ func (b *Base) ApplyAll() []board.ExternalChange {
|
|||||||
return changesCopy
|
return changesCopy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Base) ApplyNext() board.ExternalChange {
|
func (b *Base) ApplyNext() field.ExternalChange {
|
||||||
if len(b.changes) < 1 {
|
if len(b.changes) < 1 {
|
||||||
return board.ExternalChange{}
|
return field.ExternalChange{}
|
||||||
}
|
}
|
||||||
|
|
||||||
changeCopy := b.changes[0]
|
changeCopy := b.changes[0]
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.kleiax.de/homepage/board"
|
|
||||||
"git.kleiax.de/homepage/logic"
|
"git.kleiax.de/homepage/logic"
|
||||||
"git.kleiax.de/homepage/logic/strategies"
|
"git.kleiax.de/homepage/logic/strategies"
|
||||||
|
"git.kleiax.de/homepage/parser"
|
||||||
"git.kleiax.de/homepage/sudoku"
|
"git.kleiax.de/homepage/sudoku"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,7 +16,8 @@ func main() {
|
|||||||
solver.Add(&strategies.Notes{})
|
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())
|
solver.Add(&strategies.HiddenSingle{})
|
||||||
|
game, err := sudoku.New(&parser.PuzzleBank{}, solver, openFile())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err.Error())
|
fmt.Println(err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import "git.kleiax.de/homepage/field"
|
||||||
|
|
||||||
|
//TODO: const für klassik sudoku board.Props
|
||||||
|
//TODO: funktion für [][]Cell generierung
|
||||||
|
|
||||||
|
type Parser interface {
|
||||||
|
Parse(data []byte) error
|
||||||
|
GetField(i int) *field.Field
|
||||||
|
GetAllFields() []field.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParserHelper struct {
|
||||||
|
fields []field.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ph *ParserHelper) GetField(i int) *field.Field {
|
||||||
|
if i < 0 || i >= len(ph.fields) {
|
||||||
|
return &field.Field{}
|
||||||
|
}
|
||||||
|
return &ph.fields[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ph *ParserHelper) GetAllFields() []field.Field {
|
||||||
|
if len(ph.fields) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ph.fields
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.kleiax.de/homepage/field"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://github.com/grantm/sudoku-exchange-puzzle-bank/tree/master
|
||||||
|
type PuzzleBank struct {
|
||||||
|
ParserHelper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pb *PuzzleBank) Parse(data []byte) error {
|
||||||
|
|
||||||
|
lines := bytes.Split(data, []byte("\n"))
|
||||||
|
for _, line := range lines {
|
||||||
|
if len(line) == 0 { // Überspringe leere Zeile
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(line) != 99 {
|
||||||
|
return fmt.Errorf("Zeile hat die falsche länge. soll: 100, ist: %d", len(line))
|
||||||
|
}
|
||||||
|
|
||||||
|
//siehe Readme in github repo
|
||||||
|
//sha1Hash := bytes.TrimSpace(line[0:12]) // Wird aktuell nicht gebraucht, später zum sudoku vergleichen
|
||||||
|
sudokuStr := string(line[13:94])
|
||||||
|
ratingStr := string(line[96:99])
|
||||||
|
//fmt.Println(sudokuStr)
|
||||||
|
|
||||||
|
var rating float64
|
||||||
|
_, err := fmt.Sscanf(ratingStr, "%f", &rating)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("can nor parse raiting")
|
||||||
|
}
|
||||||
|
|
||||||
|
props := field.Properties{
|
||||||
|
Rows: 9,
|
||||||
|
Columns: 9,
|
||||||
|
BlockRows: 3,
|
||||||
|
BlockColumns: 3,
|
||||||
|
BlockSizeRow: 3,
|
||||||
|
BlockSizeColumn: 3,
|
||||||
|
Rating: rating,
|
||||||
|
}
|
||||||
|
|
||||||
|
cells := make([][]field.Cell, props.Rows)
|
||||||
|
for i := range cells {
|
||||||
|
cells[i] = make([]field.Cell, props.Columns)
|
||||||
|
for j := range cells[i] {
|
||||||
|
number, err := strconv.Atoi(string(sudokuStr[i*9+j]))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Kann Zeichen nicht in Zahl konvertieren")
|
||||||
|
}
|
||||||
|
pos := field.NewPosition(
|
||||||
|
i,
|
||||||
|
j,
|
||||||
|
i/props.BlockRows,
|
||||||
|
j/props.BlockColumns,
|
||||||
|
i%props.BlockRows,
|
||||||
|
j%props.BlockColumns)
|
||||||
|
|
||||||
|
cells[i][j] = *field.NewCell(number, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pb.fields = append(pb.fields, *field.New(props, cells))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+6
-5
@@ -4,21 +4,22 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.kleiax.de/homepage/board"
|
"git.kleiax.de/homepage/field"
|
||||||
"git.kleiax.de/homepage/logic"
|
"git.kleiax.de/homepage/logic"
|
||||||
|
"git.kleiax.de/homepage/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Game struct {
|
type Game struct {
|
||||||
field *board.Field
|
field *field.Field
|
||||||
solver logic.Solver
|
solver logic.Solver
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(parser board.Parser, solver logic.Solver, data []byte) (*Game, error) {
|
func New(parser parser.Parser, solver logic.Solver, data []byte) (*Game, error) {
|
||||||
err := parser.Parse(data)
|
err := parser.Parse(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Game{}, fmt.Errorf("can not create game: %w", err)
|
return &Game{}, fmt.Errorf("can not create game: %w", err)
|
||||||
}
|
}
|
||||||
return &Game{solver: solver, field: parser.GetField(0)}, nil
|
return &Game{solver: solver, field: parser.GetField(2)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Solve() error {
|
func (g *Game) Solve() error {
|
||||||
@@ -43,6 +44,6 @@ func (g *Game) isFinished() bool {
|
|||||||
return g.field.IsSolved()
|
return g.field.IsSolved()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) GetField() board.Field {
|
func (g *Game) GetField() field.Field {
|
||||||
return *g.field
|
return *g.field
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,11 @@
|
|||||||
- 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
|
- Feld immer wieder auf Validität checken (keine doppelten Zahlen in einem Part) damit keine Fehler bei den Strategien auftauchen können
|
||||||
|
- Validator in Logic oder Field?
|
||||||
|
- kommt in field
|
||||||
|
- field sollte flag haben ob vor einem change geprüft wird, ob das Feld danach noch valid ist
|
||||||
|
- Für die solver ein Interface implementieren, damit die folgenden Solver benutzt werden können:
|
||||||
|
- backtracking Solver
|
||||||
|
- dlx solver
|
||||||
|
- notes (menschlicher solver)
|
||||||
|
- Was passiert wenn zum Beispiel bei hiddenSingle ein Treffer zwei Changes auslöst? Muss ein Überprüfung her, ob die betreffende Zelle schon in einem vorheringen Change bearbeitet wird? Ich denka ja
|
||||||
|
- Debugging verbesser, Loglevel etc, überall sinnvolle Logs einbauen
|
||||||
|
- Flag oder so für sich selbst überprüfende Sachen, nur im debug modus nach jedem bisschen das Feld auf validität prüfen
|
||||||
Reference in New Issue
Block a user