diff options
Diffstat (limited to 'apps')
60 files changed, 1889 insertions, 332 deletions
diff --git a/apps/plugins/puzzles/README.rockbox b/apps/plugins/puzzles/README.rockbox index 3adce72412..26e6bbb289 100644 --- a/apps/plugins/puzzles/README.rockbox +++ b/apps/plugins/puzzles/README.rockbox | |||
@@ -59,3 +59,7 @@ April 2018: Finished up the rest of the games. All work now! Surely | |||
59 | there's still bugs to fix, so stay tuned... | 59 | there's still bugs to fix, so stay tuned... |
60 | 60 | ||
61 | December 2018: Resync to 3ece3d6. Happy holidays! | 61 | December 2018: Resync to 3ece3d6. Happy holidays! |
62 | |||
63 | May 2019: Resync to e2135d5. | ||
64 | |||
65 | June 2020: Resync to 9aa7b7c. | ||
diff --git a/apps/plugins/puzzles/help/blackbox.c b/apps/plugins/puzzles/help/blackbox.c index 938810a19f..659d26cd7a 100644 --- a/apps/plugins/puzzles/help/blackbox.c +++ b/apps/plugins/puzzles/help/blackbox.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/bridges.c b/apps/plugins/puzzles/help/bridges.c index ed901c1309..b0cdcedd34 100644 --- a/apps/plugins/puzzles/help/bridges.c +++ b/apps/plugins/puzzles/help/bridges.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/cube.c b/apps/plugins/puzzles/help/cube.c index f8026a5946..7939437658 100644 --- a/apps/plugins/puzzles/help/cube.c +++ b/apps/plugins/puzzles/help/cube.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/dominosa.c b/apps/plugins/puzzles/help/dominosa.c index f1c61620ee..20a2341c64 100644 --- a/apps/plugins/puzzles/help/dominosa.c +++ b/apps/plugins/puzzles/help/dominosa.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/fifteen.c b/apps/plugins/puzzles/help/fifteen.c index eb3f84ab26..dc3e5373e8 100644 --- a/apps/plugins/puzzles/help/fifteen.c +++ b/apps/plugins/puzzles/help/fifteen.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/filling.c b/apps/plugins/puzzles/help/filling.c index 15ba8cbe43..6602890bd8 100644 --- a/apps/plugins/puzzles/help/filling.c +++ b/apps/plugins/puzzles/help/filling.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/flip.c b/apps/plugins/puzzles/help/flip.c index c451e8c672..9553df8b77 100644 --- a/apps/plugins/puzzles/help/flip.c +++ b/apps/plugins/puzzles/help/flip.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/flood.c b/apps/plugins/puzzles/help/flood.c index 1bee956902..a800ca4444 100644 --- a/apps/plugins/puzzles/help/flood.c +++ b/apps/plugins/puzzles/help/flood.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/galaxies.c b/apps/plugins/puzzles/help/galaxies.c index eed08b8ac4..d6bbf9360c 100644 --- a/apps/plugins/puzzles/help/galaxies.c +++ b/apps/plugins/puzzles/help/galaxies.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/guess.c b/apps/plugins/puzzles/help/guess.c index 1fc4f0695f..8260b0c6f6 100644 --- a/apps/plugins/puzzles/help/guess.c +++ b/apps/plugins/puzzles/help/guess.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/inertia.c b/apps/plugins/puzzles/help/inertia.c index 8d7902dc54..184893af4a 100644 --- a/apps/plugins/puzzles/help/inertia.c +++ b/apps/plugins/puzzles/help/inertia.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/keen.c b/apps/plugins/puzzles/help/keen.c index 8f0374dcdd..4539df4625 100644 --- a/apps/plugins/puzzles/help/keen.c +++ b/apps/plugins/puzzles/help/keen.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/lightup.c b/apps/plugins/puzzles/help/lightup.c index 303aa45beb..1dca0363c4 100644 --- a/apps/plugins/puzzles/help/lightup.c +++ b/apps/plugins/puzzles/help/lightup.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/loopy.c b/apps/plugins/puzzles/help/loopy.c index eb4adedfc8..85ad8e27c4 100644 --- a/apps/plugins/puzzles/help/loopy.c +++ b/apps/plugins/puzzles/help/loopy.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/magnets.c b/apps/plugins/puzzles/help/magnets.c index becfe20902..2f18033675 100644 --- a/apps/plugins/puzzles/help/magnets.c +++ b/apps/plugins/puzzles/help/magnets.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/map.c b/apps/plugins/puzzles/help/map.c index 731b5f4745..5608eb7619 100644 --- a/apps/plugins/puzzles/help/map.c +++ b/apps/plugins/puzzles/help/map.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/mines.c b/apps/plugins/puzzles/help/mines.c index 317d626292..c37e9298ab 100644 --- a/apps/plugins/puzzles/help/mines.c +++ b/apps/plugins/puzzles/help/mines.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/net.c b/apps/plugins/puzzles/help/net.c index d83892c110..256012638e 100644 --- a/apps/plugins/puzzles/help/net.c +++ b/apps/plugins/puzzles/help/net.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/netslide.c b/apps/plugins/puzzles/help/netslide.c index dc6a0d65e1..5d6e282e04 100644 --- a/apps/plugins/puzzles/help/netslide.c +++ b/apps/plugins/puzzles/help/netslide.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/palisade.c b/apps/plugins/puzzles/help/palisade.c index 3de6c2d06d..c3e1de1b51 100644 --- a/apps/plugins/puzzles/help/palisade.c +++ b/apps/plugins/puzzles/help/palisade.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/pattern.c b/apps/plugins/puzzles/help/pattern.c index 3c51afd66c..83bfaeb5fe 100644 --- a/apps/plugins/puzzles/help/pattern.c +++ b/apps/plugins/puzzles/help/pattern.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/pearl.c b/apps/plugins/puzzles/help/pearl.c index 1c7a7ee3ab..fe3f3534fc 100644 --- a/apps/plugins/puzzles/help/pearl.c +++ b/apps/plugins/puzzles/help/pearl.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/pegs.c b/apps/plugins/puzzles/help/pegs.c index d64cbe8d56..24513761c8 100644 --- a/apps/plugins/puzzles/help/pegs.c +++ b/apps/plugins/puzzles/help/pegs.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/range.c b/apps/plugins/puzzles/help/range.c index 62c308778e..34ae51a512 100644 --- a/apps/plugins/puzzles/help/range.c +++ b/apps/plugins/puzzles/help/range.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/rect.c b/apps/plugins/puzzles/help/rect.c index 768bd1a4ce..196dc077d9 100644 --- a/apps/plugins/puzzles/help/rect.c +++ b/apps/plugins/puzzles/help/rect.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/samegame.c b/apps/plugins/puzzles/help/samegame.c index 2f8e8a3003..02fc670b80 100644 --- a/apps/plugins/puzzles/help/samegame.c +++ b/apps/plugins/puzzles/help/samegame.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/signpost.c b/apps/plugins/puzzles/help/signpost.c index ceafcfd056..c0e4c44e9f 100644 --- a/apps/plugins/puzzles/help/signpost.c +++ b/apps/plugins/puzzles/help/signpost.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/singles.c b/apps/plugins/puzzles/help/singles.c index dffc14b35b..606b4f7b41 100644 --- a/apps/plugins/puzzles/help/singles.c +++ b/apps/plugins/puzzles/help/singles.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/sixteen.c b/apps/plugins/puzzles/help/sixteen.c index eb8feed049..d72226f8a1 100644 --- a/apps/plugins/puzzles/help/sixteen.c +++ b/apps/plugins/puzzles/help/sixteen.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/slant.c b/apps/plugins/puzzles/help/slant.c index 873863757e..b78c7c694c 100644 --- a/apps/plugins/puzzles/help/slant.c +++ b/apps/plugins/puzzles/help/slant.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/solo.c b/apps/plugins/puzzles/help/solo.c index a02bb94c04..449a352fe2 100644 --- a/apps/plugins/puzzles/help/solo.c +++ b/apps/plugins/puzzles/help/solo.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/tents.c b/apps/plugins/puzzles/help/tents.c index e53f92b0f6..d26afead48 100644 --- a/apps/plugins/puzzles/help/tents.c +++ b/apps/plugins/puzzles/help/tents.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/towers.c b/apps/plugins/puzzles/help/towers.c index 62c08e011b..b062c4f350 100644 --- a/apps/plugins/puzzles/help/towers.c +++ b/apps/plugins/puzzles/help/towers.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/tracks.c b/apps/plugins/puzzles/help/tracks.c index d3bdc709be..6872ddbbab 100644 --- a/apps/plugins/puzzles/help/tracks.c +++ b/apps/plugins/puzzles/help/tracks.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/twiddle.c b/apps/plugins/puzzles/help/twiddle.c index 72495874b8..69ce44e9b0 100644 --- a/apps/plugins/puzzles/help/twiddle.c +++ b/apps/plugins/puzzles/help/twiddle.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/undead.c b/apps/plugins/puzzles/help/undead.c index 3af048a3b8..11fb57cf48 100644 --- a/apps/plugins/puzzles/help/undead.c +++ b/apps/plugins/puzzles/help/undead.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/unequal.c b/apps/plugins/puzzles/help/unequal.c index 8c5d2fa9b8..f7a612c01e 100644 --- a/apps/plugins/puzzles/help/unequal.c +++ b/apps/plugins/puzzles/help/unequal.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/unruly.c b/apps/plugins/puzzles/help/unruly.c index 55c4d5240f..88590aca32 100644 --- a/apps/plugins/puzzles/help/unruly.c +++ b/apps/plugins/puzzles/help/unruly.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/help/untangle.c b/apps/plugins/puzzles/help/untangle.c index 24f9d83986..6992d97e96 100644 --- a/apps/plugins/puzzles/help/untangle.c +++ b/apps/plugins/puzzles/help/untangle.c | |||
@@ -1,4 +1,4 @@ | |||
1 | /* auto-generated on Dec 21 2018 by genhelp.sh */ | 1 | /* auto-generated on Jun 25 2020 by genhelp.sh */ |
2 | /* help text is compressed using LZ4; see compress.c for details */ | 2 | /* help text is compressed using LZ4; see compress.c for details */ |
3 | /* DO NOT EDIT! */ | 3 | /* DO NOT EDIT! */ |
4 | 4 | ||
diff --git a/apps/plugins/puzzles/src/LICENCE b/apps/plugins/puzzles/src/LICENCE index ce0418e6cc..85f67ba795 100644 --- a/apps/plugins/puzzles/src/LICENCE +++ b/apps/plugins/puzzles/src/LICENCE | |||
@@ -2,8 +2,8 @@ This software is copyright (c) 2004-2014 Simon Tatham. | |||
2 | 2 | ||
3 | Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas | 3 | Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas |
4 | Kölker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, Bernd | 4 | Kölker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, Bernd |
5 | Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens and Michael | 5 | Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens, Michael |
6 | Quevillon. | 6 | Quevillon and Asher Gordon. |
7 | 7 | ||
8 | Permission is hereby granted, free of charge, to any person | 8 | Permission is hereby granted, free of charge, to any person |
9 | obtaining a copy of this software and associated documentation files | 9 | obtaining a copy of this software and associated documentation files |
diff --git a/apps/plugins/puzzles/src/gamedesc.txt b/apps/plugins/puzzles/src/gamedesc.txt deleted file mode 100644 index db0d9cbd01..0000000000 --- a/apps/plugins/puzzles/src/gamedesc.txt +++ /dev/null | |||
@@ -1,39 +0,0 @@ | |||
1 | blackbox:blackbox.exe:Black Box:Ball-finding puzzle:Find the hidden balls in the box by bouncing laser beams off them. | ||
2 | bridges:bridges.exe:Bridges:Bridge-placing puzzle:Connect all the islands with a network of bridges. | ||
3 | cube:cube.exe:Cube:Rolling cube puzzle:Pick up all the blue squares by rolling the cube over them. | ||
4 | dominosa:dominosa.exe:Dominosa:Domino tiling puzzle:Tile the rectangle with a full set of dominoes. | ||
5 | fifteen:fifteen.exe:Fifteen:Sliding block puzzle:Slide the tiles around to arrange them into order. | ||
6 | filling:filling.exe:Filling:Polyomino puzzle:Mark every square with the area of its containing region. | ||
7 | flip:flip.exe:Flip:Tile inversion puzzle:Flip groups of squares to light them all up at once. | ||
8 | flood:flood.exe:Flood:Flood-filling puzzle:Turn the grid the same colour in as few flood fills as possible. | ||
9 | galaxies:galaxies.exe:Galaxies:Symmetric polyomino puzzle:Divide the grid into rotationally symmetric regions each centred on a dot. | ||
10 | guess:guess.exe:Guess:Combination-guessing puzzle:Guess the hidden combination of colours. | ||
11 | inertia:inertia.exe:Inertia:Gem-collecting puzzle:Collect all the gems without running into any of the mines. | ||
12 | keen:keen.exe:Keen:Arithmetic Latin square puzzle:Complete the latin square in accordance with the arithmetic clues. | ||
13 | lightup:lightup.exe:Light Up:Light-bulb placing puzzle:Place bulbs to light up all the squares. | ||
14 | loopy:loopy.exe:Loopy:Loop-drawing puzzle:Draw a single closed loop, given clues about number of adjacent edges. | ||
15 | magnets:magnets.exe:Magnets:Magnet-placing puzzle:Place magnets to satisfy the clues and avoid like poles touching. | ||
16 | map:map.exe:Map:Map-colouring puzzle:Colour the map so that adjacent regions are never the same colour. | ||
17 | mines:mines.exe:Mines:Mine-finding puzzle:Find all the mines without treading on any of them. | ||
18 | net:netgame.exe:Net:Network jigsaw puzzle:Rotate each tile to reassemble the network. | ||
19 | netslide:netslide.exe:Netslide:Toroidal sliding network puzzle:Slide a row at a time to reassemble the network. | ||
20 | palisade:palisade.exe:Palisade:Grid-division puzzle:Divide the grid into equal-sized areas in accordance with the clues. | ||
21 | pattern:pattern.exe:Pattern:Pattern puzzle:Fill in the pattern in the grid, given only the lengths of runs of black squares. | ||
22 | pearl:pearl.exe:Pearl:Loop-drawing puzzle:Draw a single closed loop, given clues about corner and straight squares. | ||
23 | pegs:pegs.exe:Pegs:Peg solitaire puzzle:Jump pegs over each other to remove all but one. | ||
24 | range:range.exe:Range:Visible-distance puzzle:Place black squares to limit the visible distance from each numbered cell. | ||
25 | rect:rect.exe:Rectangles:Rectangles puzzle:Divide the grid into rectangles with areas equal to the numbers. | ||
26 | samegame:samegame.exe:Same Game:Block-clearing puzzle:Clear the grid by removing touching groups of the same colour squares. | ||
27 | signpost:signpost.exe:Signpost:Square-connecting puzzle:Connect the squares into a path following the arrows. | ||
28 | singles:singles.exe:Singles:Number-removing puzzle:Black out the right set of duplicate numbers. | ||
29 | sixteen:sixteen.exe:Sixteen:Toroidal sliding block puzzle:Slide a row at a time to arrange the tiles into order. | ||
30 | slant:slant.exe:Slant:Maze-drawing puzzle:Draw a maze of slanting lines that matches the clues. | ||
31 | solo:solo.exe:Solo:Number placement puzzle:Fill in the grid so that each row, column and square block contains one of every digit. | ||
32 | tents:tents.exe:Tents:Tent-placing puzzle:Place a tent next to each tree. | ||
33 | towers:towers.exe:Towers:Tower-placing Latin square puzzle:Complete the latin square of towers in accordance with the clues. | ||
34 | tracks:tracks.exe:Tracks:Path-finding railway track puzzle:Fill in the railway track according to the clues. | ||
35 | twiddle:twiddle.exe:Twiddle:Rotational sliding block puzzle:Rotate the tiles around themselves to arrange them into order. | ||
36 | undead:undead.exe:Undead:Monster-placing puzzle:Place ghosts, vampires and zombies so that the right numbers of them can be seen in mirrors. | ||
37 | unequal:unequal.exe:Unequal:Latin square puzzle:Complete the latin square in accordance with the > signs. | ||
38 | unruly:unruly.exe:Unruly:Black and white grid puzzle:Fill in the black and white grid to avoid runs of three. | ||
39 | untangle:untangle.exe:Untangle:Planar graph layout puzzle:Reposition the points so that the lines do not cross. | ||
diff --git a/apps/plugins/puzzles/src/grid.c b/apps/plugins/puzzles/src/grid.c index 89bde187be..5ea37439d4 100644 --- a/apps/plugins/puzzles/src/grid.c +++ b/apps/plugins/puzzles/src/grid.c | |||
@@ -2378,6 +2378,8 @@ static void grid_size_floret(int width, int height, | |||
2378 | *tilesize = FLORET_TILESIZE; | 2378 | *tilesize = FLORET_TILESIZE; |
2379 | *xextent = (6*px+3*qx)/2 * (width-1) + 4*qx + 2*px; | 2379 | *xextent = (6*px+3*qx)/2 * (width-1) + 4*qx + 2*px; |
2380 | *yextent = (5*qy-4*py) * (height-1) + 4*qy + 2*ry; | 2380 | *yextent = (5*qy-4*py) * (height-1) + 4*qy + 2*ry; |
2381 | if (height == 1) | ||
2382 | *yextent += (5*qy-4*py)/2; | ||
2381 | } | 2383 | } |
2382 | 2384 | ||
2383 | static grid *grid_new_floret(int width, int height, const char *desc) | 2385 | static grid *grid_new_floret(int width, int height, const char *desc) |
diff --git a/apps/plugins/puzzles/src/gtk.c b/apps/plugins/puzzles/src/gtk.c index d41f8677b9..7588ee0dc6 100644 --- a/apps/plugins/puzzles/src/gtk.c +++ b/apps/plugins/puzzles/src/gtk.c | |||
@@ -44,6 +44,17 @@ | |||
44 | # endif | 44 | # endif |
45 | #endif | 45 | #endif |
46 | 46 | ||
47 | #if defined USE_CAIRO && GTK_CHECK_VERSION(2,10,0) | ||
48 | /* We can only use printing if we are using Cairo for drawing and we | ||
49 | have a GTK version >= 2.10 (when GtkPrintOperation was added). */ | ||
50 | # define USE_PRINTING | ||
51 | # if GTK_CHECK_VERSION(2,18,0) | ||
52 | /* We can embed the page setup. Before 2.18, we needed to have a | ||
53 | separate page setup. */ | ||
54 | # define USE_EMBED_PAGE_SETUP | ||
55 | # endif | ||
56 | #endif | ||
57 | |||
47 | #if GTK_CHECK_VERSION(3,0,0) | 58 | #if GTK_CHECK_VERSION(3,0,0) |
48 | /* The old names are still more concise! */ | 59 | /* The old names are still more concise! */ |
49 | #define gtk_hbox_new(x,y) gtk_box_new(GTK_ORIENTATION_HORIZONTAL,y) | 60 | #define gtk_hbox_new(x,y) gtk_box_new(GTK_ORIENTATION_HORIZONTAL,y) |
@@ -132,6 +143,18 @@ struct font { | |||
132 | }; | 143 | }; |
133 | 144 | ||
134 | /* | 145 | /* |
146 | * An internal API for functions which need to be different for | ||
147 | * printing and drawing. | ||
148 | */ | ||
149 | struct internal_drawing_api { | ||
150 | void (*set_colour)(frontend *fe, int colour); | ||
151 | #ifdef USE_CAIRO | ||
152 | void (*fill)(frontend *fe); | ||
153 | void (*fill_preserve)(frontend *fe); | ||
154 | #endif | ||
155 | }; | ||
156 | |||
157 | /* | ||
135 | * This structure holds all the data relevant to a single window. | 158 | * This structure holds all the data relevant to a single window. |
136 | * In principle this would allow us to open multiple independent | 159 | * In principle this would allow us to open multiple independent |
137 | * puzzle windows, although I can't currently see any real point in | 160 | * puzzle windows, although I can't currently see any real point in |
@@ -180,7 +203,7 @@ struct frontend { | |||
180 | GtkWidget *cfgbox; | 203 | GtkWidget *cfgbox; |
181 | void *paste_data; | 204 | void *paste_data; |
182 | int paste_data_len; | 205 | int paste_data_len; |
183 | int pw, ph; /* pixmap size (w, h are area size) */ | 206 | int pw, ph, ps; /* pixmap size (w, h are area size, s is GDK scale) */ |
184 | int ox, oy; /* offset of pixmap in drawing area */ | 207 | int ox, oy; /* offset of pixmap in drawing area */ |
185 | #ifdef OLD_FILESEL | 208 | #ifdef OLD_FILESEL |
186 | char *filesel_name; | 209 | char *filesel_name; |
@@ -225,6 +248,23 @@ struct frontend { | |||
225 | */ | 248 | */ |
226 | bool awaiting_resize_ack; | 249 | bool awaiting_resize_ack; |
227 | #endif | 250 | #endif |
251 | #ifdef USE_CAIRO | ||
252 | int printcount, printw, printh; | ||
253 | float printscale; | ||
254 | bool printsolns, printcolour; | ||
255 | int hatch; | ||
256 | float hatchthick, hatchspace; | ||
257 | drawing *print_dr; | ||
258 | document *doc; | ||
259 | #endif | ||
260 | #ifdef USE_PRINTING | ||
261 | GtkPrintOperation *printop; | ||
262 | GtkPrintContext *printcontext; | ||
263 | GtkSpinButton *printcount_spin_button, *printw_spin_button, | ||
264 | *printh_spin_button, *printscale_spin_button; | ||
265 | GtkCheckButton *soln_check_button, *colour_check_button; | ||
266 | #endif | ||
267 | const struct internal_drawing_api *dr_api; | ||
228 | }; | 268 | }; |
229 | 269 | ||
230 | struct blitter { | 270 | struct blitter { |
@@ -247,15 +287,19 @@ void get_random_seed(void **randseed, int *randseedsize) | |||
247 | void frontend_default_colour(frontend *fe, float *output) | 287 | void frontend_default_colour(frontend *fe, float *output) |
248 | { | 288 | { |
249 | #if !GTK_CHECK_VERSION(3,0,0) | 289 | #if !GTK_CHECK_VERSION(3,0,0) |
250 | /* | 290 | if (!fe->headless) { |
251 | * Use the widget style's default background colour as the | 291 | /* |
252 | * background for the puzzle drawing area. | 292 | * If we have a widget and it has a style that specifies a |
253 | */ | 293 | * default background colour, use that as the background for |
254 | GdkColor col = gtk_widget_get_style(fe->window)->bg[GTK_STATE_NORMAL]; | 294 | * the puzzle drawing area. |
255 | output[0] = col.red / 65535.0; | 295 | */ |
256 | output[1] = col.green / 65535.0; | 296 | GdkColor col = gtk_widget_get_style(fe->window)->bg[GTK_STATE_NORMAL]; |
257 | output[2] = col.blue / 65535.0; | 297 | output[0] = col.red / 65535.0; |
258 | #else | 298 | output[1] = col.green / 65535.0; |
299 | output[2] = col.blue / 65535.0; | ||
300 | } | ||
301 | #endif | ||
302 | |||
259 | /* | 303 | /* |
260 | * GTK 3 has decided that there's no such thing as a 'default | 304 | * GTK 3 has decided that there's no such thing as a 'default |
261 | * background colour' any more, because widget styles might set | 305 | * background colour' any more, because widget styles might set |
@@ -263,9 +307,11 @@ void frontend_default_colour(frontend *fe, float *output) | |||
263 | * image. We don't want to get into overlaying our entire puzzle | 307 | * image. We don't want to get into overlaying our entire puzzle |
264 | * on an arbitrary background image, so we'll just make up a | 308 | * on an arbitrary background image, so we'll just make up a |
265 | * reasonable shade of grey. | 309 | * reasonable shade of grey. |
310 | * | ||
311 | * This is also what we do on GTK 2 in headless mode, where we | ||
312 | * don't have a widget style to query. | ||
266 | */ | 313 | */ |
267 | output[0] = output[1] = output[2] = 0.9F; | 314 | output[0] = output[1] = output[2] = 0.9F; |
268 | #endif | ||
269 | } | 315 | } |
270 | 316 | ||
271 | void gtk_status_bar(void *handle, const char *text) | 317 | void gtk_status_bar(void *handle, const char *text) |
@@ -290,6 +336,7 @@ void gtk_status_bar(void *handle, const char *text) | |||
290 | static void setup_drawing(frontend *fe) | 336 | static void setup_drawing(frontend *fe) |
291 | { | 337 | { |
292 | fe->cr = cairo_create(fe->image); | 338 | fe->cr = cairo_create(fe->image); |
339 | cairo_scale(fe->cr, fe->ps, fe->ps); | ||
293 | cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_GRAY); | 340 | cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_GRAY); |
294 | cairo_set_line_width(fe->cr, 1.0); | 341 | cairo_set_line_width(fe->cr, 1.0); |
295 | cairo_set_line_cap(fe->cr, CAIRO_LINE_CAP_SQUARE); | 342 | cairo_set_line_cap(fe->cr, CAIRO_LINE_CAP_SQUARE); |
@@ -321,12 +368,23 @@ static void snaffle_colours(frontend *fe) | |||
321 | fe->colours = midend_colours(fe->me, &fe->ncolours); | 368 | fe->colours = midend_colours(fe->me, &fe->ncolours); |
322 | } | 369 | } |
323 | 370 | ||
324 | static void set_colour(frontend *fe, int colour) | 371 | static void draw_set_colour(frontend *fe, int colour) |
325 | { | 372 | { |
326 | cairo_set_source_rgb(fe->cr, | 373 | cairo_set_source_rgb(fe->cr, |
327 | fe->colours[3*colour + 0], | 374 | fe->colours[3*colour + 0], |
328 | fe->colours[3*colour + 1], | 375 | fe->colours[3*colour + 1], |
329 | fe->colours[3*colour + 2]); | 376 | fe->colours[3*colour + 2]); |
377 | } | ||
378 | |||
379 | static void print_set_colour(frontend *fe, int colour) | ||
380 | { | ||
381 | float r, g, b; | ||
382 | |||
383 | print_get_colour(fe->print_dr, colour, fe->printcolour, | ||
384 | &(fe->hatch), &r, &g, &b); | ||
385 | |||
386 | if (fe->hatch < 0) | ||
387 | cairo_set_source_rgb(fe->cr, r, g, b); | ||
330 | } | 388 | } |
331 | 389 | ||
332 | static void set_window_background(frontend *fe, int colour) | 390 | static void set_window_background(frontend *fe, int colour) |
@@ -395,6 +453,82 @@ static void save_screenshot_png(frontend *fe, const char *screenshot_file) | |||
395 | cairo_surface_write_to_png(fe->image, screenshot_file); | 453 | cairo_surface_write_to_png(fe->image, screenshot_file); |
396 | } | 454 | } |
397 | 455 | ||
456 | static void do_hatch(frontend *fe) | ||
457 | { | ||
458 | double i, x, y, width, height, maxdim; | ||
459 | |||
460 | /* Get the dimensions of the region to be hatched. */ | ||
461 | cairo_path_extents(fe->cr, &x, &y, &width, &height); | ||
462 | |||
463 | maxdim = max(width, height); | ||
464 | |||
465 | cairo_save(fe->cr); | ||
466 | |||
467 | /* Set the line color and width. */ | ||
468 | cairo_set_source_rgb(fe->cr, 0, 0, 0); | ||
469 | cairo_set_line_width(fe->cr, fe->hatchthick); | ||
470 | /* Clip to the region. */ | ||
471 | cairo_clip(fe->cr); | ||
472 | /* Hatch the bounding area of the fill region. */ | ||
473 | if (fe->hatch == HATCH_VERT || fe->hatch == HATCH_PLUS) { | ||
474 | for (i = 0.0; i <= width; i += fe->hatchspace) { | ||
475 | cairo_move_to(fe->cr, i, 0); | ||
476 | cairo_rel_line_to(fe->cr, 0, height); | ||
477 | } | ||
478 | } | ||
479 | if (fe->hatch == HATCH_HORIZ || fe->hatch == HATCH_PLUS) { | ||
480 | for (i = 0.0; i <= height; i += fe->hatchspace) { | ||
481 | cairo_move_to(fe->cr, 0, i); | ||
482 | cairo_rel_line_to(fe->cr, width, 0); | ||
483 | } | ||
484 | } | ||
485 | if (fe->hatch == HATCH_SLASH || fe->hatch == HATCH_X) { | ||
486 | for (i = -height; i <= width; i += fe->hatchspace * ROOT2) { | ||
487 | cairo_move_to(fe->cr, i, 0); | ||
488 | cairo_rel_line_to(fe->cr, maxdim, maxdim); | ||
489 | } | ||
490 | } | ||
491 | if (fe->hatch == HATCH_BACKSLASH || fe->hatch == HATCH_X) { | ||
492 | for (i = 0.0; i <= width + height; i += fe->hatchspace * ROOT2) { | ||
493 | cairo_move_to(fe->cr, i, 0); | ||
494 | cairo_rel_line_to(fe->cr, -maxdim, maxdim); | ||
495 | } | ||
496 | } | ||
497 | cairo_stroke(fe->cr); | ||
498 | |||
499 | cairo_restore(fe->cr); | ||
500 | } | ||
501 | |||
502 | static void do_draw_fill(frontend *fe) | ||
503 | { | ||
504 | cairo_fill(fe->cr); | ||
505 | } | ||
506 | |||
507 | static void do_draw_fill_preserve(frontend *fe) | ||
508 | { | ||
509 | cairo_fill_preserve(fe->cr); | ||
510 | } | ||
511 | |||
512 | static void do_print_fill(frontend *fe) | ||
513 | { | ||
514 | if (fe->hatch < 0) | ||
515 | cairo_fill(fe->cr); | ||
516 | else | ||
517 | do_hatch(fe); | ||
518 | } | ||
519 | |||
520 | static void do_print_fill_preserve(frontend *fe) | ||
521 | { | ||
522 | if (fe->hatch < 0) { | ||
523 | cairo_fill_preserve(fe->cr); | ||
524 | } else { | ||
525 | cairo_path_t *oldpath; | ||
526 | oldpath = cairo_copy_path(fe->cr); | ||
527 | do_hatch(fe); | ||
528 | cairo_append_path(fe->cr, oldpath); | ||
529 | } | ||
530 | } | ||
531 | |||
398 | static void do_clip(frontend *fe, int x, int y, int w, int h) | 532 | static void do_clip(frontend *fe, int x, int y, int w, int h) |
399 | { | 533 | { |
400 | cairo_new_path(fe->cr); | 534 | cairo_new_path(fe->cr); |
@@ -413,7 +547,7 @@ static void do_draw_rect(frontend *fe, int x, int y, int w, int h) | |||
413 | cairo_new_path(fe->cr); | 547 | cairo_new_path(fe->cr); |
414 | cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_NONE); | 548 | cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_NONE); |
415 | cairo_rectangle(fe->cr, x, y, w, h); | 549 | cairo_rectangle(fe->cr, x, y, w, h); |
416 | cairo_fill(fe->cr); | 550 | fe->dr_api->fill(fe); |
417 | cairo_restore(fe->cr); | 551 | cairo_restore(fe->cr); |
418 | } | 552 | } |
419 | 553 | ||
@@ -447,11 +581,11 @@ static void do_draw_poly(frontend *fe, int *coords, int npoints, | |||
447 | cairo_line_to(fe->cr, coords[i*2] + 0.5, coords[i*2 + 1] + 0.5); | 581 | cairo_line_to(fe->cr, coords[i*2] + 0.5, coords[i*2 + 1] + 0.5); |
448 | cairo_close_path(fe->cr); | 582 | cairo_close_path(fe->cr); |
449 | if (fillcolour >= 0) { | 583 | if (fillcolour >= 0) { |
450 | set_colour(fe, fillcolour); | 584 | fe->dr_api->set_colour(fe, fillcolour); |
451 | cairo_fill_preserve(fe->cr); | 585 | fe->dr_api->fill_preserve(fe); |
452 | } | 586 | } |
453 | assert(outlinecolour >= 0); | 587 | assert(outlinecolour >= 0); |
454 | set_colour(fe, outlinecolour); | 588 | fe->dr_api->set_colour(fe, outlinecolour); |
455 | cairo_stroke(fe->cr); | 589 | cairo_stroke(fe->cr); |
456 | } | 590 | } |
457 | 591 | ||
@@ -462,11 +596,11 @@ static void do_draw_circle(frontend *fe, int cx, int cy, int radius, | |||
462 | cairo_arc(fe->cr, cx + 0.5, cy + 0.5, radius, 0, 2*PI); | 596 | cairo_arc(fe->cr, cx + 0.5, cy + 0.5, radius, 0, 2*PI); |
463 | cairo_close_path(fe->cr); /* Just in case... */ | 597 | cairo_close_path(fe->cr); /* Just in case... */ |
464 | if (fillcolour >= 0) { | 598 | if (fillcolour >= 0) { |
465 | set_colour(fe, fillcolour); | 599 | fe->dr_api->set_colour(fe, fillcolour); |
466 | cairo_fill_preserve(fe->cr); | 600 | fe->dr_api->fill_preserve(fe); |
467 | } | 601 | } |
468 | assert(outlinecolour >= 0); | 602 | assert(outlinecolour >= 0); |
469 | set_colour(fe, outlinecolour); | 603 | fe->dr_api->set_colour(fe, outlinecolour); |
470 | cairo_stroke(fe->cr); | 604 | cairo_stroke(fe->cr); |
471 | } | 605 | } |
472 | 606 | ||
@@ -512,23 +646,24 @@ static void wipe_and_maybe_destroy_cairo(frontend *fe, cairo_t *cr, | |||
512 | static void setup_backing_store(frontend *fe) | 646 | static void setup_backing_store(frontend *fe) |
513 | { | 647 | { |
514 | #ifndef USE_CAIRO_WITHOUT_PIXMAP | 648 | #ifndef USE_CAIRO_WITHOUT_PIXMAP |
515 | if (fe->headless) { | 649 | if (!fe->headless) { |
516 | fprintf(stderr, "headless mode does not work with GDK pixmaps\n"); | 650 | fe->pixmap = gdk_pixmap_new(gtk_widget_get_window(fe->area), |
517 | exit(1); | 651 | fe->pw*fe->ps, fe->ph*fe->ps, -1); |
652 | } else { | ||
653 | fe->pixmap = NULL; | ||
518 | } | 654 | } |
519 | |||
520 | fe->pixmap = gdk_pixmap_new(gtk_widget_get_window(fe->area), | ||
521 | fe->pw, fe->ph, -1); | ||
522 | #endif | 655 | #endif |
656 | |||
523 | fe->image = cairo_image_surface_create(CAIRO_FORMAT_RGB24, | 657 | fe->image = cairo_image_surface_create(CAIRO_FORMAT_RGB24, |
524 | fe->pw, fe->ph); | 658 | fe->pw*fe->ps, fe->ph*fe->ps); |
525 | 659 | ||
526 | wipe_and_maybe_destroy_cairo(fe, cairo_create(fe->image), true); | 660 | wipe_and_maybe_destroy_cairo(fe, cairo_create(fe->image), true); |
527 | #ifndef USE_CAIRO_WITHOUT_PIXMAP | 661 | #ifndef USE_CAIRO_WITHOUT_PIXMAP |
528 | wipe_and_maybe_destroy_cairo(fe, gdk_cairo_create(fe->pixmap), true); | 662 | if (!fe->headless) |
663 | wipe_and_maybe_destroy_cairo(fe, gdk_cairo_create(fe->pixmap), true); | ||
529 | #endif | 664 | #endif |
530 | #if GTK_CHECK_VERSION(3,22,0) | ||
531 | if (!fe->headless) { | 665 | if (!fe->headless) { |
666 | #if GTK_CHECK_VERSION(3,22,0) | ||
532 | GdkWindow *gdkwin; | 667 | GdkWindow *gdkwin; |
533 | cairo_region_t *region; | 668 | cairo_region_t *region; |
534 | GdkDrawingContext *drawctx; | 669 | GdkDrawingContext *drawctx; |
@@ -541,11 +676,11 @@ static void setup_backing_store(frontend *fe) | |||
541 | wipe_and_maybe_destroy_cairo(fe, cr, false); | 676 | wipe_and_maybe_destroy_cairo(fe, cr, false); |
542 | gdk_window_end_draw_frame(gdkwin, drawctx); | 677 | gdk_window_end_draw_frame(gdkwin, drawctx); |
543 | cairo_region_destroy(region); | 678 | cairo_region_destroy(region); |
544 | } | ||
545 | #else | 679 | #else |
546 | wipe_and_maybe_destroy_cairo( | 680 | wipe_and_maybe_destroy_cairo( |
547 | fe, gdk_cairo_create(gtk_widget_get_window(fe->area)), true); | 681 | fe, gdk_cairo_create(gtk_widget_get_window(fe->area)), true); |
548 | #endif | 682 | #endif |
683 | } | ||
549 | } | 684 | } |
550 | 685 | ||
551 | static bool backing_store_ok(frontend *fe) | 686 | static bool backing_store_ok(frontend *fe) |
@@ -616,7 +751,7 @@ static void set_window_background(frontend *fe, int colour) | |||
616 | gdk_window_set_background(fe->window->window, &fe->colours[colour]); | 751 | gdk_window_set_background(fe->window->window, &fe->colours[colour]); |
617 | } | 752 | } |
618 | 753 | ||
619 | static void set_colour(frontend *fe, int colour) | 754 | static void draw_set_colour(frontend *fe, int colour) |
620 | { | 755 | { |
621 | gdk_gc_set_foreground(fe->gc, &fe->colours[colour]); | 756 | gdk_gc_set_foreground(fe->gc, &fe->colours[colour]); |
622 | } | 757 | } |
@@ -709,11 +844,11 @@ static void do_draw_poly(frontend *fe, int *coords, int npoints, | |||
709 | } | 844 | } |
710 | 845 | ||
711 | if (fillcolour >= 0) { | 846 | if (fillcolour >= 0) { |
712 | set_colour(fe, fillcolour); | 847 | fe->dr_api->set_colour(fe, fillcolour); |
713 | gdk_draw_polygon(fe->pixmap, fe->gc, true, points, npoints); | 848 | gdk_draw_polygon(fe->pixmap, fe->gc, true, points, npoints); |
714 | } | 849 | } |
715 | assert(outlinecolour >= 0); | 850 | assert(outlinecolour >= 0); |
716 | set_colour(fe, outlinecolour); | 851 | fe->dr_api->set_colour(fe, outlinecolour); |
717 | 852 | ||
718 | /* | 853 | /* |
719 | * In principle we ought to be able to use gdk_draw_polygon for | 854 | * In principle we ought to be able to use gdk_draw_polygon for |
@@ -733,14 +868,14 @@ static void do_draw_circle(frontend *fe, int cx, int cy, int radius, | |||
733 | int fillcolour, int outlinecolour) | 868 | int fillcolour, int outlinecolour) |
734 | { | 869 | { |
735 | if (fillcolour >= 0) { | 870 | if (fillcolour >= 0) { |
736 | set_colour(fe, fillcolour); | 871 | fe->dr_api->set_colour(fe, fillcolour); |
737 | gdk_draw_arc(fe->pixmap, fe->gc, true, | 872 | gdk_draw_arc(fe->pixmap, fe->gc, true, |
738 | cx - radius, cy - radius, | 873 | cx - radius, cy - radius, |
739 | 2 * radius, 2 * radius, 0, 360 * 64); | 874 | 2 * radius, 2 * radius, 0, 360 * 64); |
740 | } | 875 | } |
741 | 876 | ||
742 | assert(outlinecolour >= 0); | 877 | assert(outlinecolour >= 0); |
743 | set_colour(fe, outlinecolour); | 878 | fe->dr_api->set_colour(fe, outlinecolour); |
744 | gdk_draw_arc(fe->pixmap, fe->gc, false, | 879 | gdk_draw_arc(fe->pixmap, fe->gc, false, |
745 | cx - radius, cy - radius, | 880 | cx - radius, cy - radius, |
746 | 2 * radius, 2 * radius, 0, 360 * 64); | 881 | 2 * radius, 2 * radius, 0, 360 * 64); |
@@ -1045,21 +1180,21 @@ void gtk_draw_text(void *handle, int x, int y, int fonttype, int fontsize, | |||
1045 | /* | 1180 | /* |
1046 | * Do the job. | 1181 | * Do the job. |
1047 | */ | 1182 | */ |
1048 | set_colour(fe, colour); | 1183 | fe->dr_api->set_colour(fe, colour); |
1049 | align_and_draw_text(fe, i, align, x, y, text); | 1184 | align_and_draw_text(fe, i, align, x, y, text); |
1050 | } | 1185 | } |
1051 | 1186 | ||
1052 | void gtk_draw_rect(void *handle, int x, int y, int w, int h, int colour) | 1187 | void gtk_draw_rect(void *handle, int x, int y, int w, int h, int colour) |
1053 | { | 1188 | { |
1054 | frontend *fe = (frontend *)handle; | 1189 | frontend *fe = (frontend *)handle; |
1055 | set_colour(fe, colour); | 1190 | fe->dr_api->set_colour(fe, colour); |
1056 | do_draw_rect(fe, x, y, w, h); | 1191 | do_draw_rect(fe, x, y, w, h); |
1057 | } | 1192 | } |
1058 | 1193 | ||
1059 | void gtk_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour) | 1194 | void gtk_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour) |
1060 | { | 1195 | { |
1061 | frontend *fe = (frontend *)handle; | 1196 | frontend *fe = (frontend *)handle; |
1062 | set_colour(fe, colour); | 1197 | fe->dr_api->set_colour(fe, colour); |
1063 | do_draw_line(fe, x1, y1, x2, y2); | 1198 | do_draw_line(fe, x1, y1, x2, y2); |
1064 | } | 1199 | } |
1065 | 1200 | ||
@@ -1067,7 +1202,7 @@ void gtk_draw_thick_line(void *handle, float thickness, | |||
1067 | float x1, float y1, float x2, float y2, int colour) | 1202 | float x1, float y1, float x2, float y2, int colour) |
1068 | { | 1203 | { |
1069 | frontend *fe = (frontend *)handle; | 1204 | frontend *fe = (frontend *)handle; |
1070 | set_colour(fe, colour); | 1205 | fe->dr_api->set_colour(fe, colour); |
1071 | do_draw_thick_line(fe, thickness, x1, y1, x2, y2); | 1206 | do_draw_thick_line(fe, thickness, x1, y1, x2, y2); |
1072 | } | 1207 | } |
1073 | 1208 | ||
@@ -1161,6 +1296,105 @@ char *gtk_text_fallback(void *handle, const char *const *strings, int nstrings) | |||
1161 | } | 1296 | } |
1162 | #endif | 1297 | #endif |
1163 | 1298 | ||
1299 | #ifdef USE_PRINTING | ||
1300 | void gtk_begin_doc(void *handle, int pages) | ||
1301 | { | ||
1302 | frontend *fe = (frontend *)handle; | ||
1303 | gtk_print_operation_set_n_pages(fe->printop, pages); | ||
1304 | } | ||
1305 | |||
1306 | void gtk_begin_page(void *handle, int number) | ||
1307 | { | ||
1308 | } | ||
1309 | |||
1310 | void gtk_begin_puzzle(void *handle, float xm, float xc, | ||
1311 | float ym, float yc, int pw, int ph, float wmm) | ||
1312 | { | ||
1313 | frontend *fe = (frontend *)handle; | ||
1314 | double ppw, pph, pox, poy, dpmmx, dpmmy; | ||
1315 | double scale; | ||
1316 | |||
1317 | ppw = gtk_print_context_get_width(fe->printcontext); | ||
1318 | pph = gtk_print_context_get_height(fe->printcontext); | ||
1319 | dpmmx = gtk_print_context_get_dpi_x(fe->printcontext) / 25.4; | ||
1320 | dpmmy = gtk_print_context_get_dpi_y(fe->printcontext) / 25.4; | ||
1321 | |||
1322 | /* | ||
1323 | * Compute the puzzle's position in pixels on the logical page. | ||
1324 | */ | ||
1325 | pox = xm * ppw + xc * dpmmx; | ||
1326 | poy = ym * pph + yc * dpmmy; | ||
1327 | |||
1328 | /* | ||
1329 | * And determine the scale. | ||
1330 | * | ||
1331 | * I need a scale such that the maximum puzzle-coordinate | ||
1332 | * extent of the rectangle (pw * scale) is equal to the pixel | ||
1333 | * equivalent of the puzzle's millimetre width (wmm * dpmmx). | ||
1334 | */ | ||
1335 | scale = wmm * dpmmx / pw; | ||
1336 | |||
1337 | /* | ||
1338 | * Now instruct Cairo to transform points based on our calculated | ||
1339 | * values (order here *is* important). | ||
1340 | */ | ||
1341 | cairo_save(fe->cr); | ||
1342 | cairo_translate(fe->cr, pox, poy); | ||
1343 | cairo_scale(fe->cr, scale, scale); | ||
1344 | |||
1345 | fe->hatchthick = 0.2 * pw / wmm; | ||
1346 | fe->hatchspace = 1.0 * pw / wmm; | ||
1347 | } | ||
1348 | |||
1349 | void gtk_end_puzzle(void *handle) | ||
1350 | { | ||
1351 | frontend *fe = (frontend *)handle; | ||
1352 | cairo_restore(fe->cr); | ||
1353 | } | ||
1354 | |||
1355 | void gtk_end_page(void *handle, int number) | ||
1356 | { | ||
1357 | } | ||
1358 | |||
1359 | void gtk_end_doc(void *handle) | ||
1360 | { | ||
1361 | } | ||
1362 | |||
1363 | void gtk_line_width(void *handle, float width) | ||
1364 | { | ||
1365 | frontend *fe = (frontend *)handle; | ||
1366 | cairo_set_line_width(fe->cr, width); | ||
1367 | } | ||
1368 | |||
1369 | void gtk_line_dotted(void *handle, bool dotted) | ||
1370 | { | ||
1371 | frontend *fe = (frontend *)handle; | ||
1372 | |||
1373 | if (dotted) { | ||
1374 | const double dash = 35.0; | ||
1375 | cairo_set_dash(fe->cr, &dash, 1, 0); | ||
1376 | } else { | ||
1377 | cairo_set_dash(fe->cr, NULL, 0, 0); | ||
1378 | } | ||
1379 | } | ||
1380 | #endif /* USE_PRINTING */ | ||
1381 | |||
1382 | const struct internal_drawing_api internal_drawing = { | ||
1383 | draw_set_colour, | ||
1384 | #ifdef USE_CAIRO | ||
1385 | do_draw_fill, | ||
1386 | do_draw_fill_preserve, | ||
1387 | #endif | ||
1388 | }; | ||
1389 | |||
1390 | #ifdef USE_CAIRO | ||
1391 | const struct internal_drawing_api internal_printing = { | ||
1392 | print_set_colour, | ||
1393 | do_print_fill, | ||
1394 | do_print_fill_preserve, | ||
1395 | }; | ||
1396 | #endif | ||
1397 | |||
1164 | const struct drawing_api gtk_drawing = { | 1398 | const struct drawing_api gtk_drawing = { |
1165 | gtk_draw_text, | 1399 | gtk_draw_text, |
1166 | gtk_draw_rect, | 1400 | gtk_draw_rect, |
@@ -1177,8 +1411,19 @@ const struct drawing_api gtk_drawing = { | |||
1177 | gtk_blitter_free, | 1411 | gtk_blitter_free, |
1178 | gtk_blitter_save, | 1412 | gtk_blitter_save, |
1179 | gtk_blitter_load, | 1413 | gtk_blitter_load, |
1414 | #ifdef USE_PRINTING | ||
1415 | gtk_begin_doc, | ||
1416 | gtk_begin_page, | ||
1417 | gtk_begin_puzzle, | ||
1418 | gtk_end_puzzle, | ||
1419 | gtk_end_page, | ||
1420 | gtk_end_doc, | ||
1421 | gtk_line_width, | ||
1422 | gtk_line_dotted, | ||
1423 | #else | ||
1180 | NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */ | 1424 | NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */ |
1181 | NULL, NULL, /* line_width, line_dotted */ | 1425 | NULL, NULL, /* line_width, line_dotted */ |
1426 | #endif | ||
1182 | #ifdef USE_PANGO | 1427 | #ifdef USE_PANGO |
1183 | gtk_text_fallback, | 1428 | gtk_text_fallback, |
1184 | #else | 1429 | #else |
@@ -1340,12 +1585,22 @@ static gint draw_area(GtkWidget *widget, cairo_t *cr, gpointer data) | |||
1340 | frontend *fe = (frontend *)data; | 1585 | frontend *fe = (frontend *)data; |
1341 | GdkRectangle dirtyrect; | 1586 | GdkRectangle dirtyrect; |
1342 | 1587 | ||
1588 | cairo_surface_t *target_surface = cairo_get_target(cr); | ||
1589 | cairo_matrix_t m; | ||
1590 | cairo_get_matrix(cr, &m); | ||
1591 | double orig_sx, orig_sy; | ||
1592 | cairo_surface_get_device_scale(target_surface, &orig_sx, &orig_sy); | ||
1593 | cairo_surface_set_device_scale(target_surface, 1.0, 1.0); | ||
1594 | cairo_translate(cr, m.x0 * (orig_sx - 1.0), m.y0 * (orig_sy - 1.0)); | ||
1595 | |||
1343 | gdk_cairo_get_clip_rectangle(cr, &dirtyrect); | 1596 | gdk_cairo_get_clip_rectangle(cr, &dirtyrect); |
1344 | cairo_set_source_surface(cr, fe->image, fe->ox, fe->oy); | 1597 | cairo_set_source_surface(cr, fe->image, fe->ox, fe->oy); |
1345 | cairo_rectangle(cr, dirtyrect.x, dirtyrect.y, | 1598 | cairo_rectangle(cr, dirtyrect.x, dirtyrect.y, |
1346 | dirtyrect.width, dirtyrect.height); | 1599 | dirtyrect.width, dirtyrect.height); |
1347 | cairo_fill(cr); | 1600 | cairo_fill(cr); |
1348 | 1601 | ||
1602 | cairo_surface_set_device_scale(target_surface, orig_sx, orig_sy); | ||
1603 | |||
1349 | return true; | 1604 | return true; |
1350 | } | 1605 | } |
1351 | #else | 1606 | #else |
@@ -1390,16 +1645,22 @@ static gint map_window(GtkWidget *widget, GdkEvent *event, | |||
1390 | static void resize_puzzle_to_area(frontend *fe, int x, int y) | 1645 | static void resize_puzzle_to_area(frontend *fe, int x, int y) |
1391 | { | 1646 | { |
1392 | int oldw = fe->w, oldpw = fe->pw, oldh = fe->h, oldph = fe->ph; | 1647 | int oldw = fe->w, oldpw = fe->pw, oldh = fe->h, oldph = fe->ph; |
1648 | int oldps = fe->ps; | ||
1393 | 1649 | ||
1394 | fe->w = x; | 1650 | fe->w = x; |
1395 | fe->h = y; | 1651 | fe->h = y; |
1396 | midend_size(fe->me, &x, &y, true); | 1652 | midend_size(fe->me, &x, &y, true); |
1397 | fe->pw = x; | 1653 | fe->pw = x; |
1398 | fe->ph = y; | 1654 | fe->ph = y; |
1655 | #if GTK_CHECK_VERSION(3,10,0) | ||
1656 | fe->ps = gtk_widget_get_scale_factor(fe->area); | ||
1657 | #else | ||
1658 | fe->ps = 1; | ||
1659 | #endif | ||
1399 | fe->ox = (fe->w - fe->pw) / 2; | 1660 | fe->ox = (fe->w - fe->pw) / 2; |
1400 | fe->oy = (fe->h - fe->ph) / 2; | 1661 | fe->oy = (fe->h - fe->ph) / 2; |
1401 | 1662 | ||
1402 | if (oldw != fe->w || oldpw != fe->pw || | 1663 | if (oldw != fe->w || oldpw != fe->pw || oldps != fe->ps || |
1403 | oldh != fe->h || oldph != fe->ph || !backing_store_ok(fe)) { | 1664 | oldh != fe->h || oldph != fe->ph || !backing_store_ok(fe)) { |
1404 | if (backing_store_ok(fe)) | 1665 | if (backing_store_ok(fe)) |
1405 | teardown_backing_store(fe); | 1666 | teardown_backing_store(fe); |
@@ -1413,6 +1674,7 @@ static gint configure_area(GtkWidget *widget, | |||
1413 | GdkEventConfigure *event, gpointer data) | 1674 | GdkEventConfigure *event, gpointer data) |
1414 | { | 1675 | { |
1415 | frontend *fe = (frontend *)data; | 1676 | frontend *fe = (frontend *)data; |
1677 | |||
1416 | resize_puzzle_to_area(fe, event->width, event->height); | 1678 | resize_puzzle_to_area(fe, event->width, event->height); |
1417 | #if GTK_CHECK_VERSION(3,0,0) | 1679 | #if GTK_CHECK_VERSION(3,0,0) |
1418 | fe->awaiting_resize_ack = false; | 1680 | fe->awaiting_resize_ack = false; |
@@ -2245,6 +2507,317 @@ static char *file_selector(frontend *fe, const char *title, bool save) | |||
2245 | 2507 | ||
2246 | #endif | 2508 | #endif |
2247 | 2509 | ||
2510 | #ifdef USE_PRINTING | ||
2511 | GObject *create_print_widget(GtkPrintOperation *print, gpointer data) | ||
2512 | { | ||
2513 | GtkLabel *count_label, *width_label, *height_label, | ||
2514 | *scale_llabel, *scale_rlabel; | ||
2515 | GtkBox *scale_hbox; | ||
2516 | GtkWidget *grid; | ||
2517 | frontend *fe = (frontend *)data; | ||
2518 | |||
2519 | fe->printcount_spin_button = | ||
2520 | GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 999, 1)); | ||
2521 | gtk_spin_button_set_numeric(fe->printcount_spin_button, true); | ||
2522 | gtk_spin_button_set_snap_to_ticks(fe->printcount_spin_button, true); | ||
2523 | fe->printw_spin_button = | ||
2524 | GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 99, 1)); | ||
2525 | gtk_spin_button_set_numeric(fe->printw_spin_button, true); | ||
2526 | gtk_spin_button_set_snap_to_ticks(fe->printw_spin_button, true); | ||
2527 | fe->printh_spin_button = | ||
2528 | GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 99, 1)); | ||
2529 | gtk_spin_button_set_numeric(fe->printh_spin_button, true); | ||
2530 | gtk_spin_button_set_snap_to_ticks(fe->printh_spin_button, true); | ||
2531 | fe->printscale_spin_button = | ||
2532 | GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(1, 1000, 1)); | ||
2533 | gtk_spin_button_set_digits(fe->printscale_spin_button, 1); | ||
2534 | gtk_spin_button_set_numeric(fe->printscale_spin_button, true); | ||
2535 | if (thegame.can_solve) { | ||
2536 | fe->soln_check_button = | ||
2537 | GTK_CHECK_BUTTON( | ||
2538 | gtk_check_button_new_with_label("Print solutions")); | ||
2539 | } | ||
2540 | if (thegame.can_print_in_colour) { | ||
2541 | fe->colour_check_button = | ||
2542 | GTK_CHECK_BUTTON( | ||
2543 | gtk_check_button_new_with_label("Print in color")); | ||
2544 | } | ||
2545 | |||
2546 | /* Set defaults to what was selected last time. */ | ||
2547 | gtk_spin_button_set_value(fe->printcount_spin_button, | ||
2548 | (gdouble)fe->printcount); | ||
2549 | gtk_spin_button_set_value(fe->printw_spin_button, | ||
2550 | (gdouble)fe->printw); | ||
2551 | gtk_spin_button_set_value(fe->printh_spin_button, | ||
2552 | (gdouble)fe->printh); | ||
2553 | gtk_spin_button_set_value(fe->printscale_spin_button, | ||
2554 | (gdouble)fe->printscale); | ||
2555 | if (thegame.can_solve) { | ||
2556 | gtk_toggle_button_set_active( | ||
2557 | GTK_TOGGLE_BUTTON(fe->soln_check_button), fe->printsolns); | ||
2558 | } | ||
2559 | if (thegame.can_print_in_colour) { | ||
2560 | gtk_toggle_button_set_active( | ||
2561 | GTK_TOGGLE_BUTTON(fe->colour_check_button), fe->printcolour); | ||
2562 | } | ||
2563 | |||
2564 | count_label = GTK_LABEL(gtk_label_new("Puzzles to print:")); | ||
2565 | width_label = GTK_LABEL(gtk_label_new("Puzzles across:")); | ||
2566 | height_label = GTK_LABEL(gtk_label_new("Puzzles down:")); | ||
2567 | scale_llabel = GTK_LABEL(gtk_label_new("Puzzle scale:")); | ||
2568 | scale_rlabel = GTK_LABEL(gtk_label_new("%")); | ||
2569 | #if GTK_CHECK_VERSION(3,0,0) | ||
2570 | gtk_widget_set_halign(GTK_WIDGET(count_label), GTK_ALIGN_START); | ||
2571 | gtk_widget_set_halign(GTK_WIDGET(width_label), GTK_ALIGN_START); | ||
2572 | gtk_widget_set_halign(GTK_WIDGET(height_label), GTK_ALIGN_START); | ||
2573 | gtk_widget_set_halign(GTK_WIDGET(scale_llabel), GTK_ALIGN_START); | ||
2574 | #else | ||
2575 | gtk_misc_set_alignment(GTK_MISC(count_label), 0, 0); | ||
2576 | gtk_misc_set_alignment(GTK_MISC(width_label), 0, 0); | ||
2577 | gtk_misc_set_alignment(GTK_MISC(height_label), 0, 0); | ||
2578 | gtk_misc_set_alignment(GTK_MISC(scale_llabel), 0, 0); | ||
2579 | #endif | ||
2580 | |||
2581 | scale_hbox = GTK_BOX(gtk_hbox_new(false, 6)); | ||
2582 | gtk_box_pack_start(scale_hbox, GTK_WIDGET(fe->printscale_spin_button), | ||
2583 | false, false, 0); | ||
2584 | gtk_box_pack_start(scale_hbox, GTK_WIDGET(scale_rlabel), | ||
2585 | false, false, 0); | ||
2586 | |||
2587 | #if GTK_CHECK_VERSION(3,0,0) | ||
2588 | grid = gtk_grid_new(); | ||
2589 | gtk_grid_set_column_spacing(GTK_GRID(grid), 18); | ||
2590 | gtk_grid_set_row_spacing(GTK_GRID(grid), 18); | ||
2591 | gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(count_label), 0, 0, 1, 1); | ||
2592 | gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(width_label), 0, 1, 1, 1); | ||
2593 | gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(height_label), 0, 2, 1, 1); | ||
2594 | gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(scale_llabel), 0, 3, 1, 1); | ||
2595 | gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->printcount_spin_button), | ||
2596 | 1, 0, 1, 1); | ||
2597 | gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->printw_spin_button), | ||
2598 | 1, 1, 1, 1); | ||
2599 | gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->printh_spin_button), | ||
2600 | 1, 2, 1, 1); | ||
2601 | gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(scale_hbox), 1, 3, 1, 1); | ||
2602 | if (thegame.can_solve) { | ||
2603 | gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->soln_check_button), | ||
2604 | 0, 4, 1, 1); | ||
2605 | } | ||
2606 | if (thegame.can_print_in_colour) { | ||
2607 | gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(fe->colour_check_button), | ||
2608 | thegame.can_solve, 4, 1, 1); | ||
2609 | } | ||
2610 | #else | ||
2611 | grid = gtk_table_new((thegame.can_solve || thegame.can_print_in_colour) ? | ||
2612 | 5 : 4, 2, false); | ||
2613 | gtk_table_set_col_spacings(GTK_TABLE(grid), 18); | ||
2614 | gtk_table_set_row_spacings(GTK_TABLE(grid), 18); | ||
2615 | gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(count_label), 0, 1, 0, 1, | ||
2616 | GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); | ||
2617 | gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(width_label), 0, 1, 1, 2, | ||
2618 | GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); | ||
2619 | gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(height_label), 0, 1, 2, 3, | ||
2620 | GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); | ||
2621 | gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(scale_llabel), 0, 1, 3, 4, | ||
2622 | GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); | ||
2623 | gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->printcount_spin_button), | ||
2624 | 1, 2, 0, 1, | ||
2625 | GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); | ||
2626 | gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->printw_spin_button), | ||
2627 | 1, 2, 1, 2, | ||
2628 | GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); | ||
2629 | gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->printh_spin_button), | ||
2630 | 1, 2, 2, 3, | ||
2631 | GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); | ||
2632 | gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(scale_hbox), 1, 2, 3, 4, | ||
2633 | GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); | ||
2634 | if (thegame.can_solve) { | ||
2635 | gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->soln_check_button), | ||
2636 | 0, 1, 4, 5, | ||
2637 | GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); | ||
2638 | } | ||
2639 | if (thegame.can_print_in_colour) { | ||
2640 | gtk_table_attach(GTK_TABLE(grid), GTK_WIDGET(fe->colour_check_button), | ||
2641 | thegame.can_solve, thegame.can_solve + 1, 4, 5, | ||
2642 | GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); | ||
2643 | } | ||
2644 | #endif | ||
2645 | gtk_container_set_border_width(GTK_CONTAINER(grid), 12); | ||
2646 | |||
2647 | gtk_widget_show_all(grid); | ||
2648 | |||
2649 | return G_OBJECT(grid); | ||
2650 | } | ||
2651 | |||
2652 | void apply_print_widget(GtkPrintOperation *print, | ||
2653 | GtkWidget *widget, gpointer data) | ||
2654 | { | ||
2655 | frontend *fe = (frontend *)data; | ||
2656 | |||
2657 | /* We ignore `widget' because it is easier and faster to store the | ||
2658 | widgets we need in `fe' then to get the children of `widget'. */ | ||
2659 | fe->printcount = | ||
2660 | gtk_spin_button_get_value_as_int(fe->printcount_spin_button); | ||
2661 | fe->printw = gtk_spin_button_get_value_as_int(fe->printw_spin_button); | ||
2662 | fe->printh = gtk_spin_button_get_value_as_int(fe->printh_spin_button); | ||
2663 | fe->printscale = gtk_spin_button_get_value(fe->printscale_spin_button); | ||
2664 | if (thegame.can_solve) { | ||
2665 | fe->printsolns = | ||
2666 | gtk_toggle_button_get_active( | ||
2667 | GTK_TOGGLE_BUTTON(fe->soln_check_button)); | ||
2668 | } | ||
2669 | if (thegame.can_print_in_colour) { | ||
2670 | fe->printcolour = | ||
2671 | gtk_toggle_button_get_active( | ||
2672 | GTK_TOGGLE_BUTTON(fe->colour_check_button)); | ||
2673 | } | ||
2674 | } | ||
2675 | |||
2676 | void print_begin(GtkPrintOperation *printop, | ||
2677 | GtkPrintContext *context, gpointer data) | ||
2678 | { | ||
2679 | frontend *fe = (frontend *)data; | ||
2680 | midend *nme = NULL; /* non-interactive midend for bulk puzzle generation */ | ||
2681 | int i; | ||
2682 | |||
2683 | fe->printcontext = context; | ||
2684 | fe->cr = gtk_print_context_get_cairo_context(context); | ||
2685 | |||
2686 | /* | ||
2687 | * Create our document structure and fill it up with puzzles. | ||
2688 | */ | ||
2689 | fe->doc = document_new(fe->printw, fe->printh, fe->printscale / 100.0F); | ||
2690 | |||
2691 | for (i = 0; i < fe->printcount; i++) { | ||
2692 | const char *err; | ||
2693 | |||
2694 | if (i == 0) { | ||
2695 | err = midend_print_puzzle(fe->me, fe->doc, fe->printsolns); | ||
2696 | } else { | ||
2697 | if (!nme) { | ||
2698 | game_params *params; | ||
2699 | |||
2700 | nme = midend_new(NULL, &thegame, NULL, NULL); | ||
2701 | |||
2702 | /* | ||
2703 | * Set the non-interactive mid-end to have the same | ||
2704 | * parameters as the standard one. | ||
2705 | */ | ||
2706 | params = midend_get_params(fe->me); | ||
2707 | midend_set_params(nme, params); | ||
2708 | thegame.free_params(params); | ||
2709 | } | ||
2710 | |||
2711 | midend_new_game(nme); | ||
2712 | err = midend_print_puzzle(nme, fe->doc, fe->printsolns); | ||
2713 | } | ||
2714 | |||
2715 | if (err) { | ||
2716 | error_box(fe->window, err); | ||
2717 | return; | ||
2718 | } | ||
2719 | } | ||
2720 | |||
2721 | if (nme) | ||
2722 | midend_free(nme); | ||
2723 | |||
2724 | /* Begin the document. */ | ||
2725 | document_begin(fe->doc, fe->print_dr); | ||
2726 | } | ||
2727 | |||
2728 | void draw_page(GtkPrintOperation *printop, | ||
2729 | GtkPrintContext *context, | ||
2730 | gint page_nr, gpointer data) | ||
2731 | { | ||
2732 | frontend *fe = (frontend *)data; | ||
2733 | document_print_page(fe->doc, fe->print_dr, page_nr); | ||
2734 | } | ||
2735 | |||
2736 | void print_end(GtkPrintOperation *printop, | ||
2737 | GtkPrintContext *context, gpointer data) | ||
2738 | { | ||
2739 | frontend *fe = (frontend *)data; | ||
2740 | |||
2741 | /* End and free the document. */ | ||
2742 | document_end(fe->doc, fe->print_dr); | ||
2743 | document_free(fe->doc); | ||
2744 | fe->doc = NULL; | ||
2745 | } | ||
2746 | |||
2747 | static void print_dialog(frontend *fe) | ||
2748 | { | ||
2749 | GError *error; | ||
2750 | static GtkPrintSettings *settings = NULL; | ||
2751 | static GtkPageSetup *page_setup = NULL; | ||
2752 | #ifndef USE_EMBED_PAGE_SETUP | ||
2753 | GtkPageSetup *new_page_setup; | ||
2754 | #endif | ||
2755 | |||
2756 | fe->printop = gtk_print_operation_new(); | ||
2757 | gtk_print_operation_set_use_full_page(fe->printop, true); | ||
2758 | gtk_print_operation_set_custom_tab_label(fe->printop, "Puzzle Settings"); | ||
2759 | g_signal_connect(fe->printop, "create-custom-widget", | ||
2760 | G_CALLBACK(create_print_widget), fe); | ||
2761 | g_signal_connect(fe->printop, "custom-widget-apply", | ||
2762 | G_CALLBACK(apply_print_widget), fe); | ||
2763 | g_signal_connect(fe->printop, "begin-print", G_CALLBACK(print_begin), fe); | ||
2764 | g_signal_connect(fe->printop, "draw-page", G_CALLBACK(draw_page), fe); | ||
2765 | g_signal_connect(fe->printop, "end-print", G_CALLBACK(print_end), fe); | ||
2766 | #ifdef USE_EMBED_PAGE_SETUP | ||
2767 | gtk_print_operation_set_embed_page_setup(fe->printop, true); | ||
2768 | #else | ||
2769 | if (page_setup == NULL) { | ||
2770 | page_setup = | ||
2771 | g_object_ref( | ||
2772 | gtk_print_operation_get_default_page_setup(fe->printop)); | ||
2773 | } | ||
2774 | if (settings == NULL) { | ||
2775 | settings = | ||
2776 | g_object_ref(gtk_print_operation_get_print_settings(fe->printop)); | ||
2777 | } | ||
2778 | new_page_setup = gtk_print_run_page_setup_dialog(GTK_WINDOW(fe->window), | ||
2779 | page_setup, settings); | ||
2780 | g_object_unref(page_setup); | ||
2781 | page_setup = new_page_setup; | ||
2782 | gtk_print_operation_set_default_page_setup(fe->printop, page_setup); | ||
2783 | #endif | ||
2784 | |||
2785 | if (settings != NULL) | ||
2786 | gtk_print_operation_set_print_settings(fe->printop, settings); | ||
2787 | if (page_setup != NULL) | ||
2788 | gtk_print_operation_set_default_page_setup(fe->printop, page_setup); | ||
2789 | |||
2790 | switch (gtk_print_operation_run(fe->printop, | ||
2791 | GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, | ||
2792 | GTK_WINDOW(fe->window), &error)) { | ||
2793 | case GTK_PRINT_OPERATION_RESULT_ERROR: | ||
2794 | error_box(fe->window, error->message); | ||
2795 | g_error_free(error); | ||
2796 | break; | ||
2797 | case GTK_PRINT_OPERATION_RESULT_APPLY: | ||
2798 | if (settings != NULL) | ||
2799 | g_object_unref(settings); | ||
2800 | settings = | ||
2801 | g_object_ref(gtk_print_operation_get_print_settings(fe->printop)); | ||
2802 | #ifdef USE_EMBED_PAGE_SETUP | ||
2803 | if (page_setup != NULL) | ||
2804 | g_object_unref(page_setup); | ||
2805 | page_setup = | ||
2806 | g_object_ref( | ||
2807 | gtk_print_operation_get_default_page_setup(fe->printop)); | ||
2808 | #endif | ||
2809 | break; | ||
2810 | default: | ||
2811 | /* Don't error out on -Werror=switch. */ | ||
2812 | break; | ||
2813 | } | ||
2814 | |||
2815 | g_object_unref(fe->printop); | ||
2816 | fe->printop = NULL; | ||
2817 | fe->printcontext = NULL; | ||
2818 | } | ||
2819 | #endif /* USE_PRINTING */ | ||
2820 | |||
2248 | struct savefile_write_ctx { | 2821 | struct savefile_write_ctx { |
2249 | FILE *fp; | 2822 | FILE *fp; |
2250 | int error; | 2823 | int error; |
@@ -2346,6 +2919,15 @@ static void menu_load_event(GtkMenuItem *menuitem, gpointer data) | |||
2346 | } | 2919 | } |
2347 | } | 2920 | } |
2348 | 2921 | ||
2922 | #ifdef USE_PRINTING | ||
2923 | static void menu_print_event(GtkMenuItem *menuitem, gpointer data) | ||
2924 | { | ||
2925 | frontend *fe = (frontend *)data; | ||
2926 | |||
2927 | print_dialog(fe); | ||
2928 | } | ||
2929 | #endif | ||
2930 | |||
2349 | static void menu_solve_event(GtkMenuItem *menuitem, gpointer data) | 2931 | static void menu_solve_event(GtkMenuItem *menuitem, gpointer data) |
2350 | { | 2932 | { |
2351 | frontend *fe = (frontend *)data; | 2933 | frontend *fe = (frontend *)data; |
@@ -2388,18 +2970,31 @@ static void menu_about_event(GtkMenuItem *menuitem, gpointer data) | |||
2388 | frontend *fe = (frontend *)data; | 2970 | frontend *fe = (frontend *)data; |
2389 | 2971 | ||
2390 | #if GTK_CHECK_VERSION(3,0,0) | 2972 | #if GTK_CHECK_VERSION(3,0,0) |
2973 | # define ABOUT_PARAMS \ | ||
2974 | "program-name", thegame.name, \ | ||
2975 | "version", ver, \ | ||
2976 | "comments", "Part of Simon Tatham's Portable Puzzle Collection" | ||
2977 | |||
2391 | extern char *const *const xpm_icons[]; | 2978 | extern char *const *const xpm_icons[]; |
2392 | extern const int n_xpm_icons; | 2979 | extern const int n_xpm_icons; |
2393 | GdkPixbuf *icon = gdk_pixbuf_new_from_xpm_data | 2980 | |
2394 | ((const gchar **)xpm_icons[n_xpm_icons-1]); | 2981 | if (n_xpm_icons) { |
2395 | gtk_show_about_dialog | 2982 | GdkPixbuf *icon = gdk_pixbuf_new_from_xpm_data |
2396 | (GTK_WINDOW(fe->window), | 2983 | ((const gchar **)xpm_icons[n_xpm_icons-1]); |
2397 | "program-name", thegame.name, | 2984 | |
2398 | "version", ver, | 2985 | gtk_show_about_dialog |
2399 | "comments", "Part of Simon Tatham's Portable Puzzle Collection", | 2986 | (GTK_WINDOW(fe->window), |
2400 | "logo", icon, | 2987 | ABOUT_PARAMS, |
2401 | (const gchar *)NULL); | 2988 | "logo", icon, |
2402 | g_object_unref(G_OBJECT(icon)); | 2989 | (const gchar *)NULL); |
2990 | g_object_unref(G_OBJECT(icon)); | ||
2991 | } | ||
2992 | else { | ||
2993 | gtk_show_about_dialog | ||
2994 | (GTK_WINDOW(fe->window), | ||
2995 | ABOUT_PARAMS, | ||
2996 | (const gchar *)NULL); | ||
2997 | } | ||
2403 | #else | 2998 | #else |
2404 | char titlebuf[256]; | 2999 | char titlebuf[256]; |
2405 | char textbuf[1024]; | 3000 | char textbuf[1024]; |
@@ -2489,6 +3084,9 @@ static frontend *new_window( | |||
2489 | char *arg, int argtype, char **error, bool headless) | 3084 | char *arg, int argtype, char **error, bool headless) |
2490 | { | 3085 | { |
2491 | frontend *fe; | 3086 | frontend *fe; |
3087 | #ifdef USE_PRINTING | ||
3088 | frontend *print_fe = NULL; | ||
3089 | #endif | ||
2492 | GtkBox *vbox, *hbox; | 3090 | GtkBox *vbox, *hbox; |
2493 | GtkWidget *menu, *menuitem; | 3091 | GtkWidget *menu, *menuitem; |
2494 | GList *iconlist; | 3092 | GList *iconlist; |
@@ -2501,13 +3099,14 @@ static frontend *new_window( | |||
2501 | fe = snew(frontend); | 3099 | fe = snew(frontend); |
2502 | memset(fe, 0, sizeof(frontend)); | 3100 | memset(fe, 0, sizeof(frontend)); |
2503 | 3101 | ||
2504 | #if !GTK_CHECK_VERSION(3,0,0) | 3102 | #ifndef USE_CAIRO |
2505 | if (headless) { | 3103 | if (headless) { |
2506 | fprintf(stderr, "headless mode not supported below GTK 3\n"); | 3104 | fprintf(stderr, "headless mode not supported for non-Cairo drawing\n"); |
2507 | exit(1); | 3105 | exit(1); |
2508 | } | 3106 | } |
2509 | #else | 3107 | #else |
2510 | fe->headless = headless; | 3108 | fe->headless = headless; |
3109 | fe->ps = 1; /* in headless mode, configure_area won't have set this */ | ||
2511 | #endif | 3110 | #endif |
2512 | 3111 | ||
2513 | fe->timer_active = false; | 3112 | fe->timer_active = false; |
@@ -2515,6 +3114,32 @@ static frontend *new_window( | |||
2515 | 3114 | ||
2516 | fe->me = midend_new(fe, &thegame, >k_drawing, fe); | 3115 | fe->me = midend_new(fe, &thegame, >k_drawing, fe); |
2517 | 3116 | ||
3117 | fe->dr_api = &internal_drawing; | ||
3118 | |||
3119 | #ifdef USE_PRINTING | ||
3120 | if (thegame.can_print) { | ||
3121 | print_fe = snew(frontend); | ||
3122 | memset(print_fe, 0, sizeof(frontend)); | ||
3123 | |||
3124 | /* Defaults */ | ||
3125 | print_fe->printcount = print_fe->printw = print_fe->printh = 1; | ||
3126 | print_fe->printscale = 100; | ||
3127 | print_fe->printsolns = false; | ||
3128 | print_fe->printcolour = thegame.can_print_in_colour; | ||
3129 | |||
3130 | /* | ||
3131 | * We need to use the same midend as the main frontend because | ||
3132 | * we need midend_print_puzzle() to be able to print the | ||
3133 | * current puzzle. | ||
3134 | */ | ||
3135 | print_fe->me = fe->me; | ||
3136 | |||
3137 | print_fe->print_dr = drawing_new(>k_drawing, print_fe->me, print_fe); | ||
3138 | |||
3139 | print_fe->dr_api = &internal_printing; | ||
3140 | } | ||
3141 | #endif | ||
3142 | |||
2518 | if (arg) { | 3143 | if (arg) { |
2519 | const char *err; | 3144 | const char *err; |
2520 | FILE *fp; | 3145 | FILE *fp; |
@@ -2568,6 +3193,12 @@ static frontend *new_window( | |||
2568 | *error = dupstr(errbuf); | 3193 | *error = dupstr(errbuf); |
2569 | midend_free(fe->me); | 3194 | midend_free(fe->me); |
2570 | sfree(fe); | 3195 | sfree(fe); |
3196 | #ifdef USE_PRINTING | ||
3197 | if (thegame.can_print) { | ||
3198 | drawing_free(print_fe->print_dr); | ||
3199 | sfree(print_fe); | ||
3200 | } | ||
3201 | #endif | ||
2571 | return NULL; | 3202 | return NULL; |
2572 | } | 3203 | } |
2573 | 3204 | ||
@@ -2711,6 +3342,16 @@ static frontend *new_window( | |||
2711 | g_signal_connect(G_OBJECT(menuitem), "activate", | 3342 | g_signal_connect(G_OBJECT(menuitem), "activate", |
2712 | G_CALLBACK(menu_save_event), fe); | 3343 | G_CALLBACK(menu_save_event), fe); |
2713 | gtk_widget_show(menuitem); | 3344 | gtk_widget_show(menuitem); |
3345 | #ifdef USE_PRINTING | ||
3346 | if (thegame.can_print) { | ||
3347 | add_menu_separator(GTK_CONTAINER(menu)); | ||
3348 | menuitem = gtk_menu_item_new_with_label("Print..."); | ||
3349 | gtk_container_add(GTK_CONTAINER(menu), menuitem); | ||
3350 | g_signal_connect(G_OBJECT(menuitem), "activate", | ||
3351 | G_CALLBACK(menu_print_event), print_fe); | ||
3352 | gtk_widget_show(menuitem); | ||
3353 | } | ||
3354 | #endif | ||
2714 | #ifndef STYLUS_BASED | 3355 | #ifndef STYLUS_BASED |
2715 | add_menu_separator(GTK_CONTAINER(menu)); | 3356 | add_menu_separator(GTK_CONTAINER(menu)); |
2716 | add_menu_ui_item(fe, GTK_CONTAINER(menu), "Undo", UI_UNDO, 'u', 0); | 3357 | add_menu_ui_item(fe, GTK_CONTAINER(menu), "Undo", UI_UNDO, 'u', 0); |
diff --git a/apps/plugins/puzzles/src/keen.c b/apps/plugins/puzzles/src/keen.c index baa1d81802..70e3e5432c 100644 --- a/apps/plugins/puzzles/src/keen.c +++ b/apps/plugins/puzzles/src/keen.c | |||
@@ -591,6 +591,92 @@ static int solver_hard(struct latin_solver *solver, void *vctx) | |||
591 | #define SOLVER(upper,title,func,lower) func, | 591 | #define SOLVER(upper,title,func,lower) func, |
592 | static usersolver_t const keen_solvers[] = { DIFFLIST(SOLVER) }; | 592 | static usersolver_t const keen_solvers[] = { DIFFLIST(SOLVER) }; |
593 | 593 | ||
594 | static int transpose(int index, int w) | ||
595 | { | ||
596 | return (index % w) * w + (index / w); | ||
597 | } | ||
598 | |||
599 | static bool keen_valid(struct latin_solver *solver, void *vctx) | ||
600 | { | ||
601 | struct solver_ctx *ctx = (struct solver_ctx *)vctx; | ||
602 | int w = ctx->w; | ||
603 | int box, i; | ||
604 | |||
605 | /* | ||
606 | * Iterate over each clue box and check it's satisfied. | ||
607 | */ | ||
608 | for (box = 0; box < ctx->nboxes; box++) { | ||
609 | int *sq = ctx->boxlist + ctx->boxes[box]; | ||
610 | int n = ctx->boxes[box+1] - ctx->boxes[box]; | ||
611 | long value = ctx->clues[box] & ~CMASK; | ||
612 | long op = ctx->clues[box] & CMASK; | ||
613 | bool fail = false; | ||
614 | |||
615 | switch (op) { | ||
616 | case C_ADD: { | ||
617 | long sum = 0; | ||
618 | for (i = 0; i < n; i++) | ||
619 | sum += solver->grid[transpose(sq[i], w)]; | ||
620 | fail = (sum != value); | ||
621 | break; | ||
622 | } | ||
623 | |||
624 | case C_MUL: { | ||
625 | long remaining = value; | ||
626 | for (i = 0; i < n; i++) { | ||
627 | if (remaining % solver->grid[transpose(sq[i], w)]) { | ||
628 | fail = true; | ||
629 | break; | ||
630 | } | ||
631 | remaining /= solver->grid[transpose(sq[i], w)]; | ||
632 | } | ||
633 | if (remaining != 1) | ||
634 | fail = true; | ||
635 | break; | ||
636 | } | ||
637 | |||
638 | case C_SUB: | ||
639 | assert(n == 2); | ||
640 | if (value != labs(solver->grid[transpose(sq[0], w)] - | ||
641 | solver->grid[transpose(sq[1], w)])) | ||
642 | fail = true; | ||
643 | break; | ||
644 | |||
645 | case C_DIV: { | ||
646 | int num, den; | ||
647 | assert(n == 2); | ||
648 | num = max(solver->grid[transpose(sq[0], w)], | ||
649 | solver->grid[transpose(sq[1], w)]); | ||
650 | den = min(solver->grid[transpose(sq[0], w)], | ||
651 | solver->grid[transpose(sq[1], w)]); | ||
652 | if (den * value != num) | ||
653 | fail = true; | ||
654 | break; | ||
655 | } | ||
656 | } | ||
657 | |||
658 | if (fail) { | ||
659 | #ifdef STANDALONE_SOLVER | ||
660 | if (solver_show_working) { | ||
661 | printf("%*sclue at (%d,%d) is violated\n", | ||
662 | solver_recurse_depth*4, "", | ||
663 | sq[0]/w+1, sq[0]%w+1); | ||
664 | printf("%*s (%s clue with target %ld containing [", | ||
665 | solver_recurse_depth*4, "", | ||
666 | (op == C_ADD ? "addition" : op == C_SUB ? "subtraction": | ||
667 | op == C_MUL ? "multiplication" : "division"), value); | ||
668 | for (i = 0; i < n; i++) | ||
669 | printf(" %d", (int)solver->grid[transpose(sq[i], w)]); | ||
670 | printf(" ]\n"); | ||
671 | } | ||
672 | #endif | ||
673 | return false; | ||
674 | } | ||
675 | } | ||
676 | |||
677 | return true; | ||
678 | } | ||
679 | |||
594 | static int solver(int w, int *dsf, long *clues, digit *soln, int maxdiff) | 680 | static int solver(int w, int *dsf, long *clues, digit *soln, int maxdiff) |
595 | { | 681 | { |
596 | int a = w*w; | 682 | int a = w*w; |
@@ -638,7 +724,7 @@ static int solver(int w, int *dsf, long *clues, digit *soln, int maxdiff) | |||
638 | ret = latin_solver(soln, w, maxdiff, | 724 | ret = latin_solver(soln, w, maxdiff, |
639 | DIFF_EASY, DIFF_HARD, DIFF_EXTREME, | 725 | DIFF_EASY, DIFF_HARD, DIFF_EXTREME, |
640 | DIFF_EXTREME, DIFF_UNREASONABLE, | 726 | DIFF_EXTREME, DIFF_UNREASONABLE, |
641 | keen_solvers, &ctx, NULL, NULL); | 727 | keen_solvers, keen_valid, &ctx, NULL, NULL); |
642 | 728 | ||
643 | sfree(ctx.dscratch); | 729 | sfree(ctx.dscratch); |
644 | sfree(ctx.iscratch); | 730 | sfree(ctx.iscratch); |
diff --git a/apps/plugins/puzzles/src/latin.c b/apps/plugins/puzzles/src/latin.c index 9d06ccd938..39930166e7 100644 --- a/apps/plugins/puzzles/src/latin.c +++ b/apps/plugins/puzzles/src/latin.c | |||
@@ -19,8 +19,8 @@ | |||
19 | static int latin_solver_top(struct latin_solver *solver, int maxdiff, | 19 | static int latin_solver_top(struct latin_solver *solver, int maxdiff, |
20 | int diff_simple, int diff_set_0, int diff_set_1, | 20 | int diff_simple, int diff_set_0, int diff_set_1, |
21 | int diff_forcing, int diff_recursive, | 21 | int diff_forcing, int diff_recursive, |
22 | usersolver_t const *usersolvers, void *ctx, | 22 | usersolver_t const *usersolvers, validator_t valid, |
23 | ctxnew_t ctxnew, ctxfree_t ctxfree); | 23 | void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree); |
24 | 24 | ||
25 | #ifdef STANDALONE_SOLVER | 25 | #ifdef STANDALONE_SOLVER |
26 | int solver_show_working, solver_recurse_depth; | 26 | int solver_show_working, solver_recurse_depth; |
@@ -711,7 +711,7 @@ int latin_solver_diff_set(struct latin_solver *solver, | |||
711 | static int latin_solver_recurse | 711 | static int latin_solver_recurse |
712 | (struct latin_solver *solver, int diff_simple, int diff_set_0, | 712 | (struct latin_solver *solver, int diff_simple, int diff_set_0, |
713 | int diff_set_1, int diff_forcing, int diff_recursive, | 713 | int diff_set_1, int diff_forcing, int diff_recursive, |
714 | usersolver_t const *usersolvers, void *ctx, | 714 | usersolver_t const *usersolvers, validator_t valid, void *ctx, |
715 | ctxnew_t ctxnew, ctxfree_t ctxfree) | 715 | ctxnew_t ctxnew, ctxfree_t ctxfree) |
716 | { | 716 | { |
717 | int best, bestcount; | 717 | int best, bestcount; |
@@ -817,7 +817,8 @@ static int latin_solver_recurse | |||
817 | ret = latin_solver_top(&subsolver, diff_recursive, | 817 | ret = latin_solver_top(&subsolver, diff_recursive, |
818 | diff_simple, diff_set_0, diff_set_1, | 818 | diff_simple, diff_set_0, diff_set_1, |
819 | diff_forcing, diff_recursive, | 819 | diff_forcing, diff_recursive, |
820 | usersolvers, newctx, ctxnew, ctxfree); | 820 | usersolvers, valid, newctx, |
821 | ctxnew, ctxfree); | ||
821 | latin_solver_free(&subsolver); | 822 | latin_solver_free(&subsolver); |
822 | if (ctxnew) | 823 | if (ctxnew) |
823 | ctxfree(newctx); | 824 | ctxfree(newctx); |
@@ -879,8 +880,8 @@ static int latin_solver_recurse | |||
879 | static int latin_solver_top(struct latin_solver *solver, int maxdiff, | 880 | static int latin_solver_top(struct latin_solver *solver, int maxdiff, |
880 | int diff_simple, int diff_set_0, int diff_set_1, | 881 | int diff_simple, int diff_set_0, int diff_set_1, |
881 | int diff_forcing, int diff_recursive, | 882 | int diff_forcing, int diff_recursive, |
882 | usersolver_t const *usersolvers, void *ctx, | 883 | usersolver_t const *usersolvers, validator_t valid, |
883 | ctxnew_t ctxnew, ctxfree_t ctxfree) | 884 | void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree) |
884 | { | 885 | { |
885 | struct latin_solver_scratch *scratch = latin_solver_new_scratch(solver); | 886 | struct latin_solver_scratch *scratch = latin_solver_new_scratch(solver); |
886 | int ret, diff = diff_simple; | 887 | int ret, diff = diff_simple; |
@@ -941,7 +942,8 @@ static int latin_solver_top(struct latin_solver *solver, int maxdiff, | |||
941 | int nsol = latin_solver_recurse(solver, | 942 | int nsol = latin_solver_recurse(solver, |
942 | diff_simple, diff_set_0, diff_set_1, | 943 | diff_simple, diff_set_0, diff_set_1, |
943 | diff_forcing, diff_recursive, | 944 | diff_forcing, diff_recursive, |
944 | usersolvers, ctx, ctxnew, ctxfree); | 945 | usersolvers, valid, ctx, |
946 | ctxnew, ctxfree); | ||
945 | if (nsol < 0) diff = diff_impossible; | 947 | if (nsol < 0) diff = diff_impossible; |
946 | else if (nsol == 1) diff = diff_recursive; | 948 | else if (nsol == 1) diff = diff_recursive; |
947 | else if (nsol > 1) diff = diff_ambiguous; | 949 | else if (nsol > 1) diff = diff_ambiguous; |
@@ -990,6 +992,17 @@ static int latin_solver_top(struct latin_solver *solver, int maxdiff, | |||
990 | } | 992 | } |
991 | #endif | 993 | #endif |
992 | 994 | ||
995 | if (diff != diff_impossible && diff != diff_unfinished && | ||
996 | diff != diff_ambiguous && valid && !valid(solver, ctx)) { | ||
997 | #ifdef STANDALONE_SOLVER | ||
998 | if (solver_show_working) { | ||
999 | printf("%*ssolution failed final validation!\n", | ||
1000 | solver_recurse_depth*4, ""); | ||
1001 | } | ||
1002 | #endif | ||
1003 | diff = diff_impossible; | ||
1004 | } | ||
1005 | |||
993 | latin_solver_free_scratch(scratch); | 1006 | latin_solver_free_scratch(scratch); |
994 | 1007 | ||
995 | return diff; | 1008 | return diff; |
@@ -998,8 +1011,8 @@ static int latin_solver_top(struct latin_solver *solver, int maxdiff, | |||
998 | int latin_solver_main(struct latin_solver *solver, int maxdiff, | 1011 | int latin_solver_main(struct latin_solver *solver, int maxdiff, |
999 | int diff_simple, int diff_set_0, int diff_set_1, | 1012 | int diff_simple, int diff_set_0, int diff_set_1, |
1000 | int diff_forcing, int diff_recursive, | 1013 | int diff_forcing, int diff_recursive, |
1001 | usersolver_t const *usersolvers, void *ctx, | 1014 | usersolver_t const *usersolvers, validator_t valid, |
1002 | ctxnew_t ctxnew, ctxfree_t ctxfree) | 1015 | void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree) |
1003 | { | 1016 | { |
1004 | int diff; | 1017 | int diff; |
1005 | #ifdef STANDALONE_SOLVER | 1018 | #ifdef STANDALONE_SOLVER |
@@ -1027,7 +1040,7 @@ int latin_solver_main(struct latin_solver *solver, int maxdiff, | |||
1027 | diff = latin_solver_top(solver, maxdiff, | 1040 | diff = latin_solver_top(solver, maxdiff, |
1028 | diff_simple, diff_set_0, diff_set_1, | 1041 | diff_simple, diff_set_0, diff_set_1, |
1029 | diff_forcing, diff_recursive, | 1042 | diff_forcing, diff_recursive, |
1030 | usersolvers, ctx, ctxnew, ctxfree); | 1043 | usersolvers, valid, ctx, ctxnew, ctxfree); |
1031 | 1044 | ||
1032 | #ifdef STANDALONE_SOLVER | 1045 | #ifdef STANDALONE_SOLVER |
1033 | sfree(names); | 1046 | sfree(names); |
@@ -1040,8 +1053,8 @@ int latin_solver_main(struct latin_solver *solver, int maxdiff, | |||
1040 | int latin_solver(digit *grid, int o, int maxdiff, | 1053 | int latin_solver(digit *grid, int o, int maxdiff, |
1041 | int diff_simple, int diff_set_0, int diff_set_1, | 1054 | int diff_simple, int diff_set_0, int diff_set_1, |
1042 | int diff_forcing, int diff_recursive, | 1055 | int diff_forcing, int diff_recursive, |
1043 | usersolver_t const *usersolvers, void *ctx, | 1056 | usersolver_t const *usersolvers, validator_t valid, |
1044 | ctxnew_t ctxnew, ctxfree_t ctxfree) | 1057 | void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree) |
1045 | { | 1058 | { |
1046 | struct latin_solver solver; | 1059 | struct latin_solver solver; |
1047 | int diff; | 1060 | int diff; |
@@ -1050,7 +1063,7 @@ int latin_solver(digit *grid, int o, int maxdiff, | |||
1050 | diff = latin_solver_main(&solver, maxdiff, | 1063 | diff = latin_solver_main(&solver, maxdiff, |
1051 | diff_simple, diff_set_0, diff_set_1, | 1064 | diff_simple, diff_set_0, diff_set_1, |
1052 | diff_forcing, diff_recursive, | 1065 | diff_forcing, diff_recursive, |
1053 | usersolvers, ctx, ctxnew, ctxfree); | 1066 | usersolvers, valid, ctx, ctxnew, ctxfree); |
1054 | latin_solver_free(&solver); | 1067 | latin_solver_free(&solver); |
1055 | return diff; | 1068 | return diff; |
1056 | } | 1069 | } |
diff --git a/apps/plugins/puzzles/src/latin.h b/apps/plugins/puzzles/src/latin.h index ff6f07c922..bb172ec3c7 100644 --- a/apps/plugins/puzzles/src/latin.h +++ b/apps/plugins/puzzles/src/latin.h | |||
@@ -85,6 +85,7 @@ int latin_solver_diff_set(struct latin_solver *solver, | |||
85 | bool extreme); | 85 | bool extreme); |
86 | 86 | ||
87 | typedef int (*usersolver_t)(struct latin_solver *solver, void *ctx); | 87 | typedef int (*usersolver_t)(struct latin_solver *solver, void *ctx); |
88 | typedef bool (*validator_t)(struct latin_solver *solver, void *ctx); | ||
88 | typedef void *(*ctxnew_t)(void *ctx); | 89 | typedef void *(*ctxnew_t)(void *ctx); |
89 | typedef void (*ctxfree_t)(void *ctx); | 90 | typedef void (*ctxfree_t)(void *ctx); |
90 | 91 | ||
@@ -96,15 +97,15 @@ enum { diff_impossible = 10, diff_ambiguous, diff_unfinished }; | |||
96 | int latin_solver(digit *grid, int o, int maxdiff, | 97 | int latin_solver(digit *grid, int o, int maxdiff, |
97 | int diff_simple, int diff_set_0, int diff_set_1, | 98 | int diff_simple, int diff_set_0, int diff_set_1, |
98 | int diff_forcing, int diff_recursive, | 99 | int diff_forcing, int diff_recursive, |
99 | usersolver_t const *usersolvers, void *ctx, | 100 | usersolver_t const *usersolvers, validator_t valid, |
100 | ctxnew_t ctxnew, ctxfree_t ctxfree); | 101 | void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree); |
101 | 102 | ||
102 | /* Version you can call if you want to alloc and free latin_solver yourself */ | 103 | /* Version you can call if you want to alloc and free latin_solver yourself */ |
103 | int latin_solver_main(struct latin_solver *solver, int maxdiff, | 104 | int latin_solver_main(struct latin_solver *solver, int maxdiff, |
104 | int diff_simple, int diff_set_0, int diff_set_1, | 105 | int diff_simple, int diff_set_0, int diff_set_1, |
105 | int diff_forcing, int diff_recursive, | 106 | int diff_forcing, int diff_recursive, |
106 | usersolver_t const *usersolvers, void *ctx, | 107 | usersolver_t const *usersolvers, validator_t valid, |
107 | ctxnew_t ctxnew, ctxfree_t ctxfree); | 108 | void *ctx, ctxnew_t ctxnew, ctxfree_t ctxfree); |
108 | 109 | ||
109 | void latin_solver_debug(unsigned char *cube, int o); | 110 | void latin_solver_debug(unsigned char *cube, int o); |
110 | 111 | ||
diff --git a/apps/plugins/puzzles/src/mines.c b/apps/plugins/puzzles/src/mines.c index c9d9852573..ae717d3f37 100644 --- a/apps/plugins/puzzles/src/mines.c +++ b/apps/plugins/puzzles/src/mines.c | |||
@@ -258,6 +258,8 @@ static const char *validate_params(const game_params *params, bool full) | |||
258 | */ | 258 | */ |
259 | if (full && params->unique && (params->w <= 2 || params->h <= 2)) | 259 | if (full && params->unique && (params->w <= 2 || params->h <= 2)) |
260 | return "Width and height must both be greater than two"; | 260 | return "Width and height must both be greater than two"; |
261 | if (params->n < 0) | ||
262 | return "Mine count may not be negative"; | ||
261 | if (params->n > params->w * params->h - 9) | 263 | if (params->n > params->w * params->h - 9) |
262 | return "Too many mines for grid size"; | 264 | return "Too many mines for grid size"; |
263 | if (params->n < 1) | 265 | if (params->n < 1) |
diff --git a/apps/plugins/puzzles/src/pattern.c b/apps/plugins/puzzles/src/pattern.c index 42f3fc55ce..a43982f452 100644 --- a/apps/plugins/puzzles/src/pattern.c +++ b/apps/plugins/puzzles/src/pattern.c | |||
@@ -20,6 +20,7 @@ enum { | |||
20 | COL_GRID, | 20 | COL_GRID, |
21 | COL_CURSOR, | 21 | COL_CURSOR, |
22 | COL_ERROR, | 22 | COL_ERROR, |
23 | COL_CURSOR_GUIDE, | ||
23 | NCOLOURS | 24 | NCOLOURS |
24 | }; | 25 | }; |
25 | 26 | ||
@@ -1660,11 +1661,12 @@ static float *game_colours(frontend *fe, int *ncolours) | |||
1660 | frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); | 1661 | frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); |
1661 | 1662 | ||
1662 | for (i = 0; i < 3; i++) { | 1663 | for (i = 0; i < 3; i++) { |
1663 | ret[COL_GRID * 3 + i] = 0.3F; | 1664 | ret[COL_GRID * 3 + i] = 0.3F; |
1664 | ret[COL_UNKNOWN * 3 + i] = 0.5F; | 1665 | ret[COL_UNKNOWN * 3 + i] = 0.5F; |
1665 | ret[COL_TEXT * 3 + i] = 0.0F; | 1666 | ret[COL_TEXT * 3 + i] = 0.0F; |
1666 | ret[COL_FULL * 3 + i] = 0.0F; | 1667 | ret[COL_FULL * 3 + i] = 0.0F; |
1667 | ret[COL_EMPTY * 3 + i] = 1.0F; | 1668 | ret[COL_EMPTY * 3 + i] = 1.0F; |
1669 | ret[COL_CURSOR_GUIDE * 3 + i] = 0.5F; | ||
1668 | } | 1670 | } |
1669 | ret[COL_CURSOR * 3 + 0] = 1.0F; | 1671 | ret[COL_CURSOR * 3 + 0] = 1.0F; |
1670 | ret[COL_CURSOR * 3 + 1] = 0.25F; | 1672 | ret[COL_CURSOR * 3 + 1] = 0.25F; |
@@ -1891,6 +1893,9 @@ static void game_redraw(drawing *dr, game_drawstate *ds, | |||
1891 | */ | 1893 | */ |
1892 | for (i = 0; i < state->common->w + state->common->h; i++) { | 1894 | for (i = 0; i < state->common->w + state->common->h; i++) { |
1893 | int colour = check_errors(state, i) ? COL_ERROR : COL_TEXT; | 1895 | int colour = check_errors(state, i) ? COL_ERROR : COL_TEXT; |
1896 | if (colour == COL_TEXT && ((cx >= 0 && i == cx) || (cy >= 0 && i == cy + ds->w))) { | ||
1897 | colour = COL_CURSOR_GUIDE; | ||
1898 | } | ||
1894 | if (ds->numcolours[i] != colour) { | 1899 | if (ds->numcolours[i] != colour) { |
1895 | draw_numbers(dr, ds, state, i, true, colour); | 1900 | draw_numbers(dr, ds, state, i, true, colour); |
1896 | ds->numcolours[i] = colour; | 1901 | ds->numcolours[i] = colour; |
diff --git a/apps/plugins/puzzles/src/printing.c b/apps/plugins/puzzles/src/printing.c index 98fdd841d3..d1a3badaaa 100644 --- a/apps/plugins/puzzles/src/printing.c +++ b/apps/plugins/puzzles/src/printing.c | |||
@@ -3,6 +3,8 @@ | |||
3 | * setup and layout. | 3 | * setup and layout. |
4 | */ | 4 | */ |
5 | 5 | ||
6 | #include <assert.h> | ||
7 | |||
6 | #include "puzzles.h" | 8 | #include "puzzles.h" |
7 | 9 | ||
8 | struct puzzle { | 10 | struct puzzle { |
@@ -88,7 +90,7 @@ void document_add_puzzle(document *doc, const game *game, game_params *par, | |||
88 | doc->got_solns = true; | 90 | doc->got_solns = true; |
89 | } | 91 | } |
90 | 92 | ||
91 | static void get_puzzle_size(document *doc, struct puzzle *pz, | 93 | static void get_puzzle_size(const document *doc, struct puzzle *pz, |
92 | float *w, float *h, float *scale) | 94 | float *w, float *h, float *scale) |
93 | { | 95 | { |
94 | float ww, hh, ourscale; | 96 | float ww, hh, ourscale; |
@@ -115,133 +117,177 @@ static void get_puzzle_size(document *doc, struct puzzle *pz, | |||
115 | } | 117 | } |
116 | 118 | ||
117 | /* | 119 | /* |
118 | * Having accumulated a load of puzzles, actually do the printing. | 120 | * Calculate the the number of pages for a document. |
119 | */ | 121 | */ |
120 | void document_print(document *doc, drawing *dr) | 122 | int document_npages(const document *doc) |
121 | { | 123 | { |
122 | int ppp; /* puzzles per page */ | 124 | int ppp; /* puzzles per page */ |
123 | int pages, passes; | 125 | int pages, passes; |
126 | |||
127 | ppp = doc->pw * doc->ph; | ||
128 | pages = (doc->npuzzles + ppp - 1) / ppp; | ||
129 | passes = (doc->got_solns ? 2 : 1); | ||
130 | |||
131 | return pages * passes; | ||
132 | } | ||
133 | |||
134 | /* | ||
135 | * Begin a document. | ||
136 | */ | ||
137 | void document_begin(const document *doc, drawing *dr) | ||
138 | { | ||
139 | print_begin_doc(dr, document_npages(doc)); | ||
140 | } | ||
141 | |||
142 | /* | ||
143 | * End a document. | ||
144 | */ | ||
145 | void document_end(const document *doc, drawing *dr) | ||
146 | { | ||
147 | print_end_doc(dr); | ||
148 | } | ||
149 | |||
150 | /* | ||
151 | * Print a single page of a document. | ||
152 | */ | ||
153 | void document_print_page(const document *doc, drawing *dr, int page_nr) | ||
154 | { | ||
155 | int ppp; /* puzzles per page */ | ||
156 | int pages; | ||
124 | int page, pass; | 157 | int page, pass; |
125 | int pageno; | 158 | int pageno; |
159 | int i, n, offset; | ||
160 | float colsum, rowsum; | ||
126 | 161 | ||
127 | ppp = doc->pw * doc->ph; | 162 | ppp = doc->pw * doc->ph; |
128 | pages = (doc->npuzzles + ppp - 1) / ppp; | 163 | pages = (doc->npuzzles + ppp - 1) / ppp; |
129 | passes = (doc->got_solns ? 2 : 1); | ||
130 | 164 | ||
131 | print_begin_doc(dr, pages * passes); | 165 | /* Get the current page, pass, and pageno based on page_nr. */ |
132 | 166 | if (page_nr < pages) { | |
133 | pageno = 1; | 167 | page = page_nr; |
134 | for (pass = 0; pass < passes; pass++) { | 168 | pass = 0; |
135 | for (page = 0; page < pages; page++) { | 169 | } |
136 | int i, n, offset; | 170 | else { |
137 | float colsum, rowsum; | 171 | assert(doc->got_solns); |
138 | 172 | page = page_nr - pages; | |
139 | print_begin_page(dr, pageno); | 173 | pass = 1; |
140 | 174 | } | |
141 | offset = page * ppp; | 175 | pageno = page_nr + 1; |
142 | n = min(ppp, doc->npuzzles - offset); | 176 | |
143 | 177 | offset = page * ppp; | |
144 | for (i = 0; i < doc->pw; i++) | 178 | n = min(ppp, doc->npuzzles - offset); |
145 | doc->colwid[i] = 0; | 179 | |
146 | for (i = 0; i < doc->ph; i++) | 180 | print_begin_page(dr, pageno); |
147 | doc->rowht[i] = 0; | 181 | |
148 | 182 | for (i = 0; i < doc->pw; i++) | |
149 | /* | 183 | doc->colwid[i] = 0; |
150 | * Lay the page out by computing all the puzzle sizes. | 184 | for (i = 0; i < doc->ph; i++) |
151 | */ | 185 | doc->rowht[i] = 0; |
152 | for (i = 0; i < n; i++) { | 186 | |
153 | struct puzzle *pz = doc->puzzles + offset + i; | 187 | /* |
154 | int x = i % doc->pw, y = i / doc->pw; | 188 | * Lay the page out by computing all the puzzle sizes. |
155 | float w, h, scale; | 189 | */ |
156 | 190 | for (i = 0; i < n; i++) { | |
157 | get_puzzle_size(doc, pz, &w, &h, &scale); | 191 | struct puzzle *pz = doc->puzzles + offset + i; |
158 | 192 | int x = i % doc->pw, y = i / doc->pw; | |
159 | /* Update the maximum width/height of this column. */ | 193 | float w, h, scale; |
160 | doc->colwid[x] = max(doc->colwid[x], w); | 194 | |
161 | doc->rowht[y] = max(doc->rowht[y], h); | 195 | get_puzzle_size(doc, pz, &w, &h, &scale); |
162 | } | 196 | |
163 | 197 | /* Update the maximum width/height of this column. */ | |
164 | /* | 198 | doc->colwid[x] = max(doc->colwid[x], w); |
165 | * Add up the maximum column/row widths to get the | 199 | doc->rowht[y] = max(doc->rowht[y], h); |
166 | * total amount of space used up by puzzles on the | 200 | } |
167 | * page. We will use this to compute gutter widths. | 201 | |
168 | */ | 202 | /* |
169 | colsum = 0.0; | 203 | * Add up the maximum column/row widths to get the |
170 | for (i = 0; i < doc->pw; i++) | 204 | * total amount of space used up by puzzles on the |
171 | colsum += doc->colwid[i]; | 205 | * page. We will use this to compute gutter widths. |
172 | rowsum = 0.0; | 206 | */ |
173 | for (i = 0; i < doc->ph; i++) | 207 | colsum = 0.0; |
174 | rowsum += doc->rowht[i]; | 208 | for (i = 0; i < doc->pw; i++) |
175 | 209 | colsum += doc->colwid[i]; | |
176 | /* | 210 | rowsum = 0.0; |
177 | * Now do the printing. | 211 | for (i = 0; i < doc->ph; i++) |
178 | */ | 212 | rowsum += doc->rowht[i]; |
179 | for (i = 0; i < n; i++) { | 213 | |
180 | struct puzzle *pz = doc->puzzles + offset + i; | 214 | /* |
181 | int x = i % doc->pw, y = i / doc->pw, j; | 215 | * Now do the printing. |
182 | float w, h, scale, xm, xc, ym, yc; | 216 | */ |
183 | int pixw, pixh, tilesize; | 217 | for (i = 0; i < n; i++) { |
184 | 218 | struct puzzle *pz = doc->puzzles + offset + i; | |
185 | if (pass == 1 && !pz->st2) | 219 | int x = i % doc->pw, y = i / doc->pw, j; |
186 | continue; /* nothing to do */ | 220 | float w, h, scale, xm, xc, ym, yc; |
187 | 221 | int pixw, pixh, tilesize; | |
188 | /* | 222 | |
189 | * The total amount of gutter space is the page | 223 | if (pass == 1 && !pz->st2) |
190 | * width minus colsum. This is divided into pw+1 | 224 | continue; /* nothing to do */ |
191 | * gutters, so the amount of horizontal gutter | 225 | |
192 | * space appearing to the left of this puzzle | 226 | /* |
193 | * column is | 227 | * The total amount of gutter space is the page |
194 | * | 228 | * width minus colsum. This is divided into pw+1 |
195 | * (width-colsum) * (x+1)/(pw+1) | 229 | * gutters, so the amount of horizontal gutter |
196 | * = width * (x+1)/(pw+1) - (colsum * (x+1)/(pw+1)) | 230 | * space appearing to the left of this puzzle |
197 | */ | 231 | * column is |
198 | xm = (float)(x+1) / (doc->pw + 1); | 232 | * |
199 | xc = -xm * colsum; | 233 | * (width-colsum) * (x+1)/(pw+1) |
200 | /* And similarly for y. */ | 234 | * = width * (x+1)/(pw+1) - (colsum * (x+1)/(pw+1)) |
201 | ym = (float)(y+1) / (doc->ph + 1); | 235 | */ |
202 | yc = -ym * rowsum; | 236 | xm = (float)(x+1) / (doc->pw + 1); |
203 | 237 | xc = -xm * colsum; | |
204 | /* | 238 | /* And similarly for y. */ |
205 | * However, the amount of space to the left of this | 239 | ym = (float)(y+1) / (doc->ph + 1); |
206 | * puzzle isn't just gutter space: we must also | 240 | yc = -ym * rowsum; |
207 | * count the widths of all the previous columns. | 241 | |
208 | */ | 242 | /* |
209 | for (j = 0; j < x; j++) | 243 | * However, the amount of space to the left of this |
210 | xc += doc->colwid[j]; | 244 | * puzzle isn't just gutter space: we must also |
211 | /* And similarly for rows. */ | 245 | * count the widths of all the previous columns. |
212 | for (j = 0; j < y; j++) | 246 | */ |
213 | yc += doc->rowht[j]; | 247 | for (j = 0; j < x; j++) |
214 | 248 | xc += doc->colwid[j]; | |
215 | /* | 249 | /* And similarly for rows. */ |
216 | * Now we adjust for this _specific_ puzzle, which | 250 | for (j = 0; j < y; j++) |
217 | * means centring it within the cell we've just | 251 | yc += doc->rowht[j]; |
218 | * computed. | 252 | |
219 | */ | 253 | /* |
220 | get_puzzle_size(doc, pz, &w, &h, &scale); | 254 | * Now we adjust for this _specific_ puzzle, which |
221 | xc += (doc->colwid[x] - w) / 2; | 255 | * means centring it within the cell we've just |
222 | yc += (doc->rowht[y] - h) / 2; | 256 | * computed. |
223 | 257 | */ | |
224 | /* | 258 | get_puzzle_size(doc, pz, &w, &h, &scale); |
225 | * And now we know where and how big we want to | 259 | xc += (doc->colwid[x] - w) / 2; |
226 | * print the puzzle, just go ahead and do so. For | 260 | yc += (doc->rowht[y] - h) / 2; |
227 | * the moment I'll pick a standard pixel tile size | 261 | |
228 | * of 512. | 262 | /* |
229 | * | 263 | * And now we know where and how big we want to |
230 | * (FIXME: would it be better to pick this value | 264 | * print the puzzle, just go ahead and do so. For |
231 | * with reference to the printer resolution? Or | 265 | * the moment I'll pick a standard pixel tile size |
232 | * permit each game to choose its own?) | 266 | * of 512. |
233 | */ | 267 | * |
234 | tilesize = 512; | 268 | * (FIXME: would it be better to pick this value |
235 | pz->game->compute_size(pz->par, tilesize, &pixw, &pixh); | 269 | * with reference to the printer resolution? Or |
236 | print_begin_puzzle(dr, xm, xc, ym, yc, pixw, pixh, w, scale); | 270 | * permit each game to choose its own?) |
237 | pz->game->print(dr, pass == 0 ? pz->st : pz->st2, tilesize); | 271 | */ |
238 | print_end_puzzle(dr); | 272 | tilesize = 512; |
239 | } | 273 | pz->game->compute_size(pz->par, tilesize, &pixw, &pixh); |
240 | 274 | print_begin_puzzle(dr, xm, xc, ym, yc, pixw, pixh, w, scale); | |
241 | print_end_page(dr, pageno); | 275 | pz->game->print(dr, pass == 0 ? pz->st : pz->st2, tilesize); |
242 | pageno++; | 276 | print_end_puzzle(dr); |
243 | } | ||
244 | } | 277 | } |
245 | 278 | ||
279 | print_end_page(dr, pageno); | ||
280 | } | ||
281 | |||
282 | /* | ||
283 | * Having accumulated a load of puzzles, actually do the printing. | ||
284 | */ | ||
285 | void document_print(const document *doc, drawing *dr) | ||
286 | { | ||
287 | int page, pages; | ||
288 | pages = document_npages(doc); | ||
289 | print_begin_doc(dr, pages); | ||
290 | for (page = 0; page < pages; page++) | ||
291 | document_print_page(doc, dr, page); | ||
246 | print_end_doc(dr); | 292 | print_end_doc(dr); |
247 | } | 293 | } |
diff --git a/apps/plugins/puzzles/src/ps.c b/apps/plugins/puzzles/src/ps.c index 94a708648a..ab8a1589f4 100644 --- a/apps/plugins/puzzles/src/ps.c +++ b/apps/plugins/puzzles/src/ps.c | |||
@@ -9,8 +9,6 @@ | |||
9 | 9 | ||
10 | #include "puzzles.h" | 10 | #include "puzzles.h" |
11 | 11 | ||
12 | #define ROOT2 1.414213562 | ||
13 | |||
14 | struct psdata { | 12 | struct psdata { |
15 | FILE *fp; | 13 | FILE *fp; |
16 | bool colour; | 14 | bool colour; |
diff --git a/apps/plugins/puzzles/src/puzzles.but b/apps/plugins/puzzles/src/puzzles.but index e22e63d544..ee519b8aa1 100644 --- a/apps/plugins/puzzles/src/puzzles.but +++ b/apps/plugins/puzzles/src/puzzles.but | |||
@@ -3360,7 +3360,8 @@ This software is \i{copyright} 2004-2014 Simon Tatham. | |||
3360 | 3360 | ||
3361 | Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas | 3361 | Portions copyright Richard Boulton, James Harvey, Mike Pinna, Jonas |
3362 | K\u00F6{oe}lker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, | 3362 | K\u00F6{oe}lker, Dariusz Olszewski, Michael Schierl, Lambros Lambrou, |
3363 | Bernd Schmidt, Steffen Bauer, Lennard Sprong and Rogier Goossens. | 3363 | Bernd Schmidt, Steffen Bauer, Lennard Sprong, Rogier Goossens, Michael |
3364 | Quevillon and Asher Gordon. | ||
3364 | 3365 | ||
3365 | Permission is hereby granted, free of charge, to any person | 3366 | Permission is hereby granted, free of charge, to any person |
3366 | obtaining a copy of this software and associated documentation files | 3367 | obtaining a copy of this software and associated documentation files |
diff --git a/apps/plugins/puzzles/src/puzzles.h b/apps/plugins/puzzles/src/puzzles.h index 1732abe3e9..45ae321cc6 100644 --- a/apps/plugins/puzzles/src/puzzles.h +++ b/apps/plugins/puzzles/src/puzzles.h | |||
@@ -11,6 +11,7 @@ | |||
11 | #include <stdbool.h> | 11 | #include <stdbool.h> |
12 | 12 | ||
13 | #define PI 3.141592653589793238462643383279502884197169399 | 13 | #define PI 3.141592653589793238462643383279502884197169399 |
14 | #define ROOT2 1.414213562373095048801688724209698078569672 | ||
14 | 15 | ||
15 | #define lenof(array) ( sizeof(array) / sizeof(*(array)) ) | 16 | #define lenof(array) ( sizeof(array) / sizeof(*(array)) ) |
16 | 17 | ||
@@ -530,7 +531,11 @@ document *document_new(int pw, int ph, float userscale); | |||
530 | void document_free(document *doc); | 531 | void document_free(document *doc); |
531 | void document_add_puzzle(document *doc, const game *game, game_params *par, | 532 | void document_add_puzzle(document *doc, const game *game, game_params *par, |
532 | game_state *st, game_state *st2); | 533 | game_state *st, game_state *st2); |
533 | void document_print(document *doc, drawing *dr); | 534 | int document_npages(const document *doc); |
535 | void document_begin(const document *doc, drawing *dr); | ||
536 | void document_end(const document *doc, drawing *dr); | ||
537 | void document_print_page(const document *doc, drawing *dr, int page_nr); | ||
538 | void document_print(const document *doc, drawing *dr); | ||
534 | 539 | ||
535 | /* | 540 | /* |
536 | * ps.c | 541 | * ps.c |
diff --git a/apps/plugins/puzzles/src/towers.c b/apps/plugins/puzzles/src/towers.c index a72cae680d..aee088fb54 100644 --- a/apps/plugins/puzzles/src/towers.c +++ b/apps/plugins/puzzles/src/towers.c | |||
@@ -574,6 +574,38 @@ static int solver_hard(struct latin_solver *solver, void *vctx) | |||
574 | #define SOLVER(upper,title,func,lower) func, | 574 | #define SOLVER(upper,title,func,lower) func, |
575 | static usersolver_t const towers_solvers[] = { DIFFLIST(SOLVER) }; | 575 | static usersolver_t const towers_solvers[] = { DIFFLIST(SOLVER) }; |
576 | 576 | ||
577 | static bool towers_valid(struct latin_solver *solver, void *vctx) | ||
578 | { | ||
579 | struct solver_ctx *ctx = (struct solver_ctx *)vctx; | ||
580 | int w = ctx->w; | ||
581 | int c, i, n, best, clue, start, step; | ||
582 | for (c = 0; c < 4*w; c++) { | ||
583 | clue = ctx->clues[c]; | ||
584 | if (!clue) | ||
585 | continue; | ||
586 | |||
587 | STARTSTEP(start, step, c, w); | ||
588 | n = best = 0; | ||
589 | for (i = 0; i < w; i++) { | ||
590 | if (solver->grid[start+i*step] > best) { | ||
591 | best = solver->grid[start+i*step]; | ||
592 | n++; | ||
593 | } | ||
594 | } | ||
595 | |||
596 | if (n != clue) { | ||
597 | #ifdef STANDALONE_SOLVER | ||
598 | if (solver_show_working) | ||
599 | printf("%*sclue %s %d is violated\n", | ||
600 | solver_recurse_depth*4, "", | ||
601 | cluepos[c/w], c%w+1); | ||
602 | #endif | ||
603 | return false; | ||
604 | } | ||
605 | } | ||
606 | return true; | ||
607 | } | ||
608 | |||
577 | static int solver(int w, int *clues, digit *soln, int maxdiff) | 609 | static int solver(int w, int *clues, digit *soln, int maxdiff) |
578 | { | 610 | { |
579 | int ret; | 611 | int ret; |
@@ -589,7 +621,7 @@ static int solver(int w, int *clues, digit *soln, int maxdiff) | |||
589 | ret = latin_solver(soln, w, maxdiff, | 621 | ret = latin_solver(soln, w, maxdiff, |
590 | DIFF_EASY, DIFF_HARD, DIFF_EXTREME, | 622 | DIFF_EASY, DIFF_HARD, DIFF_EXTREME, |
591 | DIFF_EXTREME, DIFF_UNREASONABLE, | 623 | DIFF_EXTREME, DIFF_UNREASONABLE, |
592 | towers_solvers, &ctx, NULL, NULL); | 624 | towers_solvers, towers_valid, &ctx, NULL, NULL); |
593 | 625 | ||
594 | sfree(ctx.iscratch); | 626 | sfree(ctx.iscratch); |
595 | sfree(ctx.dscratch); | 627 | sfree(ctx.dscratch); |
diff --git a/apps/plugins/puzzles/src/tracks.R b/apps/plugins/puzzles/src/tracks.R index f88dfb03eb..8b0ac97e0f 100644 --- a/apps/plugins/puzzles/src/tracks.R +++ b/apps/plugins/puzzles/src/tracks.R | |||
@@ -8,6 +8,9 @@ tracks : [G] WINDOWS COMMON tracks TRACKS_EXTRA tracks.res|noicon.res | |||
8 | 8 | ||
9 | ALL += tracks[COMBINED] TRACKS_EXTRA | 9 | ALL += tracks[COMBINED] TRACKS_EXTRA |
10 | 10 | ||
11 | trackssolver : [U] tracks[STANDALONE_SOLVER] TRACKS_EXTRA STANDALONE | ||
12 | trackssolver : [C] tracks[STANDALONE_SOLVER] TRACKS_EXTRA STANDALONE | ||
13 | |||
11 | !begin am gtk | 14 | !begin am gtk |
12 | GAMES += tracks | 15 | GAMES += tracks |
13 | !end | 16 | !end |
diff --git a/apps/plugins/puzzles/src/tracks.c b/apps/plugins/puzzles/src/tracks.c index 8f29faa0fd..924836afa9 100644 --- a/apps/plugins/puzzles/src/tracks.c +++ b/apps/plugins/puzzles/src/tracks.c | |||
@@ -12,6 +12,7 @@ | |||
12 | * #113 8x8:gCx5xAf,1,S4,2,5,4,6,2,3,4,2,5,2,S4,4,5,1 | 12 | * #113 8x8:gCx5xAf,1,S4,2,5,4,6,2,3,4,2,5,2,S4,4,5,1 |
13 | * #114 8x8:p5fAzkAb,1,6,3,3,3,S6,2,3,5,4,S3,3,5,1,5,1 | 13 | * #114 8x8:p5fAzkAb,1,6,3,3,3,S6,2,3,5,4,S3,3,5,1,5,1 |
14 | * #115 8x8:zi9d5tAb,1,3,4,5,3,S4,2,4,2,6,2,3,6,S3,3,1 | 14 | * #115 8x8:zi9d5tAb,1,3,4,5,3,S4,2,4,2,6,2,3,6,S3,3,1 |
15 | * #942 8x8:n5iCfAzAe,2,2,S5,5,3,5,4,5,4,5,2,S5,3,4,5,3 | ||
15 | */ | 16 | */ |
16 | 17 | ||
17 | #include <stdio.h> | 18 | #include <stdio.h> |
@@ -29,9 +30,11 @@ | |||
29 | * Difficulty levels. I do some macro ickery here to ensure that my | 30 | * Difficulty levels. I do some macro ickery here to ensure that my |
30 | * enum and the various forms of my name list always match up. | 31 | * enum and the various forms of my name list always match up. |
31 | */ | 32 | */ |
32 | #define DIFFLIST(A) \ | 33 | #define DIFFLIST(A) \ |
33 | A(EASY,Easy,e) \ | 34 | A(EASY,Easy,e) \ |
34 | A(TRICKY,Tricky,t) | 35 | A(TRICKY,Tricky,t) \ |
36 | A(HARD,Hard,h) \ | ||
37 | /* end of list */ | ||
35 | 38 | ||
36 | #define ENUM(upper,title,lower) DIFF_ ## upper, | 39 | #define ENUM(upper,title,lower) DIFF_ ## upper, |
37 | #define TITLE(upper,title,lower) #title, | 40 | #define TITLE(upper,title,lower) #title, |
@@ -65,10 +68,12 @@ static const struct game_params tracks_presets[] = { | |||
65 | {10, 8, DIFF_TRICKY, 1 }, | 68 | {10, 8, DIFF_TRICKY, 1 }, |
66 | {10, 10, DIFF_EASY, 1}, | 69 | {10, 10, DIFF_EASY, 1}, |
67 | {10, 10, DIFF_TRICKY, 1}, | 70 | {10, 10, DIFF_TRICKY, 1}, |
71 | {10, 10, DIFF_HARD, 1}, | ||
68 | {15, 10, DIFF_EASY, 1}, | 72 | {15, 10, DIFF_EASY, 1}, |
69 | {15, 10, DIFF_TRICKY, 1}, | 73 | {15, 10, DIFF_TRICKY, 1}, |
70 | {15, 15, DIFF_EASY, 1}, | 74 | {15, 15, DIFF_EASY, 1}, |
71 | {15, 15, DIFF_TRICKY, 1}, | 75 | {15, 15, DIFF_TRICKY, 1}, |
76 | {15, 15, DIFF_HARD, 1}, | ||
72 | }; | 77 | }; |
73 | 78 | ||
74 | static bool game_fetch_preset(int i, char **name, game_params **params) | 79 | static bool game_fetch_preset(int i, char **name, game_params **params) |
@@ -452,7 +457,7 @@ start: | |||
452 | state->numbers->col_s = px; | 457 | state->numbers->col_s = px; |
453 | } | 458 | } |
454 | 459 | ||
455 | static int tracks_solve(game_state *state, int diff); | 460 | static int tracks_solve(game_state *state, int diff, int *max_diff_out); |
456 | static void debug_state(game_state *state, const char *what); | 461 | static void debug_state(game_state *state, const char *what); |
457 | 462 | ||
458 | /* Clue-setting algorithm: | 463 | /* Clue-setting algorithm: |
@@ -533,6 +538,26 @@ static game_state *copy_and_strip(const game_state *state, game_state *ret, int | |||
533 | return ret; | 538 | return ret; |
534 | } | 539 | } |
535 | 540 | ||
541 | #ifdef STANDALONE_SOLVER | ||
542 | #include <stdarg.h> | ||
543 | static FILE *solver_diagnostics_fp = NULL; | ||
544 | static void solver_diagnostic(const char *fmt, ...) | ||
545 | { | ||
546 | va_list ap; | ||
547 | va_start(ap, fmt); | ||
548 | vfprintf(solver_diagnostics_fp, fmt, ap); | ||
549 | va_end(ap); | ||
550 | fputc('\n', solver_diagnostics_fp); | ||
551 | } | ||
552 | #define solverdebug(printf_params) do { \ | ||
553 | if (solver_diagnostics_fp) { \ | ||
554 | solver_diagnostic printf_params; \ | ||
555 | } \ | ||
556 | } while (0) | ||
557 | #else | ||
558 | #define solverdebug(printf_params) ((void)0) | ||
559 | #endif | ||
560 | |||
536 | static int solve_progress(const game_state *state) { | 561 | static int solve_progress(const game_state *state) { |
537 | int i, w = state->p.w, h = state->p.h, progress = 0; | 562 | int i, w = state->p.w, h = state->p.h, progress = 0; |
538 | 563 | ||
@@ -575,6 +600,7 @@ static int add_clues(game_state *state, random_state *rs, int diff) | |||
575 | int *positions = snewn(w*h, int), npositions = 0; | 600 | int *positions = snewn(w*h, int), npositions = 0; |
576 | int *nedges_previous_solve = snewn(w*h, int); | 601 | int *nedges_previous_solve = snewn(w*h, int); |
577 | game_state *scratch = dup_game(state); | 602 | game_state *scratch = dup_game(state); |
603 | int diff_used; | ||
578 | 604 | ||
579 | debug_state(state, "gen: Initial board"); | 605 | debug_state(state, "gen: Initial board"); |
580 | 606 | ||
@@ -591,17 +617,13 @@ static int add_clues(game_state *state, random_state *rs, int diff) | |||
591 | 617 | ||
592 | /* First, check whether the puzzle is already either too easy, or just right */ | 618 | /* First, check whether the puzzle is already either too easy, or just right */ |
593 | scratch = copy_and_strip(state, scratch, -1); | 619 | scratch = copy_and_strip(state, scratch, -1); |
594 | if (diff > 0) { | 620 | sr = tracks_solve(scratch, diff, &diff_used); |
595 | sr = tracks_solve(scratch, diff-1); | 621 | if (diff_used < diff) { |
596 | if (sr < 0) | 622 | ret = -1; /* already too easy, even without adding clues. */ |
597 | assert(!"Generator should not have created impossible puzzle"); | 623 | debug(("gen: ...already too easy, need new board.")); |
598 | if (sr > 0) { | 624 | goto done; |
599 | ret = -1; /* already too easy, even without adding clues. */ | ||
600 | debug(("gen: ...already too easy, need new board.")); | ||
601 | goto done; | ||
602 | } | ||
603 | } | 625 | } |
604 | sr = tracks_solve(scratch, diff); | 626 | |
605 | if (sr < 0) | 627 | if (sr < 0) |
606 | assert(!"Generator should not have created impossible puzzle"); | 628 | assert(!"Generator should not have created impossible puzzle"); |
607 | if (sr > 0) { | 629 | if (sr > 0) { |
@@ -629,12 +651,10 @@ static int add_clues(game_state *state, random_state *rs, int diff) | |||
629 | if (check_phantom_moves(scratch)) | 651 | if (check_phantom_moves(scratch)) |
630 | continue; /* adding a clue here would add phantom track */ | 652 | continue; /* adding a clue here would add phantom track */ |
631 | 653 | ||
632 | if (diff > 0) { | 654 | if (tracks_solve(scratch, diff, &diff_used) > 0) { |
633 | if (tracks_solve(scratch, diff-1) > 0) { | 655 | if (diff_used < diff) { |
634 | continue; /* adding a clue here makes it too easy */ | 656 | continue; /* adding a clue here makes it too easy */ |
635 | } | 657 | } |
636 | } | ||
637 | if (tracks_solve(scratch, diff) > 0) { | ||
638 | /* we're now soluble (and we weren't before): add this clue, and then | 658 | /* we're now soluble (and we weren't before): add this clue, and then |
639 | start stripping clues */ | 659 | start stripping clues */ |
640 | debug(("gen: ...adding clue at (%d,%d), now soluble", i%w, i/w)); | 660 | debug(("gen: ...adding clue at (%d,%d), now soluble", i%w, i/w)); |
@@ -676,7 +696,7 @@ strip_clues: | |||
676 | if (check_phantom_moves(scratch)) | 696 | if (check_phantom_moves(scratch)) |
677 | continue; /* removing a clue here would add phantom track */ | 697 | continue; /* removing a clue here would add phantom track */ |
678 | 698 | ||
679 | if (tracks_solve(scratch, diff) > 0) { | 699 | if (tracks_solve(scratch, diff, NULL) > 0) { |
680 | debug(("gen: ... removing clue at (%d,%d), still soluble without it", i%w, i/w)); | 700 | debug(("gen: ... removing clue at (%d,%d), still soluble without it", i%w, i/w)); |
681 | state->sflags[i] &= ~S_CLUE; /* still soluble without this clue. */ | 701 | state->sflags[i] &= ~S_CLUE; /* still soluble without this clue. */ |
682 | } | 702 | } |
@@ -686,6 +706,7 @@ strip_clues: | |||
686 | 706 | ||
687 | done: | 707 | done: |
688 | sfree(positions); | 708 | sfree(positions); |
709 | sfree(nedges_previous_solve); | ||
689 | free_game(scratch); | 710 | free_game(scratch); |
690 | return ret; | 711 | return ret; |
691 | } | 712 | } |
@@ -780,7 +801,7 @@ newpath: | |||
780 | } | 801 | } |
781 | *p++ = '\0'; | 802 | *p++ = '\0'; |
782 | 803 | ||
783 | ret = tracks_solve(state, DIFFCOUNT); | 804 | ret = tracks_solve(state, DIFFCOUNT, NULL); |
784 | assert(ret >= 0); | 805 | assert(ret >= 0); |
785 | free_game(state); | 806 | free_game(state); |
786 | 807 | ||
@@ -882,6 +903,10 @@ static game_state *new_game(midend *me, const game_params *params, const char *d | |||
882 | return state; | 903 | return state; |
883 | } | 904 | } |
884 | 905 | ||
906 | struct solver_scratch { | ||
907 | int *dsf; | ||
908 | }; | ||
909 | |||
885 | static int solve_set_sflag(game_state *state, int x, int y, | 910 | static int solve_set_sflag(game_state *state, int x, int y, |
886 | unsigned int f, const char *why) | 911 | unsigned int f, const char *why) |
887 | { | 912 | { |
@@ -889,10 +914,10 @@ static int solve_set_sflag(game_state *state, int x, int y, | |||
889 | 914 | ||
890 | if (state->sflags[i] & f) | 915 | if (state->sflags[i] & f) |
891 | return 0; | 916 | return 0; |
892 | debug(("solve: square (%d,%d) -> %s: %s", | 917 | solverdebug(("square (%d,%d) -> %s: %s", |
893 | x, y, (f == S_TRACK ? "TRACK" : "NOTRACK"), why)); | 918 | x, y, (f == S_TRACK ? "TRACK" : "NOTRACK"), why)); |
894 | if (state->sflags[i] & (f == S_TRACK ? S_NOTRACK : S_TRACK)) { | 919 | if (state->sflags[i] & (f == S_TRACK ? S_NOTRACK : S_TRACK)) { |
895 | debug(("solve: opposite flag already set there, marking IMPOSSIBLE")); | 920 | solverdebug(("opposite flag already set there, marking IMPOSSIBLE")); |
896 | state->impossible = true; | 921 | state->impossible = true; |
897 | } | 922 | } |
898 | state->sflags[i] |= f; | 923 | state->sflags[i] |= f; |
@@ -906,11 +931,11 @@ static int solve_set_eflag(game_state *state, int x, int y, int d, | |||
906 | 931 | ||
907 | if (sf & f) | 932 | if (sf & f) |
908 | return 0; | 933 | return 0; |
909 | debug(("solve: edge (%d,%d)/%c -> %s: %s", x, y, | 934 | solverdebug(("edge (%d,%d)/%c -> %s: %s", x, y, |
910 | (d == U) ? 'U' : (d == D) ? 'D' : (d == L) ? 'L' : 'R', | 935 | (d == U) ? 'U' : (d == D) ? 'D' : (d == L) ? 'L' : 'R', |
911 | (f == S_TRACK ? "TRACK" : "NOTRACK"), why)); | 936 | (f == S_TRACK ? "TRACK" : "NOTRACK"), why)); |
912 | if (sf & (f == E_TRACK ? E_NOTRACK : E_TRACK)) { | 937 | if (sf & (f == E_TRACK ? E_NOTRACK : E_TRACK)) { |
913 | debug(("solve: opposite flag already set there, marking IMPOSSIBLE")); | 938 | solverdebug(("opposite flag already set there, marking IMPOSSIBLE")); |
914 | state->impossible = true; | 939 | state->impossible = true; |
915 | } | 940 | } |
916 | S_E_SET(state, x, y, d, f); | 941 | S_E_SET(state, x, y, d, f); |
@@ -1063,7 +1088,7 @@ static int solve_check_single_sub(game_state *state, int si, int id, int n, | |||
1063 | if (ctrack != (target-1)) return 0; | 1088 | if (ctrack != (target-1)) return 0; |
1064 | if (nperp > 0 || n1edge != 1) return 0; | 1089 | if (nperp > 0 || n1edge != 1) return 0; |
1065 | 1090 | ||
1066 | debug(("check_single from (%d,%d): 1 match from (%d,%d)", | 1091 | solverdebug(("check_single from (%d,%d): 1 match from (%d,%d)", |
1067 | si%w, si/w, i1edge%w, i1edge/w)); | 1092 | si%w, si/w, i1edge%w, i1edge/w)); |
1068 | 1093 | ||
1069 | /* We have a match: anything that's more than 1 away from this square | 1094 | /* We have a match: anything that's more than 1 away from this square |
@@ -1120,12 +1145,12 @@ static int solve_check_loose_sub(game_state *state, int si, int id, int n, | |||
1120 | } | 1145 | } |
1121 | 1146 | ||
1122 | if (nloose > (target - e2count)) { | 1147 | if (nloose > (target - e2count)) { |
1123 | debug(("check %s from (%d,%d): more loose (%d) than empty (%d), IMPOSSIBLE", | 1148 | solverdebug(("check %s from (%d,%d): more loose (%d) than empty (%d), IMPOSSIBLE", |
1124 | what, si%w, si/w, nloose, target-e2count)); | 1149 | what, si%w, si/w, nloose, target-e2count)); |
1125 | state->impossible = true; | 1150 | state->impossible = true; |
1126 | } | 1151 | } |
1127 | if (nloose > 0 && nloose == (target - e2count)) { | 1152 | if (nloose > 0 && nloose == (target - e2count)) { |
1128 | debug(("check %s from (%d,%d): nloose = empty (%d), forcing loners out.", | 1153 | solverdebug(("check %s from (%d,%d): nloose = empty (%d), forcing loners out.", |
1129 | what, si%w, si/w, nloose)); | 1154 | what, si%w, si/w, nloose)); |
1130 | for (j = 0, i = si; j < n; j++, i += id) { | 1155 | for (j = 0, i = si; j < n; j++, i += id) { |
1131 | if (!(state->sflags[i] & S_MARK)) | 1156 | if (!(state->sflags[i] & S_MARK)) |
@@ -1146,7 +1171,7 @@ static int solve_check_loose_sub(game_state *state, int si, int id, int n, | |||
1146 | } | 1171 | } |
1147 | } | 1172 | } |
1148 | if (nloose == 1 && (target - e2count) == 2 && nperp == 0) { | 1173 | if (nloose == 1 && (target - e2count) == 2 && nperp == 0) { |
1149 | debug(("check %s from (%d,%d): 1 loose end, 2 empty squares, forcing parallel", | 1174 | solverdebug(("check %s from (%d,%d): 1 loose end, 2 empty squares, forcing parallel", |
1150 | what, si%w, si/w)); | 1175 | what, si%w, si/w)); |
1151 | for (j = 0, i = si; j < n; j++, i += id) { | 1176 | for (j = 0, i = si; j < n; j++, i += id) { |
1152 | if (!(state->sflags[i] & S_MARK)) | 1177 | if (!(state->sflags[i] & S_MARK)) |
@@ -1176,6 +1201,110 @@ static int solve_check_loose_ends(game_state *state) | |||
1176 | return did; | 1201 | return did; |
1177 | } | 1202 | } |
1178 | 1203 | ||
1204 | static void solve_check_neighbours_count( | ||
1205 | game_state *state, int start, int step, int n, int clueindex, | ||
1206 | bool *onefill, bool *oneempty) | ||
1207 | { | ||
1208 | int to_fill = state->numbers->numbers[clueindex]; | ||
1209 | int to_empty = n - to_fill; | ||
1210 | int i; | ||
1211 | for (i = 0; i < n; i++) { | ||
1212 | int p = start + i*step; | ||
1213 | if (state->sflags[p] & S_TRACK) | ||
1214 | to_fill--; | ||
1215 | if (state->sflags[p] & S_NOTRACK) | ||
1216 | to_empty--; | ||
1217 | } | ||
1218 | *onefill = (to_fill == 1); | ||
1219 | *oneempty = (to_empty == 1); | ||
1220 | } | ||
1221 | |||
1222 | static int solve_check_neighbours_try(game_state *state, int x, int y, | ||
1223 | int X, int Y, bool onefill, | ||
1224 | bool oneempty, unsigned dir, | ||
1225 | const char *what) | ||
1226 | { | ||
1227 | int w = state->p.w, p = y*w+x, P = Y*w+X; | ||
1228 | |||
1229 | /* | ||
1230 | * We're given a neighbouring pair of squares p,P, with 'dir' | ||
1231 | * being the direction from the former to the latter. We aim to | ||
1232 | * spot situations in which, if p is a track square, then P must | ||
1233 | * also be one (because p doesn't have enough free exits to avoid | ||
1234 | * using the one that goes towards P). | ||
1235 | * | ||
1236 | * Then, if the target number of track squares on their shared | ||
1237 | * row/column says that there's only one track square left to | ||
1238 | * place, it can't be p, because P would have to be one too, | ||
1239 | * violating the clue. So in that situation we can mark p as | ||
1240 | * unfilled. Conversely, if there's only one _non_-track square | ||
1241 | * left to place, it can't be P, so we can mark P as filled. | ||
1242 | */ | ||
1243 | |||
1244 | if ((state->sflags[p] | state->sflags[P]) & (S_TRACK | S_NOTRACK)) | ||
1245 | return 0; /* no need: we already know something about these squares */ | ||
1246 | |||
1247 | int possible_exits_except_dir = nbits[ | ||
1248 | ALLDIR & ~dir & ~S_E_DIRS(state, x, y, E_NOTRACK)]; | ||
1249 | if (possible_exits_except_dir >= 2) | ||
1250 | return 0; /* square p need not connect to P, even if it is filled */ | ||
1251 | |||
1252 | /* OK, now we know that if p is filled, P must be filled too. */ | ||
1253 | |||
1254 | int did = 0; | ||
1255 | if (onefill) { | ||
1256 | /* But at most one of them can be filled, so it can't be p. */ | ||
1257 | state->sflags[p] |= S_NOTRACK; | ||
1258 | solverdebug(("square (%d,%d) -> NOTRACK: otherwise, that and (%d,%d) " | ||
1259 | "would make too many TRACK in %s", x, y, X, Y, what)); | ||
1260 | did++; | ||
1261 | } | ||
1262 | if (oneempty) { | ||
1263 | /* Alternatively, at least one of them _must_ be filled, so P | ||
1264 | * must be. */ | ||
1265 | state->sflags[P] |= S_TRACK; | ||
1266 | solverdebug(("square (%d,%d) -> TRACK: otherwise, that and (%d,%d) " | ||
1267 | "would make too many NOTRACK in %s", X, Y, x, y, what)); | ||
1268 | did++; | ||
1269 | } | ||
1270 | return did; | ||
1271 | } | ||
1272 | |||
1273 | static int solve_check_neighbours(game_state *state, bool both_ways) | ||
1274 | { | ||
1275 | int w = state->p.w, h = state->p.h, x, y, did = 0; | ||
1276 | bool onefill, oneempty; | ||
1277 | |||
1278 | for (x = 0; x < w; x++) { | ||
1279 | solve_check_neighbours_count(state, x, w, h, x, &onefill, &oneempty); | ||
1280 | if (!both_ways) | ||
1281 | oneempty = false; /* disable the harder version of the deduction */ | ||
1282 | if (!onefill && !oneempty) | ||
1283 | continue; | ||
1284 | for (y = 0; y+1 < h; y++) { | ||
1285 | did += solve_check_neighbours_try(state, x, y, x, y+1, | ||
1286 | onefill, oneempty, D, "column"); | ||
1287 | did += solve_check_neighbours_try(state, x, y+1, x, y, | ||
1288 | onefill, oneempty, U, "column"); | ||
1289 | } | ||
1290 | } | ||
1291 | for (y = 0; y < h; y++) { | ||
1292 | solve_check_neighbours_count(state, y*w, 1, w, w+y, | ||
1293 | &onefill, &oneempty); | ||
1294 | if (!both_ways) | ||
1295 | oneempty = false; /* disable the harder version of the deduction */ | ||
1296 | if (!onefill && !oneempty) | ||
1297 | continue; | ||
1298 | for (x = 0; x+1 < w; x++) { | ||
1299 | did += solve_check_neighbours_try(state, x, y, x+1, y, | ||
1300 | onefill, oneempty, R, "row"); | ||
1301 | did += solve_check_neighbours_try(state, x+1, y, x, y, | ||
1302 | onefill, oneempty, L, "row"); | ||
1303 | } | ||
1304 | } | ||
1305 | return did; | ||
1306 | } | ||
1307 | |||
1179 | static int solve_check_loop_sub(game_state *state, int x, int y, int dir, | 1308 | static int solve_check_loop_sub(game_state *state, int x, int y, int dir, |
1180 | int *dsf, int startc, int endc) | 1309 | int *dsf, int startc, int endc) |
1181 | { | 1310 | { |
@@ -1195,7 +1324,7 @@ static int solve_check_loop_sub(game_state *state, int x, int y, int dir, | |||
1195 | return solve_set_eflag(state, x, y, dir, E_NOTRACK, "would close loop"); | 1324 | return solve_set_eflag(state, x, y, dir, E_NOTRACK, "would close loop"); |
1196 | } | 1325 | } |
1197 | if ((ic == startc && jc == endc) || (ic == endc && jc == startc)) { | 1326 | if ((ic == startc && jc == endc) || (ic == endc && jc == startc)) { |
1198 | debug(("Adding link at (%d,%d) would join start to end", x, y)); | 1327 | solverdebug(("Adding link at (%d,%d) would join start to end", x, y)); |
1199 | /* We mustn't join the start to the end if: | 1328 | /* We mustn't join the start to the end if: |
1200 | - there are other bits of track that aren't attached to either end | 1329 | - there are other bits of track that aren't attached to either end |
1201 | - the clues are not fully satisfied yet | 1330 | - the clues are not fully satisfied yet |
@@ -1287,10 +1416,145 @@ static void solve_discount_edge(game_state *state, int x, int y, int d) | |||
1287 | solve_set_eflag(state, x, y, d, E_NOTRACK, "outer edge"); | 1416 | solve_set_eflag(state, x, y, d, E_NOTRACK, "outer edge"); |
1288 | } | 1417 | } |
1289 | 1418 | ||
1290 | static int tracks_solve(game_state *state, int diff) | 1419 | static int solve_bridge_sub(game_state *state, int x, int y, int d, |
1420 | struct solver_scratch *sc) | ||
1421 | { | ||
1422 | /* | ||
1423 | * Imagine a graph on the squares of the grid, with an edge | ||
1424 | * connecting neighbouring squares only if it's not yet known | ||
1425 | * whether there's a track between them. | ||
1426 | * | ||
1427 | * This function is called if the edge between x,y and X,Y is a | ||
1428 | * bridge in that graph: that is, it's not part of any loop in the | ||
1429 | * graph, or equivalently, removing it would increase the number | ||
1430 | * of connected components in the graph. | ||
1431 | * | ||
1432 | * In that situation, we can fill in the edge by a parity | ||
1433 | * argument. Construct a closed loop of edges in the grid, all of | ||
1434 | * whose states are known except this one. The track starts and | ||
1435 | * ends outside this loop, so it must cross the boundary of the | ||
1436 | * loop an even number of times. So if we count up how many times | ||
1437 | * the track is known to cross the edges of our loop, then we can | ||
1438 | * fill in the last edge in whichever way makes that number even. | ||
1439 | * | ||
1440 | * In fact, there's not even any need to go to the effort of | ||
1441 | * constructing a _single_ closed loop. The simplest thing is to | ||
1442 | * delete the bridge edge from the graph, find a connected | ||
1443 | * component of the reduced graph whose boundary includes that | ||
1444 | * edge, and take every edge separating that component from | ||
1445 | * another. This may not lead to _exactly one_ cycle - the | ||
1446 | * component could be non-simply connected and have a hole in the | ||
1447 | * middle - but that doesn't matter, because the same parity | ||
1448 | * constraint applies just as well with more than one disjoint | ||
1449 | * loop. | ||
1450 | */ | ||
1451 | int w = state->p.w, h = state->p.h, wh = w*h; | ||
1452 | int X = x + DX(d), Y = y + DY(d); | ||
1453 | int xi, yi, di; | ||
1454 | |||
1455 | assert(d == D || d == R); | ||
1456 | |||
1457 | if (!sc->dsf) | ||
1458 | sc->dsf = snew_dsf(wh); | ||
1459 | dsf_init(sc->dsf, wh); | ||
1460 | |||
1461 | for (xi = 0; xi < w; xi++) { | ||
1462 | for (yi = 0; yi < h; yi++) { | ||
1463 | /* We expect to have been called with X,Y either to the | ||
1464 | * right of x,y or below it, not the other way round. If | ||
1465 | * that were not true, the tests in this loop to exclude | ||
1466 | * the bridge edge would have to be twice as annoying. */ | ||
1467 | |||
1468 | if (yi+1 < h && !S_E_FLAGS(state, xi, yi, D) && | ||
1469 | !(xi == x && yi == y && xi == X && yi+1 == Y)) | ||
1470 | dsf_merge(sc->dsf, yi*w+xi, (yi+1)*w+xi); | ||
1471 | |||
1472 | if (xi+1 < w && !S_E_FLAGS(state, xi, yi, R) && | ||
1473 | !(xi == x && yi == y && xi+1 == X && yi == Y)) | ||
1474 | dsf_merge(sc->dsf, yi*w+xi, yi*w+(xi+1)); | ||
1475 | } | ||
1476 | } | ||
1477 | |||
1478 | int component = dsf_canonify(sc->dsf, y*w+x); | ||
1479 | int parity = 0; | ||
1480 | for (xi = 0; xi < w; xi++) { | ||
1481 | for (yi = 0; yi < h; yi++) { | ||
1482 | if (dsf_canonify(sc->dsf, yi*w+xi) != component) | ||
1483 | continue; | ||
1484 | for (di = 1; di < 16; di *= 2) { | ||
1485 | int Xi = xi + DX(di), Yi = yi + DY(di); | ||
1486 | if ((Xi < 0 || Xi >= w || Yi < 0 || Yi >= h || | ||
1487 | dsf_canonify(sc->dsf, Yi*w+Xi) != component) && | ||
1488 | (S_E_DIRS(state, xi, yi, E_TRACK) & di)) | ||
1489 | parity ^= 1; | ||
1490 | } | ||
1491 | } | ||
1492 | } | ||
1493 | |||
1494 | solve_set_eflag(state, x, y, d, parity ? E_TRACK : E_NOTRACK, "parity"); | ||
1495 | return 1; | ||
1496 | } | ||
1497 | |||
1498 | struct solve_bridge_neighbour_ctx { | ||
1499 | game_state *state; | ||
1500 | int x, y, dirs; | ||
1501 | }; | ||
1502 | static int solve_bridge_neighbour(int vertex, void *vctx) | ||
1503 | { | ||
1504 | struct solve_bridge_neighbour_ctx *ctx = | ||
1505 | (struct solve_bridge_neighbour_ctx *)vctx; | ||
1506 | int w = ctx->state->p.w; | ||
1507 | |||
1508 | if (vertex >= 0) { | ||
1509 | ctx->x = vertex % w; | ||
1510 | ctx->y = vertex / w; | ||
1511 | ctx->dirs = ALLDIR | ||
1512 | & ~S_E_DIRS(ctx->state, ctx->x, ctx->y, E_TRACK) | ||
1513 | & ~S_E_DIRS(ctx->state, ctx->x, ctx->y, E_NOTRACK); | ||
1514 | } | ||
1515 | unsigned dir = ctx->dirs & -ctx->dirs; /* isolate lowest set bit */ | ||
1516 | if (!dir) | ||
1517 | return -1; | ||
1518 | ctx->dirs &= ~dir; | ||
1519 | int xr = ctx->x + DX(dir), yr = ctx->y + DY(dir); | ||
1520 | assert(0 <= xr && xr < w); | ||
1521 | assert(0 <= yr && yr < ctx->state->p.h); | ||
1522 | return yr * w + xr; | ||
1523 | } | ||
1524 | |||
1525 | static int solve_check_bridge_parity(game_state *state, | ||
1526 | struct solver_scratch *sc) | ||
1527 | { | ||
1528 | int w = state->p.w, h = state->p.h, wh = w*h; | ||
1529 | struct findloopstate *fls; | ||
1530 | struct solve_bridge_neighbour_ctx ctx[1]; | ||
1531 | int x, y, did = 0; | ||
1532 | |||
1533 | ctx->state = state; | ||
1534 | fls = findloop_new_state(wh); | ||
1535 | findloop_run(fls, wh, solve_bridge_neighbour, ctx); | ||
1536 | |||
1537 | for (x = 0; x < w; x++) { | ||
1538 | for (y = 0; y < h; y++) { | ||
1539 | if (y+1 < h && !findloop_is_loop_edge(fls, y*w+x, (y+1)*w+x)) | ||
1540 | did += solve_bridge_sub(state, x, y, D, sc); | ||
1541 | if (x+1 < w && !findloop_is_loop_edge(fls, y*w+x, y*w+(x+1))) | ||
1542 | did += solve_bridge_sub(state, x, y, R, sc); | ||
1543 | } | ||
1544 | } | ||
1545 | |||
1546 | findloop_free_state(fls); | ||
1547 | |||
1548 | return did; | ||
1549 | } | ||
1550 | |||
1551 | static int tracks_solve(game_state *state, int diff, int *max_diff_out) | ||
1291 | { | 1552 | { |
1292 | int x, y, w = state->p.w, h = state->p.h; | 1553 | int x, y, w = state->p.w, h = state->p.h; |
1293 | bool didsth; | 1554 | struct solver_scratch sc[1]; |
1555 | int max_diff = DIFF_EASY; | ||
1556 | |||
1557 | sc->dsf = NULL; | ||
1294 | 1558 | ||
1295 | debug(("solve...")); | 1559 | debug(("solve...")); |
1296 | state->impossible = false; | 1560 | state->impossible = false; |
@@ -1305,21 +1569,37 @@ static int tracks_solve(game_state *state, int diff) | |||
1305 | solve_discount_edge(state, w-1, y, R); | 1569 | solve_discount_edge(state, w-1, y, R); |
1306 | } | 1570 | } |
1307 | 1571 | ||
1308 | while (1) { | 1572 | while (!state->impossible) { |
1309 | didsth = false; | ||
1310 | 1573 | ||
1311 | didsth |= solve_update_flags(state); | 1574 | /* Can't use do ... while (0) because we need a 'continue' in this macro */ |
1312 | didsth |= solve_count_clues(state); | 1575 | #define TRY(curr_diff, funcall) \ |
1313 | didsth |= solve_check_loop(state); | 1576 | if (diff >= (curr_diff) && (funcall)) { \ |
1577 | if (max_diff < curr_diff) \ | ||
1578 | max_diff = curr_diff; \ | ||
1579 | continue; \ | ||
1580 | } else ((void)0) | ||
1314 | 1581 | ||
1315 | if (diff >= DIFF_TRICKY) { | 1582 | TRY(DIFF_EASY, solve_update_flags(state)); |
1316 | didsth |= solve_check_single(state); | 1583 | TRY(DIFF_EASY, solve_count_clues(state)); |
1317 | didsth |= solve_check_loose_ends(state); | 1584 | TRY(DIFF_EASY, solve_check_loop(state)); |
1318 | } | ||
1319 | 1585 | ||
1320 | if (!didsth || state->impossible) break; | 1586 | TRY(DIFF_TRICKY, solve_check_single(state)); |
1587 | TRY(DIFF_TRICKY, solve_check_loose_ends(state)); | ||
1588 | TRY(DIFF_TRICKY, solve_check_neighbours(state, false)); | ||
1589 | |||
1590 | TRY(DIFF_HARD, solve_check_neighbours(state, true)); | ||
1591 | TRY(DIFF_HARD, solve_check_bridge_parity(state, sc)); | ||
1592 | |||
1593 | #undef TRY | ||
1594 | |||
1595 | break; | ||
1321 | } | 1596 | } |
1322 | 1597 | ||
1598 | sfree(sc->dsf); | ||
1599 | |||
1600 | if (max_diff_out) | ||
1601 | *max_diff_out = max_diff; | ||
1602 | |||
1323 | return state->impossible ? -1 : check_completion(state, false) ? 1 : 0; | 1603 | return state->impossible ? -1 : check_completion(state, false) ? 1 : 0; |
1324 | } | 1604 | } |
1325 | 1605 | ||
@@ -1379,11 +1659,11 @@ static char *solve_game(const game_state *state, const game_state *currstate, | |||
1379 | char *move; | 1659 | char *move; |
1380 | 1660 | ||
1381 | solved = dup_game(currstate); | 1661 | solved = dup_game(currstate); |
1382 | ret = tracks_solve(solved, DIFFCOUNT); | 1662 | ret = tracks_solve(solved, DIFFCOUNT, NULL); |
1383 | if (ret < 1) { | 1663 | if (ret < 1) { |
1384 | free_game(solved); | 1664 | free_game(solved); |
1385 | solved = dup_game(state); | 1665 | solved = dup_game(state); |
1386 | ret = tracks_solve(solved, DIFFCOUNT); | 1666 | ret = tracks_solve(solved, DIFFCOUNT, NULL); |
1387 | } | 1667 | } |
1388 | 1668 | ||
1389 | if (ret < 1) { | 1669 | if (ret < 1) { |
@@ -2094,7 +2374,7 @@ static game_state *execute_move(const game_state *state, const char *move) | |||
2094 | goto badmove; | 2374 | goto badmove; |
2095 | move += n; | 2375 | move += n; |
2096 | } else if (c == 'H') { | 2376 | } else if (c == 'H') { |
2097 | tracks_solve(ret, DIFFCOUNT); | 2377 | tracks_solve(ret, DIFFCOUNT, NULL); |
2098 | move++; | 2378 | move++; |
2099 | } else { | 2379 | } else { |
2100 | goto badmove; | 2380 | goto badmove; |
@@ -2675,4 +2955,87 @@ const struct game thegame = { | |||
2675 | 0, /* flags */ | 2955 | 0, /* flags */ |
2676 | }; | 2956 | }; |
2677 | 2957 | ||
2958 | #ifdef STANDALONE_SOLVER | ||
2959 | |||
2960 | int main(int argc, char **argv) | ||
2961 | { | ||
2962 | game_params *p; | ||
2963 | game_state *s; | ||
2964 | char *id = NULL, *desc; | ||
2965 | int maxdiff = DIFFCOUNT, diff_used; | ||
2966 | const char *err; | ||
2967 | bool diagnostics = false, grade = false; | ||
2968 | int retd; | ||
2969 | |||
2970 | while (--argc > 0) { | ||
2971 | char *p = *++argv; | ||
2972 | if (!strcmp(p, "-v")) { | ||
2973 | diagnostics = true; | ||
2974 | } else if (!strcmp(p, "-g")) { | ||
2975 | grade = true; | ||
2976 | } else if (!strncmp(p, "-d", 2) && p[2] && !p[3]) { | ||
2977 | int i; | ||
2978 | bool bad = true; | ||
2979 | for (i = 0; i < lenof(tracks_diffchars); i++) | ||
2980 | if (tracks_diffchars[i] == p[2]) { | ||
2981 | bad = false; | ||
2982 | maxdiff = i; | ||
2983 | break; | ||
2984 | } | ||
2985 | if (bad) { | ||
2986 | fprintf(stderr, "%s: unrecognised difficulty `%c'\n", | ||
2987 | argv[0], p[2]); | ||
2988 | return 1; | ||
2989 | } | ||
2990 | } else if (*p == '-') { | ||
2991 | fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); | ||
2992 | return 1; | ||
2993 | } else { | ||
2994 | id = p; | ||
2995 | } | ||
2996 | } | ||
2997 | |||
2998 | if (!id) { | ||
2999 | fprintf(stderr, "usage: %s [-v | -g] <game_id>\n", argv[0]); | ||
3000 | return 1; | ||
3001 | } | ||
3002 | |||
3003 | desc = strchr(id, ':'); | ||
3004 | if (!desc) { | ||
3005 | fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]); | ||
3006 | return 1; | ||
3007 | } | ||
3008 | *desc++ = '\0'; | ||
3009 | |||
3010 | p = default_params(); | ||
3011 | decode_params(p, id); | ||
3012 | err = validate_desc(p, desc); | ||
3013 | if (err) { | ||
3014 | fprintf(stderr, "%s: %s\n", argv[0], err); | ||
3015 | return 1; | ||
3016 | } | ||
3017 | s = new_game(NULL, p, desc); | ||
3018 | |||
3019 | solver_diagnostics_fp = (diagnostics ? stdout : NULL); | ||
3020 | retd = tracks_solve(s, maxdiff, &diff_used); | ||
3021 | if (retd < 0) { | ||
3022 | printf("Puzzle is inconsistent\n"); | ||
3023 | } else if (grade) { | ||
3024 | printf("Difficulty rating: %s\n", | ||
3025 | (retd == 0 ? "Ambiguous" : tracks_diffnames[diff_used])); | ||
3026 | } else { | ||
3027 | char *text = game_text_format(s); | ||
3028 | fputs(text, stdout); | ||
3029 | sfree(text); | ||
3030 | if (retd == 0) | ||
3031 | printf("Could not deduce a unique solution\n"); | ||
3032 | } | ||
3033 | free_game(s); | ||
3034 | free_params(p); | ||
3035 | |||
3036 | return 0; | ||
3037 | } | ||
3038 | |||
3039 | #endif | ||
3040 | |||
2678 | /* vim: set shiftwidth=4 tabstop=8: */ | 3041 | /* vim: set shiftwidth=4 tabstop=8: */ |
diff --git a/apps/plugins/puzzles/src/twiddle.c b/apps/plugins/puzzles/src/twiddle.c index 1d91559e37..5f2ea02e6f 100644 --- a/apps/plugins/puzzles/src/twiddle.c +++ b/apps/plugins/puzzles/src/twiddle.c | |||
@@ -552,6 +552,12 @@ static char *game_text_format(const game_state *state) | |||
552 | int i, x, y, col, maxlen; | 552 | int i, x, y, col, maxlen; |
553 | bool o = state->orientable; | 553 | bool o = state->orientable; |
554 | 554 | ||
555 | /* Pedantic check: ensure buf is large enough to format an int in | ||
556 | * decimal, using the bound log10(2) < 1/3. (Obviously in practice | ||
557 | * int is not going to be larger than even 32 bits any time soon, | ||
558 | * but.) */ | ||
559 | assert(sizeof(buf) >= 1 + sizeof(int) * CHAR_BIT/3); | ||
560 | |||
555 | /* | 561 | /* |
556 | * First work out how many characters we need to display each | 562 | * First work out how many characters we need to display each |
557 | * number. We're pretty flexible on grid contents here, so we | 563 | * number. We're pretty flexible on grid contents here, so we |
@@ -563,6 +569,11 @@ static char *game_text_format(const game_state *state) | |||
563 | if (col < x) col = x; | 569 | if (col < x) col = x; |
564 | } | 570 | } |
565 | 571 | ||
572 | /* Reassure sprintf-checking compilers like gcc that the field | ||
573 | * width we've just computed is not now excessive */ | ||
574 | if (col >= sizeof(buf)) | ||
575 | col = sizeof(buf)-1; | ||
576 | |||
566 | /* | 577 | /* |
567 | * Now we know the exact total size of the grid we're going to | 578 | * Now we know the exact total size of the grid we're going to |
568 | * produce: it's got h rows, each containing w lots of col+o, | 579 | * produce: it's got h rows, each containing w lots of col+o, |
diff --git a/apps/plugins/puzzles/src/unequal.c b/apps/plugins/puzzles/src/unequal.c index 951e9ac80f..556cf01c45 100644 --- a/apps/plugins/puzzles/src/unequal.c +++ b/apps/plugins/puzzles/src/unequal.c | |||
@@ -816,6 +816,74 @@ static int solver_set(struct latin_solver *solver, void *vctx) | |||
816 | #define SOLVER(upper,title,func,lower) func, | 816 | #define SOLVER(upper,title,func,lower) func, |
817 | static usersolver_t const unequal_solvers[] = { DIFFLIST(SOLVER) }; | 817 | static usersolver_t const unequal_solvers[] = { DIFFLIST(SOLVER) }; |
818 | 818 | ||
819 | static bool unequal_valid(struct latin_solver *solver, void *vctx) | ||
820 | { | ||
821 | struct solver_ctx *ctx = (struct solver_ctx *)vctx; | ||
822 | if (ctx->state->mode == MODE_ADJACENT) { | ||
823 | int o = solver->o; | ||
824 | int x, y, nx, ny, v, nv, i; | ||
825 | |||
826 | for (x = 0; x+1 < o; x++) { | ||
827 | for (y = 0; y+1 < o; y++) { | ||
828 | v = grid(x, y); | ||
829 | for (i = 0; i < 4; i++) { | ||
830 | bool is_adj, should_be_adj; | ||
831 | |||
832 | should_be_adj = | ||
833 | (GRID(ctx->state, flags, x, y) & adjthan[i].f); | ||
834 | |||
835 | nx = x + adjthan[i].dx, ny = y + adjthan[i].dy; | ||
836 | if (nx < 0 || ny < 0 || nx >= o || ny >= o) | ||
837 | continue; | ||
838 | |||
839 | nv = grid(nx, ny); | ||
840 | is_adj = (labs(v - nv) == 1); | ||
841 | |||
842 | if (is_adj && !should_be_adj) { | ||
843 | #ifdef STANDALONE_SOLVER | ||
844 | if (solver_show_working) | ||
845 | printf("%*s(%d,%d):%d and (%d,%d):%d have " | ||
846 | "adjacent values, but should not\n", | ||
847 | solver_recurse_depth*4, "", | ||
848 | x+1, y+1, v, nx+1, ny+1, nv); | ||
849 | #endif | ||
850 | return false; | ||
851 | } | ||
852 | |||
853 | if (!is_adj && should_be_adj) { | ||
854 | #ifdef STANDALONE_SOLVER | ||
855 | if (solver_show_working) | ||
856 | printf("%*s(%d,%d):%d and (%d,%d):%d do not have " | ||
857 | "adjacent values, but should\n", | ||
858 | solver_recurse_depth*4, "", | ||
859 | x+1, y+1, v, nx+1, ny+1, nv); | ||
860 | #endif | ||
861 | return false; | ||
862 | } | ||
863 | } | ||
864 | } | ||
865 | } | ||
866 | } else { | ||
867 | int i; | ||
868 | for (i = 0; i < ctx->nlinks; i++) { | ||
869 | struct solver_link *link = &ctx->links[i]; | ||
870 | int gv = grid(link->gx, link->gy); | ||
871 | int lv = grid(link->lx, link->ly); | ||
872 | if (gv <= lv) { | ||
873 | #ifdef STANDALONE_SOLVER | ||
874 | if (solver_show_working) | ||
875 | printf("%*s(%d,%d):%d should be greater than (%d,%d):%d, " | ||
876 | "but is not\n", solver_recurse_depth*4, "", | ||
877 | link->gx+1, link->gy+1, gv, | ||
878 | link->lx+1, link->ly+1, lv); | ||
879 | #endif | ||
880 | return false; | ||
881 | } | ||
882 | } | ||
883 | } | ||
884 | return true; | ||
885 | } | ||
886 | |||
819 | static int solver_state(game_state *state, int maxdiff) | 887 | static int solver_state(game_state *state, int maxdiff) |
820 | { | 888 | { |
821 | struct solver_ctx *ctx = new_ctx(state); | 889 | struct solver_ctx *ctx = new_ctx(state); |
@@ -827,7 +895,8 @@ static int solver_state(game_state *state, int maxdiff) | |||
827 | diff = latin_solver_main(&solver, maxdiff, | 895 | diff = latin_solver_main(&solver, maxdiff, |
828 | DIFF_LATIN, DIFF_SET, DIFF_EXTREME, | 896 | DIFF_LATIN, DIFF_SET, DIFF_EXTREME, |
829 | DIFF_EXTREME, DIFF_RECURSIVE, | 897 | DIFF_EXTREME, DIFF_RECURSIVE, |
830 | unequal_solvers, ctx, clone_ctx, free_ctx); | 898 | unequal_solvers, unequal_valid, ctx, |
899 | clone_ctx, free_ctx); | ||
831 | 900 | ||
832 | memcpy(state->hints, solver.cube, state->order*state->order*state->order); | 901 | memcpy(state->hints, solver.cube, state->order*state->order*state->order); |
833 | 902 | ||
@@ -2155,7 +2224,8 @@ static int solve(game_params *p, char *desc, int debug) | |||
2155 | diff = latin_solver_main(&solver, DIFF_RECURSIVE, | 2224 | diff = latin_solver_main(&solver, DIFF_RECURSIVE, |
2156 | DIFF_LATIN, DIFF_SET, DIFF_EXTREME, | 2225 | DIFF_LATIN, DIFF_SET, DIFF_EXTREME, |
2157 | DIFF_EXTREME, DIFF_RECURSIVE, | 2226 | DIFF_EXTREME, DIFF_RECURSIVE, |
2158 | unequal_solvers, ctx, clone_ctx, free_ctx); | 2227 | unequal_solvers, unequal_valid, ctx, |
2228 | clone_ctx, free_ctx); | ||
2159 | 2229 | ||
2160 | free_ctx(ctx); | 2230 | free_ctx(ctx); |
2161 | 2231 | ||
diff --git a/apps/plugins/puzzles/src/unfinished/group.c b/apps/plugins/puzzles/src/unfinished/group.c index ef7ffba349..006a9e0ee6 100644 --- a/apps/plugins/puzzles/src/unfinished/group.c +++ b/apps/plugins/puzzles/src/unfinished/group.c | |||
@@ -43,7 +43,7 @@ | |||
43 | #define DIFFLIST(A) \ | 43 | #define DIFFLIST(A) \ |
44 | A(TRIVIAL,Trivial,NULL,t) \ | 44 | A(TRIVIAL,Trivial,NULL,t) \ |
45 | A(NORMAL,Normal,solver_normal,n) \ | 45 | A(NORMAL,Normal,solver_normal,n) \ |
46 | A(HARD,Hard,NULL,h) \ | 46 | A(HARD,Hard,solver_hard,h) \ |
47 | A(EXTREME,Extreme,NULL,x) \ | 47 | A(EXTREME,Extreme,NULL,x) \ |
48 | A(UNREASONABLE,Unreasonable,NULL,u) | 48 | A(UNREASONABLE,Unreasonable,NULL,u) |
49 | #define ENUM(upper,title,func,lower) DIFF_ ## upper, | 49 | #define ENUM(upper,title,func,lower) DIFF_ ## upper, |
@@ -280,6 +280,23 @@ static const char *validate_params(const game_params *params, bool full) | |||
280 | * Solver. | 280 | * Solver. |
281 | */ | 281 | */ |
282 | 282 | ||
283 | static int find_identity(struct latin_solver *solver) | ||
284 | { | ||
285 | int w = solver->o; | ||
286 | digit *grid = solver->grid; | ||
287 | int i, j; | ||
288 | |||
289 | for (i = 0; i < w; i++) | ||
290 | for (j = 0; j < w; j++) { | ||
291 | if (grid[i*w+j] == i+1) | ||
292 | return j+1; | ||
293 | if (grid[i*w+j] == j+1) | ||
294 | return i+1; | ||
295 | } | ||
296 | |||
297 | return 0; | ||
298 | } | ||
299 | |||
283 | static int solver_normal(struct latin_solver *solver, void *vctx) | 300 | static int solver_normal(struct latin_solver *solver, void *vctx) |
284 | { | 301 | { |
285 | int w = solver->o; | 302 | int w = solver->o; |
@@ -295,9 +312,9 @@ static int solver_normal(struct latin_solver *solver, void *vctx) | |||
295 | * So we pick any a,b,c we like; then if we know ab, bc, and | 312 | * So we pick any a,b,c we like; then if we know ab, bc, and |
296 | * (ab)c we can fill in a(bc). | 313 | * (ab)c we can fill in a(bc). |
297 | */ | 314 | */ |
298 | for (i = 1; i < w; i++) | 315 | for (i = 0; i < w; i++) |
299 | for (j = 1; j < w; j++) | 316 | for (j = 0; j < w; j++) |
300 | for (k = 1; k < w; k++) { | 317 | for (k = 0; k < w; k++) { |
301 | if (!grid[i*w+j] || !grid[j*w+k]) | 318 | if (!grid[i*w+j] || !grid[j*w+k]) |
302 | continue; | 319 | continue; |
303 | if (grid[(grid[i*w+j]-1)*w+k] && | 320 | if (grid[(grid[i*w+j]-1)*w+k] && |
@@ -358,12 +375,206 @@ static int solver_normal(struct latin_solver *solver, void *vctx) | |||
358 | } | 375 | } |
359 | } | 376 | } |
360 | 377 | ||
378 | /* | ||
379 | * Fill in the row and column for the group identity, if it's not | ||
380 | * already known and if we've just found out what it is. | ||
381 | */ | ||
382 | i = find_identity(solver); | ||
383 | if (i) { | ||
384 | bool done_something = false; | ||
385 | for (j = 1; j <= w; j++) { | ||
386 | if (!grid[(i-1)*w+(j-1)] || !grid[(j-1)*w+(i-1)]) { | ||
387 | done_something = true; | ||
388 | } | ||
389 | } | ||
390 | if (done_something) { | ||
391 | #ifdef STANDALONE_SOLVER | ||
392 | if (solver_show_working) { | ||
393 | printf("%*s%s is the group identity\n", | ||
394 | solver_recurse_depth*4, "", names[i-1]); | ||
395 | } | ||
396 | #endif | ||
397 | for (j = 1; j <= w; j++) { | ||
398 | if (!grid[(j-1)*w+(i-1)]) { | ||
399 | if (!cube(i-1, j-1, j)) { | ||
400 | #ifdef STANDALONE_SOLVER | ||
401 | if (solver_show_working) { | ||
402 | printf("%*s but %s cannot go at (%d,%d) - " | ||
403 | "contradiction!\n", | ||
404 | solver_recurse_depth*4, "", | ||
405 | names[j-1], i, j); | ||
406 | } | ||
407 | #endif | ||
408 | return -1; | ||
409 | } | ||
410 | #ifdef STANDALONE_SOLVER | ||
411 | if (solver_show_working) { | ||
412 | printf("%*s placing %s at (%d,%d)\n", | ||
413 | solver_recurse_depth*4, "", | ||
414 | names[j-1], i, j); | ||
415 | } | ||
416 | #endif | ||
417 | latin_solver_place(solver, i-1, j-1, j); | ||
418 | } | ||
419 | if (!grid[(i-1)*w+(j-1)]) { | ||
420 | if (!cube(j-1, i-1, j)) { | ||
421 | #ifdef STANDALONE_SOLVER | ||
422 | if (solver_show_working) { | ||
423 | printf("%*s but %s cannot go at (%d,%d) - " | ||
424 | "contradiction!\n", | ||
425 | solver_recurse_depth*4, "", | ||
426 | names[j-1], j, i); | ||
427 | } | ||
428 | #endif | ||
429 | return -1; | ||
430 | } | ||
431 | #ifdef STANDALONE_SOLVER | ||
432 | if (solver_show_working) { | ||
433 | printf("%*s placing %s at (%d,%d)\n", | ||
434 | solver_recurse_depth*4, "", | ||
435 | names[j-1], j, i); | ||
436 | } | ||
437 | #endif | ||
438 | latin_solver_place(solver, j-1, i-1, j); | ||
439 | } | ||
440 | } | ||
441 | return 1; | ||
442 | } | ||
443 | } | ||
444 | |||
361 | return 0; | 445 | return 0; |
362 | } | 446 | } |
363 | 447 | ||
448 | static int solver_hard(struct latin_solver *solver, void *vctx) | ||
449 | { | ||
450 | bool done_something = false; | ||
451 | int w = solver->o; | ||
452 | #ifdef STANDALONE_SOLVER | ||
453 | char **names = solver->names; | ||
454 | #endif | ||
455 | int i, j; | ||
456 | |||
457 | /* | ||
458 | * In identity-hidden mode, systematically rule out possibilities | ||
459 | * for the group identity. | ||
460 | * | ||
461 | * In solver_normal, we used the fact that any filled square in | ||
462 | * the grid whose contents _does_ match one of the elements it's | ||
463 | * the product of - that is, ab=a or ab=b - tells you immediately | ||
464 | * that the other element is the identity. | ||
465 | * | ||
466 | * Here, we use the flip side of that: any filled square in the | ||
467 | * grid whose contents does _not_ match either its row or column - | ||
468 | * that is, if ab is neither a nor b - tells you immediately that | ||
469 | * _neither_ of those elements is the identity. And if that's | ||
470 | * true, then we can also immediately rule out the possibility | ||
471 | * that it acts as the identity on any element at all. | ||
472 | */ | ||
473 | for (i = 0; i < w; i++) { | ||
474 | bool i_can_be_id = true; | ||
475 | #ifdef STANDALONE_SOLVER | ||
476 | char title[80]; | ||
477 | #endif | ||
478 | |||
479 | for (j = 0; j < w; j++) { | ||
480 | if (grid(i,j) && grid(i,j) != j+1) { | ||
481 | #ifdef STANDALONE_SOLVER | ||
482 | if (solver_show_working) | ||
483 | sprintf(title, "%s cannot be the identity: " | ||
484 | "%s%s = %s =/= %s", names[i], names[i], names[j], | ||
485 | names[grid(i,j)-1], names[j]); | ||
486 | #endif | ||
487 | i_can_be_id = false; | ||
488 | break; | ||
489 | } | ||
490 | if (grid(j,i) && grid(j,i) != j+1) { | ||
491 | #ifdef STANDALONE_SOLVER | ||
492 | if (solver_show_working) | ||
493 | sprintf(title, "%s cannot be the identity: " | ||
494 | "%s%s = %s =/= %s", names[i], names[j], names[i], | ||
495 | names[grid(j,i)-1], names[j]); | ||
496 | #endif | ||
497 | i_can_be_id = false; | ||
498 | break; | ||
499 | } | ||
500 | } | ||
501 | |||
502 | if (!i_can_be_id) { | ||
503 | /* Now rule out ij=j or ji=j for all j. */ | ||
504 | for (j = 0; j < w; j++) { | ||
505 | if (cube(i, j, j+1)) { | ||
506 | #ifdef STANDALONE_SOLVER | ||
507 | if (solver_show_working) { | ||
508 | if (title[0]) { | ||
509 | printf("%*s%s\n", solver_recurse_depth*4, "", | ||
510 | title); | ||
511 | title[0] = '\0'; | ||
512 | } | ||
513 | printf("%*s ruling out %s at (%d,%d)\n", | ||
514 | solver_recurse_depth*4, "", names[j], i, j); | ||
515 | } | ||
516 | #endif | ||
517 | cube(i, j, j+1) = false; | ||
518 | } | ||
519 | if (cube(j, i, j+1)) { | ||
520 | #ifdef STANDALONE_SOLVER | ||
521 | if (solver_show_working) { | ||
522 | if (title[0]) { | ||
523 | printf("%*s%s\n", solver_recurse_depth*4, "", | ||
524 | title); | ||
525 | title[0] = '\0'; | ||
526 | } | ||
527 | printf("%*s ruling out %s at (%d,%d)\n", | ||
528 | solver_recurse_depth*4, "", names[j], j, i); | ||
529 | } | ||
530 | #endif | ||
531 | cube(j, i, j+1) = false; | ||
532 | } | ||
533 | } | ||
534 | } | ||
535 | } | ||
536 | |||
537 | return done_something; | ||
538 | } | ||
539 | |||
364 | #define SOLVER(upper,title,func,lower) func, | 540 | #define SOLVER(upper,title,func,lower) func, |
365 | static usersolver_t const group_solvers[] = { DIFFLIST(SOLVER) }; | 541 | static usersolver_t const group_solvers[] = { DIFFLIST(SOLVER) }; |
366 | 542 | ||
543 | static bool group_valid(struct latin_solver *solver, void *ctx) | ||
544 | { | ||
545 | int w = solver->o; | ||
546 | #ifdef STANDALONE_SOLVER | ||
547 | char **names = solver->names; | ||
548 | #endif | ||
549 | int i, j, k; | ||
550 | |||
551 | for (i = 0; i < w; i++) | ||
552 | for (j = 0; j < w; j++) | ||
553 | for (k = 0; k < w; k++) { | ||
554 | int ij = grid(i, j) - 1; | ||
555 | int jk = grid(j, k) - 1; | ||
556 | int ij_k = grid(ij, k) - 1; | ||
557 | int i_jk = grid(i, jk) - 1; | ||
558 | if (ij_k != i_jk) { | ||
559 | #ifdef STANDALONE_SOLVER | ||
560 | if (solver_show_working) { | ||
561 | printf("%*sfailure of associativity: " | ||
562 | "(%s%s)%s = %s%s = %s but " | ||
563 | "%s(%s%s) = %s%s = %s\n", | ||
564 | solver_recurse_depth*4, "", | ||
565 | names[i], names[j], names[k], | ||
566 | names[ij], names[k], names[ij_k], | ||
567 | names[i], names[j], names[k], | ||
568 | names[i], names[jk], names[i_jk]); | ||
569 | } | ||
570 | #endif | ||
571 | return false; | ||
572 | } | ||
573 | } | ||
574 | |||
575 | return true; | ||
576 | } | ||
577 | |||
367 | static int solver(const game_params *params, digit *grid, int maxdiff) | 578 | static int solver(const game_params *params, digit *grid, int maxdiff) |
368 | { | 579 | { |
369 | int w = params->w; | 580 | int w = params->w; |
@@ -387,7 +598,7 @@ static int solver(const game_params *params, digit *grid, int maxdiff) | |||
387 | ret = latin_solver_main(&solver, maxdiff, | 598 | ret = latin_solver_main(&solver, maxdiff, |
388 | DIFF_TRIVIAL, DIFF_HARD, DIFF_EXTREME, | 599 | DIFF_TRIVIAL, DIFF_HARD, DIFF_EXTREME, |
389 | DIFF_EXTREME, DIFF_UNREASONABLE, | 600 | DIFF_EXTREME, DIFF_UNREASONABLE, |
390 | group_solvers, NULL, NULL, NULL); | 601 | group_solvers, group_valid, NULL, NULL, NULL); |
391 | 602 | ||
392 | latin_solver_free(&solver); | 603 | latin_solver_free(&solver); |
393 | 604 | ||
@@ -2183,6 +2394,11 @@ int main(int argc, char **argv) | |||
2183 | } | 2394 | } |
2184 | 2395 | ||
2185 | if (diff == DIFFCOUNT) { | 2396 | if (diff == DIFFCOUNT) { |
2397 | if (really_show_working) { | ||
2398 | solver_show_working = true; | ||
2399 | memcpy(grid, s->grid, p->w * p->w); | ||
2400 | ret = solver(&s->par, grid, DIFFCOUNT - 1); | ||
2401 | } | ||
2186 | if (grade) | 2402 | if (grade) |
2187 | printf("Difficulty rating: ambiguous\n"); | 2403 | printf("Difficulty rating: ambiguous\n"); |
2188 | else | 2404 | else |
diff --git a/apps/plugins/puzzles/src/unfinished/path.c b/apps/plugins/puzzles/src/unfinished/path.c index 829fbc6c75..fe5a47fd2a 100644 --- a/apps/plugins/puzzles/src/unfinished/path.c +++ b/apps/plugins/puzzles/src/unfinished/path.c | |||
@@ -69,6 +69,103 @@ | |||
69 | */ | 69 | */ |
70 | 70 | ||
71 | /* | 71 | /* |
72 | * 2020-05-11: some thoughts on a solver. | ||
73 | * | ||
74 | * Consider this example puzzle, from Wikipedia: | ||
75 | * | ||
76 | * ---4--- | ||
77 | * -3--25- | ||
78 | * ---31-- | ||
79 | * ---5--- | ||
80 | * ------- | ||
81 | * --1---- | ||
82 | * 2---4-- | ||
83 | * | ||
84 | * The kind of deduction that a human wants to make here is: which way | ||
85 | * does the path between the 4s go? In particular, does it go round | ||
86 | * the left of the W-shaped cluster of endpoints, or round the right | ||
87 | * of it? It's clear at a glance that it must go to the right, because | ||
88 | * _any_ path between the 4s that goes to the left of that cluster, no | ||
89 | * matter what detailed direction it takes, will disconnect the | ||
90 | * remaining grid squares into two components, with the two 2s not in | ||
91 | * the same component. So we immediately know that the path between | ||
92 | * the 4s _must_ go round the right-hand side of the grid. | ||
93 | * | ||
94 | * How do you model that global and topological reasoning in a | ||
95 | * computer? | ||
96 | * | ||
97 | * The most plausible idea I've seen so far is to use fundamental | ||
98 | * groups. The fundamental group of loops based at a given point in a | ||
99 | * space is a free group, under loop concatenation and up to homotopy, | ||
100 | * generated by the loops that go in each direction around each hole | ||
101 | * in the space. In this case, the 'holes' are clues, or connected | ||
102 | * groups of clues. | ||
103 | * | ||
104 | * So you might be able to enumerate all the homotopy classes of paths | ||
105 | * between (say) the two 4s as follows. Start with any old path | ||
106 | * between them (say, find the first one that breadth-first search | ||
107 | * will give you). Choose one of the 4s to regard as the base point | ||
108 | * (arbitrarily). Then breadth-first search among the space of _paths_ | ||
109 | * by the following procedure. Given a candidate path, append to it | ||
110 | * each of the possible loops that starts from the base point, | ||
111 | * circumnavigates one clue cluster, and returns to the base point. | ||
112 | * The result will typically be a path that retraces its steps and | ||
113 | * self-intersects. Now adjust it homotopically so that it doesn't. If | ||
114 | * that can't be done, then we haven't generated a fresh candidate | ||
115 | * path; if it can, then we've got a new path that is not homotopic to | ||
116 | * any path we already had, so add it to our list and queue it up to | ||
117 | * become the starting point of this search later. | ||
118 | * | ||
119 | * The idea is that this should exhaustively enumerate, up to | ||
120 | * homotopy, the different ways in which the two 4s can connect to | ||
121 | * each other within the constraint that you have to actually fit the | ||
122 | * path non-self-intersectingly into this grid. Then you can keep a | ||
123 | * list of those homotopy classes in mind, and start ruling them out | ||
124 | * by techniques like the connectivity approach described above. | ||
125 | * Hopefully you end up narrowing down to few enough homotopy classes | ||
126 | * that you can deduce something concrete about actual squares of the | ||
127 | * grid - for example, here, that if the path between 4s has to go | ||
128 | * round the right, then we know some specific squares it must go | ||
129 | * through, so we can fill those in. And then, having filled in a | ||
130 | * piece of the middle of a path, you can now regard connecting the | ||
131 | * ultimate endpoints to that mid-section as two separate subproblems, | ||
132 | * so you've reduced to a simpler instance of the same puzzle. | ||
133 | * | ||
134 | * But I don't know whether all of this actually works. I more or less | ||
135 | * believe the process for enumerating elements of the free group; but | ||
136 | * I'm not as confident that when you find a group element that won't | ||
137 | * fit in the grid, you'll never have to consider its descendants in | ||
138 | * the BFS either. And I'm assuming that 'unwind the self-intersection | ||
139 | * homotopically' is a thing that can actually be turned into a | ||
140 | * sensible algorithm. | ||
141 | * | ||
142 | * -------- | ||
143 | * | ||
144 | * Another thing that might be needed is to characterise _which_ | ||
145 | * homotopy class a given path is in. | ||
146 | * | ||
147 | * For this I think it's sufficient to choose a collection of paths | ||
148 | * along the _edges_ of the square grid, each of which connects two of | ||
149 | * the holes in the grid (including the grid exterior, which counts as | ||
150 | * a huge hole), such that they form a spanning tree between the | ||
151 | * holes. Then assign each of those paths an orientation, so that | ||
152 | * crossing it in one direction counts as 'positive' and the other | ||
153 | * 'negative'. Now analyse a candidate path from one square to another | ||
154 | * by following it and noting down which of those paths it crosses in | ||
155 | * which direction, then simplifying the result like a free group word | ||
156 | * (i.e. adjacent + and - crossings of the same path cancel out). | ||
157 | * | ||
158 | * -------- | ||
159 | * | ||
160 | * If we choose those paths to be of minimal length, then we can get | ||
161 | * an upper bound on the number of homotopy classes by observing that | ||
162 | * you can't traverse any of those barriers more times than will fit | ||
163 | * non-self-intersectingly in the grid. That might be an alternative | ||
164 | * method of bounding the search through the fundamental group to only | ||
165 | * finitely many possibilities. | ||
166 | */ | ||
167 | |||
168 | /* | ||
72 | * Standard notation for directions. | 169 | * Standard notation for directions. |
73 | */ | 170 | */ |
74 | #define L 0 | 171 | #define L 0 |