=head1 NAME

Game::Object::Worm - Worm

=head1 SYNOPSIS

Nah...

=cut





package Game::Object::Worm;
use base qw( Game::Object Game::Direction );







use strict;


use Data::Dumper;
use Game::Location;
use Game::Direction;
use Game::Event::Timed;






=head1 STUFF

=over 4

=item ExceptionCouldNotMoveForward

Thrown when the Worm could not moveForward().

    oObject - $self


=cut
use Exception::Class (
    'Exception',

    'ExceptionCouldNotMoveForward' => {
        isa => 'Exception',
        fields => [ 'oObject', ],
    },
);


=pod

=back

=cut



=head1 PROPERTIES

=head2 isRealPlayer

Whether this is a real life player or not.

Default: 1

=cut
use Class::MethodMaker get_set => [ "isRealPlayer" ];





=head2 oDirection($direction)

Game::Direction object.

=cut
use Class::MethodMaker get_set => [ "oDirection" ];





=head2 $self->oEventMove

Timed event for movement.

Default: interval 0.2

=cut
use Class::MethodMaker get_set => [ "oEventMove" ];





=head2 $self->score( ... args ... );

The accumulated score, accumulated by e.g. getting Prizes.

Default: 0

=cut
use Class::MethodMaker get_set => [ "score" ];





=head2 lengthIdeal

The ideal length for the Worm. It will prolong itself to
this size when moving. It will not shrink.

=cut
use Class::MethodMaker get_set => [ "lengthIdeal" ];





=head2 lengthActual

The actual length of the Worm.

Readonly.

=cut
sub lengthActual {
    my $self = shift;
    return( scalar( @{$self->raBodyLocation} ) );
}





=head1 METHODS

=head2 new([$left = 11], [$top = 12], [$direction = "left"], [$length = 3)

Create new Worm, facing in $direction ("left", "right",
"up", "down" (only left supported right now)), with a body a
total size of $length.

=cut
sub new {
    my $pkg = shift;
    my ($left, $top, $direction, $length) = @_;
    defined($left) or $left = 11;
    defined($top) or $top = 12;
    $direction ||= "left";
    defined($length) or $length = 3;
    
    ###demo
    my $self = $pkg->SUPER::new( Game::Location->new($left, $top) );
    $self->oEventMove( Game::Event::Timed->new() )->timeInterval(0.07);
    $self->oDirection(Game::Direction->new($direction));
    $self->score(0);
    $self->lengthIdeal($length);
    $self->isRealPlayer(1);
    
    #Build worm body
    my $char = "O";
    if ($direction eq "left") {
        $self->buildBodyRight($length, $self->oLocation, sub { my $ret = $char; $char = "o"; $ret; });
    } else {
        die;
    }
    
    return($self);
}





=head2 moveForward()

Move forward one step in the oDirection.

Return 1 on success, else 0.

=cut
sub moveForward {
    my $self = shift;
    
    #Precalculate move
    my $oLocationOld = $self->oLocation;
    my $oLocationNew = $self->oValidLocationAfterMove($self->oLocation, $self->oDirection) or return(0);
    
    ##Do move
    $self->oLocation($oLocationNew);
    
    #Remove at tail, if not too short
    my $oLocationRemoved = undef;
    if (! ( $self->lengthActual < $self->lengthIdeal )) {
        $oLocationRemoved = pop(@{$self->raBodyLocation});
    }
    
    #Add at head, always (that's the move forward)
    unshift( @{$self->raBodyLocation}, $oLocationNew );
    
    #Tell the Lawn
    if ($self->oLawn) {
        $self->oLawn->objectHasMoved($self) or return(0);
        $self->oLawn->placeObjectBodyPartAt($self, $oLocationNew, $self->raBodyChar->[0]);
        $self->oLawn->placeObjectBodyPartAt($self, $oLocationOld, $self->raBodyChar->[1]) if(scalar($self->raBodyChar) > 1);
        $oLocationRemoved and $self->oLawn->removeObjectBodyPartAt($self, $oLocationRemoved);
    }
    
    return(1);
}





=head2 oValidLocationAfterMove()

Check that a movement in the $oDirection from $oLocation is
a valid one.

Return new Game::Location (with the new location) object on
success, else undef if the move wasn't valid.

=cut
sub oValidLocationAfterMove {
    my $self = shift;
    
    my $oLocationNew = $self->oDirection->oMove($self->oLocation, 1);
    
    #Check if it's valid
    if (my $oLawn = $self->oLawn) {
        $oLawn->isLocationValidForMove($self, $oLocationNew) or return(undef);
    }

    return($oLocationNew);
}




=head1 OTHER METHODS

=over 4

=item turn($direction)

Turn in $direction.
    left
    right

Return new direction on success, else undef.

=cut
sub turn {
    my $self = shift;
    my ($direction) = @_;

    eval { $self->oDirection->turn($direction); };
    return(undef) if($@);

    return($self->oDirection->direction);
}





=item grow([$sizeIncrease = 1])

Grow with $sizeIncrease body parts over time, i.e. increase the
lengthIdeal.

Return the new lengthIdeal() on success, or undef on errors.

=cut
sub grow {
    my $self = shift;
    my ($sizeIncrease) = @_;
    $sizeIncrease ||= 1;

    return( $self->lengthIdeal( $self->lengthIdeal + $sizeIncrease ) );
}





=back

=head2 Other other methods

=over 4

=item crash()

Crash the Worm. This doesn't mean anything in particular.

Notify the Lawn.

Return 1 on success, else 0.

=cut
sub crash {
    my $self = shift;

    $self->oLawn and ($self->oLawn->wormHasCrashed($self) or return(0));

    return(1);
}





=item checkTick($timeWorld)

Check if a tick is due. If it is, move forward.

Return 1 if a tick was due, else 0. Die if the worm can't
move.

=cut
sub checkTick {
    my $self = shift;
    my ($timeWorld) = @_;

    if ($self->oEventMove()->checkTick($timeWorld)) {
        ###demo
        if (!$self->moveForward()) {            
            ExceptionCouldNotMoveForward->throw(
                oObject => $self,
                error => "Could not move forward",
            );            
        }
            
        return(1);
    }

    return(0);
}





=item awardScorePoints($points)

Add $points to the score().

Return 1 on success, else 0.

=cut
sub awardScorePoints {
    my $self = shift;
    my ($points) = @_;

    $self->score( $self->score + $points );

    return(1);
}




=pod

=back

=cut





1;





#EOF
