The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
#!/usr/bin/perl
use 5.10.0;
use strict;
use warnings;
use SDL;
use SDL::Time;
use SDL::Rect;
use SDL::Event;
use SDL::Events;
use SDLx::Sprite::Animated;
use SDLx::Surface;
use SDLx::Controller;

use File::Spec::Functions;
use FindBin;
use lib catfile($FindBin::Bin,'..','lib');
use Games::Zumbis;
use Games::Zumbis::Mapa;
use Games::Zumbis::Tiro;
use Games::Zumbis::Zumbi;
use Games::Zumbis::TelaGameOver;
use Games::Zumbis::Audio;

use Getopt::Long;
use Pod::Usage;

my $help       = 0;
my $fullscreen = 0;
GetOptions( 'fullscreen|fs=s' => \$fullscreen,
            'help|?'          => \$help,
          ) or pod2usage(1);
pod2usage(2) if $help;


my $mapa = Games::Zumbis::Mapa->new( arquivo => Games::Zumbis->sharedir->file('mapas/mapa-de-teste-1.xml') );
my $initial_ticks;
my $score = 0;

my $heroi = SDLx::Sprite::Animated->new(
    image => Games::Zumbis->sharedir->file('dados/heroi.png'),
    rect  => SDL::Rect->new(5,14,32,45),
    ticks_per_frame => 2,
);
my $heroina = SDLx::Sprite::Animated->new(
    image => Games::Zumbis->sharedir->file('dados/heroina.png'),
    rect  => SDL::Rect->new(5,14,31,45),
    ticks_per_frame => 2,
);

my $player = $heroina;

my $telagameover;

my $jogo;
my @zumbis;
my @morrendo;
my @tiros;

my $sequences = {
    stop_left  => [ [1, 3] ],
    stop_right => [ [1, 1] ],
    stop_up    => [ [1, 0] ],
    stop_down  => [ [1, 2] ],
    left       => [ [0,3], [1,3], [2,3] ],
    right      => [ [0,1], [1,1], [2,1] ],
    up         => [ [0,0], [1,0], [2,0] ],
    down       => [ [0,2], [1,2], [2,2] ],
};
$heroi->set_sequences(%$sequences);
$heroina->set_sequences(%$sequences);


my ( $player_x, $player_y ) = $mapa->playerstart_px;
#$player->x( $player_x );
#$player->y( $player_y );
my $player_vel = 0.45;
$player->sequence('stop_down');
$player->start;

my $flags = SDL_HWSURFACE | SDL_DOUBLEBUF;
   $flags = $flags | SDL_FULLSCREEN if $fullscreen;

my $tela = SDLx::Surface::display( 
    width => $mapa->width_px,
    height => $mapa->height_px,
    flags =>  $flags,
);

Games::Zumbis::Audio->init;
Games::Zumbis::Audio->start_music( Games::Zumbis->sharedir->file('dados/terrortrack.ogg'));

my %pressed;
sub eventos {
    my $e = shift;
    my $jogo = shift;
    $jogo->stop if $e->type == SDL_QUIT;
    $jogo->stop if $e->key_sym == SDLK_ESCAPE;

    if ( $e->type == SDL_KEYDOWN ) {
        my $tecla = $e->key_sym;
        if ($tecla == SDLK_LEFT) {
            $pressed{left} = 1;
        }
        elsif ($tecla == SDLK_RIGHT) {
            $pressed{right} = 1;
        }
        elsif ($tecla == SDLK_DOWN) {
            $pressed{down} = 1;
        }
        elsif($tecla == SDLK_UP) {
            $pressed{up} = 1;
        }
        elsif ($tecla == SDLK_SPACE && scalar @tiros < 4) {
            my $type = $player->sequence;
            $type =~ s/stop_//;
            push @tiros, Games::Zumbis::Tiro->new(x => $player_x, y => $player_y+20,
                                            type => $type);
        }
        if (%pressed) {
            $player->sequence((keys %pressed)[0]);
        }
    }
    elsif ( $e->type == SDL_KEYUP ) {
        my $tecla = $e->key_sym;
        if ($tecla == SDLK_f) {
            SDL::Video::wm_toggle_fullscreen( SDL::Video::get_video_surface() );
            $fullscreen = not $fullscreen;
        }
        if ($tecla == SDLK_LEFT) {
            delete $pressed{left};
            $player->sequence('stop_left')  unless %pressed;
        }
        elsif ($tecla == SDLK_RIGHT) {
            delete $pressed{right};
            $player->sequence('stop_right')  unless %pressed;;
        }
        elsif ($tecla == SDLK_DOWN) {
            delete $pressed{down};
            $player->sequence('stop_down')  unless %pressed;
        }
        elsif ($tecla == SDLK_UP) {
            delete $pressed{up};
            $player->sequence('stop_up') unless %pressed;
        }
        if (%pressed) {
            $player->sequence((keys %pressed)[0]);
        }
    }
    return 1;
}

my $last_zumbi_dt = 0;
sub cria_zumbis {
    my $dt = shift;
    $last_zumbi_dt += $dt;
    if ($last_zumbi_dt > 400 && scalar @zumbis < 7) {
        my ($x, $y) = $mapa->next_spawnpoint_px;
        push @zumbis, Games::Zumbis::Zumbi->new(x => $x, y => $y);
        $last_zumbi_dt = 0;
    }
}

sub move_heroi {
    my $dt = shift;
    my $tilesize = $mapa->dados->{tilesize};

    # verifica se o heroi foi tocado por um zumbi
    # (condicao de derrota)
    for my $z (@zumbis) {
        next if abs($player_x - $z->x) > 25;
        next if abs($player_y - $z->y) > 25;
        init_game_over();
    }

    @tiros = grep { $_->tick($dt, $mapa) } @tiros;

    @zumbis = grep { my $z = $_;
                     !grep {
                         my $t = $_;
                         ((!$t->collided &&
                           (($player->sequence =~ 'up'
                             || $player->sequence =~ 'down')?
                            (abs($t->{x} - 10 - $z->{x})<20 &&
                             abs($t->{y} - $z->{y})<20):
                            (abs($t->{x} - $z->{x})<20 &&
                             abs($t->{y} - 25 - $z->{y})<25)))?
                           ($z->sequence($z->sequence!~/morrendo/?'morrendo_'.$z->sequence:()),
                            push(@morrendo,$z),
                            $score++,
                            $t->collided(1)
                           ):0);
                     } @tiros
                 } @zumbis;

    my $sequencia = $player->sequence;
    my ($change_x, $change_y) = (0,0);
    $change_x = 0 - $player_vel * $dt if $sequencia eq 'left';
    $change_x = $player_vel * $dt if $sequencia eq 'right';
    $change_y = 0 - $player_vel * $dt if $sequencia eq 'up';
    $change_y = $player_vel * $dt if $sequencia eq 'down';

    my $start_tilex = ($player_x + $change_x) / $tilesize;
    my $start_tiley = ($player_y + $change_y) / $tilesize;
    my $end_tilex = ($player_x + 32 + $change_x) / $tilesize;
    my $end_tiley = ($player_y + 32 + $change_y) / $tilesize;
    if (!$mapa->colisao->[$start_tilex][$start_tiley] &&
        !$mapa->colisao->[$start_tilex][$end_tiley] &&
        !$mapa->colisao->[$end_tilex][$start_tiley] &&
        !$mapa->colisao->[$end_tilex][$end_tiley]) {
        $player_x += $change_x;
        $player_y += $change_y;
    }


}


sub move_zumbis { $_->tick($_[0], $mapa, $player_x, $player_y) for @zumbis }

sub exibicao {
    my $result = (SDL::get_ticks() - $initial_ticks )/1000;
    $mapa->render( $tela->surface, $result, $score );
    $_->render($tela->surface) for @morrendo;
    $_->render($tela->surface) for @tiros;
    $_->render($tela->surface) for @zumbis;
    $player->draw_xy( $tela->surface, $player_x, $player_y );
#    $tela->update;
    $tela->flip;
    return $result;
}

sub eventos_gameover {
    my $e = shift;
    $jogo->stop if $e->type    == SDL_QUIT;
    $jogo->stop if $e->key_sym == SDLK_ESCAPE;

    if ( $e->type == SDL_KEYDOWN ) {
        my $tecla = $e->key_sym;
        if ($tecla == SDLK_RETURN || $tecla == SDLK_1 || $tecla == SDLK_2) {

            if ($tecla == SDLK_1) {
                $player = $heroi;
            } elsif ($tecla == SDLK_2) {
                $player = $heroina;
            }

            @zumbis = ();
            @morrendo = ();
            @tiros = ();
            ( $player_x, $player_y ) = $mapa->playerstart_px;
            $player->sequence('stop_down');
            init_game();
        }
    }
    return 1;
}

sub render_gameover {
    $telagameover->render($tela);
    $tela->update;
}

sub init_game {
    $jogo->remove_all_handlers;
    $initial_ticks = SDL::get_ticks;
    $score = 0;
    $jogo->add_event_handler( \&eventos );
    $jogo->add_show_handler( \&exibicao );
    $jogo->add_move_handler( \&move_heroi );
    $jogo->add_move_handler( \&cria_zumbis );
    $jogo->add_move_handler( \&move_zumbis );
}

sub init_game_over {
    %pressed = ();
    $jogo->remove_all_handlers;
    my $result = exibicao();
    $telagameover = Games::Zumbis::TelaGameOver->new(surface => $tela,
                                              tempo => $result,
                                              score => $score );
    $tela->update();
    $jogo->add_event_handler( \&eventos_gameover );
    #$jogo->add_move_handler( \&animar_gameover );
    $jogo->add_show_handler( \&render_gameover );
}

$jogo = SDLx::Controller->new(dt => 0.003);
init_game();
$jogo->run;

# end of game, restore screen (if set to full)
SDL::Video::wm_toggle_fullscreen( SDL::Video::get_video_surface() ) if $fullscreen;

__END__

=head1 NAME

zumbis - a fast-paced zombie apocalypse arcade game

=head1 SYNOPSIS

zumbis [options]

Available options:

    --fullscreen, --fs          enable full screen mode

=head2 CONTROLS

Use the arrow keys to control the character, and the spacebar to shoot;

While in-game, you can press the 'f' key to toggle fullscreen mode, or
the [escape] key to exit the game;

Before restarting the game, press '1' to use a male character, or '2'
for the female character. Pressing the [enter] key will restart the
game with whatever character you've played before.

For more information, please type 'perldoc Games::Zumbis' on the command
line.

Thanks for playing! And watch out for those flesh eating things! :)