Example #1
0
def puzzle1() -> None:
    # INFO = "123456789123456789123456789123456789123456789123456789123456789123456789123456789"
    puzzle = "...6.1.....4...2...1.....6.1.......2....8....6.......4.7.....9...1...4.....1.2..3"
    texts = [(3, 1, 8), *[(x, 9) for x in range(1, 9)]]
    features: List[Feature] = [
        ContainsTextFeature(i, text) for i, text in enumerate(texts, start=1)
    ]
    sudoku = Sudoku()
    sudoku.solve(puzzle, features=features)
Example #2
0
def you_tuber() -> None:
    puzzle = '...........12986...3.....7..8.....2..1.....6..7.....4..9.....8...54823..........'
    features = [
        LimitedKnightsMove(), *[
            SameValueAsExactlyOneMateFeature((row, column))
            for row in (4, 5, 6) for column in (4, 5, 6)
        ]
    ]
    sudoku = Sudoku()
    sudoku.solve(puzzle, features=features)
Example #3
0
def magic_squares() -> None:
    puzzle = ('.' * 17) + "1" + ('.' * 54) + '.6.......'
    features = [
        MagicSquareFeature((2, 6)),
        MagicSquareFeature((4, 2)),
        MagicSquareFeature((6, 8)),
        MagicSquareFeature((8, 4)),
    ]
    sudoku = Sudoku()
    sudoku.solve(puzzle, features=features)
Example #4
0
def puzzle5() -> None:
    previo = '..7......3.....5......................3..8............15.............9....9......'
    puzzle = '......3...1...............72.........................2..................8........'
    diadem = SnakeFeature([(4, 2), (2, 1), (3, 3), (1, 4), (3, 5), (1, 6),
                           (3, 7), (2, 9), (4, 8)])
    thermometers = [
        ThermometerFeature(name, [(row, column) for row in (9, 8, 7, 6, 5, 4)])
        for column in (2, 4, 6, 8) for name in [f'Thermometer #{column // 2}']
    ]
    features = [diadem, *thermometers]
    sudoku = Sudoku()
    sudoku.solve(merge(puzzle, previo), features=features)
Example #5
0
def puzzle4() -> None:
    previo = '.......................1....4..................5...1.............................'
    puzzle = '...............5.....6.....' + (54 * '.')
    puzzle = merge(puzzle, previo)
    info1 = ((1, 2), (2, 2), (3, 3), (4, 4), (5, 4), (6, 4), (7, 4), (8, 4),
             (8, 3))
    info2 = tuple((row, 10 - column) for (row, column) in info1)
    sudoku = Sudoku()
    sudoku.solve(puzzle,
                 features=[
                     GermanSnakeFeature("Left", info1),
                     GermanSnakeFeature("Right", info2),
                     KnightsMoveFeature()
                 ])
Example #6
0
def puzzle3() -> None:
    previo = '....4........6.............8.....9..........6.........2..........................'
    puzzle = '..9...7...5.....3.7.4.....9.............5.............5.....8.1.3.....9...7...5..'

    evens = [(2, 3), (3, 2), (3, 3), (1, 4), (1, 5), (1, 6)]
    evens = evens + [(column, 10 - row) for row, column in evens]
    evens = evens + [(10 - row, 10 - column) for row, column in evens]

    features = [
        SnakeFeature([(3, 6), (3, 5), (4, 4), (5, 4), (5, 5), (5, 6), (6, 6),
                      (7, 5), (7, 4)]),
        LimitedValuesFeature(evens, (2, 4, 6, 8)),
    ]
    sudoku = Sudoku()
    sudoku.solve(merge(puzzle, previo), features=features)
Example #7
0
def double_sum_puzzle(*, show: bool = False) -> None:
    class CheckSpecialFeature(Feature):
        cells: Sequence[Cell]

        def initialize(self, grid: Grid) -> None:
            self.cells = [grid.matrix[1, 6], grid.matrix[2, 6]]

        def check_special(self) -> bool:
            if len(self.cells[0].possible_values) == 4:
                print("Danger.  Danger")
                Cell.keep_values_for_cell(self.cells, {3, 7})
                return True
            return False

    features = [
        DoubleSumFeature(House.Type.ROW, 1, 6),
        DoubleSumFeature(House.Type.ROW, 4, 10, 10),
        DoubleSumFeature(House.Type.ROW, 5, 10, 9),
        DoubleSumFeature(House.Type.ROW, 6, 10, 10),
        DoubleSumFeature(House.Type.ROW, 7, 10, 10),
        DoubleSumFeature(House.Type.ROW, 9, 9, 11),
        DoubleSumFeature(House.Type.COLUMN, 1, 16),
        DoubleSumFeature(House.Type.COLUMN, 3, 13, 13),
        DoubleSumFeature(House.Type.COLUMN, 4, 12, 11),
        DoubleSumFeature(House.Type.COLUMN, 5, 9),
        DoubleSumFeature(House.Type.COLUMN, 6, 10, 10),
        DoubleSumFeature(House.Type.COLUMN, 7, 11, 15),
        DoubleSumFeature(House.Type.COLUMN, 8, 11, 9),
        CheckSpecialFeature(),
    ]
    Sudoku().solve('.' * 81, features=features, show=show)
Example #8
0
def puzzle_08_26(*, show: bool = False) -> None:
    class PrimeRing(AdjacentRelationshipFeature):
        def __init__(self, squares):
            super().__init__("Prime", squares, cyclic=True, color='red')

        def match(self, digit1: int, digit2: int) -> bool:
            return digit1 + digit2 in {2, 3, 5, 7, 11, 13, 17}

    columns = (21, 25, 11, 0, 35, 23, 13, 4, 18)
    rows = (13, 13, 6, 9, 0, 29, 2, 13, 2)
    thermo = "3,7,S,S,S,S"
    wave = "2,2,E,E,S,E,E,N,E,E,S,S,W,S,S,E,S,S,W,W,N,W,W,S,W,W,N,N,E,N,N,W,N"
    features = [
        ThermometerFeature(f'Thermo', Feature.parse_line(thermo)),
        PrimeRing(Feature.parse_line(wave)),
        *[
            SandwichFeature(House.Type.ROW, row, total)
            for row, total in enumerate(rows, start=1)
        ],
        *[
            SandwichFeature(House.Type.COLUMN, col, total)
            for col, total in enumerate(columns, start=1)
        ],
    ]
    Sudoku().solve(' ' * 81, features=features, show=show)
Example #9
0
def puzzle_09_21(*, show: bool = False) -> None:
    class Multiplication(PossibilitiesFeature):
        def __init__(self, row, column):
            squares = [(row, column), (row, column + 1), (row + 1, column),
                       (row + 1, column + 1)]
            super().__init__(f"Square{row}{column}", squares, neighbors=True)

        def get_possibilities(self) -> List[Tuple[Set[int], ...]]:
            for x, y in itertools.product(range(1, 10), repeat=2):
                if x <= y:
                    z = x * y
                    if z >= 11 and z % 10 != 0:
                        yield ({x, y}, {x, y}, {z // 10}, {z % 10})

        def draw(self, context: dict) -> None:
            self.draw_rectangles(self.squares, color='lightgray')

    puzzle = "X..7-6...5.-.8.-..9-X-5..-.6.-.9...1-2..X".replace(
        "X", "---").replace("-", "...")
    features = [
        Multiplication(1, 1),
        Multiplication(1, 8),
        Multiplication(3, 3),
        Multiplication(6, 6),
        Multiplication(8, 1),
        Multiplication(8, 8)
    ]
    Sudoku().solve(puzzle, features=features, show=show)
Example #10
0
def tour_puzzle_one(*, show: bool = False) -> None:
    features = [
        *(LocateOneFeature(House.Type.ROW, i) for i in range(2, 9)),
        *(LocateOneFeature(House.Type.COLUMN, i) for i in range(2, 9)),
        DrawCircleFeature(((1,2), (1,4), (2, 9), (4, 8), (5, 2), (5, 3), (5, 7), (5, 8),
                           (6, 2), (8, 1), (9, 6,), (9, 8)))
    ]
    puzzle = '.......................9.......5.3.....4.37....3.8.......36...........5..........'
    Sudoku().solve(puzzle, features=features, show=show)
Example #11
0
def puzzle_08_15(*, show: bool = False) -> None:
    puzzle = "....1...4........5.............................1.....8........75.3....6.....3...."
    odds = [(3, 2), (3, 4), (3, 6), (3, 7), (3, 8), (4, 1), (4, 2), (4, 4),
            (4, 8), (5, 2), (5, 4), (5, 5), (5, 6), (5, 8), (6, 2), (6, 5),
            (6, 8), (7, 2), (7, 5), (7, 8)]
    features = [
        KingsMoveFeature(),
        LimitedValuesFeature(odds, (1, 3, 5, 7, 9), color='lightgray')
    ]
    Sudoku().solve(puzzle, features=features, show=show)
Example #12
0
def puzzle_09_20(*, show: bool = False) -> None:
    puzzle = "XXXXX-1..-XXX".replace("X", "---").replace("-", "...")
    features = [
        *SandwichFeature.all(House.Type.ROW,
                             [10, 19, 25, 28, 17, 3, 23, 6, 7]),
        *SandwichFeature.all(House.Type.COLUMN,
                             [18, 8, 21, 18, 13, 27, 25, 13, 3]),
        KnightsMoveFeature()
    ]
    Sudoku().solve(puzzle, features=features, show=show)
Example #13
0
def tour_puzzle_two(*, show: bool = False) -> None:
    previous = ".4.8.............7................9..85...76..7................4.............4.7."
    puzzle = "X,,6--XXXXX--6...5.--".replace('X', '---').replace('-', '...')
    puzzle = merge(previous, puzzle)
    # foobar = "XX..1--XXXX.....1...X".replace('X', '---').replace('-', '...')
    # print(len(foobar))
    # puzzle = merge(foobar, puzzle)
    features = [PainInTheButtFeature(i) for i in range(1, 4)]
    features.append(PainInTheButtFeatureX())
    Sudoku().solve(puzzle, features=features, show=show)
Example #14
0
def puzzle_09_15(*, show: bool = False) -> None:
    puzzle = "-----.5.3.8.2.2.5.3.6.9.9.4.6.1.-".replace('-', '.........')
    features = XVFeature.setup(
        down_x=[(1, 3), (1, 5), (1, 7), (2, 2), (2,
                                                 4), (2,
                                                      6), (2,
                                                           8), (3,
                                                                3), (3,
                                                                     5), (3,
                                                                          7)])
    Sudoku().solve(puzzle, features=features, show=show)
Example #15
0
def thermometer_07_23() -> None:
    puzzle = ".....................9.............5...............3.................8.......9..."
    thermos = [
        "1,1,SE,SE,SE,SW,SW", "1,9,SW,SW,SW,NW,NW", "9,1,NE,NE,NE,SE,SE",
        "9,9,NW,NW,NW,NE,NE"
    ]
    thermometers = [
        ThermometerFeature(f'Thermo#{i}',
                           Feature.parse_line(line),
                           color='lightgray') for i, line in enumerate(thermos)
    ]
    Sudoku().solve(puzzle, features=thermometers)
Example #16
0
def puzzle_07_30(*, show: bool = False) -> None:
    features = [
        SandwichXboxFeature(House.Type.ROW, 3, 16),
        SandwichXboxFeature(House.Type.ROW, 4, 10, right=True),
        SandwichXboxFeature(House.Type.COLUMN, 3, 30),
        SandwichXboxFeature(House.Type.COLUMN, 4, 3),
        SandwichXboxFeature(House.Type.COLUMN, 7, 17),
        KingsMoveFeature(),
        QueensMoveFeature(),
    ]
    puzzle = "." * 63 + '.5.......' + '.' * 9
    Sudoku().solve(puzzle, features=features, show=show)
Example #17
0
def puzzle_alice(*, show: bool = False) -> None:
    # puzzle = "......... 3......8. ..4...... ......... 2...9...7 ......... ......5.. .1......6 ........."
    puzzle = "......... 3....6.8. ..4...... ......... 2...9...7 ......... ......5.. .1......6 ........."  # 18:30

    pieces = "122222939112122333911123333441153666445555696497758966447958886447559886777778889"
    features = [
        AlternativeBoxesFeature(pieces), *(SameValueAsMateFeature((r, c))
                                           for r in range(1, 10)
                                           for c in range(1, 10))
    ]
    puzzle = puzzle.replace(' ', '')
    Sudoku().solve(puzzle, features=features, show=show)
Example #18
0
def puzzle_08_12(*, show: bool = False) -> None:
    thermos = [
        "5,5,nw,nw,n,ne", "5,5,nw,nw,sw,sw,s", "5,5,ne,ne,n,nw",
        "5,5,ne,ne,se,se,s", "5,5,s,s,sw,w,nw", "5,5,s,s,se,e,ne", "1,1,e",
        "1,1,s", "2,9,n,w", "8,1,s,e", "9,9,w", "9,9,n", "4,3,ne",
        "3,5,e,se,s", "6,7,sw,w,w", "6,3,n"
    ]
    thermometers = [
        ThermometerFeature(f'Thermo#{i}', Feature.parse_line(line))
        for i, line in enumerate(thermos, start=1)
    ]
    puzzle = "." * 63 + "....8...." + "." * 9
    Sudoku().solve(puzzle, features=thermometers, show=show)
Example #19
0
def puzzle7() -> None:
    puzzles = [
        '925631847364578219718429365153964782249387156687215934472853691531796428896142573',  # Diary, Red
        '398541672517263894642987513865372941123894756974156238289435167456718329731629485',  # Ring, Purple
        '369248715152769438784531269843617952291854376675392184526973841438125697917486523',  # Locket, Orangeish
        '817325496396487521524691783741952638963148257285763149158279364632814975479536812',  # Cup, Yellow
        '527961384318742596694853217285619473473528169961437852152396748746285931839174625',  # Crown, Blue
        '196842753275361489384759126963125847548937261721684935612578394837496512459213678',  # Snake, Green
    ]
    pluses = [(1, 1), (1, 9), (2, 4), (2, 6), (3, 3), (3, 7), (4, 2), (4, 4),
              (4, 6), (4, 8), (5, 3)]
    pluses = pluses + [(10 - row, 10 - column) for row, column in pluses]
    puzzle = '......................7..1.....8.................6.....3..5......................'
    Sudoku().solve(puzzle, features=[(PlusFeature(pluses, puzzles))])
Example #20
0
def puzzle_08_31(*, show: bool = False) -> None:
    thermos = [
        "1,5,SW,SW,E,S", "1,8,W,W", "3,8,SW,S,SE,E", "7,3,NW,NW", "9,1,E,E",
        "9,8,NW,SW,NW,N,N,N,N"
    ]
    thermometers = [
        ThermometerFeature(f'Thermo#{i}', Feature.parse_line(line))
        for i, line in enumerate(thermos, start=1)
    ]
    snake_squares = [thermometer.squares[0] for thermometer in thermometers]
    snake_squares.extend(((2, 2), (4, 1), (7, 2)))
    snake = SnakeFeature(snake_squares, line=False)
    puzzle = ".....8....................9.................6.....4.................6.......7.9.."
    Sudoku().solve(puzzle, features=[*thermometers, snake], show=show)
Example #21
0
def puzzle_08_02(*, show: bool = False) -> None:
    thermos = [
        "2,1,SE,SE,NE,N,NE", "2,9,SW,SW,NW,N,NW", "6,1,N,N,N", "3,9,S,S,S",
        "8,1,E,E", "8,7,E,E", "5,6,SW,NW"
    ]
    features = [
        MagicSquareFeature(),
        KingsMoveFeature(),
        *[
            ThermometerFeature(f'Thermo#{i}', Feature.parse_line(line))
            for i, line in enumerate(thermos, start=1)
        ],
    ]
    Sudoku().solve('.' * 81, features=features, show=show)
Example #22
0
def puzzle_07_30_Simon(*, show: bool = False) -> None:
    thermos = [
        "2,1,NE,S,NE,S,NE", "2,4,NE,S,NE,S,NE", "2,7,NE,S", "4,3,W,S,E,E,N",
        "4,7,W,S,E,E,N", "7,5,E,N,NW", "8,3,S,E,E,E", "9,8,N,E,N"
    ]
    thermometers = [
        ThermometerFeature(f'Thermo#{i}',
                           Feature.parse_line(line),
                           color='lightblue')
        for i, line in enumerate(thermos, start=1)
    ]
    nada = "........."
    puzzle = nada + "......3.." + nada * 6 + ".......3."
    Sudoku().solve(puzzle, features=thermometers, show=show)
Example #23
0
def thermo_magic() -> None:
    thermometers = [[(6 - r, 1) for r in range(1, 6)],
                    [(6 - r, r) for r in range(1, 6)],
                    [(1, 10 - r) for r in range(1, 6)],
                    [(10 - r, 9) for r in range(1, 6)],
                    [(10 - r, 4 + r) for r in range(1, 6)],
                    [(9, 6 - r) for r in range(1, 6)]]
    features = [
        MagicSquareFeature(dr=4, dc=4, color='lightblue'), *[
            ThermometerFeature(f'Thermo #{count}', squares)
            for count, squares in enumerate(thermometers, start=1)
        ]
    ]
    puzzle = ("." * 18) + '.....2...' + ('.' * 27) + '...8.....' + ('.' * 18)
    Sudoku().solve(puzzle, features=features)
Example #24
0
def slow_thermometer_puzzle2() -> None:
    puzzle = '.' * 72 + ".....1..."
    thermos = [
        "2,4,N,W,S,S,E,SE", "2,7,N,W,S", "4,6,N,NW", "4,7,N,SE,SE",
        "4,2,SW,E,SW,E,SW,E,SW,E,SW", "5,4,SE,E", "6,4,E,E", "7,3,S,S",
        "9,5,NW,S", "9,6,N", "9,6,NW", "6,7,E,SW,W,W,W,NW",
        "6,9,W,SW,W,W,W,NW", "8,8,NW", "8,8,W,SE,W"
    ]
    thermometers = [
        SlowThermometerFeature(f'Thermo#{i}',
                               Feature.parse_line(line),
                               color='lightblue')
        for i, line in enumerate(thermos)
    ]
    Sudoku().solve(puzzle, features=thermometers)
Example #25
0
def puzzle_08_06(*, show: bool = False) -> None:
    OFFSETS1 = [(dr, dc) for dx in (-1, 1) for dy in (-2, 2)
                for (dr, dc) in ((dx, dy), (dy, dx))]
    OFFSETS2 = [(dr, dc) for delta in range(1, 9) for dr in (-delta, delta)
                for dc in (-delta, delta)]
    OFFSETS = OFFSETS1 + OFFSETS2

    class MyFeature(SameValueAsMateFeature):
        def get_mates(self, cell: Cell, grid: Grid) -> Iterable[Cell]:
            return self.neighbors_from_offsets(grid, cell, OFFSETS)

    features = [
        MyFeature((i, j)) for i, j in itertools.product(range(1, 10), repeat=2)
    ]
    puzzle = "39.1...822.....5.....4.....6..2.....1....4.........3............6...3..551.....64"
    Sudoku().solve(puzzle, features=features, show=show)
Example #26
0
def sandwich_07_28(*, show: bool = False) -> None:
    class LiarsSandwichFeature(SandwichFeature):
        def get_possibilities(self) -> Iterable[Tuple[Set[int], ...]]:
            yield from self._get_possibilities(self.total - 1)
            yield from self._get_possibilities(self.total + 1)

    puzzle = "..6................1...........1.....4.........9...2.....................7......8"
    features = [
        *[
            LiarsSandwichFeature(House.Type.ROW, row, total)
            for row, total in enumerate((5, 8, 5, 16, 12, 7, 5, 3, 1), start=1)
        ],
        LiarsSandwichFeature(House.Type.COLUMN, 1, 5),
        LiarsSandwichFeature(House.Type.COLUMN, 5, 4),
        LiarsSandwichFeature(House.Type.COLUMN, 9, 5),
    ]
    Sudoku().solve(puzzle, features=features, show=show)
Example #27
0
def puzzle_09_05(*, show: bool = False) -> None:
    class DeltaFeature(AdjacentRelationshipFeature):
        delta: int

        def __init__(self, square: Square, delta: int, is_right: bool):
            row, column = square
            square2 = (row, column + 1) if is_right else (row + 1, column)
            self.delta = delta
            super().__init__("d", [square, square2])

        def draw(self, context: dict) -> None:
            (r1, c1), (r2, c2) = self.squares
            plt.text((c1 + c2 + 1) / 2, (r1 + r2 + 1) / 2,
                     str(self.delta),
                     verticalalignment='center',
                     horizontalalignment='center',
                     fontsize=15,
                     weight='bold',
                     color='red')

        def match(self, digit1: int, digit2: int) -> bool:
            return abs(digit1 - digit2) == self.delta

    features = [
        DeltaFeature((2, 3), 1, True),
        DeltaFeature((2, 6), 1, True),
        DeltaFeature((3, 3), 7, True),
        DeltaFeature((3, 6), 7, True),
        DeltaFeature((4, 3), 5, True),
        DeltaFeature((4, 6), 5, True),
        DeltaFeature((6, 2), 1, True),
        DeltaFeature((6, 7), 2, True),
        DeltaFeature((1, 5), 8, False),
        DeltaFeature((2, 1), 4, False),
        DeltaFeature((2, 9), 4, False),
        DeltaFeature((3, 2), 6, False),
        DeltaFeature((3, 8), 6, False),
        DeltaFeature((4, 5), 3, False),
        DeltaFeature((7, 1), 1, False),
        DeltaFeature((7, 9), 2, False),
        DeltaFeature((8, 2), 1, False),
        DeltaFeature((8, 8), 2, False),
    ]
    puzzle = "XXXXXX.921.738.-3.9-X".replace("X", "---").replace("-", "...")
    Sudoku().solve(puzzle, features=features, show=show)
Example #28
0
def puzzle_09_06(*, show: bool = False) -> None:
    class CamelJumpFeature(SameValueAsMateFeature):
        OFFSETS = [(dr, dc) for dx in (-1, 1) for dy in (-3, 3)
                   for (dr, dc) in ((dx, dy), (dy, dx))]

        def get_mates(self, cell: Cell, grid: Grid) -> Iterable[Cell]:
            return self.neighbors_from_offsets(grid, cell, self.OFFSETS)

        def draw(self, context: dict) -> None:
            if self.done:
                self.draw_outline([self.this_square], linestyle="-")

    features = [
        CamelJumpFeature(square)
        for square in itertools.product(range(1, 10), repeat=2)
    ]
    puzzle = "........9.....85..7...2.1..35...............6.96.....7...........1.7.9......452.."
    Sudoku().solve(puzzle, features=features, show=show)
Example #29
0
def puzzle8() -> None:
    puzzles = [
        '925631847364578219718429365153964782249387156687215934472853691531796428896142573',  # Diary, Red
        '398541672517263894642987513865372941123894756974156238289435167456718329731629485',  # Ring, Purple
        '369248715152769438784531269843617952291854376675392184526973841438125697917486523',  # Locket, Orangeish
        '817325496396487521524691783741952638963148257285763149158279364632814975479536812',  # Cup, Yellow
        '527961384318742596694853217285619473473528169961437852152396748746285931839174625',  # Crown, Blue
        '196842753275361489384759126963125847548937261721684935612578394837496512459213678',  # Snake, Green
        '213845679976123854548976213361782945859314762724569381632458197185297436497631528',  # Enigma, Gray
    ]

    grid = '+.y..o+.+...Gb.......p...r.+..+b.b+...........+g.g+..+.o...........ry...+.+g..g.+'
    features = [
        ColorFeature(grid, 'rpoybgG', puzzles),
        SnakeFeature.major_diagonal(),
        SnakeFeature.minor_diagonal(),
    ]
    Sudoku().solve('.' * 81, features=features)
Example #30
0
def slow_thermometer_puzzle1() -> None:
    puzzle = '.' * 81
    thermos = [[(4, 5), (5, 5), (6, 6), (5, 6), (4, 6), (3, 7), (2, 7), (1, 6),
                (1, 5), (1, 4), (2, 3), (3, 3), (4, 4)],
               [(4, 5), (5, 5), (6, 6), (5, 7), (4, 7), (3, 8), (2, 8)],
               [(2, 2), (2, 1), (1, 1), (1, 2)],
               [(1, 7), (1, 8), (2, 9), (3, 9), (4, 8), (5, 8)],
               [(6, 4), (5, 4), (4, 3), (3, 2)],
               [(5, 3), (4, 2), (4, 1), (5, 2), (5, 1), (6, 1)],
               [(6, 8), (6, 9), (5, 9), (4, 9)],
               [(8, 4), (9, 3), (8, 2), (8, 3), (7, 4), (6, 3), (7, 3),
                (7, 2)],
               [(7, 6), (7, 7), (7, 8), (7, 9), (8, 8), (9, 8), (9, 7), (8, 6),
                (7, 5)]]
    thermometers = [
        SlowThermometerFeature(f'Thermo#{i}', thermo)
        for i, thermo in enumerate(thermos, start=1)
    ]
    Sudoku().solve(puzzle, features=thermometers)