Refactor Sudoku field and solver implementation
This commit is contained in:
+356
-202
@@ -2,13 +2,11 @@ package field
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// META STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type Properties struct {
|
||||
Rows int
|
||||
Columns int
|
||||
@@ -19,289 +17,445 @@ type Properties struct {
|
||||
Rating float64
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// FIELD STRUCTURE //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
type Field struct {
|
||||
props *Properties
|
||||
cells [][]Cell
|
||||
changes []Change
|
||||
}
|
||||
|
||||
func New(props Properties, cells [][]Cell) *Field {
|
||||
field := Field{
|
||||
props: &props,
|
||||
cells: cells,
|
||||
}
|
||||
return &field
|
||||
type fieldSnapshot struct {
|
||||
numbers [][]int
|
||||
notes [][][]int
|
||||
changes []Change
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// GETTER //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
func (f *Field) GetRow(r int) (*Row, error) {
|
||||
if r >= f.props.Rows || r < 0 {
|
||||
return nil, outOfBound
|
||||
func New(props Properties, cells [][]Cell) (*Field, error) {
|
||||
if err := validateProperties(props); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(cells) != props.Rows {
|
||||
return nil, fmt.Errorf("%w: expected %d rows, got %d", ErrInvalidField, props.Rows, len(cells))
|
||||
}
|
||||
|
||||
cellPtrs := make([]*Cell, len(f.cells[r]))
|
||||
for i := range f.cells[r] {
|
||||
cellPtrs[i] = &f.cells[r][i]
|
||||
cloned := make([][]Cell, props.Rows)
|
||||
for row := 0; row < props.Rows; row++ {
|
||||
if len(cells[row]) != props.Columns {
|
||||
return nil, fmt.Errorf("%w: row %d has %d cells, expected %d", ErrInvalidField, row, len(cells[row]), props.Columns)
|
||||
}
|
||||
cloned[row] = make([]Cell, props.Columns)
|
||||
for column := 0; column < props.Columns; column++ {
|
||||
source := &cells[row][column]
|
||||
if source.pos == nil {
|
||||
return nil, fmt.Errorf("%w: cell %d/%d has no position", ErrInvalidField, row, column)
|
||||
}
|
||||
if !positionMatches(source.pos, props, row, column) {
|
||||
return nil, fmt.Errorf("%w: cell %d/%d has inconsistent position", ErrInvalidField, row, column)
|
||||
}
|
||||
if source.number < 0 || source.number > props.Rows {
|
||||
return nil, fmt.Errorf("%w: cell %d/%d contains %d", ErrInvalidField, row, column, source.number)
|
||||
}
|
||||
if source.notes == nil {
|
||||
return nil, fmt.Errorf("%w: cell %d/%d has no notes", ErrInvalidField, row, column)
|
||||
}
|
||||
notes, err := normalizeNotes(source.notes.numbers, props.Rows, source.number)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: cell %d/%d: %w", ErrInvalidField, row, column, err)
|
||||
}
|
||||
cloned[row][column] = Cell{
|
||||
number: source.number,
|
||||
notes: &Notes{numbers: notes},
|
||||
pos: NewPosition(row, column, row/props.BlockSizeRow, column/props.BlockSizeColumn, row%props.BlockSizeRow, column%props.BlockSizeColumn),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &Row{
|
||||
Line: Line{
|
||||
cells: cellPtrs,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *Field) GetColumn(c int) (*Column, error) {
|
||||
if c >= f.props.Columns || c < 0 {
|
||||
return nil, outOfBound
|
||||
result := &Field{props: &props, cells: cloned}
|
||||
if !result.hasValidNumbers() || !result.hasValidNotes() {
|
||||
return nil, fmt.Errorf("%w: duplicate values or invalid candidates", ErrInvalidField)
|
||||
}
|
||||
|
||||
// Performance
|
||||
result := &Column{
|
||||
Line: Line{
|
||||
cells: make([]*Cell, 0, f.props.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.props.BlockRows || c < 0 || c >= f.props.BlockColumns {
|
||||
return nil, outOfBound
|
||||
func validateProperties(props Properties) error {
|
||||
if props.Rows <= 0 || props.Columns <= 0 || props.Rows != props.Columns {
|
||||
return fmt.Errorf("%w: rows and columns must be equal and positive", ErrInvalidField)
|
||||
}
|
||||
|
||||
startRow := r * f.props.BlockSizeRow
|
||||
startCol := c * f.props.BlockSizeColumn
|
||||
|
||||
block := &Block{
|
||||
cells: make([][]*Cell, f.props.BlockSizeRow),
|
||||
if props.BlockRows <= 0 || props.BlockColumns <= 0 || props.BlockSizeRow <= 0 || props.BlockSizeColumn <= 0 {
|
||||
return fmt.Errorf("%w: block dimensions must be positive", ErrInvalidField)
|
||||
}
|
||||
if props.BlockRows*props.BlockSizeRow != props.Rows || props.BlockColumns*props.BlockSizeColumn != props.Columns {
|
||||
return fmt.Errorf("%w: block dimensions do not cover the field", ErrInvalidField)
|
||||
}
|
||||
if props.BlockSizeRow*props.BlockSizeColumn != props.Rows {
|
||||
return fmt.Errorf("%w: each block must contain %d cells", ErrInvalidField, props.Rows)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for row := range block.cells {
|
||||
// Effizientes Kopieren der Zeile
|
||||
block.cells[row] = make([]*Cell, f.props.BlockSizeColumn)
|
||||
|
||||
for column := range block.cells[row] {
|
||||
block.cells[row][column] = &f.cells[startRow+row][startCol+column]
|
||||
func normalizeNotes(notes []int, max, cellNumber int) ([]int, error) {
|
||||
if cellNumber != 0 && len(notes) != 0 {
|
||||
return nil, fmt.Errorf("filled cells cannot contain notes")
|
||||
}
|
||||
result := slices.Clone(notes)
|
||||
slices.Sort(result)
|
||||
result = slices.Compact(result)
|
||||
for _, note := range result {
|
||||
if note < 1 || note > max {
|
||||
return nil, fmt.Errorf("note %d is out of range", note)
|
||||
}
|
||||
}
|
||||
return block, nil
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (f *Field) GetEachPartAtPos(pos *Position) []Part {
|
||||
func positionMatches(pos *Position, props Properties, row, column int) bool {
|
||||
return pos.row == row && pos.column == column &&
|
||||
pos.blockRow == row/props.BlockSizeRow && pos.blockColumn == column/props.BlockSizeColumn &&
|
||||
pos.inBlockRow == row%props.BlockSizeRow && pos.inBlockColumn == column%props.BlockSizeColumn
|
||||
}
|
||||
|
||||
func (f *Field) GetRow(row int) (*Row, error) {
|
||||
if !f.hasStructure() || row < 0 || row >= f.props.Rows {
|
||||
return nil, ErrOutOfBounds
|
||||
}
|
||||
cells := make([]*Cell, f.props.Columns)
|
||||
for column := range f.cells[row] {
|
||||
cells[column] = &f.cells[row][column]
|
||||
}
|
||||
return &Row{Line: Line{cells: cells}}, nil
|
||||
}
|
||||
|
||||
func (f *Field) GetColumn(column int) (*Column, error) {
|
||||
if !f.hasStructure() || column < 0 || column >= f.props.Columns {
|
||||
return nil, ErrOutOfBounds
|
||||
}
|
||||
cells := make([]*Cell, f.props.Rows)
|
||||
for row := range f.cells {
|
||||
cells[row] = &f.cells[row][column]
|
||||
}
|
||||
return &Column{Line: Line{cells: cells}}, nil
|
||||
}
|
||||
|
||||
func (f *Field) GetBlock(row, column int) (*Block, error) {
|
||||
if !f.hasStructure() || row < 0 || row >= f.props.BlockRows || column < 0 || column >= f.props.BlockColumns {
|
||||
return nil, ErrOutOfBounds
|
||||
}
|
||||
startRow := row * f.props.BlockSizeRow
|
||||
startColumn := column * f.props.BlockSizeColumn
|
||||
cells := make([][]*Cell, f.props.BlockSizeRow)
|
||||
for blockRow := range cells {
|
||||
cells[blockRow] = make([]*Cell, f.props.BlockSizeColumn)
|
||||
for blockColumn := range cells[blockRow] {
|
||||
cells[blockRow][blockColumn] = &f.cells[startRow+blockRow][startColumn+blockColumn]
|
||||
}
|
||||
}
|
||||
return &Block{cells: cells}, nil
|
||||
}
|
||||
|
||||
func (f *Field) GetEachPartAtPos(pos *Position) ([]Part, error) {
|
||||
if pos == nil {
|
||||
return nil, ErrOutOfBounds
|
||||
}
|
||||
row, err := f.GetRow(pos.row)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
column, err := f.GetColumn(pos.column)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, err := f.GetBlock(pos.blockRow, pos.blockColumn)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append([]Part{}, row, column, block)
|
||||
return []Part{row, column, block}, nil
|
||||
}
|
||||
|
||||
func (f *Field) GetCell(r, c int) (*Cell, error) {
|
||||
if r > f.props.Rows || c > f.props.Columns {
|
||||
return nil, outOfBound
|
||||
func (f *Field) GetCell(row, column int) (*Cell, error) {
|
||||
if !f.hasStructure() || row < 0 || row >= f.props.Rows || column < 0 || column >= f.props.Columns {
|
||||
return nil, ErrOutOfBounds
|
||||
}
|
||||
return &f.cells[r][c], nil
|
||||
return &f.cells[row][column], nil
|
||||
}
|
||||
|
||||
func (f *Field) GetProperties() (Properties, error) {
|
||||
if !f.hasStructure() {
|
||||
return Properties{}, ErrInvalidField
|
||||
}
|
||||
return *f.props, nil
|
||||
}
|
||||
|
||||
func (f *Field) GetRating() float64 {
|
||||
if !f.hasStructure() {
|
||||
return 0
|
||||
}
|
||||
return f.props.Rating
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// FOREACH FUNCTIONS //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
func (f *Field) ForEachPart(fn func(part Part)) {
|
||||
f.ForEachRow(func(row *Row) {
|
||||
fn(row)
|
||||
})
|
||||
|
||||
f.ForEachColumn(func(column *Column) {
|
||||
fn(column)
|
||||
})
|
||||
|
||||
f.ForEachBlock(func(block *Block) {
|
||||
fn(block)
|
||||
})
|
||||
func (f *Field) ForEachPart(fn func(Part)) {
|
||||
if !f.hasStructure() || fn == nil {
|
||||
return
|
||||
}
|
||||
f.ForEachRow(func(row *Row) { fn(row) })
|
||||
f.ForEachColumn(func(column *Column) { fn(column) })
|
||||
f.ForEachBlock(func(block *Block) { fn(block) })
|
||||
}
|
||||
|
||||
func (f *Field) ForEachPartAtPos(pos *Position, fn func(part Part)) {
|
||||
parts := f.GetEachPartAtPos(pos)
|
||||
func (f *Field) ForEachPartAtPos(pos *Position, fn func(Part)) error {
|
||||
if fn == nil {
|
||||
return nil
|
||||
}
|
||||
parts, err := f.GetEachPartAtPos(pos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, part := range parts {
|
||||
fn(part)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Field) ForEachRow(fn func(*Row)) {
|
||||
if !f.hasStructure() || fn == nil {
|
||||
return
|
||||
}
|
||||
for row := 0; row < f.props.Rows; row++ {
|
||||
part, _ := f.GetRow(row)
|
||||
fn(part)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Field) ForEachRow(fn func(row *Row)) {
|
||||
for i := range f.props.Rows {
|
||||
row, err := f.GetRow(i)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
fn(row)
|
||||
func (f *Field) ForEachColumn(fn func(*Column)) {
|
||||
if !f.hasStructure() || fn == nil {
|
||||
return
|
||||
}
|
||||
for column := 0; column < f.props.Columns; column++ {
|
||||
part, _ := f.GetColumn(column)
|
||||
fn(part)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Field) ForEachColumn(fn func(column *Column)) {
|
||||
for i := range f.props.Columns {
|
||||
column, err := f.GetColumn(i)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
fn(column)
|
||||
func (f *Field) ForEachBlock(fn func(*Block)) {
|
||||
if !f.hasStructure() || fn == nil {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (f *Field) ForEachBlock(fn func(block *Block)) {
|
||||
for r := range f.props.BlockRows {
|
||||
for c := range f.props.BlockColumns {
|
||||
block, err := f.GetBlock(r, c)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
fn(block)
|
||||
for row := 0; row < f.props.BlockRows; row++ {
|
||||
for column := 0; column < f.props.BlockColumns; column++ {
|
||||
part, _ := f.GetBlock(row, column)
|
||||
fn(part)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Field) ForEachCell(fn func(cell *Cell)) {
|
||||
for _, row := range f.cells {
|
||||
for _, cell := range row {
|
||||
fn(&cell)
|
||||
func (f *Field) ForEachCell(fn func(*Cell)) {
|
||||
if !f.hasStructure() || fn == nil {
|
||||
return
|
||||
}
|
||||
for row := range f.cells {
|
||||
for column := range f.cells[row] {
|
||||
fn(&f.cells[row][column])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
// MODIFIER //
|
||||
// ────────────────────────────────────────────────────────────────────────────── //
|
||||
|
||||
func (f *Field) AddChange(eChange *ExternalChange) {
|
||||
change := Change{
|
||||
Cell: eChange.Cell,
|
||||
marks: eChange.Marks,
|
||||
action: eChange.Action,
|
||||
value: eChange.Value,
|
||||
from: eChange.From,
|
||||
triggerdBy: eChange.TriggerdBy,
|
||||
}
|
||||
|
||||
change.do()
|
||||
|
||||
f.changes = append(f.changes, change)
|
||||
}
|
||||
|
||||
func (f *Field) SetRating(rating float64) {
|
||||
f.props.Rating = rating
|
||||
if f != nil && f.props != nil {
|
||||
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(" │ ")
|
||||
if !f.hasStructure() {
|
||||
return "<invalid field>"
|
||||
}
|
||||
var result strings.Builder
|
||||
for row := 0; row < f.props.Rows; row++ {
|
||||
if row > 0 && row%f.props.BlockSizeRow == 0 {
|
||||
result.WriteByte('\n')
|
||||
}
|
||||
for column := 0; column < f.props.Columns; column++ {
|
||||
if column > 0 {
|
||||
if column%f.props.BlockSizeColumn == 0 {
|
||||
result.WriteString(" | ")
|
||||
} else {
|
||||
sb.WriteString(" ║\n") // Rechte Rahmenseite + Zeilenumbruch
|
||||
result.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
value := f.cells[row][column].number
|
||||
if value == 0 {
|
||||
result.WriteRune('·')
|
||||
} else {
|
||||
sb.WriteString(" ")
|
||||
result.WriteString(strconv.Itoa(value))
|
||||
}
|
||||
}
|
||||
|
||||
// Horizontale Trennlinien nach jeder 3. Zeile
|
||||
if (i+1)%3 == 0 && i < 8 {
|
||||
sb.WriteString("╟───────┼───────┼───────╢\n")
|
||||
if row+1 < f.props.Rows {
|
||||
result.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
// Unterer Rahmen
|
||||
sb.WriteString("╚═══════╧═══════╧═══════╝")
|
||||
|
||||
return sb.String()
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func (f *Field) StringNotesForNumber(n int) string {
|
||||
return "StringNotesForNumber is not implementet"
|
||||
func (f *Field) StringNotesForNumber(number int) string {
|
||||
if !f.hasStructure() || number < 1 || number > f.props.Rows {
|
||||
return ""
|
||||
}
|
||||
var result strings.Builder
|
||||
f.ForEachCell(func(cell *Cell) {
|
||||
if cell.notes.Has(number) {
|
||||
fmt.Fprintf(&result, "%d/%d\n", cell.pos.row, cell.pos.column)
|
||||
}
|
||||
})
|
||||
return strings.TrimSuffix(result.String(), "\n")
|
||||
}
|
||||
|
||||
func (f *Field) StringNotes() string {
|
||||
var str strings.Builder
|
||||
fmt.Fprintf(&str, "Notes:\n")
|
||||
if !f.hasStructure() {
|
||||
return "Notes:"
|
||||
}
|
||||
var result strings.Builder
|
||||
result.WriteString("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)
|
||||
fmt.Fprintf(&result, "Pos: %d/%d - Notes: %v\n", cell.pos.row, cell.pos.column, cell.notes.numbers)
|
||||
})
|
||||
return str.String()
|
||||
return strings.TrimSuffix(result.String(), "\n")
|
||||
}
|
||||
|
||||
func (f *Field) IsSolved() bool {
|
||||
result := true
|
||||
f.ForEachCell(func(cell *Cell) {
|
||||
if cell.number == 0 {
|
||||
result = false
|
||||
}
|
||||
})
|
||||
|
||||
/* TODO:
|
||||
if !f.isValid() {
|
||||
if !f.IsValid() {
|
||||
return false
|
||||
}
|
||||
*/
|
||||
|
||||
return result
|
||||
for row := range f.cells {
|
||||
for column := range f.cells[row] {
|
||||
if f.cells[row][column].number == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *Field) IsValid() bool {
|
||||
//In jedem Part gibt es jede Zahl max ein mal
|
||||
//Die Zahl kommt nicht in intersecting parts vor
|
||||
return f.hasStructure() && f.hasValidNumbers() && f.hasValidNotes()
|
||||
}
|
||||
|
||||
func (f *Field) hasStructure() bool {
|
||||
if f == nil || f.props == nil || validateProperties(*f.props) != nil || len(f.cells) != f.props.Rows {
|
||||
return false
|
||||
}
|
||||
for row := range f.cells {
|
||||
if len(f.cells[row]) != f.props.Columns {
|
||||
return false
|
||||
}
|
||||
for column := range f.cells[row] {
|
||||
cell := &f.cells[row][column]
|
||||
if cell.notes == nil || cell.pos == nil || !positionMatches(cell.pos, *f.props, row, column) || cell.number < 0 || cell.number > f.props.Rows {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *Field) hasValidNumbers() bool {
|
||||
if !f.hasStructure() {
|
||||
return false
|
||||
}
|
||||
valid := true
|
||||
f.ForEachPart(func(part Part) {
|
||||
seen := make(map[int]struct{}, f.props.Rows)
|
||||
part.ForEachCell(func(cell *Cell) {
|
||||
if cell.number == 0 || !valid {
|
||||
return
|
||||
}
|
||||
if _, exists := seen[cell.number]; exists {
|
||||
valid = false
|
||||
return
|
||||
}
|
||||
seen[cell.number] = struct{}{}
|
||||
})
|
||||
})
|
||||
return valid
|
||||
}
|
||||
|
||||
func (f *Field) hasValidNotes() bool {
|
||||
if !f.hasStructure() {
|
||||
return false
|
||||
}
|
||||
valid := true
|
||||
f.ForEachCell(func(cell *Cell) {
|
||||
if !valid {
|
||||
return
|
||||
}
|
||||
if cell.number != 0 && len(cell.notes.numbers) != 0 {
|
||||
valid = false
|
||||
return
|
||||
}
|
||||
previous := 0
|
||||
for _, note := range cell.notes.numbers {
|
||||
if note <= previous || note > f.props.Rows || f.hasPeerNumber(cell, note) {
|
||||
valid = false
|
||||
return
|
||||
}
|
||||
previous = note
|
||||
}
|
||||
})
|
||||
return valid
|
||||
}
|
||||
|
||||
func (f *Field) ownsCell(cell *Cell) bool {
|
||||
if !f.hasStructure() || cell == nil || cell.pos == nil {
|
||||
return false
|
||||
}
|
||||
row, column := cell.pos.row, cell.pos.column
|
||||
return row >= 0 && row < f.props.Rows && column >= 0 && column < f.props.Columns && &f.cells[row][column] == cell
|
||||
}
|
||||
|
||||
func (f *Field) hasPeerNumber(cell *Cell, number int) bool {
|
||||
if !f.ownsCell(cell) {
|
||||
return false
|
||||
}
|
||||
row, column := cell.pos.row, cell.pos.column
|
||||
for index := 0; index < f.props.Columns; index++ {
|
||||
if index != column && f.cells[row][index].number == number {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for index := 0; index < f.props.Rows; index++ {
|
||||
if index != row && f.cells[index][column].number == number {
|
||||
return true
|
||||
}
|
||||
}
|
||||
startRow := cell.pos.blockRow * f.props.BlockSizeRow
|
||||
startColumn := cell.pos.blockColumn * f.props.BlockSizeColumn
|
||||
for blockRow := 0; blockRow < f.props.BlockSizeRow; blockRow++ {
|
||||
for blockColumn := 0; blockColumn < f.props.BlockSizeColumn; blockColumn++ {
|
||||
peer := &f.cells[startRow+blockRow][startColumn+blockColumn]
|
||||
if peer != cell && peer.number == number {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *Field) snapshot() fieldSnapshot {
|
||||
state := fieldSnapshot{numbers: make([][]int, len(f.cells)), notes: make([][][]int, len(f.cells)), changes: slices.Clone(f.changes)}
|
||||
for row := range f.cells {
|
||||
state.numbers[row] = make([]int, len(f.cells[row]))
|
||||
state.notes[row] = make([][]int, len(f.cells[row]))
|
||||
for column := range f.cells[row] {
|
||||
state.numbers[row][column] = f.cells[row][column].number
|
||||
state.notes[row][column] = slices.Clone(f.cells[row][column].notes.numbers)
|
||||
}
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
func (f *Field) restore(state fieldSnapshot) {
|
||||
for row := range f.cells {
|
||||
for column := range f.cells[row] {
|
||||
f.cells[row][column].number = state.numbers[row][column]
|
||||
f.cells[row][column].notes.numbers = slices.Clone(state.notes[row][column])
|
||||
}
|
||||
}
|
||||
f.changes = slices.Clone(state.changes)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user