File Coverage

blib/lib/Encode/Wechsler.pm
Criterion Covered Total %
statement 77 77 100.0
branch 22 28 78.5
condition 5 5 100.0
subroutine 8 8 100.0
pod 3 3 100.0
total 115 121 95.0


line stmt bran cond sub pod time code
1             package Encode::Wechsler;
2 4     4   142824 use strict;
  4         7  
  4         122  
3 4     4   15 use warnings FATAL => 'all';
  4         4  
  4         3941  
4             our $VERSION = '0.03';
5              
6             my $i = 0;
7             # {0, 1, 2, ..., 8, 9, a, b, ..., v} correspond to the bitstrings {'00000', '00001', '00010', ..., '11111'}.
8             our %bits = map { $_ => sprintf("%05d", unpack( 'B32', pack( 'N', $i++ ) ) ) } 0 .. 9, 'a' .. 'v';
9             our %stib = reverse %bits;
10              
11             $i = 4;
12             # We use the characters 'w' and 'x' to abbreviate '00' and '000'
13             # the symbols {'y0', 'y1', y2', ..., 'yx', 'yy', 'yz'} correspond to runs of between 4 and 39 consecutive '0's.
14             our %zero = map { 'y' . $_ => 0 x $i++ } 0 .. 9, 'a' .. 'z';
15             $zero{w} = '00';
16             $zero{x} = '000';
17             #our %orez = reverse %zero;
18              
19             sub new {
20 7     7 1 237643 my $self = shift;
21 7         27 return bless {@_}, $self;
22             }
23              
24              
25             sub encode {
26 2     2 1 9 my $self = shift;
27 2         7 my %args = @_;
28 2         6 my $thingy = $args{board};
29              
30             # multi-line string
31 2 50       17 $thingy = [ split "\n", $thingy ] unless ref( $thingy );
32              
33             # array of strings
34 2 50       64 $thingy = [ map [ split //, $_ ], @$thingy ] unless ref( $thingy->[0] );
35              
36             # all become 2D array of true or false values
37 2 100       8 $thingy = [ map [ map { $_ eq '.' ? 0 : 1 } @$_ ], @$thingy ];
  109         172  
38              
39             =for later
40             my @chunks;
41             my $start_pruning;
42             for (my $i = 0; $i < @$thingy; $i += 5) {
43             # all zeros are ok until we found our first non all zeros
44             # then prune all zeros that are "trailing"
45             my @chunk;
46             for (@$thingy[ $i .. $i + 4 ]) {
47             $start_pruning ||= int( join '', @$_ );
48             if ($start_pruning) {
49             push @chunk, $_ if ref($_) && int( join '', @$_ );
50             } else {
51             push @chunk, $_ if ref($_);
52             }
53             }
54             push @chunks, [@chunk] if @chunk;
55             }
56             =cut
57 2         11 my @chunks;
58 2         23 for (my $i = 0; $i < @$thingy; $i += 5) {
59             my @chunk = map {
60 3 100 100     11 ref( $_ ) && int( join '', @$_ ) ? $_ : ()
  15         92  
61             } @$thingy[ $i .. $i + 4 ];
62 3         14 push @chunks, [@chunk];
63             }
64              
65 2         8 @chunks = map _transpose( $_ ), @chunks;
66              
67             #TODO: detect consecutive zero runs
68 2         3 my @bits;
69 2         5 for my $chunk (@chunks) {
70             push @bits, join '',
71 25         49 map { $stib{$_} }
72 3         4 map { sprintf '%05d', join '', reverse @$_ } @$chunk
  25         70  
73             ;
74             }
75              
76             # this could be removed by detecting consecutive zero runs
77 2         5 my $str = join 'z', @bits;
78 2         16 $str =~ s/0*$//;
79              
80 2         33 return $str;
81             }
82              
83             sub decode {
84 9     9 1 34 my ($self,$code) = @_;
85              
86 9         21 $code =~ s/^\s+//;
87 9         14 $code =~ s/\s+$//;
88 9 50       39 die "invalid format: $code\n" unless $code =~ /x[spq][0-9]+_[0-9a-z]+/;
89              
90 9         26 my ($prefix,$format) = split '_', $code, 2;
91              
92 9         17 $self->{max} = 0;
93             $format = join 'z', map {
94 9         20 s/(w|x|y.)/$zero{$1}/g;
  23         76  
95 23 100       59 $self->{max} = length($_) if length($_) > $self->{max};
96 23         41 $_;
97             } split 'z', $format;
98              
99 9         11 my @grid;
100 9         21 for my $part (split 'z', $format ) {
101              
102 23 100       44 if (length($part) < $self->{max}) {
103 14         25 $part .= '0' x ( $self->{max} - length($part) );
104             }
105              
106             # pad left and right
107 23 100       55 $part = ('0' x $self->{pad}) . $part . ('0' x $self->{pad}) if $self->{pad};
108              
109 23         15 my $i = 0;
110 23         70 for (split '', $part) {
111 342         207 push @{ $grid[$i] }, map int $_, reverse split //, $bits{$_};
  342         1087  
112 342         432 $i++;
113             }
114             }
115              
116 9         11 my @trans;
117 9         11 for my $i (reverse 0 .. $#{ $grid[0] }) {
  9         18  
118 115   100     2279 push @trans, [ map $_->[$i] || 0, @grid ];
119             }
120              
121             #remove leading blank rows
122 9         13 for (@trans) {
123 19 100       31 if (_sum( @$_ )) {
124 9         11 last;
125             }
126 10         18 shift @trans;
127             }
128              
129 9         47 @grid = reverse @trans;
130              
131              
132 9 100       18 if ($self->{pad}) {
133 5 50       6 unshift @grid, ([(0) x ($self->{max} + (2 * $self->{pad}))]) x $self->{pad} if _sum( $grid[ 0] );
134 5 50       7 push @grid, ([(0) x ($self->{max} + (2 * $self->{pad}))]) x $self->{pad} if _sum( $grid[-1] );
135             }
136            
137 9 50       27 return wantarray ? @grid : $self->_to_string( @grid );
138             }
139              
140             sub _to_string {
141 9     9   10 my $self = shift;
142 9         8 my $str = '';
143 9         10 for (@_) {
144 133 100       1118 $str .= $_ ? '*' : '.' for @$_;
145 133         107 $str .= "\n";
146             }
147 9         67 return $str;
148             }
149              
150             sub _sum {
151 29     29   21 my $s = 0;
152 29         98 $s += $_ for @_;
153 29         73 return $s;
154             }
155              
156             # credit: Math::Matrix
157             sub _transpose {
158 3     3   3 my $data = shift;
159 3         4 my @trans;
160 3         4 for my $i (0 .. $#{ $data->[0] }) {
  3         10  
161 25         127 push @trans, [ map $_->[$i], @$data ]
162             }
163 3         10 return \@trans;
164             }
165              
166             1;
167              
168             __END__