Skip to content

Commit 44de919

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

File tree

8 files changed

+315
-86
lines changed

8 files changed

+315
-86
lines changed

README.md

Lines changed: 164 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,3 @@
1-
<!--
2-
## Development Checklist
3-
4-
Complete these tasks to fully implement Snake solver:
5-
6-
**Note:** Complete phases in order to ensure badges and links work correctly!
7-
8-
### Phase 1: Core Implementation
9-
- [ ] **Implement puzzle logic** in `snake_mip_solver/puzzle.py`
10-
- [ ] Define puzzle parameters and constraints
11-
- [ ] Add validation methods (ensure puzzle is well-formed)
12-
- [ ] Implement solution verification
13-
- [ ] **Implement MIP solver** in `snake_mip_solver/solver.py`
14-
- [ ] Add decision variables and constraints
15-
- [ ] Implement solution extraction
16-
- [ ] **Update tests** in `tests/`
17-
- [ ] Add test cases for puzzle validation
18-
- [ ] Add solver test cases with known solutions
19-
- [ ] Ensure good test coverage
20-
21-
### Phase 2: Documentation & Examples
22-
- [ ] **Update main.py** with working example
23-
- [ ] **Update README.md** with proper usage examples
24-
- [ ] **Test locally** - ensure `python main.py` and `pytest` work
25-
- [ ] **Complete mathematical model** in `model.md`
26-
- [ ] Define sets, variables, and constraints and write full model
27-
- [ ] Add illustrations/examples if required
28-
29-
### Phase 3: Repository Setup
30-
- [ ] **Create GitHub repository** (public)
31-
- [ ] **Push initial code** and verify CI passes
32-
- [ ] **Set up Codecov**
33-
- [ ] Add repository to Codecov
34-
- [ ] Configure coverage reporting
35-
- [ ] Verify coverage badge works
36-
37-
### Phase 4: Publishing
38-
- [ ] **Test package building** with `python build_package.py`
39-
- [ ] **Publish to PyPI** with `python -m twine upload dist/*`
40-
- [ ] **Verify PyPI page** and badges in README
41-
- [ ] **Test installation** from PyPI: `pip install snake-mip-solver`
42-
43-
### Phase 5: Final Polish
44-
- [ ] **Remove this checklist** from README
45-
- [ ] **Add project-specific documentation**
46-
- [ ] **Update version** and create release tag
47-
-->
48-
491
# Snake MIP Solver
502

513
[![CI](https://github.com/DenHvideDvaerg/snake-mip-solver/actions/workflows/ci.yml/badge.svg)](https://github.com/DenHvideDvaerg/snake-mip-solver/actions/workflows/ci.yml)
@@ -58,7 +10,12 @@ A Snake puzzle solver using mathematical programming.
5810

5911
## Overview
6012

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

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

@@ -70,16 +27,164 @@ pip install snake-mip-solver
7027

7128
## Requirements
7229

73-
- Python >=3.9
30+
- Python 3.9+
7431
- Google OR-Tools
7532
- pytest (for testing)
7633

34+
## Example Puzzles
35+
36+
### 6x6 Easy Puzzle
37+
38+
This 6x6 puzzle demonstrates a straightforward Snake puzzle:
39+
40+
| Puzzle | Solution |
41+
|--------|----------|
42+
| <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"> |
43+
44+
```python
45+
def example_6x6_easy():
46+
"""6x6 easy example"""
47+
puzzle = SnakePuzzle(
48+
row_sums=[1, 1, 1, 3, 2, 5],
49+
col_sums=[4, 3, 1, 1, 1, 3],
50+
start_cell=(0, 0),
51+
end_cell=(3, 5)
52+
)
53+
return puzzle
54+
```
55+
56+
### 12x12 Evil Puzzle with Missing Constraints
57+
58+
This 12x12 puzzle demonstrates a challenging large-scale puzzle with missing row/column constraints:
59+
60+
| Puzzle | Solution |
61+
|--------|----------|
62+
| <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"> |
63+
64+
```python
65+
def example_12x12_evil():
66+
"""12x12 'Evil' puzzle from https://gridpuzzle.com/snake/evil-12"""
67+
puzzle = SnakePuzzle(
68+
row_sums=[11, 2, 7, 4, 4, None, None, None, 3, 2, None, 5],
69+
col_sums=[9, 7, None, 2, 5, 6, None, None, 5, None, None, None],
70+
start_cell=(2, 6),
71+
end_cell=(7, 5)
72+
)
73+
return puzzle
74+
```
75+
7776
## Usage
7877

7978
```python
8079
from snake_mip_solver import SnakePuzzle, SnakeSolver
80+
import time
81+
82+
def solve_puzzle(puzzle, name):
83+
"""Solve a snake puzzle and display results"""
84+
print(f"\n" + "="*60)
85+
print(f"SOLVING {name.upper()}")
86+
print("="*60)
87+
88+
# Create and use the solver
89+
solver = SnakeSolver(puzzle)
90+
91+
print("Solver information:")
92+
info = solver.get_solver_info()
93+
for key, value in info.items():
94+
print(f" {key}: {value}")
95+
96+
print("\nSolving...")
97+
start_time = time.time()
98+
solution = solver.solve(verbose=False)
99+
solve_time = time.time() - start_time
100+
101+
if solution:
102+
print(f"\nSolution found in {solve_time:.3f} seconds!")
103+
print(f"Solution has {len(solution)} filled cells")
104+
print(f"Solution: {sorted(list(solution))}")
105+
106+
# Display the board with solution
107+
print("\nPuzzle with solution:")
108+
print(puzzle.get_board_visualization(solution, show_indices=False))
109+
110+
# Validate solution
111+
if puzzle.is_valid_solution(solution):
112+
print("✅ Solution is valid!")
113+
else:
114+
print("❌ Solution validation failed!")
115+
else:
116+
print(f"\nNo solution found (took {solve_time:.3f} seconds)")
117+
118+
# Load and solve example puzzles
119+
puzzle_6x6 = example_6x6_easy()
120+
solve_puzzle(puzzle_6x6, "6x6 Easy")
121+
122+
puzzle_12x12 = example_12x12_evil()
123+
solve_puzzle(puzzle_12x12, "12x12 Evil")
124+
```
125+
126+
### Output
81127

82-
# TODO: Make example either a direct copy of or very similar to main.py
128+
```
129+
============================================================
130+
SOLVING 6X6 EASY
131+
============================================================
132+
Solver information:
133+
solver_type: SCIP 9.2.2 [LP solver: SoPlex 7.1.3]
134+
num_variables: 36
135+
num_constraints: 159
136+
puzzle_size: 6x6
137+
start_cell: (0, 0)
138+
end_cell: (3, 5)
139+
140+
Solving...
141+
142+
Solution found in 0.002 seconds!
143+
Solution has 13 filled cells
144+
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)]
145+
146+
Puzzle with solution:
147+
4 3 1 1 1 3
148+
1 S _ _ _ _ _
149+
1 x _ _ _ _ _
150+
1 x _ _ _ _ _
151+
3 x x _ _ _ E
152+
2 _ x _ _ _ x
153+
5 _ x x x x x
154+
✅ Solution is valid!
155+
156+
============================================================
157+
SOLVING 12X12 EVIL
158+
============================================================
159+
Solver information:
160+
solver_type: SCIP 9.2.2 [LP solver: SoPlex 7.1.3]
161+
num_variables: 144
162+
num_constraints: 665
163+
puzzle_size: 12x12
164+
start_cell: (2, 6)
165+
end_cell: (7, 5)
166+
167+
Solving...
168+
169+
Solution found in 0.255 seconds!
170+
Solution has 49 filled cells
171+
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)]
172+
173+
Puzzle with solution:
174+
9 7 ? 2 5 6 ? ? 5 ? ? ?
175+
11 _ x x x x x x x x x x x
176+
2 _ x _ _ _ _ _ _ _ _ _ x
177+
7 x x _ _ _ _ S _ x x x x
178+
4 x _ _ _ _ x x _ x _ _ _
179+
4 x x _ _ _ x _ _ x _ _ _
180+
? _ x _ _ _ x x x x _ _ _
181+
? x x _ _ _ _ _ _ _ _ _ _
182+
? x _ _ _ _ E _ _ _ _ _ _
183+
3 x _ _ _ x x _ _ _ _ _ _
184+
2 x _ _ _ x _ _ _ _ _ _ _
185+
? x _ _ _ x _ _ _ _ _ _ _
186+
5 x x x x x _ _ _ _ _ _ _
187+
✅ Solution is valid!
83188
```
84189

85190
### Running the example
@@ -103,8 +208,15 @@ pytest --cov=snake_mip_solver # Run with coverage
103208

104209
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.
105210

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

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

109221
## License
110222

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)