#!/usr/bin/perl # # aliens.pl - game using Async::Tiny - version 2 # change difficulty by changing size of window use Curses; use Term::ReadKey; use Async::Tiny; use Time::HiRes 'time'; use strict; use warnings;; my @ship = split /\n/, < @ . END my ($width, $height) = GetTerminalSize; my @aliens; # {row, col, state} my $missilecount = 0; my $alienwidth = 5; my $shootercol = int $width / 2; my $base = $height - 4; my $shoottime = 0; my $aliencount = 0; my $change = 1; my $t = Async::Tiny->new; # draw an explosion sub drawexplosion { my ($row, $col, $alien) = @_; $t->addDelayCallback( 3, sub # to clear explosion { my ($row, $col) = @_; addstr $row++, $col, tr// /cr for @explosion; $alien->{state} = 'dead'; $change++; }, $row, $col); addstr $row++, $col, $_ for @explosion; $change++; } # do misc stuff and refresh $t->addWaitCallback( sub { $change or return; addstr $base + 3, 2, scalar localtime; addstr $base + 2, 2, "active missiles: $missilecount "; move $base - 1, $shootercol; refresh; $change = 0; }); sub left { $shootercol > 0 and --$shootercol, ++$change } sub right { $shootercol < $width - 1 and ++$shootercol, ++$change } # fire missile sub firemissile { $shoottime + 1 > time and return; $missilecount++; $t->addRepeatCallback( 0.15, \&movemissile, [ $base - 1, $shootercol ] ); $shoottime = time; $change++; } # callback to move one missile sub movemissile { my ($pos) = @_; my ($row, $col) = @$pos; addstr $row--, $col, ' '; $row >= 0 and addstr $row, $col, '|'; $pos->[0] = $row; $change++; $row < 0 || hit($row, $col) and $missilecount--, return 'endrepeat'; } # test for missile hit on ship sub hit { my ($row, $col) = @_; for my $alien (@aliens) { $alien->{state} eq 'live' or next; my ($ar, $ac) = ($alien->{row}, $alien->{col}); $row < $ar || $row > $ar + 2 || $col < $ac || $col > $ac + 4 and next; $alien->{state} = 'explosion'; drawexplosion( $ar, $ac, $alien ); --$aliencount or $t->addDelayCallback(2, sub { die "you win\n" }); $change++; return 1; } return 0; } # process arrow keys + 'q' $t->addReadCallback( *STDIN, sub { my ($string) = @_; for ($string =~ /./gs ) # arrows or vim's hjkl { /A|j|k/ ? firemissile() : # up arrow /D|h/ ? left() : # left arrow /C|l/ ? right() : # right arrow /q/ && die "quit\n"; } }); $t->changeReadMode( *STDIN, 'character' ); # create ships across top of screen for (my $col = 3; $col < $width - $alienwidth - 3; $col += $alienwidth + 3) { push @aliens, my $alien = { row => 0, col => $col, state => 'live' }; $aliencount++; $t->addRepeatCallback( 1 + rand 1, sub # move an alien { $alien->{state} eq 'live' or return 'endrepeat'; ++$alien->{row} + 4 >= $base and die "you lose\n"; my $arow = $alien->{row}; addstr $arow++, $alien->{col}, $_ for @ship; $change++; } ); } # temporary notice $t->addDelayCallback( 0, sub { addstr int $height / 2, 2, "WARNING: Alien Ships coming out of warp !" } ); $t->addDelayCallback( 5, sub { move int $height / 2, 2; clrtoeol } ); # start up Curses and the eventloop initscr; clear; ReadMode 'cbreak'; addstr $base + 1, 2, "use left and right arrows to move gun, up arrow to fire"; addstr $base, 0, '=' x $width; eval { $t->eventloop }; my $errormsg = $@; ReadMode 'restore'; endwin; print "\n$errormsg\n";