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