File Coverage

blib/lib/Spreadsheet/HTML/Presets/Conway.pm
Criterion Covered Total %
statement 8 60 13.3
branch 0 32 0.0
condition 0 18 0.0
subroutine 4 7 57.1
pod 1 1 100.0
total 13 118 11.0


line stmt bran cond sub pod time code
1             package Spreadsheet::HTML::Presets::Conway;
2 5     5   17 use strict;
  5         6  
  5         118  
3 5     5   14 use warnings FATAL => 'all';
  5         7  
  5         3395  
4              
5 5     5   772 eval "use Color::Spectrum";
  0         0  
  0         0  
6             our $NO_SPECTRUM = $@;
7              
8 5     5   745 eval "use Encode::Wechsler";
  0            
  0            
9             our $NO_WECHSLER = $@;
10              
11             sub conway {
12 0     0 1   my ($self,$data,$args);
13 0 0         $self = shift if ref($_[0]) =~ /^Spreadsheet::HTML/;
14 0 0         ($self,$data,$args) = $self ? $self->_args( @_ ) : Spreadsheet::HTML::_args( @_ );
15              
16 0   0       $args->{on} ||= '#00BFA5';
17 0   0       $args->{off} ||= '#EEEEEE';
18             $args->{colors} = ($NO_SPECTRUM or !$args->{fade})
19             ? [ ($args->{on}) x 10 ]
20 0 0 0       : [ Color::Spectrum::generate( 10, $args->{on}, $args->{off} ) ];
21              
22 0   0       $args->{interval} ||= 200;
23              
24 0 0 0       if ($args->{wechsler} and not $NO_WECHSLER) {
    0 0        
25 0 0         my $wechsler = Encode::Wechsler->new( pad => defined $args->{pad} ? $args->{pad} : 1 );
26 0           my @grid;
27 0           eval { @grid = $wechsler->decode( $args->{wechsler} ) };
  0            
28 0 0         if ($@) {
29 0           $args->{data} = [[ 'Error' ],[ $@ ]];
30 0 0         return $self ? $self->generate( %$args ) : Spreadsheet::HTML::generate( %$args );
31             }
32              
33 0           $args->{_max_rows} = scalar @grid;
34 0           $args->{_max_cols} = scalar @{ $grid[0] };
  0            
35              
36 0           for my $row (0 .. $#grid) {
37 0           for my $col (0 .. $#{ $grid[$row] } ) {
  0            
38 0 0         $args->{"-r${row}c${col}"} = $grid[$row][$col] if $grid[$row][$col];
39             }
40             }
41             } elsif ($args->{wechsler} and $NO_WECHSLER) {
42 0           warn "You specified an argument for wechsler but Encode::Wechsler is not installed\n";
43             }
44              
45 0           my ( @cells, @ids );
46 0           for my $r ( 0 .. $args->{_max_rows} - 1 ) {
47 0           for my $c ( 0 .. $args->{_max_cols} - 1 ) {
48 0           my $cell = sprintf '-r%sc%s', $r, $c;
49             push @cells,
50             $cell => {
51             id => join( '-', $r, $c ),
52             class => 'conway',
53             width => '30px',
54             height => '30px',
55             style => { 'background-color' => $args->{off} },
56 0           };
57             # if alpha param was set to valid value, then
58             # only non-alpha cells will be set here
59             # TODO: determine alpha value (lightest color) for client
60 0 0         push @ids, join( '-', $r, $c ) if $args->{$cell};
61             }
62             }
63              
64             # find and remove 'file' param and its value if found
65             # because it was already processed via ::File::Loader by way of _args()
66 0 0 0       if ($args->{file} and $args->{file} =~ /\.(gif|png|jpe?g)$/) {
67 0           my $index = 0;
68 0           for (0 .. $#_) {
69 0 0         next if ref $_[$_];
70 0 0         if ($_[$_] eq 'file') { $index = $_; last }
  0            
  0            
71             }
72 0           splice @_, $index, 2;
73 0           push @_, ( fill => $args->{fill} );
74             }
75              
76             my @args = (
77             @cells,
78             data => $data,
79             ( $args->{wechsler} ? (matrix => 1) : () ),
80 0 0         ( $args->{wechsler} ? (fill => join 'x', $args->{_max_rows}, $args->{_max_cols}) : () ),
    0          
81             caption => { '' => { align => 'bottom' } },
82             @_,
83             );
84              
85 0           my $js = _javascript( %$args, ids => \@ids );
86 0 0         my $table = $self ? $self->generate( @args ) : Spreadsheet::HTML::generate( @args );
87 0           return $js . $table;
88             }
89              
90             sub _javascript {
91 0     0     my %args = @_;
92              
93             my $js = sprintf _js_tmpl(),
94             $args{_max_rows},
95             $args{_max_cols},
96             $args{interval},
97 0           join( ',', map "'$_'", @{ $args{ids} } ),
98             $args{off},
99 0           join( ',', map "'$_'", @{ $args{colors} } ),
  0            
100             ;
101              
102 0           return Spreadsheet::HTML::Presets::_js_wrapper( code => $js, %args );
103             }
104              
105             sub _js_tmpl {
106 0     0     return <<'END_JAVASCRIPT';
107              
108             /* Copyright 2017 Jeff Anderson */
109             /* install JavaScript::Minifier to minify this code */
110             var MATRIX;
111             var ROW = %s;
112             var COL = %s;
113             var INTERVAL = %s;
114             var tid;
115             var ids = [ %s ];
116              
117             function Cell (id) {
118             this.id = id;
119             this.neighbors = 0;
120             this.age = 0;
121             this.off = '%s';
122             this.on = [ %s ];
123              
124             this.grow = function( age ) {
125             this.age = age;
126             if (age == 0) {
127             $('#' + this.id).css( 'background-color', this.off );
128             } else {
129             $('#' + this.id).css( 'background-color', this.on[age - 1] );
130             }
131             }
132              
133             this.update = function() {
134             if (this.age) {
135             if ((this.neighbors <= 1) || (this.neighbors >= 4)) {
136             this.grow( 0 );
137             } else if (this.age < 9) {
138             this.grow( ++this.age );
139             }
140             }
141             else {
142             if (this.neighbors == 3) {
143             this.grow( 1 );
144             }
145             }
146             this.neighbors = 0;
147             }
148             }
149              
150             function toggle() {
151             if ($('#toggle').html() === 'Start') {
152             tid = setInterval( update, INTERVAL );
153             $('#toggle').html( 'Stop' );
154             } else {
155             clearInterval( tid );
156             $('#toggle').html( 'Start' );
157             }
158             }
159              
160             $(document).ready(function(){
161              
162             $('th.conway, td.conway').click( function( data ) {
163             var matches = this.id.match( /(\d+)-(\d+)/ );
164             var selected = MATRIX[matches[1]][matches[2]];
165             if (selected.age) {
166             selected.grow( 0 );
167             } else {
168             selected.grow( 1 );
169             }
170             });
171              
172             MATRIX = new Array( ROW );
173             for (var row = 0; row < ROW; row++) {
174             MATRIX[row] = new Array( COL );
175             for (var col = 0; col < COL; col++) {
176             MATRIX[row][col] = new Cell( row + '-' + col );
177             }
178             }
179              
180             // activate any "pre-loaded" IDs
181             for (var i = 0; i < ids.length; i++) {
182             var matches = ids[i].match( /(\d+)-(\d+)/ );
183             var selected = MATRIX[matches[1]][matches[2]];
184             selected.grow( 1 );
185             }
186              
187             });
188              
189             function update() {
190              
191             // count neighbors
192             for (var row = 0; row < ROW; row++) {
193             for (var col = 0; col < COL; col++) {
194              
195             for (var r = -1; r <= 1; r++) {
196             if ( (row + r >=0) & (row + r < ROW) ) {
197              
198             for (var c = -1; c <= 1; c++) {
199             if ( ((col+c >= 0) & (col+c < COL)) & ((row+r != row) | (col+c != col))) {
200             if (MATRIX[row + r][col + c].age) {
201             MATRIX[row][col].neighbors++;
202             }
203             }
204             }
205             }
206             }
207             }
208             }
209              
210             // update cells
211             for (var row = 0; row < ROW; row++) {
212             for (var col = 0; col < COL; col++) {
213             MATRIX[row][col].update();
214             }
215             }
216             }
217             END_JAVASCRIPT
218             }
219              
220             =head1 NAME
221              
222             Spreadsheet::HTML::Presets::Conway - Generate Conway's Game of Life in HTML table cells' background.
223              
224             =head1 DESCRIPTION
225              
226             This is a container for L preset methods.
227             These methods are not meant to be called from this package.
228             Instead, use the Spreadsheet::HTML interface:
229              
230             use Spreadsheet::HTML;
231             my $generator = Spreadsheet::HTML->new( data => \@data );
232             print $generator->conway;
233              
234             # or
235             use Spreadsheet::HTML qw( conway );
236             print conway( data => \@data );
237              
238             =head1 METHODS
239              
240             =over 4
241              
242             =item * C
243              
244             Game of life. From an implementation i wrote back in college.
245              
246             conway( on => 'red', off => 'gray' )
247              
248             Set the timer with C (defaults to 200 miliseconds).
249              
250             conway( interval => 75 )
251              
252             If you have L installed then you can preload the game board
253             with any valid Wechsler Code:
254              
255             conway( wechsler => 'xp3_0ggmligkcz32w46' )
256              
257             Some codes require additional padding in order to sustain properly.
258              
259             conway( wechsler => 'xp30_ccx8k2s3zy3103y531e8', pad => 2 )
260              
261             Padding defaults to 1, so you may turn it off if you don't need it:
262              
263             conway( wechsler => 'xp2_25a4owo4a52zy01221', pad => 0 )
264              
265             If you have L installed then you can activate a fading effect like so:
266              
267             conway( on => '#FF0000', off => '#999999', fade => 1 )
268              
269             # color names via Color::Library
270             conway( on => 'red', off => 'gray', fade => 1 )
271              
272             You can also load a file and pre-populate a game grid if you know which
273             color will be used as C. This example uses #F8F8F8 and tweaks the block
274             size up to 16:
275              
276             conway( file => 'conway.png', alpha => '#f8f8f8', block => 16 )
277              
278             The C param is available whenever you supply an image file.
279              
280             Uses Google's jQuery API unless you specify another URI via
281             the C param. Javascript will be minified
282             via L if it is installed.
283              
284             =back
285              
286             =head1 SEE ALSO
287              
288             =over 4
289              
290             =item L
291              
292             The interface for this functionality.
293              
294             =item L
295              
296             More presets.
297              
298             =item L
299              
300             Wechsler encoder/decoder for Conway's Game of Life boards.
301              
302             =item L
303              
304             Generates spectrums of HTML color strings.
305              
306             =item L
307              
308             Comprehensive named color dictionary.
309              
310             =back
311              
312             =head1 AUTHOR
313              
314             Jeff Anderson, C<< >>
315              
316             =head1 LICENSE AND COPYRIGHT
317              
318             Copyright 2017 Jeff Anderson.
319              
320             This program is free software; you can redistribute it and/or modify it
321             under the terms of the the Artistic License (2.0). You may obtain a
322             copy of the full license at:
323              
324             L
325              
326             Any use, modification, and distribution of the Standard or Modified
327             Versions is governed by this Artistic License. By using, modifying or
328             distributing the Package, you accept this license. Do not use, modify,
329             or distribute the Package, if you do not accept this license.
330              
331             If your Modified Version has been derived from a Modified Version made
332             by someone other than you, you are nevertheless required to ensure that
333             your Modified Version complies with the requirements of this license.
334              
335             This license does not grant you the right to use any trademark, service
336             mark, tradename, or logo of the Copyright Holder.
337              
338             This license includes the non-exclusive, worldwide, free-of-charge
339             patent license to make, have made, use, offer to sell, sell, import and
340             otherwise transfer the Package with respect to any patent claims
341             licensable by the Copyright Holder that are necessarily infringed by the
342             Package. If you institute patent litigation (including a cross-claim or
343             counterclaim) against any party alleging that the Package constitutes
344             direct or contributory patent infringement, then this Artistic License
345             to you shall terminate on the date that such litigation is filed.
346              
347             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
348             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
349             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
350             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
351             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
352             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
353             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
354             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
355              
356             =cut
357              
358             1;