[Date Prev][Date Next][Thread Prev][Thread Next] [Search] [Date Index] [Thread Index]

[MacPerl] Chaser 1.1



#!perl -w

# Chaser v1.1
# 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 a key to stop.
#
# 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
# 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 MacPerl;
{
  local $/; eval <DATA>; die $@ if $@;
  import Mac::KTools qw(/^FS_/ /^Gradient_/);
}

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_TEXT => new RGBColor((65535)x3);
use constant COL_TEXTSHAD => new RGBColor((20000)x3);
use constant COL_SPEEDBLIP => new RGBColor (65535, 0, 0);

use vars qw(
  $Done $Width $Height
        $CenterX $CenterY $CenterPt
  $Clicked $OldCStr $StartTime
  $BlipRect

  $GameRect $GameRegion
  @LevMRects
  $LevRegion
  $DrawRegion
  @MulTab @InvMulTab

  $PlayerX $PlayerY $PlayerAng
  $PlayerVel $Health
  
  @ObjMov $TRemain
  @Shots $GunCharge

  $Won $WScale
);

$| = 1;
@LevMRects = (
  [-500, -300, 500, 600],
  [-400, -200, -100, 200],
  [100, -200, 400, 200],
  [-50, 300, 50, 597],
);
MacPerl::Quit(kMacPerlQuitIfRuntime);

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 MakePoint {
  (($_[0] + -$PlayerX) * $MulTab[0] + ($_[1] + -$PlayerY) * $MulTab[1]) * .75 + $CenterX,
  (($_[0] + -$PlayerX) * $MulTab[2] + ($_[1] + -$PlayerY) * $MulTab[3]) * .75 + $CenterY,
  ;
}

sub GetDrawRgn {
  @MulTab = (-sin($PlayerAng), -cos($PlayerAng),
             -cos($PlayerAng), sin($PlayerAng));
#  @InvMulTab = (sin(-$PlayerAng), cos(-$PlayerAng),
#                cos(-$PlayerAng), -sin(-$PlayerAng));
  my @polys = map {
    my $poly = OpenPoly;
    my @p = @$_;
    MoveTo my @s = MakePoint $p[0], $p[1];
    LineTo MakePoint $p[2], $p[1];
    LineTo MakePoint $p[2], $p[3];
    LineTo MakePoint $p[0], $p[3];
    LineTo @s;
    ClosePoly;
    $poly;
  } @LevMRects;
  OpenRgn;
  foreach (@polys) {
    FramePoly $_;
    KillPoly $_;
  }
  return CloseRgn;
}

sub GetLevRgn {
  my @polys = map {
    my $poly = OpenPoly;
    my @p = @$_;
    MoveTo $p[0], $p[1];
    LineTo $p[2], $p[1];
    LineTo $p[2], $p[3];
    LineTo $p[0], $p[3];
    LineTo $p[0], $p[1];
    ClosePoly;
    $poly;
  } @LevMRects;
  OpenRgn;
  foreach (@polys) {
    FramePoly $_;
    KillPoly $_;
  }
  return CloseRgn;
}

sub DrawHealthBar {
  $Health = 0 if $Health < 0;
  ClipRect(FS_Port()->portRect);
  my $hv = $Health / 100 * $Width / 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);
}

sub DrawTargetsBar {
  RGBForeColor(COL_BACKGROUND);
  ClipRect(FS_Port()->portRect);
  PaintRect new Rect ($GameRect->left, $GameRect->top - 20, $GameRect->right, $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);
}

HideCursor;
($CenterX, $CenterY) = map $_ / 2, ($Width, $Height) = FS_Start();
$CenterPt = new Point ($CenterX, $CenterY);

FS_Hook(click => sub {$Clicked = 1});
FS_Hook(key => sub {$Done = 1});
 
SetPort FS_Port();
#$GameRect = new Rect ($Width/4, $Height/4, $Width - $Width/4, $Height - $Height/4);
#$BlipRect = new Rect ($Width/4, $Height/4, $Width/4 + 5, $Height/4 + 5);
$GameRect = new Rect ($CenterX - VWIDTH, $CenterY - VHEIGHT, $CenterX + VWIDTH, $CenterY + VHEIGHT);
$BlipRect = new Rect ($CenterX - VWIDTH, $CenterY - VHEIGHT, $CenterX - VWIDTH + 5, $CenterY - VHEIGHT + 5);
$LevRegion = GetLevRgn;
$GameRegion = NewRgn;
RectRgn $GameRegion, $GameRect;

NEWGAME:

$PlayerAng = 0;
$PlayerY = 250;
$PlayerX = 100;
$Health = 100;
$GunCharge = 0;
@Shots = ();
SetMouse($CenterPt);
$Won = 0; $WScale = 1;

@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' => 0, yv => 3, xv => 2, draw => 1, frozen => 0},

  {'y' => 550, 'x' => 450, yv => 3, xv => 2, draw => 1, frozen => 0},
  {'y' => 550, 'x' => -450, yv => 3, xv => 2, draw => 1, frozen => 0},
);
$TRemain = @ObjMov;

SetPort FS_Port();
RGBBackColor(COL_BACKGROUND);
EraseRect FS_Port()->portRect;
ClipRect $GameRect;
InvertRgn ($DrawRegion = GetDrawRgn);

DrawHealthBar();
DrawTargetsBar();
$StartTime = time;

my $cticks = 0;
my $t;
while ($t++, !$Done) {
  next if TickCount() < $cticks;
  if (TickCount() > $cticks) {
    RGBForeColor(COL_SPEEDBLIP);
    PaintRect $BlipRect;
  }
  $cticks = TickCount() + 2; # limit speed to 30fps
  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);
  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) {
    WinMsg() if $Won > 100;
    $Won++; $WScale = (($Won/60)**10) + 1;
    $PlayerAng += $Won / 100;
    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 $t % 5;

  if (!$Won) {
    my ($dx, $dy) = GetMouseOffset;
    $PlayerAng += $dx * .01;
    $PlayerVel -= $dy / ($Height/30);
    $PlayerVel = 0 if $PlayerVel < 0;

    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);
        #InvertRect($GameRect);
        $Health -= $PlayerVel * 1.5;
        DrawHealthBar();
        $PlayerVel = 0;
        #InvertRect($GameRect);
        1 while TickCount() < $cticks;
        last unless $Health > 0;
      }
    }
  }
  DisposeRgn $DrawRegion;

  foreach (@ObjMov) {
    next unless $$_{draw};
    if (PtInRect(new Point($$_{'x'}, $$_{'y'}), new Rect($PlayerX-6, $PlayerY-6, $PlayerX+6, $PlayerY+6))) {
      $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 ($$_{frozen}) {
      $$_{frozen}--;
      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 .5)-.25;
    $$_{xv} += (rand .5)-.25;
  }

  $GunCharge += 5 if $GunCharge < 100;
  if (Button() 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 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;
}

sub WinMsg {
  my $g = Gradient_New(8000);
  EraseRect(FS_Port()->portRect);
  local $" = ':';
  TextFont(0); TextSize(12);
  CenterStr(
    "Time: @{[reverse +(localtime(time - $StartTime))[0..2]]}",
    30,
    COL_TEXT,
  );
  TextFont(0); TextSize(30);
  $Clicked = 0;
  my $cticks = 0;
  while (!$Clicked and !$Done) {
    WaitNextEvent;
    Gradient_Iter($g);
    CenterStr('You won!', 0, Gradient_Col($g));
  }
  exit;
}

exit if $Done;
EraseRect(FS_Port()->portRect);
TextFont(0); TextSize(30);
CenterStr('Click to try again.', 0, COL_TEXT);
$Clicked = 0;
{
  $Health = 100; my $cticks = 0;
  while ($Health > 0 and !$Clicked and !$Done) {
    next if TickCount() < $cticks;
    $cticks = TickCount() + 3;
    WaitNextEvent;
    $Health -= .5;
    DrawHealthBar();
  };
}

goto NEWGAME if $Clicked;

END {
  ShowCursor;
  DisposeRgn $DrawRegion if $DrawRegion;
  DisposeRgn $LevRegion if $LevRegion;
}

#---------------------------------------------------------------------------------
__DATA__

package Mac::KTools;
require Exporter;
@Mac::KTools::ISA = qw(Exporter);

@Mac::KTools::EXPORT_OK = qw(
  FS_Start
  FS_Stop
  FS_Port
  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(
  $FS_On
  $FS_Win @FS_OSWins
  $FS_Bounds
  $FS_OldGrayRgn
  $FS_OldMBH
  $CGA $gnormal

  $GPort
);

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(200, 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;
    # let things redraw
  if ($gnormal) {
    FadeToGamma($gnormal, 90, inverseQuadraticFade());
    StopFading($gnormal, 1);
  }
}

sub FS_Port {$FS_Win->window}

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