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 }