Skip to content

Commit 994db9d

Browse files
Update readme (#3)
* Update model.md * Update readme * Try fix install as package error * Cleanup main.py
1 parent a6e60ab commit 994db9d

File tree

8 files changed

+315
-38
lines changed

8 files changed

+315
-38
lines changed

README.md

Lines changed: 164 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,12 @@ A Snake puzzle solver using mathematical programming.
5858

5959
## Overview
6060

61-
Snake is a logic puzzle where ...
61+
Snake is a logic puzzle where you must create a single connected path on a grid according to these rules:
62+
63+
- **Single connected path** - the snake forms one continuous line from start to end cell
64+
- **No self-touching** - the snake body never touches itself, neither orthogonally nor diagonally
65+
- **Row and column constraints** - each row/column must have a specific number of filled cells
66+
- **Missing constraints variant** - supports puzzles where some row/column constraints are unknown (specified as `None`)
6267

6368
This solver models the puzzle as a **Mixed Integer Programming (MIP)** problem to find solutions.
6469

@@ -70,16 +75,164 @@ pip install snake-mip-solver
7075

7176
## Requirements
7277

73-
- Python >=3.9
78+
- Python 3.9+
7479
- Google OR-Tools
7580
- pytest (for testing)
7681

82+
## Example Puzzles
83+
84+
### 6x6 Easy Puzzle
85+
86+
This 6x6 puzzle demonstrates a straightforward Snake puzzle:
87+
88+
| Puzzle | Solution |
89+
|--------|----------|
90+
| <img src="https://github.com/DenHvideDvaerg/snake-mip-solver/raw/main/images/6x6_Easy.png" width="400"> | <img src="https://github.com/DenHvideDvaerg/snake-mip-solver/raw/main/images/6x6_Easy_solution.png" width="400"> |
91+
92+
```python
93+
def example_6x6_easy():
94+
"""6x6 easy example"""
95+
puzzle = SnakePuzzle(
96+
row_sums=[1, 1, 1, 3, 2, 5],
97+
col_sums=[4, 3, 1, 1, 1, 3],
98+
start_cell=(0, 0),
99+
end_cell=(3, 5)
100+
)
101+
return puzzle
102+
```
103+
104+
### 12x12 Evil Puzzle with Missing Constraints
105+
106+
This 12x12 puzzle demonstrates a challenging large-scale puzzle with missing row/column constraints:
107+
108+
| Puzzle | Solution |
109+
|--------|----------|
110+
| <img src="https://github.com/DenHvideDvaerg/snake-mip-solver/raw/main/images/12x12_Evil.png" width="400"> | <img src="https://github.com/DenHvideDvaerg/snake-mip-solver/raw/main/images/12x12_Evil_solution.png" width="400"> |
111+
112+
```python
113+
def example_12x12_evil():
114+
"""12x12 'Evil' puzzle from https://gridpuzzle.com/snake/evil-12"""
115+
puzzle = SnakePuzzle(
116+
row_sums=[11, 2, 7, 4, 4, None, None, None, 3, 2, None, 5],
117+
col_sums=[9, 7, None, 2, 5, 6, None, None, 5, None, None, None],
118+
start_cell=(2, 6),
119+
end_cell=(7, 5)
120+
)
121+
return puzzle
122+
```
123+
77124
## Usage
78125

79126
```python
80127
from snake_mip_solver import SnakePuzzle, SnakeSolver
128+
import time
129+
130+
def solve_puzzle(puzzle, name):
131+
"""Solve a snake puzzle and display results"""
132+
print(f"\n" + "="*60)
133+
print(f"SOLVING {name.upper()}")
134+
print("="*60)
135+
136+
# Create and use the solver
137+
solver = SnakeSolver(puzzle)
138+
139+
print("Solver information:")
140+
info = solver.get_solver_info()
141+
for key, value in info.items():
142+
print(f" {key}: {value}")
143+
144+
print("\nSolving...")
145+
start_time = time.time()
146+
solution = solver.solve(verbose=False)
147+
solve_time = time.time() - start_time
148+
149+
if solution:
150+
print(f"\nSolution found in {solve_time:.3f} seconds!")
151+
print(f"Solution has {len(solution)} filled cells")
152+
print(f"Solution: {sorted(list(solution))}")
153+
154+
# Display the board with solution
155+
print("\nPuzzle with solution:")
156+
print(puzzle.get_board_visualization(solution, show_indices=False))
157+
158+
# Validate solution
159+
if puzzle.is_valid_solution(solution):
160+
print("✅ Solution is valid!")
161+
else:
162+
print("❌ Solution validation failed!")
163+
else:
164+
print(f"\nNo solution found (took {solve_time:.3f} seconds)")
165+
166+
# Load and solve example puzzles
167+
puzzle_6x6 = example_6x6_easy()
168+
solve_puzzle(puzzle_6x6, "6x6 Easy")
169+
170+
puzzle_12x12 = example_12x12_evil()
171+
solve_puzzle(puzzle_12x12, "12x12 Evil")
172+
```
81173

82-
# TODO: Make example either a direct copy of or very similar to main.py
174+
### Output
175+
176+
```
177+
============================================================
178+
SOLVING 6X6 EASY
179+
============================================================
180+
Solver information:
181+
solver_type: SCIP 9.2.2 [LP solver: SoPlex 7.1.3]
182+
num_variables: 36
183+
num_constraints: 159
184+
puzzle_size: 6x6
185+
start_cell: (0, 0)
186+
end_cell: (3, 5)
187+
188+
Solving...
189+
190+
Solution found in 0.002 seconds!
191+
Solution has 13 filled cells
192+
Solution: [(0, 0), (1, 0), (2, 0), (3, 0), (3, 1), (3, 5), (4, 1), (4, 5), (5, 1), (5, 2), (5, 3), (5, 4), (5, 5)]
193+
194+
Puzzle with solution:
195+
4 3 1 1 1 3
196+
1 S _ _ _ _ _
197+
1 x _ _ _ _ _
198+
1 x _ _ _ _ _
199+
3 x x _ _ _ E
200+
2 _ x _ _ _ x
201+
5 _ x x x x x
202+
✅ Solution is valid!
203+
204+
============================================================
205+
SOLVING 12X12 EVIL
206+
============================================================
207+
Solver information:
208+
solver_type: SCIP 9.2.2 [LP solver: SoPlex 7.1.3]
209+
num_variables: 144
210+
num_constraints: 665
211+
puzzle_size: 12x12
212+
start_cell: (2, 6)
213+
end_cell: (7, 5)
214+
215+
Solving...
216+
217+
Solution found in 0.255 seconds!
218+
Solution has 49 filled cells
219+
Solution: [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (0, 9), (0, 10), (0, 11), (1, 1), (1, 11), (2, 0), (2, 1), (2, 6), (2, 8), (2, 9), (2, 10), (2, 11), (3, 0), (3, 5), (3, 6), (3, 8), (4, 0), (4, 1), (4, 5), (4, 8), (5, 1), (5, 5), (5, 6), (5, 7), (5, 8), (6, 0), (6, 1), (7, 0), (7, 5), (8, 0), (8, 4), (8, 5), (9, 0), (9, 4), (10, 0), (10, 4), (11, 0), (11, 1), (11, 2), (11, 3), (11, 4)]
220+
221+
Puzzle with solution:
222+
9 7 ? 2 5 6 ? ? 5 ? ? ?
223+
11 _ x x x x x x x x x x x
224+
2 _ x _ _ _ _ _ _ _ _ _ x
225+
7 x x _ _ _ _ S _ x x x x
226+
4 x _ _ _ _ x x _ x _ _ _
227+
4 x x _ _ _ x _ _ x _ _ _
228+
? _ x _ _ _ x x x x _ _ _
229+
? x x _ _ _ _ _ _ _ _ _ _
230+
? x _ _ _ _ E _ _ _ _ _ _
231+
3 x _ _ _ x x _ _ _ _ _ _
232+
2 x _ _ _ x _ _ _ _ _ _ _
233+
? x _ _ _ x _ _ _ _ _ _ _
234+
5 x x x x x _ _ _ _ _ _ _
235+
✅ Solution is valid!
83236
```
84237

85238
### Running the example
@@ -103,8 +256,15 @@ pytest --cov=snake_mip_solver # Run with coverage
103256

104257
The solver uses **Mixed Integer Programming (MIP)** to model the puzzle constraints. Google OR-Tools provides the optimization framework, with SCIP as the default solver.
105258

106-
See the complete formulation in **[Complete Mathematical Model Documentation](https://github.com/DenHvideDvaerg/snake-mip-solver/blob/main/model.md)**
259+
The mathematical formulation includes six types of constraints:
260+
1. **Start and End Cell Constraints** - fixing the path endpoints
261+
2. **Row Sum Constraints** - ensuring correct number of cells per row
262+
3. **Column Sum Constraints** - ensuring correct number of cells per column
263+
4. **Snake Path Connectivity Constraints** - forming a single connected path
264+
5. **Diagonal Non-Touching Constraints** - preventing diagonal self-contact
265+
6. **No 2×2 Block Constraints** - preventing disconnected filled blocks
107266

267+
See the complete formulation in **[Complete Mathematical Model Documentation](https://github.com/DenHvideDvaerg/snake-mip-solver/blob/main/model.md)**
108268

109269
## License
110270

images/12x12_Evil.png

7.01 KB
Loading

images/12x12_Evil_solution.png

7.12 KB
Loading

images/6x6_Easy.png

6.45 KB
Loading

images/6x6_Easy_solution.png

6.44 KB
Loading

main.py

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,6 @@ def example_3x3():
1313
return puzzle
1414

1515

16-
def example_8x8():
17-
"""8x8 example from https://puzzlegenius.org/snake/"""
18-
puzzle = SnakePuzzle(
19-
row_sums=[4, 2, 2, 3, 1, 3, 2, 6],
20-
col_sums=[3, 2, 7, 2, 2, 4, 1, 2],
21-
start_cell=(2, 5),
22-
end_cell=(6, 7)
23-
)
24-
return puzzle
25-
26-
2716
def example_diagonal_touching():
2817
"""Diagonal touching example (infeasible)"""
2918
puzzle = SnakePuzzle(
@@ -44,6 +33,16 @@ def example_adjacent_touching():
4433
)
4534
return puzzle
4635

36+
def example_6x6_easy():
37+
"""6x6 easy example"""
38+
puzzle = SnakePuzzle(
39+
row_sums=[1, 1, 1, 3, 2, 5],
40+
col_sums=[4, 3, 1, 1, 1, 3],
41+
start_cell=(0, 0),
42+
end_cell=(3, 5)
43+
)
44+
return puzzle
45+
4746
def example_12x12_evil():
4847
"""12x12 'Evil' puzzle from https://gridpuzzle.com/snake/evil-12"""
4948
puzzle = SnakePuzzle(
@@ -80,7 +79,7 @@ def solve_puzzle(puzzle, name):
8079

8180
# Display the board with solution
8281
print("\nPuzzle with solution:")
83-
print(puzzle.get_board_visualization(solution, show_indices=True))
82+
print(puzzle.get_board_visualization(solution, show_indices=False))
8483

8584
# Validate solution
8685
if puzzle.is_valid_solution(solution):
@@ -93,17 +92,8 @@ def solve_puzzle(puzzle, name):
9392

9493
def main():
9594
# Solve different puzzle examples
96-
puzzle_3x3 = example_3x3()
97-
solve_puzzle(puzzle_3x3, "3x3 Simple")
98-
99-
puzzle_diag = example_diagonal_touching()
100-
solve_puzzle(puzzle_diag, "5x5 Diagonal Touching")
101-
102-
puzzle_adjacent_touching = example_adjacent_touching()
103-
solve_puzzle(puzzle_adjacent_touching, "4x4 Adjacent Touching")
104-
105-
puzzle_8x8 = example_8x8()
106-
solve_puzzle(puzzle_8x8, "8x8")
95+
puzzle_6x6 = example_6x6_easy()
96+
solve_puzzle(puzzle_6x6, "6x6 Easy")
10797

10898
puzzle_12x12 = example_12x12_evil()
10999
solve_puzzle(puzzle_12x12, "12x12 Evil")

0 commit comments

Comments
 (0)