Yep. I've made a complete game out of Chaser. Here it is. #!perl -w # Chaser v2.0 # by Kevin Reid <kpreid@ibm.net> # # Use the mouse to drive around and catch the moving objects. # Click to fire iceballs that turn them into non-moving objects (temporarily). # Don't run into walls. # Press Caps Lock to pause or abort. # # If you see a bright red square in the upper-left # corner, then the game is running slower than it should. # # Performance tips: # 1. Quit some applications. # 2. Reduce color depth. # 3. Get a faster Mac. # # Revision history # 2.0 # * Code structure completely rearranged # * Added difficulty settings # * Added high-score list # * Added menu for start/quit/highscores # * Targets now have upper limits on velocity # * Targets wiggle more # * Added support for non-orthogonal level elements # * Added gun charge bar # * Level rearranged # * Added pause function # * Added demo mode # 1.2 # * Fixed bug in display of health bar when resolution was not 800x600. # * Slight change to win animation. # 1.1 # * Added win animation. # * Added random background color. # * Minor efficency improvements. # * You no longer get damaged for hitting a wall with a velocity under 2. # * Damage flash now varies with intensity of damage. # * Shot firing added. # 1.0.1 # * Fixed handling of absence of ColorGamma module. # 1.0 # * First release. use strict; use Mac::QuickDraw; use Mac::Windows; use Mac::Events; use Mac::Controls; use Mac::Fonts; use Mac::Files; use MacPerl; # uncomment all occurences of "use Mac::KTools" and comment # out block if you have the module # use Mac::KTools qw(/^FS_/ /^Gradient_/); { local $/; no strict; eval <DATA>; import Mac::KTools qw(/^FS_/ /^Gradient_/); {package GButton; import Mac::KTools qw(ClickRect)}; {package GSlider; import Mac::KTools qw(ClickRect)}; {package TextGradient; import Mac::KTools qw(/^Gradient_/)}; } use vars qw( $Done $Width $Height $CenterX $CenterY $CenterPt $Clicked $OldCStr $StartTime $BlipRect $prefile $Paused $CharTyped $MinSpd $GunCRate @DiffStrings $DiffText $GameRect $GameRegion $LevRegion $DrawRegion @MulTab @InvMulTab $PlayerX $PlayerY $PlayerAng $PlayerVel $Health @ObjMov $TRemain @Shots $GunCharge $Won $WScale $Frame $Lost @HighScores $GotHigh $GotHighScore $IsDemo $Demo_TargInFront $Demo_VelGoal $Demo_AngGoal ); use constant VWIDTH => 200; use constant VHEIGHT => 150; use constant COL_BACKGROUND => new RGBColor((0)x3); use constant COL_FLOOR => new RGBColor( @{([10100,0,0], [0,5000,0], [0,0,8000])[rand 3]} ); use constant COL_WALL => new RGBColor((32768)x3); use constant COL_PLAYER => new RGBColor((65535)x3); use constant COL_TARGET => new RGBColor((65535)x3); use constant COL_HEALTHFG => new RGBColor (16384, 16384, 65535); use constant COL_HEALTHBG => new RGBColor (5000, 0, 0); use constant COL_GUNFG => new RGBColor (65535, 50000, 50000); use constant COL_GUNBG => new RGBColor (6000, 0, 10000); use constant COL_GUNLOW => new RGBColor (40000, 20000, 65535); use constant COL_TEXT => new RGBColor((65535)x3); use constant COL_TEXTSHAD => new RGBColor((20000)x3); use constant COL_SPEEDBLIP => new RGBColor (65535, 0, 0); ######## Initialization ##################################################################################################################### ($CenterX, $CenterY) = map $_ / 2, ($Width, $Height) = FS_Start(); $CenterPt = new Point ($CenterX, $CenterY); CompileLevel( [[-500, -300], [-50, -300], [-50, -600], [50, -600], [50, -300], [500, -300], [500, 300], [50, 300], [50, 600], [-50, 600], [-50, 300], [-500, 300]], r2p(-400, -200, -100, 200), r2p(100, -200, 400, 200), [[0, -60], [50, 0], [0, 60], [-50, 0]], ); FS_Hook(click => sub {$Clicked = 1}); FS_Hook(key => sub {$CharTyped = chr($_[0]); $Clicked = 1}); FS_Hook(idle => sub { $Paused = $Mac::Events::CurrentEvent->modifiers & (alphaLock | shiftKey) if $Mac::Events::CurrentEvent->what == nullEvent; }); SetPort FS_Port(); $GameRect = new Rect ($CenterX - VWIDTH, $CenterY - VHEIGHT, $CenterX + VWIDTH, $CenterY + VHEIGHT); $BlipRect = new Rect ($CenterX - VWIDTH, $CenterY - VHEIGHT, $CenterX - VWIDTH + 5, $CenterY - VHEIGHT + 5); $GameRegion = NewRgn; RectRgn $GameRegion, $GameRect; @DiffStrings = split /\n+/, <<'EOS'; Boring Child's play Easy Medium Difficult Challenge Impossible EOS $MinSpd = 2; $GunCRate = 5; @HighScores = ( ['Sadguk', 2034], ['Arald', 1402], ['Jark', 1382], ['Jaerl', 1120], ['Chagra', 823], ['Senif', 818], ['Qarkle', 760], ['Dalf', 533], ['Gwork', 230], ['Flakaw', -34], ); $prefile = FindFolder(kOnSystemDisk, kPreferencesFolderType) . ":Chaser Preferences"; require $prefile if -e $prefile; ######## Main loop ##################################################################################################################### while (1) { my $choice = MainMenu(); last if $choice eq 'Quit'; {no strict 'refs'; &$choice;} } open PREFS, "> $prefile" or die "couldn't open prefs file for writing: $!"; print PREFS <<"EOP"; # This file is rewritten each time Chaser is run. # Changes to anything other than the preference values # will be lost. \$MinSpd = $MinSpd; \$GunCRate = $GunCRate; \@HighScores = ( @{[map {"[q`$$_[0]`, $$_[1]],\n "} @HighScores]} ); 1; EOP close PREFS; MacPerl::SetFileInfo('McPL', 'TEXT', $prefile); FS_Stop(); exit; END { ShowCursor; DisposeRgn $DrawRegion if $DrawRegion; DisposeRgn $LevRegion if $LevRegion; DisposeRgn $GameRegion if $GameRegion; } ######## Menu ##################################################################################################################### sub MainMenu { my $demostart = time + 25; my $grad = new TextGradient ( Window => FS_Win(), Text => 'Chaser 2', X => $CenterX, Y => $CenterY - 160, Font => GetFNum('Impact') || GetFNum('Techno') || 0, Size => 60, ); my $action; my $qbut = new GButton (new Rect ($CenterX - 40, $CenterY + 180, $CenterX + 40, $CenterY + 200), "Quit", sub { $action = 'Quit' }); my $dbut = new GButton (new Rect ($CenterX - 40, $CenterY - 100, $CenterX + 40, $CenterY - 80), "Play Game", sub { $action = 'PlayGame' }); my $pbut = new GButton (new Rect ($CenterX - 40, $CenterY - 80, $CenterX + 40, $CenterY - 60), "Demo", sub { $action = 'PlayDemo' }); my $hbut = new GButton (new Rect ($CenterX - 40, $CenterY + 120, $CenterX + 40, $CenterY + 140), "High Scores", sub { $action = 'Scores' }) if @HighScores; my $s_spd = new GSlider (new Rect ($CenterX - 80, $CenterY - 10, $CenterX + 80, $CenterY + 10), "Minimum Speed", 0, 8, $MinSpd, sub { $MinSpd = $_[0]; UpdateDiffString(); }); my $s_gnc = new GSlider (new Rect ($CenterX - 80, $CenterY + 15, $CenterX + 80, $CenterY + 35), "Gun Charge Rate", 0, 10, $GunCRate, sub { $GunCRate = $_[0]; UpdateDiffString(); }); my $difflab = new GTextBox (new Rect ($CenterX - 80, $CenterY - 30, $CenterX + 80, $CenterY - 15), 'Difficulty Options'); $DiffText = new GTextBox (new Rect ($CenterX - 80, $CenterY + 40, $CenterX + 80, $CenterY + 55), ''); my @elems = ($qbut, $pbut, $dbut, (@HighScores ? $hbut : ()), $s_spd, $s_gnc, $difflab, $DiffText); foreach (@elems) { FS_Win()->add_pane($_) } UpdateDiffString(); while (!$action) { WaitNextEvent; $action = 'PlayDemo' if time >= $demostart; } foreach ($grad, @elems) { FS_Win()->remove_pane($_) } $action; } sub Difficulty { #($MinSpd / 8) - ($GunCRate / 40); ($MinSpd / 16) - ($GunCRate / 20) + .5; #$MinSpd / ($GunCRate + .1); } sub UpdateDiffString { my $diff = my $adiff = Difficulty; $diff = 0 if $diff < 0; $diff = 1 if $diff > 1; $DiffText->SetText($DiffStrings[($diff * $#DiffStrings) + .5]); } ######## Game ##################################################################################################################### sub Play { InitGame(); my $cticks = 0; while (!$Done) { next if TickCount() < $cticks; if (TickCount() > $cticks) { RGBForeColor(COL_SPEEDBLIP); PaintRect $BlipRect; } $cticks = TickCount() + 2; IterGame(); WaitNextEvent(0) unless $Frame % 7; if ($Paused) {last if Pause()} } CleanupGame(); } sub Pause { my $ptxt = new TextGradient ( Window => FS_Win(), Text => '- paused -', X => $CenterX, Y => $CenterY, Font => geneva, Size => 24, ); my $action; my $qbut = new GButton (new Rect ($CenterX - 40, $CenterY + 120, $CenterX + 40, $CenterY + 140), "Abort", sub { $action = 'Menu' }); FS_Win()->add_pane($qbut); ShowCursor; WaitNextEvent while $Paused and !$action; FS_Win()->remove_pane($ptxt); FS_Win()->remove_pane($qbut); return 1 if $action; SetMouse($CenterPt); WaitNextEvent; HideCursor; SetPort FS_Port(); ClipRect($GameRect); ValidRect(FS_Port()->portRect); return 0; } sub PlayDemo { $IsDemo = 1; Play(); } sub PlayGame { $IsDemo = 0; Play(); } sub InitGame { $PlayerAng = .01; $PlayerY = 260; $PlayerX = -400; $PlayerVel = 0; $Health = 100; $GunCharge = 0; @Shots = (); SetMouse($CenterPt); $Won = 0; $WScale = 1; $Lost = 0; @ObjMov = ( {'y' => -250, 'x' => 450, yv => 3, xv => 2, draw => 1, frozen => 0}, {'y' => 250, 'x' => 450, yv => 3, xv => 2, draw => 1, frozen => 0}, {'y' => -250, 'x' => -450, yv => 3, xv => 2, draw => 1, frozen => 0}, {'y' => 250, 'x' => -450, yv => 3, xv => 2, draw => 1, frozen => 0}, {'y' => 0, 'x' => 50, yv => 3, xv => 2, draw => 1, frozen => 0}, {'y' => 550, 'x' => 0, yv => 3, xv => 2, draw => 1, frozen => 0}, {'y' => -550, 'x' => 0, yv => 3, xv => 2, draw => 1, frozen => 0}, ); $TRemain = @ObjMov; SetPort FS_Port(); RGBBackColor(COL_BACKGROUND); EraseRect FS_Port()->portRect; ValidRect FS_Port()->portRect; ClipRect $GameRect; $StartTime = time; $Frame = $Done = 0; DrawHealthBar(); DrawTargetsBar(); HideCursor; if ($IsDemo) { $Clicked = 0; $Demo_AngGoal = 0; $Demo_VelGoal = 0; } } sub CleanupGame { SetPort FS_Port(); ClipRect(FS_Port()->portRect); InvalRect(FS_Port()->portRect); ShowCursor; HighScoreEntry() unless $IsDemo; Scores() if $GotHigh; $GotHigh = $Health = 0; } sub IterGame { $Frame++; SetPort FS_Port(); $DrawRegion = GetDrawRgn(); RGBForeColor(COL_WALL); { my $r = XorRgn($DrawRegion, $GameRegion); PaintRgn $r; DisposeRgn $r; } RGBForeColor(COL_FLOOR); PaintRgn $DrawRegion; RGBForeColor(COL_PLAYER); if ($IsDemo) { MoveTo($CenterX - VWIDTH+3, $CenterY + VHEIGHT-3); DrawString "demo - click or press a key to end"; } if ($Lost) { MoveTo($CenterX + 0*$WScale + $Lost, $CenterY + -10*$WScale - $Lost/2); LineTo($CenterX + 5*$WScale + $Lost, $CenterY + 10*$WScale - $Lost/2); MoveTo($CenterX + 5*$WScale - $Lost/2, $CenterY + 10*$WScale + $Lost); LineTo($CenterX + -5*$WScale - $Lost/2, $CenterY + 10*$WScale + $Lost); MoveTo($CenterX + -5*$WScale - $Lost, $CenterY + 10*$WScale); LineTo($CenterX + 0*$WScale - $Lost, $CenterY + -10*$WScale); } else { MoveTo($CenterX + 0*$WScale, $CenterY + -10*$WScale); LineTo($CenterX + 5*$WScale, $CenterY + 10*$WScale); LineTo($CenterX + -5*$WScale, $CenterY + 10*$WScale); LineTo($CenterX + 0*$WScale, $CenterY + -10*$WScale); } foreach (@ObjMov) { next unless $$_{draw}; my $f = (65535 - $$_{frozen} * 500); $f = 0 if $f < 0; RGBForeColor(new RGBColor(($f)x2, 65535)); my @p = MakePoint($$_{'x'}, $$_{'y'}); FrameOval new Rect($p[0]-5, $p[1]-5, $p[0]+5, $p[1]+5); } foreach (@Shots) { RGBForeColor($$_{color}); my @p = MakePoint($$_{'x'}, $$_{'y'}); PaintOval new Rect($p[0]-2, $p[1]-2, $p[0]+2, $p[1]+2); } if ($Won) { for (1..3*$WScale) { RGBForeColor(new RGBColor(rand 65535, rand 65535, rand 65535)); my @p = ($CenterX + rand(10 * $WScale) - 5 * $WScale, $CenterY + rand (10 * $WScale) - 5 * $WScale); PaintOval new Rect($p[0]-2, $p[1]-2, $p[0]+2, $p[1]+2); } } # WaitNextEvent unless $Frame % 5; DrawScore() unless $Frame % 30; if (not ($Won or $Lost)) { if ($IsDemo) { $Done ||= $Clicked; if (!PtInRgn(new Point($PlayerX - 50 * $MulTab[1], $PlayerY - 50 * $MulTab[3]), $LevRegion)) { $Demo_AngGoal += PtInRgn(new Point($PlayerX - 50 * -cos($PlayerAng + .5), $PlayerY - 50 * sin($PlayerAng + .5)), $LevRegion) ? .5 : -.5; $Demo_VelGoal = 2; } elsif (!PtInRgn(new Point($PlayerX - 100 * $MulTab[1], $PlayerY - 100 * $MulTab[3]), $LevRegion)) { $Demo_AngGoal += PtInRgn(new Point($PlayerX - 100 * -cos($PlayerAng + .5), $PlayerY - 100 * sin($PlayerAng + .5)), $LevRegion) ? .1 : -.1; $Demo_VelGoal = 4; } elsif ($Demo_TargInFront) { $Demo_AngGoal = $PlayerAng + $Demo_TargInFront / 20; $Demo_VelGoal = 9; } elsif (rand > .98) { $Demo_AngGoal += (rand 1) - .5; } else { $Demo_VelGoal = 8; } $PlayerAng += ($Demo_AngGoal - $PlayerAng) / 3; $PlayerVel += ($Demo_VelGoal - $PlayerVel) / 3; } else { my ($dx, $dy) = GetMouseOffset(); $dx = 30 if $dx > 30; $dx = -30 if $dx < -30; $PlayerAng += $dx * .01; $PlayerVel -= $dy / ($Height/30); } $PlayerVel = $MinSpd if $PlayerVel < $MinSpd; my ($opx, $opy) = ($PlayerX, $PlayerY); $PlayerX -= ($PlayerVel * $MulTab[1]); $PlayerY -= ($PlayerVel * $MulTab[3]); if (!PtInRgn(new Point($PlayerX, $PlayerY), $LevRegion)) { ($PlayerX, $PlayerY) = ($opx, $opy); if ($PlayerVel > 2) { my $cticks = TickCount() + 1; RGBForeColor(new RGBColor($PlayerVel * 2000 + 10000, 0, 0)); PaintRgn($DrawRegion); $Health -= $PlayerVel * 1.5; DrawHealthBar(); $Demo_AngGoal += 1 if $IsDemo; 1 while TickCount() < $cticks; if ($Health <= 0) {$Lost = 1} else {$PlayerVel = 0} } } } elsif ($Lost) { $PlayerX -= ($PlayerVel * $MulTab[1]); $PlayerY -= ($PlayerVel * $MulTab[3]); $Lost += 1.5; if ($Lost > 100) { if ($IsDemo) {$Done = 1} else {LoseMsg()} } } else { if ($Won > 100) { if ($IsDemo) {$Done = 1} else {WinMsg()} } $Won++; $WScale = (($Won/60)**10) + 1; $PlayerAng += $Won / 160; } DisposeRgn $DrawRegion; $Demo_TargInFront = 0; foreach (@ObjMov) { next unless $$_{draw}; if (PtInRect(new Point($$_{'x'}, $$_{'y'}), new Rect($PlayerX-6, $PlayerY-6, $PlayerX+7, $PlayerY+7))) { $TRemain--; $$_{draw} = 0; DrawTargetsBar(); $Won = 1 unless $TRemain; } for (my $s = 0; $s < @Shots; $s++) { my $shot = $Shots[$s]; if ( PtInRect(new Point($$_{'x'}, $$_{'y'}), new Rect($$shot{'x'}-6, $$shot{'y'}-6, $$shot{'x'}+6, $$shot{'y'}+6))) { $$_{frozen} += $$shot{charge}; splice @Shots, $s--, 1; } } if ($IsDemo) { my ($x, $y) = MakePoint($$_{'x'}, $$_{'y'}); #$Demo_TargInFront ||= ($y < $CenterY and $x > $CenterX-10 and $x < $CenterX+10 and $y > $CenterY - VHEIGHT); if ($y < $CenterY and $x > $CenterX-10 and $x < $CenterX+10 and $y > $CenterY - VHEIGHT) { $Demo_TargInFront = $$_{frozen} ? 0.00001 : ($$_{xv} * $MulTab[0] + $$_{yv} * $MulTab[1]); } } if ($$_{frozen}) { $$_{frozen} = 0 if (--$$_{frozen}) < 0; next; } my ($ox, $oy) = @$_{'x', 'y'}; $$_{'x'} += $$_{xv}; if (!PtInRgn(new Point($$_{'x'}, $$_{'y'}), $LevRegion)) { @$_{'x', 'y'} = ($ox, $oy); $$_{xv} *= -1; } $$_{'y'} += $$_{yv}; if (!PtInRgn(new Point($$_{'x'}, $$_{'y'}), $LevRegion)) { @$_{'x', 'y'} = ($ox, $oy); $$_{yv} *= -1; } $$_{yv} += (rand 2)-1; $$_{xv} += (rand 2)-1; $$_{yv} = 5 if $$_{yv} > 5; $$_{xv} = 5 if $$_{xv} > 5; $$_{yv} = -5 if $$_{yv} < -5; $$_{xv} = -5 if $$_{xv} < -5; } if ($GunCharge < 100) { $GunCharge += $GunCRate; $GunCharge = 100 if $GunCharge > 100; DrawGunBar(); } if (!$Lost and !$Won and ((Button() or $Demo_TargInFront) and $GunCharge > 50)) { my $f = (($GunCharge-50) / 50 * 65535); $f = 65535 if $f > 65535; push @Shots, {'y' => $PlayerY, 'x' => $PlayerX, xv => ($PlayerVel + 12) * cos($PlayerAng), yv => ($PlayerVel + 12) * -sin($PlayerAng), life => 100, charge => $GunCharge, color => new RGBColor(($f)x2, 65535), }; $GunCharge = 0; } for (my $s = 0; $s < @Shots; $s++) { my $shot = $Shots[$s]; $$shot{'x'} += $$shot{xv}; $$shot{'y'} += $$shot{yv}; if (!(--$$shot{life}) or !PtInRgn(new Point($$shot{'x'}, $$shot{'y'}), $LevRegion)) { splice @Shots, $s--, 1; } } } sub CompileLevel { my @polys = map { my $poly = OpenPoly; my $pol = $_; my $firstpt = shift @{$pol}; MoveTo $$firstpt[0], $$firstpt[1]; foreach my $pt (@{$pol}) { LineTo $$pt[0], $$pt[1]; } push @{$pol}, $firstpt; LineTo $$firstpt[0], $$firstpt[1]; ClosePoly; $poly; } @_; OpenRgn; foreach (@polys) { FramePoly $_; KillPoly $_; } $LevRegion = CloseRgn; my $code = <<'EOC'; sub GetDrawRgn { @MulTab = (-sin($PlayerAng), -cos($PlayerAng), -cos($PlayerAng), sin($PlayerAng)); my ($poly, @polys, @s); EOC foreach my $pol (@_) { $code .= " \$poly = OpenPoly;\n"; my $firstpt = shift @{$pol}; $code .= " MoveTo \@s = MakePoint $$firstpt[0], $$firstpt[1];\n"; foreach my $pt (@{$pol}) { $code .= " LineTo MakePoint $$pt[0], $$pt[1];\n"; } push @{$pol}, $firstpt; $code .= " LineTo \@s;\n ClosePoly;\n push \@polys, \$poly;\n"; } $code .= <<'EOC'; OpenRgn; foreach (@polys) { FramePoly $_; KillPoly $_; } return CloseRgn; } EOC eval $code; die $@ if $@; } sub MakePoint { (($_[0] + -$PlayerX) * $MulTab[0] + ($_[1] + -$PlayerY) * $MulTab[1]) * .75 / $WScale + $CenterX, (($_[0] + -$PlayerX) * $MulTab[2] + ($_[1] + -$PlayerY) * $MulTab[3]) * .75 / $WScale + $CenterY, ; } sub DrawHealthBar { $Health = 0 if $Health < 0; ClipRect(FS_Port()->portRect); my $hv = $Health / 100 * VWIDTH*2 + (!!$Health and $Health < 50); RGBForeColor(COL_HEALTHFG); PaintRect new Rect ($GameRect->left, $GameRect->bottom + 10, $GameRect->left + $hv, $GameRect->bottom + 24); RGBForeColor(COL_HEALTHBG); PaintRect new Rect ($GameRect->left + $hv, $GameRect->bottom + 10, $GameRect->right, $GameRect->bottom + 24); ClipRect($GameRect); DrawScore(); } sub DrawGunBar { ClipRect(FS_Port()->portRect); my $hv = $GunCharge / 100 * VWIDTH/2; RGBForeColor($GunCharge > 50 ? COL_GUNFG : COL_GUNLOW); PaintRect new Rect ($GameRect->right - VWIDTH/2, $GameRect->top - 14, $GameRect->right - VWIDTH/2 + $hv, $GameRect->top - 6); RGBForeColor(COL_GUNBG); PaintRect new Rect ($GameRect->right - VWIDTH/2 + $hv, $GameRect->top - 14, $GameRect->right, $GameRect->top - 6); ClipRect($GameRect); } sub DrawTargetsBar { RGBForeColor(COL_BACKGROUND); ClipRect(FS_Port()->portRect); PaintRect new Rect ($GameRect->left, $GameRect->top - 20, $GameRect->right - VWIDTH/2, $GameRect->top); my $lco = $GameRect->left + 8; my $tco = $GameRect->top - 8; for (my $i = 0; $i < @ObjMov; $i++) { RGBForeColor($i < (@ObjMov-$TRemain) ? COL_TEXT : COL_TEXTSHAD); FrameOval new Rect($lco+$i*17-5, $tco-5, $lco+$i*17+5, $tco+5); } ClipRect($GameRect); DrawScore(); } sub DrawScore { RGBForeColor(COL_TEXT); TextFont(geneva); TextSize(10); ClipRect(FS_Port()->portRect); EraseRect new Rect ($CenterX, $GameRect->top - 20, $GameRect->right - VWIDTH/2, $GameRect->top - 2); my $s = Score(); MoveTo $GameRect->right - VWIDTH/2 - StringWidth($s) - 2, $GameRect->top - 6; DrawString $s; ClipRect($GameRect); } sub WinMsg { my $g = Gradient_New(8000); EraseRect(FS_Port()->portRect); local $" = ':'; TextFont(0); TextSize(12); CenterStr( "Time: @{[reverse +(localtime(time - $StartTime))[0..2]]} Score: @{[Score()]}", 30, COL_TEXT, ); TextFont(0); TextSize(30); $Clicked = 0; my $cticks = 0; while (!$Clicked and !$Done) { WaitNextEvent(1); Gradient_Iter($g); CenterStr('You won!', 0, Gradient_Col($g)); } $Done = 1; } sub LoseMsg { EraseRect(FS_Port()->portRect); local $" = ':'; TextFont(0); TextSize(12); CenterStr( "Time: @{[reverse +(localtime(time - $StartTime))[0..2]]}", 30, COL_TEXT, ); TextFont(0); TextSize(30); CenterStr('YOU LOSE', 0, COL_TEXT); $Clicked = 0; while (!$Clicked and !$Done) { WaitNextEvent(1); } $Done = 1; } sub Score { int( $Health * (@ObjMov-$TRemain) * (Difficulty() + .5) * 2 - $Frame / 30 # Lose a point for every second ); } sub CenterStr { my ($str, $y, $fc) = @_; MoveTo(my $px = $CenterX - StringWidth($str) / 2, my $py = $CenterY + $y); if (!$OldCStr or $OldCStr ne $str) { RGBForeColor(COL_TEXTSHAD); DrawString $str; } RGBForeColor($fc); MoveTo($px - 1, $py - 1); DrawString $str; $OldCStr = $str; } ######## High Scores ##################################################################################################################### use constant ENTRY_CHAR_WIDTH => 30; use constant ENTRY_CHAR_HEIGHT => 50; use constant ENTRY_PIXSTEP => 1; use constant ENTRY_STAGES => ENTRY_CHAR_WIDTH / ENTRY_PIXSTEP / 2; sub Scores { my $mydone; my $mbut = new GButton (new Rect ($CenterX - 40, $CenterY + 120, $CenterX + 40, $CenterY + 140), "Main Menu", sub { $mydone = 1 }); my $cbut = new GButton (new Rect ($CenterX - 50, $CenterY + 95, $CenterX + 50, $CenterY + 115), "Clear Scores", sub {@HighScores = (); $mydone = 1;}); my $htext = new GTextBox (new Rect ($CenterX - 80, $CenterY - 220, $CenterX + 80, $CenterY - 200), 'High Scores'); my @elems = ($mbut, $cbut, $htext); my $vpos = $CenterY - 170; foreach (@HighScores) { push @elems, new GTextBox (new Rect ($CenterX - 100, $vpos, $CenterX + 100, $vpos + 20), $$_[0], -1); push @elems, new GTextBox (new Rect ($CenterX - 100, $vpos, $CenterX + 100, $vpos + 20), $$_[1], 1); if ($GotHigh and $$_[1] == $GotHighScore) { push @elems, new GTextBox (new Rect ($CenterX - 140, $vpos, $CenterX - 100, $vpos + 20), "--> ", 1); push @elems, new GTextBox (new Rect ($CenterX + 100, $vpos, $CenterX + 140, $vpos + 20), " <--", -1); $GotHigh = 0; } $vpos += 20; } foreach (@elems) { FS_Win()->add_pane($_) } while (!$mydone) { WaitNextEvent; } foreach (@elems) { FS_Win()->remove_pane($_) } } sub HighScoreEntry { return unless @HighScores < 10 or Score() > $HighScores[-1][1]; my $name = HandleTextField(); return unless $name; push @HighScores, [$name, Score()]; @HighScores = (sort {$b->[1] <=> $a->[1]} @HighScores)[0..($#HighScores > 9 ? 9 : $#HighScores)]; $GotHigh = 1; $GotHighScore = Score(); } sub HandleTextField { my $junkrgn = NewRgn; my $htext = new GTextBox (new Rect ($CenterX - 280, $CenterY - 170, $CenterX + 280, $CenterY - 150), 'You got a high score! Please enter your name.'); FS_Win()->add_pane($htext); WaitNextEvent; WaitNextEvent; PenNormal; my $entryrect = new Rect ($CenterX - 240, $CenterY - 25, $CenterX + 240, $CenterY + 25); EraseRect($entryrect); my $boxrect = InsetRect($entryrect, -2, -2); DrawBox($boxrect); my ($str, $leftedge, $tick) = ('', $CenterX, 0); while (1) { $CharTyped = undef; 1 until do {WaitNextEvent; defined $CharTyped}; last if $CharTyped eq "\n"; if ($CharTyped eq chr(8)) { next unless length($str); my $dchar = chop($str); for (1..ENTRY_STAGES) { 1 until TickCount() >= $tick; $tick = TickCount() + 1; $leftedge += ENTRY_PIXSTEP; ScrollRect($entryrect, ENTRY_PIXSTEP, 0, $junkrgn); DrawCharStep($dchar, $leftedge + length($str) * ENTRY_CHAR_WIDTH, $CenterY - 25, 1 - $_ / ENTRY_STAGES); DrawBox($boxrect); } next; } next if $CharTyped lt chr(31); next if length($str) >= 16; $str .= $CharTyped; for (1..ENTRY_STAGES) { 1 until TickCount() >= $tick; $tick = TickCount() + 1; $leftedge += - ENTRY_PIXSTEP; ScrollRect($entryrect, - ENTRY_PIXSTEP, 0, $junkrgn); DrawCharStep($CharTyped, $leftedge + (length($str)-1) * ENTRY_CHAR_WIDTH, $CenterY - 25, $_ / ENTRY_STAGES); DrawBox($boxrect); } } DisposeRgn $junkrgn; EraseRect $boxrect; FS_Win()->remove_pane($htext); return $str; } sub DrawBox { my ($box) = @_; RGBForeColor(COL_FLOOR); PenSize(2, 2); FrameRect($box); PenSize(1, 1); } sub DrawCharStep { my ($char, $left, $top, $stage) = @_; my $rect = ltwh($left, $top, ENTRY_CHAR_WIDTH, ENTRY_CHAR_HEIGHT); my $inrect = InsetRect($rect, (1-$stage) * ENTRY_CHAR_WIDTH, (1-$stage) * ENTRY_CHAR_HEIGHT); TextFont(monaco); TextSize(36); RGBForeColor(new RGBColor(rand 65535, rand 65535, rand 65535)); MoveTo($left + 5, $top + ENTRY_CHAR_HEIGHT - 12); EraseRect($rect); FrameRect($inrect) if $stage < .99; ClipRect($inrect); DrawString($char); ClipRect(GetPort()->portRect); } ######## Misc stuff ##################################################################################################################### sub SetMouse { use Mac::LowMem; my ($pt) = @_; LMSetMouseTemp($pt); LMSetRawMouseLocation($pt); LMSetCursorNew(1); } sub GetMouseOffset { use Mac::LowMem; my $pt = GetMouse; LMSetMouseTemp($CenterPt); LMSetRawMouseLocation($CenterPt); LMSetCursorNew(1); ($pt->h - $CenterX, $pt->v - $CenterY); } sub ltwh ($$$$) {new Rect $_[0], $_[1], $_[0]+$_[2], $_[1]+$_[3]} sub r2p { my ($left, $top, $right, $bottom) = @_; return [[$left, $top], [$left, $bottom], [$right, $bottom], [$right, $top]]; } ######## Button ##################################################################################################################### { package GButton; use Mac::Pane; use Mac::Windows; use Mac::QuickDraw; # use Mac::KTools qw(ClickRect); sub ltwh ($$$$); BEGIN { *ltwh = *main::ltwh; use vars qw(@ISA); @ISA = qw(Mac::Pane); } use constant LIGHT => new RGBColor ((48000)x3); use constant MEDIUM => new RGBColor ((31000)x3); use constant DARK => new RGBColor ((15000)x3); use constant TEXT => new RGBColor ((65535)x3); sub new { my ($class, $rect, $name, $action) = @_; my $self = bless { rect => $rect, name => $name, action => $action, active => 1, }, $class; } sub move { my ($self, $w, $rect) = @_; SetPort $w->window; InvalRect($self->{rect}); $self->{rect} = $rect; InvalRect($rect); } sub attach { my ($self, $w) = @_; SetPort $w->window; InvalRect($self->{rect}); $self->{window} = $w; } sub detach { my ($self, $w) = @_; SetPort $w->window; InvalRect($self->{rect}); $self->{window} = undef; } sub activate { my ($self, $w, $active) = @_; $self->{active} = $active; InvalRect $self->{rect}; } sub redraw { my ($self) = @_; PenNormal; my $rect = $self->{rect}; RGBForeColor(MEDIUM); PaintRect($rect); if ($self->{active}) { PenSize(1, 1); for (my $in = 0; $in < 3; $in++) { my $inr = $in + 1; RGBForeColor(LIGHT); MoveTo($rect->right - ($inr+1), $rect->top + $in); LineTo($rect->left + $in, $rect->top + $in); LineTo($rect->left + $in, $rect->bottom - ($inr+1)); RGBForeColor(DARK); MoveTo($rect->right - $inr, $rect->top + ($in+1)); LineTo($rect->right - $inr, $rect->bottom - $inr); LineTo($rect->left + ($in+1), $rect->bottom - $inr); } } TextFont(0); TextSize(0); RGBForeColor(TEXT); MoveTo($rect->left + (($rect->right - $rect->left) - StringWidth($self->{name}))/2, $rect->top + 14); DrawString $self->{name}; } sub click { my ($self, $w, $pt) = @_; return 0 unless PtInRect($pt, $self->{rect}); my $inrect = InsetRect($self->{rect}, 1, 1); my $doit; if (ClickRect($inrect)) { $self->{action}->($inrect); InvertRect($inrect); } 1; } sub SetName { my ($self, $name) = @_; $self->{name} = $name; SetPort $self->{window}->window; InvalRect $self->{rect}; } } ######## Slider ##################################################################################################################### { package GSlider; use Mac::Pane; use Mac::Windows; use Mac::QuickDraw; use Mac::Events; # use Mac::KTools qw(ClickRect); sub ltwh ($$$$); BEGIN { *ltwh = *main::ltwh; use vars qw(@ISA); @ISA = qw(Mac::Pane); } use constant LIGHT => new RGBColor ((43000)x3); use constant MEDIUM => new RGBColor ((30000)x3); use constant DARK => new RGBColor ((15000)x3); use constant TEXT => new RGBColor ((65535)x3); sub new { my ($class, $rect, $name, $min, $max, $value, $action) = @_; my $self = bless { rect => $rect, srect => InsetRect($rect, 1, 1), name => $name, action => $action, active => 1, min => $min, max => $max, value => $value, }, $class; $self->{slidewidth} = ($self->{srect}->right - $self->{srect}->left) / ($max - $min + 1); $self; } sub attach { my ($self, $w) = @_; SetPort $w->window; InvalRect($self->{rect}); $self->{window} = $w; } sub detach { my ($self, $w) = @_; SetPort $w->window; InvalRect($self->{rect}); $self->{window} = undef; } sub activate { my ($self, $w, $active) = @_; $self->{active} = $active; InvalRect $self->{rect}; } sub redraw { my ($self) = @_; PenNormal; my $rect = $self->{rect}; RGBForeColor(MEDIUM); PaintRect($rect); if ($self->{active}) { PenSize(1, 1); for (my $in = 0; $in < 3; $in++) { my $inr = $in + 1; RGBForeColor(DARK); MoveTo($rect->right - ($inr+1), $rect->top + $in); LineTo($rect->left + $in, $rect->top + $in); LineTo($rect->left + $in, $rect->bottom - ($inr+1)); RGBForeColor(LIGHT); MoveTo($rect->right - $inr, $rect->top + ($in+1)); LineTo($rect->right - $inr, $rect->bottom - $inr); LineTo($rect->left + ($in+1), $rect->bottom - $inr); } } TextFont(0); TextSize(0); RGBForeColor(TEXT); my $label = sprintf "%s: %.2f", $self->{name}, $self->{value}; MoveTo($rect->left + (($rect->right - $rect->left) - StringWidth($label))/2, $rect->top + 14); DrawString $label; my $srect = $self->{srect}; InvertRect new Rect ($srect->left + $self->{value} * $self->{slidewidth}, $srect->top, $srect->left + ($self->{value} + 1) * $self->{slidewidth}, $srect->bottom); RGBForeColor(LIGHT); MoveTo($srect->left + $self->{value} * $self->{slidewidth}, $srect->top); LineTo($srect->left + $self->{value} * $self->{slidewidth}, $srect->bottom - 1); RGBForeColor(DARK); MoveTo($srect->left + ($self->{value} + 1) * $self->{slidewidth} - 1, $srect->top); LineTo($srect->left + ($self->{value} + 1) * $self->{slidewidth} - 1, $srect->bottom - 1); } sub click { my ($self, $w, $pt) = @_; return 0 unless PtInRect($pt, $self->{rect}); my $opt; my $offset = $pt->h - $self->{value} * $self->{slidewidth}; while (StillDown()) { $opt = $pt; $pt = GetMouse; next if EqualPt($pt, $opt); my $v = (GetMouse->h - $offset) / $self->{slidewidth}; $v = $self->{min} if $v < $self->{min}; $v = $self->{max} if $v > $self->{max}; $self->{value} = $v; $self->redraw; $self->{action}->($v) if $self->{action}; } 1; } sub SetValue { my ($self, $val) = @_; $self->{value} = $val; SetPort $self->{window}->window; InvalRect $self->{rect}; } sub GetValue {$_[0]{value}} } ######## Text Box ##################################################################################################################### { package GTextBox; use Mac::Pane; use Mac::Windows; use Mac::QuickDraw; sub ltwh ($$$$); BEGIN { *ltwh = *main::ltwh; use vars qw(@ISA); @ISA = qw(Mac::Pane); } use constant TEXT => new RGBColor ((65535)x3); sub new { my ($class, $rect, $name, $align) = @_; my $self = bless { rect => $rect, name => $name, align => $align || 0, }, $class; } sub attach { my ($self, $w) = @_; SetPort $w->window; InvalRect($self->{rect}); $self->{window} = $w; } sub detach { my ($self, $w) = @_; SetPort $w->window; InvalRect($self->{rect}); $self->{window} = undef; } sub redraw { my ($self) = @_; PenNormal; my $rect = $self->{rect}; TextFont(0); TextSize(0); RGBForeColor(TEXT); if ($self->{align} == -1) { MoveTo($rect->left, $rect->top + 13)} elsif ($self->{align} == 1) { MoveTo($rect->right - StringWidth($self->{name}) - 1, $rect->top + 13)} else { MoveTo($rect->left + (($rect->right - $rect->left) - StringWidth($self->{name}))/2, $rect->top + 13)} ClipRect($rect); DrawString $self->{name}; ClipRect($self->{window}->window->portRect); } sub SetText { my ($self, $name) = @_; $self->{name} = $name; SetPort $self->{window}->window; EraseRect $self->{rect}; $self->redraw; } } ######## TextGradient ##################################################################################################################### { package TextGradient; use Carp; use Mac::Pane; use Mac::Windows; use Mac::QuickDraw; use Mac::Events; use Mac::Fonts; # use Mac::KTools qw(/^Gradient_/); use strict; use vars qw($VERSION @ISA %stdparam); BEGIN { sub ltwh ($$$$); *ltwh = *main::ltwh; $VERSION = '1.00'; @ISA = qw(Mac::Pane); %stdparam = map {($_, 1)} qw( Font Size X Y Window Text ); } sub new { my ($class, %param) = @_; foreach (keys %param) { if (!$stdparam{$_}) { carp "Unknown parameter: $_" if $^W; delete $param{$_}; } } my $self = bless { X => $param{X}, Y => $param{Y}, Font => 0, Size => 24, Text => '', }, $class; @{$self}{keys %param} = values %param; $self->SetStyle($self->{Font}, $self->{Size}); $self->{Window}->add_pane($self) if $self->{Window}; $self; } sub attach { my ($self, $window) = @_; $self->{Window} = $window; SetPort $window->window; InvalRect $self->Bounds; 1; } sub detach { my ($self, $window) = @_; SetPort $window->window; InvalRect $self->Bounds; $self->{Window} = undef; 1; } sub redraw { my ($self, $win) = @_; PenNormal; TextFont($self->{Font}); TextSize($self->{Size}); my $right = $win->window->portRect->right; my $g = Gradient_New(10000); for (my $i = $self->{Y} - $self->{ascent}; $i < $self->{Y} + $self->{descend}; $i+=1) { Gradient_Iter($g); MoveTo $self->{X}, $self->{Y}; my $brect = new Rect(0, $i, $right, $i+1); ClipRect($brect); RGBForeColor(Gradient_Col($g)); #EraseRect $brect; DrawString $self->{Text}; } ClipRect($win->window->portRect); PenNormal; } sub DESTROY { my ($self) = @_; } sub Bounds { my ($self) = @_; my $state = GetPenState; TextFont($self->{Font}); TextSize($self->{Size}); my $r = new Rect ( $self->{X} - 1, $self->{Y} - $self->{ascent} - 1, $self->{X} + StringWidth($self->{Text}), $self->{Y} + $self->{descend} + 2, ); SetPenState $state; $r; } sub SetStyle { my ($self, $font, $size) = @_; $self->{Font} = $font; $self->{Size} = $size; if ($self->{Window}) { SetPort $self->{Window}->window; InvalRect $self->Bounds if defined $self->{ascent}; } my $state = GetPenState; TextFont($self->{Font}); TextSize($size); ($self->{ascent}, $self->{descend}) = GetFontInfo(); $self->{X} -= StringWidth($self->{Text})/2; SetPenState $state; InvalRect $self->Bounds if $self->{Window}; }} __DATA__ # feel free to put this into a .pm file and use it in your programs package Mac::KTools; require Exporter; @ISA = Exporter; @EXPORT_OK = qw( FS_Start FS_Stop FS_Port FS_Win FS_Hook Gradient_New Gradient_Iter Gradient_Col ClickRect $GPort ); use strict; use Carp; use Mac::QuickDraw; use Mac::Windows; use Mac::Menus; use Mac::LowMem; use Mac::Events; use constant FILEMENU => GetMenu 129; use constant EDITMENU => GetMenu 130; use vars qw( $VERSION $FS_On $FS_Win @FS_OSWins $FS_Bounds $FS_OldGrayRgn $FS_OldMBH $CGA $gnormal $GPort ); $VERSION = "1.01"; BEGIN {eval 'use ColorGamma'; $CGA = !$@} # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # sub FS_Start { return if $FS_On; $FS_On = 1; if ($CGA) { $gnormal = new_GIHdl(); StartFading($gnormal) and die "Bad start"; FadeToBlack(100, inverseQuadraticFade()); } $FS_OldGrayRgn = CopyRgn(GetGrayRgn()); RectRgn(GetGrayRgn, GetWMgrPort->portRect); $FS_OldMBH = LMGetMBarHeight; LMSetMBarHeight(0); DisableItem FILEMENU; DisableItem EDITMENU; $FS_Win = new MacColorWindow ( $FS_Bounds = GetMainDevice->gdRect, 'Fullscreen 0', 1, dBoxProc, 1, ); $FS_Win->sethook('drawgrowicon', sub {}); SetPort $FS_Win->window; RGBBackColor(new RGBColor(0,0,0)); for (1..20) {WaitNextEvent} FadeToGamma($gnormal, 1, inverseQuadraticFade()) if $gnormal; return ($FS_Bounds->right - $FS_Bounds->left, $FS_Bounds->bottom - $FS_Bounds->top); } sub FS_Stop { return unless $FS_On; if ($CGA) { StartFading($gnormal = new_GIHdl()) and die "Bad start"; FadeToBlack(40, inverseQuadraticFade()); } LMSetMBarHeight($FS_OldMBH) if $FS_OldMBH; EnableItem GetMenu 129; EnableItem GetMenu 130; if ($FS_Win) { SetPort $FS_Win->window; RGBBackColor(new RGBColor(0,0,0)); EraseRect($FS_Bounds); } if ($FS_OldGrayRgn) { CopyRgn($FS_OldGrayRgn, GetGrayRgn); DisposeRgn $FS_OldGrayRgn; } $FS_Win->dispose if $FS_Win; $FS_On = 0; WaitNextEvent; WaitNextEvent; WaitNextEvent; WaitNextEvent; # let things redraw if ($gnormal) { FadeToGamma($gnormal, 90, inverseQuadraticFade()); StopFading($gnormal, 1); } } sub FS_Port {$FS_Win->window} sub FS_Win {$FS_Win} sub FS_Hook { my ($hook, $sub) = @_; $FS_Win->sethook($hook => sub {shift; goto &$sub}); } END {FS_Stop()} # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # sub Gradient_New { my ($speed) = @_; [map {val => rand 65535, vel => rand ($speed || 200)}, 1..3]; } sub Gradient_Iter { foreach my $c (@{$_[0]}) { $c->{val} += $c->{vel}; if ($c->{val} > 65535 or $c->{val} < 0) { $c->{vel} *= -1; redo; } } } sub Gradient_Col { return new RGBColor(map {$_->{val}} @{$_[0]}) } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # sub ClickRect { my ($r) = @_; my ($in, $oin) = (1, 1); InvertRect($r); while (StillDown()) { $in = PtInRect(GetMouse, $r); if ($in != $oin) { InvertRect($r); } $oin = $in; } $in; } # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # { package GrafPortVar; use Carp; use Mac::QuickDraw; sub TIESCALAR {bless {}, $_[0]} sub FETCH {GetPort()} sub STORE { my ($class, $port) = @_; ref $port eq 'GrafPtr' or croak "Attempt to set \$GPort to a @{[ref $port]} instead of a GrafPtr"; my $oport = GetPort; SetPort $port; return $oport; } } tie $GPort, 'GrafPortVar'; # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # __END__ # ===== Want to unsubscribe from this list? # ===== Send mail with body "unsubscribe" to macperl-request@macperl.org