File Coverage

blib/lib/Games/Maze/SVG.pm
Criterion Covered Total %
statement 85 199 42.7
branch 18 28 64.2
condition 10 15 66.6
subroutine 22 30 73.3
pod 16 16 100.0
total 151 288 52.4


line stmt bran cond sub pod time code
1             # SVG maze output
2             # Performs transformation, cleanup, and printing of output of Games::Maze
3              
4             package Games::Maze::SVG;
5              
6 28     28   1215538 use Carp;
  28         75  
  28         2843  
7 28     28   79647 use Games::Maze;
  28         551610  
  28         1248  
8 28     28   26222 use Games::Maze::SVG::Rect;
  28         100  
  28         1726  
9 28     28   52542 use Games::Maze::SVG::RectHex;
  28         91  
  28         1017  
10 28     28   32687 use Games::Maze::SVG::Hex;
  28         85  
  28         1156  
11              
12 28     28   174 use strict;
  28         55  
  28         910  
13 28     28   148 use warnings;
  28         50  
  28         1289  
14              
15             =head1 NAME
16              
17             Games::Maze::SVG - Build mazes in SVG.
18              
19             =head1 VERSION
20              
21             Version 0.80
22              
23             =cut
24              
25             our $VERSION = 0.80;
26              
27             =head1 SYNOPSIS
28              
29             Games::Maze::SVG uses the Games::Maze module to create mazes in SVG.
30              
31             use Games::Maze::SVG;
32              
33             my $foo = Games::Maze::SVG->new();
34             ...
35              
36             See Games::Maze::SVG::Manual for more information on using the module.
37              
38             =cut
39              
40 28     28   146 use constant SIGN_HEIGHT => 20;
  28         47  
  28         2354  
41 28     28   145 use constant SIDE_MARGIN => 10;
  28         51  
  28         2118  
42 28     28   147 use constant PANEL_WIDTH => 250;
  28         50  
  28         1641  
43 28     28   147 use constant PANEL_MIN_HEIGHT => 365;
  28         57  
  28         159537  
44              
45             my %crumbstyles = (
46             dash => "stroke-width:1px; stroke-dasharray:5px,3px;",
47             dot => "stroke-width:2px; stroke-dasharray:2px,6px;",
48             line => "stroke-width:1px;",
49             none => "visibility:hidden;",
50             );
51              
52             my $license = <<'EOL';
53            
54            
58            
59             xmlns:dc="http://purl.org/dc/elements/1.1/"
60             xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
61            
62             SVG Maze
63             2006
64             An SVG-based Game
65            
66             G. Wade Johnson
67            
68            
69             G. Wade Johnson
70            
71            
72            
73            
74              
75            
76            
77            
78            
79            
80            
81            
82            
83              
84            
85            
86             EOL
87              
88             =head1 FUNCTIONS
89              
90              
91             =cut
92              
93             # ----------------------------------------------
94             # Subroutines
95              
96             =head2 new( $type, %parms )
97              
98             Create a new Games::Maze::SVG object. Supports the following named parameters:
99              
100             Takes one positional parameter that is the maze type: Rect, RectHex, or Hex
101              
102             =over 4
103              
104             =item wallform
105              
106             String naming the wall format. Legal values are bevel, round, roundcorners,
107             and straight. Not all formats work with all maze shapes.
108              
109             =item crumb
110              
111             String describing the breadcrumb design. Legal values are dash,
112             dot, line, and none
113              
114             =item dir
115              
116             Directory in which to find the ecmascript for the maze interactivity. Should
117             either be relative, or in URL form.
118              
119             =item interactive
120              
121             This parameter determines if the maze will be interactive. If the value of the
122             parameter is true (1), the appropriate scripting and support is written into
123             the SVG. If the parameter is omitted or false, no interactive support is
124             provided.
125              
126             =item cols
127              
128             The number of columns used in creating the maze. Default value is 12.
129              
130             =item rows
131              
132             The number of rows used in creating the maze. Default value is 12.
133              
134             =item startcol
135              
136             The column where the entry is found. Default value is random.
137              
138             =item endcol
139              
140             The column where the exit is found. Default value is random.
141              
142             =back
143              
144             =cut
145              
146             sub new
147             {
148 35     35 1 14136 my $class = shift;
149              
150 35   100     205 my $shape = shift || 'Rect';
151              
152 35         836 my %params = @_;
153              
154 35 100 100     349 if( exists $params{crumb} && !exists $crumbstyles{ $params{crumb} } )
155             {
156 1         429 croak "Unrecognized breadcrumb style '$params{crumb}'.\n";
157             }
158              
159 34 100       351 return Games::Maze::SVG::Rect->new( @_ ) if 'Rect' eq $shape;
160 17 100       149 return Games::Maze::SVG::RectHex->new( @_ ) if 'RectHex' eq $shape;
161 9 100       101 return Games::Maze::SVG::Hex->new( @_ ) if 'Hex' eq $shape;
162              
163 1         195 croak "Unrecognized maze shape '$shape'.\n";
164             }
165              
166             =head2 $m->init_object( %parms )
167              
168             Initializes the maze object with the default values for all mazes. The derived
169             classes should call this method in their constructors.
170              
171             Returns the initial data members as a list.
172              
173             =cut
174              
175             sub init_object
176             {
177 33     33 1 97 my %parms = @_;
178              
179 33         260 my %obj = (
180             mazeparms => {},
181             wallform => 'straight',
182             crumb => 'dash',
183             dir => 'scripts/',
184             );
185 33   100     530 $obj{mazeparms}->{dimensions} = [ $parms{cols} || 12, $parms{rows} || 12, 1 ];
      100        
186 33 100       323 $obj{mazeparms}->{entry} = [ $parms{startcol}, 1, 1 ] if $parms{startcol};
187              
188 33 100       129 if( $parms{endcol} )
189             {
190 1         7 $obj{mazeparms}->{exit} = [ $parms{endcol}, $obj{mazeparms}->{dimensions}->[1], 1 ];
191             }
192              
193 33         445 return %obj;
194             }
195              
196             =head2 $m->set_interactive()
197              
198             Method makes the maze interactive.
199              
200             Returns a reference to self for chaining.
201              
202             =cut
203              
204             sub set_interactive
205             {
206 1     1 1 871 my $self = shift;
207 1         4 $self->{interactive} = 1;
208 1         7 return $self;
209             }
210              
211             =head2 $m->set_breadcrumb( $bcs )
212              
213             =over 4
214              
215             =item $bcs
216              
217             String specifying the breadcrumb style. Generates an exception if the
218             breadcrumb style is not recognized.
219              
220             =back
221              
222             Returns a reference to self for chaining.
223              
224             =cut
225              
226             sub set_breadcrumb
227             {
228 6     6 1 8502 my $self = shift;
229 6         11 my $bcs = shift;
230              
231 6 100       21 return unless defined $bcs;
232              
233 5 100       179 croak "Unrecognized breadcrumb style '$bcs'.\n"
234             unless exists $crumbstyles{$bcs};
235 4         14 $self->{crumb} = $bcs;
236 4         11 $self->{crumbstyle} = $crumbstyles{$bcs};
237              
238 4         18 return $self;
239             }
240              
241             =head2 $m->get_crumbstyle()
242              
243             Returns the CSS style for the breadcrumb.
244              
245             =cut
246              
247             sub get_crumbstyle
248             {
249 4     4 1 9 my $self = shift;
250              
251 4   33     31 return $self->{crumbstyle} ||= $crumbstyles{ $self->{crumb} };
252             }
253              
254             =head2 $m->get_script()
255              
256             Method that returns the path to the interactivity script.
257              
258             =cut
259              
260             sub get_script
261             {
262 10     10 1 31 my $self = shift;
263              
264 10         107 return "$self->{dir}$self->{scriptname}";
265             }
266              
267             =head2 $m->to_string()
268              
269             Method that converts the current maze into an SVG string.
270              
271             =cut
272              
273             sub to_string
274             {
275 0     0 1 0 my $self = shift;
276 0         0 my $maze = Games::Maze->new( %{ $self->{mazeparms} } );
  0         0  
277              
278 0         0 $maze->make();
279 0         0 my @rows = map { [ split q{}, $_ ] }
  0         0  
280             split( "\n", $maze->to_ascii() );
281              
282 0         0 my $crumb = q{};
283 0         0 my $color = {
284             mazebg => '#ffc',
285             panel => '#ccc',
286             crumb => '#f3f',
287             sprite => 'orange',
288             button => '#ccf',
289             };
290              
291 0         0 my $crumbstyle = $self->get_crumbstyle();
292              
293 0         0 $self->transform_grid( \@rows, $self->{wallform} );
294 0         0 $self->_just_maze( \@rows );
295              
296 0         0 my ( $xp, $yp ) = $self->convert_start_position( @{ $maze->{entry} } );
  0         0  
297 0         0 my ( $xe, $ye ) = $self->convert_end_position( @{ $maze->{exit} } );
  0         0  
298 0         0 my ( $xenter, $yenter ) = $self->convert_sign_position( $xp, $yp );
299 0         0 my ( $xexit, $yexit ) = $self->convert_sign_position( $xe, $ye );
300              
301 0         0 my $width = $self->{width} + 2 * SIDE_MARGIN;
302 0         0 my $height = $self->{height} + 2 * SIGN_HEIGHT;
303 0         0 my $sprite_def = $self->create_sprite();
304              
305 0         0 my $output = qq{\n};
306 0         0 my $offsety = - SIGN_HEIGHT;
307 0         0 my $offsetx = - SIDE_MARGIN;
308 0         0 my ( $xme, $yme ) = ( $xp * $self->dx(), $yp * $self->dy() );
309 0         0 my ( $xcrumb, $ycrumb ) = ( $xme + $self->dx() / 2, $yme + $self->dy() / 2 );
310              
311 0 0       0 my $panelheight = $height > PANEL_MIN_HEIGHT ? $height : PANEL_MIN_HEIGHT;
312 0 0       0 if( $self->{interactive} )
313             {
314 0         0 my $script = $self->build_all_script();
315 0         0 my $boardelem = $self->build_board_element( \@rows, $xp, $yp, $xe, $ye );
316              
317 0         0 my $totalwidth = $width + PANEL_WIDTH;
318 0         0 $output .= <<"EOH";
319 0         0
320             xmlns="http://www.w3.org/2000/svg"
321             xmlns:xlink="http://www.w3.org/1999/xlink"
322             xmlns:maze="http://www.anomaly.org/2005/maze"
323             onload="initialize()">
324             A Playable SVG Maze
325             This maze was generated using the Games::Maze::SVG Perl
326             module.
327             $license
328            
329            
343            
344            
345            
346            
347             result="lighter"/>
348            
349            
350             result="darker"/>
351            
352            
353            
354            
355            
356            
357            
358             $script
359             $boardelem
360            
361            
362             viewBox="$offsetx $offsety $width $height" id="maze">
363             EOH
364             }
365             else
366             {
367 0         0 $color->{mazebg} = '#fff';
368              
369 0         0 $output .= <<"EOH";
370            
371             xmlns="http://www.w3.org/2000/svg"
372             xmlns:xlink="http://www.w3.org/1999/xlink">
373             An SVG Maze
374             This maze was generated using the Games::Maze::SVG Perl
375             module.
376             $license
377            
378             viewBox="$offsetx $offsety $width $height" id="maze">
379             EOH
380             }
381              
382 0         0 $output .= <<"EOH";
383            
384            
395            
396             $sprite_def
397             @{[$self->wall_definitions()]}
398            
399            
400              
401             $self->{mazeout}
402            
403            
404              
405            
406            
407             Entry
408            
409            
410            
411             Exit
412            
413            
414             EOH
415              
416 0 0       0 if( $self->{interactive} )
417             {
418 0         0 my ( $cx, $cy ) = ( ( $self->{width} + PANEL_WIDTH ) / 2, ( 35 + $panelheight / 2 ) );
419 0         0 $output .= $self->build_control_panel( 0, $panelheight );
420 0         0 $output .= <<"EOM";
421             Solved!
422             EOM
423             }
424 0         0 return $output . "\n";
425             }
426              
427             =head2 $m->toString()
428              
429             Alias for C to deal with inconsistent name from earlier versions.
430              
431             =cut
432              
433 0     0 1 0 sub toString { return $_[0]->to_string(); }
434              
435             =head2 $m->make_board_array( $rows )
436              
437             Build a two-dimensional array of integers that maps the board from
438             the two dimensional matrix of wall descriptions.
439              
440             =cut
441              
442             sub make_board_array
443             {
444 14     14 1 3698 my $self = shift;
445 14         30 my $rows = shift;
446 14         44 my @board = ();
447              
448 14         23 foreach my $row ( @{$rows} )
  14         44  
449             {
450 166 100       289 push @board, [ map { $_ ? 1 : 0 } @{$row} ];
  2526         8609  
  166         271  
451             }
452              
453 14         108 return \@board;
454             }
455              
456             =head2 $m->get_script_list()
457              
458             Returns a list of script URLs that will be needed by the interactive
459             maze.
460              
461             =cut
462              
463             sub get_script_list
464             {
465 6     6 1 3084 my $self = shift;
466 6         73 my @scripts = (
467             "$self->{dir}point.es", "$self->{dir}sprite.es",
468             "$self->{dir}maze.es", $self->get_script(),
469             );
470              
471 6         33 return @scripts;
472             }
473              
474             =head2 $m->build_all_script()
475              
476             Generate the full set of script sections for the maze.
477              
478             =cut
479              
480             sub build_all_script
481             {
482 3     3 1 6 my $self = shift;
483              
484 3         6 my $script = q{};
485              
486 3         13 foreach my $url ( $self->get_script_list() )
487             {
488 12         33 $script .= qq{
506             EOS
507              
508 3         24 return $script;
509             }
510              
511             =head2 $m->build_board_element( $rows, $xp, $yp, $xe, $ye )
512              
513             Create the element that describes the board.
514              
515             =over 4
516              
517             =item $rows
518              
519             reference to an array of rows.
520              
521             =item $xp, $yp
522              
523             Starting position
524              
525             =item $xe, $ye
526              
527             Ending position
528              
529             =back
530              
531             =cut
532              
533             sub build_board_element
534             {
535 0     0 1 0 my $self = shift;
536 0         0 my $rows = shift;
537 0         0 my ( $xp, $yp, $xe, $ye ) = @_;
538              
539 0         0 my $tilex = $self->dx();
540 0         0 my $tiley = $self->dy();
541              
542 0         0 my $board = $self->make_board_array( $rows );
543              
544 0         0 my $elem .= qq{ \n};
545 0         0 foreach my $row ( @{$board} )
  0         0  
546             {
547 0         0 $elem .= qq{ } . join( q{}, @{$row} ) . "\n";
  0         0  
548             }
549 0         0 $elem .= <<'EOS';
550            
551             EOS
552              
553 0         0 return $elem;
554             }
555              
556             =head2 $m->build_control_panel( $startx, $height )
557              
558             Create the displayable control panel
559              
560             =over 4
561              
562             =item $startx
563              
564             the starting x coordinate for the panel
565              
566             =item $height
567              
568             the height of the maze
569              
570             =back
571              
572             =cut
573              
574             sub build_control_panel
575             {
576 0     0 1 0 my $self = shift;
577 0         0 my $startx = shift;
578 0         0 my $height = shift;
579 0         0 my $panelwidth = PANEL_WIDTH;
580              
581 0         0 my $offset = 20;
582 0         0 my $output .= <<"EOB";
583            
584            
585             class="panel"/>
586             EOB
587 0         0 $output .= _create_text_button( 'restart', $offset, 20, 50, 20, 'Begin' );
588 0         0 $output .= _create_text_button( 'save_position', $offset + 60, 20, 50, 20, 'Save' );
589 0         0 $output .= _create_text_button( 'restore_position', $offset + 120, 20, 50, 20, 'Back' );
590 0         0 $output .= <<"EOB";
591              
592            
593            
594             fill="none" stroke-width="0.5" stroke="black"/>
595             Move View
596             EOB
597 0         0 $output .= _create_view_button( 'maze_up', 22, 0, '10,5 5,15 15,15' );
598 0         0 $output .= _create_view_button( 'maze_left', 0, 22, '5,10 15,5 15,15' );
599 0         0 $output .= _create_view_button( 'maze_right', 44, 22, '15,10 5,5 5,15' );
600 0         0 $output .= _create_view_button( 'maze_down', 22, 44, '10,15 5,5 15,5' );
601 0         0 $output .= _create_view_button( 'maze_reset', 22, 22, '7,7 7,13 13,13 13,7' );
602              
603             =begin COMMENT
604              
605             $output .= <<"EOB";
606            
607            
608            
609             EOB
610             $output .= $self->_create_thumbnail();
611              
612             =cut
613              
614 0         0 $output .= <<"EOB";
615            
616              
617            
618             Click Begin button to start
619             Use the arrow keys to move the sprite
620             Hold the shift to move quickly.
621             The mouse must remain over the
622             maze for the keys to work.
623             Use arrow buttons to shift the maze
624             Center button centers view on sprite
625             Save button saves current position
626             Back button restores last position
627            
628            
629             EOB
630              
631 0         0 return $output;
632             }
633              
634             =begin COMMENT
635              
636             # _create_thumbnail
637             #
638             # Create the thumbnail image used to show the player where they are on the
639             # larger field.
640             #
641             sub _create_thumbnail
642             {
643             my $self = shift;
644             my ($x, $y, $wid, $ht) = (0,0,80,80);
645              
646             if($self->{width} > $self->{height})
647             {
648             $ht = int(80 * $self->{height} / $self->{width});
649             $y = (80 - $ht) / 2;
650             }
651             else
652             {
653             $wid = int(80 * $self->{width} / $self->{height});
654             $x = (80 - $wid) / 2;
655             }
656             qq{ \n};
657             }
658              
659             =cut
660              
661             # _create_text_button
662             #
663             # $function - function name to call
664             # $x - x-coordinate of the button
665             # $y - y-coordinate of the button
666             # $width - width of the button
667             # $height - height of the button
668             # $text - text to be displayed on the button
669              
670             sub _create_text_button
671             {
672 0     0   0 my $fn = shift;
673 0         0 my $x = shift;
674 0         0 my $y = shift;
675 0         0 my $width = shift;
676 0         0 my $height = shift;
677 0         0 my $text = shift;
678              
679 0         0 my $tx = $width / 2;
680 0         0 my $ty = $height / 2 + 5;
681              
682 0         0 my $output = <<"EOF";
683              
684            
685             onmousedown="push(evt)" onmouseup="release(evt)" onmouseout="release(evt)">
686            
687             $text
688            
689             EOF
690              
691 0         0 return $output;
692             }
693              
694             # _create_view_button
695             #
696             # $function - function name to call
697             # $x - x-coordinate of the button
698             # $y - y-coordinate of the button
699             # $icon - list of points for the icon
700              
701             sub _create_view_button
702             {
703 0     0   0 my $fn = shift;
704 0         0 my $x = shift;
705 0         0 my $y = shift;
706 0         0 my $icon = shift;
707              
708 0         0 my $output = <<"EOF";
709              
710            
711             onmousedown="push(evt)" onmouseup="release(evt)" onmouseout="release(evt)">
712            
713            
714            
715             EOF
716              
717 0         0 return $output;
718             }
719              
720             =head2 $m->create_sprite()
721              
722             Create the sprite definition for inclusion in the SVG.
723              
724             =cut
725              
726             sub create_sprite
727             {
728 0     0 1 0 my $self = shift;
729 0         0 my ( $dx2, $dy2 ) = ( $self->dx() / 2, $self->dy() / 2 );
730              
731 0         0 return qq| |
732 0         0 . qq||;
  0         0  
  0         0  
  0         0  
733             }
734              
735             #
736             # Generates just the maze portion of the SVG.
737             #
738             # $dx - The size of the tiles in the X direction.
739             # $dy - The size of the tiles in the Y direction.
740             # $rows - Reference to an array of row data.
741             #
742             # returns a string containing the SVG for the maze description.
743             sub _just_maze
744             {
745 0     0   0 my $self = shift;
746 0         0 my $dx = $self->dx();
747 0         0 my $dy = $self->dy();
748 0         0 my $rows = shift;
749              
750 0         0 my $output = q{};
751 0         0 my ( $maxx, $y ) = ( 0, 0 );
752              
753 0         0 foreach my $r ( @{$rows} )
  0         0  
754             {
755 0         0 my $x = 0;
756 0         0 foreach my $c ( @{$r} )
  0         0  
757             {
758 0 0 0     0 $output .= qq{ \n}
759             if $c and $c ne q{$};
760 0         0 $x += $dx;
761             }
762 0         0 $y += $dy;
763 0 0       0 $maxx = $x if $maxx < $x;
764             }
765              
766 0         0 $self->{width} = $maxx;
767 0         0 $self->{height} = $y;
768 0         0 $self->{mazeout} = $output;
769              
770 0         0 return $self;
771             }
772              
773             =head2 $m->dx()
774              
775             Returns the delta X value for building this maze.
776              
777             =cut
778              
779             sub dx
780             {
781 28     28 1 53 my $self = shift;
782              
783 28         93 return $self->{dx};
784             }
785              
786             =head2 $m->dy()
787              
788             Returns the delta Y value for building this maze.
789              
790             =cut
791              
792             sub dy
793             {
794 34     34 1 3785 my $self = shift;
795              
796 34         165 return $self->{dy};
797             }
798              
799             =head1 AUTHOR
800              
801             G. Wade Johnson, C<< >>
802              
803             =head1 BUGS
804              
805             Please report any bugs or feature requests to
806             C, or through the web interface at
807             L.
808             I will be notified, and then you'll automatically be notified of progress on
809             your bug as I make changes.
810              
811             =head1 ACKNOWLEDGEMENTS
812              
813             Thanks go to Valen Johnson and Jason Wood for extensive test play of the
814             mazes.
815              
816             =head1 COPYRIGHT & LICENSE
817              
818             Copyright 2004-2013 G. Wade Johnson, all rights reserved.
819              
820             This program is free software; you can redistribute it and/or modify it
821             under the same terms as Perl itself.
822              
823             =cut
824              
825             1;