File Coverage

blib/lib/Games/ScottAdams/Action.pm
Criterion Covered Total %
statement 98 104 94.2
branch 27 38 71.0
condition 5 9 55.5
subroutine 16 16 100.0
pod 0 13 0.0
total 146 180 81.1


line stmt bran cond sub pod time code
1             # $Id: Action.pm,v 1.5 2006/11/04 10:11:11 mike Exp $
2              
3             # Action.pm - an action in a Scott Adams game.
4              
5             package Games::ScottAdams::Action;
6 1     1   5 use strict;
  1         2  
  1         390  
7              
8              
9             sub new {
10 65     65 0 92 my $class = shift();
11 65         105 my($verb, $noun, $num) = @_;
12              
13 65         557 return bless {
14             verb => $verb,
15             noun => $noun,
16             num => $num, # 0-based index into Game's list of actions
17             ### I don't think we actually use this
18             comment => undef, # optional comment to be written through
19             cond => [], # array of conditions to be satisfied
20             res => [], # array of results to be executed
21             }, $class;
22             }
23              
24              
25             sub verb {
26 65     65 0 71 my $this = shift();
27 65         238 return $this->{verb};
28             }
29              
30             sub noun {
31 65     65 0 76 my $this = shift();
32 65         150 return $this->{noun};
33             }
34              
35             sub comment {
36 73     73 0 99 my $this = shift();
37 73         91 my($name) = @_;
38              
39 73         108 my $old = $this->{comment};
40 73 100       141 if (defined $name) {
41 4         7 $this->{comment} = $name;
42             }
43 73         352 return $old;
44             }
45              
46              
47             # We'd like to compile these up front so we can complain about
48             # unrecognised condition and actions while we still know where we are
49             # in the source file. Unfortunately, we can't do it in general as the
50             # action may refer to the names of rooms or items that have not yet
51             # been defined. So all we can do at this stage is remember them for
52             # later.
53             #
54             sub add_cond {
55 59     59 0 70 my $this = shift();
56 59         69 my($text) = @_;
57              
58 59         113 push @{ $this->{cond} }, $text;
  59         315  
59             }
60              
61              
62             sub add_result {
63 121     121 0 134 my $this = shift();
64 121         137 my($text) = @_;
65              
66 121         132 push @{ $this->{res} }, $text;
  121         542  
67             }
68              
69              
70             # PRIVATE to the compile() method.
71 149     149 0 373 sub ARG_NONE { 0 } # no argument
72 131     131 0 398 sub ARG_NUM { 1 } # argument specifies a flag
73 91     91 0 193 sub ARG_ROOM { 2 } # argument identifies a room
74 82     82 0 169 sub ARG_ITEM { 3 } # argument identifies an item
75 2     2 0 6 sub ARG_ITEMROOM { 4 } # arguments identify an item and a room
76 2     2 0 5 sub ARG_ITEMITEM { 5 } # arguments identify two items
77              
78 1     1   6 use vars qw(%_cond %_res); # Global as they need to be visible to "sad"
  1         2  
  1         1541  
79             %_cond = (
80             carried => [ 1, ARG_ITEM ],
81             here => [ 2, ARG_ITEM ],
82             accessible => [ 3, ARG_ITEM ],
83             at => [ 4, ARG_ROOM ],
84             '!here' => [ 5, ARG_ITEM ],
85             '!carried' => [ 6, ARG_ITEM ],
86             '!at' => [ 7, ARG_ROOM ],
87             flag => [ 8, ARG_NUM ],
88             '!flag' => [ 9, ARG_NUM ],
89             loaded => [ 10, ARG_NONE ],
90             '!loaded' => [ 11, ARG_NONE ],
91             '!accessible' => [ 12, ARG_ITEM ],
92             exists => [ 13, ARG_ITEM ],
93             '!exists' => [ 14, ARG_ITEM ],
94             counter_le => [ 15, ARG_NUM ],
95             # counter_ge => [ 16, ARG_NUM ],
96             counter_gt => [ 16, ARG_NUM ],
97             # ### The documentation accompanying the scottfree
98             # interpreter says that condition 16 tests for
99             # current counter's value greater than or equal
100             # to the argument, but inspection of the source
101             # shows that it actually tests for strictly
102             # greater-than.
103             '!moved' => [ 17, ARG_ITEM ],
104             moved => [ 18, ARG_ITEM ],
105             counter_eq => [ 19, ARG_NUM ],
106             );
107              
108             %_res = (
109             get => [ 52, ARG_ITEM ],
110             drop => [ 53, ARG_ITEM ],
111             moveto => [ 54, ARG_ROOM ],
112             destroy => [ 55, ARG_ITEM ],
113             set_dark => [ 56, ARG_NONE ],
114             clear_dark => [ 57, ARG_NONE ],
115             set_flag => [ 58, ARG_NUM ],
116             destroy2 => [ 59, ARG_ITEM ],
117             # Same as 55 in ScottFree
118             clear_flag => [ 60, ARG_NUM ],
119             die => [ 61, ARG_NONE ],
120             put => [ 62, ARG_ITEMROOM ],
121             game_over => [ 63, ARG_NONE ],
122             look => [ 64, ARG_NONE ],
123             score => [ 65, ARG_NONE ],
124             inventory => [ 66, ARG_NONE ],
125             set_0 => [ 67, ARG_NONE ],
126             clear_0 => [ 68, ARG_NONE ],
127             refill_lamp => [ 69, ARG_NONE ], ### UNTESTED
128             clear_screen => [ 70, ARG_NONE ], ### UNTESTED
129             save_game => [ 71, ARG_NONE ],
130             swap => [ 72, ARG_ITEMITEM ],
131             continue => [ 73, ARG_NONE ], ### UNTESTED
132             # Automatic -- is there ever any need to use it explicitly?
133             superget => [ 74, ARG_ITEM ], ### UNTESTED
134             put_with => [ 75, ARG_ITEMITEM ],
135             look2 => [ 76, ARG_NONE ], ### UNTESTED
136             # Same as 64 in ScottFree
137             decrease_counter => [ 77, ARG_NONE ],
138             print_counter => [ 78, ARG_NONE ],
139             set_counter => [ 79, ARG_NUM ],
140             swap_loc_default => [ 80, ARG_NONE ],
141             select_counter => [ 81, ARG_NUM ], ### UNTESTED
142             # Current counter is swapped with specified backup counter
143             add_counter => [ 82, ARG_NUM ], ### UNTESTED
144             subtract_counter => [ 83, ARG_NUM ], ### UNTESTED
145             print_noun => [ 84, ARG_NONE ],
146             print_noun_nl => [ 85, ARG_NONE ],
147             nl => [ 86, ARG_NONE ],
148             swap_loc => [ 87, ARG_NUM ],
149             pause => [ 88, ARG_NONE ],
150             special => [ 89, ARG_NUM ],
151             # This is special -- see ../../../../scottfree/Definition
152             );
153              
154              
155             sub compile {
156 65     65 0 76 my $this = shift();
157 65         75 my($game) = @_;
158              
159 65         109 my $verb = $game->resolve_verb($this->verb());
160 65         168 my $noun = $this->noun();
161 65 100       130 if ($verb == 0) {
162             # This is a %occur, so the noun is a percentage probability
163 18 50       37 $noun = 100 if !$noun;
164             } else {
165 47         125 $noun = $game->resolve_noun($noun);
166             }
167              
168 65         122 my @condval = ( 150*$verb + $noun );
169 65         67 foreach my $cond (@{ $this->{cond} }) {
  65         149  
170 59         137 my($opcode, $arg) = _lookup($game, $cond, 'condition', \%_cond);
171 59 50       134 $arg = 0 if !defined $arg;
172 59         150 push @condval, $opcode + 20*$arg;
173             }
174              
175 65 50       151 die "Oops! SA format doesn't support >5 conditions in an action"
176             if @condval > 6;
177              
178             # Now gather results, with parameters going on the end of @condval
179             #warn "handling results:\n" . join ('', map {"\t$_\n"}
180             # @{ $this->{res} });
181 65         69 my @resval;
182 65         66 foreach my $res (@{ $this->{res} }) {
  65         136  
183 121         272 my($opcode, @arg) = _lookup($game, $res, 'result', \%_res);
184 121         372 push @resval, [ $opcode, @arg ];
185             }
186              
187             # Right. This is slightly tricky. We now want to pack all the
188             # results, together with their parameters, into as few action
189             # octuplets as possible. We have four result slots available in
190             # the first one, together with zero or more parameter slots
191             # remaining in the condition area; thereafter, each action
192             # octuplet offers four more result slots together with five
193             # parameter slots in the condition area (which of course is one
194             # more than we'll ever need.)
195 65         89 my @conds; # list of completed octuplets
196 65         85 my $argslot = @condval; # 0-based index within current octuplet
197 65         76 my $resslot = 0; # 0-based index into "virtual array"
198 65         146 push @condval, map { 0 } 1..(8-@condval);
  396         578  
199              
200 65         180 for (my $i = 0; $i < @resval; $i++) {
201 121         173 my $res = $resval[$i];
202 121         187 my($opcode, @arg) = @$res;
203 121         239 @arg = grep { defined } @arg;
  60         167  
204              
205             ### Seems like 6 in next line should be 5. Think harder.
206 121 100 33     697 if ($argslot + @arg > 6 || $resslot == 4 ||
      100        
      33        
207             ($resslot == 3 && $i < @resval-1)) {
208             # Current octuplet is full: skip to next
209 6         11 my $cindex = 6 + int($resslot/2);
210 6 50       15 $condval[$cindex] +=
211             ($resslot % 2 == 0 ? 150 : 1) * 73;
212 6         27 push @conds, join(' ', @condval);
213 6         13 @condval = map { 0 } 1..8;
  48         74  
214 6         9 $argslot = 1; # because slot 0 holds verb & noun
215 6         9 $resslot = 0;
216             }
217              
218 121         206 my $cindex = 6 + int($resslot/2);
219 121 100       246 $condval[$cindex] +=
220             ($resslot % 2 == 0 ? 150 : 1) * $opcode;
221 121         128 $resslot++;
222 121         288 foreach my $arg (@arg) {
223 60 50       112 if (!defined $arg) {
224 0         0 print STDERR "", "arg in '@arg' (", scalar(@arg), ") undef\n";
225             }
226 60         92 $condval[$argslot] = 20*$arg;
227 60         282 $argslot++;
228             }
229             }
230              
231 65         304 push @conds, join(' ', @condval);
232             #print STDERR "", "returning conds: ", join(' -- ', @conds), "\n";
233 65         328 return @conds;
234             }
235              
236              
237             sub _lookup {
238 180     180   260 my($game, $text, $caption, $href) = @_;
239              
240 180         304 $text =~ s/^\s+//;
241 180         462 my($op, $arg) = split /\s+/, $text, 2;
242 180 100       371 if ($op eq 'msg') {
243             # This check is a hack, but does spot an otherwise subtle bug
244 54 50       101 die "Oops! `msg' used as a condition (missing %result line?)"
245             if $caption eq 'condition';
246              
247 54         143 my $mnum = $game->resolve_message($arg);
248 54 50       252 return ($mnum <= 51 ? $mnum : $mnum+50);
249             }
250              
251 126         187 my $ref = $href->{$op};
252 126 50       230 die "unrecognised $caption op '$op'"
253             if !defined $ref;
254              
255 126         177 my($opcode, $argtype) = @$ref;
256 126 100       205 if ($argtype == ARG_NONE) {
    100          
    100          
    100          
    50          
    0          
257 8         22 return ($opcode);
258             } elsif ($argtype == ARG_NUM) {
259             # Numeric argument already has the right numeric value.
260             } elsif ($argtype == ARG_ROOM) {
261 21         57 $arg = $game->resolve_room($arg, 'action');
262             } elsif ($argtype == ARG_ITEM) {
263 66         174 $arg = $game->resolve_item($arg, 'action');
264             } elsif ($argtype == ARG_ITEMROOM) {
265 1         3 my($arg1, $arg2) = split /\s+/, $arg, 2;
266 1         4 $arg1 = $game->resolve_item($arg1, 'action');
267 1         4 $arg2 = $game->resolve_room($arg2, 'action');
268 1         10 return ($opcode, $arg1, $arg2);
269             } elsif ($argtype == ARG_ITEMITEM) {
270 0         0 my($arg1, $arg2) = split /\s+/, $arg, 2;
271 0         0 $arg1 = $game->resolve_item($arg1, 'action');
272 0         0 $arg2 = $game->resolve_item($arg2, 'action');
273 0         0 return ($opcode, $arg1, $arg2);
274             } else {
275 0         0 die "unsupported argument type $argtype for op '$op'";
276             }
277              
278 117         299 return ($opcode, $arg);
279             }
280              
281              
282             1;