multi-puzzle-solver 1.1.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. multi_puzzle_solver-1.1.8.dist-info/METADATA +4326 -0
  2. multi_puzzle_solver-1.1.8.dist-info/RECORD +106 -0
  3. multi_puzzle_solver-1.1.8.dist-info/WHEEL +5 -0
  4. multi_puzzle_solver-1.1.8.dist-info/top_level.txt +1 -0
  5. puzzle_solver/__init__.py +184 -0
  6. puzzle_solver/core/utils.py +298 -0
  7. puzzle_solver/core/utils_ortools.py +333 -0
  8. puzzle_solver/core/utils_visualizer.py +575 -0
  9. puzzle_solver/puzzles/abc_view/abc_view.py +75 -0
  10. puzzle_solver/puzzles/aquarium/aquarium.py +97 -0
  11. puzzle_solver/puzzles/area_51/area_51.py +159 -0
  12. puzzle_solver/puzzles/battleships/battleships.py +139 -0
  13. puzzle_solver/puzzles/binairo/binairo.py +98 -0
  14. puzzle_solver/puzzles/binairo/binairo_plus.py +7 -0
  15. puzzle_solver/puzzles/black_box/black_box.py +243 -0
  16. puzzle_solver/puzzles/branches/branches.py +64 -0
  17. puzzle_solver/puzzles/bridges/bridges.py +104 -0
  18. puzzle_solver/puzzles/chess_range/chess_melee.py +6 -0
  19. puzzle_solver/puzzles/chess_range/chess_range.py +406 -0
  20. puzzle_solver/puzzles/chess_range/chess_solo.py +9 -0
  21. puzzle_solver/puzzles/chess_sequence/chess_sequence.py +262 -0
  22. puzzle_solver/puzzles/circle_9/circle_9.py +44 -0
  23. puzzle_solver/puzzles/clouds/clouds.py +81 -0
  24. puzzle_solver/puzzles/connect_the_dots/connect_the_dots.py +50 -0
  25. puzzle_solver/puzzles/cow_and_cactus/cow_and_cactus.py +66 -0
  26. puzzle_solver/puzzles/dominosa/dominosa.py +67 -0
  27. puzzle_solver/puzzles/filling/filling.py +94 -0
  28. puzzle_solver/puzzles/flip/flip.py +64 -0
  29. puzzle_solver/puzzles/flood_it/flood_it.py +174 -0
  30. puzzle_solver/puzzles/flood_it/parse_map/parse_map.py +197 -0
  31. puzzle_solver/puzzles/galaxies/galaxies.py +110 -0
  32. puzzle_solver/puzzles/galaxies/parse_map/parse_map.py +216 -0
  33. puzzle_solver/puzzles/guess/guess.py +232 -0
  34. puzzle_solver/puzzles/heyawake/heyawake.py +152 -0
  35. puzzle_solver/puzzles/hidden_stars/hidden_stars.py +52 -0
  36. puzzle_solver/puzzles/hidoku/hidoku.py +59 -0
  37. puzzle_solver/puzzles/inertia/inertia.py +121 -0
  38. puzzle_solver/puzzles/inertia/parse_map/parse_map.py +207 -0
  39. puzzle_solver/puzzles/inertia/tsp.py +400 -0
  40. puzzle_solver/puzzles/kakurasu/kakurasu.py +38 -0
  41. puzzle_solver/puzzles/kakuro/kakuro.py +81 -0
  42. puzzle_solver/puzzles/kakuro/krypto_kakuro.py +95 -0
  43. puzzle_solver/puzzles/keen/keen.py +76 -0
  44. puzzle_solver/puzzles/kropki/kropki.py +94 -0
  45. puzzle_solver/puzzles/light_up/light_up.py +58 -0
  46. puzzle_solver/puzzles/linesweeper/linesweeper.py +71 -0
  47. puzzle_solver/puzzles/link_a_pix/link_a_pix.py +91 -0
  48. puzzle_solver/puzzles/lits/lits.py +138 -0
  49. puzzle_solver/puzzles/magnets/magnets.py +96 -0
  50. puzzle_solver/puzzles/map/map.py +56 -0
  51. puzzle_solver/puzzles/mathema_grids/mathema_grids.py +119 -0
  52. puzzle_solver/puzzles/mathrax/mathrax.py +93 -0
  53. puzzle_solver/puzzles/minesweeper/minesweeper.py +123 -0
  54. puzzle_solver/puzzles/mosaic/mosaic.py +38 -0
  55. puzzle_solver/puzzles/n_queens/n_queens.py +71 -0
  56. puzzle_solver/puzzles/nonograms/nonograms.py +121 -0
  57. puzzle_solver/puzzles/nonograms/nonograms_colored.py +220 -0
  58. puzzle_solver/puzzles/norinori/norinori.py +96 -0
  59. puzzle_solver/puzzles/number_path/number_path.py +76 -0
  60. puzzle_solver/puzzles/numbermaze/numbermaze.py +97 -0
  61. puzzle_solver/puzzles/nurikabe/nurikabe.py +130 -0
  62. puzzle_solver/puzzles/palisade/palisade.py +91 -0
  63. puzzle_solver/puzzles/pearl/pearl.py +107 -0
  64. puzzle_solver/puzzles/pipes/pipes.py +82 -0
  65. puzzle_solver/puzzles/range/range.py +59 -0
  66. puzzle_solver/puzzles/rectangles/rectangles.py +128 -0
  67. puzzle_solver/puzzles/ripple_effect/ripple_effect.py +83 -0
  68. puzzle_solver/puzzles/rooms/rooms.py +75 -0
  69. puzzle_solver/puzzles/schurs_numbers/schurs_numbers.py +73 -0
  70. puzzle_solver/puzzles/shakashaka/shakashaka.py +201 -0
  71. puzzle_solver/puzzles/shingoki/shingoki.py +116 -0
  72. puzzle_solver/puzzles/signpost/signpost.py +93 -0
  73. puzzle_solver/puzzles/singles/singles.py +53 -0
  74. puzzle_solver/puzzles/slant/parse_map/parse_map.py +135 -0
  75. puzzle_solver/puzzles/slant/slant.py +111 -0
  76. puzzle_solver/puzzles/slitherlink/slitherlink.py +130 -0
  77. puzzle_solver/puzzles/snail/snail.py +97 -0
  78. puzzle_solver/puzzles/split_ends/split_ends.py +93 -0
  79. puzzle_solver/puzzles/star_battle/star_battle.py +75 -0
  80. puzzle_solver/puzzles/star_battle/star_battle_shapeless.py +7 -0
  81. puzzle_solver/puzzles/stitches/parse_map/parse_map.py +267 -0
  82. puzzle_solver/puzzles/stitches/stitches.py +96 -0
  83. puzzle_solver/puzzles/sudoku/sudoku.py +267 -0
  84. puzzle_solver/puzzles/suguru/suguru.py +55 -0
  85. puzzle_solver/puzzles/suko/suko.py +54 -0
  86. puzzle_solver/puzzles/tapa/tapa.py +97 -0
  87. puzzle_solver/puzzles/tatami/tatami.py +64 -0
  88. puzzle_solver/puzzles/tents/tents.py +80 -0
  89. puzzle_solver/puzzles/thermometers/thermometers.py +82 -0
  90. puzzle_solver/puzzles/towers/towers.py +89 -0
  91. puzzle_solver/puzzles/tracks/tracks.py +88 -0
  92. puzzle_solver/puzzles/trees_logic/trees_logic.py +48 -0
  93. puzzle_solver/puzzles/troix/dumplings.py +7 -0
  94. puzzle_solver/puzzles/troix/troix.py +75 -0
  95. puzzle_solver/puzzles/twiddle/twiddle.py +112 -0
  96. puzzle_solver/puzzles/undead/undead.py +130 -0
  97. puzzle_solver/puzzles/unequal/unequal.py +128 -0
  98. puzzle_solver/puzzles/unruly/unruly.py +54 -0
  99. puzzle_solver/puzzles/vectors/vectors.py +94 -0
  100. puzzle_solver/puzzles/vermicelli/vermicelli.py +74 -0
  101. puzzle_solver/puzzles/walls/walls.py +52 -0
  102. puzzle_solver/puzzles/yajilin/yajilin.py +87 -0
  103. puzzle_solver/puzzles/yin_yang/parse_map/parse_map.py +172 -0
  104. puzzle_solver/puzzles/yin_yang/yin_yang.py +103 -0
  105. puzzle_solver/utils/etc/parser/board_color_digit.py +497 -0
  106. puzzle_solver/utils/visualizer.py +155 -0
@@ -0,0 +1,106 @@
1
+ puzzle_solver/__init__.py,sha256=NYPVckpg9PKDgPK80Nr-zqhFeR83MS_TX9yCOUp9hlU,8307
2
+ puzzle_solver/core/utils.py,sha256=SQJIugj5EGoV8YNprGSXCZdE24Moo56UWd38BCQ2-2g,10990
3
+ puzzle_solver/core/utils_ortools.py,sha256=p0Gco5tUWMKw5xIA7YLLoyGYW591lV6EUEpf4cQAZAs,15111
4
+ puzzle_solver/core/utils_visualizer.py,sha256=IdNI9UB1Y_E300ksOIYUEprRywkTvavOMYetbg4FIoo,23662
5
+ puzzle_solver/puzzles/abc_view/abc_view.py,sha256=Qr0rZKmKQ2teStHjQ5VPQ4k-XptsjJAlZ1WXWk5Aax4,4570
6
+ puzzle_solver/puzzles/aquarium/aquarium.py,sha256=dGqYEWMoh4di5DN4sd-GtYb6QeTpVYFQJHBkrrmrudQ,5649
7
+ puzzle_solver/puzzles/area_51/area_51.py,sha256=noiDSdDTVQFHR6ztVzyxmVbEFACsaWn-vRZlLSRLVlc,9967
8
+ puzzle_solver/puzzles/battleships/battleships.py,sha256=U4xJ_NJC2baHvfaAfJ01YEBjixq9gD0h8GP9L1V-_oM,7223
9
+ puzzle_solver/puzzles/binairo/binairo.py,sha256=EBpXYD9Mxuig4uJl3xkcQ6_tbnoG13mVV7RZpQEXm38,5790
10
+ puzzle_solver/puzzles/binairo/binairo_plus.py,sha256=TvLG3olwANtft3LuCF-y4OofpU9PNa4IXDqgZqsD-g0,267
11
+ puzzle_solver/puzzles/black_box/black_box.py,sha256=7CxCdkP_JC3_9mrObkg7lrYGLBMok_DdBd3Nx0xCSkA,12560
12
+ puzzle_solver/puzzles/branches/branches.py,sha256=BKrzZNU-y80uNiwahEr-eUM7X7baEVPzAdc18--XOlE,3691
13
+ puzzle_solver/puzzles/bridges/bridges.py,sha256=HSoYBlht92TMknhe8IaC0B3ilNaSh5xMorB_Gs1Jatg,5395
14
+ puzzle_solver/puzzles/chess_range/chess_melee.py,sha256=D-_Oi8OyxsVe1j3dIKYwRlxgeb3NWLmDWGcv-oclY0c,195
15
+ puzzle_solver/puzzles/chess_range/chess_range.py,sha256=St8Fk-HOhInvIJy02zbW1Pa1t2qjpli-Fes07tjgBRU,21472
16
+ puzzle_solver/puzzles/chess_range/chess_solo.py,sha256=ByDfcRsk5FVmFicpU_DpLoLTJ99Kr___vX4y8ln8_EQ,400
17
+ puzzle_solver/puzzles/chess_sequence/chess_sequence.py,sha256=6ap3Wouf2PxHV4P56B9ol1QT98Ym6VHaxorQZWl6LnY,13692
18
+ puzzle_solver/puzzles/circle_9/circle_9.py,sha256=Y94GM2eVYZc0YRu0XJ_ynMbBJZALKUZqbZA6Fe1ZwVQ,2401
19
+ puzzle_solver/puzzles/clouds/clouds.py,sha256=NAPDq_62Unfa9oYGqMt0hbA9TVw1tYQzYvEuoZC84i8,4100
20
+ puzzle_solver/puzzles/connect_the_dots/connect_the_dots.py,sha256=lxLUk7KjiTLND0CJ4f9IY_DS23rOYE-q0g1FKcRPaeo,3158
21
+ puzzle_solver/puzzles/cow_and_cactus/cow_and_cactus.py,sha256=tsVZZMNUtmJ5mj2thw3z7_n6zW0l8IAC9gtnFyfKcrk,3648
22
+ puzzle_solver/puzzles/dominosa/dominosa.py,sha256=YezNDc7RHUmOe3Ip6G2aHY0PadYGyo7CDacxplE2-9E,3362
23
+ puzzle_solver/puzzles/filling/filling.py,sha256=ILgGJqVI7yd6HPvWeKEsr620qorxLtAQYKTq65PqarY,4952
24
+ puzzle_solver/puzzles/flip/flip.py,sha256=cei6irTfntqctaLS3-rdYu8M2tw7ibzlHHvTIHW18yo,3518
25
+ puzzle_solver/puzzles/flood_it/flood_it.py,sha256=jnCtH1sZIt6K4hbQDSsiM1Cd8FjQNP7cfw2ObUW5fEQ,7948
26
+ puzzle_solver/puzzles/flood_it/parse_map/parse_map.py,sha256=m7gcpvN3THZdYLowdR_Jwx3HyttaV4K2DqrX_U7uFqU,8209
27
+ puzzle_solver/puzzles/galaxies/galaxies.py,sha256=IiKPU3fz5Aokhj5OjeT5jd_vdNuWnLzjZylGOTepsNU,5600
28
+ puzzle_solver/puzzles/galaxies/parse_map/parse_map.py,sha256=XmFqVN_oRfq9AZFWy5ViUJ2Szjgx-srrRkFPJXEEyFo,9358
29
+ puzzle_solver/puzzles/guess/guess.py,sha256=MpyrF6YVu0S1fzX-BllwxGKRGacWJpeLbNn5GetuEyo,10792
30
+ puzzle_solver/puzzles/heyawake/heyawake.py,sha256=HwCg1MQr6K742S-L5PjrkM1YYZWEFGV5gurTgT7q-6Q,9086
31
+ puzzle_solver/puzzles/hidden_stars/hidden_stars.py,sha256=LkzcDJNBZmuQJHyuEQz3YJJIZ8AHOj0FEwsl_uXvoxM,3154
32
+ puzzle_solver/puzzles/hidoku/hidoku.py,sha256=9Bj4ObmdgL6MCAYkPUTPj9fv0nr5XkJ92uNxSPgqqEA,3414
33
+ puzzle_solver/puzzles/inertia/inertia.py,sha256=-Y5fr7aK20zwmGHsZql7pYCq1kyMZglvkVZ6uIDf1HA,5658
34
+ puzzle_solver/puzzles/inertia/tsp.py,sha256=mAhlSjCWespASeN8uLZ0JkYDw-ZqFEpal6NM-ubpCXw,15313
35
+ puzzle_solver/puzzles/inertia/parse_map/parse_map.py,sha256=x0d64gTBd0HC2lO5uOpX2VKWfwj8rRiz0mQM_lqNmWs,8457
36
+ puzzle_solver/puzzles/kakurasu/kakurasu.py,sha256=ts4oPrFNeakNGi1rgAd6CIVQgeWSgnBGtA3qev_6ffY,1894
37
+ puzzle_solver/puzzles/kakuro/kakuro.py,sha256=9Sk-Q3EQsflyaJo5zywV-OpJn4WnNbbNAfiMFzN00F4,4440
38
+ puzzle_solver/puzzles/kakuro/krypto_kakuro.py,sha256=pppZk_gnl249Wta_2Um-tMDB-HLb7itkOgecCozfak4,5598
39
+ puzzle_solver/puzzles/keen/keen.py,sha256=0sxLE-fN5LpebT4EnDFe4WR2D4LG98A-My4OfJGTRuQ,4197
40
+ puzzle_solver/puzzles/kropki/kropki.py,sha256=fmmmACrRVqbsYSAcMT0aF3puijEND9URXMrZeywr_gQ,5821
41
+ puzzle_solver/puzzles/light_up/light_up.py,sha256=a6TLaGnCfzhB2GMf9qmNx2xK_oCPRA1SSrmGYWjasIw,2898
42
+ puzzle_solver/puzzles/linesweeper/linesweeper.py,sha256=LOj86CygiN00RUyG9IgZK5MQIsqyhcvVrivmzhHlf-w,4027
43
+ puzzle_solver/puzzles/link_a_pix/link_a_pix.py,sha256=1xh8c9V7FdtWrARWkya-8Z5fWP2o6BFj7PKf-ybSsbs,4752
44
+ puzzle_solver/puzzles/lits/lits.py,sha256=QeXFiCnWNI9jzUj3QLgU20mXxE2DD9o4BJsUb0TswKk,7231
45
+ puzzle_solver/puzzles/magnets/magnets.py,sha256=APg41g7_xSVUqHbSUxcWicnlmSKzkpnG2yXgQ0qMZmE,5437
46
+ puzzle_solver/puzzles/map/map.py,sha256=sxc57tapB8Tsgam-yoDitln1o-EB_SbIYvO6WEYy3us,2582
47
+ puzzle_solver/puzzles/mathema_grids/mathema_grids.py,sha256=wXj3pfXUMh3deFA6XXndZXod6ZNyCVt9vX1akt9zz20,6380
48
+ puzzle_solver/puzzles/mathrax/mathrax.py,sha256=qMO7h7pb3pRDw1hjJTqU8ALvARrajfTT8yuKzbTYRIM,4854
49
+ puzzle_solver/puzzles/minesweeper/minesweeper.py,sha256=vRckTvI5LeaWJ8PMG0applOQtiZWg430Sa3Sdf2RaW8,5865
50
+ puzzle_solver/puzzles/mosaic/mosaic.py,sha256=T89tkyTbob3LT20vwY3hkkEtNi8bxp2_CLaVi1gzhBc,1974
51
+ puzzle_solver/puzzles/n_queens/n_queens.py,sha256=ii1w0Pi0lcQz_XT1gkpNYlkWgwW_Jtvm0HL8OAg9fRY,4161
52
+ puzzle_solver/puzzles/nonograms/nonograms.py,sha256=Q-VHI0IPR2igccnE617HPThj5tnBgt27MiLWIZPtYcI,5587
53
+ puzzle_solver/puzzles/nonograms/nonograms_colored.py,sha256=XpxzpJw0GA-tE7PiltlA-dfypaTvqNIDLnKl1LxIjD4,10500
54
+ puzzle_solver/puzzles/norinori/norinori.py,sha256=ZEDWrD7zvEuqXOdXGOrELh1n_mWzhzZa3chs6Zqd3Pc,4570
55
+ puzzle_solver/puzzles/number_path/number_path.py,sha256=3m-3rdcZCXHSRspAWYlDq9GIUGmvO6SgDtRn4jsVsSc,4468
56
+ puzzle_solver/puzzles/numbermaze/numbermaze.py,sha256=xcr-fB7tjFVgWnZI4e0IBRmKLOdbf8XAOH_s7h-baY0,6095
57
+ puzzle_solver/puzzles/nurikabe/nurikabe.py,sha256=hX0VcjPwO8PfY2kiIpQV45FWIvKRosFebk588tp5wzk,6603
58
+ puzzle_solver/puzzles/palisade/palisade.py,sha256=rUYI39DOxKu_XSJ-Y_mdQGpGM-YLgKw_kjDWes97ncQ,4701
59
+ puzzle_solver/puzzles/pearl/pearl.py,sha256=slPVCzPObQLNk4EYqe55YR4JeRCUs07Mjdks1fWKZSY,6696
60
+ puzzle_solver/puzzles/pipes/pipes.py,sha256=2HDHCWhD-fLYTRoJsx15gOrsgt_SbjlGF2QDS5UX6m8,4680
61
+ puzzle_solver/puzzles/range/range.py,sha256=4qxueHR2A1tbtx1lgIzHBW7v-vtmydxr6k1KhX9VtB8,3060
62
+ puzzle_solver/puzzles/rectangles/rectangles.py,sha256=6MuJHyw7woIljlqxt76zfhw8F2_2biKMFM7oiXdXZsg,7010
63
+ puzzle_solver/puzzles/ripple_effect/ripple_effect.py,sha256=a-Va-nAoS81j6DMozg4vBtwrkyRVX7ybqK_ZYgbfU0U,4433
64
+ puzzle_solver/puzzles/rooms/rooms.py,sha256=VDOo-uqSW_y0ErpKPXSv-gcoftt30MF71D_e0YPCIaE,4297
65
+ puzzle_solver/puzzles/schurs_numbers/schurs_numbers.py,sha256=EXaHpRZVlUh8xCjRFAy38V1c-4dc3dLltwemBiNgtoQ,2836
66
+ puzzle_solver/puzzles/shakashaka/shakashaka.py,sha256=SyyNVmSAkoZ0Ca-dupl2r7zcsGgnwiCK0vRrmlTncaM,9572
67
+ puzzle_solver/puzzles/shingoki/shingoki.py,sha256=rhKRRUz1sCdIb3yflUAkmwXfCONYOB0AOmWKXs-NNn4,6893
68
+ puzzle_solver/puzzles/signpost/signpost.py,sha256=38LlMvP5Fx4qrTXmw4aNCt3yUbG3fhdSk6-YXmhAHFg,3861
69
+ puzzle_solver/puzzles/singles/singles.py,sha256=X9NygVPZG8WT03HzijlbbTwVsvcsk5H8tZHBjxRt5aU,2910
70
+ puzzle_solver/puzzles/slant/slant.py,sha256=l4q9g0BfqQsA6zsySiemJC5iFsqsO6LoqTUqcTEqYEk,5897
71
+ puzzle_solver/puzzles/slant/parse_map/parse_map.py,sha256=8thQxWbq0qjehKb2VzgUP22PGj-9n9djwbt3LGMVLJw,4811
72
+ puzzle_solver/puzzles/slitherlink/slitherlink.py,sha256=cDAM-aLHschlWVfzx6TDt_TGklRvsCazBlhn5ZrCRb8,7119
73
+ puzzle_solver/puzzles/snail/snail.py,sha256=KZq9gsGDhWwn8CsY1-kMjgc4lvycUEB4BLFe0qbVThs,4315
74
+ puzzle_solver/puzzles/split_ends/split_ends.py,sha256=SkWFmQSSKEYItpI1dpgPGKVoMLRJifQ3dMKMLDNQBc4,5667
75
+ puzzle_solver/puzzles/star_battle/star_battle.py,sha256=K0_iekluZlQs34jLb-KOYmL6ZsyVKjd5NInCwUXcXgs,3843
76
+ puzzle_solver/puzzles/star_battle/star_battle_shapeless.py,sha256=lj05V0Y3A3NjMo1boMkPIwBhMtm6SWydjgAMeCf5EIo,225
77
+ puzzle_solver/puzzles/stitches/stitches.py,sha256=wj9z9979FaVtp2hcVu3YDTGlxtwljoQNBgMMM5U3icc,6109
78
+ puzzle_solver/puzzles/stitches/parse_map/parse_map.py,sha256=6BWrW2U2jnBeremQP2zEZA3V2Gu4K3TeTha8cBCylXs,11655
79
+ puzzle_solver/puzzles/sudoku/sudoku.py,sha256=-vYpcoEuTcvvCyP9-yQTMmqVpljBTeu5mkHAXED9cRk,13272
80
+ puzzle_solver/puzzles/suguru/suguru.py,sha256=pK7_ogW6SaqimMiZcZ-V0JgITbEvPnaTE_KUKiPoP_o,3077
81
+ puzzle_solver/puzzles/suko/suko.py,sha256=22q0h5DO44tRjvy1HFbu_-msMf6D_JuxPtReZJjRQBY,3176
82
+ puzzle_solver/puzzles/tapa/tapa.py,sha256=UkOFhVoBn_Z3ssbdjpoTbTiEuKjrEJAROqd8CMQ9_-M,5319
83
+ puzzle_solver/puzzles/tatami/tatami.py,sha256=9AiIj3-8Hb6rlSdXyBTaQ9U5FvwN3NRLt3Z683Yb9HQ,3993
84
+ puzzle_solver/puzzles/tents/tents.py,sha256=RRNJ9uafrGyI_SYtNpw9CfbNlKeYfMA5K472w8mujf8,4941
85
+ puzzle_solver/puzzles/thermometers/thermometers.py,sha256=bGcVmpPeqL5AJtj8jkK8gYThzv9aGCd_QrWEiYBCA2s,4011
86
+ puzzle_solver/puzzles/towers/towers.py,sha256=t019GNQooeMC_XvuxRqZjoZUrPhHZP7WIEQTAl9NjwY,4733
87
+ puzzle_solver/puzzles/tracks/tracks.py,sha256=oJuMaF1oeh5moXl_TABFxpKBsDdwcGI9BvG3dm5J0NY,4903
88
+ puzzle_solver/puzzles/trees_logic/trees_logic.py,sha256=bHLH1OPncGmOTQzsRgk6lQFQgaTBrzrIA1AkKECPN24,2787
89
+ puzzle_solver/puzzles/troix/dumplings.py,sha256=75GuHf2QS50u9Y2MAvjxt_5_xoS0jkN0XvFslNOM7uc,189
90
+ puzzle_solver/puzzles/troix/troix.py,sha256=TdWSm4YOSaBY9ehp5mgxkO54HMlSfCQLV-FvBDIbaWA,4240
91
+ puzzle_solver/puzzles/twiddle/twiddle.py,sha256=3gPoeD0DoiiZbIhtptdXFldO_t1QShL6IxkDqJMzjkk,5446
92
+ puzzle_solver/puzzles/undead/undead.py,sha256=Gv6oPs2j2BLMN58FL96_V50ImWLmR_VPwF3owlPJEsY,5748
93
+ puzzle_solver/puzzles/unequal/unequal.py,sha256=ExY2XDCrqROCDpRLfHo8uVr1zuli1QvbCdNCiDhlCac,6978
94
+ puzzle_solver/puzzles/unruly/unruly.py,sha256=_C6FhYm9rqwhlQa6TMTxYr3rWcP_QS-E93xo86CZQjg,2846
95
+ puzzle_solver/puzzles/vectors/vectors.py,sha256=kq0dB7JbtJaisFYu2WEkzwNiT-n6luP-fdVC8w0bCZY,4825
96
+ puzzle_solver/puzzles/vermicelli/vermicelli.py,sha256=C95Fklf9UAuELXmlMUfeVxS13rUUxVAkrmtqsBpm-6M,3904
97
+ puzzle_solver/puzzles/walls/walls.py,sha256=JPV8uQg4zq6UsDzB0MhdB9SqP8kb5ANaiytkEMBGi1Q,3044
98
+ puzzle_solver/puzzles/yajilin/yajilin.py,sha256=2_0nZzrWAvmuYVZ9KRH7T-XTdZU7iuwQggp2M8MIHuA,5226
99
+ puzzle_solver/puzzles/yin_yang/yin_yang.py,sha256=D0JacUdK5yPrfScmGqX-p8144VbwxfDgIaqF8hwLXlM,5039
100
+ puzzle_solver/puzzles/yin_yang/parse_map/parse_map.py,sha256=drjfoHqmFf6U-ZQUwrBbfGINRxDQpgbvy4U3D9QyMhM,6617
101
+ puzzle_solver/utils/visualizer.py,sha256=T2g5We9J3tkhyXWoN2OrIDIJDjt6w5sDd2ksOub0ZI8,6819
102
+ puzzle_solver/utils/etc/parser/board_color_digit.py,sha256=EnF1Cs72DiNPYbTrLyNkE8OiiijGF59yC7vrai5Biho,15627
103
+ multi_puzzle_solver-1.1.8.dist-info/METADATA,sha256=0D7be2kfV2f1160j3WFWhjgzRcTCbv48bhjK2NvAog8,326933
104
+ multi_puzzle_solver-1.1.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
105
+ multi_puzzle_solver-1.1.8.dist-info/top_level.txt,sha256=exwVUQa-anK9vYrpKzBPvH8bX43iElWI4VeNiAyBGJY,14
106
+ multi_puzzle_solver-1.1.8.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ puzzle_solver
@@ -0,0 +1,184 @@
1
+ from puzzle_solver.puzzles.abc_view import abc_view as abc_view_solver
2
+ from puzzle_solver.puzzles.aquarium import aquarium as aquarium_solver
3
+ from puzzle_solver.puzzles.area_51 import area_51 as area_51_solver
4
+ from puzzle_solver.puzzles.battleships import battleships as battleships_solver
5
+ from puzzle_solver.puzzles.binairo import binairo as binairo_solver
6
+ from puzzle_solver.puzzles.binairo import binairo_plus as binairo_plus_solver
7
+ from puzzle_solver.puzzles.black_box import black_box as black_box_solver
8
+ from puzzle_solver.puzzles.branches import branches as branches_solver
9
+ from puzzle_solver.puzzles.bridges import bridges as bridges_solver
10
+ from puzzle_solver.puzzles.chess_range import chess_range as chess_range_solver
11
+ from puzzle_solver.puzzles.chess_range import chess_solo as chess_solo_solver
12
+ from puzzle_solver.puzzles.chess_range import chess_melee as chess_melee_solver
13
+ from puzzle_solver.puzzles.circle_9 import circle_9 as circle_9_solver
14
+ from puzzle_solver.puzzles.clouds import clouds as clouds_solver
15
+ from puzzle_solver.puzzles.connect_the_dots import connect_the_dots as connect_the_dots_solver
16
+ from puzzle_solver.puzzles.cow_and_cactus import cow_and_cactus as cow_and_cactus_solver
17
+ from puzzle_solver.puzzles.dominosa import dominosa as dominosa_solver
18
+ from puzzle_solver.puzzles.troix import dumplings as dumplings_solver
19
+ from puzzle_solver.puzzles.filling import filling as filling_solver
20
+ from puzzle_solver.puzzles.flood_it import flood_it as flood_it_solver
21
+ from puzzle_solver.puzzles.flip import flip as flip_solver
22
+ from puzzle_solver.puzzles.galaxies import galaxies as galaxies_solver
23
+ from puzzle_solver.puzzles.guess import guess as guess_solver
24
+ from puzzle_solver.puzzles.heyawake import heyawake as heyawake_solver
25
+ from puzzle_solver.puzzles.hidden_stars import hidden_stars as hidden_stars_solver
26
+ from puzzle_solver.puzzles.hidoku import hidoku as hidoku_solver
27
+ from puzzle_solver.puzzles.inertia import inertia as inertia_solver
28
+ from puzzle_solver.puzzles.kakurasu import kakurasu as kakurasu_solver
29
+ from puzzle_solver.puzzles.kakuro import kakuro as kakuro_solver
30
+ from puzzle_solver.puzzles.keen import keen as keen_solver
31
+ from puzzle_solver.puzzles.kropki import kropki as kropki_solver
32
+ from puzzle_solver.puzzles.kakuro import krypto_kakuro as krypto_kakuro_solver
33
+ from puzzle_solver.puzzles.light_up import light_up as light_up_solver
34
+ from puzzle_solver.puzzles.linesweeper import linesweeper as linesweeper_solver
35
+ from puzzle_solver.puzzles.link_a_pix import link_a_pix as link_a_pix_solver
36
+ from puzzle_solver.puzzles.magnets import magnets as magnets_solver
37
+ from puzzle_solver.puzzles.map import map as map_solver
38
+ from puzzle_solver.puzzles.mathema_grids import mathema_grids as mathema_grids_solver
39
+ from puzzle_solver.puzzles.mathrax import mathrax as mathrax_solver
40
+ from puzzle_solver.puzzles.minesweeper import minesweeper as minesweeper_solver
41
+ from puzzle_solver.puzzles.mosaic import mosaic as mosaic_solver
42
+ from puzzle_solver.puzzles.n_queens import n_queens as n_queens_solver
43
+ from puzzle_solver.puzzles.nonograms import nonograms as nonograms_solver
44
+ from puzzle_solver.puzzles.nonograms import nonograms_colored as nonograms_colored_solver
45
+ from puzzle_solver.puzzles.norinori import norinori as norinori_solver
46
+ from puzzle_solver.puzzles.number_path import number_path as number_path_solver
47
+ from puzzle_solver.puzzles.numbermaze import numbermaze as numbermaze_solver
48
+ from puzzle_solver.puzzles.nurikabe import nurikabe as nurikabe_solver
49
+ from puzzle_solver.puzzles.palisade import palisade as palisade_solver
50
+ from puzzle_solver.puzzles.lits import lits as lits_solver
51
+ from puzzle_solver.puzzles.pearl import pearl as pearl_solver
52
+ from puzzle_solver.puzzles.pipes import pipes as pipes_solver
53
+ from puzzle_solver.puzzles.range import range as range_solver
54
+ from puzzle_solver.puzzles.rectangles import rectangles as rectangles_solver
55
+ from puzzle_solver.puzzles.ripple_effect import ripple_effect as ripple_effect_solver
56
+ from puzzle_solver.puzzles.rooms import rooms as rooms_solver
57
+ from puzzle_solver.puzzles.schurs_numbers import schurs_numbers as schurs_numbers_solver
58
+ from puzzle_solver.puzzles.shakashaka import shakashaka as shakashaka_solver
59
+ from puzzle_solver.puzzles.shingoki import shingoki as shingoki_solver
60
+ from puzzle_solver.puzzles.signpost import signpost as signpost_solver
61
+ from puzzle_solver.puzzles.singles import singles as singles_solver
62
+ from puzzle_solver.puzzles.slant import slant as slant_solver
63
+ from puzzle_solver.puzzles.slitherlink import slitherlink as slitherlink_solver
64
+ from puzzle_solver.puzzles.snail import snail as snail_solver
65
+ from puzzle_solver.puzzles.split_ends import split_ends as split_ends_solver
66
+ from puzzle_solver.puzzles.star_battle import star_battle as star_battle_solver
67
+ from puzzle_solver.puzzles.star_battle import star_battle_shapeless as star_battle_shapeless_solver
68
+ from puzzle_solver.puzzles.stitches import stitches as stitches_solver
69
+ from puzzle_solver.puzzles.sudoku import sudoku as sudoku_solver
70
+ from puzzle_solver.puzzles.suguru import suguru as suguru_solver
71
+ from puzzle_solver.puzzles.suko import suko as suko_solver
72
+ from puzzle_solver.puzzles.tapa import tapa as tapa_solver
73
+ from puzzle_solver.puzzles.tatami import tatami as tatami_solver
74
+ from puzzle_solver.puzzles.tents import tents as tents_solver
75
+ from puzzle_solver.puzzles.thermometers import thermometers as thermometers_solver
76
+ from puzzle_solver.puzzles.towers import towers as towers_solver
77
+ from puzzle_solver.puzzles.tracks import tracks as tracks_solver
78
+ from puzzle_solver.puzzles.trees_logic import trees_logic as trees_logic_solver
79
+ from puzzle_solver.puzzles.troix import troix as troix_solver
80
+ from puzzle_solver.puzzles.twiddle import twiddle as twiddle_solver
81
+ from puzzle_solver.puzzles.undead import undead as undead_solver
82
+ from puzzle_solver.puzzles.unequal import unequal as unequal_solver
83
+ from puzzle_solver.puzzles.unruly import unruly as unruly_solver
84
+ from puzzle_solver.puzzles.vectors import vectors as vectors_solver
85
+ from puzzle_solver.puzzles.vermicelli import vermicelli as vermicelli_solver
86
+ from puzzle_solver.puzzles.walls import walls as walls_solver
87
+ from puzzle_solver.puzzles.yajilin import yajilin as yajilin_solver
88
+ from puzzle_solver.puzzles.yin_yang import yin_yang as yin_yang_solver
89
+
90
+ from puzzle_solver.puzzles.inertia.parse_map.parse_map import main as inertia_image_parser
91
+
92
+ __all__ = [
93
+ abc_view_solver,
94
+ aquarium_solver,
95
+ area_51_solver,
96
+ battleships_solver,
97
+ binairo_solver,
98
+ binairo_plus_solver,
99
+ black_box_solver,
100
+ branches_solver,
101
+ bridges_solver,
102
+ chess_range_solver,
103
+ chess_solo_solver,
104
+ chess_melee_solver,
105
+ circle_9_solver,
106
+ clouds_solver,
107
+ connect_the_dots_solver,
108
+ cow_and_cactus_solver,
109
+ dominosa_solver,
110
+ dumplings_solver,
111
+ filling_solver,
112
+ flood_it_solver,
113
+ flip_solver,
114
+ galaxies_solver,
115
+ guess_solver,
116
+ heyawake_solver,
117
+ hidden_stars_solver,
118
+ hidoku_solver,
119
+ inertia_solver,
120
+ kakurasu_solver,
121
+ kakuro_solver,
122
+ keen_solver,
123
+ kropki_solver,
124
+ krypto_kakuro_solver,
125
+ light_up_solver,
126
+ linesweeper_solver,
127
+ link_a_pix_solver,
128
+ magnets_solver,
129
+ map_solver,
130
+ mathema_grids_solver,
131
+ mathrax_solver,
132
+ minesweeper_solver,
133
+ mosaic_solver,
134
+ n_queens_solver,
135
+ nonograms_solver,
136
+ norinori_solver,
137
+ number_path_solver,
138
+ numbermaze_solver,
139
+ nonograms_colored_solver,
140
+ nurikabe_solver,
141
+ palisade_solver,
142
+ lits_solver,
143
+ pearl_solver,
144
+ pipes_solver,
145
+ range_solver,
146
+ rectangles_solver,
147
+ ripple_effect_solver,
148
+ rooms_solver,
149
+ schurs_numbers_solver,
150
+ shakashaka_solver,
151
+ shingoki_solver,
152
+ signpost_solver,
153
+ singles_solver,
154
+ slant_solver,
155
+ slitherlink_solver,
156
+ snail_solver,
157
+ split_ends_solver,
158
+ star_battle_solver,
159
+ star_battle_shapeless_solver,
160
+ stitches_solver,
161
+ sudoku_solver,
162
+ suguru_solver,
163
+ suko_solver,
164
+ tapa_solver,
165
+ tatami_solver,
166
+ tents_solver,
167
+ thermometers_solver,
168
+ towers_solver,
169
+ tracks_solver,
170
+ trees_logic_solver,
171
+ troix_solver,
172
+ twiddle_solver,
173
+ undead_solver,
174
+ unequal_solver,
175
+ unruly_solver,
176
+ vectors_solver,
177
+ vermicelli_solver,
178
+ walls_solver,
179
+ yajilin_solver,
180
+ yin_yang_solver,
181
+ inertia_image_parser,
182
+ ]
183
+
184
+ __version__ = '1.1.8'
@@ -0,0 +1,298 @@
1
+ from dataclasses import dataclass
2
+ from typing import Tuple, Iterable, Union
3
+ from enum import Enum
4
+
5
+ import numpy as np
6
+
7
+
8
+ class Direction(Enum):
9
+ UP = 1
10
+ DOWN = 2
11
+ LEFT = 3
12
+ RIGHT = 4
13
+
14
+ class Direction8(Enum):
15
+ UP = 1
16
+ DOWN = 2
17
+ LEFT = 3
18
+ RIGHT = 4
19
+ UP_LEFT = 5
20
+ UP_RIGHT = 6
21
+ DOWN_LEFT = 7
22
+ DOWN_RIGHT = 8
23
+
24
+ @dataclass(frozen=True, order=True)
25
+ class Pos:
26
+ x: int
27
+ y: int
28
+
29
+ def __add__(self, other: 'Pos') -> 'Pos':
30
+ return get_pos(self.x + other.x, self.y + other.y)
31
+
32
+
33
+ Shape = frozenset[Pos] # a shape on the 2d board is just a set of positions
34
+
35
+
36
+ def get_pos(x: int, y: int) -> Pos:
37
+ return Pos(x=x, y=y)
38
+
39
+
40
+ def get_next_pos(cur_pos: Pos, direction: Union[Direction, Direction8]) -> Pos:
41
+ delta_x, delta_y = get_deltas(direction)
42
+ return get_pos(cur_pos.x+delta_x, cur_pos.y+delta_y)
43
+
44
+
45
+ def get_ray(pos: Pos, direction: Union[Direction, Direction8], V: int, H: int, include_self: bool = False) -> list[Pos]:
46
+ out = []
47
+ if include_self:
48
+ out.append(pos)
49
+ while True:
50
+ pos = get_next_pos(pos, direction)
51
+ if not in_bounds(pos, V, H):
52
+ break
53
+ out.append(pos)
54
+ return out
55
+
56
+
57
+ def get_neighbors4(pos: Pos, V: int, H: int, include_self: bool = False) -> Iterable[Pos]:
58
+ if include_self:
59
+ yield pos
60
+ for dx, dy in ((1,0),(-1,0),(0,1),(0,-1)):
61
+ p2 = get_pos(x=pos.x+dx, y=pos.y+dy)
62
+ if in_bounds(p2, V, H):
63
+ yield p2
64
+
65
+
66
+ def get_neighbors8(pos: Pos, V: int, H: int, include_self: bool = False) -> Iterable[Pos]:
67
+ for dx in [-1, 0, 1]:
68
+ for dy in [-1, 0, 1]:
69
+ if not include_self and (dx, dy) == (0, 0):
70
+ continue
71
+ d_pos = get_pos(x=pos.x+dx, y=pos.y+dy)
72
+ if in_bounds(d_pos, V, H):
73
+ yield d_pos
74
+
75
+
76
+ def get_row_pos(row_idx: int, H: int) -> Iterable[Pos]:
77
+ for x in range(H):
78
+ yield get_pos(x=x, y=row_idx)
79
+
80
+
81
+ def get_col_pos(col_idx: int, V: int) -> Iterable[Pos]:
82
+ for y in range(V):
83
+ yield get_pos(x=col_idx, y=y)
84
+
85
+
86
+ def get_all_pos(V: int, H: int) -> Iterable[Pos]:
87
+ for y in range(V):
88
+ for x in range(H):
89
+ yield get_pos(x=x, y=y)
90
+
91
+
92
+ def get_all_pos_to_idx_dict(V: int, H: int) -> dict[Pos, int]:
93
+ return {get_pos(x=x, y=y): y*H+x for y in range(V) for x in range(H)}
94
+
95
+
96
+ def get_char(board: np.array, pos: Pos) -> str:
97
+ return board[pos.y][pos.x]
98
+
99
+
100
+ def set_char(board: np.array, pos: Pos, char: str):
101
+ board[pos.y][pos.x] = char
102
+
103
+
104
+ def in_bounds(pos: Pos, V: int, H: int) -> bool:
105
+ return 0 <= pos.y < V and 0 <= pos.x < H
106
+
107
+
108
+ def get_opposite_direction(direction: Union[Direction, Direction8]) -> Union[Direction, Direction8]:
109
+ if direction == Direction.RIGHT:
110
+ return Direction.LEFT
111
+ elif direction == Direction.LEFT:
112
+ return Direction.RIGHT
113
+ elif direction == Direction.DOWN:
114
+ return Direction.UP
115
+ elif direction == Direction.UP:
116
+ return Direction.DOWN
117
+ elif direction == Direction8.RIGHT:
118
+ return Direction8.LEFT
119
+ elif direction == Direction8.LEFT:
120
+ return Direction8.RIGHT
121
+ elif direction == Direction8.DOWN:
122
+ return Direction8.UP
123
+ elif direction == Direction8.UP:
124
+ return Direction8.DOWN
125
+ elif direction == Direction8.UP_LEFT:
126
+ return Direction8.DOWN_RIGHT
127
+ elif direction == Direction8.UP_RIGHT:
128
+ return Direction8.DOWN_LEFT
129
+ elif direction == Direction8.DOWN_LEFT:
130
+ return Direction8.UP_RIGHT
131
+ elif direction == Direction8.DOWN_RIGHT:
132
+ return Direction8.UP_LEFT
133
+ else:
134
+ raise ValueError(f'invalid direction: {direction}')
135
+
136
+
137
+ def get_deltas(direction: Union[Direction, Direction8]) -> Tuple[int, int]:
138
+ if direction == Direction.RIGHT or direction == Direction8.RIGHT:
139
+ return +1, 0
140
+ elif direction == Direction.LEFT or direction == Direction8.LEFT:
141
+ return -1, 0
142
+ elif direction == Direction.DOWN or direction == Direction8.DOWN:
143
+ return 0, +1
144
+ elif direction == Direction.UP or direction == Direction8.UP:
145
+ return 0, -1
146
+ elif direction == Direction8.UP_LEFT:
147
+ return -1, -1
148
+ elif direction == Direction8.UP_RIGHT:
149
+ return +1, -1
150
+ elif direction == Direction8.DOWN_LEFT:
151
+ return -1, +1
152
+ elif direction == Direction8.DOWN_RIGHT:
153
+ return +1, +1
154
+ else:
155
+ raise ValueError(f'invalid direction: {direction}')
156
+
157
+
158
+ def polyominoes(N) -> set[Shape]:
159
+ """Generate all polyominoes of size N. Every rotation and reflection is considered different and included in the result.
160
+ Translation is not considered different and is removed from the result (otherwise the result would be infinite).
161
+
162
+ Below is the number of unique polyominoes of size N (not including rotations and reflections) and the lenth of the returned result (which includes all rotations and reflections)
163
+ N name #shapes #results
164
+ 1 monomino 1 1
165
+ 2 domino 1 2
166
+ 3 tromino 2 6
167
+ 4 tetromino 5 19
168
+ 5 pentomino 12 63
169
+ 6 hexomino 35 216
170
+ 7 heptomino 108 760
171
+ 8 octomino 369 2,725
172
+ 9 nonomino 1,285 9,910
173
+ 10 decomino 4,655 36,446
174
+ 11 undecomino 17,073 135,268
175
+ 12 dodecomino 63,600 505,861
176
+ Source: https://en.wikipedia.org/wiki/Polyomino
177
+
178
+ Args:
179
+ N (int): The size of the polyominoes to generate.
180
+
181
+ Returns:
182
+ set[(frozenset[Pos], int)]: A set of all polyominoes of size N (rotated and reflected up to D4 symmetry).
183
+ """
184
+ assert N >= 1, 'N cannot be less than 1'
185
+ # need a frozenset because regular sets are not hashable
186
+ FastShape = frozenset[Tuple[int, int]]
187
+ shapes: set[FastShape] = {frozenset({(0, 0)})}
188
+ for i in range(1, N):
189
+ next_shapes: set[FastShape] = set()
190
+ directions = ((1,0),(-1,0),(0,1),(0,-1)) if i > 1 else (((1,0),(0,1))) # cannot take left on first step, if confused read: https://louridas.github.io/rwa/assignments/polyominoes/
191
+ for s in shapes:
192
+ # frontier of a single shape: all 4-neighbors of existing cells not already in the shape
193
+ frontier = set()
194
+ for x, y in s:
195
+ # only need to consider 3 directions and neighbors condition is (n.y > 0 or (n.y == 0 and n.x >= 0)) it's obvious if you plot it
196
+ # if confused read: https://louridas.github.io/rwa/assignments/polyominoes/
197
+ for dx, dy in directions:
198
+ n = (x + dx, y + dy)
199
+ if n not in s and (n[1] > 0 or (n[1] == 0 and n[0] >= 0)):
200
+ frontier.add(n)
201
+ for cell in frontier:
202
+ t = s | {cell}
203
+ # normalize by translation only: shift so min x,y is (0,0). This removes translational symmetries.
204
+ minx = min(x for x, y in t)
205
+ miny = min(y for x, y in t)
206
+ t0 = frozenset((x - minx, y - miny) for x, y in t)
207
+ next_shapes.add(t0)
208
+ shapes = next_shapes
209
+ # shapes is now complete, now classify up to D4 symmetry (rotations/reflections), translations ignored
210
+ shapes = {frozenset(Pos(x, y) for x, y in s) for s in shapes} # regular class, not the dirty-fast one
211
+ return shapes
212
+
213
+
214
+ def polyominoes_with_shape_id(N):
215
+ """Refer to polyominoes() for more details. This function returns a set of all polyominoes of size N (rotated and reflected up to D4 symmetry) along with a unique ID for each polyomino that is unique up to D4 symmetry.
216
+ Args:
217
+ N (int): The size of the polyominoes to generate.
218
+
219
+ Returns:
220
+ set[(frozenset[Pos], int)]: A set of all polyominoes of size N (rotated and reflected up to D4 symmetry) along with a unique ID for each polyomino that is unique up to D4 symmetry.
221
+ """
222
+ FastPos = Tuple[int, int]
223
+ FastShape = frozenset[Tuple[int, int]]
224
+ shapes = polyominoes(N)
225
+ shapes = {frozenset((p.x, p.y) for p in s) for s in shapes}
226
+ mats = (
227
+ ( 1, 0, 0, 1), # regular
228
+ (-1, 0, 0, 1), # reflect about x
229
+ ( 1, 0, 0,-1), # reflect about y
230
+ (-1, 0, 0,-1), # reflect about x and y
231
+ # trnaspose then all 4 above
232
+ ( 0, 1, 1, 0), ( 0, 1, -1, 0), ( 0,-1, 1, 0), ( 0,-1, -1, 0),
233
+ )
234
+ # compute canonical representative for each shape (lexicographically smallest normalized transform)
235
+ shape_to_canon: dict[FastShape, tuple[FastPos, ...]] = {}
236
+ for s in shapes:
237
+ reps: list[tuple[FastPos, ...]] = []
238
+ for a, b, c, d in mats:
239
+ pts = {(a*x + b*y, c*x + d*y) for x, y in s}
240
+ minx = min(x for x, y in pts)
241
+ miny = min(y for x, y in pts)
242
+ rep = tuple(sorted((x - minx, y - miny) for x, y in pts))
243
+ reps.append(rep)
244
+ canon = min(reps)
245
+ shape_to_canon[s] = canon
246
+
247
+ canon_set = set(shape_to_canon.values())
248
+ canon_to_id = {canon: i for i, canon in enumerate(sorted(canon_set))}
249
+ result = {(s, canon_to_id[shape_to_canon[s]]) for s in shapes}
250
+ result = {(frozenset(Pos(x, y) for x, y in s), _id) for s, _id in result}
251
+ return result
252
+
253
+
254
+ def shapes_between(A_pos: Pos, B_pos: Pos, N: int) -> set[Shape]:
255
+ """Return all shapes of size N where it is possible to draw a path from A to B covering all cells in the shape."""
256
+ def manhattan(p: tuple[int, int], q: tuple[int, int]) -> int:
257
+ return abs(p[0] - q[0]) + abs(p[1] - q[1])
258
+ def is_possible(p: tuple[int, int], q: tuple[int, int], steps_needed: int) -> bool:
259
+ """Returns whether it's possible to reach q from p in exactly steps_needed steps. Looks at manhattan distance and parity."""
260
+ dist = manhattan(p, q)
261
+ return dist <= steps_needed and (steps_needed - dist) % 2 == 0 # the %2 checks parity (i.e. when its false every possible path will overshoot or undershoot B by 1)
262
+
263
+ A, B = (A_pos.x, A_pos.y), (B_pos.x, B_pos.y)
264
+ ax, ay = A
265
+ bx, by = B
266
+ min_steps_needed = N - 1
267
+ # how far we're allowed to wander away from the A-B box
268
+ slack = (min_steps_needed - manhattan(A, B)) // 2
269
+ x_min = min(ax, bx) - slack
270
+ x_max = max(ax, bx) + slack
271
+ y_min = min(ay, by) - slack
272
+ y_max = max(ay, by) + slack
273
+ results: set[Shape] = set()
274
+ path: list[tuple[int, int]] = [A]
275
+ visited = {A}
276
+
277
+ def dfs(curr: tuple[int, int], cells_used: int):
278
+ moves_left = N - cells_used
279
+ if not is_possible(curr, B, moves_left):
280
+ return
281
+ if curr == B:
282
+ if cells_used == N: # we've reached B and have exactly N cells, this is a wanted shape!
283
+ results.add(frozenset(get_pos(x=x, y=y) for x, y in path))
284
+ return
285
+ x, y = curr
286
+ for nx, ny in ((x+1, y), (x-1, y), (x, y+1), (x, y-1)):
287
+ if not (x_min <= nx <= x_max and y_min <= ny <= y_max):
288
+ continue
289
+ nxt = (nx, ny)
290
+ if nxt in visited:
291
+ continue
292
+ visited.add(nxt)
293
+ path.append(nxt)
294
+ dfs(nxt, cells_used + 1)
295
+ path.pop()
296
+ visited.remove(nxt)
297
+ dfs(A, 1)
298
+ return results