An automated solver for Skyscrapers↗ puzzles, relying only on pure logic.
For a rundown of the algorithm, jump to § Algorithm. For an explanation of how Skyscrapers puzzles work, I have a quickfire explanation on Skyscraping↗.
show
pass #0:
| 4 | 3 | 2 | 2 | 1 |
---------------------------------------------------------
4 | [12345] | [12345] | [12345] | [12345] | [12345] | 1
3 | [12345] | [12345] | [12345] | [12345] | [12345] | 2
2 | [12345] | [12345] | [12345] | [12345] | [12345] | 3
1 | [12345] | [12345] | [12345] | [12345] | [12345] | 2
3 | [12345] | [12345] | [12345] | [12345] | [12345] | 2
---------------------------------------------------------
| 2 | 2 | 2 | 1 | 3 |
pass #1:
| 4 | 3 | 2 | 2 | 1 |
---------------------------------------------------------
4 | [ 12] | [ 123] | [ 1234] | 4 | 5 | 1
3 | [ 23] | 4 | 5 | [ 12] | [ 23] | 2
2 | 4 | 5 | [ 1234] | [ 123] | [ 123] | 3
1 | 5 | [ 123] | [ 123] | [ 123] | 4 | 2
3 | [ 123] | [ 1234] | [ 1234] | 5 | [ 123] | 2
---------------------------------------------------------
| 2 | 2 | 2 | 1 | 3 |
pass #2:
| 4 | 3 | 2 | 2 | 1 |
---------------------------------------------------------
4 | 1 | 3 | [ 123] | 4 | 5 | 1
3 | [ 23] | 4 | 5 | 1 | [ 23] | 2
2 | 4 | 5 | [ 123] | [ 123] | [ 123] | 3
1 | 5 | [ 12] | [ 123] | [ 123] | 4 | 2
3 | [ 23] | 2 | 4 | 5 | 1 | 2
---------------------------------------------------------
| 2 | 2 | 2 | 1 | 3 |
pass #3:
| 4 | 3 | 2 | 2 | 1 |
---------------------------------------------------------
4 | 1 | 3 | 2 | 4 | 5 | 1
3 | 2 | 4 | 5 | 1 | 3 | 2
2 | 4 | 5 | 1 | 3 | 2 | 3
1 | 5 | 1 | 3 | 2 | 4 | 2
3 | 3 | 2 | 4 | 5 | 1 | 2
---------------------------------------------------------
| 2 | 2 | 2 | 1 | 3 |Clone the repository:
> git clone https://github.com/Sup2point0/ascendant[!Important] You will need Rust nightly↗ since the project uses
generic_const_exprs.
See the algorithm in action:
ascendant> cargo run -- solve-one 7 --diff=2 --random-dateSee what else you can do:
ascendant> cargo run -- --helpFetch puzzles:
# fetch 6x6 puzzles of all difficulties
ascendant> cargo run -- fetch --sizes 6 --diffs 1 2 3Solve puzzles in bulk and view stats:
# solve all 4x4, 5x5, 6x6 puzzles of difficulty 3
ascendant> cargo run -- solve-all --sizes 4 5 6 --diffs 3View the steps in solving one puzzle:
# solve the 7x7 full hard (difficulty 2) puzzle from April 1, showing all solution substeps
ascendant> cargo run -- solve-one 7 --diff=2 --date=0401 --debugFull puzzles have clues along every lane in both directions. Sparse puzzles have much fewer clues, and are significantly more difficult. Bear in mind most human-oriented puzzles1 are sparse 5x5 or 6x6 ;)
| size | difficulty | solved | time | as of |
|---|---|---|---|---|
| 4x4 | full | 365/365 | ~0.5 s | 2026 January |
| sparse | 256/365 | ^ | 2026 January | |
| 5x5 | full easy | 365/365 | ~1.5 s | 2026 January |
| full hard | 365/365 | ^ | 2026 January | |
| sparse | 93/365 | ^ | 2026 January | |
| 6x6 | full easy | 359/365 | ~4.5 s | 2026 January |
| full hard | 355/365 | ^ | 2026 January | |
| sparse | 23/365 | ^ | 2026 January | |
| 7x7 | full easy | 331/365 | ~3.5 s | 2026 January |
| full hard | 335/365 | ^ | 2026 January | |
| 8x8 | full easy | 321/365 | ~7.5 s | 2026 January |
| full hard | 310/365 | ^ | 2026 January |
Note
7x7 and 8x8 are taking less time currently since I haven't tried the solver on Sparse puzzles for those sizes yet!
show
- The algorithm is not strong at all on sparse puzzles, although it is fairly impressive how far it can get on sparse 5x5 puzzles.
- Only 23/365 on the sparse 6x6 puzzles... 🤣
- 4x4 sparse puzzles are quite specially intractable, simply down to how barren information is – some puzzles only have, like, 3 clues!
- Despite the increase in information with larger puzzle sizes, the increase in complexity is much more significant.
- That being said, the algorithm often reaches an almost-solved state.
- I’m currently figuring out how to improve the algorithm to handle these final near-solved states. Many of them need only a single push before the rules of Sudoku can clear the rest of the puzzle!
- I’m still torn over whether to implement a weak version of lane-isolated backtracking, because humans can do this mentally, so it's arguably not beyond the reach of “logic”. But it’s so ambiguous...
I affectionally call it peak descent!
The solving algorithm is iterative – it’ll keep passing over the grid, trying to make logical deductions, until it can no longer find any.
These are the steps in each pass-through:2
-
Ascent: Use the clues to establish a foundation of candidates for each cell.
- e.g. A lane
$\text{4 | \_ \_ \_ \_ \_ \_ |}$ in a 6x6 puzzle could start with any of$[123]$ , but not$[456]$ .
- e.g. A lane
-
Peak Descent: Enforce ascending sequences by descending peaks.
- If a peak has been found in a lane, step down from the peak towards the clue, calculating how many skyscrapers are currently guaranteed to be visible.
- Subtract this from the clue to find how many more skyscrapers should be visible in front of the first peak.
- Use this to restrict the candidates of the sequence.
- e.g. In a lane
$\text{4 | \_ \_ 4 \_ 6 5 |}$ this deduces$\text{4 | [12] [23] 4 \_ 6 5 |}$ . - e.g. In a lane
$\text{3 | \_ \_ \_ 5 \_ 6 |}$ this deduces$\text{3 | [4] [123] [123] 5 \_ 6 |}$ .
- e.g. In a lane
-
Sudoku: Eliminate invalid candidates by the rules of Sudoku.
- e.g. A lane
$\text{| 3 [36] [123] \_ \_ \_ |}$ can be eliminated to$\text{| 3 [6] [12] \_ \_ \_ |}$ .
- e.g. A lane
-
Pinpoint: Mark cells which are the only place in their lane for a digit to go as solved.
- e.g. A lane
$\text{| [123] [23] [24] [34] 5 6 |}$ can be solved to$\text{| 1 [23] [24] [34] 5 6 |}$ .
- e.g. A lane
-
Isolate: Eliminate candidates outside of ‘isolated’ groups (couples, triplets, etc.)
- Two
$[12]$ cells between them are guaranteed to use both$1$ and$2$ .- We might not know which way round, but we can still deduce that
$1$ and$2$ cannot go in any other cells in the lane.
- We might not know which way round, but we can still deduce that
- This can be thought of as a ‘pseudo-pinpoint’ followed by sudoku elimination.
- e.g. A lane
$\text{| [12] [12] [123] [26] \_ \_ |}$ can be eliminated to$\text{| [12] [12] [3] [6] \_ \_ |}$ .
- Two
- Solve: Mark cells with only 1 candidate left as solved.
See the Glossary on Skyscraping↗.
Future strategies to implement include:
- Twin/triplet/... elimination: 2
$[xy]$ cells eliminate$x$ and$y$ as candidates from all other cells in the lane.- (more advanced) Closed set elimination: Any set of cells which between them consume
$[xyz...]$ eliminate$[xyz]$ as candidates from all other cells in the lane.
- (more advanced) Closed set elimination: Any set of cells which between them consume
- Solve cells with 2 candidates where one would be visible and the other obscured.
- The goal is to get as far as possible with purely logical deductions, i.e. no ‘guesswork’ or ‘backtracking’. Even though guesswork isn’t well-defined↗...
- The strategies I plan on implementing are mostly those covered in Skyscraping↗.
- If we stick firmly to this, then we may be able to also generate new skyscrapers puzzles!