#!/usr/bin/perl use warnings; use strict; use Tk; # by zentara of perlmonks # Up and Down Arrows adjust firing power at battery expense. # Left and Right keys rotate turret. # Spacebar fires. # 's' key turns sound on/off # If one warhead hits the ground, it's over. # Ammo is limited to 500 # There are 100 incoming missles possible. # Batteries are recharged by Solar Panels. # Kludged a way to rotate, Tk::Zinc would be better # to do this. # this Mach1 version adds radar-fire-control # which can be used for pause # fixed bug where shells didn't explode with target my $mw = MainWindow->new(-bg=>'black'); $mw->geometry('+100+100'); my $height = 400; my $width = 600; # first create a canvas widget my $canvas = $mw->Canvas( -height => $height, -width => $width, -bg => 'black', )->pack(); my $turret = $canvas->createOval( $width / 2 - 50, $height - 50, $width / 2 + 50, $height + 50, -fill => 'steelblue', -tags => ['turret'] ); my $px0 = $width/2; my $py0 = $height; my $px = $width/2; my $py = $height-65; my $px_new = $px; my $py_new = $py; my $angle = 1.57; # pi divided by 2, 90 degrees in radians my $power = 50; my $status = ' Ready '; my %projectile; my %missle; my $launcher; my @ammo = (1..15); #reusable object array for projectiles my $bat_level = 100; my $ammo_tot = 500; my $missles_max = 100; my @missles = (1..20); #reusable object array for missles, max in play my $hits = 0; my %radar; #radar variables $radar{'on'} = 0; $radar{'multifirelock_set'} = 0; $radar{'angle'} = $angle; my $sound = 1; #computer beep on,'s' key controls on/off my $cannon = $canvas->createLine( $px0,$py0,$px,$py, -width => 10, -fill => 'lightblue', -tags => ['cannon'], ); $canvas->lower('cannon', 'turret'); #1 degree in rads is pi divided by 180 = .01745 $mw->bind('',sub{ &rotate(.01745) }); $mw->bind('',sub{ &rotate(-.01745) }); $mw->bind('',sub{ &power(10) }); $mw->bind('',sub{ &power(-10) }); $mw->bind('', sub{ &fire} ); $mw->bind('',sub{ if($sound == 1){$sound = 0} else{$sound = 1} }); my $frame = $mw->Frame(-background =>'grey45')->pack(-fill=>'x'); my $frame1 = $mw->Frame(-background =>'grey45')->pack(-fill=>'x'); $frame->Label(-text =>'Power ', -bg => 'grey45', -fg => 'green', -borderwidth => 0)->pack(-side=>'left'); $frame->Label(-textvariable => \$power, -bg => 'grey45', -fg => 'green', -width => 3, -borderwidth => 0)->pack(-side=>'left'); $frame->Label(-text => ' ', -bg => 'grey45', -borderwidth => 0)->pack(-side=>'left'); $frame->Label(-textvariable => \$status, -bg => 'grey45', -fg => 'yellow', -width => 15, -borderwidth => 0)->pack(-side=>'left'); $frame->Label(-text => ' ', -bg => 'grey45', -borderwidth => 0)->pack(-side=>'left'); $frame->Label(-text =>'Battery Level ', -bg => 'grey45', -fg => 'lightblue', -borderwidth => 0)->pack(-side=>'left'); $frame->Label(-textvariable => \$bat_level, -bg => 'grey45', -fg => 'lightblue', -width =>4, -borderwidth => 0)->pack(-side=>'left'); $frame->Label(-text => ' ', -bg => 'grey45', -borderwidth => 0)->pack(-side=>'left'); $frame->Label(-text =>'Ammo Supply ', -bg => 'grey45', -fg => 'red', -borderwidth => 0)->pack(-side=>'left'); $frame->Label(-textvariable => \$ammo_tot, -bg => 'grey45', -fg => 'red', -width => 3, -borderwidth => 0)->pack(-side=>'left'); $frame->Label(-text => ' ', -bg => 'grey45', -borderwidth => 0)->pack(-side=>'left'); $frame->Label(-text =>'Hits ', -bg => 'grey45', -fg => 'orange', -borderwidth => 0)->pack(-side=>'left'); $frame->Label(-textvariable => \$hits, -bg => 'grey45', -fg => 'orange', -width => 3, -borderwidth => 0)->pack(-side=>'left'); $frame1->Button( -text => 'Exit', -command => sub{ exit }, -highlightbackground =>'blue', )->pack(-side=>'right',-padx => 3); my $startbut; $startbut = $frame1->Button( -text => 'New Game', -command => sub{ $startbut->configure(-state=>'disabled'); &launch }, -highlightbackground =>'blue', )->pack(-side=>'right',-padx=>3); my $radarbut= $frame1->Button( -text => 'Radar-auto-fire', -activebackground => 'lightyellow', -highlightbackground =>'blue', -command => sub{ &radar }, )->pack(-side=>'left',-padx=>3); my $solar_panel = Tk::After->new($canvas,1000,'repeat', sub { $bat_level++; $bat_level = sprintf "%.1f", $bat_level; if($bat_level > 100){ $bat_level = 100 } }); MainLoop(); ##################################################################### sub launch{ if($radar{'on'}){ $mw->bind('', sub{ } ) }else{ $mw->bind('', sub{ &fire} ) } $status = 'Ready'; $launcher = Tk::After->new($canvas,1000,'repeat', sub { my $rand = int(rand(100)); if( $rand > 70 ){ #launch $missles_max--; if($missles_max == 0){ print chr(07) if $sound; $status = 'You Win'; &restart; } my $misl = shift @missles; my $mx = int(rand $width); my $my = -20; $missle{$misl}{'warhead'} = $canvas->createOval($mx-8,$my-8,$mx+8,$my+8, -fill => 'yellow', -tags =>['warhead'], ); my ($dx,$dy); $dx = 0; $dy = .8; $missle{$misl}{'repeater'} = Tk::After->new($canvas,10,'repeat', sub { $canvas->move($missle{$misl}{'warhead'}, $dx,$dy); my ($x,$y,$x1,$y1) = $canvas->bbox($missle{$misl}{'warhead'}); my @overlap = $canvas->find( 'overlapping', $x,$y,$x1,$y1 ); if(scalar @overlap > 1){ my $des_angle = $radar{'angle'}; if($des_angle < 1){$des_angle -= .05} #leading fire if($des_angle > 2){$des_angle += .05} my @tags = map{$canvas->gettags($_)} @overlap; if( grep /shell/, @tags) { $missle{$misl}{'repeater'}->cancel; $canvas->delete($missle{$misl}{'warhead'}); $missle{$misl} = (); push @missles, $misl; $hits++; #cleanup shells my (@shells) = grep /\d+/, @tags; foreach my $num(@shells) { $projectile{$num}{'repeater'}->cancel; $canvas->delete($projectile{$num}{'shell'}); $projectile{$num} = (); push @ammo, $num; } } else{ #radar contact print chr(07) if $sound; &rotate($des_angle); &fire; } } if($y > $height + 10) { $missle{$misl}{'repeater'}->cancel; $canvas->delete($missle{$misl}{'warhead'}); $missle{$misl} = (); push @missles, $misl; print chr(07) if $sound; $status = 'Uh Oh Boom'; &restart; }; }); } }); }#################################################################### sub fire{ return if( $radar{'multifirelock_set'} == 1); #splatter fire prevention if((scalar @ammo == 0)|| ($ammo_tot < 0)|| ($bat_level < 0)) { print chr(07) if $sound; $status = 'Gun Jambed'; return } my $num = shift @ammo; $projectile{$num}{'shell'} = $canvas->createOval($px_new-4,$py_new-4,$px_new+4,$py_new+4, -fill => 'pink', -tags=> ['shell', $num], ); $bat_level -= 1.5; $bat_level = sprintf "%.1f",$bat_level; $ammo_tot--; my ($dx,$dy); if($px_new == $px0){ $dy = -$power/10 ; $dx = 0} else{ $dx = cos($angle) * $power/10; $dy = -sin($angle)* $power/10; } $projectile{$num}{'repeater'} = Tk::After->new($canvas,10,'repeat', sub {$canvas->move($projectile{$num}{'shell'}, $dx,$dy); my ($x,$y) = $canvas->bbox($projectile{$num}{'shell'}); if($y > $height + 10 || $y < -10 || $x < -10 || $x > $width +10) { $projectile{$num}{'repeater'}->cancel; $canvas->delete($projectile{$num}{'shell'}); $projectile{$num} = (); push @ammo, $num; $status = 'Ready'; }; }); if( $radar{'on'}){ #prevent excessive splatter firing $radar{'multifirelock_set'} = 1; $canvas->after(40, sub{ $radar{'multifirelock_set'} = 0; }); } } ######################################################################### sub power{ my $pow = shift; $power += $pow; if($power < 10){$power = 10} if($power > 100){$power = 100} } ########################################################################## sub rotate{ my $change = shift; $angle += 5*$change; #more action for manual key control if( $radar{'on'} ){ $angle = $change} if( $angle > 3.1 ){$angle = 3.1;return} if( $angle < .1 ){$angle = .1;return} $angle = sprintf "%.4f",$angle; # print "$angle\t"; $py_new = $height - sin($angle)*65; $px_new = ($width/2) + ( cos($angle)*65); $canvas->delete($cannon); $cannon = (); $cannon = $canvas->createLine( $px0,$py0,$px_new,$py_new, -width => 10, -fill => 'lightblue', -tags => ['cannon'], ); $canvas->lower('cannon', 'turret'); } ######################################################################### sub radar{ if($radar{'on'} == 0){ $radarbut->configure(-bg => 'lightyellow'); $radar{'on'} = 1; }else{ $radarbut->configure(-bg => 'lightgrey',); $radar{'on'} = 0; } #---------------- if($radar{'on'} == 1){ $power = 100; $mw->bind('', sub{ } ); $radar{'angle'} = 1.571; #90 degrees $radar{'line'} = $canvas->createLine( $px0,$py0,$width/2,0, -width => 1, -fill => 'lightgrey', -dash => [2,4], -tags => ['radar'], ); $canvas->lower('radar', 'cannon'); $radar{'multifirelock_set'} = 0; $radar{'repeater'} = Tk::After->new($canvas,10,'repeat', sub { $canvas->delete($radar{'line'}); $radar{'line'} = (); $radar{'angle'} += .01745; if($radar{'angle'} > 3.1415){ $radar{'angle'} = 0 } $radar{'line'} = $canvas->createLine( $px0, $py0, $width/2 + $height*cos($radar{'angle'}), $height - $height*sin($radar{'angle'}), -width => 1, -fill => 'lightgrey', -dash => [2,4], -tags => ['radar'], ); $canvas->lower('radar', 'cannon'); }); } #---------------- if($radar{'on'} == 0){ $mw->bind('', sub{ &fire } ); $radar{'repeater'}->cancel; $canvas->delete($radar{'line'}); $radar{'multifirelock_set'} = 0; $radar{'line'} = (); } } ########################################################################### sub restart{ $launcher->cancel; $mw->bind('', sub{ } ); my $wait; $wait = Tk::After->new($canvas,10,'repeat', sub { if(scalar @missles == 20){ $wait->cancel; $bat_level = 100; $ammo_tot = 500; $missles_max = 100; $hits = 0; $startbut->configure(-state =>'normal'); }else{return} }); } #########################################################################