Initial commit
This commit is contained in:
+100
@@ -0,0 +1,100 @@
|
||||
package board
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// NOTES STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type Notes struct {
|
||||
numbers []int
|
||||
}
|
||||
|
||||
func (n *Notes) Add(field *Field, cell *Cell, note int, trigger string, marks []Mark) {
|
||||
for _, existing := range n.numbers {
|
||||
if existing == note {
|
||||
return // Note already exists
|
||||
}
|
||||
}
|
||||
field.changes = append(field.changes, Change{
|
||||
Cell: cell,
|
||||
marks: marks,
|
||||
action: ActionSetNote,
|
||||
value: note,
|
||||
from: 0,
|
||||
triggerdBy: trigger,
|
||||
})
|
||||
n.numbers = append(n.numbers, note)
|
||||
}
|
||||
|
||||
func (n *Notes) Remove(field *Field, cell *Cell, note int, trigger string, marks []Mark) {
|
||||
for i, existing := range n.numbers {
|
||||
if existing == note {
|
||||
field.changes = append(field.changes, Change{
|
||||
Cell: cell,
|
||||
marks: marks,
|
||||
action: ActionRemoveNote,
|
||||
value: 0,
|
||||
from: note,
|
||||
triggerdBy: trigger,
|
||||
})
|
||||
|
||||
n.numbers = append(n.numbers[:i], n.numbers[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// CELL STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type Cell struct {
|
||||
number int
|
||||
Notes *Notes
|
||||
Pos *Position
|
||||
}
|
||||
|
||||
func (c *Cell) SetNumber(field *Field, n int, trigger string, marks []Mark) {
|
||||
if c.number == n {
|
||||
return
|
||||
}
|
||||
|
||||
if field == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if n <= 0 || n > field.rows || n > field.columns {
|
||||
return
|
||||
}
|
||||
|
||||
field.changes = append(field.changes, Change{
|
||||
Cell: c,
|
||||
marks: marks,
|
||||
action: ActionSetNumber,
|
||||
value: n,
|
||||
from: c.number,
|
||||
triggerdBy: trigger,
|
||||
})
|
||||
|
||||
c.number = n
|
||||
}
|
||||
|
||||
func (c *Cell) RemoveNumber(field *Field, trigger string, marks []Mark) {
|
||||
if c.number == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if field == nil {
|
||||
return
|
||||
}
|
||||
|
||||
field.changes = append(field.changes, Change{
|
||||
action: ActionSetNumber,
|
||||
Cell: c,
|
||||
value: 0,
|
||||
from: c.number,
|
||||
marks: marks,
|
||||
triggerdBy: trigger,
|
||||
})
|
||||
|
||||
c.number = 0
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package board
|
||||
|
||||
import "image/color"
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// MARK STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type Mark struct {
|
||||
Cell *Cell
|
||||
Change *Change
|
||||
color color.Color
|
||||
}
|
||||
|
||||
func (m *Mark) GetColor() color.Color {
|
||||
return m.color
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// CHANGE STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type ChangeAction int
|
||||
|
||||
const (
|
||||
ActionSetNumber = iota
|
||||
ActionSetNote
|
||||
ActionRemoveNumber
|
||||
ActionRemoveNote
|
||||
)
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// CHANGE STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type Change struct {
|
||||
Cell *Cell
|
||||
marks []Mark
|
||||
action ChangeAction
|
||||
value int
|
||||
from int
|
||||
triggerdBy string //strategy or manuell
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// GETTER //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
func (c *Change) GetMarks() []Mark {
|
||||
return c.marks
|
||||
}
|
||||
|
||||
func (c *Change) GetAction() ChangeAction {
|
||||
return c.action
|
||||
}
|
||||
|
||||
func (c *Change) GetTo() int {
|
||||
return c.value
|
||||
}
|
||||
|
||||
func (c *Change) GetFrom() int {
|
||||
return c.from
|
||||
}
|
||||
|
||||
func (c *Change) GetTriggeredBy() string {
|
||||
return c.triggerdBy
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// ??? //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
func (c *Change) do() {
|
||||
switch c.action {
|
||||
|
||||
case ActionSetNumber:
|
||||
c.Cell.number = c.value
|
||||
|
||||
case ActionSetNote:
|
||||
c.Cell.Notes.numbers = append(c.Cell.Notes.numbers, c.value)
|
||||
|
||||
case ActionRemoveNumber:
|
||||
c.Cell.number = 0
|
||||
|
||||
case ActionRemoveNote:
|
||||
for i, existing := range c.Cell.Notes.numbers {
|
||||
if existing == c.from {
|
||||
c.Cell.Notes.numbers = append(c.Cell.Notes.numbers[:i], c.Cell.Notes.numbers[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package board
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
outOfBound = errors.New("invalid coordinates")
|
||||
invalidField = errors.New("can not parse Field")
|
||||
)
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
package board
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// FIELD STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type Field struct {
|
||||
rows int
|
||||
columns int
|
||||
blockRows int
|
||||
blockColumns int
|
||||
blockSizeRow int
|
||||
blockSizeColumn int
|
||||
cells [][]Cell
|
||||
changes []Change
|
||||
sha1 []byte
|
||||
rating float64
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// GETTER //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
func (f *Field) GetRow(r int) (*Line, error) {
|
||||
if r >= f.rows || r < 0 {
|
||||
return nil, outOfBound
|
||||
}
|
||||
|
||||
return &Line{cells: append([]Cell(nil), f.cells[r]...)}, nil
|
||||
}
|
||||
|
||||
func (f *Field) GetColumn(c int) (*Line, error) {
|
||||
if c >= f.columns || c < 0 {
|
||||
return nil, outOfBound
|
||||
}
|
||||
|
||||
// Performance
|
||||
result := &Line{
|
||||
cells: make([]Cell, 0, f.rows),
|
||||
}
|
||||
|
||||
// Copying
|
||||
for _, row := range f.cells {
|
||||
result.cells = append(result.cells, row[c])
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (f *Field) GetBlock(r, c int) (*Block, error) {
|
||||
if r < 0 || r >= f.blockRows || c < 0 || c >= f.blockColumns {
|
||||
return nil, outOfBound
|
||||
}
|
||||
|
||||
startRow := r * f.blockSizeRow
|
||||
startCol := c * f.blockSizeColumn
|
||||
|
||||
block := &Block{
|
||||
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])
|
||||
}
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func (f *Field) GetCell(r, c int) (*Cell, error) {
|
||||
if r > f.rows || c > f.columns {
|
||||
return nil, outOfBound
|
||||
}
|
||||
return &f.cells[r][c], nil
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// FOREACH FUNCTIONS //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
func (f *Field) ForEachPart(fn func(part Part)) {
|
||||
f.ForEachRow(func(line *Line) {
|
||||
fn(line)
|
||||
})
|
||||
|
||||
f.ForEachColumn(func(line *Line) {
|
||||
fn(line)
|
||||
})
|
||||
|
||||
f.ForEachBlock(func(block *Block) {
|
||||
fn(block)
|
||||
})
|
||||
}
|
||||
|
||||
func (f *Field) ForEachRow(fn func(line *Line)) {
|
||||
for i := range f.rows {
|
||||
line, err := f.GetRow(i)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
fn(line)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Field) ForEachColumn(fn func(line *Line)) {
|
||||
for i := range f.columns {
|
||||
column, err := f.GetColumn(i)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
fn(column)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (f *Field) ForEachBlock(fn func(block *Block)) {
|
||||
for r := range f.blockRows {
|
||||
for c := range f.blockColumns {
|
||||
block, err := f.GetBlock(r, c)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
fn(block)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Field) ForEachCell(fn func(cell *Cell)) {
|
||||
for _, row := range f.cells {
|
||||
for _, cell := range row {
|
||||
fn(&cell)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// MODIFIER //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
func (f *Field) AddChange(cell *Cell, action ChangeAction, value, from int, trigger string, marks []Mark) {
|
||||
change := Change{
|
||||
Cell: cell,
|
||||
marks: marks,
|
||||
action: action,
|
||||
value: value,
|
||||
from: from,
|
||||
triggerdBy: trigger,
|
||||
}
|
||||
|
||||
change.do()
|
||||
|
||||
f.changes = append(f.changes, change)
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
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].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
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package board
|
||||
|
||||
type Part interface {
|
||||
IsSolved() bool
|
||||
ForEachCell(fn func(cell *Cell))
|
||||
GetMissingNumbers() []int
|
||||
RemoveNote(field *Field, note int, trigger string, marks []Mark)
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// LINE STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type Line struct {
|
||||
cells []Cell
|
||||
}
|
||||
|
||||
func (l *Line) IsSolved() bool {
|
||||
//TODO
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *Line) ForEachCell(fn func(cell *Cell)) {
|
||||
for _, cell := range l.cells {
|
||||
fn(&cell)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Line) GetMissingNumbers() []int {
|
||||
return getMissingNumbersHelper(len(l.cells), l.ForEachCell)
|
||||
}
|
||||
|
||||
func (l *Line) RemoveNote(field *Field, note int, trigger string, marks []Mark) {
|
||||
l.ForEachCell(func(cell *Cell) {
|
||||
cell.Notes.Remove(field, cell, note, trigger, marks)
|
||||
})
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// BLOCK STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type Block struct {
|
||||
cells [][]Cell
|
||||
}
|
||||
|
||||
func (b *Block) IsSolved() bool {
|
||||
//TODO
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *Block) ForEachCell(fn func(cell *Cell)) {
|
||||
for _, row := range b.cells {
|
||||
for _, cell := range row {
|
||||
fn(&cell)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Block) GetMissingNumbers() []int {
|
||||
max := len(b.cells) * len(b.cells[0])
|
||||
return getMissingNumbersHelper(max, b.ForEachCell)
|
||||
}
|
||||
|
||||
func (b *Block) RemoveNote(field *Field, note int, trigger string, marks []Mark) {
|
||||
b.ForEachCell(func(cell *Cell) {
|
||||
cell.Notes.Remove(field, cell, note, trigger, marks)
|
||||
})
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// HELPER //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
func getMissingNumbersHelper(max int, iterate func(fn func(cell *Cell))) []int {
|
||||
present := make(map[int]bool, max)
|
||||
|
||||
iterate(func(cell *Cell) {
|
||||
if cell.number != 0 {
|
||||
present[cell.number] = true
|
||||
}
|
||||
})
|
||||
|
||||
var missing []int
|
||||
for i := 1; i <= max; i++ {
|
||||
if !present[i] {
|
||||
missing = append(missing, i)
|
||||
}
|
||||
}
|
||||
return missing
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package board
|
||||
|
||||
type Position struct {
|
||||
row int
|
||||
column int
|
||||
blockRow int
|
||||
blockColumn int
|
||||
inBlockRow int
|
||||
inBlockColumn int
|
||||
}
|
||||
|
||||
func (p *Position) GetRow() int {
|
||||
return p.row
|
||||
}
|
||||
|
||||
func (p *Position) GetColumn() int {
|
||||
return p.column
|
||||
}
|
||||
|
||||
func (p *Position) GetBlockRow() int {
|
||||
return p.blockRow
|
||||
}
|
||||
|
||||
func (p *Position) GetBlockColumn() int {
|
||||
return p.blockColumn
|
||||
}
|
||||
|
||||
func (p *Position) GetInBlockRow() int {
|
||||
return p.inBlockRow
|
||||
}
|
||||
|
||||
func (p *Position) GetInBlockColumn() int {
|
||||
return p.inBlockColumn
|
||||
}
|
||||
|
||||
func (p *Position) GetCoords() (int, int) {
|
||||
return p.row, p.column
|
||||
}
|
||||
|
||||
func (p *Position) GetBlockCoords() (int, int) {
|
||||
return p.blockRow, p.blockColumn
|
||||
}
|
||||
|
||||
func (p *Position) GetInBlockCoords() (int, int) {
|
||||
return p.inBlockRow, p.inBlockColumn
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package board
|
||||
|
||||
import "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) 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
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
This is a free and unencumbered data set released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this data, either in source form or transformed, for any
|
||||
purpose, commercial or non-commercial, and by any means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org>
|
||||
@@ -0,0 +1,37 @@
|
||||
# Sudoku Exchange "Puzzle Bank"
|
||||
|
||||
This repository contains several hundred thousand Sudoku puzzles that were
|
||||
computer generated for use by the [Sudoku Exchange](https://sudokuexchange.com/)
|
||||
web site.
|
||||
|
||||
The puzzles were generated using the
|
||||
[QQWing Sudoku](https://github.com/stephenostermiller/qqwing) software, which
|
||||
ensure that each puzzle has a unique solution.
|
||||
The generated puzzles were then graded using
|
||||
[Sukaku Explainer](https://github.com/SudokuMonster/SukakuExplainer) and
|
||||
sorted into four 'buckets':
|
||||
|
||||
| Filename | Difficulty Rating |
|
||||
| ------------------- | ----------------- |
|
||||
| [easy.txt][1] | < 1.5 |
|
||||
| [medium.txt][2] | < 2.5 |
|
||||
| [hard.txt][3] | < 5.0 |
|
||||
| [diabolical.txt][4] | ≥ 5.0 |
|
||||
|
||||
Each text file has one puzzle per line, represented as three space-separated
|
||||
fields and a Unix-style line-ending, for a total of 100 bytes per record:
|
||||
|
||||
12 bytes of SHA1 hash of the digits string (for randomising order)
|
||||
81 bytes of puzzle digits
|
||||
4 bytes of rating (nn.n)
|
||||
3 bytes of white-space (including the linefeed);
|
||||
100 bytes total
|
||||
|
||||
### License
|
||||
|
||||
The data set is dedicated to the [public domain](LICENSE.txt).
|
||||
|
||||
[1]: https://github.com/grantm/sudoku-exchange-puzzle-bank/raw/master/easy.txt
|
||||
[2]: https://github.com/grantm/sudoku-exchange-puzzle-bank/raw/master/medium.txt
|
||||
[3]: https://github.com/grantm/sudoku-exchange-puzzle-bank/raw/master/hard.txt
|
||||
[4]: https://github.com/grantm/sudoku-exchange-puzzle-bank/raw/master/diabolical.txt
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
0000183b305c 451723860007000800000816000000030000005000100730040086906000204840572093000409000 1.2
|
||||
0001d5d6314e 302401809001000300000000000040708010780502036000090000200609003900000008800070005 1.2
|
||||
000212406270 000823001003000400070000052300960010000102000010038006830000040002000900600789000 1.2
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
package logic
|
||||
|
||||
type Change struct {
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package logic
|
||||
@@ -0,0 +1,43 @@
|
||||
package logic
|
||||
|
||||
import "git.kleiax.de/homepage/libs/sudoku/board"
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// SOLVER STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type Solver struct {
|
||||
strategies []Strategy
|
||||
conf struct {
|
||||
all bool
|
||||
repeat bool
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Solver) Add(strategy Strategy) {
|
||||
s.strategies = append(s.strategies, strategy)
|
||||
}
|
||||
|
||||
func (s *Solver) initStragies(field *board.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
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Solver) search() {
|
||||
|
||||
}
|
||||
|
||||
func (s *Solver) getSolutionPath() []board.Change {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package strategies
|
||||
|
||||
type XChain struct {
|
||||
Base
|
||||
}
|
||||
type XYChain struct {
|
||||
Base
|
||||
}
|
||||
|
||||
type XChainLoop struct {
|
||||
Base
|
||||
}
|
||||
type XYChainLoop struct {
|
||||
Base
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package strategies
|
||||
|
||||
import "git.kleiax.de/homepage/libs/sudoku/board"
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// LAST_DIGIT STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type LastDigit struct {
|
||||
Base
|
||||
}
|
||||
|
||||
func (ld *LastDigit) SearchProgressableCells() int {
|
||||
ld.field.ForEachPart(func(part board.Part) {
|
||||
missingNumbers := part.GetMissingNumbers()
|
||||
if len(missingNumbers) == 1 {
|
||||
// var emptyCell *Cell
|
||||
// part.forEachCell(func(cell *Cell) {
|
||||
// if cell.number == 0 {
|
||||
// emptyCell = cell
|
||||
// }
|
||||
// })
|
||||
// change := Change{
|
||||
// cell: emptyCell,
|
||||
// action: ActionSetNumber,
|
||||
// value: missingNumbers[0],
|
||||
// }
|
||||
// ld.changes = append(ld.changes, change)
|
||||
}
|
||||
})
|
||||
//fmt.Printf("LastDigit Changes %d", len(ld.changes))
|
||||
return len(ld.changes)
|
||||
}
|
||||
|
||||
func (ld *LastDigit) getName() string {
|
||||
return "Last Digit - Letzte Zahl"
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// CRP STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type CRP struct {
|
||||
Base
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// SIMPLE_COLORING_T1 STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type SimpleColoringT1 struct {
|
||||
Base
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package strategies
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// HIDDEN_SINGLE STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type HiddenSingle struct {
|
||||
Base
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// HIDDEN_PAIR STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type HiddenPair struct {
|
||||
Base
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// HIDDEN_TRIPLE STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type HiddenTriple struct {
|
||||
Base
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package strategies
|
||||
|
||||
type LockedPair struct {
|
||||
Base
|
||||
}
|
||||
|
||||
type LockedCandidateT1 struct {
|
||||
Base
|
||||
}
|
||||
type LockedCandidateT2 struct {
|
||||
Base
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package strategies
|
||||
|
||||
import "git.kleiax.de/homepage/libs/sudoku/board"
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// NAKED_SINGLE STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type NakedSingle struct {
|
||||
Base
|
||||
}
|
||||
|
||||
func (ns *NakedSingle) SearchProgressableCells() int {
|
||||
ns.field.ForEachPart(func(part board.Part) {
|
||||
part.ForEachCell(func(cell *board.Cell) {
|
||||
// candidates := cell.notes.numbers
|
||||
// if len(candidates) == 1 {
|
||||
// change := Change{
|
||||
// cell: cell,
|
||||
// action: ActionSetNumber,
|
||||
// value: candidates[0],
|
||||
// }
|
||||
// ns.changes = append(ns.changes, change)
|
||||
// }
|
||||
})
|
||||
})
|
||||
return len(ns.changes)
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// NAKED_DOUBLE STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type NakedPair struct {
|
||||
Base
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// NAKED_TRIPLE STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type NakedTriple struct {
|
||||
Base
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package strategies
|
||||
|
||||
type EmptyRectangle struct {
|
||||
Base
|
||||
}
|
||||
type UniqueRectangleT1 struct {
|
||||
Base
|
||||
}
|
||||
type UniqueRectangleT4 struct {
|
||||
Base
|
||||
}
|
||||
type UniqueRectangleT7 struct {
|
||||
Base
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package strategies
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.kleiax.de/homepage/libs/sudoku/board"
|
||||
)
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// STRATEGY INTERFACE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type Strategy interface {
|
||||
Init(f *board.Field)
|
||||
ApplyAll() int
|
||||
ApplyNext() bool
|
||||
ApplyOne(n int) bool
|
||||
Name() string
|
||||
SearchProgressableCells() int
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// BASE STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type Base struct {
|
||||
name string
|
||||
field *board.Field
|
||||
changes []board.Change //eigener Typ muss her
|
||||
}
|
||||
|
||||
func (b *Base) Init(f *board.Field) {
|
||||
b.field = f
|
||||
}
|
||||
|
||||
func (b *Base) ApplyAll() int {
|
||||
// for _, change := range sb.changes {
|
||||
// //change.do()
|
||||
// }
|
||||
return len(b.changes)
|
||||
}
|
||||
|
||||
func (b *Base) ApplyNext() bool {
|
||||
if len(b.changes) < 1 {
|
||||
return false
|
||||
}
|
||||
//sb.changes[0].do()
|
||||
b.changes = b.changes[1:]
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *Base) ApplyOne(n int) bool {
|
||||
if len(b.changes) <= n || n < 0 {
|
||||
return false
|
||||
}
|
||||
//sb.changes[n].do()
|
||||
b.changes = append(b.changes[:n], b.changes[n+1:]...)
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *Base) getName() string {
|
||||
return "Unkown"
|
||||
}
|
||||
|
||||
func (b *Base) Name() string {
|
||||
return fmt.Sprintf("Die Strategie heißt: %s", b.getName())
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package strategies
|
||||
|
||||
type Turbot2StringKite struct {
|
||||
Base
|
||||
}
|
||||
type TurbotSkyscraper struct {
|
||||
Base
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package strategies
|
||||
|
||||
type XWing struct {
|
||||
Base
|
||||
}
|
||||
type XYWing struct {
|
||||
Base
|
||||
}
|
||||
type XYZWing struct {
|
||||
Base
|
||||
}
|
||||
type WXYZWingBasic struct {
|
||||
Base
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"git.kleiax.de/homepage/libs/sudoku"
|
||||
)
|
||||
|
||||
func main() {
|
||||
solver := sudoku.Solver{}
|
||||
solver.Add(&sudoku.LastDigit{})
|
||||
solver.Add(&sudoku.NakedSingle{})
|
||||
game, err := sudoku.New(&sudoku.PuzzleBank{}, solver, openFile())
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
field := game.GetField()
|
||||
fmt.Println(field.String())
|
||||
|
||||
err = game.Solve()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
|
||||
field = game.GetField()
|
||||
fmt.Println(field.String())
|
||||
}
|
||||
|
||||
func openFile() []byte {
|
||||
file, err := os.Open("libs/sudoku/data/sudoku-exchange-puzzle-bank/easy3.txt")
|
||||
if err != nil {
|
||||
fmt.Println("Kann datei nicht öffnen")
|
||||
os.Exit(3)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
content, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
fmt.Println("Kann Daten nicht extrahieren")
|
||||
os.Exit(4)
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package sudoku
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.kleiax.de/homepage/libs/sudoku/board"
|
||||
"git.kleiax.de/homepage/libs/sudoku/logic"
|
||||
)
|
||||
|
||||
type Game struct {
|
||||
field *board.Field
|
||||
solver logic.Solver
|
||||
}
|
||||
|
||||
func New(parser Parser, solver Solver, data []byte) (*Game, error) {
|
||||
err := parser.parse(data)
|
||||
if err != nil {
|
||||
return &Game{}, fmt.Errorf("can not create game: %w", err)
|
||||
}
|
||||
return &Game{solver: solver, field: parser.getField(0)}, nil
|
||||
}
|
||||
|
||||
func (g *Game) Solve() error {
|
||||
g.solver.initStragies(g.field)
|
||||
for !g.isFinished() {
|
||||
if !g.nextSolveStep() {
|
||||
return errors.New("no strategy can solve the puzzle")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Game) nextSolveStep() bool {
|
||||
return g.solver.run(0)
|
||||
}
|
||||
|
||||
func (g *Game) prevSolveStep() {
|
||||
// far far in the future
|
||||
}
|
||||
|
||||
func (g *Game) isFinished() bool {
|
||||
return g.field.isSolved()
|
||||
}
|
||||
|
||||
func (g *Game) GetField() Field {
|
||||
return *g.field
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package sudoku
|
||||
|
||||
// type StrategyVisualization interface {
|
||||
// pointOut(f *Field) []Mark
|
||||
// }
|
||||
Reference in New Issue
Block a user