C.12. tkhanoi.pplThis next PPL program is a classic Tower of Hanoi game. #!/usr/local/bin/perl -w # # Towers of Hanoi, Perl/Tk style. 2000/06/14, sol0@Lehigh.EDU # Global package, subroutine and data declarations. use Tk; use Tk::Dialog; use subs qw/do_hanoi fini hanoi init move_ring/; use strict; my $canvas; # the Hanoi playing field my @colors; # 24 graduated ring colors my $fly_y; # canvas Y-coord along which rings fly my $max_rings; # be nice and keep @colors count-consistent my $num_moves; # total ring moves my %pole; # tracks pole X-coord and ring count my %ring; # tracks ring canvas ID, width and pole my $ring_base; # canvas Y-coord of base of ring pile my $ring_spacing; # pixels between adjacent rings my $stopped; # 1 IFF simulation is stopped my $velocity; # pixel delta the rings move while flying my $version = '1.0, 2000/06/14'; # Main. my $mw = MainWindow->new(-use => $Plugin::brinfo{xwindow_id}); init; MainLoop; sub do_hanoi { # Initialize for a new simulation. my($n) = @_; # number of rings return unless $stopped; $stopped = 0; # start ... $num_moves = 0; # ... new simulation my $ring_height = 26; $ring_spacing = 0.67 * $ring_height; my $ring_width = 96 + $n * 12; my $canvas_width = 3 * $ring_width + 4 * 12; my $canvas_height = $ring_spacing * $n + $fly_y + 2 * $ring_height; $ring_base = $canvas_height - 32; # Remove all rings from the previous run and resize the canvas. for (my $i = 0; $i < $max_rings; $i++) { $canvas->delete($ring{$i, 'id'}) if defined $ring{$i, 'id'}; } $canvas->configure(-width => $canvas_width, -height => $canvas_height); # Initialize the poles: no rings, updated X coordinate. for (my $i = 0; $i < 3; $i++) { $pole{$i, 'x'} = ($i * $canvas_width / 3) + ($ring_width / 2) + 8; $pole{$i, 'ring_count'} = 0; } # Initialize the rings: canvas ID, pole number, and width. for (my $i = 0; $i < $n; $i++) { my $color = '#' . $colors[$i % 24]; my $w = $ring_width - ($i * 12); my $y = $ring_base - $i * $ring_spacing; my $x = $pole{0, 'x'} - $w / 2; my $r = $n - $i; $ring{$r, 'id'} = $canvas->createOval( $x, $y, $x + $w, $y + $ring_height, -fill => $color, -outline => 'black', -width => 1, ); $ring{$r, 'width'} = $w; $ring{$r, 'pole'} = 0; $pole{0, 'ring_count'}++; } # Start the simulation. $mw->update; hanoi $n, 0, 2, 1; $stopped = 1; } # end do_hanoi sub hanoi { # Recursively move rings until complete or stopped by the user. my($n, $from, $to, $work) = @_; return if $n <=0 or $stopped; hanoi $n - 1, $from, $work, $to; move_ring $n, $to unless $stopped; hanoi $n - 1, $work, $to, $from; } sub init { $fly_y = 32; # Y-coord rings use to fly between poles $stopped = 1; my $stop = sub {$stopped = 1}; my $about = $mw->Dialog( -title => 'About tkhanoi', -bitmap => 'info', -default_button => 'OK', -buttons => ['OK'], -text => "tkhanoi version $version\n\n" . "r - run simulation\n" . "s - stop simulation\n" . "q - quit program\n", -wraplength => '6i', ); # Menubar and menubuttons. $mw->title("Towers of Hanoi"); $mw->configure(-menu => my $menubar = $mw->Menu); my $file = $menubar->cascade(-label => 'File'); $file->command(-label => '~Quit', -command => \&fini,-accelerator => 'q'); my $game = $menubar->cascade(-label => 'Game'); $game->command(-label => '~Run', -command => sub {}, -accelerator => 'r'); $game->command(-label => '~Stop', -command => $stop, -accelerator => 's'); my $help = $menubar->cascade(-label => 'Help'); $help->command(-label => 'About', -command => sub {$about->Show}); my $info = $mw->Frame->pack; # Number of rings scale. my $rframe = $info->Frame(qw/-borderwidth 2 -relief raised/); my $rlabel = $rframe->Label(-text => 'Number of Rings'); my $rscale = $rframe->Scale( qw/-orient horizontal -from 1 -to 24 -length 200/, ); $rscale->set(4); $game->cget(-menu)->entryconfigure('Run', -command => sub {do_hanoi $rscale->get}, ); $rframe->pack(qw/-side left/); $rscale->pack(qw/-side right -expand 1 -fill x/); $rlabel->pack(qw/-side left/); # Ring velocity scale. my $vframe = $info->Frame(qw/-borderwidth 2 -relief raised/); my $vlabel = $vframe->Label(-text => 'Ring Velocity %'); my $vscale = $vframe->Scale( qw/-orient horizontal -from 0 -to 100 -length 200/, -command => sub {$velocity = shift}, ); $vscale->set(2); $vframe->pack(qw/-side left/); $vscale->pack(qw/-side right -expand 1 -fill x/); $vlabel->pack(qw/-side left/); # The simulation is played out on a canvas. $canvas = $mw->Canvas(qw/-relief sunken/); $canvas->pack(qw/-expand 1 -fill both/); $canvas->createWindow(40, 10, -window => $canvas->Label(-textvariable => \$num_moves, -foreground => 'blue'), ); # Each ring has a unique color, hopefully. @colors = (qw/ ffff0000b000 ffff00006000 ffff40000000 ffff60000000 ffff80000000 ffffa0000000 ffffc0000000 ffffe0000000 ffffffff0000 d000ffff0000 b000ffff0000 9000ffff0000 6000ffff3000 0000ffff6000 0000ffff9000 0000ffffc000 0000ffffffff 0000e000ffff 0000c000ffff 0000a000ffff 00008000ffff 00006000ffff 00004000ffff 00000000ffff /); $max_rings = 24; warn "Too few colors for $max_rings rings!" if $max_rings > $#colors + 1; # Global key bindings that emulate menu commands. $mw->bind('<KeyPress-r>' => sub {do_hanoi $rscale->get}); $mw->bind('<KeyPress-q>' => \&fini); $mw->bind('<KeyPress-s>' => $stop); } # end init sub fini { $mw->destroy; } sub move_ring { # Move ring $n - its bounding box coordinates - to pole $to. my($n, $to) = @_; $num_moves++; my $r = $ring{$n, 'id'}; my($x0, $y0, $x1, $y1) = map {int($_ + 0.5)} $canvas->coords($r); # Float the ring upwards to the flying Y-coordinate, and decrement # this pole's count. my $delta; while ($y0 > $fly_y) { $delta = $y0 - $fly_y > $velocity ? $velocity : $y0 - $fly_y; $canvas->coords($r, $x0, $y0 -= $delta, $x1, $y1 -= $delta); $mw->update; } $pole{$ring{$n, 'pole'}, 'ring_count'}--; # Determine the target X coordinate based on destination pole, and # fly the ring over to the new pole. The first while moves rings # left-to-right, the second while moves rings right-to-left. my $x = $pole{$to, 'x'} - $ring{$n, 'width'} / 2; while ($x0 < $x) { $delta = $x - $x0 > $velocity ? $velocity : $x - $x0; $canvas->coords($r, $x0 += $delta, $y0, $x1 += $delta, $y1); $mw->update; } while ($x0 > $x) { $delta = $x0 - $x > $velocity ? $velocity : $x0 - $x; $canvas->coords($r, $x0 -= $delta, $y0, $x1 -= $delta, $y1); $mw->update; } # Determine ring's target Y coordinate, based on the destination # pole's ring count, and float the ring down. my $y = $ring_base - $pole{$to, 'ring_count'} * $ring_spacing; while ($y0 < $y) { $delta = $y - $y0 > $velocity ? $velocity : $y - $y0; $canvas->coords($r, $x0, $y0 += $delta, $x1, $y1 += $delta); $mw->update; } $pole{$to, 'ring_count'}++; $ring{$n, 'pole'} = $to; } # end move_ring Copyright © 2002 O'Reilly & Associates. All rights reserved. |
|