package CannonField;
use Qt 2.0;
use POSIX 'math_h'; # No atan() in Perl.
use Qt::signals 'hit()', 'missed()', 'angleChanged(int)', 'forceChanged(int)';
use Qt::slots 'setAngle(int)', 'setForce(int)', 'shoot()', 'newTarget()';
use Qt::slots 'setGameOver()', 'restartGame()';
@ISA = qw(Qt::Widget);
sub new {
my $self = shift->SUPER::new(@_);
@$self{'ang', 'f', 'shooting', 'timerCount', 'shoot_ang', 'shoot_f',
'gameEnded', 'barrelPressed', 'target'} =
(45, 0, 0, 0, 0, 0, 0, 0, Qt::Point->new(0, 0));
$self->newTarget();
return $self;
}
sub angle { return shift->{'ang'} }
sub force { return shift->{'f'} }
sub gameOver { return shift->{'gameEnded'} }
sub isShooting { return shift->{'shooting'} }
sub setAngle {
my $self = shift;
my $degrees = shift;
$degrees = 5 if $degrees < 5;
$degrees = 70 if $degrees > 70;
return if $self->{'ang'} == $degrees;
$self->{'ang'} = $degrees;
$self->repaint($self->cannonRect(), 0);
emit $self->angleChanged($self->{'ang'});
}
sub setForce {
my $self = shift;
my $newton = shift;
$newton = 0 if $newton < 0;
return if $self->{'f'} == $newton;
$self->{'f'} = $newton;
emit $self->forceChanged($self->{'f'});
}
sub shoot {
my $self = shift;
return if $self->{'shooting'};
@$self{'timerCount', 'shoot_ang', 'shoot_f', 'shooting'} =
(0, $self->{'ang'}, $self->{'f'}, 1);
$self->startTimer(50);
}
# You *ARE* using perl5.004 or above, right?
# srand(time ^ $$);
sub newTarget {
my $self = shift;
$self->erase($self->targetRect());
$self->{'target'} = Qt::Point->new(200 + rand(65536) % 190,
10 + rand(65536) % 255);
$self->repaint($self->targetRect());
}
sub setGameOver {
my $self = shift;
return if $self->{'gameEnded'};
$self->stopShooting() if $self->{'shooting'};
$self->{'gameEnded'} = 1;
$self->repaint();
}
sub restartGame {
my $self = shift;
$self->stopShooting() if $self->{'shooting'};
$self->{'gameEnded'} = 0;
$self->repaint();
}
sub timerEvent {
my $self = shift;
$self->erase($self->shotRect());
$self->{'timerCount'}++;
my $shotR = $self->shotRect();
if($shotR->intersects($self->targetRect())) {
$self->stopShooting();
emit $self->hit();
return;
}
if(($shotR->x() > $self->width() || $shotR->y() > $self->height()) ||
$shotR->intersects($self->barrierRect())) {
$self->stopShooting();
emit $self->missed();
return;
}
$self->repaint($shotR, 0);
}
sub paintEvent {
my $self = shift;
my $updateR = shift->rect();
my $p = Qt::Painter->new;
$p->begin($self);
$self->paintCannon($p) if $updateR->intersects($self->cannonRect());
$self->paintBarrier($p) if $updateR->intersects($self->barrierRect());
if($self->{'gameEnded'}) {
$p->setPen(Qt::black);
$p->setFont(Qt::Font->new('Courier', 48, Qt::Font::Bold));
$p->drawText($self->rect(), Qt::AlignCenter, 'Game Over');
} else {
$self->paintShot($p) if $self->isShooting() &&
$updateR->intersects($self->shotRect());
$self->paintTarget($p) if $updateR->intersects($self->targetRect());
}
$p->end();
}
sub mousePressEvent {
my $self = shift;
my $e = shift;
return if $e->button() != Qt::LeftButton;
$self->{'barrelPressed'} = 1 if $self->barrelHit($e->pos());
}
sub qRound {
my $d = shift;
return $d > 0.0 ? int($d+0.5) : int($d-0.5);
}
sub mouseMoveEvent {
my $self = shift;
my $e = shift;
return unless $self->{'barrelPressed'};
my $pnt = $e->pos();
$pnt->setX(1) if $pnt->x() <= 0;
$pnt->setY($self->height() - 1) if $pnt->y() >= $self->height();
my $rad = atan(($self->rect()->bottom() - $pnt->y()) / $pnt->x());
$self->setAngle(qRound($rad*180/3.14159265));
}
sub mouseReleaseEvent {
my $self = shift;
my $e = shift;
$self->{'barrelPressed'} = 0 if $e->button() == Qt::LeftButton;
}
sub stopShooting {
my $self = shift;
$self->{'shooting'} = 0;
$self->killTimers();
}
sub paintShot {
my $self = shift;
my $p = shift;
$p->setBrush(Qt::black);
$p->setPen(Qt::NoPen);
$p->drawRect($self->shotRect());
}
sub paintTarget {
my $self = shift;
my $p = shift;
$p->setBrush(Qt::red);
$p->setPen(Qt::black);
$p->drawRect($self->targetRect());
}
sub paintBarrier {
my $self = shift;
my $p = shift;
$p->setBrush(Qt::yellow);
$p->setPen(Qt::black);
$p->drawRect($self->barrierRect());
}
$barrel_rect = Qt::Rect->new(33, -4, 15, 8);
sub paintCannon {
my $self = shift;
my $p = shift;
my $cr = $self->cannonRect();
my $pix = Qt::Pixmap->new($cr->size());
my $tmp = Qt::Painter->new;
$pix->fill($self, $cr->topLeft());
$tmp->begin($pix);
$tmp->setBrush(Qt::blue);
$tmp->setPen(Qt::NoPen);
$tmp->translate(0, $pix->height() - 1);
$tmp->drawPie(Qt::Rect->new(-35, -35, 70, 70), 0, 90*16);
$tmp->rotate(-$self->{'ang'});
$tmp->drawRect($barrel_rect);
$tmp->end();
$p->drawPixmap($cr->topLeft(), $pix);
}
sub cannonRect {
my $self = shift;
my $r = Qt::Rect->new(0, 0, 50, 50);
$r->moveBottomLeft($self->rect()->bottomLeft());
return $r;
}
sub shotRect {
my $self = shift;
my $gravity = 4;
my $time = $self->{'timerCount'}/4.0;
my $velocity = $self->{'shoot_f'};
my $radians = $self->{'shoot_ang'}*3.14159265/180;
my $velx = $velocity*cos($radians);
my $vely = $velocity*sin($radians);
my $x0 = ($barrel_rect->right() + 5)*cos($radians);
my $y0 = ($barrel_rect->right() + 5)*sin($radians);
my $x = $x0 + $velx*$time;
my $y = $y0 + $vely*$time - 0.5*$gravity*$time*$time;
my $r = Qt::Rect->new(0, 0, 6, 6);
$r->moveCenter(Qt::Point->new(qRound($x), $self->height() - 1 - qRound($y)));
return $r;
}
sub targetRect {
my $self = shift;
my $target = $self->{'target'};
my $r = Qt::Rect->new(0, 0, 20, 10);
$r->moveCenter(Qt::Point->new($target->x(), $self->height() - 1 -
$target->y()));
return $r;
}
sub barrierRect {
my $self = shift;
return Qt::Rect->new(145, $self->height() - 100, 15, 100);
}
sub barrelHit {
my $self = shift;
my $p = shift;
my $mtx = Qt::WMatrix->new;
$mtx->translate(0, $self->height() - 1);
$mtx->rotate(-$self->{'ang'});
$mtx = $mtx->invert();
return $barrel_rect->contains($mtx->map($p));
}
sub sizeHint {
return Qt::Size->new(400, 300);
}
sub sizePolicy {
return Qt::SizePolicy->new(Qt::SizePolicy::Expanding,
Qt::SizePolicy::Expanding);
}