#!/usr/bin/env perl
#
#############################################################################
# Asciiquarium - An aquarium animation in ASCII art
#
# This program displays an aquarium/sea animation using ASCII art.
# It requires the module Term::Animation, which requires Curses. You
# can get both modules from http://search.cpan.org. Asciiquarium will
# only run on platforms with a curses library, so Windows is not supported.
#
# The current version of this program is available at:
#
# http://robobunny.com/projects/asciiquarium
#
#############################################################################
# Author:
#   Kirk Baucom <kbaucom@schizoid.com>
#
# Contributors:
#   Joan Stark: http://www.geocities.com/SoHo/7373/
#     most of the ASCII art
#
# License:
#
# Copyright (C) 2013 Kirk Baucom (kbaucom@schizoid.com)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#############################################################################

use Term::Animation 2.0;
use Term::Animation::Entity;
use Data::Dumper;
use Curses;
use strict;
use warnings;

my $version = "1.1";

my @random_objects = init_random_objects();

# the Z depth at which certain items occur
my %depth = (
	# no gui yet
	guiText		=> 0,
	gui		=> 1,

	# under water
	shark		=> 2,
	fish_start	=> 3,
	fish_end	=> 20,
	seaweed		=> 21, 
	castle		=> 22,

	# waterline
	water_line3	=> 2,
	water_gap3	=> 3,
	water_line2	=> 4,
	water_gap2	=> 5,
	water_line1	=> 6,
	water_gap1	=> 7,
	water_line0	=> 8,
	water_gap0	=> 9,
);

main();

####################### MAIN #######################

sub main {

	my $anim = Term::Animation->new();

	# set the wait time for getch
	halfdelay(1);
	#nodelay(1);

	$anim->color(1);

	my $start_time = time;
	my $paused = 0;
	while(1) {

		add_environment($anim);
		add_castle($anim);
		add_all_seaweed($anim);
		add_all_fish($anim);
		random_object(undef, $anim);
		
		$anim->redraw_screen();

		my $nexttime = 0;

		while(1) {
			my $in = getch();

			if   ( $in eq 'q' ) { quit(); }   # Exit
			elsif( $in eq 'r' || $in eq KEY_RESIZE()) { last; }     # Redraw (will recreate all objects)
			elsif( $in eq 'p' ) { $paused = !$paused; }

			$anim->animate() unless($paused);
		}
		$anim->update_term_size();
		$anim->remove_all_entities();

	}

}

sub add_environment {
	my ($anim) = @_;

	my @water_line_segment = (
		q{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~},
		q{^^^^ ^^^  ^^^   ^^^    ^^^^      },
		q{^^^^      ^^^^     ^^^    ^^     },
		q{^^      ^^^^      ^^^    ^^^^^^  }
	);

	# tile the segments so they stretch across the screen
	my $segment_size = length($water_line_segment[0]);
	my $segment_repeat = int($anim->width()/$segment_size) + 1;
	foreach my $i (0..$#water_line_segment) {
		$water_line_segment[$i] = $water_line_segment[$i]x$segment_repeat;
	}

	foreach my $i (0..$#water_line_segment) {
		$anim->new_entity(
			name		=> "water_seg_$i",
			type		=> "waterline",
			shape		=> $water_line_segment[$i],
			position	=> [ 0, $i+5, $depth{'water_line'  . $i} ],
			default_color	=> 'cyan',
			depth		=> 22,
			physical	=> 1,
		);
	}
}

sub add_castle {
	my ($anim) = @_;
	my $castle_image = q{
               T~~
               |
              /^\
             /   \
 _   _   _  /     \  _   _   _
[ ]_[ ]_[ ]/ _   _ \[ ]_[ ]_[ ]
|_=__-_ =_|_[ ]_[ ]_|_=-___-__|
 | _- =  | =_ = _    |= _=   |
 |= -[]  |- = _ =    |_-=_[] |
 | =_    |= - ___    | =_ =  |
 |=  []- |-  /| |\   |=_ =[] |
 |- =_   | =| | | |  |- = -  |
 |_______|__|_|_|_|__|_______|
};

	my $castle_mask = q{
                RR

              yyy
             y   y
            y     y
           y       y



              yyy
             yy yy
            y y y y
            yyyyyyy
};

	$anim->new_entity(
		name		=> "castle",
		shape		=> $castle_image,
		color		=> $castle_mask,
		position	=> [ $anim->width()-32, $anim->height()-13, $depth{'castle'} ],
		default_color	=> 'BLACK',
	);
}

sub add_all_seaweed {
	my ($anim) = @_;
	# figure out how many seaweed to add by the width of the screen
	my $seaweed_count = int($anim->width() / 15);
	for (1..$seaweed_count) {
		add_seaweed(undef, $anim);
	}
}

sub add_seaweed {
	my ($old_seaweed, $anim) = @_;
	my @seaweed_image = ('','');
	my $height = int(rand(4)) + 3;
	for my $i (1..$height) {
		my $left_side = $i%2;
		my $right_side = !$left_side;
		$seaweed_image[$left_side] .= "(\n";
		$seaweed_image[$right_side] .= " )\n";
	}
	my $x = int(rand($anim->width()-2)) + 1;
	my $y = $anim->height() - $height;
	my $anim_speed = rand(.05) + .25;
	$anim->new_entity(
		name		=> 'seaweed' . rand(1),
		shape		=> \@seaweed_image,
		position	=> [ $x, $y, $depth{'seaweed'} ],
		callback_args	=> [ 0, 0, 0, $anim_speed ],
		die_time	=> time() + int(rand(4*60)) + (8*60), # seaweed lives for 8 to 12 minutes
		death_cb	=> \&add_seaweed,
		default_color	=> 'green',
	);
}

# add an air bubble to a fish
sub add_bubble {
	my ($fish, $anim) = @_;

	my $cb_args = $fish->callback_args();
	my @fish_size = $fish->size();
	my @fish_pos = $fish->position();
	my @bubble_pos = @fish_pos;

	# moving right
	if($cb_args->[0] > 0) {
		$bubble_pos[0] += $fish_size[0];
	}
	$bubble_pos[1] += int($fish_size[1] / 2);
	# bubble always goes on top of the fish
	$bubble_pos[2]--;

	$anim->new_entity(
		shape		=> [ '.', 'o', 'O', 'O', 'O' ],
		type		=> 'bubble',
		position	=> \@bubble_pos,
		callback_args	=> [ 0, -1, 0, .1 ],
		die_offscreen	=> 1,
		physical	=> 1,
		coll_handler	=> \&bubble_collision, 
		default_color	=> 'CYAN',
	);
}

sub bubble_collision {
	my ($bubble, $anim) = @_;
	my $collisions = $bubble->collisions();
	foreach my $col_obj (@{$collisions}) {
		if($col_obj->type eq 'waterline') {
			$bubble->kill();
			last;
		}
	}

}

sub add_all_fish {
	my ($anim) = @_;
	# figure out how many fish to add by the size of the screen,
	# minus the stuff above the water
	my $screen_size = ($anim->height() - 9) * $anim->width();
	my $fish_count = int($screen_size / 350);
	for (1..$fish_count) {
		add_fish(undef, $anim);
	}
}


sub add_fish {
	my ($old_fish, $anim) = @_;
	my @fish_image = (

q{
       \
     ...\..,
\  /'       \
 >=     (  ' >
/  \      / /
    `"'"'/''
},
q{
       2
     1112111
6  11       1
 66     7  4 5
6  1      3 1
    11111311
},
q{
      /
  ,../...
 /       '\  /
< '  )     =<
 \ \      /  \
  `'\'"'"'
},
q{
      2
  1112111
 1       11  6
5 4  7     66
 1 3      1  6
  11311111
},
q{
    \
\ /--\
>=  (o>
/ \__/
    /
},
q{
    2
6 1111
66  745
6 1111
    3
},
q{
  /
 /--\ /
<o)  =<
 \__/ \
  \
},
q{
  2
 1111 6
547  66
 1111 6
  3
},
q{
       \:.
\;,   ,;\\\\\,,
  \\\\\;;:::::::o
  ///;;::::::::<
 /;` ``/////``
},
q{
       222
666   1122211
  6661111111114
  66611111111115
 666 113333311
},
q{
      .:/
   ,,///;,   ,;/
 o:::::::;;///
>::::::::;;\\\\\
  ''\\\\\\\\\'' ';\
},
q{
      222
   1122211   666
 4111111111666
51111111111666
  113333311 666
},
q{
  __
><_'>
   '
},
q{
  11
61145
   3
},
q{
 __
<'_><
 `
},
q{
 11
54116
 3
},
q{
   ..\,
>='   ('>
  '''/''
},
q{
   1121
661   745
  111311
},
q{
  ,/..
<')   `=<
 ``\```
},
q{
  1211
547   166
 113111
},
q{
   \
  / \
>=_('>
  \_/
   /
},
q{
   2
  1 1
661745
  111
   3
},
q{
  /
 / \
<')_=<
 \_/
  \
},
q{
  2
 1 1
547166
 111
  3
},
q{
  ,\
>=('>
  '/
},
q{
  12
66745
  13
},
q{
 /,
<')=<
 \`
},
q{
 21
54766
 31
},
q{
  __
\/ o\
/\__/
},
q{
  11
61 41
61111
},
q{
 __
/o \/
\__/\
},
q{
 11
14 16
11116
},
);
	
	# 1: body
	# 2: dorsal fin
	# 3: flippers
	# 4: eye
	# 5: mouth
	# 6: tailfin
	# 7: gills

	my @colors = ('c','C','r','R','y','Y','b','B','g','G','m','M');
	my $fish_num = int(rand($#fish_image/2));
	my $fish_index = $fish_num * 2;
	my $speed = rand(2) + .25;
	my $depth = int(rand($depth{'fish_end'} - $depth{'fish_start'})) + $depth{'fish_start'};
	my $color_mask = $fish_image[$fish_index+1];
	$color_mask =~ s/4/W/gm;
	$color_mask = rand_color($color_mask);

	if($fish_num % 2) {
		$speed *= -1;
	}
	my $fish_object = Term::Animation::Entity->new(
		type		=> 'fish',
		shape		=> $fish_image[$fish_index],
		auto_trans	=> 1,
		color		=> $color_mask,
		position	=> [ 0, 0, $depth ],
		callback	=> \&fish_callback,
		callback_args	=> [ $speed, 0, 0 ],
		die_offscreen	=> 1,
		death_cb	=> \&add_fish,
		physical	=> 1,
		coll_handler	=> \&fish_collision,
	);

	my $max_height = 9;
	my $min_height = $anim->height() - $fish_object->{'HEIGHT'};
	$fish_object->{'Y'} = int(rand($min_height - $max_height)) + $max_height;
	if($fish_num % 2) {
		$fish_object->{'X'} = $anim->width()-2;
	} else {
		$fish_object->{'X'} = 1 - $fish_object->{'WIDTH'};
	}
	$anim->add_entity($fish_object);
}

sub fish_callback {
	my ($fish, $anim) = @_;
	if(int(rand(100)) > 97) {
		add_bubble($fish, $anim);
	}
	return $fish->move_entity($anim);
}

sub fish_collision {
	my ($fish, $anim) = @_;
	my $collisions = $fish->collisions();
	foreach my $col_obj (@{$collisions}) {
		if($col_obj->type eq 'teeth') {
			add_splat($anim, $col_obj->position());
			$fish->kill();
			last;
		} elsif($col_obj->type eq 'hook_point') {
			retract($col_obj);
			retract($fish);
			# get the hook and line
			my $hook = $anim->get_entities_of_type('fishhook')->[0];
			my $line = $anim->get_entities_of_type('fishline')->[0];
			retract($anim->entity($hook));
			retract($anim->entity($line));
			last;
		}
	}
}

sub add_splat {
	my ($anim, $x, $y, $z) = @_;
	my @splat_image = (
q#

   .
  ***
   '

#,
q#

 ",*;`
 "*,**
 *"'~'

#,
q#
  , ,
 " ","'
 *" *'"
  " ; .

#,
q#
* ' , ' `
' ` * . '
 ' `' ",'
* ' " * .
" * ', '
#,
);

	$anim->new_entity(
		shape		=> \@splat_image,
		position	=> [ $x - 4, $y - 2, $z-2 ],
		default_color	=> 'RED',
		callback_args	=> [ 0, 0, 0, .25 ],
		transparent	=> ' ',
		die_frame	=> 15,
	);
}

sub add_shark {
	my ($old_ent, $anim) = @_;
	my @shark_image = (
q#
                              __
                             ( `\
  ,??????????????????????????)   `\
;' `.????????????????????????(     `\__
 ;   `.?????????????__..---''          `~~~~-._
  `.   `.____...--''                       (b  `--._
    >                     _.-'      .((      ._     )
  .`.-`--...__         .-'     -.___.....-(|/|/|/|/'
 ;.'?????????`. ...----`.___.',,,_______......---'
 '???????????'-'
#,
q#
                     __
                    /' )
                  /'   (??????????????????????????,
              __/'     )????????????????????????.' `;
      _.-~~~~'          ``---..__?????????????.'   ;
 _.--'  b)                       ``--...____.'   .'
(     _.      )).      `-._                     <
 `\|\|\|\|)-.....___.-     `-.         __...--'-.'.
   `---......_______,,,`.___.'----... .'?????????`.;
                                     `-`???????????`
#,
  );


	my @shark_mask = (
q#





                                           cR
 
                                          cWWWWWWWW


#,
q#





        Rc

  WWWWWWWWc


#,
  );

	my $dir = int(rand(2));
	my $x = -53;
	my $y = int(rand($anim->height() - (10 + 9))) + 9;
	my $teeth_x = -9;
	my $teeth_y = $y + 7;
	my $speed = 2;
	if($dir) {
		$speed *= -1;
		$x = $anim->width()-2;
		$teeth_x = $x + 9;
	}

	$anim->new_entity(
		type		=> 'teeth',
		shape		=> "*",
		position	=> [ $teeth_x, $teeth_y, $depth{'shark'}+1 ],
		depth		=> $depth{'fish_end'} - $depth{'fish_start'},
		callback_args	=> [ $speed, 0, 0 ],
		physical	=> 1,
	);

	$anim->new_entity(
		type		=> "shark",
		color		=> $shark_mask[$dir],
		shape		=> $shark_image[$dir],
		auto_trans	=> 1,
		position	=> [ $x, $y, $depth{'shark'} ],
		default_color	=> 'WHITE',
		callback_args	=> [ $speed, 0, 0 ],
		die_offscreen	=> 1,
		death_cb	=> sub { group_death(@_, 'teeth') },
		default_color	=> 'CYAN',
	);

}

# when a shark dies, kill the "teeth" too, the associated
# entity that does the actual collision
sub group_death {
	my ($entity, $anim, @bound_types) = @_;
	foreach my $type (@bound_types) {
		my $bound_entities = $anim->get_entities_of_type($type);
		foreach my $obj (@{$bound_entities}) {
			$anim->del_entity($obj);
		}
	}
	random_object($entity, $anim);
}

# pull the fishhook, line and whatever got caught back
# to the surface
sub retract {
	my ($entity) = @_;
	$entity->physical(0);
	if($entity->type eq 'fish') {
		my @pos = $entity->position();
		$pos[2] = $depth{'water_gap2'};
		$entity->position( @pos );
		$entity->callback( \&fishhook_cb );
	} else {
		$entity->callback_args( 'hooked' );
	}
}

# move the fishhook
sub fishhook_cb {
	my ($entity, $anim) = @_;

	my @pos = $entity->position;
	
	# this means we hooked something, reel it in
	if(defined($entity->callback_args())) {
		$pos[1]--;
	
	# otherwise, just lower until we reach 1/4 from the bottom
	} else {
		if( ( $pos[1] + $entity->height) < $anim->height * .75) {
			$pos[1]++;
		}
	}
	
	return @pos;
}

sub add_fishhook {
	my ($old_ent, $anim) = @_;

	my $hook_image = 
q{
       o
      ||
      ||
/ \   ||
  \__//
  `--' 
};

	my $point_image =
q{
.
 
\
 
};
	my $line_image = "|\n"x50 . " \n"x6;

	my $x = 10 + ( int(rand($anim->width() - 20)) );
	my $y = -4;
	my $point_x = $x + 1;
	my $point_y = $y + 2;

	$anim->new_entity(
		type		=> 'fishline',
		shape		=> $line_image,
		position	=> [ $x + 7, $y - 50, $depth{'water_line1'} ],
		auto_trans	=> 1,
		callback_args	=> undef,
		callback	=> \&fishhook_cb,
	);

	$anim->new_entity(
		type		=> 'fishhook',
		shape		=> $hook_image,
		trans_char	=> ' ',
		position	=> [ $x, $y, $depth{'water_line1'} ],
		auto_trans	=> 1,
		die_offscreen	=> 1,
		death_cb	=> sub { group_death(@_, 'teeth', 'fishline') },
		default_color	=> 'GREEN',
		callback_args	=> undef,
		callback	=> \&fishhook_cb,
	);

	$anim->new_entity(
		type		=> 'hook_point',
		shape		=> $point_image,
		position	=> [ $point_x, $point_y, $depth{'shark'}+1 ],
		depth		=> $depth{'fish_end'} - $depth{'fish_start'},
		callback_args	=> undef,
		physical	=> 1,
		default_color	=> 'GREEN',
		callback	=> \&fishhook_cb,
		
	);
}

sub add_ship {
	my ($old_ent, $anim) = @_;

	my @ship_image = (	
q{
     |    |    |
    )_)  )_)  )_)
   )___))___))___)\
  )____)____)_____)\\\
_____|____|____|____\\\\\__
\                   /
},
q{
         |    |    |
        (_(  (_(  (_(
      /(___((___((___(
    //(_____(____(____(
__///____|____|____|_____
    \                   /
});

	my @ship_mask = (
q{
     y    y    y

                  w
                   ww
yyyyyyyyyyyyyyyyyyyywwwyy
y                   y
},
q{
         y    y    y

      w
    ww
yywwwyyyyyyyyyyyyyyyyyyyy
    y                   y
});

	my $dir = int(rand(2));
	my $x = -24;
	my $speed = 1;
	if($dir) {
		$speed *= -1;
		$x = $anim->width()-2;
	}

	$anim->new_entity(
		color		=> $ship_mask[$dir],
		shape		=> $ship_image[$dir],
		auto_trans	=> 1,
		position	=> [ $x, 0, $depth{'water_gap1'} ],
		default_color	=> 'WHITE',
		callback_args	=> [ $speed, 0, 0 ],
		die_offscreen	=> 1,
		death_cb	=> \&random_object,
	);
}

sub add_whale {
	my ($old_ent, $anim) = @_;
	my @whale_image = (
q{
        .-----:
      .'       `.
,????/       (o) \
\`._/          ,__)
},
q{
    :-----.
  .'       `.
 / (o)       \????,
(__,          \_.'/
});
	my @whale_mask = (
q{
             C C
           CCCCCCC
           C  C  C
        BBBBBBB
      BB       BB
B    B       BWB B
BBBBB          BBBB
},
q{
   C C
 CCCCCCC
 C  C  C
    BBBBBBB
  BB       BB
 B BWB       B    B
BBBB          BBBBB
}
);

	my @water_spout = (
q{


   :
},q{

   :
   :
},q{
  . .
  -:-
   :
},q{
  . .
 .-:-.
   :
},q{
  . .
'.-:-.`
'  :  '
},q{

 .- -.
;  :  ;
},q{


;     ;
});


	my $dir = int(rand(2));
	my $x;
	my $speed = 1;
	my $spout_align;
	my @whale_anim;
	my @whale_anim_mask;

	if($dir) {
		$spout_align = 1;
		$speed *= -1;
		$x = $anim->width()-2;
	} else {
		$spout_align = 11;
		$x = -18;
	}
	
	# no water spout
	for (1..5) {
		push(@whale_anim, "\n\n\n" . $whale_image[$dir]);
		push(@whale_anim_mask, $whale_mask[$dir]);
	}

	# animate water spout
	foreach my $spout_frame (@water_spout) {
		my $whale_frame = $whale_image[$dir];
		my $aligned_spout_frame;
		$aligned_spout_frame = join("\n" . ' 'x$spout_align, split("\n", $spout_frame));
		$whale_frame = $aligned_spout_frame . $whale_image[$dir];
		push(@whale_anim, $whale_frame);
		push(@whale_anim_mask, $whale_mask[$dir]);
	}

	$anim->new_entity(
		color		=> \@whale_anim_mask,
		shape		=> \@whale_anim,
		auto_trans	=> 1,
		position	=> [ $x, 0, $depth{'water_gap2'} ],
		default_color	=> 'WHITE',
		callback_args	=> [ $speed, 0, 0, 1 ],
		die_offscreen	=> 1,
		death_cb	=> \&random_object,
	);

}

sub add_monster {
	my ($old_ent, $anim) = @_;
	my @monster_image = (
		[
q{
                                                          ____
            __??????????????????????????????????????????/   o  \
          /    \????????_?????????????????????_???????/     ____ >
  _??????|  __  |?????/   \????????_????????/   \????|     |
 | \?????|  ||  |????|     |?????/   \?????|     |???|     |
},q{
                                                          ____
                                             __?????????/   o  \
             _?????????????????????_???????/    \?????/     ____ >
   _???????/   \????????_????????/   \????|  __  |???|     |
  | \?????|     |?????/   \?????|     |???|  ||  |???|     |
},q{
                                                          ____
                                  __????????????????????/   o  \
 _??????????????????????_???????/    \????????_???????/     ____ >
| \??????????_????????/   \????|  __  |?????/   \????|     |
 \ \???????/   \?????|     |???|  ||  |????|     |???|     |
},q{
                                                          ____
                       __???????????????????????????????/   o  \
  _??????????_???????/    \????????_??????????????????/     ____ >
 | \???????/   \????|  __  |?????/   \????????_??????|     |
  \ \?????|     |???|  ||  |????|     |?????/   \????|     |
}
	],[
q{
    ____
  /  o   \??????????????????????????????????????????__
< ____     \???????_?????????????????????_????????/    \
      |     |????/   \????????_????????/   \?????|  __  |??????_
      |     |???|     |?????/   \?????|     |????|  ||  |?????/ |
},q{
    ____
  /  o   \?????????__
< ____     \?????/    \???????_?????????????????????_
      |     |???|  __  |????/   \????????_????????/   \???????_
      |     |???|  ||  |???|     |?????/   \?????|     |?????/ |
},q{
    ____
  /  o   \????????????????????__
< ____     \???????_????????/    \???????_??????????????????????_
      |     |????/   \?????|  __  |????/   \????????_??????????/ |
      |     |???|     |????|  ||  |???|     |?????/   \???????/ /
},q{
    ____
  /  o   \???????????????????????????????__
< ____     \??????????????????_????????/    \???????_??????????_
      |     |??????_????????/   \?????|  __  |????/   \???????/ |
      |     |????/   \?????|     |????|  ||  |???|     |?????/ /
}
	]);

	my @monster_mask = (
q{

                                                            W



},q{

     W



});
	my $dir = int(rand(2));
	my $x;
	my $speed = 2;
	if($dir) {
		$speed *= -1;
		$x = $anim->width()-2;
	} else {
		$x = -64
	}
	my @monster_anim_mask;
	for(1..4) { push(@monster_anim_mask, $monster_mask[$dir]); }

	$anim->new_entity(
		shape		=> $monster_image[$dir],
		auto_trans	=> 1,
		color		=> \@monster_anim_mask,
		position	=> [ $x, 2, $depth{'water_gap2'} ],
		callback_args	=> [ $speed, 0, 0, .25 ],
		death_cb	=> \&random_object,
		die_offscreen	=> 1,
		default_color	=> 'GREEN',
	);
}

sub add_big_fish {
	my ($old_ent, $anim) = @_;

	my @big_fish_image = (
q{
 ______
`""-.  `````-----.....__
     `.  .      .       `-.
       :     .     .       `.
 ,     :   .    .          _ :
: `.   :                  (@) `._
 `. `..'     .     =`-.       .__)
   ;     .        =  ~  :     .-"
 .' .'`.   .    .  =.-'  `._ .'
: .'   :               .   .'
 '   .'  .    .     .   .-'
   .'____....----''.'=.'
   ""             .'.'
               ''"'`
},q{
                           ______
          __.....-----'''''  .-""'
       .-'       .      .  .'
     .'       .     .     :
    : _          .    .   :     ,
 _.' (@)                  :   .' :
(__.       .-'=     .     `..' .'
 "-.     :  ~  =        .     ;
   `. _.'  `-.=  .    .   .'`. `.
     `.   .               :   `. :
       `-.   .     .    .  `.   `
          `.=`.``----....____`.
            `.`.             ""
              '`"``
});

	my @big_fish_mask = (
q{
 111111
11111  11111111111111111
     11  2      2       111
       1     2     2       11
 1     1   2    2          1 1
1 11   1                  1W1 111
 11 1111     2     1111       1111
   1     2        1  1  1     111
 11 1111   2    2  1111  111 11
1 11   1               2   11
 1   11  2    2     2   111
   111111111111111111111
   11             1111
               11111
},q{
                           111111
          11111111111111111  11111
       111       2      2  11
     11       2     2     1
    1 1          2    2   1     1
 111 1W1                  1   11 1
1111       1111     2     1111 11
 111     1  1  1        2     1
   11 111  1111  2    2   1111 11
     11   2               1   11 1
       111   2     2    2  11   1
          111111111111111111111
            1111             11
              11111
});


	my $dir = int(rand(2));
	my $x;
	my $speed = 3;
	if($dir) {
		$x = $anim->width()-1;
		$speed *= -1;
	} else {
		$x = -34;
	}
	my $max_height = 9;
	my $min_height = $anim->height() - 15;
	my $y = int(rand($min_height - $max_height)) + $max_height;
	my $color_mask = rand_color($big_fish_mask[$dir]);
	$anim->new_entity(
		shape		=> $big_fish_image[$dir],
		auto_trans	=> 1,
		color		=> $color_mask,
		position	=> [ $x, $y, $depth{'shark'} ],
		callback_args	=> [ $speed, 0, 0 ],
		death_cb	=> \&random_object,
		die_offscreen	=> 1,
		default_color	=> 'YELLOW',
	);

}

sub add_ducks {
	my ($old_ent, $anim) = @_;
	my @duck_image = (
	[
q{
      _??????????_??????????_  
,____(')=??,____(')=??,____(')<
 \~~= ')????\~~= ')????\~~= ') 
},q{
      _??????????_??????????_  
,____(')=??,____(')<??,____(')=
 \~~= ')????\~~= ')????\~~= ') 
},q{
      _??????????_??????????_  
,____(')<??,____(')=??,____(')=
 \~~= ')????\~~= ')????\~~= ') 
}
	],[
q{
  _??????????_??????????_
>(')____,??=(')____,??=(')____,
 (` =~~/????(` =~~/????(` =~~/
},q{
  _??????????_??????????_
=(')____,??>(')____,??=(')____,
 (` =~~/????(` =~~/????(` =~~/
},q{
  _??????????_??????????_
=(')____,??=(')____,??>(')____,
 (` =~~/????(` =~~/????(` =~~/
}
	]
	);
	
	my @duck_mask = (
q{
      g          g          g
wwwwwgcgy  wwwwwgcgy  wwwwwgcgy
 wwww Ww    wwww Ww    wwww Ww
},q{
  g          g          g
ygcgwwwww  ygcgwwwww  ygcgwwwww
 wW wwww    wW wwww    wW wwww
});

	my $dir = int(rand(2));
	my $x;
	my $speed = 1;
	if($dir) {
		$speed *= -1;
		$x = $anim->width()-2;
	} else {
		$x = -30
	}
	
	$anim->new_entity(
		shape		=> $duck_image[$dir],
		auto_trans	=> 1,
		color		=> $duck_mask[$dir],
		position	=> [ $x, 5, $depth{'water_gap3'} ],
		callback_args	=> [ $speed, 0, 0, .25 ],
		death_cb	=> \&random_object,
		die_offscreen	=> 1,
		default_color	=> 'WHITE',
	);
}

sub add_dolphins {
	my ($old_ent, $anim) = @_;
	my @dolphin_image = (
	[
q{
        ,
      __)\_
(\_.-'    a`-.
(/~~````(/~^^`
},q{
        ,
(\__  __)\_
(/~.''    a`-.
    ````\)~^^`
}
	],[
q{
     ,
   _/(__
.-'a    `-._/)
'^^~\)''''~~\)
},q{
     ,
   _/(__  __/)
.-'a    ``.~\)
'^^~(/''''
}
	]
	);
	
	
	my @dolphin_mask = (
q{


          W
},q{


   W
});


	my $dir = int(rand(2));
	
	my $x;
	my $speed = 1;
	my $distance = 15; # how far apart the dolphins are
	
	# right to left
	if($dir) {
		$speed *= -1;
		$distance *= -1;
		$x = $anim->width()-2;
		
	# left to right
	} else {
		$x = -13
	}
	
	my $up = [$speed,-.5,0,.5];
	my $down = [$speed,.5,0,.5];
	my $glide = [$speed,0,0,.5];
	
	my @path;
	
	for(1..14) { push(@path, $up); }
	for(1..2) { push(@path, $glide); }
	for(1..14) { push(@path, $down); }
	for(1..6) { push(@path, $glide); }
	
	my $dolphin3 = $anim->new_entity(
		shape		=> $dolphin_image[$dir],
		auto_trans	=> 1,
		color		=> $dolphin_mask[$dir],
		position	=> [ $x - ($distance * 2), 8, $depth{'water_gap3'} ],
		callback_args	=> [ 0, [@path] ],
		death_cb	=> \&random_object,
		die_offscreen	=> 0,
		default_color	=> 'blue',
	);
	
	my $dolphin2 = $anim->new_entity(
		shape		=> $dolphin_image[$dir],
		auto_trans	=> 1,
		color		=> $dolphin_mask[$dir],
		position	=> [ $x - $distance, 2, $depth{'water_gap3'} ],
		callback_args	=> [ 12, [@path] ],
		die_offscreen	=> 0,
		default_color	=> 'BLUE',
	);
	
	my $dolphin1 = $anim->new_entity(
		shape		=> $dolphin_image[$dir],
		auto_trans	=> 1,
		color		=> $dolphin_mask[$dir],
		position	=> [ $x, 5, $depth{'water_gap3'} ],
		callback_args	=> [ 24, [@path] ],
		# have the lead dolphin tell the others to die offscreen, since they start offscreen
		death_cb	=> sub{ $dolphin2->die_offscreen(1); $dolphin3->die_offscreen(1) },
		die_offscreen	=> 1,
		default_color	=> 'CYAN',
	);
	
}

sub add_swan {
	my ($old_ent, $anim) = @_;
	my @swan_image = (
	[
q{
       ___
,_    / _,\
| \   \( \|
|  \_  \\\
(_   \_) \
(\_   `   \
 \   -=~  /
}
	],[
q{
 ___
/,_ \    _,
|/ )/   / |
  //  _/  |
 / ( /   _)
/   `   _/)
\  ~=-   /
}
	]
	);
	
	my @swan_mask = (
q{

         g
         yy
},q{

 g
yy
});

	my $dir = int(rand(2));
	my $x;
	my $speed = 1;
	if($dir) {
		$speed *= -1;
		$x = $anim->width()-2;
	} else {
		$x = -10
	}
	
	$anim->new_entity(
		shape		=> $swan_image[$dir],
		auto_trans	=> 1,
		color		=> $swan_mask[$dir],
		position	=> [ $x, 1, $depth{'water_gap3'} ],
		callback_args	=> [ $speed, 0, 0, .25 ],
		death_cb	=> \&random_object,
		die_offscreen	=> 1,
		default_color	=> 'WHITE',
	);
}

sub init_random_objects {
	return (
		\&add_ship,
		\&add_whale,
		\&add_monster,
		\&add_big_fish,
		\&add_shark,
		\&add_fishhook,
		\&add_swan,
		\&add_ducks,
		\&add_dolphins,
	);
}

# add one of the random objects to the screen
sub random_object {
	my ($dead_object, $anim) = @_;
	my $sub = int(rand(scalar(@random_objects)));
	$random_objects[$sub]->($dead_object, $anim);
}

sub dprint {
	open(D, ">>", "debug");
	print D @_, "\n";
	close(D);
}

sub sighandler {
	my ($sig) = @_;
	if($sig eq 'INT') { quit(); }
	elsif($sig eq 'WINCH') {
		# ignore SIGWINCH, only redraw when requested
	}
	else { quit("Exiting with SIG$sig"); }
}

sub quit {
	my ($mesg) = @_;
	print STDERR $mesg, "\n" if(defined($mesg));
	exit;
}


sub initialize {
	# this may be paranoid, but i don't want to leave
	# the user's terminal in a state that they might not
	# know how to fix if we die badly
	foreach my $sig (keys %SIG) {
		$SIG{$sig} = 'sighandler' unless(defined($SIG{$sig}));
	}
}


sub center {
	my ($width, $mesg) = @_;
	my $l = length($mesg);
	if($l < $width) {
		return ' 'x(int(($width - length($mesg))/2)) . $mesg;
	}
	elsif($l > $width) {
		return(substr($mesg, 0, ($width - ($l + 3))) . "...");
	}
	else {
		return $mesg;
	}
}

sub rand_color {
	my ($color_mask) = @_;
	my @colors = ('c','C','r','R','y','Y','b','B','g','G','m','M');
	foreach my $i (1..9) {
		my $color = $colors[int(rand($#colors))];
		$color_mask =~ s/$i/$color/gm;
	}
	return $color_mask;
}
