Commit | Line | Data |
---|---|---|
86530b38 AT |
1 | # A N-puzzle implemented via the Grid geometry manager. |
2 | # | |
3 | # This program is described in the Perl/Tk column from Volume 1, Issue 4 of | |
4 | # The Perl Journal (http://tpj.com/tpj), and is included in the Perl/Tk | |
5 | # distribution with permission. It has been modified slightly to conform | |
6 | # to the widget demo standard. | |
7 | ||
8 | #!/usr/local/bin/perl -w | |
9 | # | |
10 | # puz - demonstrate the Grid geometry manager by implementing an n-puzzle. | |
11 | # | |
12 | # Stephen O. Lidie, Lehigh University Computing Center, lusol@Lehigh.EDU | |
13 | # 96/08/11. | |
14 | # | |
15 | # Copyright (C) 1996 - 1998 Stephen O. Lidie. All rights reserved. | |
16 | # | |
17 | # This program is free software; you can redistribute it and/or modify it under | |
18 | # the same terms as Perl itself. | |
19 | ||
20 | require 5.002; | |
21 | use Tk; | |
22 | use Tk::Dialog; | |
23 | use strict; | |
24 | use subs qw(beep create_puz create_ui puz_fini move_piece new_puz randomly xy); | |
25 | ||
26 | my $CAMEL; # Perl/Tk Xcamel.gif Photo image | |
27 | my $CAMEL_HEIGHT; # Xcamel height | |
28 | my $CAMEL_WIDTH; # Xcamel width | |
29 | my (@LEVELS) = (9, 16, 36, 64); # possible puzzle piece counts | |
30 | my $MW = MainWindow->new; # program's main window | |
31 | my @ORDER; # random puzzle piece ordinals | |
32 | my $PIECES = $LEVELS[1]; # total puzzle piece count | |
33 | my $OLD_PIECES = -1; # previous puzzle piece count | |
34 | my $PF; # puzzle Frame | |
35 | my @PUZ; # puzzle piece information | |
36 | my $SIDE; # pieces per side of puzzle | |
37 | my $SPACE; # shortcut to puzzle space piece | |
38 | my $SPACE_IMAGE; # space piece image | |
39 | ||
40 | create_ui; | |
41 | create_puz; | |
42 | ||
43 | sub beep {$MW->bell} | |
44 | ||
45 | sub create_puz { | |
46 | ||
47 | return if $PIECES == $OLD_PIECES; | |
48 | ||
49 | # Create all the puzzle pieces - buttons with images - and arrange them | |
50 | # in a rectangular grid. @PUZ is a list of button widget references which | |
51 | # represent the puzzle pieces. | |
52 | # | |
53 | # The actual ordering is controlled by @ORDER, a list of list of two: | |
54 | # | |
55 | # $ORDER[$i]->[0] = puzzle piece ordinal | |
56 | # $ORDER[$i]->[1] = random number used to shuffle the puzzle ordinals | |
57 | # | |
58 | # If the puzzle frame $PF exists, we've been here before, which means that | |
59 | # all images and widgets associated with the previous puzzle need | |
60 | # destroying, plugging a potential memory leak. It's important to note | |
61 | # that an image must be explicity deleted - it doesn't magically go away | |
62 | # if a widget, which just happens to use it, is destroyed. So, loop | |
63 | # through all the puzzle pieces and delete their images, then destroy the | |
64 | # puzzle's master frame $PF, destroying all child widgets. Now, this | |
65 | # scheme isn't particulary efficient, but it is simple; ideally, we'd like | |
66 | # to create these images only once and reuse them as required. | |
67 | ||
68 | if (Exists $PF) { | |
69 | my $image; | |
70 | foreach (@PUZ) { | |
71 | $image = $_->cget(-image); | |
72 | $image = $SPACE_IMAGE if not defined $image; | |
73 | $image->delete; | |
74 | } | |
75 | $PF->destroy; | |
76 | } | |
77 | ||
78 | $PF = $MW->Frame->grid; # create the puzzle frame grid master | |
79 | $OLD_PIECES = $PIECES; | |
80 | $#PUZ = $#ORDER = $PIECES - 1; | |
81 | $SIDE = sqrt $PIECES; | |
82 | ||
83 | my($i, $o, $c, $r, $w, $h, $x, $y, $but, $gif); | |
84 | ||
85 | foreach (0..$#ORDER) {$ORDER[$_] = [$_, undef]} | |
86 | ||
87 | for($i = 0; $i <= $#PUZ; $i++) { | |
88 | $o = $ORDER[$i]->[0]; | |
89 | ($c, $r) = xy $o; # puzzle ordinal to column/row | |
90 | $w = $CAMEL_WIDTH / $SIDE; | |
91 | $h = $CAMEL_HEIGHT / $SIDE; | |
92 | $x = $c * $w; # x/column pixel offset | |
93 | $y = $r * $h; # y/row pixel offset | |
94 | $gif = $PF->Photo; # new, empty, GIF image | |
95 | $gif->copy($CAMEL, -from => $x, $y, $x+$w, $y+$h); | |
96 | $but = $PF->Button(-image => $gif, | |
97 | -relief => 'flat', | |
98 | -borderwidth => 0, | |
99 | -command => \&beep, | |
100 | -highlightthickness => 0, | |
101 | ); | |
102 | $PUZ[$o] = $but; | |
103 | ($c, $r) = xy $i; | |
104 | $but->grid(-column => $c, -row => $r, -sticky => 'nsew'); | |
105 | if ($o == 0) { | |
106 | $SPACE_IMAGE = $gif; | |
107 | $SPACE = $but; | |
108 | } | |
109 | } # forend all puzzle pieces | |
110 | ||
111 | } # end create_puz | |
112 | ||
113 | sub create_ui { | |
114 | ||
115 | # Create a color Photo image of the Xcamel puzzle. | |
116 | ||
117 | $CAMEL = $MW->Photo(-file => "$WIDTRIB/lib/npuz/Xcamel.npuz"); | |
118 | $CAMEL_WIDTH = $CAMEL->image('width'); | |
119 | $CAMEL_HEIGHT = $CAMEL->image('height'); | |
120 | ||
121 | # Create the menubar. | |
122 | ||
123 | my $mf = $MW->Frame(-bg => 'blue')->grid(-sticky => 'ew'); | |
124 | $mf->gridColumnconfigure(1, -weight => 1); | |
125 | ||
126 | my $mbf = $mf->Menubutton(-text => 'File', -relief => 'raised'); | |
127 | $mbf->command(-label => 'New Puzzle', -command => \&new_puz); | |
128 | $mbf->separator; | |
129 | $mbf->command(-label => 'Quit', -command => [$MW => 'bell']); | |
130 | ||
131 | my $mbp = $mf->Menubutton(-text => 'Prefs', -relief => 'raised'); | |
132 | my $pieces = 'Pieces'; | |
133 | $mbp->cascade(-label => $pieces); | |
134 | my $mbpm = $mbp->cget(-menu); | |
135 | my $mbpmp = $mbpm->Menu; | |
136 | $mbp->entryconfigure($pieces, -menu => $mbpmp); | |
137 | foreach (@LEVELS) { | |
138 | $mbpmp->radiobutton(-label => $_, | |
139 | -variable => \$PIECES, | |
140 | -value => $_, | |
141 | -command => \&create_puz, | |
142 | ); | |
143 | } | |
144 | ||
145 | my $mbq = $mf->Menubutton(-text => 'Help', -relief => 'raised'); | |
146 | my $about = $MW->Dialog(-text => <<"END" | |
147 | npuz Version 1.0\n | |
148 | Select \"File/New Puzzle\", then click around the red \"space\" to rearrange the pieces and solve the puzzle!\n\nThis program is described in the Perl/Tk column from Volume 1, Issue 4 of The Perl Journal (http://tpj.com/tpj), and is included in the Perl/Tk distribution with permission. | |
149 | END | |
150 | ); | |
151 | $about->configure(-wraplength => '6i'); | |
152 | $mbq->command(-label => 'About', -command => [$about => 'Show']); | |
153 | ||
154 | $mbf->grid(-row => 0, -column => 0, -sticky => 'w'); | |
155 | $mbp->grid(-row => 0, -column => 1, -sticky => 'w'); | |
156 | $mbq->grid(-row => 0, -column => 2, -sticky => 'e'); | |
157 | ||
158 | } # end create_ui | |
159 | ||
160 | sub puz_fini { | |
161 | ||
162 | # Return true iff all puzzle pieces are in order. | |
163 | ||
164 | my($i, $c, $r, %info); | |
165 | for($i = 0; $i <= $#PUZ; $i++) { | |
166 | ($c, $r) = xy $i; | |
167 | %info = $PUZ[$i]->gridInfo; | |
168 | return 0 if $c != $info{-column} or $r != $info{-row}; | |
169 | } | |
170 | return 1; | |
171 | ||
172 | } # end puz_fini | |
173 | ||
174 | sub move_piece { | |
175 | ||
176 | my($piece) = @_; | |
177 | ||
178 | my(%info, $c, $r, $sc, $sr); | |
179 | %info = $piece->gridInfo; ($c, $r) = @info{-column,-row}; | |
180 | %info = $SPACE->gridInfo; ($sc, $sr) = @info{-column,-row}; | |
181 | if ( ($sr == $r and ($sc == $c-1 or $sc == $c+1)) or | |
182 | ($sc == $c and ($sr == $r-1 or $sr == $r+1)) ) { | |
183 | $SPACE->grid(-column => $c, -row => $r); | |
184 | $piece->grid(-column => $sc, -row => $sr); | |
185 | } | |
186 | if (puz_fini) { | |
187 | my $color = ($SPACE->configure(-activebackground))[3]; | |
188 | $SPACE->configure(-image => $SPACE_IMAGE, | |
189 | -activebackground => $color, | |
190 | -background => $color, | |
191 | -relief => 'flat', | |
192 | ); | |
193 | foreach (@PUZ) {$_->configure(-command => \&beep)} | |
194 | } | |
195 | ||
196 | } # end move_piece | |
197 | ||
198 | sub new_puz { | |
199 | ||
200 | srand time; | |
201 | foreach (0..$#ORDER) {$ORDER[$_]->[1] = rand $#ORDER} | |
202 | my @order = sort randomly @ORDER; | |
203 | #@order = @ORDER; # here's how I solve the puzzle (; | |
204 | my($i, $o, $c, $r, $but); | |
205 | ||
206 | for($i = 0; $i <= $#PUZ; $i++) { | |
207 | $o = $order[$i]->[0]; | |
208 | $but = $PUZ[$o]; | |
209 | if ($o == 0) { | |
210 | $but->configure(-background => 'red', | |
211 | -relief => 'sunken', | |
212 | -image => undef, | |
213 | -activebackground => 'red', | |
214 | ); | |
215 | } else { | |
216 | $but->configure(-command => [\&move_piece, $but]); | |
217 | } | |
218 | ($c, $r) = xy $i; | |
219 | $but->grid(-column => $c, -row => $r, -sticky => 'nsew'); | |
220 | } | |
221 | ||
222 | } # end new_puz | |
223 | ||
224 | sub randomly {$a->[1] <=> $b->[1]} # randomize order of puzzle pieces | |
225 | ||
226 | sub xy {my($n) = @_; ($n % $SIDE, int $n / $SIDE)} # ordinal to X/Y |