Source code for dedoku.techniques.bug

"""Bivalue Universal Grave (BUG) technique, in its BUG+1 form.

A *bivalue universal grave* is a board state where every unsolved cell has
exactly two candidates and every candidate appears exactly twice in each
house. Such a state can never belong to a puzzle with a unique solution:
the remaining digits could be assigned in two complementary ways.

The practical *BUG+1* deduction: when every unsolved cell is bivalue
except a single cell with three candidates, that cell must take the one
candidate appearing three times in its row, column, and subgrid — placing
any other value would leave the board as a deadly BUG. Like the other
uniqueness arguments, this assumes the puzzle has exactly one solution.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from .base import Placement, Step, Technique

if TYPE_CHECKING:
    from ..grid import Grid

__all__ = ["BivalueUniversalGrave"]


[docs] class BivalueUniversalGrave(Technique): """Resolve a BUG+1 state by solving its only trivalue cell.""" name = "BUG" requires_unique_solution = True
[docs] def apply(self, grid: Grid) -> Step | None: """Detect a BUG+1 state and place the BUG-breaking digit. :param grid: The board to inspect and mutate. :type grid: Grid :returns: The placement performed, or ``None`` if the board is not in a BUG+1 state. :rtype: Step | None """ trivalue = None for cell in grid.cells: if cell.is_solved: continue size = len(cell.candidates) if size == 2: continue if size == 3 and trivalue is None: trivalue = cell continue return None if trivalue is None: return None for digit in sorted(trivalue.candidates): counts = [ len(unit.cells_with_candidate(digit)) for unit in (trivalue.row, trivalue.column, trivalue.subgrid) ] if all(count == 3 for count in counts): trivalue.set_value(digit) return Step( technique=self.name, description=( f"BUG+1: every unsolved cell is bivalue except " f"{trivalue.label}; only digit {digit} appears " f"three times in its houses, so it must go there " f"to avoid a deadly bivalue universal grave" ), placements=( Placement(trivalue.row_index, trivalue.column_index, digit), ), ) return None