Skip to content

Commit 36e5c0b

Browse files
Add unit tests for advanced strategy eliminations
1 parent 149c13a commit 36e5c0b

File tree

1 file changed

+200
-0
lines changed

1 file changed

+200
-0
lines changed

tests/test_strategies_core.py

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
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 test_locked_candidates_pointing_row() -> None:
26+
grid = _empty_grid()
27+
cand = candidates(grid)
28+
digit = 7
29+
_clear_digit(cand, digit)
30+
for c in range(3):
31+
cand[0][c].add(digit)
32+
cand[0][4].add(digit)
33+
34+
move = apply_locked_candidates_pointing(grid, cand)
35+
36+
assert move is not None
37+
assert move["strategy"] == "locked_pointing_row"
38+
assert move["r"] == 0 and move["c"] == 4
39+
assert digit not in cand[0][4]
40+
41+
42+
def test_locked_candidates_pointing_col() -> None:
43+
grid = _empty_grid()
44+
cand = candidates(grid)
45+
digit = 5
46+
_clear_digit(cand, digit)
47+
for r in range(3):
48+
cand[r][1].add(digit)
49+
cand[4][1].add(digit)
50+
51+
move = apply_locked_candidates_pointing(grid, cand)
52+
53+
assert move is not None
54+
assert move["strategy"] == "locked_pointing_col"
55+
assert move["c"] == 1 and move["r"] == 4
56+
assert digit not in cand[4][1]
57+
58+
59+
def test_box_line_claiming_row() -> None:
60+
grid = _empty_grid()
61+
cand = candidates(grid)
62+
digit = 6
63+
_clear_digit(cand, digit)
64+
for c in range(3):
65+
cand[0][c].add(digit)
66+
cand[1][0].add(digit)
67+
68+
move = apply_box_line_claiming(grid, cand)
69+
70+
assert move is not None
71+
assert move["strategy"] == "box_line_row"
72+
assert move["box"] == 0
73+
assert digit not in cand[1][0]
74+
75+
76+
def test_box_line_claiming_col() -> None:
77+
grid = _empty_grid()
78+
cand = candidates(grid)
79+
digit = 4
80+
_clear_digit(cand, digit)
81+
for r in range(3):
82+
cand[r][0].add(digit)
83+
cand[1][1].add(digit)
84+
85+
move = apply_box_line_claiming(grid, cand)
86+
87+
assert move is not None
88+
assert move["strategy"] == "box_line_col"
89+
assert move["box"] == 0
90+
assert digit not in cand[1][1]
91+
92+
93+
def test_naked_pair_eliminates_other_candidates() -> None:
94+
grid = _empty_grid()
95+
cand = candidates(grid)
96+
_clear_digit(cand, 1)
97+
_clear_digit(cand, 2)
98+
_clear_digit(cand, 3)
99+
cand[0][0].update({1, 2})
100+
cand[0][1].update({1, 2})
101+
cand[0][2].update({1, 2, 3})
102+
103+
move = apply_naked_pair(grid, cand)
104+
105+
assert move is not None
106+
assert move["strategy"] == "naked_pair"
107+
assert move["r"] == 0 and move["c"] == 2
108+
assert 1 not in cand[0][2] and 2 not in cand[0][2]
109+
110+
111+
def test_hidden_pair_prunes_extras() -> None:
112+
grid = _empty_grid()
113+
cand = candidates(grid)
114+
digit_a, digit_b, extra = 7, 8, 9
115+
_clear_digit(cand, digit_a)
116+
_clear_digit(cand, digit_b)
117+
_clear_digit(cand, extra)
118+
cand[1][0].update({digit_a, digit_b, extra})
119+
cand[1][1].update({digit_a, digit_b, extra})
120+
121+
move = apply_hidden_pair(grid, cand)
122+
123+
assert move is not None
124+
assert move["strategy"] == "hidden_pair"
125+
assert move["r"] in (1,)
126+
assert move["c"] in (0, 1)
127+
assert extra not in cand[1][0]
128+
assert extra not in cand[1][1]
129+
130+
131+
def test_naked_triple_clears_unit() -> None:
132+
grid = _empty_grid()
133+
cand = candidates(grid)
134+
digits = (1, 2, 3, 4)
135+
for d in digits:
136+
_clear_digit(cand, d)
137+
cand[2][0].update({1, 2})
138+
cand[2][1].update({1, 3})
139+
cand[2][2].update({2, 3})
140+
cand[2][3].update({1, 4})
141+
142+
move = apply_naked_triple(grid, cand)
143+
144+
assert move is not None
145+
assert move["strategy"] == "naked_triple"
146+
assert move["r"] == 2 and move["c"] == 3
147+
assert 1 not in cand[2][3] and 2 not in cand[2][3] and 3 not in cand[2][3]
148+
149+
150+
def test_hidden_triple_culls_extras() -> None:
151+
grid = _empty_grid()
152+
cand = candidates(grid)
153+
triple = (4, 5, 6)
154+
extra = 7
155+
for d in (*triple, extra):
156+
_clear_digit(cand, d)
157+
cand[3][0].update({4, 5, extra})
158+
cand[3][1].update({4, 6})
159+
cand[3][2].update({5, 6})
160+
161+
move = apply_hidden_triple(grid, cand)
162+
163+
assert move is not None
164+
assert move["strategy"] == "hidden_triple"
165+
assert move["r"] == 3 and move["c"] == 0
166+
assert extra not in cand[3][0]
167+
168+
169+
def test_x_wing_rows_then_cols() -> None:
170+
grid = _empty_grid()
171+
cand = candidates(grid)
172+
digit_row = 9
173+
_clear_digit(cand, digit_row)
174+
for c in (2, 6):
175+
cand[1][c].add(digit_row)
176+
cand[5][c].add(digit_row)
177+
cand[0][2].add(digit_row)
178+
179+
move_row = apply_x_wing(grid, cand)
180+
181+
assert move_row is not None
182+
assert move_row["strategy"] == "x_wing_row"
183+
assert move_row["digit"] == digit_row
184+
assert digit_row not in cand[0][2]
185+
186+
grid2 = _empty_grid()
187+
cand2 = candidates(grid2)
188+
digit_col = 4
189+
_clear_digit(cand2, digit_col)
190+
for r in (3, 6):
191+
cand2[r][1].add(digit_col)
192+
cand2[r][7].add(digit_col)
193+
cand2[3][0].add(digit_col)
194+
195+
move_col = apply_x_wing(grid2, cand2)
196+
197+
assert move_col is not None
198+
assert move_col["strategy"] == "x_wing_col"
199+
assert move_col["digit"] == digit_col
200+
assert digit_col not in cand2[3][0]

0 commit comments

Comments
 (0)