# SVG maze output # Performs transformation, cleanup, and printing of output of Games::Maze package Games::Maze::SVG; use Carp; use Games::Maze; use Games::Maze::SVG::Rect; use Games::Maze::SVG::RectHex; use Games::Maze::SVG::Hex; use strict; use warnings; =head1 NAME Games::Maze::SVG - Build mazes in SVG. =head1 VERSION Version 0.75 =cut our $VERSION = 0.75; =head1 SYNOPSIS Games::Maze::SVG uses the Games::Maze module to create mazes in SVG. use Games::Maze::SVG; my $foo = Games::Maze::SVG->new(); ... See Games::Maze::SVG::Manual for more information on using the module. =cut use constant SIGN_HEIGHT => 20; use constant SIDE_MARGIN => 10; use constant PANEL_WIDTH => 250; use constant PANEL_MIN_HEIGHT => 365; my %crumbstyles = ( dash => "stroke-width:1px; stroke-dasharray:5px,3px;", dot => "stroke-width:2px; stroke-dasharray:2px,6px;", line => "stroke-width:1px;", none => "visibility:hidden;", ); my $license = <<'EOL'; SVG Maze 2006 An SVG-based Game G. Wade Johnson G. Wade Johnson EOL =head1 FUNCTIONS =cut # ---------------------------------------------- # Subroutines =over 4 =item new Create a new Games::Maze::SVG object. Supports the following named parameters: Takes one positional parameter that is the maze type: Rect, RectHex, or Hex =over 4 =item wallform String naming the wall format. Legal values are bevel, round, roundcorners, and straight. Not all formats work with all maze shapes. =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. =item interactive This parameter determines if the maze will be interactive. If the value of the parameter is true (1), the appropriate scripting and support is written into the SVG. If the parameter is omitted or false, no interactive support is provided. =item cols The number of columns used in creating the maze. Default value is 12. =item rows The number of rows used in creating the maze. Default value is 12. =item startcol The column where the entry is found. Default value is random. =item endcol The column where the exit is found. Default value is random. =back =cut sub new { my $class = shift; my $shape = shift || 'Rect'; my %params = @_; if(exists $params{crumb} && !exists $crumbstyles{$params{crumb}}) { croak "Unrecognized breadcrumb style '$params{crumb}'.\n" } return Games::Maze::SVG::Rect->new( @_ ) if 'Rect' eq $shape; return Games::Maze::SVG::RectHex->new( @_ ) if 'RectHex' eq $shape; return Games::Maze::SVG::Hex->new( @_ ) if 'Hex' eq $shape; croak "Unrecognized maze shape '$shape'.\n"; } =item init_object Initializes the maze object with the default values for all mazes. The derived classes should call this method in their constructors. Returns the initial data members as a list. =cut sub init_object { my %parms = @_; my %obj = ( mazeparms => {}, wallform => 'straight', crumb => 'dash', dir => 'scripts/', ); $obj{mazeparms}->{dimensions} = [ $parms{cols}||12, $parms{rows}||12, 1 ]; $obj{mazeparms}->{entry} = [ $parms{startcol}, 1, 1 ] if $parms{startcol}; if($parms{endcol}) { $obj{mazeparms}->{exit} = [ $parms{endcol}, $obj{mazeparms}->{dimensions}->[1], 1 ]; } return %obj; } =item set_interactive Method makes the maze interactive. Returns a reference to self for chaining. =cut sub set_interactive { my $self = shift; $self->{interactive} = 1; return $self; } =item set_breadcrumb =over 4 =item $bcs String specifying the breadcrumb style. Generates an exception if the breadcrumb style is not recognized. =back Returns a reference to self for chaining. =cut sub set_breadcrumb { my $self = shift; my $bcs = shift; return unless defined $bcs; croak "Unrecognized breadcrumb style '$bcs'.\n" unless exists $crumbstyles{$bcs}; $self->{crumb} = $bcs; $self->{crumbstyle} = $crumbstyles{$bcs}; return $self; } =item get_crumbstyle Returns the CSS style for the breadcrumb. =cut sub get_crumbstyle { my $self = shift; return $self->{crumbstyle} ||= $crumbstyles{$self->{crumb}}; } =item get_script Method that returns the path to the interactivity script. =cut sub get_script { my $self = shift; return "$self->{dir}$self->{scriptname}"; } =item toString Method that converts the current maze into an SVG string. =cut sub toString { my $self = shift; my $maze = Games::Maze->new( %{$self->{mazeparms}} ); $maze->make(); my @rows = map { [ split q{}, $_ ] } split( "\n", $maze->to_ascii() ); my $crumb = q{}; my $color = { mazebg => '#ffc', panel => '#ccc', crumb => '#f3f', sprite => 'orange', button => '#ccf', }; my $crumbstyle = $self->get_crumbstyle(); $self->transform_grid( \@rows, $self->{wallform} ); $self->_just_maze( \@rows ); my ($xp, $yp) = $self->convert_start_position( @{$maze->{entry}} ); my ($xe, $ye) = $self->convert_end_position( @{$maze->{exit}} ); my ($xenter, $yenter) = $self->convert_sign_position( $xp, $yp ); my ($xexit, $yexit) = $self->convert_sign_position( $xe, $ye ); my $width = $self->{width} + 2 * SIDE_MARGIN; my $height = $self->{height} + 2 * SIGN_HEIGHT; my $sprite_def = $self->create_sprite(); my $output = qq{\n} ; my $offsety = - SIGN_HEIGHT; my $offsetx = - SIDE_MARGIN; my ($xme, $yme) = ($xp*$self->dx(), $yp*$self->dy()); my ($xcrumb, $ycrumb) = ($xme+$self->dx()/2, $yme+$self->dy()/2); my $panelheight = $height > PANEL_MIN_HEIGHT ? $height : PANEL_MIN_HEIGHT; if($self->{interactive}) { my $script = $self->build_all_script(); my $boardelem = $self->build_board_element( \@rows, $xp, $yp, $xe, $ye ); my $totalwidth = $width + PANEL_WIDTH; $output .= <<"EOH"; A Playable SVG Maze This maze was generated using the Games::Maze::SVG Perl module. $license $script $boardelem EOH } else { $color->{mazebg} = '#fff'; $output .= <<"EOH"; An SVG Maze This maze was generated using the Games::Maze::SVG Perl module. $license EOH } $output .= <<"EOH"; $sprite_def @{[$self->wall_definitions()]} $self->{mazeout} Entry Exit EOH if($self->{interactive}) { my ($cx,$cy) = (($self->{width}+PANEL_WIDTH)/2, (35+$panelheight/2)); $output .= $self->build_control_panel( 0, $panelheight ); $output .= <<"EOM"; Solved! EOM } return $output . "\n"; } =item make_board_array Build a two-dimensional array of integers that maps the board from the two dimensional matrix of wall descriptions. =cut sub make_board_array { my $self = shift; my $rows = shift; my @board = (); foreach my $row (@{$rows}) { push @board, [ map { $_ ? 1 : 0 } @{$row} ]; } return \@board; } =item get_script_list Returns a list of script URLs that will be needed by the interactive maze. =cut sub get_script_list { my $self = shift; my @scripts = ( "$self->{dir}point.es", "$self->{dir}sprite.es", "$self->{dir}maze.es", $self->get_script(), ); return @scripts; } =item build_all_script Generate the full set of script sections for the maze. =cut sub build_all_script { my $self = shift; my $script = q{}; foreach my $url ($self->get_script_list()) { $script .= qq{ \n}; } $script .= <<"EOS"; EOS return $script; } =item build_board_element Create the element that describes the board. =over 4 =item $rows reference to an array of rows. =back =cut sub build_board_element { my $self = shift; my $rows = shift; my ($xp, $yp, $xe, $ye) = @_; my $tilex = $self->dx(); my $tiley = $self->dy(); my $board = $self->make_board_array( $rows ); my $elem .= qq{ \n}; foreach my $row (@{$board}) { $elem .= qq{ } . join( q{}, @{$row} ) ."\n"; } $elem .= <<'EOS'; EOS return $elem; } =item build_control_panel Create the displayable control panel =over 4 =item $startx the starting x coordinate for the panel =back =cut sub build_control_panel { my $self = shift; my $startx = shift; my $height = shift; my $panelwidth = PANEL_WIDTH; my $offset = 20; my $output .= <<"EOB"; EOB $output .= _create_text_button( 'restart', $offset, 20, 50, 20, 'Begin' ); $output .= _create_text_button( 'save_position', $offset+60, 20, 50, 20, 'Save' ); $output .= _create_text_button( 'restore_position', $offset+120, 20, 50, 20, 'Back' ); $output .= <<"EOB"; Move View EOB $output .= _create_view_button( 'maze_up', 22, 0, '10,5 5,15 15,15' ); $output .= _create_view_button( 'maze_left', 0, 22, '5,10 15,5 15,15' ); $output .= _create_view_button( 'maze_right', 44, 22, '15,10 5,5 5,15' ); $output .= _create_view_button( 'maze_down', 22, 44, '10,15 5,5 15,5' ); $output .= _create_view_button( 'maze_reset', 22, 22, '7,7 7,13 13,13 13,7' ); =begin COMMENT $output .= <<"EOB"; EOB $output .= $self->_create_thumbnail(); =cut $output .= <<"EOB"; Click Begin button to start Use the arrow keys to move the sprite Hold the shift to move quickly. The mouse must remain over the maze for the keys to work. Use arrow buttons to shift the maze Center button centers view on sprite Save button saves current position Back button restores last position EOB return $output; } =begin COMMENT # _create_thumbnail # # Create the thumbnail image used to show the player where they are on the # larger field. # sub _create_thumbnail { my $self = shift; my ($x, $y, $wid, $ht) = (0,0,80,80); if($self->{width} > $self->{height}) { $ht = int(80 * $self->{height} / $self->{width}); $y = (80 - $ht) / 2; } else { $wid = int(80 * $self->{width} / $self->{height}); $x = (80 - $wid) / 2; } qq{ \n}; } =cut # _create_text_button # # $function - function name to call # $x - x-coordinate of the button # $y - y-coordinate of the button # $width - width of the button # $height - height of the button # $text - text to be displayed on the button sub _create_text_button { my $fn = shift; my $x = shift; my $y = shift; my $width = shift; my $height = shift; my $text = shift; my $tx = $width/2; my $ty = $height/2 + 5; my $output = <<"EOF"; $text EOF return $output; } # _create_view_button # # $function - function name to call # $x - x-coordinate of the button # $y - y-coordinate of the button # $icon - list of points for the icon sub _create_view_button { my $fn = shift; my $x = shift; my $y = shift; my $icon = shift; my $output = <<"EOF"; EOF return $output; } =item create_sprite Create the sprite definition for inclusion in the SVG. =cut sub create_sprite { my $self = shift; my ($dx2, $dy2) = ($self->dx()/2, $self->dy()/2); return qq| | . qq||; } # # Generates just the maze portion of the SVG. # # $dx - The size of the tiles in the X direction. # $dy - The size of the tiles in the Y direction. # $rows - Reference to an array of row data. # # returns a string containing the SVG for the maze description. sub _just_maze { my $self = shift; my $dx = $self->dx(); my $dy = $self->dy(); my $rows = shift; my $output = q{}; my ($maxx,$y) = (0,0); foreach my $r (@{$rows}) { my $x = 0; foreach my $c (@{$r}) { $output .= qq{ \n} if $c and $c ne q{$}; $x += $dx; } $y += $dy; $maxx = $x if $maxx < $x; } $self->{width} = $maxx; $self->{height} = $y; $self->{mazeout} = $output; return $self; } =item dx Returns the delta X value for building this maze. =cut sub dx { my $self = shift; return $self->{dx}; } =item dy Returns the delta Y value for building this maze. =cut sub dy { my $self = shift; return $self->{dy}; } =back =head1 AUTHOR G. Wade Johnson, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. 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;