#!/usr/bin/perl -w =comment On original Super Pang graphics: GFXSET 0 : Backgrounds, Font PALETTES: 0 : 1 : Player 2 (narancs) 2 : Player 1 (kék) 3 : Kék szigony 4 : narancs szigony 5 : Zöld golyók 6 : Kék golyók 7 : 8 : 9 : Sötét narancssárga golyók (?) A : Sötét piros golyók B : C : Gyümölcsök D : E : Narancssárga golyók F : Piros golyók =cut use strict; use SDL; use SDL::App; use SDL::Event; use SDL::Surface; use SDL::Timer; use SDL::Palette; use SDL::Sound; use SDL::Mixer; use vars qw ( $App $Background @GameObjects $BallSurface $GuySurface $HarpoonSurface $PopSurface %Sounds $Mixer @BallDesc $ScreenHeight $ScreenWidth %Keys %Events ); ########################################################################## # GLOBAL CONFIGURATION ########################################################################## %Sounds = ( 'pop' => 'pop.voc', 'shoot' => 'sound044.voc', 'death' => 'meow.voc', ); @BallDesc = ( { 'width' => 128, 'height' => 106, 'speedY' => 11, 'popIndex' => 0, 'rect' => new SDL::Rect(-x => 0, -y => 12, -width => 128, -height => 106) }, { 'width' => 96, 'height' => 80, 'speedY' => 9, 'popIndex' => 1, 'rect' => new SDL::Rect(-x => 128, -y => 8, -width => 96, -height => 80) }, { 'width' => 64, 'height' => 53, 'speedY' => 7, 'popIndex' => 2, 'rect' => new SDL::Rect(-x => 0, -y => 134, -width => 64, -height => 53) }, { 'width' => 32, 'height' => 28, 'speedY' => 5.5, 'popIndex' => 3, 'rect' => new SDL::Rect(-x => 224, -y => 2, -width => 32, -height => 28) }, { 'width' => 16, 'height' => 15, 'speedY' => 4.5, 'popIndex' => 4, 'rect' => new SDL::Rect(-x => 232, -y => 42, -width => 16, -height => 15) }, ); $BallDesc[0]->{nextgen} = $BallDesc[1]; $BallDesc[1]->{nextgen} = $BallDesc[2]; $BallDesc[2]->{nextgen} = $BallDesc[3]; $BallDesc[3]->{nextgen} = $BallDesc[4]; $ScreenWidth = 800; $ScreenHeight = 600; ########################################################################## # GAME OBJECT CLASSES ########################################################################## package Ball; package Guy; package Harpoon; package Pop; package GameObject; sub new { my ($class) = @_; my $self = { 'rect' => new SDL::Rect( -x => 0, -y => 0, -width => 0, -height => 0 ), 'speedX' => 2, 'speedY' => 2, 'x' => 0, 'y' => 0, 'w' => 10, 'h' => 10, }; bless $self, $class; } sub Delete { my $self = shift; my ($i); for ($i = 0; $i < scalar @::GameObjects; ++$i) { if ($::GameObjects[$i] eq $self) { splice @::GameObjects, $i, 1; last; } } $self->Clear(); } sub Advance { my ($self) = @_; $self->{x} += $self->{speedX}; $self->{y} += $self->{speedY}; } sub Clear { my ($self) = @_; $::App->fill( $self->{rect}, new SDL::Color() ); } sub TransferRect { my ($self) = @_; $self->{rect}->x($self->{x}); $self->{rect}->y($self->{y}); $self->{rect}->width($self->{w}); $self->{rect}->height($self->{h}); } sub Draw { my ($self) = @_; $self->TransferRect(); $::App->fill( $self->{rect}, new SDL::Color(-r => 0x80) ); } package Ball; @Ball::ISA = qw(GameObject); $Ball::Gravity = 0.1; for (my $i=0; $i < 5; ++$i) { my $desc = $::BallDesc[$i]; $desc->{bounceY} = $desc->{speedY} * $desc->{speedY} / $Ball::Gravity / 2; } sub new { my ($class, $description, $x, $y, $dir) = @_; my ($self); $self = new GameObject; %{$self} = ( %{$self}, 'x' => $x, 'y' => $y, 'w' => $description->{width}, 'h' => $description->{height}, 'desc' => $description, ); $self->{speedX} = $dir > 0 ? 2 : -2; bless $self, $class; } sub Advance { my ($self) = @_; $self->{speedY} += $Ball::Gravity; $self->SUPER::Advance(); if ($self->{y} > $::ScreenHeight - $self->{h}) { $self->{y} = $::ScreenHeight - $self->{h}; $self->{speedY} = -$self->{desc}->{speedY}; } if ($self->{y} < 0) { $self->{y} = 0; $self->{speedY} = abs($self->{speedY}); } if ($self->{x} < 0) { $self->{x} = 0; $self->{speedX} = abs( $self->{speedX} ); } if ($self->{x} > $::ScreenWidth - $self->{w}) { $self->{x} = $::ScreenWidth - $self->{w}; $self->{speedX} = -abs( $self->{speedX} ); } my ($gameObject); foreach $gameObject (@::GameObjects) { if ( ref $gameObject eq 'Harpoon' ) { if ($self->Collisions($gameObject)) { $self->Pop; $gameObject->Delete(); last; } } elsif ( ref $gameObject eq 'Guy' ) { if ($self->Collisions($gameObject)) { $gameObject->Kill(); } } } } sub Draw { my ($self) = @_; $self->TransferRect(); $::BallSurface->blit( $self->{desc}->{rect}, $::App, $self->{rect} ); } sub Collisions { my ($self, $other) = @_; # Bounding box detection return 0 if $self->{x} >= $other->{x} + $other->{w}; return 0 if $other->{x} >= $self->{x} + $self->{w}; return 0 if $self->{y} >= $other->{y} + $other->{h}; return 0 if $other->{y} >= $self->{y} + $self->{h}; # Circle vs rectangle collision my ($centerX, $centerY, $boxAxisX, $boxAxisY, $boxCenterX, $boxCenterY, $distSquare, $distance); $boxAxisX = ($other->{collisionw} or $other->{w}) / 2; $boxAxisY = ($other->{collisionh} or $other->{h}) / 2; $boxCenterX = $other->{x} + $other->{w} / 2; $boxCenterY = $other->{y} + $other->{h} / 2; $centerX = $self->{x} + $self->{w} / 2; $centerY = $self->{y} + $self->{h} / 2; # Translate coordinates to the box center $centerX -= $boxCenterX; $centerY -= $boxCenterY; $centerX = abs($centerX); $centerY = abs($centerY); if ($centerX < $boxAxisX) { return 1 if $centerY < $boxAxisY + $self->{h} / 2; return 0; } if ($centerY < $boxAxisY) { return 2 if $centerX < $boxAxisX + $self->{w} / 2; return 0; } $distSquare = ($centerX-$boxAxisX) * ($centerX-$boxAxisX); $distSquare+= ($centerY-$boxAxisY) * ($centerY-$boxAxisY); return 3 if $distSquare < $self->{w} * $self->{h} / 4; return 0; } sub Pop { my $self = shift; my ($nextgen, $child1, $child2, $x, $y, $speedY, $altitude); ::PlaySound('pop'); $nextgen = $self->{desc}->{nextgen}; if ($nextgen) { $x = $self->{x} + $self->{w} / 2; $y = $self->{y} + ( $self->{h} - $nextgen->{height} ) / 2; $altitude = $::ScreenHeight - $self->{y} - $self->{h}; if ($altitude > $nextgen->{bounceY}) { $speedY = 3; } else { $speedY = 3; while ($speedY * $speedY / $Ball::Gravity / 2 + $altitude < $nextgen->{bounceY}) { ++$speedY; } } # print STDERR "POP speed: $speedY; alt: $altitude; ngspeedy: $nextgen->{speedY}; ngbounce: $nextgen->{bounceY}\n"; $child1 = new Ball($nextgen, $x - $nextgen->{width}, $y, 0); $child1->{speedY} = -$speedY; $child2 = new Ball($nextgen, $x, $y, 1); $child2->{speedY} = -$speedY; push @::GameObjects, ($child1, $child2); } push @::GameObjects, (new Pop($self->{x}, $self->{y}, $self->{desc}->{popIndex})); $self->Delete(); } package Pop; @Pop::ISA = qw(GameObject); @Pop::Description = ( { 'xoffset' => 0, 'yoffset' => 12, 'srcx' => 0, 'srcy' => 0, 'size' => 128, }, { 'xoffset' => 0, 'yoffset' => 8, 'srcx' => 0, 'srcy' => 128, 'size' => 96, }, { 'xoffset' => 0, 'yoffset' => 6, 'srcx' => 0, 'srcy' => 224, 'size' => 64, }, { 'xoffset' => 0, 'yoffset' => 2, 'srcx' => 256, 'srcy' => 224, 'size' => 32, }, { 'xoffset' =>-8, 'yoffset' => -8, 'srcx' => 256, 'srcy' => 256, 'size' => 32, }, ); sub new { my ($class, $x, $y, $index) = @_; my ($self, $desc); $desc = $Pop::Description[$index], $self = new GameObject; %{$self} = ( %{$self}, 'x' => $x + $desc->{xoffset}, 'y' => $y + $desc->{yoffset}, 'w' => $desc->{size}, 'h' => $desc->{size}, 'desc' => $desc, 'anim' => 0, ); bless $self, $class; } sub Advance { my $self = shift; ++$self->{anim}; if ($self->{anim} >= 20) { $self->Delete(); } } sub Draw { my $self = shift; my ($phase, $srcrect); $self->TransferRect(); $phase = int($self->{anim} / 5); $phase = 3 if $phase > 3; $srcrect = new SDL::Rect( -x => $self->{desc}->{srcx} + $phase * $self->{w}, -y => $self->{desc}->{srcy}, -width => $self->{w}, -height => $self->{h} ); $::PopSurface->blit( $srcrect, $::App, $self->{rect} ); } package Guy; @Guy::ISA = qw(GameObject); sub new { my ($class, $number) = @_; my ($self); $self = new GameObject; %{$self} = ( %{$self}, 'number' => $number, 'x' => 100 + $number * 100, 'y' => $::ScreenHeight - 64, 'w' => 64, 'h' => 64, 'collisionw' => '30', 'collisionh' => '55', 'delay' => 0, 'speedY' => 0, 'speedX' => 0, 'dir' => $number % 2, 'state' => 'idle', 'harpoons' => 0, 'invincible' => 100, ); bless $self, $class; } sub Fire { my ($self) = @_; if ($self->{harpoons} < 2) { ++$self->{harpoons}; unshift @::GameObjects, (new Harpoon($self)); $self->{state} = 'shoot'; $self->{delay} = 7; ::PlaySound('shoot'); return 1; } return 0; } sub Advance { my ($self) = @_; --$self->{invincible}; if ($self->{delay} > 0) { --$self->{delay}; return; } $self->{speedX} = 0; $self->{state} = 'idle'; if ( $::Events{::SDLK_UP} ) { return if $self->Fire(); } if ( $::Keys{::SDLK_LEFT} ) { $self->{speedX} = -4; $self->{dir} = 0; $self->{state} = 'walk'; } elsif ( $::Keys{::SDLK_RIGHT} ) { $self->{speedX} = 4; $self->{dir} = 1; $self->{state} = 'walk'; } $self->SUPER::Advance(); if ($self->{x} < 0) { $self->{x} = 0; } if ($self->{x} > $::ScreenWidth - $self->{w}) { $self->{x} = $::ScreenWidth - $self->{w}; } } sub Draw { my ($self) = @_; my ($srcrect, $srcx, $srcy, $srcw, $srch); return if ($self->{invincible} > 0 and int($self->{invincible} / 4) % 2); $srcw = $srch = 64; if ($self->{state} eq 'idle') { $srcx = $self->{dir} * 128; $srcy = 64; } elsif ($self->{state} eq 'walk') { $srcx = $self->{dir} * 256 + (int($self->{x} / 16) % 4) * 64; $srcy = 0; } elsif ($self->{state} eq 'shoot') { $srcx = $self->{dir} * 128 + 64; $srcx -= 64 if ($self->{delay} <= 1); $srcy = 64; } $srcrect = new SDL::Rect( -width => $srcw, -height => $srch, -x => $srcx, -y => $srcy ); $self->TransferRect(); $::GuySurface->blit($srcrect, $::App, $self->{rect}); } sub Kill { my ($self) = @_; return if $self->{invincible} > 0; $self->{killed} = 1; } package Harpoon; @Harpoon::ISA = qw(GameObject); sub new { my ($class, $guy) = @_; my ($self); $self = new GameObject; %{$self} = ( %{$self}, 'x' => $guy->{x} + 22, 'y' => $::ScreenHeight - 32, 'w' => 18, 'h' => 32, 'anim' => 0, 'speedY' => -5, 'speedX' => 0, 'guy' => $guy, ); bless $self, $class; } sub Delete { my $self = shift; --$self->{guy}->{harpoons}; $self->SUPER::Delete(); } sub Advance { my $self = shift; $self->{y} += $self->{speedY}; if ($self->{y} < 0) { $self->Delete(); return; } $self->{h} = $::ScreenHeight - $self->{y}; ++$self->{anim}; } sub Draw { my $self = shift; my ($x, $y, $h, $maxh, $dstrect, $srcrect); $self->TransferRect(); $y = $self->{y}; $dstrect = new SDL::Rect( -w => $self->{w}, -x => $self->{x}, -y => $y ); $srcrect = new SDL::Rect( -w => $self->{w}, -x => (40, 70, 102)[ int($self->{anim} / 4) % 3 ], -y => 0 ); $maxh = 64; # The harpoon needs to be drawn from tile pieces. # $y iterates from $self->{y} to $::ScreenHeight # We draw at most $maxh height tiles at a time. while ($y < $::ScreenHeight) { $h = $::ScreenHeight - $y; $h = $maxh if $h > $maxh; $dstrect->y( $y ); $dstrect->height( $h ); $srcrect->height( $h ); $::HarpoonSurface->blit( $srcrect, $::App, $dstrect ); # Prepare for next piece $y += $h; $srcrect->y( 32 ); # First piece starts at 0, rest start at 32 $maxh = 32; # First piece maxheight is 64, rest is 32 } } ########################################################################## # MAIN PROGRAM ########################################################################## package main; sub LoadSurfaces { my ($i); $BallSurface = new SDL::Surface( -name => "ball.png" ); $GuySurface = new SDL::Surface( -name => "guy.png" ); $HarpoonSurface = new SDL::Surface( -name => "harpoon.png" ); $PopSurface = new SDL::Surface( -name => "pang.png" ); return; my ($palette, $numColors, $color, $red, $green, $blue); $palette = $GuySurface->palette(); $numColors = SDL::PaletteNColors($palette); for ($i = 0; $i < $numColors; ++$i) { $color = SDL::PaletteColors($palette, $i); ($red, $green, $blue) = ( SDL::ColorR($color), SDL::ColorG($color), SDL::ColorB($color) ); if ($blue > $red and $blue > $green) { SDL::PaletteColors($palette, $i, $red, $blue, $green); } print STDERR join(' ', $i, SDL::ColorR($color), SDL::ColorG($color), SDL::ColorB($color) ), "\n"; } } sub LoadSounds { $Mixer = eval { SDL::Mixer->new(-frequency => 22050, -channels => 2, -size => 1024); }; if ($@) { warn $@; return 0; } my ($soundName, $fileName); while (($soundName, $fileName) = each %Sounds) { $Sounds{$soundName} = new SDL::Sound($fileName); } } sub PlaySound { my $sound = shift; $Mixer and $Sounds{$sound} and $Mixer->play_channel(-1, $Sounds{$sound}, 0); } sub AdvanceGame { foreach my $gameObject (@GameObjects) { $gameObject->Advance(); if (ref $gameObject eq 'Ball') { if (rand(100) < 1) { # $gameObject->Pop(); } } } } sub HandleEvents { my ($event, $type); $event = new SDL::Event; while (1) { last unless $event->poll(); $type = $event->type(); if ($type == SDL_QUIT) { exit; } elsif ($type == SDL_KEYDOWN) { my $keypressed = $event->key_sym; $Keys{$keypressed} = 1; $Events{$keypressed} = 1; if ($keypressed == SDLK_ESCAPE) { exit; } } elsif ($type == SDL_KEYUP) { my $keypressed = $event->key_sym; $Keys{$keypressed} = 0; } } } sub DrawGame { my ($gameObject); foreach $gameObject (@GameObjects) { $gameObject->Clear(); } foreach $gameObject (@GameObjects) { $gameObject->Draw(); } $App->sync(); } $App = new SDL::App -title => "UPiPang", -width => $ScreenWidth, -height => $ScreenHeight, -depth => 32; # -fullscreen => 1; &LoadSurfaces(); &LoadSounds(); my $guy = new Guy(0); push @GameObjects, ( $guy, new Ball($BallDesc[0], rand(600), 100, 0), new Ball($BallDesc[1], rand(600), 100, 1), new Ball($BallDesc[2], rand(600), 100, 0), new Ball($BallDesc[3], rand(600), 100, 1), new Ball($BallDesc[4], rand(600), 100, 0), ); my $event = new SDL::Event; my ($lastTick, $currentTick, $advance); $currentTick = $App->ticks; $lastTick = $currentTick; while (1) { $currentTick = $App->ticks; $advance = int( ($currentTick - $lastTick ) / 10 ); if ($advance > 10) { print STDERR "advance = $advance!\n"; $advance = 10; } if ($advance <= 0) { $App->delay(10); next; } %Events = (); &HandleEvents(); while ($advance--) { &AdvanceGame(); } $lastTick = $currentTick; if ($guy->{killed}) { &DrawGame(); &PlaySound('death'); $guy->{x} = 300; $guy->{invincible} = 100; $guy->{killed} = 0; $App->delay(1000); $lastTick = $App->ticks; } &DrawGame(); }