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   393023 use strict;
  6         17  
  6         257  
4 6     6   34 use warnings;
  6         14  
  6         410  
5              
6 6     6   35 use vars qw($VERSION);
  6         18  
  6         8143  
7             $VERSION = '1.08';
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 3321 my ($class, $string) = @_;
40 83         221 bless {
41             _board => _ring_fence($string),
42 5         17 _has => { map { $_ => 1 } grep /\S/, split //, uc $string },
43             }, $class;
44             }
45              
46             sub qu {
47 6     6 1 8 my $self = shift;
48 6         94 my $this = shift;
49              
50 6         8 $self->{_qu} = 1; # Boggle style
51 6 100 100     32 $self->{_qu} = 0 if(defined $this && !$this);
52             }
53              
54             sub has_word {
55 39     39 1 6427 my $self = shift;
56 39         238 my $word = uc shift;
57 39         57 my $board = $self->{_board};
58              
59 39 100       91 if($self->{_qu}) {
60 12 100       71 return if $word =~ /Q(?!U)/; # Can't have lone Q in boggle.
61 4         12 $word =~ s/QU/Q/;
62             }
63              
64 31 100       61 return unless $self->_have_letters($word);
65              
66 24         35 my $last = chop $word;
67 24         37 my $rows = scalar(@$board)-1;
68 24         45 foreach my $posy (0..$rows) {
69 88         97 my $cols = scalar(@{ $board->[$posy] })-1;
  88         158  
70 88         128 foreach my $posx (0..$cols) {
71 484 100       1894 if($board->[$posy][$posx] eq $last) {
72 25 100       74 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   48 my ($self, $word) = @_;
85 31 100       85 while (my $let = chop $word) { return unless $self->{_has}->{$let}; }
  140         11139  
86 24         68 return 1;
87             }
88              
89             sub _can_play {
90 258     258   400 my ($board, $word, $posy, $posx) = @_;
91 258 100       506 if (length $word > 1) {
92 207         318 my $last = chop $word;
93 207 100       400 if($board->[$posy][$posx] eq $last) {
94 97         149 local $board->[$posy][$posx] = "-";
95 97         128 foreach my $dir (@directions) {
96 233 100       553 return 1 if(_can_play($board, $word, $posy+$dir->[0], $posx+$dir->[1]));
97             }
98             }
99 122         463 return 0;
100             }
101 51 100       418 return ($board->[$posy][$posx] eq $word) ? 1 : 0;
102             }
103              
104             sub _ring_fence {
105 5     5   41 my @block = split /\s/, uc $_[0];
106 5         12 my $width = length($block[0])+2;
107 20         536 push my @board, [('-') x $width],
108 5         24 (map {['-',split(//),'-']} @block),
109             [('-') x $width];
110 5         83 return \@board;
111             }
112              
113             1;
114              
115             __END__