161 lines
3.0 KiB
Go
161 lines
3.0 KiB
Go
package field
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
type Part interface {
|
|
IsSolved() bool
|
|
ForEachCell(func(*Cell))
|
|
GetMissingNumbers() []int
|
|
RemoveNote(*Field, int, string, []Mark) error
|
|
String() string
|
|
}
|
|
|
|
type Line struct {
|
|
cells []*Cell
|
|
}
|
|
|
|
func (l *Line) IsSolved() bool {
|
|
return partIsSolved(l)
|
|
}
|
|
|
|
func (l *Line) ForEachCell(fn func(*Cell)) {
|
|
if l == nil || fn == nil {
|
|
return
|
|
}
|
|
for _, cell := range l.cells {
|
|
fn(cell)
|
|
}
|
|
}
|
|
|
|
func (l *Line) GetMissingNumbers() []int {
|
|
if l == nil {
|
|
return nil
|
|
}
|
|
return getMissingNumbers(len(l.cells), l.ForEachCell)
|
|
}
|
|
|
|
func (l *Line) RemoveNote(f *Field, note int, trigger string, marks []Mark) error {
|
|
return removeNoteFromPart(l, f, note, trigger, marks)
|
|
}
|
|
|
|
func (l *Line) String() string {
|
|
return partString(l, "Line")
|
|
}
|
|
|
|
type Row struct {
|
|
Line
|
|
}
|
|
|
|
func (r *Row) String() string {
|
|
return partString(r, "Row")
|
|
}
|
|
|
|
type Column struct {
|
|
Line
|
|
}
|
|
|
|
func (c *Column) String() string {
|
|
return partString(c, "Column")
|
|
}
|
|
|
|
type Block struct {
|
|
cells [][]*Cell
|
|
}
|
|
|
|
func (b *Block) IsSolved() bool {
|
|
return partIsSolved(b)
|
|
}
|
|
|
|
func (b *Block) ForEachCell(fn func(*Cell)) {
|
|
if b == nil || fn == nil {
|
|
return
|
|
}
|
|
for _, row := range b.cells {
|
|
for _, cell := range row {
|
|
fn(cell)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *Block) GetMissingNumbers() []int {
|
|
if b == nil || len(b.cells) == 0 {
|
|
return nil
|
|
}
|
|
size := 0
|
|
for _, row := range b.cells {
|
|
size += len(row)
|
|
}
|
|
return getMissingNumbers(size, b.ForEachCell)
|
|
}
|
|
|
|
func (b *Block) RemoveNote(f *Field, note int, trigger string, marks []Mark) error {
|
|
return removeNoteFromPart(b, f, note, trigger, marks)
|
|
}
|
|
|
|
func (b *Block) String() string {
|
|
return partString(b, "Block")
|
|
}
|
|
|
|
func partIsSolved(part Part) bool {
|
|
if part == nil {
|
|
return false
|
|
}
|
|
cellCount := 0
|
|
seen := make(map[int]struct{})
|
|
valid := true
|
|
part.ForEachCell(func(cell *Cell) {
|
|
cellCount++
|
|
if cell == nil || cell.number <= 0 {
|
|
valid = false
|
|
return
|
|
}
|
|
if _, exists := seen[cell.number]; exists {
|
|
valid = false
|
|
}
|
|
seen[cell.number] = struct{}{}
|
|
})
|
|
return valid && cellCount > 0 && len(seen) == cellCount
|
|
}
|
|
|
|
func removeNoteFromPart(part Part, f *Field, note int, trigger string, marks []Mark) error {
|
|
var firstErr error
|
|
part.ForEachCell(func(cell *Cell) {
|
|
if firstErr != nil || !cell.notes.Has(note) {
|
|
return
|
|
}
|
|
firstErr = cell.notes.Remove(f, cell, note, trigger, marks)
|
|
})
|
|
return firstErr
|
|
}
|
|
|
|
func partString(part Part, name string) string {
|
|
if part == nil {
|
|
return name + ":"
|
|
}
|
|
var result strings.Builder
|
|
fmt.Fprintf(&result, "%s:\t", name)
|
|
part.ForEachCell(func(cell *Cell) {
|
|
fmt.Fprintf(&result, "%d ", cell.GetNumber())
|
|
})
|
|
return strings.TrimSuffix(result.String(), " ")
|
|
}
|
|
|
|
func getMissingNumbers(max int, iterate func(func(*Cell))) []int {
|
|
present := make(map[int]struct{}, max)
|
|
iterate(func(cell *Cell) {
|
|
if cell != nil && cell.number != 0 {
|
|
present[cell.number] = struct{}{}
|
|
}
|
|
})
|
|
missing := make([]int, 0, max-len(present))
|
|
for number := 1; number <= max; number++ {
|
|
if _, exists := present[number]; !exists {
|
|
missing = append(missing, number)
|
|
}
|
|
}
|
|
return missing
|
|
}
|