File Coverage

blib/lib/Games/Trackword.pm
Criterion Covered Total %
statement 50 50 100.0
branch 22 22 100.0
condition 3 3 100.0
subroutine 9 9 100.0
pod 3 3 100.0
total 87 87 100.0


line stmt bran cond sub pod time code
1             package Games::Trackword;
2              
3 6     6   93190 use strict;
  6         11  
  6         188  
4 6     6   22 use warnings;
  6         7  
  6         160  
5              
6 6     6   22 use vars qw($VERSION);
  6         8  
  6         3472  
7             $VERSION = '1.09';
8              
9             #----------------------------------------------------------------------------
10              
11             =head1 NAME
12              
13             Games::Trackword - Find words on a Trackword grid.
14              
15             =head1 SYNOPSIS
16              
17             use Games::Trackword;
18              
19             my $board = Games::Trackword->new('TRA WKC ORD');
20              
21             foreach my $word (@wordlist) {
22             print "OK $word\n" if $board->has_word($word);
23             }
24              
25             =head1 DESCRIPTION
26              
27             This module lets you set up a Trackword grid and query whether or not it is
28             possible to find words on that grid.
29              
30             Note that although Trackword is similar to Boggle, you are not restricted by
31             the size of the grid and you cannot move diagonally between each letter, you
32             may only move up/down or left/right.
33              
34             =cut
35              
36             my @directions = ([-1,0],[0,-1],[1,0],[0,1]);
37              
38             sub new {
39 5     5 1 1761 my ($class, $string) = @_;
40 83         136 bless {
41             _board => _ring_fence($string),
42 5         13 _has => { map { $_ => 1 } grep /\S/, split //, uc $string },
43             }, $class;
44             }
45              
46             sub qu {
47 6     6 1 11 my $self = shift;
48 6         8 my $this = shift;
49              
50 6         9 $self->{_qu} = 1; # Boggle style
51 6 100 100     39 $self->{_qu} = 0 if(defined $this && !$this);
52             }
53              
54             sub has_word {
55 39     39 1 7096 my $self = shift;
56 39         55 my $word = uc shift;
57 39         51 my $board = $self->{_board};
58              
59 39 100       86 if($self->{_qu}) {
60 12 100       89 return if $word =~ /Q(?!U)/; # Can't have lone Q in boggle.
61 4         13 $word =~ s/QU/Q/;
62             }
63              
64 31 100       48 return unless $self->_have_letters($word);
65              
66 24         33 my $last = chop $word;
67 24         32 my $rows = scalar(@$board)-1;
68 24         44 foreach my $posy (0..$rows) {
69 88         67 my $cols = scalar(@{ $board->[$posy] })-1;
  88         119  
70 88         110 foreach my $posx (0..$cols) {
71 484 100       1145 if($board->[$posy][$posx] eq $last) {
72 25 100       71 return 1 if(_can_play($board, $word.$last, $posy, $posx));
73             }
74             }
75             }
76              
77 3         17 return 0;
78             }
79              
80             # Quick sanity check to stop us looking for words with letters we don't
81             # have. We don't check to ensure that we have ENOUGH copies of each
82             # letter in the word, as that is considerably slower.
83             sub _have_letters {
84 31     31   40 my ($self, $word) = @_;
85 31 100       74 while (my $let = chop $word) { return unless $self->{_has}->{$let}; }
  140         525  
86 24         49 return 1;
87             }
88              
89             sub _can_play {
90 258     258   259 my ($board, $word, $posy, $posx) = @_;
91 258 100       531 if (length $word > 1) {
92 207         216 my $last = chop $word;
93 207 100       365 if($board->[$posy][$posx] eq $last) {
94 97         122 local $board->[$posy][$posx] = "-";
95 97         105 foreach my $dir (@directions) {
96 233 100       494 return 1 if(_can_play($board, $word, $posy+$dir->[0], $posx+$dir->[1]));
97             }
98             }
99 122         288 return 0;
100             }
101 51 100       355 return ($board->[$posy][$posx] eq $word) ? 1 : 0;
102             }
103              
104             sub _ring_fence {
105 5     5   33 my @block = split /\s/, uc $_[0];
106 5         10 my $width = length($block[0])+2;
107 20         77 push my @board, [('-') x $width],
108 5         20 (map {['-',split(//),'-']} @block),
109             [('-') x $width];
110 5         72 return \@board;
111             }
112              
113             1;
114              
115             __END__