Solve a Numbrix puzzle: Difference between revisions
Content added Content deleted
(+ D entry) |
(Tcl implementation added) |
||
Line 1,057: | Line 1,057: | ||
</pre> |
</pre> |
||
[[Category:Puzzles]] |
[[Category:Puzzles]] |
||
=={{header|Tcl}}== |
|||
Following loosely the structure of [[Solve_a_Hidato_puzzle#Tcl]]. |
|||
<lang Tcl># Loop over adjacent pairs in a list. |
|||
# Example: |
|||
# % eachpair {a b} {1 2 3} {puts $a $b} |
|||
# 1 2 |
|||
# 2 3 |
|||
proc eachpair {varNames ls script} { |
|||
if {[lassign $varNames _i _j] ne ""} { |
|||
return -code error "Must supply exactly two arguments" |
|||
} |
|||
tailcall foreach $_i [lrange $ls 0 end-1] $_j [lrange $ls 1 end] $script |
|||
} |
|||
namespace eval numbrix { |
|||
namespace path {::tcl::mathop ::tcl::mathfunc} |
|||
proc parse {txt} { |
|||
set map [split [string trim $txt] \n] |
|||
} |
|||
proc print {map} { |
|||
join [lmap row $map { |
|||
join [lmap val $row { |
|||
format %2d $val |
|||
}] " " |
|||
}] \n |
|||
} |
|||
proc mark {map x y i} { |
|||
lset map $x $y $i |
|||
} |
|||
proc moves {x y} { |
|||
foreach {dx dy} { |
|||
0 1 |
|||
-1 0 1 0 |
|||
0 -1 |
|||
} { |
|||
lappend r [+ $dx $x] [+ $dy $y] |
|||
} |
|||
return $r |
|||
} |
|||
proc rmap {map} { ;# generate a reverse map in a dict {val {x y} ..} |
|||
set rmap {} |
|||
set h [llength $map] |
|||
set w [llength [lindex $map 0]] |
|||
set x $w |
|||
while {[incr x -1]>=0} { |
|||
set y $h |
|||
while {[incr y -1]>=0} { |
|||
set i [lindex $map $x $y] |
|||
if {$i} { |
|||
dict set rmap [lindex $map $x $y] [list $x $y] |
|||
} |
|||
} |
|||
} |
|||
return $rmap |
|||
} |
|||
proc gaps {rmap} { ;# list all the gaps to be filled |
|||
set known [lsort -integer [dict keys $rmap]] |
|||
set gaps {} |
|||
eachpair {i j} $known { |
|||
if {$j > $i+1} { |
|||
lappend gaps $i $j |
|||
} |
|||
} |
|||
return $gaps |
|||
} |
|||
proc fixgaps {map rmap gaps} { ;# add a "tail" gap if needed |
|||
set w [llength $map] |
|||
set h [llength [lindex $map 0]] |
|||
set size [* $h $w] |
|||
set max [max {*}[dict keys $rmap]] |
|||
if {$max ne $size} { |
|||
lappend gaps $max Inf |
|||
} |
|||
return $gaps |
|||
} |
|||
proc paths {map x0 y0 n} { ;# generate all the maps with a single path filled legally |
|||
if {$n == 0} {return [list $map]} |
|||
set i [lindex $map $x0 $y0] |
|||
set paths {} |
|||
foreach {x y} [moves $x0 $y0] { |
|||
set j [lindex $map $x $y] |
|||
if {$j eq ""} { |
|||
continue |
|||
} elseif {$j == 0 && $n == $n+1} { |
|||
return [list [mark $map $x $y [+ $i 1]]] |
|||
} elseif {$j == $i+1} { |
|||
lappend paths $map |
|||
continue |
|||
} elseif {$j || ($n == 1)} { |
|||
continue |
|||
} else { |
|||
lappend paths {*}[ |
|||
paths [ |
|||
mark $map $x $y [+ $i 1] |
|||
] $x $y [- $n 1] |
|||
] |
|||
} |
|||
} |
|||
return $paths |
|||
} |
|||
proc solve {map} { |
|||
# fixpoint map |
|||
while 1 { ;# first we iteratively fill in paths with distinct solutions |
|||
set rmap [rmap $map] |
|||
set gaps [gaps $rmap] |
|||
set gaps [fixgaps $map $rmap $gaps] |
|||
if {$gaps eq ""} { |
|||
return $map |
|||
} |
|||
set oldmap $map |
|||
foreach {i j} $gaps { |
|||
lassign [dict get $rmap $i] x0 y0 |
|||
set n [- $j $i] |
|||
set paths [paths $map $x0 $y0 $n] |
|||
if {$paths eq ""} { |
|||
return "" |
|||
} elseif {[llength $paths] == 1} { |
|||
#puts "solved $i..$j" |
|||
#puts [print $map] |
|||
set map [lindex $paths 0] |
|||
} |
|||
;# we could intersect the paths to maybe get some tiles |
|||
} |
|||
if {$map eq $oldmap} { |
|||
break |
|||
} |
|||
} |
|||
#puts "unique paths exhausted - going DFS" |
|||
try { ;# for any left over paths, go DFS |
|||
;# we might want to sort the gaps first |
|||
foreach {i j} $gaps { |
|||
lassign [dict get $rmap $i] x0 y0 |
|||
set n [- $j $i] |
|||
set paths [paths $map $x0 $y0 $n] |
|||
foreach path $paths { |
|||
#puts "recursing on $i..$j" |
|||
set sol [solve $path] |
|||
if {$sol ne ""} { |
|||
return $sol |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
namespace export {[a-z]*} |
|||
namespace ensemble create |
|||
} |
|||
set puzzles { |
|||
{ |
|||
0 0 0 0 0 0 0 0 0 |
|||
0 0 46 45 0 55 74 0 0 |
|||
0 38 0 0 43 0 0 78 0 |
|||
0 35 0 0 0 0 0 71 0 |
|||
0 0 33 0 0 0 59 0 0 |
|||
0 17 0 0 0 0 0 67 0 |
|||
0 18 0 0 11 0 0 64 0 |
|||
0 0 24 21 0 1 2 0 0 |
|||
0 0 0 0 0 0 0 0 0 |
|||
} |
|||
{ |
|||
0 0 0 0 0 0 0 0 0 |
|||
0 11 12 15 18 21 62 61 0 |
|||
0 6 0 0 0 0 0 60 0 |
|||
0 33 0 0 0 0 0 57 0 |
|||
0 32 0 0 0 0 0 56 0 |
|||
0 37 0 1 0 0 0 73 0 |
|||
0 38 0 0 0 0 0 72 0 |
|||
0 43 44 47 48 51 76 77 0 |
|||
0 0 0 0 0 0 0 0 0 |
|||
} |
|||
} |
|||
foreach puzzle $puzzles { |
|||
set map [numbrix parse $puzzle] |
|||
puts "\n== Puzzle [incr i] ==" |
|||
puts [numbrix print $map] |
|||
set sol [numbrix solve $map] |
|||
if {$sol ne ""} { |
|||
puts "\n== Solution $i ==" |
|||
puts [numbrix print $sol] |
|||
} else { |
|||
puts "\n== No Solution for Puzzle $i ==" |
|||
} |
|||
}</lang> |
|||
{{Out}} |
|||
<pre> |
|||
== Puzzle 1 == |
|||
0 0 0 0 0 0 0 0 0 |
|||
0 0 46 45 0 55 74 0 0 |
|||
0 38 0 0 43 0 0 78 0 |
|||
0 35 0 0 0 0 0 71 0 |
|||
0 0 33 0 0 0 59 0 0 |
|||
0 17 0 0 0 0 0 67 0 |
|||
0 18 0 0 11 0 0 64 0 |
|||
0 0 24 21 0 1 2 0 0 |
|||
0 0 0 0 0 0 0 0 0 |
|||
== Solution 1 == |
|||
49 50 51 52 53 54 75 76 81 |
|||
48 47 46 45 44 55 74 77 80 |
|||
37 38 39 40 43 56 73 78 79 |
|||
36 35 34 41 42 57 72 71 70 |
|||
31 32 33 14 13 58 59 68 69 |
|||
30 17 16 15 12 61 60 67 66 |
|||
29 18 19 20 11 62 63 64 65 |
|||
28 25 24 21 10 1 2 3 4 |
|||
27 26 23 22 9 8 7 6 5 |
|||
== Puzzle 2 == |
|||
0 0 0 0 0 0 0 0 0 |
|||
0 11 12 15 18 21 62 61 0 |
|||
0 6 0 0 0 0 0 60 0 |
|||
0 33 0 0 0 0 0 57 0 |
|||
0 32 0 0 0 0 0 56 0 |
|||
0 37 0 1 0 0 0 73 0 |
|||
0 38 0 0 0 0 0 72 0 |
|||
0 43 44 47 48 51 76 77 0 |
|||
0 0 0 0 0 0 0 0 0 |
|||
== Solution 2 == |
|||
9 10 13 14 19 20 63 64 65 |
|||
8 11 12 15 18 21 62 61 66 |
|||
7 6 5 16 17 22 59 60 67 |
|||
34 33 4 3 24 23 58 57 68 |
|||
35 32 31 2 25 54 55 56 69 |
|||
36 37 30 1 26 53 74 73 70 |
|||
39 38 29 28 27 52 75 72 71 |
|||
40 43 44 47 48 51 76 77 78 |
|||
41 42 45 46 49 50 81 80 79 |
|||
</pre> |