
HJG Added "Done"-Message when puzzle is completed. The website seems to have been "reorganized", the puzzle is now at http://invention.smithsonian.org/centerpieces/iap/playhouse_puzzle.html

uniquename 2013aug01Here is an image of the GUI starting with the pieces for the boat puzzle. The bird is above.
##+##########################################################################
#
# Puzzle Blocks
# by Keith Vetter, May 9, 2003
# see http://invention.smithsonian.org/centerpieces/iap/playhouse_puzzle.html
#
# TODO
# allow identical pieces to go into any identical spot
package require Tk
set S(title) "Puzzle Blocks"
set S(msg) ""
set S(snap) 10 ;# "Close enough" distance
proc DoDisplay {} {
wm title . $::S(title)
pack [frame .ctrl -relief ridge -bd 2 -padx 5 -pady 5] \
-side right -fill both -ipady 5
pack [frame .screen -bd 2 -relief raised] -side top -fill both -expand 1
canvas .c -relief raised -borderwidth 0 -height 500 -width 500
button .bird -text "Bird Puzzle" -command {GoPuzzle Bird} -bd 4
.bird configure -font "[font actual [.bird cget -font]] -weight bold"
option add *font [.bird cget -font]
button .boat -text "Boat Puzzle" -command {GoPuzzle Boat} -bd 4
button .about -text About -command [list tk_messageBox -title About -message "$::S(title) \
by Keith Vetter, May 2003"]
label .msg -textvariable S(msg) -bd 2 -bg white -relief ridge
pack .msg -in .screen -side bottom -fill both
pack .c -in .screen -side top -fill both -expand 1
grid .bird -in .ctrl -sticky ew -row 0
grid .boat -in .ctrl -sticky ew
grid rowconfigure .ctrl 50 -weight 1
grid .about -in .ctrl -row 100 -sticky ew
bind all <Alt-c> {console show}
bind .c <Configure> {ReCenter %W %h %w}
update
}
proc ReCenter {W h w} { ;# Called by configure event
set h2 [expr {$h / 2}] ; set w2 [expr {$w / 2}]
$W config -scrollregion [list -$w2 -$h2 $w2 $h2]
}
proc RotateItem {id Oxy angle} {
foreach {Ox Oy} $Oxy break
set rangle [expr {$angle * atan(1) * 4 / 180.0}] ;# Radians
set xy {}
foreach {x y} [.c coords $id] {
# rotates vector (Ox,Oy)->(x,y) by angle degrees clockwise
set x [expr {$x - $Ox}] ;# Shift to origin
set y [expr {$y - $Oy}]
set xx [expr {$x * cos($rangle) - $y * sin($rangle)}] ;# Rotate
set yy [expr {$x * sin($rangle) + $y * cos($rangle)}]
set xx [expr {$xx + $Ox}] ;# Shift back
set yy [expr {$yy + $Oy}]
lappend xy $xx $yy
}
.c coords $id $xy
set ::C(rotate,$id) [expr {($::C(rotate,$id)+$angle) % 360}]
}
proc MoveItem {who x y} {
foreach {cx cy} [Centroid $who] break
.c move $who [expr {$x - $cx}] [expr {$y - $cy}]
}
proc GoPuzzle {who} {
set ::S(msg) "Drag and drop the pieces; right click to rotate"
.c delete all
$who
DrawBoard
DrawBlocks
}
proc DrawBlocks {} {
global C S Y
catch {unset Y}
foreach {x0 y0 x1 y1} [.c cget -scrollregion] break
set maxx [expr {$x1 - 30}]
set maxy [expr {$y1 - 30}]
foreach b [array names C coords,*] {
foreach {_ who} [split $b ,] break
.c create poly $C($b) -tag $who -fill yellow -outline black
set C(rotate,$who) 0
set Y($who) 1
.c scale $who 0 0 $C(scale) $C(scale)
.c bind $who <Button-1> [list Mouse1 $who %x %y 0]
.c bind $who <B1-Motion> [list Mouse1 $who %x %y 1]
.c bind $who <ButtonRelease-1> [list Mouse1 $who %x %y 2]
.c bind $who <Button-3> [list Mouse3 $who]
MoveItem $who [Random -$maxx $maxx] [Random -$maxy $maxy]
RotateItem $who [Centroid $who] [expr {int(rand()*8)*45}]
}
}
proc DrawBoard {} {
.c create poly $::C(board) -tag board -outline black -fill blue4 -dash 1
.c scale board 0 0 $::C(scale) $::C(scale)
MoveItem board 0 0
.c lower board
set ::C(board2) [.c coords board]
}
proc Mouse1 {who x y what} {
global S C Y
set x [.c canvasx $x]
set y [.c canvasy $y]
if {$what == 0} { ;# Button down
.c itemconfig $who -width 3 -fill yellow
set Y($who) 1 ;# Mark as out of position
.c raise $who
} elseif {$what == 2} { ;# Button up
.c itemconfig $who -width 1
OkaySnap $who ;# See if it in correct position
} else { ;# Button move
set dx [expr {$x - $S(down,x)}]
set dy [expr {$y - $S(down,y)}]
.c move $who $dx $dy
}
set S(down,x) $x ;# Remember last position
set S(down,y) $y
}
proc Mouse3 {who} {
.c itemconfig $who -fill yellow
set ::Y($who) 1 ;# Mark as out of position
RotateItem $who [Centroid $who] 45
OkaySnap $who
}
proc Random {min max} {return [expr {$min + rand() * ($max - $min)}]}
proc Centroid {who} {
foreach {x0 y0 x1 y1} [.c bbox $who] break
return [list [expr {($x0 + $x1) / 2.0}] [expr {($y0 + $y1) / 2.0}]]
}
proc OkaySnap {who} { ;# See if close enough
global C S Y
foreach {p angles} $C(end,$who) break
set n [lsearch $angles "a$C(rotate,$who)"]
if {$n == -1} return
set c [lindex $angles [expr {$n + 1}]]
foreach {x1 y1} [lrange $C(board2) [expr {2*$p}] [expr {2*$p+1}]] break
foreach {x0 y0} [lrange [.c coords $who] [expr {2*$c}] [expr {2*$c+1}]] break
set dx [expr {$x1 - $x0}]
set dy [expr {$y1 - $y0}]
set dist [expr {sqrt($dx*$dx + $dy*$dy)}]
if {$dist > $S(snap)} return
.c move $who $dx $dy
.c itemconfig $who -fill green
.c lower $who
.c lower board
snd_click play
set Y($who) 0 ;# Mark as in place
foreach a [array names Y] { ;# Are we done?
if {$Y($a)} return
}
.c raise board
.c itemconfig board -width 5 -dash {} -fill green
set S(msg) "Done !"
}
proc DoSounds {} {
proc snd_click {play} {} ;# Stub
if {[catch {package require base64}]} return
if {[catch {package require snack}]} return
set sdata {UklGRkACAABXQVZFZm10IBAAAAABAAEAESsAABErAAABAAgAZGF0YRwCAACAgId0Z
HZbU5aMj7/MsIZ6UX6nWIiITWiIRUGUlZesvrGCiKiKTl96Fit3YF5emrGHqcqhlJuAdWxgW
01EbWSHubW1uJ2MkqGPYFVSamtvgHmEh5ybraWLkHp5Xm5oWGRvb3WSlYqMi4+JhY6Ac25xd
Xp5jYR/hoODdIN8e356goCHgoqGgIV/g35/d3N2eHZ6gIOIgouHioaNioGAfHpycHp2dH2Hi
ouNiYiKhIF9enZzd3l+dX2BgYKIjoaJhIJ/fX6AfHl8fICAgICEgISFhYF/gH+AfIJ/gH6Af
X6AfICAfYB+gn2DfoGAgIOAgYB8e3x9gIKChYCDgIN/g32Afn+BgIF+gH+BgIOAgX2CfYGAg
IB/gH9/fIB/gICBgH+Df4KAgIB9gHuBfYKAgoCAhICDgIN+gH+Af4CAgIGAg4CFgIOAgICAg
H9/f32AfoF/gn+BgICAf4B/gICAgICAgIKAgYCAgH+AfYB8f4CAgoGBgIKBgHt0cnqEi4yIh
oKHioOBeoF+gHRvbW10eYSHhoyMmI+PhIF5dm9tbW92fICJjpKRkY6JhHx5b2xlbnWAhYeOj
pSQkIiAe3R1cnNzdnx/gomLj4yJhICAfHp3d3d6fYKDhoKGgIeAhX1/eXt9foCAg4GCg4CDf
YF6gHmAfYCBgIR/h4CEf4B9fn98gHuEfYV/g4CAgn6Fe4R6gn1/gHuDe4V+g4CAgn8=}
regsub -all {s} $sdata {} sdata ;# Bug in base64 package
sound snd_click
snd_click data [::base64::decode $sdata]
}
proc Bird {} {
global C
set 2r2 [expr {2 * sqrt(2)}]
catch {unset C}
set C(board) [list 0 0 1 0 3 2 5 2 7 0 6 0 8 -2 5 -2 3 0 2 0 2 -1.5 1 -1.5]
set C(scale) 60
set C(coords,b1) {0 0 1 -1.5 2 -1.5 1 0}
set C(end,b1) {0 {a0 0 a180 2}}
set C(coords,b2) {0 0 1 -1.5 1 0}
set C(end,b2) {1 {a0 0}}
set C(coords,b3) {0 0 -2 -2 0 -2 2 0}
set C(end,b3) {2 {a0 0 a180 2}}
set C(coords,b4) [list $2r2 0 0 0 0 -$2r2]
set C(end,b4) {3 {a45 0}}
set C(coords,b5) {2 0 0 0 0 -2}
set C(end,b5) {5 {a90 0}}
set C(coords,b6) {2 0 0 0 0 -2}
set C(end,b6) {3 {a90 0}}
set C(coords,b7) {0 -1 1 -1 1 0 0 0}
set C(end,b7) {7 {a0 0 a90 3 a180 2 a270 1}}
set C(coords,b8) {1 0 0 0 0 -1 1 -1}
set C(end,b8) {5 {a0 0 a90 3 a180 2 a270 1}}
}
proc Boat {} {
global C
set r2 [expr {sqrt(2)}]
set r22 [expr {sqrt(2)/2}]
set r22_1 [expr {sqrt(2)/2 + 1}]
set r24 [expr {sqrt(2)/4}]
catch {unset C}
set C(scale) 110
set C(board) {0 0} ;# P0
AppendBoard -$r22 -$r22 ;# P1
AppendBoard [expr {$r22 + $r24/2}] 0 ;# P2
AppendBoard 0 -$r24 ;# P3
AppendBoard 0 -$r22_1 ;# P4
AppendBoard $r22_1 $r22_1 ;# P5
AppendBoard [expr {-$r22_1 + $r24}] 0 ;# P6
AppendBoard 0 $r24 ;# P7
AppendBoard [expr {$r24/2 + $r2 + $r22}] 0 ;# P8
AppendBoard -$r22 $r22 ;# P9
AppendBoard -$r22 0 ;# P10
set C(coords,b1) [list 0 0 0 -$r22 $r22 0]
set C(end,b1) {3 {a0 0}}
set C(coords,b2) [list 0 0 0 -$r22 $r22 0]
set C(end,b2) {0 {a180 1}}
set C(coords,b3) [list 0 0 0 -$r22 $r22 0]
set C(end,b3) {10 {a0 2}}
set C(coords,b4) [list 0 0 0 -1 1 0]
set C(end,b4) {10 {a315 0}}
set C(coords,b5) [list 0 0 0 -1 1 0]
set C(end,b5) {5 {a0 2}}
set C(coords,b6) [list 0 0 $r22 $r22 $r22 $r22_1 0 1]
set C(end,b6) {4 {a0 0 a180 2}}
set C(coords,b7) [list 0 0 $r22 -$r22 [expr {$r22+$r22}] -$r22 $r22 0]
set C(end,b7) {10 {a0 0 a180 2}}
set C(coords,b8) [list 0 0 0 -$r22 $r22 -$r22 $r22 0]
set C(end,b8) {0 {a0 0 a90 3 a180 2 a270 1}}
set C(coords,b9) [list 0 0 0 -$r24 $r24 -$r24 $r24 0]
set C(end,b9) {3 {a0 1 a90 0 a180 3 a270 2}}
}
proc AppendBoard {dx dy} {
foreach {x y} [lrange $::C(board) end-1 end] break ;# Last point in list
set x [expr {round(10000 * ($x + $dx)) / 10000.0}]
set y [expr {round(10000 * ($y + $dy)) / 10000.0}]
lappend ::C(board) $x $y
}
DoDisplay
DoSounds
if {[expr {rand()}] > .5} { set what Bird } else { set what Boat }
GoPuzzle $what
