File Coverage

blib/lib/Karel/Robot.pm
Criterion Covered Total %
statement 93 95 97.8
branch 17 24 70.8
condition 6 8 75.0
subroutine 21 21 100.0
pod 5 5 100.0
total 142 153 92.8


line stmt bran cond sub pod time code
1             package Karel::Robot;
2              
3             =head1 NAME
4              
5             Karel::Robot
6              
7             =head1 DESCRIPTION
8              
9             Basic robot class. It represents a robot wihtout a grid.
10              
11             =head1 METHODS
12              
13             =over 4
14              
15             =cut
16              
17 8     8   564028 use warnings;
  8         11  
  8         247  
18 8     8   29 use strict;
  8         11  
  8         160  
19              
20 8     8   2975 use Karel::Grid;
  8         34  
  8         267  
21 8     8   3250 use Karel::Parser;
  8         21  
  8         250  
22 8     8   45 use Carp;
  8         11  
  8         470  
23 8     8   4389 use Module::Load qw{ load };
  8         7589  
  8         58  
24 8     8   452 use Moo;
  8         116  
  8         85  
25 8     8   6133 use Syntax::Construct qw{ // };
  8         7160  
  8         46  
26 8     8   532 use namespace::clean;
  8         12  
  8         45  
27              
28             =item my $robot = 'Karel::Robot'->new
29              
30             The constructor. It can take one parameter: C. Its value
31             should be a parser object, by default an instance of C.
32              
33             =item $robot->set_grid($grid, $x, $y, $direction)
34              
35             Applies the L role to the $robot. C<$grid>
36             must be a C instance, $x and $y denote the position of
37             the robot in the grid. Optional $direction is one of C (for
38             North, East, South, and West), defaults to C.
39              
40             =cut
41              
42             sub set_grid {
43 43     43 1 257 my ($self, $grid, $x, $y, $direction) = @_;
44 43   100     118 $direction //= 'N';
45 43         163 my $with_grid_class = $self->class_with_grid;
46 43 50       229 if (! $self->does($with_grid_class)) {
47 43         51637 load($with_grid_class);
48 43         3137 'Moo::Role'->apply_roles_to_object($self, $with_grid_class);
49 43         1330 $self->set_grid($grid, $x, $y, $direction);
50             }
51             }
52              
53             =item class_with_grid
54              
55             The class to which the robot is reblessed after obraining the grid. By
56             default, it's the robot's class plus C<::WithGrid>.
57              
58             =cut
59              
60 43     43 1 142 sub class_with_grid { ref(shift) . '::WithGrid' }
61              
62              
63             =item $robot->load_grid( [ file | handle ] => '...' )
64              
65             Loads grid from the given source. You can specify a scalar reference
66             as C, too. The format of the input is as follows:
67              
68             # karel 4 2
69             WWWWWW
70             W v W
71             W1w W
72             WWWWWW
73              
74             The first line specifies width and height of the grid. An ASCII map of
75             the grid follows with the following symbols:
76              
77             W outer wall
78             w inner wall
79             space blank
80             1 .. 9 marks
81              
82             The robot's position and direction is denoted by either of C<< ^ > v <
83             >> B the cell in which the robot should start. In the
84             example above, the robots starts at coordinates 4, 1 and faces South.
85              
86             =cut
87              
88             my %faces = ( '^' => 'N',
89             '>' => 'E',
90             'v' => 'S',
91             '<' => 'W' );
92              
93             sub load_grid {
94 18     18 1 3178 my ($self, $type, $that) = @_;
95              
96 18         28 my %backup;
97 18 100       101 if ($self->can('grid')) {
98 1         21 @backup{qw{ grid x y direction }} = map $self->$_,
99             qw( grid x y direction );
100             }
101              
102 18         19 my $IN;
103 3 50   3   100 my $open = { file => sub { open $IN, '<', $that or croak "$that: $!" },
104 13 50   13   187 string => sub { open $IN, '<', \$that or croak "'$that': $!" },
105 1     1   2 handle => sub { $IN = $that },
106 18         155 }->{$type};
107 18 100       122 croak "Unknown type $type" unless $open;
108 17         37 $open->();
109              
110 17         118 local $/ = "\n";
111 17         116 my $header = <$IN>;
112 17 50       134 croak 'Invalid format'
113             unless $header =~ /^\# \s* karel \s+ (v[0-9]+\.[0-9]{2}) \s+ ([0-9]+) \s+ ([0-9]+)/x;
114 17         62 my ($version, $x, $y) = ($1, $2, $3);
115 17         372 my $grid = 'Karel::Grid'->new( x => $x,
116             y => $y,
117             );
118              
119 17         81 my $r = 0;
120 17         19 my (@pos, $direction);
121 17         68 while (<$IN>) {
122 73         89 chomp;
123 73         175 my @chars = split //;
124 73         84 my $c = 0;
125 73         122 while ($c != $#chars) {
126 303 100 66     1011 next if 'W' eq $chars[$c]
      66        
127             && ( $r == 0 || $r == $y + 1
128             || $c == 0 || $c == $x + 1);
129             my $build = { w => 'build_wall',
130             ' ' => 'clear',
131             # marks
132             ( map {
133 1260         845 my $x = $_;
134             $x => sub {
135 14     14   59 $_[0]->drop_mark(@_[1, 2]) for 1 .. $x
136             }
137 1260         2415 } 1 .. 9 ),
138             # robot
139             ( map {
140 560         459 my $f = $_;
141             $f => sub {
142 17 50   17   33 croak 'Two robots in a grid' if $direction;
143 17         23 $direction = $faces{$f};
144 17         25 @pos = ($c, $r);
145 17         27 splice @chars, $c, 1;
146 8     8   6077 no warnings 'exiting';
  8         13  
  8         3504  
147             redo
148 17         53 }
149 560         1854 } keys %faces )
150 140         175 }->{ $chars[$c] };
151 140 100       1453 croak "Unknown or invalid grid character '$chars[$c]' at $c, $.."
152             unless $build;
153 139         402 $grid->$build($c, $r);
154             } continue {
155 285         1084 ++$c;
156             }
157             } continue {
158 72         206 ++$r;
159             }
160              
161 16 100       52 croak 'Wall at starting position' if 'w' eq lc $grid->at(@pos);
162              
163             eval {
164 14         53 $_[0]->set_grid($grid, @pos, $direction);
165 14 50       106 1 } or do {
  14         303  
166 0         0 $_[0]->set_grid(@backup{qw{ grid x y direction }});
167 0         0 croak $@
168             };
169             }
170              
171             has parser => ( is => 'ro',
172             default => sub { 'Karel::Parser'->new },
173             );
174              
175              
176             =item $commands = $robot->knows($command_name)
177              
178             If the robot knows the command, returns its definition; dies
179             otherwise.
180              
181             =cut
182              
183             sub knows {
184 119     119 1 7336 my ($self, $command) = @_;
185 119         490 $self->knowledge->{$command}[0]
186             }
187              
188             sub _learn {
189 45     45   10235 my ($self, $command, $parsed, $code) = @_;
190 45         76 my ($prog, $from, $to) = @$parsed;
191 45         136 my $knowledge = $self->knowledge;
192 45         118 $knowledge->{$command} = [ $prog, $code ]; # TODO: No leaks!
193 45         204 $self->_set_knowledge($knowledge);
194             }
195              
196             =item $robot->learn($program)
197              
198             Teaches the robot new commands. Dies if the definitions contain
199             unknown commands.
200              
201             =cut
202              
203             sub learn {
204 13     13 1 4974 my ($self, $prog) = @_;
205 13         81 my ($commands, $unknown) = $self->parser->parse($prog);
206 13         139 $self->_learn($_, $commands->{$_}, $prog) for keys %$commands;
207 13         54 for my $command (keys %$unknown) {
208 21 50       48 croak "Dont' know $command" unless $self->knows($command);
209             }
210             }
211              
212             has knowledge => ( is => 'rwp' );
213              
214             =back
215              
216             =cut
217              
218              
219             __PACKAGE__