Skip to content

Commit 0858e9e

Browse files
Merge pull request #39 from SaridakisStamatisChristos/codex/add-missing-tests-in-strategies.py
Add targeted strategy tests to improve coverage
2 parents 149c13a + 910b3b6 commit 0858e9e

File tree

1 file changed

+228
-0
lines changed

1 file changed

+228
-0
lines changed

tests/test_strategies_core.py

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
from __future__ import annotations
2+
3+
from sudoku_dlx.strategies import (
4+
apply_box_line_claiming,
5+
apply_hidden_pair,
6+
apply_hidden_triple,
7+
apply_locked_candidates_pointing,
8+
apply_naked_pair,
9+
apply_naked_triple,
10+
apply_x_wing,
11+
candidates,
12+
)
13+
14+
15+
def _empty_grid() -> list[list[int]]:
16+
return [[0] * 9 for _ in range(9)]
17+
18+
19+
def _clear_digit(cand: list[list[set[int]]], digit: int) -> None:
20+
for row in cand:
21+
for cell in row:
22+
cell.discard(digit)
23+
24+
25+
def _set_candidates(cand: list[list[set[int]]], r: int, c: int, values: set[int]) -> None:
26+
cand[r][c].clear()
27+
cand[r][c].update(values)
28+
29+
30+
def test_locked_candidates_pointing_row() -> None:
31+
grid = _empty_grid()
32+
cand = candidates(grid)
33+
digit = 7
34+
_clear_digit(cand, digit)
35+
for c in range(3):
36+
cand[0][c].add(digit)
37+
cand[0][4].add(digit)
38+
39+
move = apply_locked_candidates_pointing(grid, cand)
40+
41+
assert move is not None
42+
assert move["strategy"] == "locked_pointing_row"
43+
assert move["r"] == 0 and move["c"] == 4
44+
assert digit not in cand[0][4]
45+
46+
47+
def test_locked_candidates_pointing_col() -> None:
48+
grid = _empty_grid()
49+
cand = candidates(grid)
50+
digit = 5
51+
_clear_digit(cand, digit)
52+
for r in range(3):
53+
cand[r][1].add(digit)
54+
cand[4][1].add(digit)
55+
56+
move = apply_locked_candidates_pointing(grid, cand)
57+
58+
assert move is not None
59+
assert move["strategy"] == "locked_pointing_col"
60+
assert move["c"] == 1 and move["r"] == 4
61+
assert digit not in cand[4][1]
62+
63+
64+
def test_box_line_claiming_row() -> None:
65+
grid = _empty_grid()
66+
cand = candidates(grid)
67+
digit = 6
68+
_clear_digit(cand, digit)
69+
for c in range(3):
70+
cand[0][c].add(digit)
71+
cand[1][0].add(digit)
72+
73+
move = apply_box_line_claiming(grid, cand)
74+
75+
assert move is not None
76+
assert move["strategy"] == "box_line_row"
77+
assert move["box"] == 0
78+
assert digit not in cand[1][0]
79+
80+
81+
def test_box_line_claiming_col() -> None:
82+
grid = _empty_grid()
83+
cand = candidates(grid)
84+
digit = 4
85+
_clear_digit(cand, digit)
86+
cand[0][0].add(digit)
87+
cand[1][0].add(digit)
88+
cand[2][1].add(digit)
89+
90+
move = apply_box_line_claiming(grid, cand)
91+
92+
assert move is not None
93+
assert move["strategy"] == "box_line_col"
94+
assert move["box"] == 0
95+
assert move["r"] == 2 and move["c"] == 1
96+
assert digit not in cand[2][1]
97+
98+
99+
def test_naked_pair_eliminates_other_candidates() -> None:
100+
grid = _empty_grid()
101+
cand = candidates(grid)
102+
_clear_digit(cand, 1)
103+
_clear_digit(cand, 2)
104+
_clear_digit(cand, 3)
105+
_set_candidates(cand, 0, 0, {1, 2})
106+
_set_candidates(cand, 0, 1, {1, 2})
107+
_set_candidates(cand, 0, 2, {1, 2, 3})
108+
109+
move = apply_naked_pair(grid, cand)
110+
111+
assert move is not None
112+
assert move["strategy"] == "naked_pair"
113+
assert move["r"] == 0 and move["c"] == 2
114+
assert move["pair"] == [1, 2]
115+
assert move["remove"] in (1, 2)
116+
assert move["remove"] not in cand[0][2]
117+
118+
119+
def test_hidden_pair_prunes_extras() -> None:
120+
grid = _empty_grid()
121+
cand = candidates(grid)
122+
digit_a, digit_b, extra = 7, 8, 9
123+
_clear_digit(cand, digit_a)
124+
_clear_digit(cand, digit_b)
125+
_clear_digit(cand, extra)
126+
_set_candidates(cand, 1, 0, {digit_a, digit_b, extra})
127+
_set_candidates(cand, 1, 1, {digit_a, digit_b, extra})
128+
129+
move = apply_hidden_pair(grid, cand)
130+
131+
assert move is not None
132+
assert move["strategy"] == "hidden_pair"
133+
assert move["r"] in (1,)
134+
assert move["c"] in (0, 1)
135+
assert move["remove"] == extra
136+
first_target = (move["r"], move["c"])
137+
assert cand[first_target[0]][first_target[1]] == {digit_a, digit_b}
138+
139+
move_second = apply_hidden_pair(grid, cand)
140+
141+
assert move_second is not None
142+
assert move_second["strategy"] == "hidden_pair"
143+
assert (move_second["r"], move_second["c"]) != first_target
144+
assert move_second["remove"] == extra
145+
second_target = (move_second["r"], move_second["c"])
146+
assert cand[second_target[0]][second_target[1]] == {digit_a, digit_b}
147+
148+
149+
def test_naked_triple_clears_unit() -> None:
150+
grid = _empty_grid()
151+
cand = candidates(grid)
152+
digits = (1, 2, 3, 4)
153+
for d in digits:
154+
_clear_digit(cand, d)
155+
_set_candidates(cand, 2, 0, {1, 2})
156+
_set_candidates(cand, 2, 1, {1, 3})
157+
_set_candidates(cand, 2, 2, {2, 3})
158+
_set_candidates(cand, 2, 3, {1, 4})
159+
160+
before = {pos: cand[pos[0]][pos[1]].copy() for pos in ((2, 0), (2, 1), (2, 2), (2, 3))}
161+
162+
move = apply_naked_triple(grid, cand)
163+
164+
assert move is not None
165+
assert move["strategy"] == "naked_triple"
166+
assert move["unit"] == "row"
167+
assert move["unit_index"] == 2
168+
target = (move["r"], move["c"])
169+
removed = move["v"]
170+
171+
assert target[0] == 2
172+
assert target in before
173+
assert removed in {1, 2, 3}
174+
assert removed in before[target]
175+
assert cand[target[0]][target[1]] == before[target] - {removed}
176+
177+
178+
def test_hidden_triple_culls_extras() -> None:
179+
grid = _empty_grid()
180+
cand = candidates(grid)
181+
triple = (4, 5, 6)
182+
extra = 7
183+
for d in (*triple, extra):
184+
_clear_digit(cand, d)
185+
_set_candidates(cand, 3, 0, {4, 5, extra})
186+
_set_candidates(cand, 3, 1, {4, 6})
187+
_set_candidates(cand, 3, 2, {5, 6})
188+
189+
move = apply_hidden_triple(grid, cand)
190+
191+
assert move is not None
192+
assert move["strategy"] == "hidden_triple"
193+
assert move["r"] == 3 and move["c"] == 0
194+
assert extra not in cand[3][0]
195+
196+
197+
def test_x_wing_rows_then_cols() -> None:
198+
grid = _empty_grid()
199+
cand = candidates(grid)
200+
digit_row = 9
201+
_clear_digit(cand, digit_row)
202+
for c in (2, 6):
203+
cand[1][c].add(digit_row)
204+
cand[5][c].add(digit_row)
205+
cand[0][2].add(digit_row)
206+
207+
move_row = apply_x_wing(grid, cand)
208+
209+
assert move_row is not None
210+
assert move_row["strategy"] == "x_wing_row"
211+
assert move_row["digit"] == digit_row
212+
assert digit_row not in cand[0][2]
213+
214+
grid2 = _empty_grid()
215+
cand2 = candidates(grid2)
216+
digit_col = 4
217+
_clear_digit(cand2, digit_col)
218+
for r in (3, 6):
219+
cand2[r][1].add(digit_col)
220+
cand2[r][7].add(digit_col)
221+
cand2[3][0].add(digit_col)
222+
223+
move_col = apply_x_wing(grid2, cand2)
224+
225+
assert move_col is not None
226+
assert move_col["strategy"] == "x_wing_col"
227+
assert move_col["digit"] == digit_col
228+
assert digit_col not in cand2[3][0]

0 commit comments

Comments
 (0)