@@ -116,6 +116,46 @@ def test_naked_pair_eliminates_other_candidates() -> None:
116116 assert move ["remove" ] not in cand [0 ][2 ]
117117
118118
119+ def test_naked_pair_column_eliminates_other_candidates () -> None :
120+ grid = _empty_grid ()
121+ cand = candidates (grid )
122+ _clear_digit (cand , 4 )
123+ _clear_digit (cand , 5 )
124+ _clear_digit (cand , 6 )
125+ _set_candidates (cand , 0 , 3 , {4 , 5 })
126+ _set_candidates (cand , 1 , 3 , {4 , 5 })
127+ _set_candidates (cand , 4 , 3 , {4 , 5 , 6 })
128+
129+ move = apply_naked_pair (grid , cand )
130+
131+ assert move is not None
132+ assert move ["strategy" ] == "naked_pair"
133+ assert move ["unit" ] == "col" and move ["unit_index" ] == 3
134+ assert move ["r" ] == 4 and move ["c" ] == 3
135+ assert move ["remove" ] in (4 , 5 )
136+ assert move ["remove" ] not in cand [4 ][3 ]
137+
138+
139+ def test_naked_pair_box_eliminates_other_candidates () -> None :
140+ grid = _empty_grid ()
141+ cand = candidates (grid )
142+ _clear_digit (cand , 7 )
143+ _clear_digit (cand , 8 )
144+ _clear_digit (cand , 9 )
145+ _set_candidates (cand , 0 , 0 , {7 , 8 })
146+ _set_candidates (cand , 1 , 1 , {7 , 8 })
147+ _set_candidates (cand , 2 , 2 , {7 , 8 , 9 })
148+
149+ move = apply_naked_pair (grid , cand )
150+
151+ assert move is not None
152+ assert move ["strategy" ] == "naked_pair"
153+ assert move ["unit" ] == "box" and move ["unit_index" ] == 0
154+ assert move ["r" ] == 2 and move ["c" ] == 2
155+ assert move ["remove" ] in (7 , 8 )
156+ assert move ["remove" ] not in cand [2 ][2 ]
157+
158+
119159def test_hidden_pair_prunes_extras () -> None :
120160 grid = _empty_grid ()
121161 cand = candidates (grid )
@@ -146,6 +186,82 @@ def test_hidden_pair_prunes_extras() -> None:
146186 assert cand [second_target [0 ]][second_target [1 ]] == {digit_a , digit_b }
147187
148188
189+ def test_hidden_pair_column_prunes_extras () -> None :
190+ grid = _empty_grid ()
191+ cand = candidates (grid )
192+ digit_a , digit_b , extra = 4 , 5 , 6
193+ for d in (digit_a , digit_b , extra ):
194+ _clear_digit (cand , d )
195+ _set_candidates (cand , 0 , 2 , {digit_a , digit_b , extra })
196+ _set_candidates (cand , 4 , 2 , {digit_a , digit_b , extra })
197+
198+ move = apply_hidden_pair (grid , cand )
199+
200+ assert move is not None
201+ assert move ["strategy" ] == "hidden_pair"
202+ assert move ["unit" ] == "col" and move ["unit_index" ] == 2
203+ first_target = (move ["r" ], move ["c" ])
204+ assert cand [first_target [0 ]][first_target [1 ]] == {digit_a , digit_b }
205+
206+ move_second = apply_hidden_pair (grid , cand )
207+
208+ assert move_second is not None
209+ assert move_second ["strategy" ] == "hidden_pair"
210+ assert (move_second ["r" ], move_second ["c" ]) != first_target
211+ assert cand [move_second ["r" ]][move_second ["c" ]] == {digit_a , digit_b }
212+
213+
214+ def test_hidden_pair_box_prunes_extras () -> None :
215+ grid = _empty_grid ()
216+ cand = candidates (grid )
217+ digit_a , digit_b , extra = 1 , 2 , 3
218+ for d in (digit_a , digit_b , extra ):
219+ _clear_digit (cand , d )
220+ _set_candidates (cand , 3 , 3 , {digit_a , digit_b , extra })
221+ _set_candidates (cand , 4 , 4 , {digit_a , digit_b , extra })
222+
223+ move = apply_hidden_pair (grid , cand )
224+
225+ assert move is not None
226+ assert move ["strategy" ] == "hidden_pair"
227+ assert move ["unit" ] == "box" and move ["unit_index" ] == 4
228+ first_target = (move ["r" ], move ["c" ])
229+ assert cand [first_target [0 ]][first_target [1 ]] == {digit_a , digit_b }
230+
231+ move_second = apply_hidden_pair (grid , cand )
232+
233+ assert move_second is not None
234+ assert move_second ["strategy" ] == "hidden_pair"
235+ assert (move_second ["r" ], move_second ["c" ]) != first_target
236+ assert cand [move_second ["r" ]][move_second ["c" ]] == {digit_a , digit_b }
237+
238+
239+ def test_hidden_pair_single_occurrence_path () -> None :
240+ grid = _empty_grid ()
241+ cand = candidates (grid )
242+ digit_a , digit_b , extra_a , extra_b = 4 , 6 , 7 , 8
243+ for d in (digit_a , digit_b , extra_a , extra_b ):
244+ _clear_digit (cand , d )
245+ _set_candidates (cand , 0 , 5 , {digit_a , digit_b , extra_a })
246+ _set_candidates (cand , 0 , 6 , {digit_a , extra_b })
247+
248+ move = apply_hidden_pair (grid , cand )
249+
250+ assert move is not None
251+ assert move ["strategy" ] == "hidden_pair"
252+ assert move ["unit" ] == "row" and move ["unit_index" ] == 0
253+ target = (move ["r" ], move ["c" ])
254+ assert target in ((0 , 5 ), (0 , 6 ))
255+ assert cand [target [0 ]][target [1 ]] <= {digit_a , digit_b }
256+
257+ move_second = apply_hidden_pair (grid , cand )
258+
259+ assert move_second is not None
260+ assert move_second ["strategy" ] == "hidden_pair"
261+ assert (move_second ["r" ], move_second ["c" ]) != target
262+ assert cand [move_second ["r" ]][move_second ["c" ]] <= {digit_a , digit_b }
263+
264+
149265def test_naked_triple_clears_unit () -> None :
150266 grid = _empty_grid ()
151267 cand = candidates (grid )
0 commit comments