#  SVG maze output
#  Performs transformation, cleanup, and printing of output of Games::Maze

package Games::Maze::SVG::Rect;

use base Games::Maze::SVG;

use Carp;
use Games::Maze;
use strict;
use warnings;

=head1 NAME

Games::Maze::SVG::Rect - Build rectangular mazes in SVG.

=head1 VERSION

Version 0.80

=cut

our $VERSION = 0.80;

=head1 SYNOPSIS

Games::Maze::SVG::Rect uses the Games::Maze module to create mazes in SVG.

    use Games::Maze::SVG;

    my $foo = Games::Maze::SVG->new( 'Rect' );
    ...

=cut

use constant DELTA_X => 10;
use constant DELTA_Y => 10;

# ----------------
#  Shape transformation tables
my %Blocks = (
    ': - |' => 'ul',
    ':-  |' => 'ur',
    ': -| ' => 'll',
    ':- | ' => 'lr',
    ':--  ' => 'h',
    '-::  ' => 'h',
    ':  ||' => 'v',
    '|  ::' => 'v',
    ':-   ' => 'l',
    ': -  ' => 'r',
    ':  | ' => 't',
    ':   |' => 'd',
    ': -||' => 'tr',
    ':- ||' => 'tl',
    ':--| ' => 'tu',
    ':-- |' => 'td',
    ':--||' => 'cross',
    ':.- |' => 'oul',
    ':- .|' => 'our',
    ': -.|' => 'oul',
    ':-. |' => 'our',
    ':.-.|' => 'oul',
    ':-..|' => 'our',
    ':.-| ' => 'oll',
    ':-.| ' => 'olr',
    ': -|.' => 'oll',
    ':- |.' => 'olr',
    ':.-|.' => 'oll',
    ':-.|.' => 'olr',
    ':--. ' => 'oh',
    '-::. ' => 'oh',
    ':-- .' => 'oh',
    '-:: .' => 'oh',
    ':. ||' => 'ov',
    '|. ::' => 'ov',
    ': .||' => 'ov',
    '| .::' => 'ov',
    ':- . ' => 'ol',
    ': -. ' => 'or',
    ':-.  ' => 'ol',
    ':.-  ' => 'or',
    ':-  .' => 'ol',
    ': - .' => 'or',
    ':. | ' => 'ot',
    ':.  |' => 'od',
    ': .| ' => 'ot',
    ': . |' => 'od',
    ':  |.' => 'ot',
    ':  .|' => 'od',
    ':. |.' => 'ot',
    ':. .|' => 'od',
    ': .|.' => 'ot',
    ': ..|' => 'od',
    ':.-||' => 'otr',
    ':-.||' => 'otl',
    ':--|.' => 'otu',
    ':--.|' => 'otd',
);

my %Walls = _get_wall_forms();

=head1 FUNCTIONS

=cut

# ----------------------------------------------
#  Subroutines

=over 4

=item new

Create a new Games::Maze::SVG::Rect object. Supports the following named
parameters:

=over 4

=item wallform

String naming the wall format. Legal values are bevel, round, roundcorners,
and straight.

=item crumb

String describing the breadcrumb design. Legal values are dash,
dot, line, and none

=item dir

Directory in which to find the ecmascript for the maze interactivity. Should
either be relative, or in URL form.

=back

=cut

sub new
{
    my $class = shift;

    my $obj = { Games::Maze::SVG::init_object( @_ ), @_, };

    if( !exists $Walls{ $obj->{wallform} } )
    {
        my $forms = join( ", ", sort keys %Walls );
        croak "\n'$obj->{wallform}' is not a valid wall form.\nTry one of: $forms\n\n";
    }

    $obj->{mazeparms}->{cell} = 'Quad';
    $obj->{mazeparms}->{form} = 'Rectangle';
    $obj->{scriptname}        = "rectmaze.es";
    $obj->{dx}                = DELTA_X;
    $obj->{dy}                = DELTA_Y;

    return bless $obj, $class;
}

=item is_hex

Method always returns false.

=cut

sub is_hex
{
    return;
}

=item is_hex_shaped

Method always returns false.

=cut

sub is_hex_shaped
{
    return;
}

=item set_wall_form

Set the wall format for the current maze.

=over 4

=item $form

String specifying a wall format.

=back

Returns a reference to self for chaining.

=cut

sub set_wall_form
{
    my $self = shift;
    my $form = shift;

    if( exists $Walls{$form} )
    {
        $self->{wallform} = $form;
    }
    else
    {
        my $forms = join( ", ", sort keys %Walls );
        croak "\n'$form' is not a valid wall form.\nTry one of: $forms\n\n";
    }
    return $self;
}

=item transform_grid

Convert the rectangular grid from ascii format to SVG definition
   references.

=over 4

=item $rows

Reference to an array of rows

=item $walls

String specifying wall format.

=back

=cut

sub transform_grid
{
    my $self  = shift;
    my $rows  = shift;
    my $walls = shift;
    my @out   = ();

    my $sp = 'bevel' eq ( $walls || q{} ) ? q{.} : q{ };
    remove_horiz_padding( $rows );

    # transform the printout into block commands
    my $height = @{$rows};
    my $width  = @{ $rows->[0] };
    for ( my $r = 0; $r < $height; ++$r )
    {
        for ( my $c = 0; $c < $width; ++$c )
        {
            if( $rows->[$r]->[$c] eq q{ } )
            {
                $out[$r]->[$c] = 0;
            }
            else
            {

                # convert the cell and its neighbors into a signature
                my $sig = $rows->[$r]->[$c]    # cell
                    . ( $c == 0 ? $sp : $rows->[$r]->[ $c - 1 ] )    # left neighbor
                    . ( $rows->[$r]->[ $c + 1 ] || $sp )             # right neighbor
                    . ( $r == 0 ? $sp : $rows->[ $r - 1 ]->[$c] )    # up neighbor
                    . ( $rows->[ $r + 1 ] ? $rows->[ $r + 1 ]->[$c] : $sp );    # down neighbor
                      # convert the signature into the block name
                croak "Missing block for '$sig'.\n" unless exists $Blocks{$sig};
                $out[$r]->[$c] = $Blocks{$sig};
            }
        }
    }

    return @{$rows} = @out;
}

=item remove_horiz_padding

Remove the extra horizontal space inserted to regularize the look
 of the rectangular maze

=over 4

=item $rows

Reference to an array of rows

=back

=cut

sub remove_horiz_padding
{
    my $rows = shift;

    for ( my $i = $#{ $rows->[0] }; $i > 0; $i -= 3 )
    {
        splice( @{$_}, $i - 1, 1 ) foreach ( @{$rows} );
    }

    # apparently trailing spaces that I wasn't aware of.
    foreach my $r ( @{$rows} )
    {
        pop @{$r} if $r->[-1] eq q{ };
    }

    return;
}

=item wall_definitions

Method that returns the definition for the shapes used to build the walls.

=cut

sub wall_definitions
{
    my $self = shift;

    return $Walls{ $self->{wallform} };
}

# _get_wall_forms
#
#Extract the wall forms from the DATA file handle.
#
#Returns a hash of wall forms.

sub _get_wall_forms
{
    local $/ = "\n===\n";
    chomp( my @list = <DATA> );

    $/ = "\n";
    chomp( @list );

    return @list;
}

=item convert_start_position

Convert the supplied x and y coordinates into the appropriate real coordinates
for a start position on this map.

=over 4

=item $x x coord from the maze

=item $y y coord from the maze

=back

returns a two element list containing (x, y).

=cut

sub convert_start_position
{
    my $self = shift;
    my ( $x, $y ) = @_;

    $x = 2 * ( $x - 1 ) + 1;
    $y = 2 * ( $y - 1 );

    return ( $x, $y );
}

=item convert_end_position

Convert the supplied x and y coordinates into the appropriate real coordinates
for a end position on this map.

=over 4

=item $x x coord from the maze

=item $y y coord from the maze

=back

returns a two element list containing (x, y).

=cut

sub convert_end_position
{
    my $self = shift;
    my ( $x, $y ) = @_;

    $x = 2 * ( $x - 1 ) + 1;
    $y = 2 * ( $y - 1 ) + 2;

    return ( $x, $y );
}

=item convert_sign_position

Convert the supplied x and y coordinates into the appropriate real coordinates
for a the position of the exit sign.

=over 4

=item $x x coord from the maze

=item $y y coord from the maze

=back

returns a two element list containing (x, y).

=cut

sub convert_sign_position
{
    my $self = shift;
    my ( $x, $y ) = @_;

    $x *= $self->dx();
    $y *= $self->dy();

    $x += 0.5 * $self->dx();

    # adjust bottom
    if( $y > $self->{height} / 2 )
    {
        $y += 2 * $self->dy();
    }
    else
    {
        $y -= $self->dy();
    }

    return ( $x, $y );
}

=back

=head1 AUTHOR

G. Wade Johnson, C<< <wade@anomaly.org> >>

=head1 BUGS

Please report any bugs or feature requests to
C<bug-game-maze-svg@rt.cpan.org>, or through the web interface at
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Game-Maze-SVG>.
I will be notified, and then you'll automatically be notified of progress on
your bug as I make changes.

=head1 ACKNOWLEDGEMENTS

Thanks go to Valen Johnson and Jason Wood for extensive test play of the
mazes.

=head1 COPYRIGHT & LICENSE

Copyright 2004-2006 G. Wade Johnson, all rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=cut

1;

__DATA__
straight
===
      <path id="ul" d="M5,10 v-5 h5"/>
      <path id="ur" d="M0,5  h5  v5"/>
      <path id="ll" d="M5,0  v5  h5"/>
      <path id="lr" d="M0,5  h5  v-5"/>
      <path id="h"  d="M0,5  h10"/>
      <path id="v"  d="M5,0  v10"/>
      <path id="l"  d="M0,5  h5"/>
      <path id="r"  d="M5,5  h5"/>
      <path id="t"  d="M5,0  v5"/>
      <path id="d"  d="M5,5  v5"/>
      <path id="tr" d="M5,0  v10 M5,5 h5"/>
      <path id="tl" d="M5,0  v10 M0,5 h5"/>
      <path id="tu" d="M0,5  h10 M5,0 v5"/>
      <path id="td" d="M0,5  h10 M5,5 v5"/>
      <path id="cross" d="M0,5 h10 M5,0 v10"/>
===
roundcorners
===
      <path id="ul" d="M5,10 Q5,5 10,5"/>
      <path id="ur" d="M0,5  Q5,5 5,10"/>
      <path id="ll" d="M5,0  Q5,5 10,5"/>
      <path id="lr" d="M0,5  Q5,5 5,0"/>
      <path id="h"  d="M0,5  h10"/>
      <path id="v"  d="M5,0  v10"/>
      <path id="l"  d="M0,5  h5"/>
      <path id="r"  d="M5,5  h5"/>
      <path id="t"  d="M5,0  v5"/>
      <path id="d"  d="M5,5  v5"/>
      <path id="tr" d="M5,0  v10 M5,5 h5"/>
      <path id="tl" d="M5,0  v10 M0,5 h5"/>
      <path id="tu" d="M0,5  h10 M5,0 v5"/>
      <path id="td" d="M0,5  h10 M5,5 v5"/>
      <path id="cross" d="M0,5 h10 M5,0 v10"/>
===
round
===
      <path id="ul" d="M5,10 Q5,5 10,5"/>
      <path id="ur" d="M0,5  Q5,5 5,10"/>
      <path id="ll" d="M5,0  Q5,5 10,5"/>
      <path id="lr" d="M0,5  Q5,5 5,0"/>
      <path id="h"  d="M0,5  h10"/>
      <path id="v"  d="M5,0  v10"/>
      <path id="l"  d="M0,5  h5"/>
      <path id="r"  d="M5,5  h5"/>
      <path id="t"  d="M5,0  v5"/>
      <path id="d"  d="M5,5  v5"/>
      <path id="tr" d="M5,0  Q5,5 10,5 Q5,5 5,10"/>
      <path id="tl" d="M5,0  Q5,5 0,5  Q5,5 5,10"/>
      <path id="tu" d="M0,5  Q5,5 5,0  Q5,5 10,5"/>
      <path id="td" d="M0,5  Q5,5 5,10 Q5,5 10,5"/>
      <path id="cross"
                    d="M0,5 Q5,5 5,0  Q5,5 10,5 Q5,5 5,10 Q5,5 0,5"/>
===
bevel
===
      <path id="ul" d="M5,10.1 v-.1 l5,-5 h.1"/>
      <path id="ur" d="M-.1,5 h.1 l5,5 v.1"/>
      <path id="ll" d="M5,-.1 v.1 l5,5 h.1"/>
      <path id="lr" d="M-.1,5 h.1 l5,-5 v-.1"/>
      <path id="h"  d="M0,5  h10"/>
      <path id="v"  d="M5,0  v10"/>
      <path id="l"  d="M0,5  h5"/>
      <path id="r"  d="M5,5  h5"/>
      <path id="t"  d="M5,0  v5"/>
      <path id="d"  d="M5,5  v5"/>
      <polygon id="tr" points="5,0 5,10 10,5"/>
      <polygon id="tl" points="5,0 5,10 0,5"/>
      <polygon id="tu" points="0,5 10,5 5,0"/>
      <polygon id="td" points="0,5 10,5 5,10"/>
      <polygon id="cross" points="0,5 5,10 10,5 5,0"/>
      <path id="oul" d="M5,10.1 v-.1 l5,-5 h.1"/>
      <path id="our" d="M-.1,5 h.1 l5,5 v.1"/>
      <path id="oll" d="M5,-.1 v.1 l5,5 h.1"/>
      <path id="olr" d="M-.1,5 h.1 l5,-5 v-.1"/>
      <path id="oh"  d="M0,5  h10"/>
      <path id="ov"  d="M5,0  v10"/>
      <path id="ol"  d="M0,5  h5"/>
      <path id="or"  d="M5,5  h5"/>
      <path id="ot"  d="M5,0  v5"/>
      <path id="od"  d="M5,5  v5"/>
      <path id="otr" d="M5,0 l5,5 l-5,5"/>
      <path id="otl" d="M5,0 l-5,5 l5,5"/>
      <path id="otu" d="M0,5 l5,-5 l5,5"/>
      <path id="otd" d="M0,5 l5,5 l5,-5"/>
