@@ -214,6 +214,46 @@ def test_naked_pair_eliminates_other_candidates() -> None:
214214 assert move ["remove" ] not in cand [0 ][2 ]
215215
216216
217+ def test_naked_pair_column_eliminates_other_candidates () -> None :
218+ grid = _empty_grid ()
219+ cand = candidates (grid )
220+ _clear_digit (cand , 4 )
221+ _clear_digit (cand , 5 )
222+ _clear_digit (cand , 6 )
223+ _set_candidates (cand , 0 , 3 , {4 , 5 })
224+ _set_candidates (cand , 1 , 3 , {4 , 5 })
225+ _set_candidates (cand , 4 , 3 , {4 , 5 , 6 })
226+
227+ move = apply_naked_pair (grid , cand )
228+
229+ assert move is not None
230+ assert move ["strategy" ] == "naked_pair"
231+ assert move ["unit" ] == "col" and move ["unit_index" ] == 3
232+ assert move ["r" ] == 4 and move ["c" ] == 3
233+ assert move ["remove" ] in (4 , 5 )
234+ assert move ["remove" ] not in cand [4 ][3 ]
235+
236+
237+ def test_naked_pair_box_eliminates_other_candidates () -> None :
238+ grid = _empty_grid ()
239+ cand = candidates (grid )
240+ _clear_digit (cand , 7 )
241+ _clear_digit (cand , 8 )
242+ _clear_digit (cand , 9 )
243+ _set_candidates (cand , 0 , 0 , {7 , 8 })
244+ _set_candidates (cand , 1 , 1 , {7 , 8 })
245+ _set_candidates (cand , 2 , 2 , {7 , 8 , 9 })
246+
247+ move = apply_naked_pair (grid , cand )
248+
249+ assert move is not None
250+ assert move ["strategy" ] == "naked_pair"
251+ assert move ["unit" ] == "box" and move ["unit_index" ] == 0
252+ assert move ["r" ] == 2 and move ["c" ] == 2
253+ assert move ["remove" ] in (7 , 8 )
254+ assert move ["remove" ] not in cand [2 ][2 ]
255+
256+
217257def test_hidden_pair_prunes_extras () -> None :
218258 grid = _empty_grid ()
219259 cand = candidates (grid )
@@ -244,6 +284,82 @@ def test_hidden_pair_prunes_extras() -> None:
244284 assert cand [second_target [0 ]][second_target [1 ]] == {digit_a , digit_b }
245285
246286
287+ def test_hidden_pair_column_prunes_extras () -> None :
288+ grid = _empty_grid ()
289+ cand = candidates (grid )
290+ digit_a , digit_b , extra = 4 , 5 , 6
291+ for d in (digit_a , digit_b , extra ):
292+ _clear_digit (cand , d )
293+ _set_candidates (cand , 0 , 2 , {digit_a , digit_b , extra })
294+ _set_candidates (cand , 4 , 2 , {digit_a , digit_b , extra })
295+
296+ move = apply_hidden_pair (grid , cand )
297+
298+ assert move is not None
299+ assert move ["strategy" ] == "hidden_pair"
300+ assert move ["unit" ] == "col" and move ["unit_index" ] == 2
301+ first_target = (move ["r" ], move ["c" ])
302+ assert cand [first_target [0 ]][first_target [1 ]] == {digit_a , digit_b }
303+
304+ move_second = apply_hidden_pair (grid , cand )
305+
306+ assert move_second is not None
307+ assert move_second ["strategy" ] == "hidden_pair"
308+ assert (move_second ["r" ], move_second ["c" ]) != first_target
309+ assert cand [move_second ["r" ]][move_second ["c" ]] == {digit_a , digit_b }
310+
311+
312+ def test_hidden_pair_box_prunes_extras () -> None :
313+ grid = _empty_grid ()
314+ cand = candidates (grid )
315+ digit_a , digit_b , extra = 1 , 2 , 3
316+ for d in (digit_a , digit_b , extra ):
317+ _clear_digit (cand , d )
318+ _set_candidates (cand , 3 , 3 , {digit_a , digit_b , extra })
319+ _set_candidates (cand , 4 , 4 , {digit_a , digit_b , extra })
320+
321+ move = apply_hidden_pair (grid , cand )
322+
323+ assert move is not None
324+ assert move ["strategy" ] == "hidden_pair"
325+ assert move ["unit" ] == "box" and move ["unit_index" ] == 4
326+ first_target = (move ["r" ], move ["c" ])
327+ assert cand [first_target [0 ]][first_target [1 ]] == {digit_a , digit_b }
328+
329+ move_second = apply_hidden_pair (grid , cand )
330+
331+ assert move_second is not None
332+ assert move_second ["strategy" ] == "hidden_pair"
333+ assert (move_second ["r" ], move_second ["c" ]) != first_target
334+ assert cand [move_second ["r" ]][move_second ["c" ]] == {digit_a , digit_b }
335+
336+
337+ def test_hidden_pair_single_occurrence_path () -> None :
338+ grid = _empty_grid ()
339+ cand = candidates (grid )
340+ digit_a , digit_b , extra_a , extra_b = 4 , 6 , 7 , 8
341+ for d in (digit_a , digit_b , extra_a , extra_b ):
342+ _clear_digit (cand , d )
343+ _set_candidates (cand , 0 , 5 , {digit_a , digit_b , extra_a })
344+ _set_candidates (cand , 0 , 6 , {digit_a , extra_b })
345+
346+ move = apply_hidden_pair (grid , cand )
347+
348+ assert move is not None
349+ assert move ["strategy" ] == "hidden_pair"
350+ assert move ["unit" ] == "row" and move ["unit_index" ] == 0
351+ target = (move ["r" ], move ["c" ])
352+ assert target in ((0 , 5 ), (0 , 6 ))
353+ assert cand [target [0 ]][target [1 ]] <= {digit_a , digit_b }
354+
355+ move_second = apply_hidden_pair (grid , cand )
356+
357+ assert move_second is not None
358+ assert move_second ["strategy" ] == "hidden_pair"
359+ assert (move_second ["r" ], move_second ["c" ]) != target
360+ assert cand [move_second ["r" ]][move_second ["c" ]] <= {digit_a , digit_b }
361+
362+
247363def test_naked_triple_clears_unit () -> None :
248364 grid = _empty_grid ()
249365 cand = candidates (grid )
0 commit comments