4 # c4: Chip's Challenge Combined Converter
6 # Use "perldoc c4" to read the documentation.
8 # Copyright (C) 2003-2006 Brian Raiter. This program is licensed under
9 # an MIT-style license. Please see the documentation for details.
15 # First, some global functions used across packages.
20 # All the names of all the tiles.
25 ([ "empty", "floor" ],
27 [ "ic chip", "computer chip" ],
30 [ "hidden wall", "invisible wall permanent", "inv wall permanent" ],
31 [ "wall north", "partition north", "blocked north" ],
32 [ "wall west", "partition west", "blocked west" ],
33 [ "wall south", "partition south", "blocked south" ],
34 [ "wall east", "partition east", "blocked east" ],
35 [ "block", "moveable block", "movable block" ],
38 [ "force floor south", "force south", "slide south",
39 "slide floor south" ],
40 [ "block north", "cloning block north" ],
41 [ "block west", "cloning block west" ],
42 [ "block south", "cloning block south" ],
43 [ "block east", "cloning block east" ],
44 [ "force floor north", "force north", "slide north",
45 "slide floor north" ],
46 [ "force floor east", "force east", "slide east", "slide floor east" ],
47 [ "force floor west", "force west", "slide west", "slide floor west" ],
49 [ "blue door", "door blue" ],
50 [ "red door", "door red" ],
51 [ "green door", "door green" ],
52 [ "yellow door", "door yellow" ],
53 [ "ice wall southeast", "ice wall se", "ice se",
54 "ice corner southeast", "ice corner se" ],
55 [ "ice wall southwest", "ice wall sw", "ice sw",
56 "ice corner southwest", "ice corner sw" ],
57 [ "ice wall northwest", "ice wall nw", "ice nw",
58 "ice corner northwest", "ice corner nw" ],
59 [ "ice wall northeast", "ice wall ne", "ice ne",
60 "ice corner northeast", "ice corner ne" ],
61 [ "blue block floor", "blue block fake", "blue wall fake" ],
62 [ "blue block wall", "blue block real", "blue wall real" ],
66 [ "green button", "button green", "toggle button", "button toggle" ],
67 [ "red button", "button red", "clone button", "button clone" ],
68 [ "toggle closed", "toggle wall closed", "closed toggle wall",
69 "toggle door closed", "closed toggle door" ],
70 [ "toggle open", "toggle wall open", "open toggle wall",
71 "toggle door open", "open toggle door" ],
72 [ "brown button", "button brown", "trap button", "button trap" ],
73 [ "blue button", "button blue", "tank button", "button tank" ],
76 [ "trap", "beartrap", "bear trap" ],
77 [ "invisible wall", "invisible wall temporary", "inv wall temporary" ],
79 [ "popup wall", "pass once" ],
81 [ "wall southeast", "partition southeast", "blocked southeast",
82 "wall se", "partition se", "blocked se" ],
83 [ "clone machine", "cloner", "cloning machine" ],
84 [ "force floor any", "force any", "slide any", "slide floor any",
85 "force floor random", "force random",
86 "slide random", "slide floor random",
87 "random slide floor" ],
97 [ "(chip swimming north)", "(chip swimming n)" ],
98 [ "(chip swimming west)", "(chip swimming w)" ],
99 [ "(chip swimming south)", "(chip swimming s)" ],
100 [ "(chip swimming east)", "(chip swimming e)" ],
101 [ "bug north", "bee north" ],
102 [ "bug west", "bee west" ],
103 [ "bug south", "bee south" ],
104 [ "bug east", "bee east" ],
105 [ "fireball north", "flame north" ],
106 [ "fireball west", "flame west" ],
107 [ "fireball south", "flame south" ],
108 [ "fireball east", "flame east" ],
117 [ "glider north", "ghost north" ],
118 [ "glider west", "ghost west" ],
119 [ "glider south", "ghost south" ],
120 [ "glider east", "ghost east" ],
121 [ "teeth north", "frog north" ],
122 [ "teeth west", "frog west" ],
123 [ "teeth south", "frog south" ],
124 [ "teeth east", "frog east" ],
125 [ "walker north", "dumbbell north" ],
126 [ "walker west", "dumbbell west" ],
127 [ "walker south", "dumbbell south" ],
128 [ "walker east", "dumbbell east" ],
133 [ "paramecium north", "centipede north" ],
134 [ "paramecium west", "centipede west" ],
135 [ "paramecium south", "centipede south" ],
136 [ "paramecium east", "centipede east" ],
137 [ "blue key", "key blue" ],
138 [ "red key", "key red" ],
139 [ "green key", "key green" ],
140 [ "yellow key", "key yellow" ],
141 [ "water boots", "boots water", "water shield", "flippers" ],
142 [ "fire boots", "boots fire", "fire shield" ],
143 [ "ice boots", "boots ice", "spike shoes", "spiked shoes",
144 "ice skates", "skates" ],
145 [ "force boots", "boots force", "slide boots", "boots slide",
146 "magnet", "suction boots" ],
152 push @tilenames, $names->[0];
153 @tilenames{@$names} = ($#tilenames) x @$names;
156 # The original 150 passwords.
158 my @origpasswords = @{
159 [qw(BDHP JXMJ ECBQ YMCJ TQKB WNLP FXQO NHAG
160 KCRE VUWS CNPE WVHI OCKS BTDY COZQ SKKK
161 AJMG HMJL MRHR KGFP UGRW WZIN HUVE UNIZ
162 PQGV YVYJ IGGZ UJDD QGOL BQZP RYMS PEFS
163 BQSN NQFI VDTM NXIS VQNK BIFA ICXY YWFH
164 GKWD LMFU UJDP TXHL OVPZ HDQJ LXPP JYSF
165 PPXI QBDH IGGJ PPHT CGNX ZMGC SJES FCJE
166 UBXU YBLT BLDM ZYVI RMOW TIGW GOHX IJPQ
167 UPUN ZIKZ GGJA RTDI NLLY GCCG LAJM EKFT
168 QCCR MKNH MJDV NMRH FHIC GRMO JINU EVUG
169 SCWF LLIO OVPJ UVEO LEBX FLHH YJYS WZYV
170 VCZO OLLM JPQG DTMI REKF EWCS BIFQ WVHY
171 IOCS TKWD XUVU QJXR RPIR VDDU PTAC KWNL
172 YNEG NXYB ECRE LIOC KZQR XBAO KRQJ NJLA
173 PTAS JWNL EGRW HXMF FPZT OSCW PHTY FLXP
174 BPYS SJUM YKZE TASX MYRT QRLD JMWZ FTLA
175 HEAN XHIZ FIRD ZYFA TIGG XPPH LYWO LUZL
176 HPPX LUJT VLHH SJUK MCJE UCRY OKOR GVXQ
177 YBLI JHEN COZA RGSK DIGW GNLP)]
180 # Return true if the given tile is one of the creatures, one of the
183 sub iscreature($) { $_[0] >= 0x40 && $_[0] < 0x64 }
184 sub isblock($) { $_[0] == 0x0A || ($_[0] >= 0x0E && $_[0] < 0x12) }
185 sub ischip($) { $_[0] >= 0x6C && $_[0] < 0x70 }
187 my $filename = undef;
189 my $filelevel = undef;
192 if (defined $filename) {
193 if (defined $filelevel) {
194 print STDERR "$filename: level $filelevel: ";
195 } elsif (defined $filepos) {
196 print STDERR "$filename, byte $filepos: ";
198 print STDERR "$filename:$.: ";
200 print STDERR "$filename: ";
203 if (defined $filelevel) {
204 print STDERR "$filename: level $filelevel: ";
205 } elsif (defined $filepos) {
206 print STDERR "byte $filepos: ";
208 print STDERR "line $.: ";
211 print STDERR @_, "\n";
215 # Given a pack template, return the size of the packed data in bytes.
216 # The template is assumed to only contain the types a, C, v, and V.
220 my $template = shift;
222 while (length $template) {
223 my $char = substr $template, 0, 1, "";
224 my $n = $char eq "V" ? 4 : $char eq "v" ? 2 : 1;
225 $n *= $1 if $template =~ s/\A(\d+)//;
231 # Read a sequence of bytes from a binary file, according to a pack
232 # template. The unpacked values are returned.
237 my $template = shift;
238 my $levelsize = shift;
240 $len = ::packlen $template;
241 return ::err "invalid template given to fileread" unless $len > 0;
242 my $ret = sysread $input, $buf, $len;
243 return ::err $! unless defined $ret;
244 return ::err "unexpected EOF" unless $ret;
247 if (ref $levelsize) {
248 return ::err "invalid metadata in data file",
249 " (expecting $len bytes; found only $$levelsize)"
250 unless $len <= $$levelsize;
253 my (@fields) = (unpack $template, $buf);
254 foreach my $field (@fields) {
258 return ::err "invalid data in data file"
259 if defined $min && $field < $min or defined $max && $field > $max;
261 return wantarray ? @fields : $fields[-1];
264 # Translate escape sequences in the given string.
269 s/\\([0-7][0-7][0-7])/chr oct$1/eg;
278 s/([^\020-\176])/sprintf"\\%03o",ord$1/eg;
282 # Take a standard creature list from a dat file and augment it as
283 # necessary for a Lynx-based file format. This involves adding entries
284 # for Chip, blocks, immobile creatures, and creatures on clone
287 sub makelynxcrlist($$)
290 my $datcreatures = shift;
294 if (defined $datcreatures) {
295 foreach my $n (0 .. $#$datcreatures) {
296 $listed[$datcreatures->[$n][0]][$datcreatures->[$n][1]] = $n;
301 foreach my $y (0 .. 31) {
302 foreach my $x (0 .. 31) {
303 my $obj = $map->[$y][$x][0];
304 next unless ::iscreature $obj || ::isblock $obj || ::ischip $obj;
305 my ($seq, $ff, $mobile) = (0, 0, 1);
307 return "multiple Chips present" if defined $chip;
309 } elsif (::isblock $obj) {
310 $mobile = -1 if $map->[$y][$x][1] == $tilenames{"cloner"};
312 if ($map->[$y][$x][1] == $tilenames{"cloner"}) {
315 $mobile = defined $listed[$y][$x] ? 1 : 0;
317 $seq = $listed[$y][$x] + 1 if defined $listed[$y][$x];
319 push @crlist, [ $seq, $y, $x, $mobile ];
322 return "Chip absent" unless defined $chip;
323 return "over 128 creatures" if @crlist > 128;
324 ($crlist[$chip], $crlist[0]) = ($crlist[0], $crlist[$chip]);
327 foreach my $n (0 .. $#crlist) { push @sortlist, $n if $crlist[$n][0] }
328 @sortlist = sort { $crlist[$a][0] <=> $crlist[$b][0] } @sortlist;
331 foreach my $n (0 .. $#crlist) {
332 my $creature = $crlist[$n];
333 $creature = $crlist[shift @sortlist] if $creature->[0];
334 push @lynxcreatures, [ $creature->[1],
339 return \@lynxcreatures;
342 # Translate a creature list from a lynx-based file format to one
343 # appropriate for a dat-based file format.
345 sub makedatcrlist($$)
348 my $lynxcreatures = shift;
351 return undef unless defined $lynxcreatures;
353 foreach my $creature (@$lynxcreatures) {
354 next if $creature->[2] != 1;
355 next if ::ischip $map->[$creature->[0]][$creature->[1]][0];
356 next if ::isblock $map->[$creature->[0]][$creature->[1]][0];
357 push @crlist, [ $creature->[0], $creature->[1] ];
364 # The textual source file format
369 # The list of default tile symbols.
371 my %tilesymbols = %{{
372 " " => $tilenames{"empty"},
373 "#" => $tilenames{"wall"},
374 "\$" => $tilenames{"ic chip"},
375 "," => $tilenames{"water"},
376 "&" => $tilenames{"fire"},
377 "~" => $tilenames{"wall north"},
378 "|" => $tilenames{"wall west"},
379 "_" => $tilenames{"wall south"},
380 " |" => $tilenames{"wall east"},
381 "[]" => $tilenames{"block"},
382 "[" => $tilenames{"block"},
383 ";" => $tilenames{"dirt"},
384 "=" => $tilenames{"ice"},
385 "v" => $tilenames{"force south"},
386 "^" => $tilenames{"force north"},
387 ">" => $tilenames{"force east"},
388 "<" => $tilenames{"force west"},
389 "E" => $tilenames{"exit"},
390 "H" => $tilenames{"socket"},
391 "6" => $tilenames{"bomb"},
392 ":" => $tilenames{"gravel"},
393 "?" => $tilenames{"hint button"},
394 "_|" => $tilenames{"wall southeast"},
395 "<>" => $tilenames{"force any"},
396 "@" => $tilenames{"chip south"},
397 "^]" => [ $tilenames{"cloning block north"}, $tilenames{"clone machine"} ],
398 "<]" => [ $tilenames{"cloning block west"}, $tilenames{"clone machine"} ],
399 "v]" => [ $tilenames{"cloning block south"}, $tilenames{"clone machine"} ],
400 ">]" => [ $tilenames{"cloning block east"}, $tilenames{"clone machine"} ]
407 # Error message display.
409 sub err(@) { warn "line $.: ", @_, "\n"; return; }
411 # The list of incomplete tile names recognized. Each incomplete name
412 # has a list of characters that complete them.
414 my %partialnames = %{{
415 "key" => { "blue key" => "b", "red key" => "r",
416 "green key" => "g", "yellow key" => "y" },
417 "door" => { "blue door" => "b", "red door" => "r",
418 "green door" => "g", "yellow door" => "y" },
419 "bug" => { "bug north" => "n", "bug west" => "w",
420 "bug south" => "s", "bug east" => "e" },
421 "bee" => { "bee north" => "n", "bee west" => "w",
422 "bee south" => "s", "bee east" => "e" },
423 "fireball" => { "fireball north" => "n", "fireball west" => "w",
424 "fireball south" => "s", "fireball east" => "e" },
425 "flame" => { "flame north" => "n", "flame west" => "w",
426 "flame south" => "s", "flame east" => "e" },
427 "ball" => { "ball north" => "n", "ball west" => "w",
428 "ball south" => "s", "ball east" => "e" },
429 "tank" => { "tank north" => "n", "tank west" => "w",
430 "tank south" => "s", "tank east" => "e" },
431 "glider" => { "glider north" => "n", "glider west" => "w",
432 "glider south" => "s", "glider east" => "e" },
433 "ghost" => { "ghost north" => "n", "ghost west" => "w",
434 "ghost south" => "s", "ghost east" => "e" },
435 "teeth" => { "teeth north" => "n", "teeth west" => "w",
436 "teeth south" => "s", "teeth east" => "e" },
437 "frog" => { "frog north" => "n", "frog west" => "w",
438 "frog south" => "s", "frog east" => "e" },
439 "walker" => { "walker north" => "n", "walker west" => "w",
440 "walker south" => "s", "walker east" => "e" },
441 "dumbbell" => { "dumbbell north" => "n", "dumbbell west" => "w",
442 "dumbbell south" => "s", "dumbbell east" => "e" },
443 "blob" => { "blob north" => "n", "blob west" => "w",
444 "blob south" => "s", "blob east" => "e" },
445 "paramecium"=> { "paramecium north" => "n", "paramecium west" => "w",
446 "paramecium south" => "s", "paramecium east" => "e" },
447 "centipede" => { "centipede north" => "n", "centipede west" => "w",
448 "centipede south" => "s", "centipede east" => "e" },
449 "chip" => { "chip north" => "n", "chip west" => "w",
450 "chip south" => "s", "chip east" => "e" },
452 => { "(swimming chip north)" => "n",
453 "(swimming chip west)" => "w",
454 "(swimming chip south)" => "s",
455 "(swimming chip east)" => "e" }
458 # The list of tile definitions that are defined throughout the set. A
459 # number of definitions are made by default at startup.
461 my %globaltiles = %tilesymbols;
463 # The list of tile definitions for a given level.
467 # Add a list of tile definitions to a hash.
471 my $tiledefs = shift;
472 while (my $def = shift) { $tiledefs->{$def->[0]} = $def->[1] }
475 # Given a string, return the tile with that name. If the name is not
476 # recognized, undef is returned and a error message is displayed.
478 sub lookuptilename($)
483 return $tilenames{$name} if exists $tilenames{$name};
485 if ($name =~ /^0x([0-9A-Fa-f][0-9A-Fa-f])$/) {
487 return $value if $value >= 0 && $value <= 255;
490 my $n = length $name;
491 foreach my $key (keys %tilenames) {
492 if ($name eq substr $key, 0, $n) {
493 return ::err "ambiguous object id \"$name\""
494 if defined $value && $value != $tilenames{$key};
495 $value = $tilenames{$key};
498 return ::err "unknown object id \"$name\"" unless defined $value;
502 # Given two characters, return the tile or pair of tiles which the
503 # characters represent. The characters can stand for a pair of tiles
504 # directly, or each character can independently represent one tile. In
505 # either case, a pair of tiles is returned as an array ref. A single
506 # tile is returned directly. If one or both characters are
507 # unrecognized, undef is returned and an error message is displayed.
513 $symbol =~ s/\A(.) \Z/$1/;
515 return $localtiles{$symbol} if exists $localtiles{$symbol};
516 return $globaltiles{$symbol} if exists $globaltiles{$symbol};
518 if (length($symbol) == 2) {
519 my $top = lookuptile substr $symbol, 0, 1;
520 if (defined $top && ref $top && $top->[1] < 0) {
522 } elsif (defined $top && !ref $top) {
523 my $bot = lookuptile substr $symbol, 1, 1;
524 if (defined $bot && !ref $bot) {
525 return [ $top, $bot ];
530 return ::err "unrecognized map tile \"$symbol\"";
533 # Return the number of chips present on the map.
540 foreach my $y (0 .. 31) {
541 foreach my $x (0 .. 31) {
542 ++$count if $map->[$y][$x][0] == 0x02;
543 ++$count if $map->[$y][$x][1] == 0x02;
549 # Given a completed map, return the default list of traps connections
550 # as an array ref. (The default list follows the original Lynx rules
551 # of connecting buttons to the first subsequent trap in reading
557 my $firsttrap = undef;
561 foreach my $y (0 .. 31) {
562 foreach my $x (0 .. 31) {
563 if ($map->[$y][$x][0] == 0x27 || $map->[$y][$x][1] == 0x27) {
564 push @buttons, [ $y, $x ];
565 } elsif ($map->[$y][$x][0] == 0x2B || $map->[$y][$x][1] == 0x2B) {
566 push @traps, map { { from => $_, to => [ $y, $x ] } } @buttons;
568 $firsttrap = [ $y, $x ] unless defined $firsttrap;
572 push @traps, map { { from => $_, to => $firsttrap } } @buttons
573 if @buttons && defined $firsttrap;
577 # Given a completed map, return the default list of clone machine
578 # connections as an array ref. (This function looks a lot like the
581 sub buildclonerlist($)
588 foreach my $y (0 .. 31) {
589 foreach my $x (0 .. 31) {
590 if ($map->[$y][$x][0] == 0x24 || $map->[$y][$x][1] == 0x24) {
591 push @buttons, [ $y, $x ];
592 } elsif ($map->[$y][$x][0] == 0x31 || $map->[$y][$x][1] == 0x31) {
593 push @cms, map { { from => $_, to => [ $y, $x ] } } @buttons;
595 $firstcm = [ $y, $x ] unless defined $firstcm;
599 push @cms, map { { from => $_, to => $firstcm } } @buttons
600 if @buttons && defined $firstcm;
604 # Given a completed map, return the default ordering of creatures as
605 # an array ref. (The default ordering is to first list the creatures
606 # in reading order, including Chip. Then, the first creature on the
607 # list swaps positions with Chip, who is then removed from the list.)
609 sub buildcreaturelist($$)
616 foreach my $y (0 .. 31) {
617 foreach my $x (0 .. 31) {
618 my $tile = $map->[$y][$x][0];
619 if (::iscreature $tile) {
620 push @crlist, [ $y, $x ];
621 } elsif (::isblock $tile) {
622 push @crlist, [ $y, $x, 0 ];
623 } elsif (::ischip $tile) {
625 push @crlist, [ $y, $x, 0 ];
629 if ($ruleset eq "lynx") {
630 ($crlist[0], $crlist[$chippos]) = ($crlist[$chippos], $crlist[0])
632 foreach my $item (@crlist) { $#$item = 1 }
634 if (defined $chippos && $chippos > 1) {
635 my $cr = shift @crlist;
636 $crlist[$chippos - 1] = $cr;
638 for (my $n = $#crlist ; $n >= 0 ; --$n) {
639 splice @crlist, $n, 1 if $#{$crlist[$n]} > 1;
646 # Compare two arrays of lines of text. Wherever the same pair of
647 # characters appears in same place in both arrays, the occurrence in
648 # the first array is replaced with spaces.
650 sub subtracttext(\@\@)
655 for (my $n = 0 ; $n < @$array && $n < @$remove ; ++$n) {
657 while ($m < length $array->[$n] && $m < length $remove->[$n]) {
658 my $a = substr $array->[$n], $m, 2;
659 my $b = substr $remove->[$n], $m, 2;
660 $a .= " " if length $a == 1;
661 $b .= " " if length $b == 1;
662 substr($array->[$n], $m, 2) = " " if $a eq $b;
668 # Interpret a textual description of a section of the map. The
669 # interpreted map data is added to the map array passed as the first
670 # argument. The second and third arguments set the origin of the map
671 # section. The remaining arguments are the lines from the text file
672 # describing the map section. The return value is 1 if the
673 # interpretation is successful. If any part of the map sections cannot
674 # be understood, undef is returned and an error message is displayed.
681 return ::err "map extends below the 32nd row" if $y0 + @_ > 32;
682 for (my $y = $y0 ; @_ ; ++$y) {
684 return ::err "map extends beyond the 32nd column"
685 if $x0 + length($row) / 2 > 32;
686 for (my $x = $x0 ; length $row ; ++$x) {
687 my $cell = lookuptile substr $row, 0, 2;
688 return ::err "unrecognized tile at ($x $y)" unless defined $cell;
689 return unless defined $cell;
691 if ($cell->[1] < 0) {
692 $map->[$y][$x] = [ $cell, 0x00 ];
694 $map->[$y][$x] = $cell;
697 $map->[$y][$x] = [ $cell, 0x00 ];
699 substr($row, 0, 2) = "";
705 # Interpret a textual overlay section. The first argument is the
706 # level's hash ref. The second and third arguments set the origin of
707 # the overlay section. The remaining arguments are the lines from the
708 # text file describing the overlay. The return value is 1 if the
709 # interpretation is successful. If any part of the overlay section
710 # cannot be understood, undef is returned and an error message is
719 return ::err "overlay extends below the 32nd row" if $y0 + @_ > 32;
720 for (my $y = $y0 ; @_ ; ++$y) {
722 return ::err "overlay extends beyond the 32nd column"
723 if $x0 + length($row) / 2 > 32;
724 for (my $x = $x0 ; length $row ; ++$x) {
725 $_ = substr $row, 0, 1, "";
726 push @{$symbols{$_}}, [ $y, $x ] unless $_ eq " " || $_ eq "";
727 $_ = substr $row, 0, 1, "";
728 push @{$symbols{$_}}, [ $y, $x ] unless $_ eq " " || $_ eq "";
732 foreach my $symbol (sort keys %symbols) {
733 my $list = $symbols{$symbol};
735 my ($y, $x) = ($list->[0][0], $list->[0][1]);
736 my $cell = $data->{map}[$y][$x];
737 return ::err "no creature under \"$symbol\" at ($x $y)"
738 unless defined $cell &&
739 (::iscreature $cell->[0] || ::iscreature $cell->[1]);
740 push @{$data->{creatures}}, [ $y, $x ];
742 my $linktype = undef;
745 foreach my $pos (@$list) {
746 my ($y, $x) = ($pos->[0], $pos->[1]);
747 my $cell = $data->{map}[$y][$x];
748 my $obj = $cell->[1] || $cell->[0];
749 if ($obj == $tilenames{"red button"}) {
751 push @from, [ $y, $x ];
752 } elsif ($obj == $tilenames{"brown button"}) {
754 push @from, [ $y, $x ];
755 } elsif ($obj == $tilenames{"clone machine"}) {
757 return ::err "clone machine under \"$symbol\" at ($x $y) ",
758 "wired to non-button at ($to->[1] $to->[0])"
761 } elsif ($obj == $tilenames{"beartrap"}) {
763 return ::err "beartrap under \"$symbol\" at ($x $y) ",
764 "wired to non-button at ($to->[1] $to->[0])"
768 return ::err "no button/trap/clone machine ",
769 "under \"$symbol\" at ($x $y)";
772 return ::err "inconsistent connection ",
773 "under \"$symbol\" at ($x $y)"
774 unless $linktype eq $type;
776 push @{$data->{$linktype}},
777 map { { from => $_, to => $to } } @from;
783 # Interpret a tile definition. Given a line of text supplying the tile
784 # definition, the function returns an array ref. Each element in the
785 # array is a pair: the first element gives the character(s), and the
786 # second element supplies the tile(s). If the definition is ambiguous
787 # or invalid, undef is returned and an error message is displayed.
792 $def =~ s/^(\S\S?)\t//
793 or return ::err "syntax error in tile defintion \"$def\"";
799 if ($def =~ /^([^\+]*[^\+\s])\s*\+\s*([^\+\s][^\+]*)$/) {
800 my ($def1, $def2) = ($1, $2);
802 $tile1 = lookuptilename $def1;
803 return unless defined $tile1;
804 if (lc $def2 eq "pos") {
805 return ::err "ordered tile definition \"$symbol\" ",
806 "must be a single character"
807 unless length($symbol) == 1;
810 $tile2 = lookuptilename $def2;
811 return unless defined $tile2;
813 return [ [ $symbol, [ $tile1, $tile2 ] ] ];
817 if (exists $partialnames{$def}) {
818 return ::err "incomplete tile definition \"$symbol\" ",
819 "must be a single character"
820 unless length($symbol) == 1;
821 foreach my $comp (keys %{$partialnames{$def}}) {
822 push @defs, [ $symbol . $partialnames{$def}{$comp},
828 my $tile = lookuptilename $def;
829 return [ [ $symbol, $tile ] ] if defined $tile;
833 # Given a handle to a text file, read the introductory lines that
834 # precede the first level definition, if any, and return a hash ref
835 # for storing the level set. If an error occurs, undef is returned and
836 # an error message is displayed.
841 my $data = { ruleset => "lynx" };
842 my $slurpingdefs = undef;
847 if (defined $slurpingdefs) {
848 if (/^\s*[Ee][Nn][Dd]\s*$/) {
851 my $def = parsetiledef $_;
853 addtiledefs %globaltiles, @$def;
856 } elsif (/^\s*[Tt][Ii][Ll][Ee][Ss]\s*$/) {
862 next if /^\s*$/ || /^%/;
864 /^\s*(\S+)\s+(\S(?:.*\S)?)\s*$/ or return ::err "syntax error";
865 my ($name, $value) = ($1, $2);
867 if ($name eq "ruleset") {
869 return ::err "invalid ruleset \"$value\""
870 unless $value =~ /^(lynx|ms)$/;
871 $data->{ruleset} = $value;
872 } elsif ($name eq "maxlevel") {
873 return ::err "invalid maximum level \"$value\""
874 unless $value =~ /\A\d+\Z/ && $value < 65536;
875 $data->{maxlevel} = $value;
877 return ::err "invalid statement \"$name\"";
881 return ::err "unclosed definition section" if $slurpingdefs;
885 # Given a handle to a text file, positioned at the start of a level
886 # description, parse the lines describing the level and return a hash
887 # ref containing the level data. If the end of the file is encountered
888 # before a level description is found, false is returned. If any
889 # errors are encountered, undef is returned and an error message is
897 my %data = (number => $number, leveltime => 0);
898 my $seenanything = undef;
899 my $slurpingdefs = undef;
900 my $slurpingmap = undef;
904 $data{passwd} = $origpasswords[$number - 1]
905 if $number >= 1 && $number <= 150;
907 for my $y (0 .. 31) {
908 for my $x (0 .. 31) { $data{map}[$y][$x] = [ 0, 0 ] }
914 if (defined $slurpingdefs) {
915 if (/^\s*[Ee][Nn][Dd]\s*$/) {
918 my $def = parsetiledef $_;
920 addtiledefs %localtiles, @$def;
923 } elsif (defined $slurpingmap) {
924 if (/^\s*([AEae])[Nn][Dd]\s*$/) {
925 my $overlay = lc($1) eq "a";
926 if ($slurpingmap->[2] >= 0) {
927 my @overlaytext = splice @maptext, $slurpingmap->[2];
928 return ::err "overlay section is taller than map section"
929 if @overlaytext > @maptext;
930 subtracttext @overlaytext, @maptext;
931 return unless parsecon \%data,
936 $slurpingmap->[2] = @maptext;
937 return unless parsemap $data{map},
947 1 while s{^([^\t]*)\t}{$1 . (" " x (8 - length($1) % 8))}e;
951 } elsif (/^\s*[Tt][Ii][Ll][Ee][Ss]\s*$/) {
954 } elsif (/^\s*[Mm][Aa][Pp]\s*(?:(\d+)\s+(\d+)\s*)?$/) {
955 $slurpingmap = [ $2 || 0, $1 || 0, -1 ];
957 } elsif (/^\s*[Mm][Aa][Pp]/) {
958 return ::err "invalid syntax following \"map\"";
959 } elsif (/^\s*[Tt][Rr][Aa][Pp][Ss]\s*$/) {
960 $data{traps} ||= [ ];
962 } elsif (/^\s*[Cc][Ll][Oo][Nn][Ee][Rr][Ss]\s*$/) {
963 $data{cloners} ||= [ ];
965 } elsif (/^\s*[Cc][Rr][Ee][Aa][Tt][Uu][Rr][Ee][Ss]\s*$/) {
966 $data{creatures} ||= [ ];
971 next if /^\s*$/ || /^%/;
974 /^\s*(\S+)\s+(\S(?:.*\S)?)\s*$/ or return ::err "syntax error";
975 my ($name, $value) = ($1, $2);
977 if ($name eq "level") {
978 return ::err "invalid level number \"$value\""
979 unless $value =~ /\A\d+\Z/ && $value < 65536;
980 $data{number} = $value;
981 } elsif ($name eq "time") {
982 return ::err "invalid level time \"$value\""
983 unless $value =~ /\A\d+\Z/ && $value < 65536;
984 $data{leveltime} = $value;
985 } elsif ($name eq "chips") {
986 return ::err "invalid chip count \"$value\""
987 unless $value =~ /\A\d+\Z/ && $value < 65536;
988 $data{chips} = $value;
989 } elsif ($name eq "title" || $name eq "name") {
990 $value = ::unescape $value if $value =~ s/\A\"(.*)\"\Z/$1/;
991 $data{title} .= " " if defined $data{title};
992 $data{title} .= $value;
993 } elsif ($name eq "password" || $name eq "passwd") {
994 return ::err "invalid password \"$value\""
995 unless $value =~ /\A[A-Z][A-Z][A-Z][A-Z]\Z/;
996 $data{passwd} = $value;
997 } elsif ($name eq "hint") {
998 $value = ::unescape $value if $value =~ s/\A\"(.*)\"\Z/$1/;
999 $data{hint} .= " " if defined $data{hint};
1000 $data{hint} .= $value;
1001 } elsif ($name eq "traps") {
1002 $data{traps} ||= [ ];
1003 while ($value =~ s/\A\s* (\d+)\s+(\d+) \s*[-=]?>\s*
1004 (\d+)\s+(\d+) (?:\s*[,;])?//x) {
1005 push @{$data{traps}}, { from => [ $2, $1 ],
1008 return ::err "syntax error in trap list at \"$value\""
1009 if $value && $value !~ /\A[,;]\Z/;
1010 } elsif ($name eq "cloners") {
1011 $data{cloners} ||= [ ];
1012 while ($value =~ s/\A\s* (\d+)\s+(\d+) \s*[-=]?>\s*
1013 (\d+)\s+(\d+) (?:\s*[,;])?//x) {
1014 push @{$data{cloners}}, { from => [ $2, $1 ],
1017 return ::err "syntax error in clone machine list at \"$value\""
1018 if $value && $value !~ /\A[,;]\Z/;
1019 } elsif ($name eq "creatures") {
1020 $data{creatures} ||= [ ];
1021 while ($value =~ s/\A\s* (\d+)\s+(\d+) (?:\s*[,;])?//x) {
1022 push @{$data{creatures}}, [ $2, $1 ];
1024 return ::err "syntax error in creature list at \"$value\""
1025 if $value && $value !~ /\A[,;]\Z/;
1026 } elsif ($name eq "border") {
1027 my $cell = lookuptile $value;
1028 return unless defined $cell;
1029 $cell = [ $cell, 0x00 ] unless ref $cell;
1030 foreach my $y (0 .. 31) { $data{map}[$y][0] = [ @$cell ] }
1031 foreach my $y (0 .. 31) { $data{map}[$y][31] = [ @$cell ] }
1032 foreach my $x (1 .. 30) { $data{map}[0][$x] = [ @$cell ] }
1033 foreach my $x (1 .. 30) { $data{map}[31][$x] = [ @$cell ] }
1034 } elsif ($name eq "field") {
1035 return ::err "invalid field spec \"$value\""
1036 unless $value =~ /^(\d+)\s+(\d+(?:\s+\d+)*)$/;
1037 my ($num, $data) = ($1, $2);
1038 return ::err "multiple specs for field $num"
1039 if exists $data{fields}{$num};
1040 $data{fields}{$num} = join "", map { chr } split " ", $data;
1042 return ::err "invalid command \"$name\"";
1045 return "" unless $seenanything;
1047 return ::err "unclosed defs section" if $slurpingdefs;
1048 return ::err "unclosed map section" if $slurpingmap;
1050 return ::err "missing level title" unless exists $data{title};
1051 return ::err "missing password" unless exists $data{passwd};
1052 return ::err "missing level map" unless exists $data{map};
1054 $data{chips} = getchipcount $data{map} unless exists $data{chips};
1055 $data{traps} ||= buildtraplist $data{map};
1056 $data{cloners} ||= buildclonerlist $data{map};
1057 $data{creatures} ||= buildcreaturelist $data{map}, $ruleset;
1058 $data{lynxcreatures} = ::makelynxcrlist $data{map}, $data{creatures};
1059 $data{fields} ||= { };
1061 return ::err "title too long (", length($data{title}), "); ",
1062 "254 is the maximum length allowed"
1063 if length($data{title}) > 254;
1064 return ::err "hint too long (", length($data{hint}), "); ",
1065 "254 is the maximum length allowed"
1066 if exists $data{hint} && length($data{hint}) > 254;
1067 return ::err "too many (", scalar(@{$data{traps}}), ") ",
1068 "trap connections; 25 is the maximum allowed"
1069 if @{$data{traps}} > 25;
1070 return ::err "too many (", scalar(@{$data{cloners}}), ") ",
1071 "clone machine connections; 31 is the maximum allowed"
1072 if @{$data{cloners}} > 31;
1073 return ::err "too many (", scalar(@{$data{creatures}}), ") ",
1074 "creatures; 127 is the maximum allowed"
1075 if @{$data{creatures}} > 127;
1080 # This function takes a handle to a text file and returns a hash ref
1081 # containing the described level set. If the file could not be
1082 # completely translated, undef is returned and one or more error
1083 # messages will be displayed.
1090 $data = parseheader $input;
1091 return unless $data;
1095 my $level = parselevel $input, $data->{ruleset}, $lastnumber + 1;
1096 return unless defined $level;
1098 $lastnumber = $level->{number};
1099 push @{$data->{levels}}, $level;
1103 $#{$data->{levels}} = $data->{maxlevel} - 1
1104 if exists $data->{maxlevel} && $data->{maxlevel} < @{$data->{levels}};
1116 $globalsymbols{"0"}[1] = " ";
1117 $globalsymbols{"0"}[2] = " ";
1118 $globalsymbols{"0:0"}[1] = " ";
1119 $globalsymbols{"0:0"}[2] = " ";
1120 foreach my $symbol (keys %tilesymbols) {
1122 if (ref $tilesymbols{$symbol}) {
1123 $key = "$tilesymbols{$symbol}[0]:$tilesymbols{$symbol}[1]";
1125 $key = $tilesymbols{$symbol};
1127 $globalsymbols{$key}[length $symbol] ||= $symbol;
1137 my @segments = split /(\S\s\S)/, ::escape shift;
1139 push @segments, "" if @segments % 2 == 0;
1140 for (my $n = 1 ; $n < $#segments; ++$n) {
1141 $segments[$n - 1] .= substr($segments[$n], 0, 1);
1142 $segments[$n] = substr($segments[$n], 2, 1) . $segments[$n + 1];
1143 splice @segments, $n + 1, 1;
1146 my $width = 75 - length $prefix;
1147 my $line = shift @segments;
1149 if (!$line || length($line) + length($segments[0]) < $width) {
1150 $line .= " " . shift @segments;
1152 $line = "\"$line\"" if $line =~ /\\/;
1153 print $output "$prefix $line\n";
1154 $line = shift @segments;
1157 $line = "\"$line\"" if $line =~ /\\/ || $line =~ /^\s/ || $line =~ /\s$/;
1158 print $output "$prefix $line\n";
1170 local $_ = "$prefix $item";
1172 print $output $_ or return;
1174 $x += 3 + length $_[0];
1177 print $output " ; $item" or return;
1179 print $output "\n" or return;
1187 my $max = shift || 2;
1189 return $globalsymbols{$tile}[$max] if defined $globalsymbols{$tile}[$max];
1190 return $globalsymbols{$tile}[1] if defined $globalsymbols{$tile}[1];
1191 return $localsymbols{$tile}[$max] if defined $localsymbols{$tile}[$max];
1192 return $localsymbols{$tile}[1] if defined $localsymbols{$tile}[1];
1196 sub getnewsym() { shift @symbollist }
1200 @symbollist = split //,
1201 "ABCDFGIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345789@*+.,'`-!";
1207 my $bot = shift || 0;
1211 return " " if $top == 0 && $bot == 0;
1213 $tile = $bot ? "$top:$bot" : $top;
1214 $symbol = tilesymbol $tile;
1215 if (defined $symbol) {
1216 $symbol = "$symbol " if length($symbol) == 1;
1222 $symbol = tilesymbol $bot, 1;
1223 return " $symbol" if defined $symbol;
1225 my $st = tilesymbol $top, 1;
1227 my $sb = tilesymbol $bot, 1;
1228 return "$st$sb" if defined $sb;
1233 $symbol = getnewsym;
1234 unless (defined $symbol) {
1235 ::err "too many unique tile combinations required";
1238 $localsymbols{$tile}[length $symbol] = $symbol;
1240 $symbol = "$symbol " if length($symbol) == 1;
1251 foreach my $y (0 .. 31) {
1252 foreach my $x (0 .. 31) {
1253 next if $map->[$y][$x][0] == 0 && $map->[$y][$x][1] == 0;
1259 return (0, 0, 0, 0, 0) unless $count;
1262 if ($map->[0][0][0] != 0 && $map->[0][0][1] == 0) {
1263 my $tile = $map->[0][0][0];
1264 foreach my $n (1 .. 31) {
1265 goto noborder unless $map->[$n][0][0] == $tile
1266 && $map->[$n][31][0] == $tile
1267 && $map->[0][$n][0] == $tile
1268 && $map->[31][$n][0] == $tile
1269 && $map->[$n][0][1] == 0
1270 && $map->[$n][31][1] == 0
1271 && $map->[0][$n][1] == 0
1272 && $map->[31][$n][1] == 0;
1275 $xs[0] = $xs[31] = $ys[0] = $ys[31] = 0;
1279 my ($left, $right, $top, $bottom) = (-1, 32, -1, 32);
1280 1 until $xs[++$left];
1281 1 until $xs[--$right];
1282 1 until $ys[++$top];
1283 1 until $ys[--$bottom];
1285 return 0, 31, 0, 31, 0 if $border && $left == 1 && $right == 30
1286 && $top == 1 && $bottom == 30;
1288 return ($left, $right, $top, $bottom, $border);
1291 sub writeheader($\%)
1296 print $output "ruleset $data->{ruleset}\n"
1297 and print $output "\n%%%\n";
1300 sub writelevelheader($\%)
1305 printwrap $output, "title ", $level->{title} or return;
1306 print $output "passwd $level->{passwd}\n" or return;
1307 print $output "chips $level->{chips}\n" or return
1308 if exists $level->{chips} && $level->{chips};
1309 print $output "time $level->{leveltime}\n" or return
1310 if exists $level->{leveltime} && $level->{leveltime};
1311 printwrap $output, "hint ", $level->{hint} or return
1312 if exists $level->{hint};
1316 sub writelevelmap($\@)
1320 my (@tiletext, @maptext);
1322 undef %localsymbols;
1325 my ($left, $right, $top, $bottom, $border) = trimmap @$map;
1327 $border = cellsymbol $border if $border;
1328 foreach my $y ($top .. $bottom) {
1330 foreach my $x ($left .. $right) {
1331 $mapline .= cellsymbol $map->[$y][$x][0], $map->[$y][$x][1];
1333 $mapline =~ s/\s+$//;
1334 push @maptext, "$mapline\n";
1337 foreach my $tiles (keys %localsymbols) {
1338 foreach my $tile (@{$localsymbols{$tiles}}) {
1339 next unless defined $tile;
1340 my $line = "$tile\t";
1341 if ($tiles =~ /^(\d+):(\d+)$/) {
1342 my ($top, $bot) = ($1, $2);
1343 $line .= "$tilenames[$top] + $tilenames[$bot]\n";
1345 $line .= "$tilenames[$tiles]\n";
1347 push @tiletext, $line;
1350 @tiletext = sort @tiletext;
1352 print $output "tiles\n", @tiletext, "end\n\n" or return if @tiletext;
1353 print $output "border $border\n\n" or return if $border;
1355 print $output ($left || $top ? "map $left $top\n" : "map\n"),
1361 sub writelevelcloners($\%)
1367 my $default = txtfile::buildclonerlist $level->{map};
1368 if (!defined $level->{cloners}) {
1369 return print $output "cloners\n\n" if @$default;
1373 if (@$default == @{$level->{cloners}}) {
1374 for ($n = 0 ; $n < @$default ; ++$n) {
1375 last if $default->[$n]{from}[0] != $level->{cloners}[$n]{from}[0]
1376 || $default->[$n]{from}[1] != $level->{cloners}[$n]{from}[1]
1377 || $default->[$n]{to}[0] != $level->{cloners}[$n]{to}[0]
1378 || $default->[$n]{to}[1] != $level->{cloners}[$n]{to}[1];
1381 return 1 if $n == @$default;
1383 printlist $output, "cloners",
1384 map { "$_->{from}[1] $_->{from}[0] -> $_->{to}[1] $_->{to}[0]" }
1385 @{$level->{cloners}}
1390 sub writeleveltraps($\%)
1396 my $default = txtfile::buildtraplist $level->{map};
1397 if (!defined $level->{traps}) {
1398 return print $output "traps\n\n" if @$default;
1402 if (@$default == @{$level->{traps}}) {
1403 for ($n = 0 ; $n < @$default ; ++$n) {
1404 last if $default->[$n]{from}[0] != $level->{traps}[$n]{from}[0]
1405 || $default->[$n]{from}[1] != $level->{traps}[$n]{from}[1]
1406 || $default->[$n]{to}[0] != $level->{traps}[$n]{to}[0]
1407 || $default->[$n]{to}[1] != $level->{traps}[$n]{to}[1];
1410 return 1 if $n == @$default;
1412 printlist $output, "traps",
1413 map { "$_->{from}[1] $_->{from}[0] -> $_->{to}[1] $_->{to}[0]" }
1419 sub writelevelcrlist($\%$)
1423 my $ruleset = shift;
1426 my $default = txtfile::buildcreaturelist $level->{map}, $ruleset;
1427 if (!defined $level->{creatures}) {
1428 return print $output "creatures\n\n" if @$default;
1433 if (@$default == @{$level->{creatures}}) {
1434 for ($n = 0 ; $n < @$default ; ++$n) {
1435 last if $default->[$n][0] != $level->{creatures}[$n][0]
1436 || $default->[$n][1] != $level->{creatures}[$n][1];
1439 return 1 if $n == @$default;
1441 printlist $output, "creatures",
1442 map { "$_->[1] $_->[0]" } @{$level->{creatures}}
1447 sub writelevel($\%$)
1451 my $ruleset = shift;
1453 writelevelheader $output, %$level or return;
1454 writelevelmap $output, @{$level->{map}} or return;
1455 writeleveltraps $output, %$level or return;
1456 writelevelcloners $output, %$level or return;
1457 writelevelcrlist $output, %$level, $ruleset or return;
1459 print $output "%%%\n";
1467 $globalsymbols{$tilenames{"block north"}} =
1468 [ @{$globalsymbols{$tilenames{"block"}}} ]
1469 if $data->{ruleset} eq "lynx";
1471 writeheader $output, %$data or return;
1474 foreach my $level (@{$data->{levels}}) {
1475 $filelevel = $level->{number};
1477 print $output "\n" or return;
1478 print $output "level $level->{number}\n" or return
1479 unless $level->{number} == $lastnumber;
1480 writelevel $output, %$level, $data->{ruleset} or return;
1481 $lastnumber = $level->{number};
1493 # Given a string of run-length encoded data, return the original
1494 # uncompressed string.
1496 sub rleuncompress($)
1499 1 while s/\xFF(.)(.)/$2 x ord$1/se;
1508 my ($sig, $maxlevel) = ::fileread $input, "Vv" or return;
1509 if ($sig == 0x0002AAAC) {
1510 $data{ruleset} = "ms";
1511 } elsif ($sig == 0x0102AAAC) {
1512 $data{ruleset} = "lynx";
1514 return ::err "not a valid data file";
1516 return ::err "file contains no maps" if $maxlevel <= 0;
1517 $data{maxlevel} = $maxlevel;
1522 sub parselevelmap($$)
1527 if (length($layer1) > 1024) {
1528 ::err "warning: excess data in top layer of map";
1529 substr($layer1, 1024) = "";
1531 if (length($layer2) > 1024) {
1532 ::err "warning: excess data in bottom layer of map";
1533 substr($layer2, 1024) = "";
1535 return ::err "invalid map in data file"
1536 unless length($layer1) == 1024 && length($layer2) == 1024;
1537 foreach my $y (0 .. 31) {
1538 foreach my $x (0 .. 31) {
1539 $map[$y][$x][0] = ord substr $layer1, 0, 1, "";
1540 $map[$y][$x][1] = ord substr $layer2, 0, 1, "";
1550 my ($fieldnum, $fieldsize, $data);
1553 return ::err $! unless defined sysread $input, $levelsize, 2;
1554 return "" unless length($levelsize) == 2;
1555 $levelsize = unpack "v", $levelsize;
1556 return ::err "invalid metadata in file (only $levelsize bytes in level)"
1557 unless $levelsize > 8;
1559 @level{qw(number leveltime chips)} = ::fileread $input, "vvv", $levelsize
1562 ($fieldnum, $fieldsize) = ::fileread $input, "vv", $levelsize,
1565 my $layer1 = ::fileread $input, "a$fieldsize", $levelsize or return;
1566 $fieldsize = ::fileread $input, "v", $levelsize, 0, 1024 or return;
1567 my $layer2 = ::fileread $input, "a$fieldsize", $levelsize or return;
1568 ::fileread $input, "v", $levelsize or return;
1569 $level{map} = parselevelmap rleuncompress $layer1, rleuncompress $layer2
1572 while ($levelsize > 0) {
1573 ($fieldnum, $fieldsize) = ::fileread $input, "CC", $levelsize, 1, 10
1575 $data = ::fileread $input, "a$fieldsize", $levelsize or return;
1576 if ($fieldnum == 1) {
1577 return ::err "invalid field" unless $fieldsize > 1;
1578 $level{leveltime} = unpack "v", $data;
1579 return ::err "invalid data in field 1"
1580 unless $level{leveltime} >= 0 && $level{leveltime} <= 65535;
1581 } elsif ($fieldnum == 2) {
1582 return ::err "invalid field" unless $fieldsize > 1;
1583 $level{chips} = unpack "v", $data;
1584 return ::err "invalid data in field 2"
1585 unless $level{chips} >= 0 && $level{chips} <= 65535;
1586 } elsif ($fieldnum == 3) {
1587 ($level{title} = $data) =~ s/\0\Z//;
1588 } elsif ($fieldnum == 4) {
1590 my @values = unpack "v$fieldsize", $data;
1591 for (my $i = 0 ; $i < $fieldsize / 5 ; ++$i) {
1592 $level{traps}[$i]{from}[1] = shift @values;
1593 $level{traps}[$i]{from}[0] = shift @values;
1594 $level{traps}[$i]{to}[1] = shift @values;
1595 $level{traps}[$i]{to}[0] = shift @values;
1598 } elsif ($fieldnum == 5) {
1600 my @values = unpack "v$fieldsize", $data;
1601 for (my $i = 0 ; $i < $fieldsize / 4 ; ++$i) {
1602 $level{cloners}[$i]{from}[1] = shift @values;
1603 $level{cloners}[$i]{from}[0] = shift @values;
1604 $level{cloners}[$i]{to}[1] = shift @values;
1605 $level{cloners}[$i]{to}[0] = shift @values;
1607 } elsif ($fieldnum == 6) {
1608 ($level{passwd} = $data) =~ s/\0\Z//;
1609 $level{passwd} ^= "\x99" x length $level{passwd};
1610 } elsif ($fieldnum == 7) {
1611 ($level{hint} = $data) =~ s/\0\Z//;
1612 } elsif ($fieldnum == 8) {
1613 ::err "field 8 not yet supported; ignoring";
1614 } elsif ($fieldnum == 9) {
1615 ::err "ignoring useless field 9 entry";
1616 } elsif ($fieldnum == 10) {
1617 my @values = unpack "C$fieldsize", $data;
1618 for (my $i = 0 ; $i < $fieldsize / 2 ; ++$i) {
1619 $level{creatures}[$i][1] = shift @values;
1620 $level{creatures}[$i][0] = shift @values;
1624 return ::err "$levelsize bytes left over at end" if $levelsize;
1626 $level{lynxcreatures} = ::makelynxcrlist $level{map}, $level{creatures};
1636 $data = parseheader $input;
1637 return unless $data;
1640 my $level = parselevel $input;
1641 return unless defined $level;
1643 push @{$data->{levels}}, $level;
1646 ::err "warning: number of levels incorrect in header ($data->{maxlevel}, ",
1647 "should be ", scalar(@{$data->{levels}}), ")"
1648 unless $data->{maxlevel} == @{$data->{levels}};
1657 # Given a string of packed data, return a string containing the same
1658 # data run-length encoded.
1665 while (length $in) {
1666 my $byte = substr $in, 0, 1;
1668 ++$n while $n < length $in && $byte eq substr $in, $n, 1;
1669 substr($in, 0, $n) = "";
1670 while ($n >= 255) { $out .= "\xFF\xFF$byte"; $n -= 255; }
1672 $out .= "\xFF" . chr($n) . $byte;
1680 # Given a level set definition, return the pack arguments for creating
1681 # the .dat file's header data.
1683 sub mkdatfileheader(\%)
1688 if ($data->{ruleset} eq "ms") {
1689 push @fields, 0x0002AAAC;
1691 push @fields, 0x0102AAAC;
1693 push @fields, scalar @{$data->{levels}};
1694 return ("Vv", @fields);
1697 # Given a level definition, return the pack arguments for creating the
1698 # level's header data in the .dat file.
1700 sub mkdatfilelevelheader(\%)
1705 push @fields, $data->{number};
1706 push @fields, $data->{leveltime};
1707 push @fields, $data->{chips};
1708 return ("vvv", @fields);
1711 # Given a level definition, return the pack arguments for creating the
1712 # level's map data in the .dat file.
1714 sub mkdatfilelevelmap(\%)
1717 my $map = $data->{map};
1718 my ($layer1, $layer2);
1721 for my $y (0 .. 31) {
1722 for my $x (0 .. 31) {
1723 if (defined $map->[$y][$x]) {
1724 if (defined $map->[$y][$x][0]) {
1725 $layer1 .= chr $map->[$y][$x][0];
1729 if (defined $map->[$y][$x][1]) {
1730 $layer2 .= chr $map->[$y][$x][1];
1741 $layer1 = rlecompress $layer1;
1742 $layer2 = rlecompress $layer2;
1745 push @fields, length $layer1;
1746 push @fields, $layer1;
1747 push @fields, length $layer2;
1748 push @fields, $layer2;
1750 return ("vva$fields[1]va$fields[3]", @fields);
1753 # Given a level definition, return the pack arguments for creating the
1754 # level's title field in the .dat file.
1756 sub mkdatfileleveltitle(\%)
1759 my $n = length($data->{title}) + 1;
1760 return ("CCa$n", 3, $n, $data->{title});
1763 # Given a level definition, return the pack arguments for creating the
1764 # level's hint field in the .dat file.
1766 sub mkdatfilelevelhint(\%)
1769 return ("") unless exists $data->{hint};
1770 my $n = length($data->{hint}) + 1;
1771 return ("CCa$n", 7, $n, $data->{hint});
1774 # Given a level definition, return the pack arguments for creating the
1775 # level's password field in the .dat file.
1777 sub mkdatfilelevelpasswd(\%)
1780 my $n = length($data->{passwd}) + 1;
1781 return ("CCa$n", 6, $n, $data->{passwd} ^ "\x99\x99\x99\x99");
1784 # Given a level definition, return the pack arguments for creating the
1785 # level's bear trap list field in the .dat file.
1787 sub mkdatfileleveltraps(\%)
1791 return ("") unless exists $data->{traps};
1792 my $list = $data->{traps};
1794 return ("") unless $n;
1798 push @fields, $n * 10;
1799 foreach my $i (0 .. $#$list) {
1800 push @fields, $list->[$i]{from}[1], $list->[$i]{from}[0];
1801 push @fields, $list->[$i]{to}[1], $list->[$i]{to}[0];
1804 return (("CCv" . ($n * 5)), @fields);
1807 # Given a level definition, return the pack arguments for creating the
1808 # level's clone machine list field in the .dat file.
1810 sub mkdatfilelevelcloners(\%)
1814 return ("") unless exists $data->{cloners};
1815 my $list = $data->{cloners};
1817 return ("") unless $n;
1821 push @fields, $n * 8;
1822 foreach my $i (0 .. $#$list) {
1823 push @fields, $list->[$i]{from}[1], $list->[$i]{from}[0];
1824 push @fields, $list->[$i]{to}[1], $list->[$i]{to}[0];
1826 return (("CCv" . ($n * 4)), @fields);
1829 # Given a level definition, return the pack arguments for creating the
1830 # level's creature list field in the .dat file.
1832 sub mkdatfilelevelcrlist(\%)
1836 return ("") unless exists $data->{creatures};
1837 my $list = $data->{creatures};
1838 return ("") unless $list && @$list;
1843 push @fields, $n * 2;
1844 foreach my $i (0 .. $#$list) {
1845 push @fields, $list->[$i][1], $list->[$i][0];
1847 return (("CCC" . ($n * 2)), @fields);
1850 # Given a level definition, return the pack arguments for creating the
1851 # level's miscellaneous fields, if any, in the .dat file.
1853 sub mkdatfilelevelmisc(\%)
1856 my ($template, @fields) = ("");
1858 return ("") unless exists $data->{fields};
1859 foreach my $num (keys %{$data->{fields}}) {
1860 my $n = length($data->{fields}{$num});
1861 $template .= "CCa$n";
1862 push @fields, $num, $n, $data->{fields}{$num};
1864 return ($template, @fields);
1867 # Given a level definition, return the pack arguments for creating the
1868 # level in the .dat file.
1870 sub mkdatfilelevel(\%)
1873 my ($template, @fields);
1876 @p = mkdatfilelevelheader %$data; $template .= shift @p; push @fields, @p;
1877 @p = mkdatfilelevelmap %$data; $template .= shift @p; push @fields, @p;
1879 my $data2pos = @fields; $template .= "v"; push @fields, 0;
1880 my $tmplt2pos = length $template;
1882 @p = mkdatfileleveltitle %$data; $template .= shift @p; push @fields, @p;
1883 @p = mkdatfilelevelhint %$data; $template .= shift @p; push @fields, @p;
1884 @p = mkdatfilelevelpasswd %$data; $template .= shift @p; push @fields, @p;
1885 @p = mkdatfileleveltraps %$data; $template .= shift @p; push @fields, @p;
1886 @p = mkdatfilelevelcloners %$data; $template .= shift @p; push @fields, @p;
1887 @p = mkdatfilelevelcrlist %$data; $template .= shift @p; push @fields, @p;
1888 @p = mkdatfilelevelmisc %$data; $template .= shift @p; push @fields, @p;
1890 $fields[$data2pos] = ::packlen substr $template, $tmplt2pos;
1892 unshift @fields, ::packlen $template;
1893 $template = "v$template";
1895 return ($template, @fields);
1898 # Given a level set definition, return the pack arguments for creating
1904 my ($template, @fields);
1907 @p = mkdatfileheader %$data;
1908 $template = shift @p;
1911 foreach my $level (@{$data->{levels}}) {
1912 $filelevel = $level->{number};
1913 @p = mkdatfilelevel %$level;
1914 $template .= shift @p;
1918 return ($template, @fields);
1921 # This function takes a handle to a binary file and a hash ref
1922 # defining a level set, and writes the level set to the binary file as
1923 # a .dat file. The return value is false if the file's contents could
1924 # not be completely created; otherwise a true value is returned.
1931 my @args = mkdatfile %$data;
1932 my $template = shift @args;
1933 print $file pack $template, @args;
1942 my @objectkey = ($tilenames{"empty"},
1946 $tilenames{"blue block floor"},
1947 $tilenames{"force north"},
1948 $tilenames{"force east"},
1949 $tilenames{"force south"},
1950 $tilenames{"force west"},
1951 $tilenames{"force any"},
1952 $tilenames{"ice corner se"},
1953 $tilenames{"ice corner sw"},
1954 $tilenames{"ice corner nw"},
1955 $tilenames{"ice corner ne"},
1956 $tilenames{"teleport"},
1957 $tilenames{"ice boots"},
1958 $tilenames{"fire boots"},
1959 $tilenames{"force boots"},
1960 $tilenames{"water boots"},
1962 $tilenames{"water"},
1963 $tilenames{"thief"},
1964 $tilenames{"popup wall"},
1965 $tilenames{"toggle open"},
1966 $tilenames{"toggle closed"},
1967 $tilenames{"green button"},
1968 $tilenames{"red door"},
1969 $tilenames{"blue door"},
1970 $tilenames{"yellow door"},
1971 $tilenames{"green door"},
1972 $tilenames{"red key"},
1973 $tilenames{"blue key"},
1974 $tilenames{"yellow key"},
1975 $tilenames{"green key"},
1976 $tilenames{"blue button"},
1977 $tilenames{"computer chip"}, # counted
1978 $tilenames{"socket"},
1980 $tilenames{"invisible wall temporary"},
1981 $tilenames{"invisible wall permanent"},
1982 $tilenames{"gravel"},
1983 $tilenames{"wall east"},
1984 $tilenames{"wall south"},
1985 $tilenames{"wall southeast"},
1987 $tilenames{"bear trap"},
1988 $tilenames{"brown button"},
1989 $tilenames{"clone machine"},
1990 $tilenames{"red button"},
1991 $tilenames{"computer chip"}, # uncounted
1992 $tilenames{"blue block wall"},
1993 $tilenames{"hint button"});
1995 my @creaturekey = (0, 0, 0, 0,
1996 $tilenames{"chip north"}, $tilenames{"chip east"},
1997 $tilenames{"chip south"}, $tilenames{"chip west"},
1998 $tilenames{"bug north"}, $tilenames{"bug east"},
1999 $tilenames{"bug south"}, $tilenames{"bug west"},
2000 $tilenames{"centipede north"}, $tilenames{"centipede east"},
2001 $tilenames{"centipede south"}, $tilenames{"centipede west"},
2002 $tilenames{"fireball north"}, $tilenames{"fireball east"},
2003 $tilenames{"fireball south"}, $tilenames{"fireball west"},
2004 $tilenames{"glider north"}, $tilenames{"glider east"},
2005 $tilenames{"glider south"}, $tilenames{"glider west"},
2006 $tilenames{"ball north"}, $tilenames{"ball east"},
2007 $tilenames{"ball south"}, $tilenames{"ball west"},
2008 $tilenames{"block north"}, $tilenames{"block east"},
2009 $tilenames{"block south"}, $tilenames{"block west"},
2010 $tilenames{"tank north"}, $tilenames{"tank east"},
2011 $tilenames{"tank south"}, $tilenames{"tank west"},
2012 $tilenames{"walker north"}, $tilenames{"walker east"},
2013 $tilenames{"walker south"}, $tilenames{"walker west"},
2014 $tilenames{"blob north"}, $tilenames{"blob east"},
2015 $tilenames{"blob south"}, $tilenames{"blob west"},
2016 $tilenames{"teeth north"}, $tilenames{"teeth east"},
2017 $tilenames{"teeth south"}, $tilenames{"teeth west"});
2020 ("\n"," ","0","1","2","3","4","5","6","7","8","9","A","B","C","D",
2021 "E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T",
2022 "U","V","W","X","Y","Z","!",'"',"'","(",")",",","-",".",":",";",
2023 "?", (("%") x 207));
2025 my @levelfilenames = @{
2026 [qw(lesson_1.pak lesson_2.pak lesson_3.pak lesson_4.pak
2027 lesson_5.pak lesson_6.pak lesson_7.pak lesson_8.pak
2028 nuts_and.pak brushfir.pak trinity.pak hunt.pak
2029 southpol.pak telebloc.pak elementa.pak cellbloc.pak
2030 nice_day.pak castle_m.pak digger.pak tossed_s.pak
2031 iceberg.pak forced_e.pak blobnet.pak oorto_ge.pak
2032 blink.pak chchchip.pak go_with_.pak ping_pon.pak
2033 arcticfl.pak mishmesh.pak knot.pak scavenge.pak
2034 on_the_r.pak cypher.pak lemmings.pak ladder.pak
2035 seeing_s.pak sampler.pak glut.pak floorgas.pak
2036 i.pak beware_o.pak lock_blo.pak refracti.pak
2037 monster_.pak three_do.pak pier_sev.pak mugger_s.pak
2038 problems.pak digdirt.pak i_slide.pak the_last.pak
2039 traffic_.pak grail.pak potpourr.pak deepfree.pak
2040 mulligan.pak loop_aro.pak hidden_d.pak scoundre.pak
2041 rink.pak slo_mo.pak block_fa.pak spooks.pak
2042 amsterda.pak victim.pak chipmine.pak eeny_min.pak
2043 bounce_c.pak nightmar.pak corridor.pak reverse_.pak
2044 morton.pak playtime.pak steam.pak four_ple.pak
2045 invincib.pak force_sq.pak drawn_an.pak vanishin.pak
2046 writers_.pak socialis.pak up_the_b.pak wars.pak
2047 telenet.pak suicide.pak citybloc.pak spirals.pak
2048 block.pak playhous.pak jumping_.pak vortex.pak
2049 roadsign.pak now_you_.pak four_squ.pak paranoia.pak
2050 metastab.pak shrinkin.pak catacomb.pak colony.pak
2051 apartmen.pak icehouse.pak memory.pak jailer.pak
2052 short_ci.pak kablam.pak balls_o_.pak block_ou.pak
2053 torturec.pak chiller.pak time_lap.pak fortune_.pak
2054 open_que.pak deceptio.pak oversea_.pak block_ii.pak
2055 the_mars.pak miss_dir.pak slide_st.pak alphabet.pak
2056 perfect_.pak t_fair.pak the_pris.pak firetrap.pak
2057 mixed_nu.pak block_n_.pak skelzie.pak all_full.pak
2058 lobster_.pak ice_cube.pak totally_.pak mix_up.pak
2059 blobdanc.pak pain.pak trust_me.pak doublema.pak
2060 goldkey.pak partial_.pak yorkhous.pak icedeath.pak
2061 undergro.pak pentagra.pak stripes.pak fireflie.pak
2062 level145.pak cake_wal.pak force_fi.pak mind_blo.pak
2063 special.pak level150.pak)]
2066 my (%objectkey, %creaturekey, %textkey);
2067 for (0 .. $#objectkey) { $objectkey{$objectkey[$_]} = $_ }
2068 for (0 .. $#creaturekey) { $creaturekey{$creaturekey[$_]} = $_ }
2069 $creaturekey{$tilenames{"block"}} = $creaturekey{$tilenames{"block north"}};
2070 for (0 .. $#textkey) { $textkey{$textkey[$_]} = chr $_ }
2076 sub longestmatch($$$)
2078 my $dictionary = shift;
2082 my ($longest, $longestlen) = ("", 0);
2083 foreach my $entry (@$dictionary) {
2084 my $len = length $entry->{text};
2085 if ($len > $longestlen && $entry->{text} eq substr $data, $pos, $len) {
2086 ($longest, $longestlen) = ($entry, $len);
2095 my $dictionary = [ ];
2098 while ($pos < length $data) {
2099 my $entry = { refcount => 0 };
2101 $match = longestmatch $dictionary, $data, $pos;
2103 $entry->{left} = $match;
2104 $len = length $match->{text};
2108 $entry->{text} = substr $data, $pos, $len;
2110 last if $pos >= length $data;
2111 $match = longestmatch $dictionary, $data, $pos;
2113 $entry->{right} = $match;
2114 $len = length $match->{text};
2118 $entry->{text} .= substr $data, $pos, $len;
2120 push @$dictionary, $entry;
2126 sub refcountadd($$);
2130 $entry->{refcount} += shift;
2131 refcountadd $entry->{left}, $entry->{refcount} if exists $entry->{left};
2132 refcountadd $entry->{right}, $entry->{refcount} if exists $entry->{right};
2137 my $dictionary = shift;
2141 while ($pos < length $data) {
2142 my $entry = longestmatch $dictionary, $data, $pos;
2144 ++$entry->{refcount};
2145 $pos += length $entry->{text};
2150 foreach my $entry (@$dictionary) { refcountadd $entry, 0 }
2155 my $dictionary = shift;
2159 while ($data =~ /(.)/gs) { $used[ord $1] = 1 }
2161 foreach my $entry (@$dictionary) {
2162 ++$n while $used[$n];
2163 die "too many dictionary entries; not enough keys" if $n >= 256;
2164 $entry->{key} = chr $n;
2171 my $dictionary = shift;
2172 my ($out, $len) = ("", 0);
2174 foreach my $entry (@$dictionary) {
2175 $out .= $entry->{key};
2176 if (exists $entry->{left}) {
2177 $out .= $entry->{left}{key};
2179 $out .= substr $entry->{text}, 0, 1;
2181 if (exists $entry->{right}) {
2182 $out .= $entry->{right}{key};
2184 $out .= substr $entry->{text}, -1;
2189 return ($out, $len);
2194 my $dictionary = shift;
2196 my ($out, $len) = ("", 0);
2199 while ($pos < length $data) {
2200 my $entry = longestmatch $dictionary, $data, $pos;
2202 $out .= $entry->{key};
2203 $pos += length $entry->{text};
2205 $out .= substr $data, $pos, 1;
2211 return ($out, $len);
2217 my $dictionary = builddict $data;
2218 countuses $dictionary, $data;
2219 $dictionary = [ grep { $_->{refcount} > 3 } @$dictionary ];
2220 assignkeys $dictionary, $data;
2221 my ($cdict, $dictlen) = composedict $dictionary;
2222 my ($cdata, $datalen) = composedata $dictionary, $data;
2223 return pack("vv", $dictlen, $datalen) . $cdict . $cdata;
2230 my $tablesize = unpack "v", substr $data, 0, 2, "";
2231 my $datasize = unpack "v", substr $data, 0, 2, "";
2233 my @data = map { ord } split //, $data;
2236 for (my $n = 0 ; $n < $tablesize ; ++$n) {
2237 return ::err "@{[$tablesize - $n]} entries missing"
2239 my $key = shift @data;
2240 my $val1 = shift @data;
2241 my $val2 = shift @data;
2242 if (defined $table[$val1]) {
2243 $val1 = $table[$val1];
2247 if (defined $table[$val2]) {
2248 $val2 = $table[$val2];
2252 $table[$key] = "$val1$val2";
2256 foreach my $byte (@data) {
2257 if (defined $table[$byte]) {
2258 $data .= $table[$byte];
2270 my @data = map { ord } split //, shift;
2272 return ::err "@{[1024 - @data]} bytes missing from map data"
2273 unless @data == 1024;
2274 $level->{chips} = 0;
2275 foreach my $y (0 .. 31) {
2276 foreach my $x (0 .. 31) {
2277 my $obj = shift @data;
2278 ::err "undefined object $obj at ($x $y)"
2279 unless defined $objectkey[$obj];
2280 $level->{map}[$y][$x][0] = $objectkey[$obj];
2281 $level->{map}[$y][$x][1] = 0;
2282 ++$level->{chips} if $obj == 0x23;
2294 my @t = map { ord } split //, substr $data, 0, 128, "";
2295 my @x = map { ord } split //, substr $data, 0, 128, "";
2296 my @y = map { ord } split //, substr $data, 0, 128, "";
2298 foreach my $n (0 .. 127) {
2300 my $x = $x[$n] >> 3;
2301 my $y = $y[$n] >> 3;
2302 my $t = $creaturekey[$t[$n] & 0x7F];
2303 push @{$level->{creatures}}, [ $y, $x ]; # unless $t[$n] & 0x80;
2304 $level->{map}[$y][$x][1] = $level->{map}[$y][$x][0];
2305 $level->{map}[$y][$x][0] = $t;
2316 $data = expand $data or return ::err "invalid data";
2319 $_ = substr $data, 0, 1024, "";
2321 or return ::err "invalid map";
2322 $_ = substr $data, 0, 384, "";
2323 parsecrlist $level, $_
2324 or return ::err "invalid creature list";
2325 $level->{creatures} = ::makedatcrlist $level->{map},
2326 $level->{lynxcreatures};
2327 $level->{traps} = txtfile::buildtraplist $level->{map};
2328 $level->{cloners} = txtfile::buildclonerlist $level->{map};
2330 $level->{leveltime} = unpack "v", substr $data, 0, 2, "";
2332 $data = join "", map { $textkey[ord] } split //, $data;
2333 $data =~ s/\A([^\n]+)\n//;
2334 $level->{title} = $1;
2338 $level->{hint} = $data;
2346 my $dirname = shift;
2347 my $data = { ruleset => "lynx" };
2349 foreach my $n (0 .. $#levelfilenames) {
2350 $filename = "$dirname/$levelfilenames[$n]";
2351 $filelevel = $n + 1;
2352 next unless -e $filename;
2353 open FILE, "< $filename" or return ::err $!;
2355 my $level = parselevel join "", <FILE>;
2357 return unless defined $level;
2358 $level->{number} = $n + 1;
2359 $level->{passwd} = $origpasswords[$n];
2360 push @{$data->{levels}}, $level;
2369 my $data = { ruleset => "lynx" };
2371 my $buf = ::fileread $input, "a20" or return;
2372 return ::err "invalid ROM file"
2373 unless $buf eq "LYNX\000\002\000\000\001\000chipchal.l";
2376 sysseek $input, 0x02F0, 0 or return ::err $!;
2377 for (my $n = 0 ; $n < 150 ; ++$n) {
2378 my @rec = ::fileread $input, "C4vv" or return;
2379 $levels[$n][0] = (($rec[0] << 9) | $rec[1]
2380 | (($rec[2] & 0x01) << 8)) + 0x40;
2381 $levels[$n][1] = $rec[5];
2384 for (my $n = 0 ; $n < 150 ; ++$n) {
2385 $filelevel = $n + 1;
2386 $buf = sysseek $input, $levels[$n][0], 0 or return ::err $!;
2387 $buf = ::fileread $input, "a$levels[$n][1]" or return;
2388 next if $levels[$n][1] == 5 && $buf eq "\000\000\001\000\377";
2389 my $level = parselevel $buf;
2390 return unless defined $level;
2391 $level->{number} = $n + 1;
2392 $level->{passwd} = $origpasswords[$n];
2393 push @{$data->{levels}}, $level;
2403 sub translatetext($;$)
2406 my $multiline = shift || 0;
2409 my ($x, $y) = (0, 0);
2410 my $brk = [ undef ];
2412 foreach my $char (split //, $in) {
2413 if ($char eq "\n") {
2417 } elsif ($x >= 19) {
2418 if (!$multiline || $y >= 6) {
2419 ::err "truncated text";
2420 substr($out, 17 - $x) = "" if $y >= 6 && $x >= 19;
2425 substr($out, $brk->[1], 1) = "\0";
2432 } elsif ($char eq " ") {
2433 $brk = [ $x, length $out ];
2435 $out .= $textkey{uc $char};
2448 for (my $y = 0 ; $y < 32 ; ++$y) {
2449 for (my $x = 0 ; $x < 32 ; ++$x) {
2451 my $top = $level->{map}[$y][$x][0];
2452 my $bot = $level->{map}[$y][$x][1];
2453 if (::iscreature $top || ::ischip $top || ::isblock $top) {
2455 if (::iscreature $obj || ::isblock $obj || ::ischip $obj) {
2456 ::err "ignoring buried creature";
2460 ::err "ignoring buried object" if $bot;
2463 if ($obj == $tilenames{"computer chip"}) {
2464 $obj = $chips < $level->{chips} ? 0x23 : 0x31;
2467 $obj = $objectkey{$obj};
2468 unless (defined $obj) {
2469 ::err "ignoring non-Lynx object";
2477 ::err "chips needed was reduced" if $chips < $level->{chips};
2482 sub mklevelcrlist($)
2488 return ::err "invalid creature list: $level->{lynxcreatures}"
2489 unless ref $level->{lynxcreatures};
2491 my ($types, $xs, $ys) = ("", "", "");
2492 foreach my $creature (@{$level->{lynxcreatures}}) {
2493 my $y = $creature->[0];
2494 my $x = $creature->[1];
2495 my $type = $level->{map}[$y][$x][0];
2496 $type = $creaturekey{$type};
2497 unless (defined $type) {
2498 ::err "ignoring non-Lynx creature in creature list";
2501 $type |= 0x80 if $creature->[2] < 0;
2504 ++$y, ++$x if $creature->[2] == 0;
2505 $types .= chr $type;
2510 return pack "a128 a128 a128", $types, $xs, $ys;
2519 $part = mklevelmap $level;
2520 return unless defined $part;
2523 $part = mklevelcrlist $level;
2524 return unless defined $part;
2527 $out .= pack "v", $level->{leveltime};
2529 $part = translatetext $level->{title};
2530 return unless defined $part;
2533 if (exists $level->{hint}) {
2534 $part = translatetext $level->{hint}, 1;
2535 return unless defined $part;
2541 return compress $out;
2546 my $dirname = shift;
2549 ::err "warning: storing an MS-ruleset level set in a Lynx-only file format"
2550 unless $data->{ruleset} eq "lynx";
2552 foreach my $level (@{$data->{levels}}) {
2553 $filename = $dirname;
2555 if ($level->{number} >= @levelfilenames) {
2556 ::err "ignoring level $level->{number}, number too high";
2558 } elsif ($level->{number} < 1) {
2559 ::err "ignoring level $level->{number}, number invalid";
2562 $filename = "$dirname/$levelfilenames[$level->{number} - 1]";
2563 $filelevel = $level->{number};
2564 ::err "ignoring password"
2565 if $level->{passwd} ne $origpasswords[$level->{number} - 1];
2566 open FILE, "> $filename" or return ::err $!;
2568 my $out = mkleveldata $level or return;
2569 print FILE $out or return ::err $!;
2570 close FILE or return ::err $!;
2581 ::err "warning: storing an MS-ruleset level set in a Lynx-only file format"
2582 unless $data->{ruleset} eq "lynx";
2584 my $buf = ::fileread $file, "a22" or return;
2585 return ::err "invalid ROM file"
2586 unless $buf eq "LYNX\000\002\000\000\001\000chipchal.lyx";
2588 sysseek $file, 0x02F0, 0 or return ::err $!;
2589 my @ptr = ::fileread $file, "C4" or return ::err $!;
2590 my $startpos = (($ptr[0] << 9) | $ptr[1] | (($ptr[2] & 0x01) << 8));
2594 foreach my $level (@{$data->{levels}}) {
2595 my $n = $level->{number};
2598 ::err "ignoring invalid-numbered level $n";
2599 } elsif ($n > 149) {
2601 } elsif (defined $levellist[$n]) {
2602 ::err "ignoring duplicate level $n";
2604 ::err "ignoring password"
2605 if $level->{passwd} ne $origpasswords[$n - 1];
2606 $levellist[$n] = mkleveldata $level;
2607 return unless defined $levellist[$n];
2610 ::err "ignored $dropped level(s) above level 149" if $dropped;
2614 my $ptr = $startpos;
2615 for (my $n = 1 ; $n <= 149 ; ++$n) {
2617 if ($levellist[$n]) {
2618 $levels .= $levellist[$n];
2619 $size = length $levellist[$n];
2621 $levels .= "\000\000\001\000\377";
2624 $index .= pack "C4vv", ($ptr >> 9), ($ptr & 0xFF),
2625 (($ptr >> 8) & 0x01), 0, 0, $size;
2628 $levels .= "\000\000\001\000\377";
2629 $index .= pack "C4vv", ($ptr >> 9), ($ptr & 0xFF),
2630 (($ptr >> 8) & 0x01), 0, 0, 5;
2632 return ::err "too much data; cannot fit inside the ROM file"
2633 if length $levels > 0x11D00;
2635 sysseek $file, 0x02F0, 0 or return ::err $!;
2636 syswrite $file, $index or return ::err $!;
2637 sysseek $file, $startpos + 0x40, 0 or return ::err $!;
2638 syswrite $file, $levels or return ::err $!;
2649 # The terse names used by the universal-dump file format.
2651 my @shortnames = ("empty", # 0x00
2656 "inv_wall_per", # 0x05
2675 "green_door", # 0x18
2676 "yellow_door", # 0x19
2677 "ice_turn_SE", # 0x1A
2678 "ice_turn_SW", # 0x1B
2679 "ice_turn_NW", # 0x1C
2680 "ice_turn_NE", # 0x1D
2681 "blue_floor", # 0x1E
2686 "green_button", # 0x23
2687 "red_button", # 0x24
2688 "toggle_close", # 0x25
2689 "toggle_open", # 0x26
2690 "brown_button", # 0x27
2691 "blue_button", # 0x28
2695 "inv_wall_tmp", # 0x2C
2697 "popup_wall", # 0x2E
2698 "hint_button", # 0x2F
2702 "chip_drowned", # 0x33
2703 "chip_burned", # 0x34
2704 "chip_bombed", # 0x35
2708 "chip_exiting", # 0x39
2711 "chip_swim_N", # 0x3C
2712 "chip_swim_W", # 0x3D
2713 "chip_swim_S", # 0x3E
2714 "chip_swim_E", # 0x3F
2719 "fireball_N", # 0x44
2720 "fireball_W", # 0x45
2721 "fireball_S", # 0x46
2722 "fireball_E", # 0x47
2747 "centipede_N", # 0x60
2748 "centipede_W", # 0x61
2749 "centipede_S", # 0x62
2750 "centipede_E", # 0x63
2754 "yellow_key", # 0x67
2755 "water_boots", # 0x68
2756 "fire_boots", # 0x69
2758 "force_boots", # 0x6B
2764 for (0x70 .. 0xFF) { $shortnames[$_] = sprintf "tile_%02X", $_ }
2772 print $output "BEGIN CUD 1 ruleset $data->{ruleset}\n\n" or return;
2774 foreach my $level (@{$data->{levels}}) {
2775 printf $output "%03d chips %d\n", $level->{number}, $level->{chips}
2777 printf $output "%03d time %d\n", $level->{number}, $level->{leveltime}
2779 printf $output "%03d passwd %s\n", $level->{number}, $level->{passwd}
2781 printf $output "%03d title:%s\n", $level->{number},
2782 ::escape $level->{title}
2784 printf $output "%03d hint", $level->{number} or return;
2785 print $output ":", ::escape $level->{hint} or return
2786 if exists $level->{hint};
2787 print $output "\n" or return;
2790 $list = $level->{traps};
2791 foreach my $i (0 .. $#$list) {
2792 $notes[$list->[$i]{from}[0]][$list->[$i]{from}[1]]{tfr} = $i + 1;
2793 $notes[$list->[$i]{to}[0]][$list->[$i]{to}[1]]{tto} = $i + 1;
2795 $list = $level->{cloners};
2796 foreach my $i (0 .. $#$list) {
2797 $notes[$list->[$i]{from}[0]][$list->[$i]{from}[1]]{cfr} = $i + 1;
2798 $notes[$list->[$i]{to}[0]][$list->[$i]{to}[1]]{cto} = $i + 1;
2800 $list = $level->{creatures};
2801 foreach my $i (0 .. $#$list) {
2802 $notes[$list->[$i][0]][$list->[$i][1]]{crl} = $i + 1;
2805 foreach my $y (0 .. 31) {
2806 foreach my $x (0 .. 31) {
2807 next if $level->{map}[$y][$x][0] == 0
2808 && $level->{map}[$y][$x][1] == 0
2809 && !defined $notes[$y][$x];
2810 printf $output "%03d (%02d %02d) ", $level->{number}, $x, $y
2812 printf $output "%-12.12s %-12.12s ",
2813 $shortnames[$level->{map}[$y][$x][0]],
2814 $shortnames[$level->{map}[$y][$x][1]]
2816 printf $output " Tfr=%-2.2s", $notes[$y][$x]{tfr} or return
2817 if exists $notes[$y][$x]{tfr};
2818 printf $output " Tto=%-2.2s", $notes[$y][$x]{tto} or return
2819 if exists $notes[$y][$x]{tto};
2820 printf $output " Cfr=%-2.2s", $notes[$y][$x]{cfr} or return
2821 if exists $notes[$y][$x]{cfr};
2822 printf $output " Cto=%-2.2s", $notes[$y][$x]{cto} or return
2823 if exists $notes[$y][$x]{cto};
2824 printf $output " CL=%-3.3s", $notes[$y][$x]{crl} or return
2825 if exists $notes[$y][$x]{crl};
2826 printf $output "\n" or return;
2829 printf $output "\n" or return;
2832 print $output "END\n" or return;
2843 use constant yowzitch => <<EOT;
2844 Usage: c4 [-INTYPE] INFILE [-OUTTYPE] OUTFILE
2846 The type switches can be omitted if the file's type can be inferred
2847 directly. Available types:
2849 -D Microsoft data file (*.dat)
2850 -T textual source file (*.txt)
2851 -R Lynx ROM file (*.lnx, *.lyx)
2852 -P MS-DOS fileset (directory of *.pak files)
2853 -U Chip's universal dump file (*.cud) [write-only]
2855 use constant vourzhon => "1.0\n";
2857 my ($infile, $outfile);
2858 my ($intype, $outtype);
2865 } elsif (/\.dat$/) {
2867 } elsif (/\.txt$/ || /^-$/) {
2869 } elsif (/\.lnx$/ || /\.lyx$/) {
2871 } elsif (/\.cud$/) {
2879 open FILE, shift or return;
2881 sysread FILE, $_, 16 or return;
2883 return "D" if /\A\xAC\xAA\x02/;
2884 return "R" if /\ALYNX\0/;
2885 return "T" if /\A\s*(rul|til|max|%%%)/;
2889 die yowzitch unless @ARGV;
2890 print yowzitch and exit if $ARGV[0] =~ /^--?(h(elp)?|\?)$/;
2891 print vourzhon and exit if $ARGV[0] =~ /^--?[Vv](ersion)?$/;
2894 if ($infile =~ /^-([A-Za-z])$/) {
2898 die yowzitch unless @ARGV;
2900 if ($outfile =~ /^-([A-Za-z])$/) {
2904 die yowzitch unless defined $infile && defined $outfile && @ARGV == 0;
2906 $intype ||= deducetype $infile;
2907 $outtype ||= deducetype $outfile;
2908 die "$outfile: file type unspecified\n" unless $outtype;
2909 $intype = findfiletype $infile if !defined $intype && -f $infile;
2910 die "$infile: file type unspecified\n" unless $intype;
2914 $filename = $infile;
2915 if ($intype eq "D") {
2916 open FILE, "< $infile" or die "$infile: $!\n";
2918 $data = datfile::read \*FILE or exit 1;
2920 } elsif ($intype eq "T") {
2921 open FILE, "< $infile" or die "$infile: $!\n";
2922 $data = txtfile::read \*FILE or exit 1;
2924 } elsif ($intype eq "P") {
2925 $data = lynxfmt::readmsdos $infile or exit 1;
2926 } elsif ($intype eq "R") {
2927 open FILE, "< $infile" or die "$infile: $!\n";
2929 $data = lynxfmt::readrom \*FILE or exit 1;
2931 } elsif ($intype eq "U") {
2932 die "File type -U is a write-only file format.\n";
2934 die "Unknown file type option -$intype.\n";
2941 $filename = $outfile;
2943 if ($outtype eq "D") {
2944 open FILE, "> $outfile" or die "$outfile: $!\n";
2946 datfile::write \*FILE, $data or die "$outfile: $!\n";
2947 close FILE or die "$outfile: $!\n";
2948 } elsif ($outtype eq "T") {
2949 open FILE, "> $outfile" or die "$outfile: $!\n";
2950 txtfile::write \*FILE, $data or die "$outfile: $!\n";
2951 close FILE or die "$outfile: $!\n";
2952 } elsif ($outtype eq "P") {
2953 lynxfmt::writemsdos $outfile, $data or exit 1;
2954 } elsif ($outtype eq "R") {
2955 open FILE, "+< $outfile" or die "$outfile: $!\n";
2957 lynxfmt::writerom \*FILE, $data or die "$outfile: $!\n";
2958 close FILE or die "$outfile: $!\n";
2959 } elsif ($outtype eq "U") {
2960 open FILE, "> $outfile" or die "$outfile: $!\n";
2961 cudfile::write \*FILE, $data or die "$outfile: $!\n";
2962 close FILE or die "$outfile: $!\n";
2964 die "Unknown file type option -$outtype.\n";
2973 c4 - Chip's Challenge combined converter
2977 c4 [-INTYPE] INFILENAME [-OUTTYPE] OUTFILENAME
2979 c4 allows one to translate between the several different types of
2980 files used to represent level sets for the game Chip's Challenge.
2982 c4 expects there to be two files named on the command-line. c4 reads
2983 the levels stored in the first file, and then writes the levels out to
2984 the second file. The format to use with each file usually can be
2985 inferred by c4 by examining the filenames. If not, then it may be
2986 necessary to use switches before one or both filenames to indicate
2989 There are four different types of files that c4 understands.
2991 -D MS data file (*.dat).
2993 This is the file type used by Chip's Challenge for Microsoft Windows
2994 3.x. It is the file type used by most other programs, such as ChipEdit
2997 -R Lynx ROM file (*.lnx, *.lyx)
2999 This "file type" is actually just a ROM image of the original Chip's
3000 Challenge for the Atari Lynx handheld. It is used by Lynx emulators
3003 -P MS-DOS fileset (directory of *.pak files)
3005 This is the format used by the MS-DOS port of Chip's Challenge. In
3006 this case, the filename given on the command line actually names a
3007 directory, containing *.pak files.
3009 -T textual source file (*.txt)
3011 This file type is native to c4. It is a plain text file, which allows
3012 levels to be defined pictorially using a simple text editor. A
3013 complete description of the syntax of these files is provided below.
3017 c4 mylevels.txt mylevels.dat
3019 Create a .dat file from a textual source file.
3021 c4 -P levels -D doslevels.dat
3023 "levels" is a directory of MS-DOS *.pak files. c4 translates the
3024 directory contents into a single .dat file. Note that the switches in
3025 this example are optional, as c4 would be able to infer the desired
3028 c4 mylevels.dat chipsch.lnx
3030 Embed the levels from the .dat file into a Lynx ROM file. Note that c4
3031 does NOT create chipsch.lnx. You must provide the ROM image file,
3032 which c4 then alters to contain your levels. (Obviously, you should
3033 not use this command on your master copy of the ROM file.)
3035 c4 chipsch.lnx -T out
3037 Output the levels in the .dat file as a text file. Here the -T switch
3038 is needed to indicate that a text file is the desired output format.
3040 When producing a text file, c4 will attempt to produce legible source,
3041 but the results will often not be as good as what a human being would
3042 produce. (In particular, c4 cannot draw overlays.)
3046 Be aware that there can be various problems when translating a set of
3047 levels using the MS ruleset to one of the Lynx-only file formats.
3048 There are numerous objects and configurations in the MS ruleset which
3049 cannot be represented in the Lynx ruleset. Usually c4 will display a
3050 warning when some aspect of the data could not be transferred intact
3053 The remainder of this documentation describes the syntax of the
3054 textual source file format.
3056 =head1 LAYOUT OF THE INPUT FILE
3058 The source file is broken up into subsections. Each subsection defines
3059 a separate level in the set.
3061 The subsections are separated from each other by a line containing
3062 three percent signs:
3066 A line of three percent signs also comes before the first level and
3067 after the last level, at the end of the source file.
3069 Any other line that begins with a percent sign is treated as a
3070 comment, and its contents are ignored.
3072 Beyond these things, the source file consists of statements.
3073 Statements generally appear as a single line of text. Some statements,
3074 however, require multiple lines. These multi-line statements are
3075 terminated with the word B<end> appearing alone on a line.
3077 =head1 INPUT FILE HEADER STATEMENTS
3079 There are a couple of statements that can appear at the very top of
3080 the source file, before the first level subsection.
3082 ruleset [ lynx | ms ]
3084 The B<ruleset> statement is the most important of these. It defines
3085 the ruleset for the level set. If the B<ruleset> statment is absent,
3086 it defaults to B<lynx>.
3090 The B<maxlevel> statement specifies the number of the last level in
3091 the .dat file. By default, this value is provided automatically and
3092 does not need to be specified.
3094 In addition to the above, a set of tile definitions can appear in the
3095 header area. See below for a full description of the B<tiles>
3096 multi-line statement. Any tile definitions provided here remain in
3097 force throughout the file.
3099 =head1 INPUT FILE LEVEL STATEMENTS
3101 Within each level's subsection, the following two statments will
3102 usually appear at the top.
3107 The B<title> statement supplies the level's title, or name. The title
3108 string can be surrounded by double quotes, or unadorned. The
3109 B<password> statement supplies the level's password. This password
3110 must consist of exactly four uppercase alphabetic characters.
3112 If the level's number is 150 or less, the B<password> statement may be
3113 omitted. In that case the level's password will default to match that
3114 level in the original Lynx set. (N.B.: The Lynx ROM file format does
3115 not provide a mechanism for setting passwords, so in that case the
3116 default password will be used regardless.)
3118 The following statements may also appear in a level subsection.
3122 The B<chips> statement defines how many chips are required on this
3123 level to open the chip socket. The default value is zero.
3127 The B<time> statement defines how many seconds are on the level's
3128 clock. The default value is zero (i.e., no time limit).
3132 The B<hint> statement defines the level's hint text. As with the
3133 B<title> statement, the string can either be unadorned or delimited
3134 with double quotes. If a section contains multiple B<hint> statements,
3135 the texts are appended together, e.g.:
3137 hint This is a relatively long hint, and so it
3138 hint is helpful to be able to break it up across
3141 Note that the same can be done with B<title> statements.
3149 The B<tiles> multi-line statement introduces one or more tile
3150 definitions. The definitions appear one per line, until a line
3151 containing B<end> is found. Note that the tile definitions given here
3152 only apply to the current level. A complete description of tile
3153 definitions is given below.
3155 map [ X Y ] map [ X Y ]
3165 The B<map> statement defines the actual contents of (part of) the
3166 level's map. The line containing the B<map> statement can optionally
3167 include a pair of coordinates; these coordinates indicate where the
3168 the section will be located on the level's map. If coordinates are
3169 omitted, the defined section will be located at (0 0) -- i.e., the
3170 upper-left corner of the level. The lines inside the B<map> statement
3171 pictorially define the contents of the map section, until a line
3172 containing B<and> or B<end> is encountered. When the map is terminated
3173 by B<and>, then the lines defining the map section are immediately
3174 followed by lines defining an overlay. The overlay uses the same
3175 origin as the map section (though it is permissible for the overlay to
3176 be smaller than the map section it is paired with). A complete
3177 description of the map and overlay sections is given below.
3181 The B<border> statement specifies a tile. The edges of the map are
3182 then changed to contain this tile. Typically this is used to enclose
3185 The following statements are also available, though they are usually
3186 not needed. They provide means for explicitly defining level data, for
3187 the occasional situation where the usual methods are more cumbersome.
3189 creatures X1 Y1 ; X2 Y2 ...
3191 The B<creatures> statements permits explicit naming of the coordinates
3192 in the creature list. Pairs of coordinates are separated from each
3193 other by semicolons; any number of coordinate pairs can be specified.
3194 There can be multiple B<creatures> statements in a level's subsection.
3196 traps P1 Q1 -> R1 S1 ; P2 Q2 -> R2 S2 ...
3198 The B<traps> statement permits explicit naming of the coordinates for
3199 elements in the bear trap list. Coordinates are given in one or more
3200 groups of four, separated by semicolons. Each group consists of the x-
3201 and y-coordinates of the brown button, an arrow (->), and then the x-
3202 and y-coordinates of the bear trap. Any number of B<traps> statements
3203 can appear in a level's subsection.
3205 cloners P1 Q1 -> R1 S1 ; P2 Q2 -> R2 S2 ...
3207 The B<cloners> statement permits explicit naming of elements in the
3208 clone machine list. It uses the same syntax as the B<traps> statment,
3209 with the red button's coordinates preceding the coordinates of the
3214 The B<level> statement defines the level's number. By default it is
3215 one more than the number of the prior level.
3217 field NN B01 B02 ...
3219 The B<field> statement allows fields to be directly specified and
3220 embedded in the .dat file. The first argument specifies the field
3221 number; the remaining arguments provide the byte values for the actual
3222 field data. These statements are only meaningful in conjunction with
3223 producing a .dat file.
3225 =head1 DEFINING TILES
3227 A tile definition consists of two parts. The first part is either one
3228 or two characters. The characters can be letters, numbers, punctuation
3229 -- anything except spaces. The second part is the name of a tile or a
3230 pair of tiles. The characters then become that tile's representation.
3232 Here is an example of some tile definitions:
3241 (Note that a single tab character comes after the characters and
3242 before the tile names.) Once these definitions have been provided, the
3243 newly-defined characters can then be used in a map.
3245 The above definitions all use singular tiles. To define a pair of
3246 tiles, combine the two names with a plus sign, like so:
3250 G glider north + clone machine
3253 Notice that the top tile is named first, then the bottom tile.
3255 The B<tiles> statement is the only statement that can appear in the
3256 header, as well as in a level's subsection. Tile definitions in the
3257 header are global, and can be used in every subsection. Tile
3258 definitions inside a subsection are local, and apply only to that
3261 A number of tile definitions are pre-set ahead of time, supplying
3262 standard representations for some of the most common tiles. (If these
3263 representations are not desired, the characters can always be
3264 redefined.) Here are some of the built-in definitions:
3266 # wall $ computer chip
3270 6 bomb ? hint button
3272 See below for the complete list of tile names and built-in
3275 A few groups tiles allow one to specify multiple definitions in a
3276 single line. For example:
3282 This one definition is equivalent to the following:
3291 (Note that "G" by itself is still undefined.) All creatures, including
3292 Chip, can be defined using this abbreviated form.
3294 Doors and keys are the other groups that have this feature; the
3295 following definition:
3312 Once all the needed tiles have defined representations, using the map
3313 statement is a simple matter. Here is an example:
3323 This is a map of a small room. A block stands in the way of the
3324 entrance. Three of the four corners contain fire; the fourth contains
3325 a chip. On the east wall is an exit guarded by a chip socket.
3327 Note that each cell in the map is two characters wide. (Thus, for
3328 example, the octothorpes describe a solid wall around the room.)
3330 Here is a larger example, which presents the map from LESSON 2:
3341 # # # # # # # # # # # #
3343 # E H # # B , , [][]C ? #
3345 # # # # # # # # # # # #
3351 There are a couple of different ways to fill a cell with two tiles.
3352 The first way is to simply use tile definitions which contains two
3357 G glider east + clone machine
3367 The second way is to squeeze two representations into a single cell.
3368 Obviously, this can only be done with both representations are a
3384 In both cases, the top tile always comes before the bottom tile. Note
3385 that you can "bury" a tile by placing it to the right of a space:
3393 Any number of map statements can appear in a level's subsection. The
3394 map statements will be combined together to make the complete map.
3396 =head1 OVERLAY SECTIONS
3398 Every map statement can optionally include an overlay section. This
3399 overlay permits button connections and monster ordering to be defined.
3401 The overlay is applied to the same position as the map section it
3402 accompanies. The overlay can duplicate parts of the map section it
3403 covers, and any such duplication will be ignored. The only characters
3404 in the overlay that are significant are the ones that differ from the
3405 map section it covers. These characters are treated as labels. Labels
3406 are always a single character; two non-space characters in a cell
3407 always indicates two separate labels. Any non-space characters can be
3408 used as labels, as long as they don't match up with the map.
3410 An overlay section defines a button connection by using the same label
3411 in two (or more) cells. One of the labelled cells will contain either
3412 a bear trap or a clone machine, and the other will contain the
3413 appropriate button. If there are more than two cells with the same
3414 label, all but one should contain a button.
3416 Characters that only appear once in an overlay, on the other hand,
3417 indicate creatures. The characters then indicate the ordering of the
3418 creatures in the creature list with respect to each other. The
3419 ordering of characters is the usual ASCII sequence (e.g., numbers
3420 first, then capital letters, then lowercase letters).
3422 For example, here is a map with an overlay that demonstrates all three
3445 In this example, capitals are used for the clone machine connections,
3446 lowercase for the bear trap connections, and numbers are used for the
3449 (Note that the gliders atop clone machines are not numbered. While it
3450 is not an error to include clone machine creatures in the ordering,
3451 they are ignored under the MS ruleset.)
3453 It is not necessary to reproduce any of the map section's text in the
3454 overlay section. Blanks can be used instead. The ignoring of matching
3455 text is simply a feature designed to assist the user in keeping the
3456 overlay's contents properly aligned.
3458 The B<traps>, B<cloners>, and B<creatures> statements can be used in
3459 lieu of, or in conjunction with, data from overlay sections. In the
3460 case of the creature list, items are added to the list in the order
3461 that they are encountered in the source text.
3463 If a level contains no overlay information and none of the above three
3464 statements, then this information will be filled in automatically. The
3465 data will be determined by following the original Lynx-based rules --
3466 viz., buttons are connected to the next beartrap/clone machine in
3467 reading order, wrapping around to the top if necessary. (Likewise, the
3468 creature ordering is just the order of the creatures in their initial
3469 placement, modified by swapping the first creature with Chip.) Thus,
3470 if you actually want to force an empty bear trap list, clone machine
3471 list, or creature list, you must include an empty B<traps>,
3472 B<cloners>, and/or B<creatures> statement.
3476 Here is the complete list of tiles as they are named in definitions.
3477 Two or more names appearing on the same line indicates that they are
3478 two different names for the same tile. Note that the tile names are
3479 not case-sensitive; capitalization is ignored.
3488 computer chip ic chip
3491 ice corner southeast ice se
3492 ice corner southwest ice sw
3493 ice corner northwest ice nw
3494 ice corner northeast ice ne
3495 force floor north force north
3496 force floor south force south
3497 force floor east force east
3498 force floor west force west
3499 force floor random force random force any
3500 hidden wall permanent invisible wall permanent
3501 hidden wall temporary invisible wall temporary
3502 wall north partition north
3503 wall south partition south
3504 wall east partition east
3505 wall west partition west
3506 wall southeast partition southeast wall se
3507 closed toggle wall closed toggle door toggle closed
3508 open toggle wall open toggle door toggle open
3511 green door door green
3512 yellow door door yellow
3516 yellow key key yellow
3517 blue button button blue tank button
3518 red button button red clone button
3519 green button button green toggle button
3520 brown button button brown trap button
3521 blue block floor blue wall fake
3522 blue block wall blue wall real
3529 clone machine cloner
3530 water boots water shield flippers
3531 fire boots fire shield
3532 ice boots spiked shoes skates
3533 force boots magnet suction boots
3534 block moveable block
3535 cloning block north block north
3536 cloning block south block south
3537 cloning block east block east
3538 cloning block west block west
3546 paramecium north centipede north
3547 fireball north flame north
3548 glider north ghost north
3550 walker north dumbbell north
3551 teeth north frog north
3553 (The last nine lines, listing the creatures, only show the
3554 north-facing versions. The remaining 27 names, for the south-, east-,
3555 and west-facing versions, follow the obvious patttern.)
3557 Note that tile names may be abbreviated to any unique prefix. In
3558 particular, this permits one to write names like "glider north" as
3561 There are also tile names for the "extra" MS tiles. These tiles are
3562 listed in parentheses, as an indicator that they were not originally
3563 intended to be used in maps.
3575 (chip swimming north) (chip swimming n)
3576 (chip swimming west) (chip swimming w)
3577 (chip swimming south) (chip swimming s)
3578 (chip swimming east) (chip swimming e)
3580 Finally, note that one can also explicitly refer to tiles by their
3581 hexadecimal byte value under the MS rules by using the "0x" prefix.
3582 Thus, the names "0x2A" and "bomb" are equivalent.
3584 =head1 PREDEFINED TILE DEFINITIONS
3586 The following is the complete list of built-in tile definitions:
3593 ~ wall north ^ force floor north
3594 _ wall south v force floor south
3595 | wall west < force floor west
3596 | wall east > force floor east
3597 _| wall southeast <> force floor random
3598 ? hint button @ chip south
3600 ^] cloning block north + clone machine
3601 <] cloning block west + clone machine
3602 v] cloning block south + clone machine
3603 >] cloning block east + clone machine
3607 c4, Copyright (C) 2003-2006 Brian Raiter <breadbox@muppetlabs.com>
3609 Permission is hereby granted, free of charge, to any person obtaining
3610 a copy of this software and documentation (the "Software"), to deal in
3611 the Software without restriction, including without limitation the
3612 rights to use, copy, modify, merge, publish, distribute, sublicense,
3613 and/or sell copies of the Software, and to permit persons to whom the
3614 Software is furnished to do so, subject to the following conditions:
3616 The above copyright notice and this permission notice shall be
3617 included in all copies or substantial portions of the Software.
3619 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
3620 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
3621 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
3622 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
3623 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
3624 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
3625 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.