--- tworld-1.3.0.orig/debian/postinst +++ tworld-1.3.0/debian/postinst @@ -0,0 +1,22 @@ +#!/bin/sh +# postinst script for tworld +# copyright 2006-2011 by Gerfried Fuchs +# Licenced under WTFPLv2 + +set -e + +if [ "$1" != configure ]; then + exit 0 +fi + +if dpkg --compare-versions "$2" lt-nl "1.3.0-5"; then + if [ -d /usr/share/doc/tworld ]; then + if rmdir /usr/share/doc/tworld 2>/dev/null; then + ln -s tworld-data /usr/share/doc/tworld + fi + fi +fi + +if [ -x /usr/bin/update-menus ]; then + update-menus +fi --- tworld-1.3.0.orig/debian/rules +++ tworld-1.3.0/debian/rules @@ -0,0 +1,141 @@ +#!/usr/bin/make -f +# debian/rules for tworld +# copyright 2006-2011 by Gerfried Fuchs +# Licenced under WTFPLv2 + +PKG1 = tworld +PKG2 = tworld-data +TMP1 = $(CURDIR)/debian/$(PKG1) +TMP2 = $(CURDIR)/debian/$(PKG2) + +INSTALL = install +INSTALL_FILE = $(INSTALL) -p -oroot -groot -m644 +INSTALL_SCRIPT = $(INSTALL) -p -oroot -groot -m755 +INSTALL_DIR = $(INSTALL) -p -d -oroot -groot -m755 + +# These are used for cross-compiling and for saving the configure script +# from having to guess our platform (since we know it already) +DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) +DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) + + +CFLAGS = -Wall -W -fomit-frame-pointer -DNDEBUG +LDFLAGS = -Wall -W + +ifneq (,$(filter noopt,$(DEB_BUILD_OPTIONS))) + CFLAGS += -O0 +else + CFLAGS += -O2 +endif +ifeq (,$(filter nostrip,$(DEB_BUILD_OPTIONS))) + LDFLAGS += -s + STRIP = true +endif + +include /usr/share/quilt/quilt.make + + +clean: unpatch + $(checkdir) + $(checkroot) + [ ! -f Makefile ] || $(MAKE) clean + -rm -rf $(TMP1) $(TMP2) debian/substvars debian/files build-stamp \ + Makefile oshw-sdl/Makefile oshw install-stamp + + +config.status: configure patch + $(checkdir) + CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" ./configure \ + --host=$(DEB_HOST_GNU_TYPE) --build=$(DEB_BUILD_GNU_TYPE) \ + --prefix=/usr/games --with-sharedir=\$${prefix}/../share/games/tworld \ + --bindir=\$${prefix} --mandir=\$${prefix}/../share/man + + +build: build-stamp +build-stamp: config.status + $(checkdir) + $(MAKE) + touch build-stamp + + +install: install-stamp +install-stamp: build + $(checkdir) + $(checkroot) + -rm -rf $(TMP1) $(TMP2) + + $(MAKE) install prefix=$(TMP2)/usr/games + test "$(STRIP)" != true || strip \ + --remove-section=.comment --remove-section=.note \ + $(TMP2)/usr/games/tworld + + $(INSTALL_DIR) $(TMP1)/usr + cd $(TMP1)/usr && $(INSTALL_DIR) games share/man/man6 share/doc \ + share/applications share/pixmaps + mv $(TMP2)/usr/games/tworld $(TMP1)/usr/games + mv $(TMP2)/usr/share/man/man6/tworld.6 $(TMP1)/usr/share/man/man6 + gzip --best $(TMP1)/usr/share/man/man6/tworld.6 + ln -s $(PKG2) $(TMP1)/usr/share/doc/$(PKG1) + $(INSTALL_FILE) debian/tworld.desktop $(TMP1)/usr/share/applications + $(INSTALL_FILE) debian/tworld.xpm $(TMP1)/usr/share/pixmaps + + $(INSTALL_SCRIPT) debian/c4 $(TMP2)/usr/games + pod2man -s 6 -r 'Tile World' -c '' debian/c4 \ + $(TMP2)/usr/share/man/man6/c4.6 + $(INSTALL_DIR) $(TMP2)/usr/share/doc/$(PKG2) + $(INSTALL_FILE) BUGS README docs/tworld.html \ + $(TMP2)/usr/share/doc/$(PKG2) + $(INSTALL_FILE) Changelog \ + $(TMP2)/usr/share/doc/$(PKG2)/changelog + cd $(TMP2)/usr/share && gzip -9 man/man6/c4.6 \ + doc/$(PKG2)/README doc/$(PKG2)/tworld.html \ + doc/$(PKG2)/changelog + + touch install-stamp + + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + $(checkdir) + $(checkroot) + $(INSTALL_DIR) $(TMP1)/DEBIAN $(TMP2)/DEBIAN \ + $(TMP1)/usr/share/menu + + $(INSTALL_FILE) debian/menu $(TMP1)/usr/share/menu/$(PKG1) + $(INSTALL_SCRIPT) debian/postinst debian/postrm \ + $(TMP1)/DEBIAN + dpkg-shlibdeps -Tdebian/substvars -dDepends \ + $(TMP1)/usr/games/* + dpkg-gencontrol -ldebian/changelog -isp -Tdebian/substvars \ + -p$(PKG1) -P$(TMP1) + cd $(TMP1) && find * -type f ! -regex '^DEBIAN/.*' -print0 | \ + xargs -r0 md5sum > DEBIAN/md5sums + dpkg --build $(TMP1) .. + + $(INSTALL_FILE) debian/copyright debian/README.Debian \ + $(TMP2)/usr/share/doc/$(PKG2) + $(INSTALL_FILE) debian/changelog \ + $(TMP2)/usr/share/doc/$(PKG2)/changelog.Debian + gzip -9 $(TMP2)/usr/share/doc/$(PKG2)/changelog.Debian + dpkg-gencontrol -ldebian/changelog -isp \ + -p$(PKG2) -P$(TMP2) + cd $(TMP2) && find * -type f ! -regex '^DEBIAN/.*' -print0 | \ + xargs -r0 md5sum > DEBIAN/md5sums + dpkg --build $(TMP2) .. + +binary: binary-indep binary-arch + + +define checkdir + test -f debian/rules +endef + +define checkroot + test root = "`whoami`" +endef + +.PHONY: clean build install binary-indep binary-arch binary --- tworld-1.3.0.orig/debian/changelog +++ tworld-1.3.0/debian/changelog @@ -0,0 +1,68 @@ +tworld (1.3.0-6) unstable; urgency=low + + [ Peter Pentchev ] + * Add patch from Drake Wilson to fix solution files for 64 bit architectures + (closes: #427360; LP: #307897) + * Explicit call set -e in maintainer scripts. + * Enhance short description of -data package. + + [ Gerfried Fuchs ] + * Bump Standards-Version to 3.9.2. + * Switch packaging licensing to WTFPLv2. + * Supress error message in postinst if rmdir fails. Also, use lt-nl rather + than le-nl. + * Add Changelog as upstream changelog. + * Add some typo fixes into patch fix-manpage. + + -- Gerfried Fuchs Tue, 24 May 2011 22:53:12 +0200 + +tworld (1.3.0-5) unstable; urgency=low + + * Switch doc dir from tworld to symlink as dpkg isn't able to do so. + + -- Gerfried Fuchs Fri, 12 Sep 2008 15:13:35 +0200 + +tworld (1.3.0-4) unstable; urgency=low + + * Removed debian/docs cruft. + * Switched Maintainer to Games Team. + * Added copyright informations for c4 to debian/copyright. + * Switched packaging licensing to BSD style. + * Added Vcs-* informations to debian/control. + * Split the architecture independent data files into its own tworld-data + package. + * Use filter instead of findstring for DEB_BUILD_OPTIONS parsing as + suggested by policy. + * Strip unneeded sections from binary. + + -- Gerfried Fuchs Fri, 12 Sep 2008 13:40:26 +0200 + +tworld (1.3.0-3) unstable; urgency=low + + * Updated to Standards-Version 3.8.0: + - Added Homepage source control field. + - Added README.source because of quilt usage. + * Add patch for fixing the manual pages, add quilt Build-Depends for that. + * Updated copyright informations a bit. + * Moved c4 into the debian directory, no changes outside of debian/ anymore. + * Add desktop file (LP: #220311) + + -- Gerfried Fuchs Tue, 10 Jun 2008 12:53:47 +0200 + +tworld (1.3.0-2) unstable; urgency=low + + * Remove -s from LDFLAGS, makes DEB_BUILD_OPTIONS=nostrip work + (closes: #438228) + * Remove perl from Build-Depends since it's Build-Essential: yes. + * Test for Makefile instead of ignoring make clean errors. + * Switched to menu section Games/Puzzles due to removed Arcade section from + new menu policy. + * Added menu icon debian/tworld.xpm extracted from res/tiles.bmp. + + -- Gerfried Fuchs Mon, 20 Aug 2007 14:50:49 +0200 + +tworld (1.3.0-1) unstable; urgency=low + + * Initial release (closes: #373416) + + -- Gerfried Fuchs Tue, 13 Jun 2006 06:27:02 -0500 --- tworld-1.3.0.orig/debian/watch +++ tworld-1.3.0/debian/watch @@ -0,0 +1,5 @@ +# Compulsory line, this is a version 3 file +version=3 + +http://www.muppetlabs.com/~breadbox/pub/software/tworld/ tworld-([0-9.]+)\.tar\.gz debian uupdate +# URL pattern version script --- tworld-1.3.0.orig/debian/menu +++ tworld-1.3.0/debian/menu @@ -0,0 +1,3 @@ +?package(tworld):needs="X11" section="Games/Puzzles" \ + title="Tile World" command="/usr/games/tworld" \ + icon="/usr/share/pixmaps/tworld.xpm" --- tworld-1.3.0.orig/debian/copyright +++ tworld-1.3.0/debian/copyright @@ -0,0 +1,63 @@ +This package was debianized by Gerfried Fuchs on +Tue, 13 Jun 2006 06:27:02 -0500. + +It was downloaded from + +Copyright Holder: Brian Raiter + +License: +======== + Copyright (C) 2001-2006 by Brian Raiter + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + ------------------------------------------------------------------------- + + c4, Copyright (C) 2003-2006 Brian Raiter + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and documentation (the "Software"), to deal in + the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +On Debian GNU/Linux systems, the complete text of the GNU General +Public License version 2 can be found in `/usr/share/common-licenses/GPL-2', +later versions can be found in the same directory. + + +The Debian packaging is Copyright 2006-2011 by +Gerfried Fuchs , licensed under WTFPLv2: + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. --- tworld-1.3.0.orig/debian/tworld.xpm +++ tworld-1.3.0/debian/tworld.xpm @@ -0,0 +1,364 @@ +/* XPM */ +static char * tworld_xpm[] = { +"32 32 329 2", +" c None", +". c #77490C", +"+ c #744606", +"@ c #6E440C", +"# c #694210", +"$ c #66430F", +"% c #6A440A", +"& c #724500", +"* c #68400C", +"= c #764A0F", +"- c #784A08", +"; c #5D4525", +"> c #584326", +", c #564031", +"' c #4C412D", +") c #4D3B20", +"! c #563E18", +"~ c #663F05", +"{ c #543C18", +"] c #714710", +"^ c #59442D", +"/ c #4E4733", +"( c #584837", +"_ c #574330", +": c #56462E", +"< c #4F3E33", +"[ c #433A2D", +"} c #45351F", +"| c #483114", +"1 c #5E452B", +"2 c #4E442B", +"3 c #574B31", +"4 c #554833", +"5 c #58483A", +"6 c #534230", +"7 c #4F3E32", +"8 c #483B2B", +"9 c #3F3624", +"0 c #392E1F", +"a c #7D423C", +"b c #584130", +"c c #56442E", +"d c #5E4A33", +"e c #50442F", +"f c #59473B", +"g c #5A4639", +"h c #4C432B", +"i c #42352D", +"j c #3D3129", +"k c #382E26", +"l c #522E29", +"m c #A74949", +"n c #A24747", +"o c #954543", +"p c #5C3C27", +"q c #4F442C", +"r c #514435", +"s c #544536", +"t c #534033", +"u c #614E37", +"v c #4A3B23", +"w c #42392C", +"x c #393029", +"y c #38251E", +"z c #552626", +"A c #783535", +"B c #7E3939", +"C c #A54A4A", +"D c #A34747", +"E c #884136", +"F c #4C371E", +"G c #463D2C", +"H c #50482D", +"I c #4E442D", +"J c #594B2F", +"K c #4F4028", +"L c #4B4028", +"M c #413727", +"N c #3B301A", +"O c #281E0E", +"P c #3A1B13", +"Q c #662C2C", +"R c #793535", +"S c #6C3535", +"T c #9B4545", +"U c #9B4444", +"V c #964242", +"W c #693921", +"X c #483708", +"Y c #46382A", +"Z c #67553F", +"` c #6A573E", +" . c #54482D", +".. c #493A27", +"+. c #4B3F2D", +"@. c #4A392E", +"#. c #35291A", +"$. c #211A05", +"%. c #2E1710", +"&. c #582626", +"*. c #6C2F2F", +"=. c #5D2D2D", +"-. c #814949", +";. c #783434", +">. c #773434", +",. c #753333", +"'. c #493109", +"). c #422D00", +"!. c #451E2A", +"~. c #5A3644", +"{. c #6C504A", +"]. c #514239", +"^. c #483D31", +"/. c #4C3C3A", +"(. c #432C37", +"_. c #46262B", +":. c #231500", +"<. c #211500", +"[. c #4A1D1D", +"}. c #522222", +"|. c #481C1C", +"1. c #804949", +"2. c #6C2E2E", +"3. c #4F2020", +"4. c #502321", +"5. c #3D2C00", +"6. c #402609", +"7. c #4A1F26", +"8. c #583040", +"9. c #5C3A42", +"0. c #4C3739", +"a. c #453033", +"b. c #422F33", +"c. c #3F2534", +"d. c #441E23", +"e. c #2E1809", +"f. c #1C1500", +"g. c #421C19", +"h. c #461C1C", +"i. c #513A3A", +"j. c #764949", +"k. c #5F2A2A", +"l. c #481D1D", +"m. c #3F231F", +"n. c #2D1F00", +"o. c #3A1E11", +"p. c #512222", +"q. c #652C2C", +"r. c #632A2A", +"s. c #542323", +"t. c #592626", +"u. c #502323", +"v. c #341A0E", +"w. c #3D201F", +"x. c #461D1D", +"y. c #4A2929", +"z. c #7A3E3E", +"A. c #482222", +"B. c #453230", +"C. c #1E1500", +"D. c #371A11", +"E. c #572525", +"F. c #4E1F1F", +"G. c #512424", +"H. c #4F2323", +"I. c #491C1C", +"J. c #413634", +"K. c #4A2C2C", +"L. c #8A3C3C", +"M. c #733232", +"N. c #4D2626", +"O. c #433635", +"P. c #471C1C", +"Q. c #2D1809", +"R. c #3F3836", +"S. c #472121", +"T. c #863B3B", +"U. c #763333", +"V. c #4E2727", +"W. c #484B3C", +"X. c #1C3311", +"Y. c #2E2711", +"Z. c #4C2020", +"`. c #2D2311", +" + c #1C2F11", +".+ c #44443C", +"++ c #482121", +"@+ c #743434", +"#+ c #622B2B", +"$+ c #4A2626", +"%+ c #245624", +"&+ c #296129", +"*+ c #344523", +"=+ c #46251E", +"-+ c #42231C", +";+ c #2E3A1C", +">+ c #1C461C", +",+ c #471E1E", +"'+ c #693535", +")+ c #4E2121", +"!+ c #482525", +"~+ c #265B26", +"{+ c #2D682D", +"]+ c #285926", +"^+ c #2F4422", +"/+ c #3E311C", +"(+ c #412F1F", +"_+ c #412D1F", +":+ c #3B2F1C", +"<+ c #293D1C", +"[+ c #1D461C", +"}+ c #4C2727", +"|+ c #472020", +"1+ c #754946", +"2+ c #592E2A", +"3+ c #2A642A", +"4+ c #225222", +"5+ c #1C471C", +"6+ c #1E471E", +"7+ c #1C491C", +"8+ c #4B201F", +"9+ c #513833", +"0+ c #9A7766", +"a+ c #97765E", +"b+ c #315E31", +"c+ c #2A612A", +"d+ c #215021", +"e+ c #1D471D", +"f+ c #274827", +"g+ c #274A27", +"h+ c #1C481C", +"i+ c #204C20", +"j+ c #1D4A1D", +"k+ c #745C4B", +"l+ c #614C3D", +"m+ c #927963", +"n+ c #8C7159", +"o+ c #7E6856", +"p+ c #556C55", +"q+ c #285728", +"r+ c #204D20", +"s+ c #244824", +"t+ c #285328", +"u+ c #245424", +"v+ c #225022", +"w+ c #1E491E", +"x+ c #1F471F", +"y+ c #76624E", +"z+ c #6E5846", +"A+ c #564637", +"B+ c #927F70", +"C+ c #6C5949", +"D+ c #255325", +"E+ c #245524", +"F+ c #275A27", +"G+ c #1D481D", +"H+ c #2B4C2B", +"I+ c #7C6550", +"J+ c #5E4B3B", +"K+ c #55463B", +"L+ c #5F4F41", +"M+ c #2D652D", +"N+ c #1E4B1E", +"O+ c #1F4C1F", +"P+ c #265826", +"Q+ c #204E20", +"R+ c #1C451C", +"S+ c #72665D", +"T+ c #67584C", +"U+ c #5C5148", +"V+ c #565F33", +"W+ c #264E21", +"X+ c #1E421E", +"Y+ c #235423", +"Z+ c #1F451C", +"`+ c #273509", +" @ c #7C5E24", +".@ c #6D5605", +"+@ c #484911", +"@@ c #363909", +"#@ c #171400", +"$@ c #221C00", +"%@ c #333609", +"&@ c #2B3A11", +"*@ c #343105", +"=@ c #2A1E00", +"-@ c #84610B", +";@ c #89661A", +">@ c #75570D", +",@ c #4C3800", +"'@ c #1B1300", +")@ c #221900", +"!@ c #3A2B00", +"~@ c #3B2B00", +"{@ c #302300", +"]@ c #846414", +"^@ c #AB906C", +"/@ c #8B6E2B", +"(@ c #5F4400", +"_@ c #3F2D06", +":@ c #382800", +"<@ c #453205", +"[@ c #3D2D05", +"}@ c #342600", +"|@ c #100C00", +"1@ c #7F6114", +"2@ c #89671A", +"3@ c #7C5B0A", +"4@ c #5F4500", +"5@ c #35290D", +"6@ c #4A3916", +"7@ c #543D05", +"8@ c #453305", +"9@ c #745B27", +"0@ c #785800", +"a@ c #725300", +"b@ c #553E04", +"c@ c #3A311E", +"d@ c #5A4204", +"e@ c #523B00", +"f@ c #493500", +"g@ c #2E2100", +"h@ c #67542B", +"i@ c #614A1C", +"j@ c #4C3E22", +"k@ c #4A3C1D", +"l@ c #4C3700", +"m@ c #1D1A17", +"n@ c #000000", +" ", +" ", +" . + @ # $ % & * ", +" = - ; > , ' ) ! ~ { ", +" ] ^ / ( _ : < [ } | ", +" 1 2 3 4 5 6 7 8 9 0 ", +" a b c d e f g h i j k l ", +" m n o p q r s t u v w x y z A B ", +" C m D E F G H I J K L M N O P Q R S ", +" T U V W X Y Z ` ...+.@.#.$.%.&.*.=. ", +" -.;.>.,.'.).!.~.{.].^./.(._.:.<.[.}.|. ", +" 1.2.3.4.5.6.7.8.9.0.a.b.c.d.e.f.g.h.h.i. ", +" j.k.l.m.n.o.p.q.r.s.t.u.|.h.v.f.w.x.h.y. ", +" z.}.A.B.C.D.u.E.F.l.G.H.I.h.D.f.J.A.h.K. ", +" L.M.N.O.f.e.P.h.h.h.I.P.h.h.Q.f.R.S.h.h. ", +" T.U.V.W.X.Y.P.I.|.h.Z.l.h.h.`. +.+++h.h. ", +" @+#+$+ %+&+*+=+P.h.|.h.-+;+>+>+ h.h.,+ ", +" '+)+!+ ~+{+]+^+/+(+_+:+<+[+>+>+ }+P.|+ ", +" 1+2+ ~+3+4+5+5+6+>+>+5+7+>+>+ 8+9+ ", +" 0+a+ b+c+d+e+>+f+g+h+i+j+>+g+ k+l+ ", +" m+n+o+ p+q+r+5+>+s+t+u+v+w+x+ y+z+A+ ", +" B+C+ D+r+5+>+>+E+F+d+G+H+ I+J+K+ ", +" L+ M+4+N+>+>+O+P+Q+5+R+ S+T+U+ ", +" V+W+e+>+R+X+Y+G+Z+`+ ", +" @.@+@@@#@$@%@&@*@=@ ", +" -@;@>@,@'@)@!@~@~@{@ ", +" ]@^@/@(@_@:@<@[@~@}@|@ ", +" 1@2@3@4@5@6@7@8@5.}@|@ ", +" 9@0@a@b@c@ d@e@f@g@ ", +" h@i@j@ k@l@5. ", +" m@ n@n@ ", +" n@n@ n@ "}; --- tworld-1.3.0.orig/debian/c4 +++ tworld-1.3.0/debian/c4 @@ -0,0 +1,3627 @@ +#!/usr/bin/perl -w + +# +# c4: Chip's Challenge Combined Converter +# +# Use "perldoc c4" to read the documentation. +# +# Copyright (C) 2003-2006 Brian Raiter. This program is licensed under +# an MIT-style license. Please see the documentation for details. +# + +use strict; + +# +# First, some global functions used across packages. +# + +package main; + +# All the names of all the tiles. +# +my @tilenames; +my %tilenames; +foreach my $names + ([ "empty", "floor" ], + [ "wall" ], + [ "ic chip", "computer chip" ], + [ "water" ], + [ "fire" ], + [ "hidden wall", "invisible wall permanent", "inv wall permanent" ], + [ "wall north", "partition north", "blocked north" ], + [ "wall west", "partition west", "blocked west" ], + [ "wall south", "partition south", "blocked south" ], + [ "wall east", "partition east", "blocked east" ], + [ "block", "moveable block", "movable block" ], + [ "dirt" ], + [ "ice" ], + [ "force floor south", "force south", "slide south", + "slide floor south" ], + [ "block north", "cloning block north" ], + [ "block west", "cloning block west" ], + [ "block south", "cloning block south" ], + [ "block east", "cloning block east" ], + [ "force floor north", "force north", "slide north", + "slide floor north" ], + [ "force floor east", "force east", "slide east", "slide floor east" ], + [ "force floor west", "force west", "slide west", "slide floor west" ], + [ "exit" ], + [ "blue door", "door blue" ], + [ "red door", "door red" ], + [ "green door", "door green" ], + [ "yellow door", "door yellow" ], + [ "ice wall southeast", "ice wall se", "ice se", + "ice corner southeast", "ice corner se" ], + [ "ice wall southwest", "ice wall sw", "ice sw", + "ice corner southwest", "ice corner sw" ], + [ "ice wall northwest", "ice wall nw", "ice nw", + "ice corner northwest", "ice corner nw" ], + [ "ice wall northeast", "ice wall ne", "ice ne", + "ice corner northeast", "ice corner ne" ], + [ "blue block floor", "blue block fake", "blue wall fake" ], + [ "blue block wall", "blue block real", "blue wall real" ], + [ "(combination)" ], + [ "thief", "spy" ], + [ "socket" ], + [ "green button", "button green", "toggle button", "button toggle" ], + [ "red button", "button red", "clone button", "button clone" ], + [ "toggle closed", "toggle wall closed", "closed toggle wall", + "toggle door closed", "closed toggle door" ], + [ "toggle open", "toggle wall open", "open toggle wall", + "toggle door open", "open toggle door" ], + [ "brown button", "button brown", "trap button", "button trap" ], + [ "blue button", "button blue", "tank button", "button tank" ], + [ "teleport" ], + [ "bomb" ], + [ "trap", "beartrap", "bear trap" ], + [ "invisible wall", "invisible wall temporary", "inv wall temporary" ], + [ "gravel" ], + [ "popup wall", "pass once" ], + [ "hint button" ], + [ "wall southeast", "partition southeast", "blocked southeast", + "wall se", "partition se", "blocked se" ], + [ "clone machine", "cloner", "cloning machine" ], + [ "force floor any", "force any", "slide any", "slide floor any", + "force floor random", "force random", + "slide random", "slide floor random", + "random slide floor" ], + [ "(chip drowned)" ], + [ "(chip burned)" ], + [ "(chip bombed)" ], + [ "(unused 1)" ], + [ "(unused 2)" ], + [ "(unused 3)" ], + [ "(exiting)" ], + [ "(exit 1)" ], + [ "(exit 2)" ], + [ "(chip swimming north)", "(chip swimming n)" ], + [ "(chip swimming west)", "(chip swimming w)" ], + [ "(chip swimming south)", "(chip swimming s)" ], + [ "(chip swimming east)", "(chip swimming e)" ], + [ "bug north", "bee north" ], + [ "bug west", "bee west" ], + [ "bug south", "bee south" ], + [ "bug east", "bee east" ], + [ "fireball north", "flame north" ], + [ "fireball west", "flame west" ], + [ "fireball south", "flame south" ], + [ "fireball east", "flame east" ], + [ "ball north" ], + [ "ball west" ], + [ "ball south" ], + [ "ball east" ], + [ "tank north" ], + [ "tank west" ], + [ "tank south" ], + [ "tank east" ], + [ "glider north", "ghost north" ], + [ "glider west", "ghost west" ], + [ "glider south", "ghost south" ], + [ "glider east", "ghost east" ], + [ "teeth north", "frog north" ], + [ "teeth west", "frog west" ], + [ "teeth south", "frog south" ], + [ "teeth east", "frog east" ], + [ "walker north", "dumbbell north" ], + [ "walker west", "dumbbell west" ], + [ "walker south", "dumbbell south" ], + [ "walker east", "dumbbell east" ], + [ "blob north" ], + [ "blob west" ], + [ "blob south" ], + [ "blob east" ], + [ "paramecium north", "centipede north" ], + [ "paramecium west", "centipede west" ], + [ "paramecium south", "centipede south" ], + [ "paramecium east", "centipede east" ], + [ "blue key", "key blue" ], + [ "red key", "key red" ], + [ "green key", "key green" ], + [ "yellow key", "key yellow" ], + [ "water boots", "boots water", "water shield", "flippers" ], + [ "fire boots", "boots fire", "fire shield" ], + [ "ice boots", "boots ice", "spike shoes", "spiked shoes", + "ice skates", "skates" ], + [ "force boots", "boots force", "slide boots", "boots slide", + "magnet", "suction boots" ], + [ "chip north" ], + [ "chip west" ], + [ "chip south" ], + [ "chip east" ]) +{ + push @tilenames, $names->[0]; + @tilenames{@$names} = ($#tilenames) x @$names; +} + +# The original 150 passwords. +# +my @origpasswords = @{ + [qw(BDHP JXMJ ECBQ YMCJ TQKB WNLP FXQO NHAG + KCRE VUWS CNPE WVHI OCKS BTDY COZQ SKKK + AJMG HMJL MRHR KGFP UGRW WZIN HUVE UNIZ + PQGV YVYJ IGGZ UJDD QGOL BQZP RYMS PEFS + BQSN NQFI VDTM NXIS VQNK BIFA ICXY YWFH + GKWD LMFU UJDP TXHL OVPZ HDQJ LXPP JYSF + PPXI QBDH IGGJ PPHT CGNX ZMGC SJES FCJE + UBXU YBLT BLDM ZYVI RMOW TIGW GOHX IJPQ + UPUN ZIKZ GGJA RTDI NLLY GCCG LAJM EKFT + QCCR MKNH MJDV NMRH FHIC GRMO JINU EVUG + SCWF LLIO OVPJ UVEO LEBX FLHH YJYS WZYV + VCZO OLLM JPQG DTMI REKF EWCS BIFQ WVHY + IOCS TKWD XUVU QJXR RPIR VDDU PTAC KWNL + YNEG NXYB ECRE LIOC KZQR XBAO KRQJ NJLA + PTAS JWNL EGRW HXMF FPZT OSCW PHTY FLXP + BPYS SJUM YKZE TASX MYRT QRLD JMWZ FTLA + HEAN XHIZ FIRD ZYFA TIGG XPPH LYWO LUZL + HPPX LUJT VLHH SJUK MCJE UCRY OKOR GVXQ + YBLI JHEN COZA RGSK DIGW GNLP)] +}; + +# Return true if the given tile is one of the creatures, one of the +# blocks, or Chip. +# +sub iscreature($) { $_[0] >= 0x40 && $_[0] < 0x64 } +sub isblock($) { $_[0] == 0x0A || ($_[0] >= 0x0E && $_[0] < 0x12) } +sub ischip($) { $_[0] >= 0x6C && $_[0] < 0x70 } + +my $filename = undef; +my $filepos = undef; +my $filelevel = undef; +sub err(@) +{ + if (defined $filename) { + if (defined $filelevel) { + print STDERR "$filename: level $filelevel: "; + } elsif (defined $filepos) { + print STDERR "$filename, byte $filepos: "; + } elsif ($.) { + print STDERR "$filename:$.: "; + } else { + print STDERR "$filename: "; + } + } else { + if (defined $filelevel) { + print STDERR "$filename: level $filelevel: "; + } elsif (defined $filepos) { + print STDERR "byte $filepos: "; + } elsif ($.) { + print STDERR "line $.: "; + } + } + print STDERR @_, "\n"; + return; +} + +# Given a pack template, return the size of the packed data in bytes. +# The template is assumed to only contain the types a, C, v, and V. +# +sub packlen($) +{ + my $template = shift; + my $size = 0; + while (length $template) { + my $char = substr $template, 0, 1, ""; + my $n = $char eq "V" ? 4 : $char eq "v" ? 2 : 1; + $n *= $1 if $template =~ s/\A(\d+)//; + $size += $n; + } + return $size; +} + +# Read a sequence of bytes from a binary file, according to a pack +# template. The unpacked values are returned. +# +sub fileread($$;\$@) +{ + my $input = shift; + my $template = shift; + my $levelsize = shift; + my ($buf, $len); + $len = ::packlen $template; + return ::err "invalid template given to fileread" unless $len > 0; + my $ret = sysread $input, $buf, $len; + return ::err $! unless defined $ret; + return ::err "unexpected EOF" unless $ret; + $filepos ||= 0; + $filepos += $ret; + if (ref $levelsize) { + return ::err "invalid metadata in data file", + " (expecting $len bytes; found only $$levelsize)" + unless $len <= $$levelsize; + $$levelsize -= $len; + } + my (@fields) = (unpack $template, $buf); + foreach my $field (@fields) { + last unless @_; + my $min = shift; + my $max = shift; + return ::err "invalid data in data file" + if defined $min && $field < $min or defined $max && $field > $max; + } + return wantarray ? @fields : $fields[-1]; +} + +# Translate escape sequences in the given string. +# +sub unescape($) +{ + local $_ = shift; + s/\\([0-7][0-7][0-7])/chr oct$1/eg; + s/\\([\\\"])/$1/g; + return $_; +} + +sub escape($) +{ + local $_ = shift; + s/([\\\"])/\\$1/g; + s/([^\020-\176])/sprintf"\\%03o",ord$1/eg; + return $_; +} + +# Take a standard creature list from a dat file and augment it as +# necessary for a Lynx-based file format. This involves adding entries +# for Chip, blocks, immobile creatures, and creatures on clone +# machines. +# +sub makelynxcrlist($$) +{ + my $map = shift; + my $datcreatures = shift; + my @crlist; + my @listed; + + if (defined $datcreatures) { + foreach my $n (0 .. $#$datcreatures) { + $listed[$datcreatures->[$n][0]][$datcreatures->[$n][1]] = $n; + } + } + + my $chip = undef; + foreach my $y (0 .. 31) { + foreach my $x (0 .. 31) { + my $obj = $map->[$y][$x][0]; + next unless ::iscreature $obj || ::isblock $obj || ::ischip $obj; + my ($seq, $ff, $mobile) = (0, 0, 1); + if (::ischip $obj) { + return "multiple Chips present" if defined $chip; + $chip = @crlist; + } elsif (::isblock $obj) { + $mobile = -1 if $map->[$y][$x][1] == $tilenames{"cloner"}; + } else { + if ($map->[$y][$x][1] == $tilenames{"cloner"}) { + $mobile = -1; + } else { + $mobile = defined $listed[$y][$x] ? 1 : 0; + } + $seq = $listed[$y][$x] + 1 if defined $listed[$y][$x]; + } + push @crlist, [ $seq, $y, $x, $mobile ]; + } + } + return "Chip absent" unless defined $chip; + return "over 128 creatures" if @crlist > 128; + ($crlist[$chip], $crlist[0]) = ($crlist[0], $crlist[$chip]); + + my @sortlist; + foreach my $n (0 .. $#crlist) { push @sortlist, $n if $crlist[$n][0] } + @sortlist = sort { $crlist[$a][0] <=> $crlist[$b][0] } @sortlist; + + my @lynxcreatures; + foreach my $n (0 .. $#crlist) { + my $creature = $crlist[$n]; + $creature = $crlist[shift @sortlist] if $creature->[0]; + push @lynxcreatures, [ $creature->[1], + $creature->[2], + $creature->[3] ]; + } + + return \@lynxcreatures; +} + +# Translate a creature list from a lynx-based file format to one +# appropriate for a dat-based file format. +# +sub makedatcrlist($$) +{ + my $map = shift; + my $lynxcreatures = shift; + my @crlist; + + return undef unless defined $lynxcreatures; + + foreach my $creature (@$lynxcreatures) { + next if $creature->[2] != 1; + next if ::ischip $map->[$creature->[0]][$creature->[1]][0]; + next if ::isblock $map->[$creature->[0]][$creature->[1]][0]; + push @crlist, [ $creature->[0], $creature->[1] ]; + } + + return \@crlist; +} + +# +# The textual source file format +# + +package txtfile; + +# The list of default tile symbols. +# +my %tilesymbols = %{{ + " " => $tilenames{"empty"}, + "#" => $tilenames{"wall"}, + "\$" => $tilenames{"ic chip"}, + "," => $tilenames{"water"}, + "&" => $tilenames{"fire"}, + "~" => $tilenames{"wall north"}, + "|" => $tilenames{"wall west"}, + "_" => $tilenames{"wall south"}, + " |" => $tilenames{"wall east"}, + "[]" => $tilenames{"block"}, + "[" => $tilenames{"block"}, + ";" => $tilenames{"dirt"}, + "=" => $tilenames{"ice"}, + "v" => $tilenames{"force south"}, + "^" => $tilenames{"force north"}, + ">" => $tilenames{"force east"}, + "<" => $tilenames{"force west"}, + "E" => $tilenames{"exit"}, + "H" => $tilenames{"socket"}, + "6" => $tilenames{"bomb"}, + ":" => $tilenames{"gravel"}, + "?" => $tilenames{"hint button"}, + "_|" => $tilenames{"wall southeast"}, + "<>" => $tilenames{"force any"}, + "@" => $tilenames{"chip south"}, + "^]" => [ $tilenames{"cloning block north"}, $tilenames{"clone machine"} ], + "<]" => [ $tilenames{"cloning block west"}, $tilenames{"clone machine"} ], + "v]" => [ $tilenames{"cloning block south"}, $tilenames{"clone machine"} ], + ">]" => [ $tilenames{"cloning block east"}, $tilenames{"clone machine"} ] +}}; + +# +# +# + +# Error message display. +# +sub err(@) { warn "line $.: ", @_, "\n"; return; } + +# The list of incomplete tile names recognized. Each incomplete name +# has a list of characters that complete them. +# +my %partialnames = %{{ + "key" => { "blue key" => "b", "red key" => "r", + "green key" => "g", "yellow key" => "y" }, + "door" => { "blue door" => "b", "red door" => "r", + "green door" => "g", "yellow door" => "y" }, + "bug" => { "bug north" => "n", "bug west" => "w", + "bug south" => "s", "bug east" => "e" }, + "bee" => { "bee north" => "n", "bee west" => "w", + "bee south" => "s", "bee east" => "e" }, + "fireball" => { "fireball north" => "n", "fireball west" => "w", + "fireball south" => "s", "fireball east" => "e" }, + "flame" => { "flame north" => "n", "flame west" => "w", + "flame south" => "s", "flame east" => "e" }, + "ball" => { "ball north" => "n", "ball west" => "w", + "ball south" => "s", "ball east" => "e" }, + "tank" => { "tank north" => "n", "tank west" => "w", + "tank south" => "s", "tank east" => "e" }, + "glider" => { "glider north" => "n", "glider west" => "w", + "glider south" => "s", "glider east" => "e" }, + "ghost" => { "ghost north" => "n", "ghost west" => "w", + "ghost south" => "s", "ghost east" => "e" }, + "teeth" => { "teeth north" => "n", "teeth west" => "w", + "teeth south" => "s", "teeth east" => "e" }, + "frog" => { "frog north" => "n", "frog west" => "w", + "frog south" => "s", "frog east" => "e" }, + "walker" => { "walker north" => "n", "walker west" => "w", + "walker south" => "s", "walker east" => "e" }, + "dumbbell" => { "dumbbell north" => "n", "dumbbell west" => "w", + "dumbbell south" => "s", "dumbbell east" => "e" }, + "blob" => { "blob north" => "n", "blob west" => "w", + "blob south" => "s", "blob east" => "e" }, + "paramecium"=> { "paramecium north" => "n", "paramecium west" => "w", + "paramecium south" => "s", "paramecium east" => "e" }, + "centipede" => { "centipede north" => "n", "centipede west" => "w", + "centipede south" => "s", "centipede east" => "e" }, + "chip" => { "chip north" => "n", "chip west" => "w", + "chip south" => "s", "chip east" => "e" }, + "(swimming chip)" + => { "(swimming chip north)" => "n", + "(swimming chip west)" => "w", + "(swimming chip south)" => "s", + "(swimming chip east)" => "e" } +}}; + +# The list of tile definitions that are defined throughout the set. A +# number of definitions are made by default at startup. +# +my %globaltiles = %tilesymbols; + +# The list of tile definitions for a given level. +# +my %localtiles; + +# Add a list of tile definitions to a hash. +# +sub addtiledefs(\%@) +{ + my $tiledefs = shift; + while (my $def = shift) { $tiledefs->{$def->[0]} = $def->[1] } +} + +# Given a string, return the tile with that name. If the name is not +# recognized, undef is returned and a error message is displayed. +# +sub lookuptilename($) +{ + my $name = shift; + my $value = undef; + + return $tilenames{$name} if exists $tilenames{$name}; + + if ($name =~ /^0x([0-9A-Fa-f][0-9A-Fa-f])$/) { + $value = hex $1; + return $value if $value >= 0 && $value <= 255; + } + + my $n = length $name; + foreach my $key (keys %tilenames) { + if ($name eq substr $key, 0, $n) { + return ::err "ambiguous object id \"$name\"" + if defined $value && $value != $tilenames{$key}; + $value = $tilenames{$key}; + } + } + return ::err "unknown object id \"$name\"" unless defined $value; + return $value; +} + +# Given two characters, return the tile or pair of tiles which the +# characters represent. The characters can stand for a pair of tiles +# directly, or each character can independently represent one tile. In +# either case, a pair of tiles is returned as an array ref. A single +# tile is returned directly. If one or both characters are +# unrecognized, undef is returned and an error message is displayed. +# +sub lookuptile($); +sub lookuptile($) +{ + my $symbol = shift; + $symbol =~ s/\A(.) \Z/$1/; + + return $localtiles{$symbol} if exists $localtiles{$symbol}; + return $globaltiles{$symbol} if exists $globaltiles{$symbol}; + + if (length($symbol) == 2) { + my $top = lookuptile substr $symbol, 0, 1; + if (defined $top && ref $top && $top->[1] < 0) { + return $top; + } elsif (defined $top && !ref $top) { + my $bot = lookuptile substr $symbol, 1, 1; + if (defined $bot && !ref $bot) { + return [ $top, $bot ]; + } + } + } + + return ::err "unrecognized map tile \"$symbol\""; +} + +# Return the number of chips present on the map. +# +sub getchipcount($) +{ + my $map = shift; + my $count = 0; + + foreach my $y (0 .. 31) { + foreach my $x (0 .. 31) { + ++$count if $map->[$y][$x][0] == 0x02; + ++$count if $map->[$y][$x][1] == 0x02; + } + } + return $count; +} + +# Given a completed map, return the default list of traps connections +# as an array ref. (The default list follows the original Lynx rules +# of connecting buttons to the first subsequent trap in reading +# order.) +# +sub buildtraplist($) +{ + my $map = shift; + my $firsttrap = undef; + my @traps; + my @buttons; + + foreach my $y (0 .. 31) { + foreach my $x (0 .. 31) { + if ($map->[$y][$x][0] == 0x27 || $map->[$y][$x][1] == 0x27) { + push @buttons, [ $y, $x ]; + } elsif ($map->[$y][$x][0] == 0x2B || $map->[$y][$x][1] == 0x2B) { + push @traps, map { { from => $_, to => [ $y, $x ] } } @buttons; + undef @buttons; + $firsttrap = [ $y, $x ] unless defined $firsttrap; + } + } + } + push @traps, map { { from => $_, to => $firsttrap } } @buttons + if @buttons && defined $firsttrap; + return \@traps; +} + +# Given a completed map, return the default list of clone machine +# connections as an array ref. (This function looks a lot like the +# prior one.) +# +sub buildclonerlist($) +{ + my $map = shift; + my $firstcm = undef; + my @cms; + my @buttons; + + foreach my $y (0 .. 31) { + foreach my $x (0 .. 31) { + if ($map->[$y][$x][0] == 0x24 || $map->[$y][$x][1] == 0x24) { + push @buttons, [ $y, $x ]; + } elsif ($map->[$y][$x][0] == 0x31 || $map->[$y][$x][1] == 0x31) { + push @cms, map { { from => $_, to => [ $y, $x ] } } @buttons; + undef @buttons; + $firstcm = [ $y, $x ] unless defined $firstcm; + } + } + } + push @cms, map { { from => $_, to => $firstcm } } @buttons + if @buttons && defined $firstcm; + return \@cms; +} + +# Given a completed map, return the default ordering of creatures as +# an array ref. (The default ordering is to first list the creatures +# in reading order, including Chip. Then, the first creature on the +# list swaps positions with Chip, who is then removed from the list.) +# +sub buildcreaturelist($$) +{ + my $map = shift; + my $ruleset = shift; + my $chippos = undef; + my @crlist; + + foreach my $y (0 .. 31) { + foreach my $x (0 .. 31) { + my $tile = $map->[$y][$x][0]; + if (::iscreature $tile) { + push @crlist, [ $y, $x ]; + } elsif (::isblock $tile) { + push @crlist, [ $y, $x, 0 ]; + } elsif (::ischip $tile) { + $chippos = @crlist; + push @crlist, [ $y, $x, 0 ]; + } + } + } + if ($ruleset eq "lynx") { + ($crlist[0], $crlist[$chippos]) = ($crlist[$chippos], $crlist[0]) + if $chippos; + foreach my $item (@crlist) { $#$item = 1 } + } else { + if (defined $chippos && $chippos > 1) { + my $cr = shift @crlist; + $crlist[$chippos - 1] = $cr; + } + for (my $n = $#crlist ; $n >= 0 ; --$n) { + splice @crlist, $n, 1 if $#{$crlist[$n]} > 1; + } + } + + return \@crlist; +} + +# Compare two arrays of lines of text. Wherever the same pair of +# characters appears in same place in both arrays, the occurrence in +# the first array is replaced with spaces. +# +sub subtracttext(\@\@) +{ + my $array = shift; + my $remove = shift; + + for (my $n = 0 ; $n < @$array && $n < @$remove ; ++$n) { + my $m = 0; + while ($m < length $array->[$n] && $m < length $remove->[$n]) { + my $a = substr $array->[$n], $m, 2; + my $b = substr $remove->[$n], $m, 2; + $a .= " " if length $a == 1; + $b .= " " if length $b == 1; + substr($array->[$n], $m, 2) = " " if $a eq $b; + $m += 2; + } + } +} + +# Interpret a textual description of a section of the map. The +# interpreted map data is added to the map array passed as the first +# argument. The second and third arguments set the origin of the map +# section. The remaining arguments are the lines from the text file +# describing the map section. The return value is 1 if the +# interpretation is successful. If any part of the map sections cannot +# be understood, undef is returned and an error message is displayed. +# +sub parsemap($$$@) +{ + my $map = shift; + my $y0 = shift; + my $x0 = shift; + return ::err "map extends below the 32nd row" if $y0 + @_ > 32; + for (my $y = $y0 ; @_ ; ++$y) { + my $row = shift; + return ::err "map extends beyond the 32nd column" + if $x0 + length($row) / 2 > 32; + for (my $x = $x0 ; length $row ; ++$x) { + my $cell = lookuptile substr $row, 0, 2; + return ::err "unrecognized tile at ($x $y)" unless defined $cell; + return unless defined $cell; + if (ref $cell) { + if ($cell->[1] < 0) { + $map->[$y][$x] = [ $cell, 0x00 ]; + } else { + $map->[$y][$x] = $cell; + } + } else { + $map->[$y][$x] = [ $cell, 0x00 ]; + } + substr($row, 0, 2) = ""; + } + } + return 1; +} + +# Interpret a textual overlay section. The first argument is the +# level's hash ref. The second and third arguments set the origin of +# the overlay section. The remaining arguments are the lines from the +# text file describing the overlay. The return value is 1 if the +# interpretation is successful. If any part of the overlay section +# cannot be understood, undef is returned and an error message is +# displayed. +# +sub parsecon($$$@) +{ + my %symbols; + my $data = shift; + my $y0 = shift; + my $x0 = shift; + return ::err "overlay extends below the 32nd row" if $y0 + @_ > 32; + for (my $y = $y0 ; @_ ; ++$y) { + my $row = shift; + return ::err "overlay extends beyond the 32nd column" + if $x0 + length($row) / 2 > 32; + for (my $x = $x0 ; length $row ; ++$x) { + $_ = substr $row, 0, 1, ""; + push @{$symbols{$_}}, [ $y, $x ] unless $_ eq " " || $_ eq ""; + $_ = substr $row, 0, 1, ""; + push @{$symbols{$_}}, [ $y, $x ] unless $_ eq " " || $_ eq ""; + } + } + + foreach my $symbol (sort keys %symbols) { + my $list = $symbols{$symbol}; + if (@$list == 1) { + my ($y, $x) = ($list->[0][0], $list->[0][1]); + my $cell = $data->{map}[$y][$x]; + return ::err "no creature under \"$symbol\" at ($x $y)" + unless defined $cell && + (::iscreature $cell->[0] || ::iscreature $cell->[1]); + push @{$data->{creatures}}, [ $y, $x ]; + } else { + my $linktype = undef; + my $to = undef; + my (@from, $type); + foreach my $pos (@$list) { + my ($y, $x) = ($pos->[0], $pos->[1]); + my $cell = $data->{map}[$y][$x]; + my $obj = $cell->[1] || $cell->[0]; + if ($obj == $tilenames{"red button"}) { + $type = "cloners"; + push @from, [ $y, $x ]; + } elsif ($obj == $tilenames{"brown button"}) { + $type = "traps"; + push @from, [ $y, $x ]; + } elsif ($obj == $tilenames{"clone machine"}) { + $type = "cloners"; + return ::err "clone machine under \"$symbol\" at ($x $y) ", + "wired to non-button at ($to->[1] $to->[0])" + if defined $to; + $to = [ $y, $x ]; + } elsif ($obj == $tilenames{"beartrap"}) { + $type = "traps"; + return ::err "beartrap under \"$symbol\" at ($x $y) ", + "wired to non-button at ($to->[1] $to->[0])" + if defined $to; + $to = [ $y, $x ]; + } else { + return ::err "no button/trap/clone machine ", + "under \"$symbol\" at ($x $y)"; + } + $linktype ||= $type; + return ::err "inconsistent connection ", + "under \"$symbol\" at ($x $y)" + unless $linktype eq $type; + } + push @{$data->{$linktype}}, + map { { from => $_, to => $to } } @from; + } + } + return 1; +} + +# Interpret a tile definition. Given a line of text supplying the tile +# definition, the function returns an array ref. Each element in the +# array is a pair: the first element gives the character(s), and the +# second element supplies the tile(s). If the definition is ambiguous +# or invalid, undef is returned and an error message is displayed. +# +sub parsetiledef($) +{ + my $def = shift; + $def =~ s/^(\S\S?)\t// + or return ::err "syntax error in tile defintion \"$def\""; + my $symbol = $1; + $def = lc $def; + $def =~ s/^\s+//; + $def =~ s/\s+$//; + + if ($def =~ /^([^\+]*[^\+\s])\s*\+\s*([^\+\s][^\+]*)$/) { + my ($def1, $def2) = ($1, $2); + my ($tile1, $tile2); + $tile1 = lookuptilename $def1; + return unless defined $tile1; + if (lc $def2 eq "pos") { + return ::err "ordered tile definition \"$symbol\" ", + "must be a single character" + unless length($symbol) == 1; + $tile2 = -1; + } else { + $tile2 = lookuptilename $def2; + return unless defined $tile2; + } + return [ [ $symbol, [ $tile1, $tile2 ] ] ]; + } + + my @defs; + if (exists $partialnames{$def}) { + return ::err "incomplete tile definition \"$symbol\" ", + "must be a single character" + unless length($symbol) == 1; + foreach my $comp (keys %{$partialnames{$def}}) { + push @defs, [ $symbol . $partialnames{$def}{$comp}, + $tilenames{$comp} ]; + } + return \@defs; + } + + my $tile = lookuptilename $def; + return [ [ $symbol, $tile ] ] if defined $tile; + return; +} + +# Given a handle to a text file, read the introductory lines that +# precede the first level definition, if any, and return a hash ref +# for storing the level set. If an error occurs, undef is returned and +# an error message is displayed. +# +sub parseheader($) +{ + my $input = shift; + my $data = { ruleset => "lynx" }; + my $slurpingdefs = undef; + local $_; + + while (<$input>) { + chomp; + if (defined $slurpingdefs) { + if (/^\s*[Ee][Nn][Dd]\s*$/) { + undef $slurpingdefs; + } else { + my $def = parsetiledef $_; + return unless $def; + addtiledefs %globaltiles, @$def; + } + next; + } elsif (/^\s*[Tt][Ii][Ll][Ee][Ss]\s*$/) { + $slurpingdefs = 1; + next; + } + + last if /^%%%$/; + next if /^\s*$/ || /^%/; + + /^\s*(\S+)\s+(\S(?:.*\S)?)\s*$/ or return ::err "syntax error"; + my ($name, $value) = ($1, $2); + $name = lc $name; + if ($name eq "ruleset") { + $value = lc $value; + return ::err "invalid ruleset \"$value\"" + unless $value =~ /^(lynx|ms)$/; + $data->{ruleset} = $value; + } elsif ($name eq "maxlevel") { + return ::err "invalid maximum level \"$value\"" + unless $value =~ /\A\d+\Z/ && $value < 65536; + $data->{maxlevel} = $value; + } else { + return ::err "invalid statement \"$name\""; + } + } + + return ::err "unclosed definition section" if $slurpingdefs; + return $data; +} + +# Given a handle to a text file, positioned at the start of a level +# description, parse the lines describing the level and return a hash +# ref containing the level data. If the end of the file is encountered +# before a level description is found, false is returned. If any +# errors are encountered, undef is returned and an error message is +# displayed. +# +sub parselevel($$$) +{ + my $input = shift; + my $ruleset = shift; + my $number = shift; + my %data = (number => $number, leveltime => 0); + my $seenanything = undef; + my $slurpingdefs = undef; + my $slurpingmap = undef; + my @maptext; + local $_; + + $data{passwd} = $origpasswords[$number - 1] + if $number >= 1 && $number <= 150; + + for my $y (0 .. 31) { + for my $x (0 .. 31) { $data{map}[$y][$x] = [ 0, 0 ] } + } + undef %localtiles; + + while (<$input>) { + chomp; + if (defined $slurpingdefs) { + if (/^\s*[Ee][Nn][Dd]\s*$/) { + undef $slurpingdefs; + } else { + my $def = parsetiledef $_; + return unless $def; + addtiledefs %localtiles, @$def; + } + next; + } elsif (defined $slurpingmap) { + if (/^\s*([AEae])[Nn][Dd]\s*$/) { + my $overlay = lc($1) eq "a"; + if ($slurpingmap->[2] >= 0) { + my @overlaytext = splice @maptext, $slurpingmap->[2]; + return ::err "overlay section is taller than map section" + if @overlaytext > @maptext; + subtracttext @overlaytext, @maptext; + return unless parsecon \%data, + $slurpingmap->[0], + $slurpingmap->[1], + @overlaytext; + } else { + $slurpingmap->[2] = @maptext; + return unless parsemap $data{map}, + $slurpingmap->[0], + $slurpingmap->[1], + @maptext; + } + unless ($overlay) { + undef $slurpingmap; + undef @maptext; + } + } else { + 1 while s{^([^\t]*)\t}{$1 . (" " x (8 - length($1) % 8))}e; + push @maptext, $_; + } + next; + } elsif (/^\s*[Tt][Ii][Ll][Ee][Ss]\s*$/) { + $slurpingdefs = 1; + next; + } elsif (/^\s*[Mm][Aa][Pp]\s*(?:(\d+)\s+(\d+)\s*)?$/) { + $slurpingmap = [ $2 || 0, $1 || 0, -1 ]; + next; + } elsif (/^\s*[Mm][Aa][Pp]/) { + return ::err "invalid syntax following \"map\""; + } elsif (/^\s*[Tt][Rr][Aa][Pp][Ss]\s*$/) { + $data{traps} ||= [ ]; + next; + } elsif (/^\s*[Cc][Ll][Oo][Nn][Ee][Rr][Ss]\s*$/) { + $data{cloners} ||= [ ]; + next; + } elsif (/^\s*[Cc][Rr][Ee][Aa][Tt][Uu][Rr][Ee][Ss]\s*$/) { + $data{creatures} ||= [ ]; + next; + } + + last if /^%%%$/; + next if /^\s*$/ || /^%/; + + $seenanything = 1; + /^\s*(\S+)\s+(\S(?:.*\S)?)\s*$/ or return ::err "syntax error"; + my ($name, $value) = ($1, $2); + $name = lc $name; + if ($name eq "level") { + return ::err "invalid level number \"$value\"" + unless $value =~ /\A\d+\Z/ && $value < 65536; + $data{number} = $value; + } elsif ($name eq "time") { + return ::err "invalid level time \"$value\"" + unless $value =~ /\A\d+\Z/ && $value < 65536; + $data{leveltime} = $value; + } elsif ($name eq "chips") { + return ::err "invalid chip count \"$value\"" + unless $value =~ /\A\d+\Z/ && $value < 65536; + $data{chips} = $value; + } elsif ($name eq "title" || $name eq "name") { + $value = ::unescape $value if $value =~ s/\A\"(.*)\"\Z/$1/; + $data{title} .= " " if defined $data{title}; + $data{title} .= $value; + } elsif ($name eq "password" || $name eq "passwd") { + return ::err "invalid password \"$value\"" + unless $value =~ /\A[A-Z][A-Z][A-Z][A-Z]\Z/; + $data{passwd} = $value; + } elsif ($name eq "hint") { + $value = ::unescape $value if $value =~ s/\A\"(.*)\"\Z/$1/; + $data{hint} .= " " if defined $data{hint}; + $data{hint} .= $value; + } elsif ($name eq "traps") { + $data{traps} ||= [ ]; + while ($value =~ s/\A\s* (\d+)\s+(\d+) \s*[-=]?>\s* + (\d+)\s+(\d+) (?:\s*[,;])?//x) { + push @{$data{traps}}, { from => [ $2, $1 ], + to => [ $4, $3 ] }; + } + return ::err "syntax error in trap list at \"$value\"" + if $value && $value !~ /\A[,;]\Z/; + } elsif ($name eq "cloners") { + $data{cloners} ||= [ ]; + while ($value =~ s/\A\s* (\d+)\s+(\d+) \s*[-=]?>\s* + (\d+)\s+(\d+) (?:\s*[,;])?//x) { + push @{$data{cloners}}, { from => [ $2, $1 ], + to => [ $4, $3 ] }; + } + return ::err "syntax error in clone machine list at \"$value\"" + if $value && $value !~ /\A[,;]\Z/; + } elsif ($name eq "creatures") { + $data{creatures} ||= [ ]; + while ($value =~ s/\A\s* (\d+)\s+(\d+) (?:\s*[,;])?//x) { + push @{$data{creatures}}, [ $2, $1 ]; + } + return ::err "syntax error in creature list at \"$value\"" + if $value && $value !~ /\A[,;]\Z/; + } elsif ($name eq "border") { + my $cell = lookuptile $value; + return unless defined $cell; + $cell = [ $cell, 0x00 ] unless ref $cell; + foreach my $y (0 .. 31) { $data{map}[$y][0] = [ @$cell ] } + foreach my $y (0 .. 31) { $data{map}[$y][31] = [ @$cell ] } + foreach my $x (1 .. 30) { $data{map}[0][$x] = [ @$cell ] } + foreach my $x (1 .. 30) { $data{map}[31][$x] = [ @$cell ] } + } elsif ($name eq "field") { + return ::err "invalid field spec \"$value\"" + unless $value =~ /^(\d+)\s+(\d+(?:\s+\d+)*)$/; + my ($num, $data) = ($1, $2); + return ::err "multiple specs for field $num" + if exists $data{fields}{$num}; + $data{fields}{$num} = join "", map { chr } split " ", $data; + } else { + return ::err "invalid command \"$name\""; + } + } + return "" unless $seenanything; + + return ::err "unclosed defs section" if $slurpingdefs; + return ::err "unclosed map section" if $slurpingmap; + + return ::err "missing level title" unless exists $data{title}; + return ::err "missing password" unless exists $data{passwd}; + return ::err "missing level map" unless exists $data{map}; + + $data{chips} = getchipcount $data{map} unless exists $data{chips}; + $data{traps} ||= buildtraplist $data{map}; + $data{cloners} ||= buildclonerlist $data{map}; + $data{creatures} ||= buildcreaturelist $data{map}, $ruleset; + $data{lynxcreatures} = ::makelynxcrlist $data{map}, $data{creatures}; + $data{fields} ||= { }; + + return ::err "title too long (", length($data{title}), "); ", + "254 is the maximum length allowed" + if length($data{title}) > 254; + return ::err "hint too long (", length($data{hint}), "); ", + "254 is the maximum length allowed" + if exists $data{hint} && length($data{hint}) > 254; + return ::err "too many (", scalar(@{$data{traps}}), ") ", + "trap connections; 25 is the maximum allowed" + if @{$data{traps}} > 25; + return ::err "too many (", scalar(@{$data{cloners}}), ") ", + "clone machine connections; 31 is the maximum allowed" + if @{$data{cloners}} > 31; + return ::err "too many (", scalar(@{$data{creatures}}), ") ", + "creatures; 127 is the maximum allowed" + if @{$data{creatures}} > 127; + + return \%data; +} + +# This function takes a handle to a text file and returns a hash ref +# containing the described level set. If the file could not be +# completely translated, undef is returned and one or more error +# messages will be displayed. +# +sub read($) +{ + my $input = shift; + my $data; + + $data = parseheader $input; + return unless $data; + + my $lastnumber = 0; + for (;;) { + my $level = parselevel $input, $data->{ruleset}, $lastnumber + 1; + return unless defined $level; + last unless $level; + $lastnumber = $level->{number}; + push @{$data->{levels}}, $level; + last if eof $input; + } + + $#{$data->{levels}} = $data->{maxlevel} - 1 + if exists $data->{maxlevel} && $data->{maxlevel} < @{$data->{levels}}; + + return $data; +} + +# +# +# + +my %globalsymbols; +my %localsymbols; + +$globalsymbols{"0"}[1] = " "; +$globalsymbols{"0"}[2] = " "; +$globalsymbols{"0:0"}[1] = " "; +$globalsymbols{"0:0"}[2] = " "; +foreach my $symbol (keys %tilesymbols) { + my $key; + if (ref $tilesymbols{$symbol}) { + $key = "$tilesymbols{$symbol}[0]:$tilesymbols{$symbol}[1]"; + } else { + $key = $tilesymbols{$symbol}; + } + $globalsymbols{$key}[length $symbol] ||= $symbol; +} + +my @symbollist; +my $newsym = -1; + +sub printwrap($$$) +{ + my $output = shift; + my $prefix = shift; + my @segments = split /(\S\s\S)/, ::escape shift; + + push @segments, "" if @segments % 2 == 0; + for (my $n = 1 ; $n < $#segments; ++$n) { + $segments[$n - 1] .= substr($segments[$n], 0, 1); + $segments[$n] = substr($segments[$n], 2, 1) . $segments[$n + 1]; + splice @segments, $n + 1, 1; + } + + my $width = 75 - length $prefix; + my $line = shift @segments; + while (@segments) { + if (!$line || length($line) + length($segments[0]) < $width) { + $line .= " " . shift @segments; + } else { + $line = "\"$line\"" if $line =~ /\\/; + print $output "$prefix $line\n"; + $line = shift @segments; + } + } + $line = "\"$line\"" if $line =~ /\\/ || $line =~ /^\s/ || $line =~ /\s$/; + print $output "$prefix $line\n"; + + return 1; +} + +sub printlist($$@) +{ + my $output = shift; + my $prefix = shift; + + while (@_) { + my $item = shift; + local $_ = "$prefix $item"; + my $x = length $_; + print $output $_ or return; + while (@_) { + $x += 3 + length $_[0]; + last if $x > 76; + $item = shift; + print $output " ; $item" or return; + } + print $output "\n" or return; + } + return 1; +} + +sub tilesymbol($;$) +{ + my $tile = shift; + my $max = shift || 2; + + return $globalsymbols{$tile}[$max] if defined $globalsymbols{$tile}[$max]; + return $globalsymbols{$tile}[1] if defined $globalsymbols{$tile}[1]; + return $localsymbols{$tile}[$max] if defined $localsymbols{$tile}[$max]; + return $localsymbols{$tile}[1] if defined $localsymbols{$tile}[1]; + return undef; +} + +sub getnewsym() { shift @symbollist } + +sub resetnewsyms() +{ + @symbollist = split //, + "ABCDFGIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz012345789@*+.,'`-!"; +} + +sub cellsymbol($;$) +{ + my $top = shift; + my $bot = shift || 0; + my $tile; + my $symbol; + + return " " if $top == 0 && $bot == 0; + + $tile = $bot ? "$top:$bot" : $top; + $symbol = tilesymbol $tile; + if (defined $symbol) { + $symbol = "$symbol " if length($symbol) == 1; + return $symbol; + } + + if ($bot) { + if ($top == 0) { + $symbol = tilesymbol $bot, 1; + return " $symbol" if defined $symbol; + } else { + my $st = tilesymbol $top, 1; + if (defined $st) { + my $sb = tilesymbol $bot, 1; + return "$st$sb" if defined $sb; + } + } + } + + $symbol = getnewsym; + unless (defined $symbol) { + ::err "too many unique tile combinations required"; + $symbol = "\\"; + } + $localsymbols{$tile}[length $symbol] = $symbol; + + $symbol = "$symbol " if length($symbol) == 1; + return $symbol; +} + +sub trimmap(\@) +{ + my $map = shift; + my @xs = (0) x 32; + my @ys = (0) x 32; + + my $count = 0; + foreach my $y (0 .. 31) { + foreach my $x (0 .. 31) { + next if $map->[$y][$x][0] == 0 && $map->[$y][$x][1] == 0; + ++$xs[$x]; + ++$ys[$y]; + ++$count; + } + } + return (0, 0, 0, 0, 0) unless $count; + + my $border = 0; + if ($map->[0][0][0] != 0 && $map->[0][0][1] == 0) { + my $tile = $map->[0][0][0]; + foreach my $n (1 .. 31) { + goto noborder unless $map->[$n][0][0] == $tile + && $map->[$n][31][0] == $tile + && $map->[0][$n][0] == $tile + && $map->[31][$n][0] == $tile + && $map->[$n][0][1] == 0 + && $map->[$n][31][1] == 0 + && $map->[0][$n][1] == 0 + && $map->[31][$n][1] == 0; + } + $border = $tile; + $xs[0] = $xs[31] = $ys[0] = $ys[31] = 0; + noborder: + } + + my ($left, $right, $top, $bottom) = (-1, 32, -1, 32); + 1 until $xs[++$left]; + 1 until $xs[--$right]; + 1 until $ys[++$top]; + 1 until $ys[--$bottom]; + + return 0, 31, 0, 31, 0 if $border && $left == 1 && $right == 30 + && $top == 1 && $bottom == 30; + + return ($left, $right, $top, $bottom, $border); +} + +sub writeheader($\%) +{ + my $output = shift; + my $data = shift; + + print $output "ruleset $data->{ruleset}\n" + and print $output "\n%%%\n"; +} + +sub writelevelheader($\%) +{ + my $output = shift; + my $level = shift; + + printwrap $output, "title ", $level->{title} or return; + print $output "passwd $level->{passwd}\n" or return; + print $output "chips $level->{chips}\n" or return + if exists $level->{chips} && $level->{chips}; + print $output "time $level->{leveltime}\n" or return + if exists $level->{leveltime} && $level->{leveltime}; + printwrap $output, "hint ", $level->{hint} or return + if exists $level->{hint}; + print $output "\n"; +} + +sub writelevelmap($\@) +{ + my $output = shift; + my $map = shift; + my (@tiletext, @maptext); + + undef %localsymbols; + resetnewsyms; + + my ($left, $right, $top, $bottom, $border) = trimmap @$map; + + $border = cellsymbol $border if $border; + foreach my $y ($top .. $bottom) { + my $mapline = ""; + foreach my $x ($left .. $right) { + $mapline .= cellsymbol $map->[$y][$x][0], $map->[$y][$x][1]; + } + $mapline =~ s/\s+$//; + push @maptext, "$mapline\n"; + } + + foreach my $tiles (keys %localsymbols) { + foreach my $tile (@{$localsymbols{$tiles}}) { + next unless defined $tile; + my $line = "$tile\t"; + if ($tiles =~ /^(\d+):(\d+)$/) { + my ($top, $bot) = ($1, $2); + $line .= "$tilenames[$top] + $tilenames[$bot]\n"; + } else { + $line .= "$tilenames[$tiles]\n"; + } + push @tiletext, $line; + } + } + @tiletext = sort @tiletext; + + print $output "tiles\n", @tiletext, "end\n\n" or return if @tiletext; + print $output "border $border\n\n" or return if $border; + + print $output ($left || $top ? "map $left $top\n" : "map\n"), + @maptext, + "end\n\n" + or return; +} + +sub writelevelcloners($\%) +{ + my $output = shift; + my $level = shift; + my $n; + + my $default = txtfile::buildclonerlist $level->{map}; + if (!defined $level->{cloners}) { + return print $output "cloners\n\n" if @$default; + return 1; + } + $n = 0; + if (@$default == @{$level->{cloners}}) { + for ($n = 0 ; $n < @$default ; ++$n) { + last if $default->[$n]{from}[0] != $level->{cloners}[$n]{from}[0] + || $default->[$n]{from}[1] != $level->{cloners}[$n]{from}[1] + || $default->[$n]{to}[0] != $level->{cloners}[$n]{to}[0] + || $default->[$n]{to}[1] != $level->{cloners}[$n]{to}[1]; + } + } + return 1 if $n == @$default; + + printlist $output, "cloners", + map { "$_->{from}[1] $_->{from}[0] -> $_->{to}[1] $_->{to}[0]" } + @{$level->{cloners}} + or return; + print $output "\n"; +} + +sub writeleveltraps($\%) +{ + my $output = shift; + my $level = shift; + my $n; + + my $default = txtfile::buildtraplist $level->{map}; + if (!defined $level->{traps}) { + return print $output "traps\n\n" if @$default; + return 1; + } + $n = 0; + if (@$default == @{$level->{traps}}) { + for ($n = 0 ; $n < @$default ; ++$n) { + last if $default->[$n]{from}[0] != $level->{traps}[$n]{from}[0] + || $default->[$n]{from}[1] != $level->{traps}[$n]{from}[1] + || $default->[$n]{to}[0] != $level->{traps}[$n]{to}[0] + || $default->[$n]{to}[1] != $level->{traps}[$n]{to}[1]; + } + } + return 1 if $n == @$default; + + printlist $output, "traps", + map { "$_->{from}[1] $_->{from}[0] -> $_->{to}[1] $_->{to}[0]" } + @{$level->{traps}} + or return; + print $output "\n"; +} + +sub writelevelcrlist($\%$) +{ + my $output = shift; + my $level = shift; + my $ruleset = shift; + my $n; + + my $default = txtfile::buildcreaturelist $level->{map}, $ruleset; + if (!defined $level->{creatures}) { + return print $output "creatures\n\n" if @$default; + return 1; + } + + $n = 0; + if (@$default == @{$level->{creatures}}) { + for ($n = 0 ; $n < @$default ; ++$n) { + last if $default->[$n][0] != $level->{creatures}[$n][0] + || $default->[$n][1] != $level->{creatures}[$n][1]; + } + } + return 1 if $n == @$default; + + printlist $output, "creatures", + map { "$_->[1] $_->[0]" } @{$level->{creatures}} + or return; + print $output "\n"; +} + +sub writelevel($\%$) +{ + my $output = shift; + my $level = shift; + my $ruleset = shift; + + writelevelheader $output, %$level or return; + writelevelmap $output, @{$level->{map}} or return; + writeleveltraps $output, %$level or return; + writelevelcloners $output, %$level or return; + writelevelcrlist $output, %$level, $ruleset or return; + + print $output "%%%\n"; +} + +sub write($$) +{ + my $output = shift; + my $data = shift; + + $globalsymbols{$tilenames{"block north"}} = + [ @{$globalsymbols{$tilenames{"block"}}} ] + if $data->{ruleset} eq "lynx"; + + writeheader $output, %$data or return; + + my $lastnumber = 0; + foreach my $level (@{$data->{levels}}) { + $filelevel = $level->{number}; + ++$lastnumber; + print $output "\n" or return; + print $output "level $level->{number}\n" or return + unless $level->{number} == $lastnumber; + writelevel $output, %$level, $data->{ruleset} or return; + $lastnumber = $level->{number}; + } + + return 1; +} + +# +# +# + +package datfile; + +# Given a string of run-length encoded data, return the original +# uncompressed string. +# +sub rleuncompress($) +{ + local $_ = shift; + 1 while s/\xFF(.)(.)/$2 x ord$1/se; + return $_; +} + +sub parseheader($) +{ + my $input = shift; + my %data; + + my ($sig, $maxlevel) = ::fileread $input, "Vv" or return; + if ($sig == 0x0002AAAC) { + $data{ruleset} = "ms"; + } elsif ($sig == 0x0102AAAC) { + $data{ruleset} = "lynx"; + } else { + return ::err "not a valid data file"; + } + return ::err "file contains no maps" if $maxlevel <= 0; + $data{maxlevel} = $maxlevel; + + return \%data; +} + +sub parselevelmap($$) +{ + my $layer1 = shift; + my $layer2 = shift; + my @map; + if (length($layer1) > 1024) { + ::err "warning: excess data in top layer of map"; + substr($layer1, 1024) = ""; + } + if (length($layer2) > 1024) { + ::err "warning: excess data in bottom layer of map"; + substr($layer2, 1024) = ""; + } + return ::err "invalid map in data file" + unless length($layer1) == 1024 && length($layer2) == 1024; + foreach my $y (0 .. 31) { + foreach my $x (0 .. 31) { + $map[$y][$x][0] = ord substr $layer1, 0, 1, ""; + $map[$y][$x][1] = ord substr $layer2, 0, 1, ""; + } + } + return \@map; +} + +sub parselevel($) +{ + my $input = shift; + my %level; + my ($fieldnum, $fieldsize, $data); + + my $levelsize = ""; + return ::err $! unless defined sysread $input, $levelsize, 2; + return "" unless length($levelsize) == 2; + $levelsize = unpack "v", $levelsize; + return ::err "invalid metadata in file (only $levelsize bytes in level)" + unless $levelsize > 8; + + @level{qw(number leveltime chips)} = ::fileread $input, "vvv", $levelsize + or return; + + ($fieldnum, $fieldsize) = ::fileread $input, "vv", $levelsize, + 1, 1, 0, 1024 + or return; + my $layer1 = ::fileread $input, "a$fieldsize", $levelsize or return; + $fieldsize = ::fileread $input, "v", $levelsize, 0, 1024 or return; + my $layer2 = ::fileread $input, "a$fieldsize", $levelsize or return; + ::fileread $input, "v", $levelsize or return; + $level{map} = parselevelmap rleuncompress $layer1, rleuncompress $layer2 + or return; + + while ($levelsize > 0) { + ($fieldnum, $fieldsize) = ::fileread $input, "CC", $levelsize, 1, 10 + or last; + $data = ::fileread $input, "a$fieldsize", $levelsize or return; + if ($fieldnum == 1) { + return ::err "invalid field" unless $fieldsize > 1; + $level{leveltime} = unpack "v", $data; + return ::err "invalid data in field 1" + unless $level{leveltime} >= 0 && $level{leveltime} <= 65535; + } elsif ($fieldnum == 2) { + return ::err "invalid field" unless $fieldsize > 1; + $level{chips} = unpack "v", $data; + return ::err "invalid data in field 2" + unless $level{chips} >= 0 && $level{chips} <= 65535; + } elsif ($fieldnum == 3) { + ($level{title} = $data) =~ s/\0\Z//; + } elsif ($fieldnum == 4) { + $fieldsize /= 2; + my @values = unpack "v$fieldsize", $data; + for (my $i = 0 ; $i < $fieldsize / 5 ; ++$i) { + $level{traps}[$i]{from}[1] = shift @values; + $level{traps}[$i]{from}[0] = shift @values; + $level{traps}[$i]{to}[1] = shift @values; + $level{traps}[$i]{to}[0] = shift @values; + shift @values; + } + } elsif ($fieldnum == 5) { + $fieldsize /= 2; + my @values = unpack "v$fieldsize", $data; + for (my $i = 0 ; $i < $fieldsize / 4 ; ++$i) { + $level{cloners}[$i]{from}[1] = shift @values; + $level{cloners}[$i]{from}[0] = shift @values; + $level{cloners}[$i]{to}[1] = shift @values; + $level{cloners}[$i]{to}[0] = shift @values; + } + } elsif ($fieldnum == 6) { + ($level{passwd} = $data) =~ s/\0\Z//; + $level{passwd} ^= "\x99" x length $level{passwd}; + } elsif ($fieldnum == 7) { + ($level{hint} = $data) =~ s/\0\Z//; + } elsif ($fieldnum == 8) { + ::err "field 8 not yet supported; ignoring"; + } elsif ($fieldnum == 9) { + ::err "ignoring useless field 9 entry"; + } elsif ($fieldnum == 10) { + my @values = unpack "C$fieldsize", $data; + for (my $i = 0 ; $i < $fieldsize / 2 ; ++$i) { + $level{creatures}[$i][1] = shift @values; + $level{creatures}[$i][0] = shift @values; + } + } + } + return ::err "$levelsize bytes left over at end" if $levelsize; + + $level{lynxcreatures} = ::makelynxcrlist $level{map}, $level{creatures}; + + return \%level; +} + +sub read($) +{ + my $input = shift; + my $data; + + $data = parseheader $input; + return unless $data; + + for (;;) { + my $level = parselevel $input; + return unless defined $level; + last unless $level; + push @{$data->{levels}}, $level; + } + + ::err "warning: number of levels incorrect in header ($data->{maxlevel}, ", + "should be ", scalar(@{$data->{levels}}), ")" + unless $data->{maxlevel} == @{$data->{levels}}; + + return $data; +} + +# +# +# + +# Given a string of packed data, return a string containing the same +# data run-length encoded. +# +sub rlecompress($) +{ + my $in = shift; + my $out = ""; + + while (length $in) { + my $byte = substr $in, 0, 1; + my $n = 1; + ++$n while $n < length $in && $byte eq substr $in, $n, 1; + substr($in, 0, $n) = ""; + while ($n >= 255) { $out .= "\xFF\xFF$byte"; $n -= 255; } + if ($n > 3) { + $out .= "\xFF" . chr($n) . $byte; + } elsif ($n) { + $out .= $byte x $n; + } + } + return $out; +} + +# Given a level set definition, return the pack arguments for creating +# the .dat file's header data. +# +sub mkdatfileheader(\%) +{ + my $data = shift; + my @fields; + + if ($data->{ruleset} eq "ms") { + push @fields, 0x0002AAAC; + } else { + push @fields, 0x0102AAAC; + } + push @fields, scalar @{$data->{levels}}; + return ("Vv", @fields); +} + +# Given a level definition, return the pack arguments for creating the +# level's header data in the .dat file. +# +sub mkdatfilelevelheader(\%) +{ + my $data = shift; + my @fields; + + push @fields, $data->{number}; + push @fields, $data->{leveltime}; + push @fields, $data->{chips}; + return ("vvv", @fields); +} + +# Given a level definition, return the pack arguments for creating the +# level's map data in the .dat file. +# +sub mkdatfilelevelmap(\%) +{ + my $data = shift; + my $map = $data->{map}; + my ($layer1, $layer2); + my @fields; + + for my $y (0 .. 31) { + for my $x (0 .. 31) { + if (defined $map->[$y][$x]) { + if (defined $map->[$y][$x][0]) { + $layer1 .= chr $map->[$y][$x][0]; + } else { + $layer1 .= "\0"; + } + if (defined $map->[$y][$x][1]) { + $layer2 .= chr $map->[$y][$x][1]; + } else { + $layer2 .= "\0"; + } + } else { + $layer1 .= "\0"; + $layer2 .= "\0"; + } + } + } + + $layer1 = rlecompress $layer1; + $layer2 = rlecompress $layer2; + + push @fields, 1; + push @fields, length $layer1; + push @fields, $layer1; + push @fields, length $layer2; + push @fields, $layer2; + + return ("vva$fields[1]va$fields[3]", @fields); +} + +# Given a level definition, return the pack arguments for creating the +# level's title field in the .dat file. +# +sub mkdatfileleveltitle(\%) +{ + my $data = shift; + my $n = length($data->{title}) + 1; + return ("CCa$n", 3, $n, $data->{title}); +} + +# Given a level definition, return the pack arguments for creating the +# level's hint field in the .dat file. +# +sub mkdatfilelevelhint(\%) +{ + my $data = shift; + return ("") unless exists $data->{hint}; + my $n = length($data->{hint}) + 1; + return ("CCa$n", 7, $n, $data->{hint}); +} + +# Given a level definition, return the pack arguments for creating the +# level's password field in the .dat file. +# +sub mkdatfilelevelpasswd(\%) +{ + my $data = shift; + my $n = length($data->{passwd}) + 1; + return ("CCa$n", 6, $n, $data->{passwd} ^ "\x99\x99\x99\x99"); +} + +# Given a level definition, return the pack arguments for creating the +# level's bear trap list field in the .dat file. +# +sub mkdatfileleveltraps(\%) +{ + my $data = shift; + + return ("") unless exists $data->{traps}; + my $list = $data->{traps}; + my $n = @$list; + return ("") unless $n; + my @fields; + + push @fields, 4; + push @fields, $n * 10; + foreach my $i (0 .. $#$list) { + push @fields, $list->[$i]{from}[1], $list->[$i]{from}[0]; + push @fields, $list->[$i]{to}[1], $list->[$i]{to}[0]; + push @fields, 0; + } + return (("CCv" . ($n * 5)), @fields); +} + +# Given a level definition, return the pack arguments for creating the +# level's clone machine list field in the .dat file. +# +sub mkdatfilelevelcloners(\%) +{ + my $data = shift; + + return ("") unless exists $data->{cloners}; + my $list = $data->{cloners}; + my $n = @$list; + return ("") unless $n; + my @fields; + + push @fields, 5; + push @fields, $n * 8; + foreach my $i (0 .. $#$list) { + push @fields, $list->[$i]{from}[1], $list->[$i]{from}[0]; + push @fields, $list->[$i]{to}[1], $list->[$i]{to}[0]; + } + return (("CCv" . ($n * 4)), @fields); +} + +# Given a level definition, return the pack arguments for creating the +# level's creature list field in the .dat file. +# +sub mkdatfilelevelcrlist(\%) +{ + my $data = shift; + + return ("") unless exists $data->{creatures}; + my $list = $data->{creatures}; + return ("") unless $list && @$list; + my $n = @$list; + my @fields; + + push @fields, 10; + push @fields, $n * 2; + foreach my $i (0 .. $#$list) { + push @fields, $list->[$i][1], $list->[$i][0]; + } + return (("CCC" . ($n * 2)), @fields); +} + +# Given a level definition, return the pack arguments for creating the +# level's miscellaneous fields, if any, in the .dat file. +# +sub mkdatfilelevelmisc(\%) +{ + my $data = shift; + my ($template, @fields) = (""); + + return ("") unless exists $data->{fields}; + foreach my $num (keys %{$data->{fields}}) { + my $n = length($data->{fields}{$num}); + $template .= "CCa$n"; + push @fields, $num, $n, $data->{fields}{$num}; + } + return ($template, @fields); +} + +# Given a level definition, return the pack arguments for creating the +# level in the .dat file. +# +sub mkdatfilelevel(\%) +{ + my $data = shift; + my ($template, @fields); + my @p; + + @p = mkdatfilelevelheader %$data; $template .= shift @p; push @fields, @p; + @p = mkdatfilelevelmap %$data; $template .= shift @p; push @fields, @p; + + my $data2pos = @fields; $template .= "v"; push @fields, 0; + my $tmplt2pos = length $template; + + @p = mkdatfileleveltitle %$data; $template .= shift @p; push @fields, @p; + @p = mkdatfilelevelhint %$data; $template .= shift @p; push @fields, @p; + @p = mkdatfilelevelpasswd %$data; $template .= shift @p; push @fields, @p; + @p = mkdatfileleveltraps %$data; $template .= shift @p; push @fields, @p; + @p = mkdatfilelevelcloners %$data; $template .= shift @p; push @fields, @p; + @p = mkdatfilelevelcrlist %$data; $template .= shift @p; push @fields, @p; + @p = mkdatfilelevelmisc %$data; $template .= shift @p; push @fields, @p; + + $fields[$data2pos] = ::packlen substr $template, $tmplt2pos; + + unshift @fields, ::packlen $template; + $template = "v$template"; + + return ($template, @fields); +} + +# Given a level set definition, return the pack arguments for creating +# the .dat file. +# +sub mkdatfile(\%) +{ + my $data = shift; + my ($template, @fields); + my @p; + + @p = mkdatfileheader %$data; + $template = shift @p; + @fields = @p; + + foreach my $level (@{$data->{levels}}) { + $filelevel = $level->{number}; + @p = mkdatfilelevel %$level; + $template .= shift @p; + push @fields, @p; + } + + return ($template, @fields); +} + +# This function takes a handle to a binary file and a hash ref +# defining a level set, and writes the level set to the binary file as +# a .dat file. The return value is false if the file's contents could +# not be completely created; otherwise a true value is returned. +# +sub write($$) +{ + my $file = shift; + my $data = shift; + + my @args = mkdatfile %$data; + my $template = shift @args; + print $file pack $template, @args; +} + +# +# +# + +package lynxfmt; + +my @objectkey = ($tilenames{"empty"}, + $tilenames{"wall"}, + $tilenames{"ice"}, + $tilenames{"dirt"}, + $tilenames{"blue block floor"}, + $tilenames{"force north"}, + $tilenames{"force east"}, + $tilenames{"force south"}, + $tilenames{"force west"}, + $tilenames{"force any"}, + $tilenames{"ice corner se"}, + $tilenames{"ice corner sw"}, + $tilenames{"ice corner nw"}, + $tilenames{"ice corner ne"}, + $tilenames{"teleport"}, + $tilenames{"ice boots"}, + $tilenames{"fire boots"}, + $tilenames{"force boots"}, + $tilenames{"water boots"}, + $tilenames{"fire"}, + $tilenames{"water"}, + $tilenames{"thief"}, + $tilenames{"popup wall"}, + $tilenames{"toggle open"}, + $tilenames{"toggle closed"}, + $tilenames{"green button"}, + $tilenames{"red door"}, + $tilenames{"blue door"}, + $tilenames{"yellow door"}, + $tilenames{"green door"}, + $tilenames{"red key"}, + $tilenames{"blue key"}, + $tilenames{"yellow key"}, + $tilenames{"green key"}, + $tilenames{"blue button"}, + $tilenames{"computer chip"}, # counted + $tilenames{"socket"}, + $tilenames{"exit"}, + $tilenames{"invisible wall temporary"}, + $tilenames{"invisible wall permanent"}, + $tilenames{"gravel"}, + $tilenames{"wall east"}, + $tilenames{"wall south"}, + $tilenames{"wall southeast"}, + $tilenames{"bomb"}, + $tilenames{"bear trap"}, + $tilenames{"brown button"}, + $tilenames{"clone machine"}, + $tilenames{"red button"}, + $tilenames{"computer chip"}, # uncounted + $tilenames{"blue block wall"}, + $tilenames{"hint button"}); + +my @creaturekey = (0, 0, 0, 0, + $tilenames{"chip north"}, $tilenames{"chip east"}, + $tilenames{"chip south"}, $tilenames{"chip west"}, + $tilenames{"bug north"}, $tilenames{"bug east"}, + $tilenames{"bug south"}, $tilenames{"bug west"}, + $tilenames{"centipede north"}, $tilenames{"centipede east"}, + $tilenames{"centipede south"}, $tilenames{"centipede west"}, + $tilenames{"fireball north"}, $tilenames{"fireball east"}, + $tilenames{"fireball south"}, $tilenames{"fireball west"}, + $tilenames{"glider north"}, $tilenames{"glider east"}, + $tilenames{"glider south"}, $tilenames{"glider west"}, + $tilenames{"ball north"}, $tilenames{"ball east"}, + $tilenames{"ball south"}, $tilenames{"ball west"}, + $tilenames{"block north"}, $tilenames{"block east"}, + $tilenames{"block south"}, $tilenames{"block west"}, + $tilenames{"tank north"}, $tilenames{"tank east"}, + $tilenames{"tank south"}, $tilenames{"tank west"}, + $tilenames{"walker north"}, $tilenames{"walker east"}, + $tilenames{"walker south"}, $tilenames{"walker west"}, + $tilenames{"blob north"}, $tilenames{"blob east"}, + $tilenames{"blob south"}, $tilenames{"blob west"}, + $tilenames{"teeth north"}, $tilenames{"teeth east"}, + $tilenames{"teeth south"}, $tilenames{"teeth west"}); + +my @textkey = + ("\n"," ","0","1","2","3","4","5","6","7","8","9","A","B","C","D", + "E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T", + "U","V","W","X","Y","Z","!",'"',"'","(",")",",","-",".",":",";", + "?", (("%") x 207)); + +my @levelfilenames = @{ + [qw(lesson_1.pak lesson_2.pak lesson_3.pak lesson_4.pak + lesson_5.pak lesson_6.pak lesson_7.pak lesson_8.pak + nuts_and.pak brushfir.pak trinity.pak hunt.pak + southpol.pak telebloc.pak elementa.pak cellbloc.pak + nice_day.pak castle_m.pak digger.pak tossed_s.pak + iceberg.pak forced_e.pak blobnet.pak oorto_ge.pak + blink.pak chchchip.pak go_with_.pak ping_pon.pak + arcticfl.pak mishmesh.pak knot.pak scavenge.pak + on_the_r.pak cypher.pak lemmings.pak ladder.pak + seeing_s.pak sampler.pak glut.pak floorgas.pak + i.pak beware_o.pak lock_blo.pak refracti.pak + monster_.pak three_do.pak pier_sev.pak mugger_s.pak + problems.pak digdirt.pak i_slide.pak the_last.pak + traffic_.pak grail.pak potpourr.pak deepfree.pak + mulligan.pak loop_aro.pak hidden_d.pak scoundre.pak + rink.pak slo_mo.pak block_fa.pak spooks.pak + amsterda.pak victim.pak chipmine.pak eeny_min.pak + bounce_c.pak nightmar.pak corridor.pak reverse_.pak + morton.pak playtime.pak steam.pak four_ple.pak + invincib.pak force_sq.pak drawn_an.pak vanishin.pak + writers_.pak socialis.pak up_the_b.pak wars.pak + telenet.pak suicide.pak citybloc.pak spirals.pak + block.pak playhous.pak jumping_.pak vortex.pak + roadsign.pak now_you_.pak four_squ.pak paranoia.pak + metastab.pak shrinkin.pak catacomb.pak colony.pak + apartmen.pak icehouse.pak memory.pak jailer.pak + short_ci.pak kablam.pak balls_o_.pak block_ou.pak + torturec.pak chiller.pak time_lap.pak fortune_.pak + open_que.pak deceptio.pak oversea_.pak block_ii.pak + the_mars.pak miss_dir.pak slide_st.pak alphabet.pak + perfect_.pak t_fair.pak the_pris.pak firetrap.pak + mixed_nu.pak block_n_.pak skelzie.pak all_full.pak + lobster_.pak ice_cube.pak totally_.pak mix_up.pak + blobdanc.pak pain.pak trust_me.pak doublema.pak + goldkey.pak partial_.pak yorkhous.pak icedeath.pak + undergro.pak pentagra.pak stripes.pak fireflie.pak + level145.pak cake_wal.pak force_fi.pak mind_blo.pak + special.pak level150.pak)] +}; + +my (%objectkey, %creaturekey, %textkey); +for (0 .. $#objectkey) { $objectkey{$objectkey[$_]} = $_ } +for (0 .. $#creaturekey) { $creaturekey{$creaturekey[$_]} = $_ } +$creaturekey{$tilenames{"block"}} = $creaturekey{$tilenames{"block north"}}; +for (0 .. $#textkey) { $textkey{$textkey[$_]} = chr $_ } + +# +# +# + +sub longestmatch($$$) +{ + my $dictionary = shift; + my $data = shift; + my $pos = shift; + + my ($longest, $longestlen) = ("", 0); + foreach my $entry (@$dictionary) { + my $len = length $entry->{text}; + if ($len > $longestlen && $entry->{text} eq substr $data, $pos, $len) { + ($longest, $longestlen) = ($entry, $len); + } + } + return $longest; +} + +sub builddict($) +{ + my $data = shift; + my $dictionary = [ ]; + + my $pos = 0; + while ($pos < length $data) { + my $entry = { refcount => 0 }; + my ($match, $len); + $match = longestmatch $dictionary, $data, $pos; + if ($match) { + $entry->{left} = $match; + $len = length $match->{text}; + } else { + $len = 1; + } + $entry->{text} = substr $data, $pos, $len; + $pos += $len; + last if $pos >= length $data; + $match = longestmatch $dictionary, $data, $pos; + if ($match) { + $entry->{right} = $match; + $len = length $match->{text}; + } else { + $len = 1; + } + $entry->{text} .= substr $data, $pos, $len; + $pos += $len; + push @$dictionary, $entry; + } + + return $dictionary; +} + +sub refcountadd($$); +sub refcountadd($$) +{ + my $entry = shift; + $entry->{refcount} += shift; + refcountadd $entry->{left}, $entry->{refcount} if exists $entry->{left}; + refcountadd $entry->{right}, $entry->{refcount} if exists $entry->{right}; +} + +sub countuses($$) +{ + my $dictionary = shift; + my $data = shift; + + my $pos = 0; + while ($pos < length $data) { + my $entry = longestmatch $dictionary, $data, $pos; + if ($entry) { + ++$entry->{refcount}; + $pos += length $entry->{text}; + } else { + ++$pos; + } + } + foreach my $entry (@$dictionary) { refcountadd $entry, 0 } +} + +sub assignkeys($$) +{ + my $dictionary = shift; + my $data = shift; + my @used; + + while ($data =~ /(.)/gs) { $used[ord $1] = 1 } + my $n = 0; + foreach my $entry (@$dictionary) { + ++$n while $used[$n]; + die "too many dictionary entries; not enough keys" if $n >= 256; + $entry->{key} = chr $n; + $used[$n] = 1; + } +} + +sub composedict($) +{ + my $dictionary = shift; + my ($out, $len) = ("", 0); + + foreach my $entry (@$dictionary) { + $out .= $entry->{key}; + if (exists $entry->{left}) { + $out .= $entry->{left}{key}; + } else { + $out .= substr $entry->{text}, 0, 1; + } + if (exists $entry->{right}) { + $out .= $entry->{right}{key}; + } else { + $out .= substr $entry->{text}, -1; + } + ++$len; + } + + return ($out, $len); +} + +sub composedata($$) +{ + my $dictionary = shift; + my $data = shift; + my ($out, $len) = ("", 0); + + my $pos = 0; + while ($pos < length $data) { + my $entry = longestmatch $dictionary, $data, $pos; + if ($entry) { + $out .= $entry->{key}; + $pos += length $entry->{text}; + } else { + $out .= substr $data, $pos, 1; + ++$pos; + } + ++$len; + } + + return ($out, $len); +} + +sub compress($) +{ + my $data = shift; + my $dictionary = builddict $data; + countuses $dictionary, $data; + $dictionary = [ grep { $_->{refcount} > 3 } @$dictionary ]; + assignkeys $dictionary, $data; + my ($cdict, $dictlen) = composedict $dictionary; + my ($cdata, $datalen) = composedata $dictionary, $data; + return pack("vv", $dictlen, $datalen) . $cdict . $cdata; +} + +sub expand($) +{ + my $data = shift; + + my $tablesize = unpack "v", substr $data, 0, 2, ""; + my $datasize = unpack "v", substr $data, 0, 2, ""; + + my @data = map { ord } split //, $data; + my @table; + + for (my $n = 0 ; $n < $tablesize ; ++$n) { + return ::err "@{[$tablesize - $n]} entries missing" + unless @data; + my $key = shift @data; + my $val1 = shift @data; + my $val2 = shift @data; + if (defined $table[$val1]) { + $val1 = $table[$val1]; + } else { + $val1 = chr $val1; + } + if (defined $table[$val2]) { + $val2 = $table[$val2]; + } else { + $val2 = chr $val2; + } + $table[$key] = "$val1$val2"; + } + + $data = ""; + foreach my $byte (@data) { + if (defined $table[$byte]) { + $data .= $table[$byte]; + } else { + $data .= chr $byte; + } + } + + return $data; +} + +sub parsemap($$) +{ + my $level = shift; + my @data = map { ord } split //, shift; + + return ::err "@{[1024 - @data]} bytes missing from map data" + unless @data == 1024; + $level->{chips} = 0; + foreach my $y (0 .. 31) { + foreach my $x (0 .. 31) { + my $obj = shift @data; + ::err "undefined object $obj at ($x $y)" + unless defined $objectkey[$obj]; + $level->{map}[$y][$x][0] = $objectkey[$obj]; + $level->{map}[$y][$x][1] = 0; + ++$level->{chips} if $obj == 0x23; + } + } + + return 1; +} + +sub parsecrlist($$) +{ + my $level = shift; + my $data = shift; + + my @t = map { ord } split //, substr $data, 0, 128, ""; + my @x = map { ord } split //, substr $data, 0, 128, ""; + my @y = map { ord } split //, substr $data, 0, 128, ""; + + foreach my $n (0 .. 127) { + next unless $t[$n]; + my $x = $x[$n] >> 3; + my $y = $y[$n] >> 3; + my $t = $creaturekey[$t[$n] & 0x7F]; + push @{$level->{creatures}}, [ $y, $x ]; # unless $t[$n] & 0x80; + $level->{map}[$y][$x][1] = $level->{map}[$y][$x][0]; + $level->{map}[$y][$x][0] = $t; + } + + return 1; +} + +sub parselevel($) +{ + my $data = shift; + my $level = { }; + + $data = expand $data or return ::err "invalid data"; + + local $_; + $_ = substr $data, 0, 1024, ""; + parsemap $level, $_ + or return ::err "invalid map"; + $_ = substr $data, 0, 384, ""; + parsecrlist $level, $_ + or return ::err "invalid creature list"; + $level->{creatures} = ::makedatcrlist $level->{map}, + $level->{lynxcreatures}; + $level->{traps} = txtfile::buildtraplist $level->{map}; + $level->{cloners} = txtfile::buildclonerlist $level->{map}; + + $level->{leveltime} = unpack "v", substr $data, 0, 2, ""; + + $data = join "", map { $textkey[ord] } split //, $data; + $data =~ s/\A([^\n]+)\n//; + $level->{title} = $1; + $data =~ s/\n+\Z//; + if (length $data) { + $data =~ tr/\n/ /s; + $level->{hint} = $data; + } + + return $level; +} + +sub readmsdos($) +{ + my $dirname = shift; + my $data = { ruleset => "lynx" }; + + foreach my $n (0 .. $#levelfilenames) { + $filename = "$dirname/$levelfilenames[$n]"; + $filelevel = $n + 1; + next unless -e $filename; + open FILE, "< $filename" or return ::err $!; + binmode FILE; + my $level = parselevel join "", ; + close FILE; + return unless defined $level; + $level->{number} = $n + 1; + $level->{passwd} = $origpasswords[$n]; + push @{$data->{levels}}, $level; + } + + return $data; +} + +sub readrom($) +{ + my $input = shift; + my $data = { ruleset => "lynx" }; + + my $buf = ::fileread $input, "a20" or return; + return ::err "invalid ROM file" + unless $buf eq "LYNX\000\002\000\000\001\000chipchal.l"; + + my @levels; + sysseek $input, 0x02F0, 0 or return ::err $!; + for (my $n = 0 ; $n < 150 ; ++$n) { + my @rec = ::fileread $input, "C4vv" or return; + $levels[$n][0] = (($rec[0] << 9) | $rec[1] + | (($rec[2] & 0x01) << 8)) + 0x40; + $levels[$n][1] = $rec[5]; + } + + for (my $n = 0 ; $n < 150 ; ++$n) { + $filelevel = $n + 1; + $buf = sysseek $input, $levels[$n][0], 0 or return ::err $!; + $buf = ::fileread $input, "a$levels[$n][1]" or return; + next if $levels[$n][1] == 5 && $buf eq "\000\000\001\000\377"; + my $level = parselevel $buf; + return unless defined $level; + $level->{number} = $n + 1; + $level->{passwd} = $origpasswords[$n]; + push @{$data->{levels}}, $level; + } + + return $data; +} + +# +# +# + +sub translatetext($;$) +{ + my $in = shift; + my $multiline = shift || 0; + + my $out = ""; + my ($x, $y) = (0, 0); + my $brk = [ undef ]; + + foreach my $char (split //, $in) { + if ($char eq "\n") { + ++$y; + $x = -1; + $brk = [ undef ]; + } elsif ($x >= 19) { + if (!$multiline || $y >= 6) { + ::err "truncated text"; + substr($out, 17 - $x) = "" if $y >= 6 && $x >= 19; + last; + } + if ($brk->[0]) { + $x -= $brk->[0]; + substr($out, $brk->[1], 1) = "\0"; + } else { + $x = -1; + $out .= "\0"; + } + ++$y; + $brk = [ undef ]; + } elsif ($char eq " ") { + $brk = [ $x, length $out ]; + } + $out .= $textkey{uc $char}; + ++$x; + } + + return $out; +} + +sub mklevelmap($) +{ + my $level = shift; + my $out = ""; + my $chips = 0; + + for (my $y = 0 ; $y < 32 ; ++$y) { + for (my $x = 0 ; $x < 32 ; ++$x) { + my $obj; + my $top = $level->{map}[$y][$x][0]; + my $bot = $level->{map}[$y][$x][1]; + if (::iscreature $top || ::ischip $top || ::isblock $top) { + $obj = $bot; + if (::iscreature $obj || ::isblock $obj || ::ischip $obj) { + ::err "ignoring buried creature"; + $obj = 0; + } + } else { + ::err "ignoring buried object" if $bot; + $obj = $top; + } + if ($obj == $tilenames{"computer chip"}) { + $obj = $chips < $level->{chips} ? 0x23 : 0x31; + ++$chips; + } else { + $obj = $objectkey{$obj}; + unless (defined $obj) { + ::err "ignoring non-Lynx object"; + $obj = 0; + } + } + $out .= chr $obj; + } + } + + ::err "chips needed was reduced" if $chips < $level->{chips}; + + return $out; +} + +sub mklevelcrlist($) +{ + my $level = shift; + my @listed; + my @crlist; + + return ::err "invalid creature list: $level->{lynxcreatures}" + unless ref $level->{lynxcreatures}; + + my ($types, $xs, $ys) = ("", "", ""); + foreach my $creature (@{$level->{lynxcreatures}}) { + my $y = $creature->[0]; + my $x = $creature->[1]; + my $type = $level->{map}[$y][$x][0]; + $type = $creaturekey{$type}; + unless (defined $type) { + ::err "ignoring non-Lynx creature in creature list"; + next; + } + $type |= 0x80 if $creature->[2] < 0; + $y <<= 3; + $x <<= 3; + ++$y, ++$x if $creature->[2] == 0; + $types .= chr $type; + $xs .= chr $x; + $ys .= chr $y; + } + + return pack "a128 a128 a128", $types, $xs, $ys; +} + +sub mkleveldata($) +{ + my $level = shift; + my $out = ""; + my $part; + + $part = mklevelmap $level; + return unless defined $part; + $out .= $part; + + $part = mklevelcrlist $level; + return unless defined $part; + $out .= $part; + + $out .= pack "v", $level->{leveltime}; + + $part = translatetext $level->{title}; + return unless defined $part; + $out .= "$part\0"; + + if (exists $level->{hint}) { + $part = translatetext $level->{hint}, 1; + return unless defined $part; + $out .= "$part\0"; + } + + $out .= "\0"; + + return compress $out; +} + +sub writemsdos($$) +{ + my $dirname = shift; + my $data = shift; + + ::err "warning: storing an MS-ruleset level set in a Lynx-only file format" + unless $data->{ruleset} eq "lynx"; + + foreach my $level (@{$data->{levels}}) { + $filename = $dirname; + $filelevel = undef; + if ($level->{number} >= @levelfilenames) { + ::err "ignoring level $level->{number}, number too high"; + next; + } elsif ($level->{number} < 1) { + ::err "ignoring level $level->{number}, number invalid"; + next; + } + $filename = "$dirname/$levelfilenames[$level->{number} - 1]"; + $filelevel = $level->{number}; + ::err "ignoring password" + if $level->{passwd} ne $origpasswords[$level->{number} - 1]; + open FILE, "> $filename" or return ::err $!; + binmode FILE; + my $out = mkleveldata $level or return; + print FILE $out or return ::err $!; + close FILE or return ::err $!; + } + + return 1; +} + +sub writerom($$) +{ + my $file = shift; + my $data = shift; + + ::err "warning: storing an MS-ruleset level set in a Lynx-only file format" + unless $data->{ruleset} eq "lynx"; + + my $buf = ::fileread $file, "a22" or return; + return ::err "invalid ROM file" + unless $buf eq "LYNX\000\002\000\000\001\000chipchal.lyx"; + + sysseek $file, 0x02F0, 0 or return ::err $!; + my @ptr = ::fileread $file, "C4" or return ::err $!; + my $startpos = (($ptr[0] << 9) | $ptr[1] | (($ptr[2] & 0x01) << 8)); + + my @levellist; + my $dropped; + foreach my $level (@{$data->{levels}}) { + my $n = $level->{number}; + $filelevel = $n; + if ($n < 1) { + ::err "ignoring invalid-numbered level $n"; + } elsif ($n > 149) { + ++$dropped; + } elsif (defined $levellist[$n]) { + ::err "ignoring duplicate level $n"; + } else { + ::err "ignoring password" + if $level->{passwd} ne $origpasswords[$n - 1]; + $levellist[$n] = mkleveldata $level; + return unless defined $levellist[$n]; + } + } + ::err "ignored $dropped level(s) above level 149" if $dropped; + + my $levels = ""; + my $index = ""; + my $ptr = $startpos; + for (my $n = 1 ; $n <= 149 ; ++$n) { + my $size; + if ($levellist[$n]) { + $levels .= $levellist[$n]; + $size = length $levellist[$n]; + } else { + $levels .= "\000\000\001\000\377"; + $size = 5; + } + $index .= pack "C4vv", ($ptr >> 9), ($ptr & 0xFF), + (($ptr >> 8) & 0x01), 0, 0, $size; + $ptr += $size; + } + $levels .= "\000\000\001\000\377"; + $index .= pack "C4vv", ($ptr >> 9), ($ptr & 0xFF), + (($ptr >> 8) & 0x01), 0, 0, 5; + + return ::err "too much data; cannot fit inside the ROM file" + if length $levels > 0x11D00; + + sysseek $file, 0x02F0, 0 or return ::err $!; + syswrite $file, $index or return ::err $!; + sysseek $file, $startpos + 0x40, 0 or return ::err $!; + syswrite $file, $levels or return ::err $!; + + return 1; +} + +# +# +# + +package cudfile; + +# The terse names used by the universal-dump file format. +# +my @shortnames = ("empty", # 0x00 + "wall", # 0x01 + "ic_chip", # 0x02 + "water", # 0x03 + "fire", # 0x04 + "inv_wall_per", # 0x05 + "wall_N", # 0x06 + "wall_W", # 0x07 + "wall_S", # 0x08 + "wall_E", # 0x09 + "block", # 0x0A + "dirt", # 0x0B + "ice", # 0x0C + "force_S", # 0x0D + "block_N", # 0x0E + "block_W", # 0x0F + "block_S", # 0x10 + "block_E", # 0x11 + "force_N", # 0x12 + "force_E", # 0x13 + "force_W", # 0x14 + "exit", # 0x15 + "blue_door", # 0x16 + "red_door", # 0x17 + "green_door", # 0x18 + "yellow_door", # 0x19 + "ice_turn_SE", # 0x1A + "ice_turn_SW", # 0x1B + "ice_turn_NW", # 0x1C + "ice_turn_NE", # 0x1D + "blue_floor", # 0x1E + "blue_wall", # 0x1F + "overlay", # 0x20 + "thief", # 0x21 + "socket", # 0x22 + "green_button", # 0x23 + "red_button", # 0x24 + "toggle_close", # 0x25 + "toggle_open", # 0x26 + "brown_button", # 0x27 + "blue_button", # 0x28 + "teleport", # 0x29 + "bomb", # 0x2A + "trap", # 0x2B + "inv_wall_tmp", # 0x2C + "gravel", # 0x2D + "popup_wall", # 0x2E + "hint_button", # 0x2F + "wall_SE", # 0x30 + "cloner", # 0x31 + "force_any", # 0x32 + "chip_drowned", # 0x33 + "chip_burned", # 0x34 + "chip_bombed", # 0x35 + "unused_1", # 0x36 + "unused_2", # 0x37 + "unused_3", # 0x38 + "chip_exiting", # 0x39 + "exit_1", # 0x3A + "exit_2", # 0x3B + "chip_swim_N", # 0x3C + "chip_swim_W", # 0x3D + "chip_swim_S", # 0x3E + "chip_swim_E", # 0x3F + "bug_N", # 0x40 + "bug_W", # 0x41 + "bug_S", # 0x42 + "bug_E", # 0x43 + "fireball_N", # 0x44 + "fireball_W", # 0x45 + "fireball_S", # 0x46 + "fireball_E", # 0x47 + "ball_N", # 0x48 + "ball_W", # 0x49 + "ball_S", # 0x4A + "ball_E", # 0x4B + "tank_N", # 0x4C + "tank_W", # 0x4D + "tank_S", # 0x4E + "tank_E", # 0x4F + "glider_N", # 0x50 + "glider_W", # 0x51 + "glider_S", # 0x52 + "glider_E", # 0x53 + "teeth_N", # 0x54 + "teeth_W", # 0x55 + "teeth_S", # 0x56 + "teeth_E", # 0x57 + "walker_N", # 0x58 + "walker_W", # 0x59 + "walker_S", # 0x5A + "walker_E", # 0x5B + "blob_N", # 0x5C + "blob_W", # 0x5D + "blob_S", # 0x5E + "blob_E", # 0x5F + "centipede_N", # 0x60 + "centipede_W", # 0x61 + "centipede_S", # 0x62 + "centipede_E", # 0x63 + "blue_key", # 0x64 + "red_key", # 0x65 + "green_key", # 0x66 + "yellow_key", # 0x67 + "water_boots", # 0x68 + "fire_boots", # 0x69 + "ice_boots", # 0x6A + "force_boots", # 0x6B + "chip_N", # 0x6C + "chip_W", # 0x6D + "chip_S", # 0x6E + "chip_E" # 0x6F +); +for (0x70 .. 0xFF) { $shortnames[$_] = sprintf "tile_%02X", $_ } + +sub write($$) +{ + my $output = shift; + my $data = shift; + my $list; + + print $output "BEGIN CUD 1 ruleset $data->{ruleset}\n\n" or return; + + foreach my $level (@{$data->{levels}}) { + printf $output "%03d chips %d\n", $level->{number}, $level->{chips} + or return; + printf $output "%03d time %d\n", $level->{number}, $level->{leveltime} + or return; + printf $output "%03d passwd %s\n", $level->{number}, $level->{passwd} + or return; + printf $output "%03d title:%s\n", $level->{number}, + ::escape $level->{title} + or return; + printf $output "%03d hint", $level->{number} or return; + print $output ":", ::escape $level->{hint} or return + if exists $level->{hint}; + print $output "\n" or return; + + my @notes; + $list = $level->{traps}; + foreach my $i (0 .. $#$list) { + $notes[$list->[$i]{from}[0]][$list->[$i]{from}[1]]{tfr} = $i + 1; + $notes[$list->[$i]{to}[0]][$list->[$i]{to}[1]]{tto} = $i + 1; + } + $list = $level->{cloners}; + foreach my $i (0 .. $#$list) { + $notes[$list->[$i]{from}[0]][$list->[$i]{from}[1]]{cfr} = $i + 1; + $notes[$list->[$i]{to}[0]][$list->[$i]{to}[1]]{cto} = $i + 1; + } + $list = $level->{creatures}; + foreach my $i (0 .. $#$list) { + $notes[$list->[$i][0]][$list->[$i][1]]{crl} = $i + 1; + } + + foreach my $y (0 .. 31) { + foreach my $x (0 .. 31) { + next if $level->{map}[$y][$x][0] == 0 + && $level->{map}[$y][$x][1] == 0 + && !defined $notes[$y][$x]; + printf $output "%03d (%02d %02d) ", $level->{number}, $x, $y + or return; + printf $output "%-12.12s %-12.12s ", + $shortnames[$level->{map}[$y][$x][0]], + $shortnames[$level->{map}[$y][$x][1]] + or return; + printf $output " Tfr=%-2.2s", $notes[$y][$x]{tfr} or return + if exists $notes[$y][$x]{tfr}; + printf $output " Tto=%-2.2s", $notes[$y][$x]{tto} or return + if exists $notes[$y][$x]{tto}; + printf $output " Cfr=%-2.2s", $notes[$y][$x]{cfr} or return + if exists $notes[$y][$x]{cfr}; + printf $output " Cto=%-2.2s", $notes[$y][$x]{cto} or return + if exists $notes[$y][$x]{cto}; + printf $output " CL=%-3.3s", $notes[$y][$x]{crl} or return + if exists $notes[$y][$x]{crl}; + printf $output "\n" or return; + } + } + printf $output "\n" or return; + } + + print $output "END\n" or return; + + return 1; +} + +# +# +# + +package main; + +use constant yowzitch => < "1.0\n"; + +my ($infile, $outfile); +my ($intype, $outtype); + +sub deducetype($) +{ + local $_ = shift; + if (-d $_) { + return "P"; + } elsif (/\.dat$/) { + return "D"; + } elsif (/\.txt$/ || /^-$/) { + return "T"; + } elsif (/\.lnx$/ || /\.lyx$/) { + return "R"; + } elsif (/\.cud$/) { + return "U"; + } + return; +} + +sub findfiletype($) +{ + open FILE, shift or return; + local $_; + sysread FILE, $_, 16 or return; + close FILE; + return "D" if /\A\xAC\xAA\x02/; + return "R" if /\ALYNX\0/; + return "T" if /\A\s*(rul|til|max|%%%)/; + return; +} + +die yowzitch unless @ARGV; +print yowzitch and exit if $ARGV[0] =~ /^--?(h(elp)?|\?)$/; +print vourzhon and exit if $ARGV[0] =~ /^--?[Vv](ersion)?$/; + +$infile = shift; +if ($infile =~ /^-([A-Za-z])$/) { + $intype = uc $1; + $infile = shift; +} +die yowzitch unless @ARGV; +$outfile = shift; +if ($outfile =~ /^-([A-Za-z])$/) { + $outtype = uc $1; + $outfile = shift; +} +die yowzitch unless defined $infile && defined $outfile && @ARGV == 0; + +$intype ||= deducetype $infile; +$outtype ||= deducetype $outfile; +die "$outfile: file type unspecified\n" unless $outtype; +$intype = findfiletype $infile if !defined $intype && -f $infile; +die "$infile: file type unspecified\n" unless $intype; + +my $data; + +$filename = $infile; +if ($intype eq "D") { + open FILE, "< $infile" or die "$infile: $!\n"; + binmode FILE; + $data = datfile::read \*FILE or exit 1; + close FILE; +} elsif ($intype eq "T") { + open FILE, "< $infile" or die "$infile: $!\n"; + $data = txtfile::read \*FILE or exit 1; + close FILE; +} elsif ($intype eq "P") { + $data = lynxfmt::readmsdos $infile or exit 1; +} elsif ($intype eq "R") { + open FILE, "< $infile" or die "$infile: $!\n"; + binmode FILE; + $data = lynxfmt::readrom \*FILE or exit 1; + close FILE; +} elsif ($intype eq "U") { + die "File type -U is a write-only file format.\n"; +} else { + die "Unknown file type option -$intype.\n"; +} + +undef $filename; +undef $filelevel; +undef $filepos; + +$filename = $outfile; + +if ($outtype eq "D") { + open FILE, "> $outfile" or die "$outfile: $!\n"; + binmode FILE; + datfile::write \*FILE, $data or die "$outfile: $!\n"; + close FILE or die "$outfile: $!\n"; +} elsif ($outtype eq "T") { + open FILE, "> $outfile" or die "$outfile: $!\n"; + txtfile::write \*FILE, $data or die "$outfile: $!\n"; + close FILE or die "$outfile: $!\n"; +} elsif ($outtype eq "P") { + lynxfmt::writemsdos $outfile, $data or exit 1; +} elsif ($outtype eq "R") { + open FILE, "+< $outfile" or die "$outfile: $!\n"; + binmode FILE; + lynxfmt::writerom \*FILE, $data or die "$outfile: $!\n"; + close FILE or die "$outfile: $!\n"; +} elsif ($outtype eq "U") { + open FILE, "> $outfile" or die "$outfile: $!\n"; + cudfile::write \*FILE, $data or die "$outfile: $!\n"; + close FILE or die "$outfile: $!\n"; +} else { + die "Unknown file type option -$outtype.\n"; +} + +# +# The documentation +# + +=head1 NAME + +c4 - Chip's Challenge combined converter + +=head1 SYNOPSIS + + c4 [-INTYPE] INFILENAME [-OUTTYPE] OUTFILENAME + +c4 allows one to translate between the several different types of +files used to represent level sets for the game Chip's Challenge. + +c4 expects there to be two files named on the command-line. c4 reads +the levels stored in the first file, and then writes the levels out to +the second file. The format to use with each file usually can be +inferred by c4 by examining the filenames. If not, then it may be +necessary to use switches before one or both filenames to indicate +their type. + +There are four different types of files that c4 understands. + + -D MS data file (*.dat). + +This is the file type used by Chip's Challenge for Microsoft Windows +3.x. It is the file type used by most other programs, such as ChipEdit +and Tile World. + + -R Lynx ROM file (*.lnx, *.lyx) + +This "file type" is actually just a ROM image of the original Chip's +Challenge for the Atari Lynx handheld. It is used by Lynx emulators +such as Handy. + + -P MS-DOS fileset (directory of *.pak files) + +This is the format used by the MS-DOS port of Chip's Challenge. In +this case, the filename given on the command line actually names a +directory, containing *.pak files. + + -T textual source file (*.txt) + +This file type is native to c4. It is a plain text file, which allows +levels to be defined pictorially using a simple text editor. A +complete description of the syntax of these files is provided below. + +=head1 EXAMPLES + + c4 mylevels.txt mylevels.dat + +Create a .dat file from a textual source file. + + c4 -P levels -D doslevels.dat + +"levels" is a directory of MS-DOS *.pak files. c4 translates the +directory contents into a single .dat file. Note that the switches in +this example are optional, as c4 would be able to infer the desired +formats. + + c4 mylevels.dat chipsch.lnx + +Embed the levels from the .dat file into a Lynx ROM file. Note that c4 +does NOT create chipsch.lnx. You must provide the ROM image file, +which c4 then alters to contain your levels. (Obviously, you should +not use this command on your master copy of the ROM file.) + + c4 chipsch.lnx -T out + +Output the levels in the .dat file as a text file. Here the -T switch +is needed to indicate that a text file is the desired output format. + +When producing a text file, c4 will attempt to produce legible source, +but the results will often not be as good as what a human being would +produce. (In particular, c4 cannot draw overlays.) + +=head1 NOTES + +Be aware that there can be various problems when translating a set of +levels using the MS ruleset to one of the Lynx-only file formats. +There are numerous objects and configurations in the MS ruleset which +cannot be represented in the Lynx ruleset. Usually c4 will display a +warning when some aspect of the data could not be transferred intact +because of this. + +The remainder of this documentation describes the syntax of the +textual source file format. + +=head1 LAYOUT OF THE INPUT FILE + +The source file is broken up into subsections. Each subsection defines +a separate level in the set. + +The subsections are separated from each other by a line containing +three percent signs: + + %%% + +A line of three percent signs also comes before the first level and +after the last level, at the end of the source file. + +Any other line that begins with a percent sign is treated as a +comment, and its contents are ignored. + +Beyond these things, the source file consists of statements. +Statements generally appear as a single line of text. Some statements, +however, require multiple lines. These multi-line statements are +terminated with the word B appearing alone on a line. + +=head1 INPUT FILE HEADER STATEMENTS + +There are a couple of statements that can appear at the very top of +the source file, before the first level subsection. + + ruleset [ lynx | ms ] + +The B statement is the most important of these. It defines +the ruleset for the level set. If the B statment is absent, +it defaults to B. + + maxlevel NNN + +The B statement specifies the number of the last level in +the .dat file. By default, this value is provided automatically and +does not need to be specified. + +In addition to the above, a set of tile definitions can appear in the +header area. See below for a full description of the B +multi-line statement. Any tile definitions provided here remain in +force throughout the file. + +=head1 INPUT FILE LEVEL STATEMENTS + +Within each level's subsection, the following two statments will +usually appear at the top. + + title STRING + password PASS + +The B statement supplies the level's title, or name. The title +string can be surrounded by double quotes, or unadorned. The +B<password> statement supplies the level's password. This password +must consist of exactly four uppercase alphabetic characters. + +If the level's number is 150 or less, the B<password> statement may be +omitted. In that case the level's password will default to match that +level in the original Lynx set. (N.B.: The Lynx ROM file format does +not provide a mechanism for setting passwords, so in that case the +default password will be used regardless.) + +The following statements may also appear in a level subsection. + + chips NNN + +The B<chips> statement defines how many chips are required on this +level to open the chip socket. The default value is zero. + + time NNN + +The B<time> statement defines how many seconds are on the level's +clock. The default value is zero (i.e., no time limit). + + hint STRING + +The B<hint> statement defines the level's hint text. As with the +B<title> statement, the string can either be unadorned or delimited +with double quotes. If a section contains multiple B<hint> statements, +the texts are appended together, e.g.: + + hint This is a relatively long hint, and so it + hint is helpful to be able to break it up across + hint several lines. + +Note that the same can be done with B<title> statements. + + tiles + DEF1 + DEF2 + ... + end + +The B<tiles> multi-line statement introduces one or more tile +definitions. The definitions appear one per line, until a line +containing B<end> is found. Note that the tile definitions given here +only apply to the current level. A complete description of tile +definitions is given below. + + map [ X Y ] map [ X Y ] + LINE1 LINE1 + LINE2 LINE2 + ... ... + and end + OVER1 + OVER2 + ... + end + +The B<map> statement defines the actual contents of (part of) the +level's map. The line containing the B<map> statement can optionally +include a pair of coordinates; these coordinates indicate where the +the section will be located on the level's map. If coordinates are +omitted, the defined section will be located at (0 0) -- i.e., the +upper-left corner of the level. The lines inside the B<map> statement +pictorially define the contents of the map section, until a line +containing B<and> or B<end> is encountered. When the map is terminated +by B<and>, then the lines defining the map section are immediately +followed by lines defining an overlay. The overlay uses the same +origin as the map section (though it is permissible for the overlay to +be smaller than the map section it is paired with). A complete +description of the map and overlay sections is given below. + + border TL + +The B<border> statement specifies a tile. The edges of the map are +then changed to contain this tile. Typically this is used to enclose +the level in walls. + +The following statements are also available, though they are usually +not needed. They provide means for explicitly defining level data, for +the occasional situation where the usual methods are more cumbersome. + + creatures X1 Y1 ; X2 Y2 ... + +The B<creatures> statements permits explicit naming of the coordinates +in the creature list. Pairs of coordinates are separated from each +other by semicolons; any number of coordinate pairs can be specified. +There can be multiple B<creatures> statements in a level's subsection. + + traps P1 Q1 -> R1 S1 ; P2 Q2 -> R2 S2 ... + +The B<traps> statement permits explicit naming of the coordinates for +elements in the bear trap list. Coordinates are given in one or more +groups of four, separated by semicolons. Each group consists of the x- +and y-coordinates of the brown button, an arrow (->), and then the x- +and y-coordinates of the bear trap. Any number of B<traps> statements +can appear in a level's subsection. + + cloners P1 Q1 -> R1 S1 ; P2 Q2 -> R2 S2 ... + +The B<cloners> statement permits explicit naming of elements in the +clone machine list. It uses the same syntax as the B<traps> statment, +with the red button's coordinates preceding the coordinates of the +clone machine. + + level NNN + +The B<level> statement defines the level's number. By default it is +one more than the number of the prior level. + + field NN B01 B02 ... + +The B<field> statement allows fields to be directly specified and +embedded in the .dat file. The first argument specifies the field +number; the remaining arguments provide the byte values for the actual +field data. These statements are only meaningful in conjunction with +producing a .dat file. + +=head1 DEFINING TILES + +A tile definition consists of two parts. The first part is either one +or two characters. The characters can be letters, numbers, punctuation +-- anything except spaces. The second part is the name of a tile or a +pair of tiles. The characters then become that tile's representation. + +Here is an example of some tile definitions: + + tiles + # wall + * teleport + rb red button + @ chip south + end + +(Note that a single tab character comes after the characters and +before the tile names.) Once these definitions have been provided, the +newly-defined characters can then be used in a map. + +The above definitions all use singular tiles. To define a pair of +tiles, combine the two names with a plus sign, like so: + + tiles + X block + bomb + G glider north + clone machine + end + +Notice that the top tile is named first, then the bottom tile. + +The B<tiles> statement is the only statement that can appear in the +header, as well as in a level's subsection. Tile definitions in the +header are global, and can be used in every subsection. Tile +definitions inside a subsection are local, and apply only to that +level. + +A number of tile definitions are pre-set ahead of time, supplying +standard representations for some of the most common tiles. (If these +representations are not desired, the characters can always be +redefined.) Here are some of the built-in definitions: + + # wall $ computer chip + , water H socket + = ice E exit + & fire [] block + 6 bomb ? hint button + +See below for the complete list of tile names and built-in +definitions. + +A few groups tiles allow one to specify multiple definitions in a +single line. For example: + + tiles + G glider + end + +This one definition is equivalent to the following: + + tiles + Gn glider north + Gs glider south + Ge glider east + Gw glider west + end + +(Note that "G" by itself is still undefined.) All creatures, including +Chip, can be defined using this abbreviated form. + +Doors and keys are the other groups that have this feature; the +following definition: + + tiles + D door + end + +is equivalent to: + + tiles + Dr red door + Db blue door + Dy yellow door + Dg green door + end + +=head1 MAP SECTIONS + +Once all the needed tiles have defined representations, using the map +statement is a simple matter. Here is an example: + + map + # # # # # # + # & & # # # + [] H E # + # & $ # # # + # # # # # # + end + +This is a map of a small room. A block stands in the way of the +entrance. Three of the four corners contain fire; the fourth contains +a chip. On the east wall is an exit guarded by a chip socket. + +Note that each cell in the map is two characters wide. (Thus, for +example, the octothorpes describe a solid wall around the room.) + +Here is a larger example, which presents the map from LESSON 2: + + tiles + B bug north + C chip south + end + + map 7 7 + # # # # # # # + # $ # + # # + # # # # # # # # # # # # + # # # # B , , $ # + # E H # # B , , [][]C ? # + # # # # B , , $ # + # # # # # # # # # # # # + # # + # $ # + # # # # # # # + end + +There are a couple of different ways to fill a cell with two tiles. +The first way is to simply use tile definitions which contains two +tiles: + + tiles + X block + bomb + G glider east + clone machine + end + + map 12 14 + # # + 6 E # + # # X + G + end + +The second way is to squeeze two representations into a single cell. +Obviously, this can only be done with both representations are a +single character. + + tiles + [ block + G glider east + + clone machine + end + + map 12 14 + # # + 6 E # + # # [6 + G+ + end + +In both cases, the top tile always comes before the bottom tile. Note +that you can "bury" a tile by placing it to the right of a space: + + map + # # # # # # + 6 6 6E # + # # # # # # + end + +Any number of map statements can appear in a level's subsection. The +map statements will be combined together to make the complete map. + +=head1 OVERLAY SECTIONS + +Every map statement can optionally include an overlay section. This +overlay permits button connections and monster ordering to be defined. + +The overlay is applied to the same position as the map section it +accompanies. The overlay can duplicate parts of the map section it +covers, and any such duplication will be ignored. The only characters +in the overlay that are significant are the ones that differ from the +map section it covers. These characters are treated as labels. Labels +are always a single character; two non-space characters in a cell +always indicates two separate labels. Any non-space characters can be +used as labels, as long as they don't match up with the map. + +An overlay section defines a button connection by using the same label +in two (or more) cells. One of the labelled cells will contain either +a bear trap or a clone machine, and the other will contain the +appropriate button. If there are more than two cells with the same +label, all but one should contain a button. + +Characters that only appear once in an overlay, on the other hand, +indicate creatures. The characters then indicate the ordering of the +creatures in the creature list with respect to each other. The +ordering of characters is the usual ASCII sequence (e.g., numbers +first, then capital letters, then lowercase letters). + +For example, here is a map with an overlay that demonstrates all three +of these uses: + + tiles + G glider east + + clone machine + r red button + * beartrap + b brown button + end + + map + G v # + G+ * r * G+ b & # r + G+ * r # # r + # > b b G < # # + and + 2 v # + A c C d C d & # A + B a C # # B + # > a c 1 < # # + end + +In this example, capitals are used for the clone machine connections, +lowercase for the bear trap connections, and numbers are used for the +creature ordering. + +(Note that the gliders atop clone machines are not numbered. While it +is not an error to include clone machine creatures in the ordering, +they are ignored under the MS ruleset.) + +It is not necessary to reproduce any of the map section's text in the +overlay section. Blanks can be used instead. The ignoring of matching +text is simply a feature designed to assist the user in keeping the +overlay's contents properly aligned. + +The B<traps>, B<cloners>, and B<creatures> statements can be used in +lieu of, or in conjunction with, data from overlay sections. In the +case of the creature list, items are added to the list in the order +that they are encountered in the source text. + +If a level contains no overlay information and none of the above three +statements, then this information will be filled in automatically. The +data will be determined by following the original Lynx-based rules -- +viz., buttons are connected to the next beartrap/clone machine in +reading order, wrapping around to the top if necessary. (Likewise, the +creature ordering is just the order of the creatures in their initial +placement, modified by swapping the first creature with Chip.) Thus, +if you actually want to force an empty bear trap list, clone machine +list, or creature list, you must include an empty B<traps>, +B<cloners>, and/or B<creatures> statement. + +=head1 TILE NAMES + +Here is the complete list of tiles as they are named in definitions. +Two or more names appearing on the same line indicates that they are +two different names for the same tile. Note that the tile names are +not case-sensitive; capitalization is ignored. + + empty + wall + water + fire + dirt + ice + gravel + computer chip ic chip + socket + exit + ice corner southeast ice se + ice corner southwest ice sw + ice corner northwest ice nw + ice corner northeast ice ne + force floor north force north + force floor south force south + force floor east force east + force floor west force west + force floor random force random force any + hidden wall permanent invisible wall permanent + hidden wall temporary invisible wall temporary + wall north partition north + wall south partition south + wall east partition east + wall west partition west + wall southeast partition southeast wall se + closed toggle wall closed toggle door toggle closed + open toggle wall open toggle door toggle open + blue door door blue + red door door red + green door door green + yellow door door yellow + blue key key blue + red key key red + green key key green + yellow key key yellow + blue button button blue tank button + red button button red clone button + green button button green toggle button + brown button button brown trap button + blue block floor blue wall fake + blue block wall blue wall real + thief + teleport + bomb + beartrap trap + popup wall + hint button + clone machine cloner + water boots water shield flippers + fire boots fire shield + ice boots spiked shoes skates + force boots magnet suction boots + block moveable block + cloning block north block north + cloning block south block south + cloning block east block east + cloning block west block west + chip north + chip south + chip east + chip west + ball north + tank north + bug north bee north + paramecium north centipede north + fireball north flame north + glider north ghost north + blob north + walker north dumbbell north + teeth north frog north + +(The last nine lines, listing the creatures, only show the +north-facing versions. The remaining 27 names, for the south-, east-, +and west-facing versions, follow the obvious patttern.) + +Note that tile names may be abbreviated to any unique prefix. In +particular, this permits one to write names like "glider north" as +simply "glider n". + +There are also tile names for the "extra" MS tiles. These tiles are +listed in parentheses, as an indicator that they were not originally +intended to be used in maps. + + (combination) + (chip drowned) + (chip burned) + (chip bombed) + (unused 1) + (unused 2) + (unused 3) + (exiting) + (exit 1) + (exit 2) + (chip swimming north) (chip swimming n) + (chip swimming west) (chip swimming w) + (chip swimming south) (chip swimming s) + (chip swimming east) (chip swimming e) + +Finally, note that one can also explicitly refer to tiles by their +hexadecimal byte value under the MS rules by using the "0x" prefix. +Thus, the names "0x2A" and "bomb" are equivalent. + +=head1 PREDEFINED TILE DEFINITIONS + +The following is the complete list of built-in tile definitions: + + # wall E exit + $ ic chip H socket + , water = ice + & fire 6 bomb + ; dirt : gravel + ~ wall north ^ force floor north + _ wall south v force floor south + | wall west < force floor west + | wall east > force floor east + _| wall southeast <> force floor random + ? hint button @ chip south + [] block [ block + ^] cloning block north + clone machine + <] cloning block west + clone machine + v] cloning block south + clone machine + >] cloning block east + clone machine + +=head1 LICENSE + +c4, Copyright (C) 2003-2006 Brian Raiter <breadbox@muppetlabs.com> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and documentation (the "Software"), to deal in +the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +=cut --- tworld-1.3.0.orig/debian/postrm +++ tworld-1.3.0/debian/postrm @@ -0,0 +1,10 @@ +#!/bin/sh +# postrm script for tworld +# copyright 2006-2011 by Gerfried Fuchs <rhonda@debian.org> +# Licenced under WTFPLv2 + +set -e + +if [ -x /usr/bin/update-menus ]; then + update-menus +fi --- tworld-1.3.0.orig/debian/tworld.desktop +++ tworld-1.3.0/debian/tworld.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Name=Tile World +Icon=/usr/share/pixmaps/tworld.xpm +Exec=tworld +Categories=Game;LogicGame; + --- tworld-1.3.0.orig/debian/control +++ tworld-1.3.0/debian/control @@ -0,0 +1,40 @@ +Source: tworld +Section: games +Priority: optional +Maintainer: Debian Games Team <pkg-games-devel@lists.alioth.debian.org> +Uploaders: Gerfried Fuchs <rhonda@debian.org> +Build-Depends: libsdl1.2-dev, quilt +Standards-Version: 3.9.2 +Vcs-Git: git://git.debian.org/git/pkg-games/tworld.git +Vcs-Browser: http://git.debian.org/?p=pkg-games/tworld.git +Homepage: http://www.muppetlabs.com/~breadbox/software/tworld/ + +Package: tworld-data +Architecture: all +Replaces: tworld (<< 1.3.0-4) +Description: Chip's Challenge Game Engine Emulation - level data files + Tile World is an emulation of the game "Chip's Challenge". "Chip's + Challenge" was originally written for the Atari Lynx by Chuck Sommerville, + and was later ported to MS Windows by Microsoft (among other ports). + . + This package contains the architecture independent data files. + +Package: tworld +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, tworld-data (= ${source:Version}) +Description: Chip's Challenge Game Engine Emulation + Tile World is an emulation of the game "Chip's Challenge". "Chip's + Challenge" was originally written for the Atari Lynx by Chuck Sommerville, + and was later ported to MS Windows by Microsoft (among other ports). + . + Please note: Tile World is an emulation of the game engine(s) only. It does + not come with the chips.dat file that contains the original level set. This + file is copyrighted and cannot be freely distributed. The chips.dat file + was originally part of the MS version of "Chip's Challenge". If you have a + copy of this version of the game, you can use that file to play the game in + Tile World. If you do not have a copy of this file, however, you can still + play Tile World with the many freely available level sets created by fans of + the original game, including CCLP2. Because the version that Microsoft + released introduced a number of changes to the rules of the game, Tile World + is capable of emulating either the MS version or the original Atari Lynx + version of the game. --- tworld-1.3.0.orig/debian/README.Debian +++ tworld-1.3.0/debian/README.Debian @@ -0,0 +1,15 @@ +tworld for Debian +----------------- + + This package includes only the intro data files. I haven't digged through the +CCLP2 (Chip's Challenge Level Pack 2) yet to look for DFSG free levels in there +to include them here. Most propably they will need to go to non-free. + + I've also included the c4 script in this package that is available from the +upstream homepage: With the help of it you can convert the different level data +files available from one format into another, including a pain text format that +you can use to create your own level files. If you do so please feel encouraged +to send them to me, including a short note under what licence you offer them. +(Hopefully a DFSG free one!) + + -- Gerfried Fuchs <alfie@debian.org>, Tue, 13 Jun 2006 06:27:02 -0500 --- tworld-1.3.0.orig/debian/README.source +++ tworld-1.3.0/debian/README.source @@ -0,0 +1,2 @@ +This package uses quilt for its patch management, see +/usr/share/doc/quilt/README.source if you are unfamiliar with it. --- tworld-1.3.0.orig/debian/patches/02_integer-types +++ tworld-1.3.0/debian/patches/02_integer-types @@ -0,0 +1,321 @@ +Description: Fix 32/64-bit issues. +Forwarded: no +Author: Drake Wilson <drake@begriffli.ch> +Bug-Debian: http://bugs.debian.org/427360 +Bug-Ubuntu: https://launchpad.net/bugs/307897 + +--- a/defs.h ++++ b/defs.h +@@ -8,6 +8,7 @@ + #define _defs_h_ + + #include <stdio.h> ++#include <stdint.h> + #include "gen.h" + + /* +@@ -34,8 +35,8 @@ + /* Pseudorandom number generators. + */ + typedef struct prng { +- unsigned long initial; /* initial seed value */ +- unsigned long value; /* latest random value */ ++ uint32_t initial; /* initial seed value */ ++ uint32_t value; /* latest random value */ + char shared; /* FALSE if independent sequence */ + } prng; + +@@ -65,8 +66,8 @@ + */ + typedef struct solutioninfo { + actlist moves; /* the actual moves of the solution */ +- unsigned long rndseed; /* the PRNG's initial seed */ +- unsigned long flags; /* other flags (currently unused) */ ++ uint32_t rndseed; /* the PRNG's initial seed */ ++ uint32_t flags; /* other flags (currently unused) */ + unsigned char rndslidedir; /* random slide's initial direction */ + signed char stepping; /* the timer offset */ + } solutioninfo; +@@ -196,7 +197,7 @@ + int solutionsize; /* size of the saved solution data */ + unsigned char *leveldata; /* the data defining the level */ + unsigned char *solutiondata; /* the player's best solution so far */ +- unsigned long levelhash; /* the level data's hash value */ ++ uint32_t levelhash; /* the level data's hash value */ + char const *unsolvable; /* why level is unsolvable, or NULL */ + char name[256]; /* name of the level */ + char passwd[256]; /* the level's password */ +--- a/fileio.c ++++ b/fileio.c +@@ -242,7 +242,7 @@ + + /* Read one byte as an unsigned integer value. + */ +-int filereadint8(fileinfo *file, unsigned char *val8, char const *msg) ++int filereadint8(fileinfo *file, uint8_t *val8, char const *msg) + { + int byte; + +@@ -255,7 +255,7 @@ + + /* Write one byte as an unsigned integer value. + */ +-int filewriteint8(fileinfo *file, unsigned char val8, char const *msg) ++int filewriteint8(fileinfo *file, uint8_t val8, char const *msg) + { + errno = 0; + if (fputc(val8, file->fp) != EOF) +@@ -265,7 +265,7 @@ + + /* Read two bytes as an unsigned integer value stored in little-endian. + */ +-int filereadint16(fileinfo *file, unsigned short *val16, char const *msg) ++int filereadint16(fileinfo *file, uint16_t *val16, char const *msg) + { + int byte; + +@@ -282,7 +282,7 @@ + + /* Write two bytes as an unsigned integer value in little-endian. + */ +-int filewriteint16(fileinfo *file, unsigned short val16, char const *msg) ++int filewriteint16(fileinfo *file, uint16_t val16, char const *msg) + { + errno = 0; + if (fputc(val16 & 0xFF, file->fp) != EOF +@@ -293,7 +293,7 @@ + + /* Read four bytes as an unsigned integer value stored in little-endian. + */ +-int filereadint32(fileinfo *file, unsigned long *val32, char const *msg) ++int filereadint32(fileinfo *file, uint32_t *val32, char const *msg) + { + int byte; + +@@ -316,7 +316,7 @@ + + /* Write four bytes as an unsigned integer value in little-endian. + */ +-int filewriteint32(fileinfo *file, unsigned long val32, char const *msg) ++int filewriteint32(fileinfo *file, uint32_t val32, char const *msg) + { + errno = 0; + if (fputc(val32 & 0xFF, file->fp) != EOF +--- a/fileio.h ++++ b/fileio.h +@@ -8,6 +8,7 @@ + #define _fileio_h_ + + #include "defs.h" ++#include <stdint.h> + + /* Reset a fileinfo structure to indicate no file. + */ +@@ -49,17 +50,17 @@ + * from the current position in the given file. For the multi-byte + * values, the value is assumed to be stored in little-endian. + */ +-extern int filereadint8(fileinfo *file, unsigned char *val8, ++extern int filereadint8(fileinfo *file, uint8_t *val8, + char const *msg); +-extern int filewriteint8(fileinfo *file, unsigned char val8, ++extern int filewriteint8(fileinfo *file, uint8_t val8, + char const *msg); +-extern int filereadint16(fileinfo *file, unsigned short *val16, ++extern int filereadint16(fileinfo *file, uint16_t *val16, + char const *msg); +-extern int filewriteint16(fileinfo *file, unsigned short val16, ++extern int filewriteint16(fileinfo *file, uint16_t val16, + char const *msg); +-extern int filereadint32(fileinfo *file, unsigned long *val32, ++extern int filereadint32(fileinfo *file, uint32_t *val32, + char const *msg); +-extern int filewriteint32(fileinfo *file, unsigned long val32, ++extern int filewriteint32(fileinfo *file, uint32_t val32, + char const *msg); + + /* Read size bytes from the given file and return the bytes in a +--- a/oshw.h ++++ b/oshw.h +@@ -131,7 +131,7 @@ + + /* The font provides special monospaced digit characters at 144-153. + */ +-enum { CHAR_MZERO = 144 }; ++#define CHAR_MZERO ((char)144) + + /* + * Video output functions. +--- a/random.c ++++ b/random.c +@@ -24,12 +24,12 @@ + /* The most recently generated random number is stashed here, so that + * it can provide the initial seed of the next PRNG. + */ +-static unsigned long lastvalue = 0x80000000UL; ++static uint32_t lastvalue = 0x80000000UL; + + /* The standard linear congruential random-number generator needs no + * introduction. + */ +-static unsigned long nextvalue(unsigned long value) ++static uint32_t nextvalue(uint32_t value) + { + return ((value * 1103515245UL) + 12345UL) & 0x7FFFFFFFUL; + } +@@ -67,7 +67,7 @@ + + /* Reset a PRNG to an independent sequence. + */ +-void restartprng(prng *gen, unsigned long seed) ++void restartprng(prng *gen, uint32_t seed) + { + gen->value = gen->initial = seed & 0x7FFFFFFFUL; + gen->shared = FALSE; +--- a/random.h ++++ b/random.h +@@ -19,7 +19,7 @@ + + /* Restart an existing PRNG upon a predetermined sequence. + */ +-extern void restartprng(prng *gen, unsigned long initial); ++extern void restartprng(prng *gen, uint32_t initial); + + /* Retrieve the original seed value of the current sequence. + */ +--- a/series.c ++++ b/series.c +@@ -46,9 +46,9 @@ + + /* Calculate a hash value for the given block of data. + */ +-static unsigned long hashvalue(unsigned char const *data, unsigned int size) ++static uint32_t hashvalue(unsigned char const *data, unsigned int size) + { +- static unsigned long remainders[256] = { ++ static uint32_t remainders[256] = { + 0x00000000, 0x04C11DB7, 0x09823B6E, 0x0D4326D9, 0x130476DC, 0x17C56B6B, + 0x1A864DB2, 0x1E475005, 0x2608EDB8, 0x22C9F00F, 0x2F8AD6D6, 0x2B4BCB61, + 0x350C9B64, 0x31CD86D3, 0x3C8EA00A, 0x384FBDBD, 0x4C11DB70, 0x48D0C6C7, +@@ -94,7 +94,7 @@ + 0xBCB4666D, 0xB8757BDA, 0xB5365D03, 0xB1F740B4 + }; + +- unsigned long accum; ++ uint32_t accum; + unsigned int i, j; + + for (j = 0, accum = 0xFFFFFFFFUL ; j < size ; ++j) { +@@ -113,7 +113,7 @@ + */ + static int readseriesheader(gameseries *series) + { +- unsigned short val16; ++ uint16_t val16; + int ruleset; + + if (!filereadint16(&series->mapfile, &val16, "not a valid data file")) +@@ -149,7 +149,7 @@ + { + unsigned char *data; + unsigned char const *dataend; +- unsigned short size; ++ uint16_t size; + int n; + + if (!filereadint16(file, &size, NULL)) +@@ -450,7 +450,7 @@ + fileinfo file; + seriesdata *sdata = (seriesdata*)data; + gameseries *series; +- unsigned long magic; ++ uint32_t magic; + char *datfilename; + int config, f; + +--- a/solution.c ++++ b/solution.c +@@ -8,6 +8,7 @@ + #include <stdlib.h> + #include <string.h> + #include <ctype.h> ++#include <stdint.h> + #include "defs.h" + #include "err.h" + #include "fileio.h" +@@ -215,9 +216,9 @@ + static int readsolutionheader(fileinfo *file, int ruleset, int *flags, + int *extrasize, unsigned char *extra) + { +- unsigned long sig; +- unsigned short f; +- unsigned char n; ++ uint32_t sig; ++ uint16_t f; ++ uint8_t n; + + if (!filereadint32(file, &sig, "not a valid solution file")) + return FALSE; +@@ -475,7 +476,7 @@ + */ + static int readsolution(fileinfo *file, gamesetup *game) + { +- unsigned long size; ++ uint32_t size; + + game->number = 0; + game->sgflags = 0; +@@ -706,8 +707,8 @@ + int loadsolutionsetname(char const *filename, char *buffer) + { + fileinfo file; +- unsigned long dwrd; +- unsigned short word; ++ uint32_t dwrd; ++ uint16_t word; + int size; + + clearfileinfo(&file); +--- a/unslist.c ++++ b/unslist.c +@@ -22,7 +22,7 @@ + int setid; /* the ID of the level set's name */ + int levelnum; /* the level's number */ + int size; /* the levels data's compressed size */ +- unsigned long hashval; /* the levels data's hash value */ ++ uint32_t hashval; /* the levels data's hash value */ + int note; /* the entry's annotation ID, if any */ + } unslistentry; + +@@ -112,7 +112,7 @@ + /* Add a new entry with the given data to the list. + */ + static int addtounslist(int setid, int levelnum, +- int size, unsigned long hashval, int note) ++ int size, uint32_t hashval, int note) + { + if (listcount == listallocated) { + listallocated = listallocated ? listallocated * 2 : 16; +@@ -152,8 +152,10 @@ + { + char buf[256], token[256]; + char const *p; +- unsigned long hashval; +- int setid, size; ++ uint32_t hashval; ++ unsigned long hashval_long; ++ int setid; ++ unsigned int size; + int lineno, levelnum, n; + + setid = 0; +@@ -169,7 +171,8 @@ + continue; + } + n = sscanf(p, "%d: %04X%08lX: %[^\n\r]", +- &levelnum, &size, &hashval, token); ++ &levelnum, &size, &hashval_long, token); ++ hashval = (uint32_t)hashval_long; + if (n > 0 && levelnum > 0 && levelnum < 65536 && setid) { + if (n == 1) { + n = sscanf(p, "%*d: %s", token); --- tworld-1.3.0.orig/debian/patches/01_fix-manpage +++ tworld-1.3.0/debian/patches/01_fix-manpage @@ -0,0 +1,168 @@ +Author: Gerfried Fuchs <rhonda@debian.org> vim:ft=diff: +Description: patch to fix manual pages + The patch contains fixes for typos and for syntax issues that might lead to + misrendering of the manpage. + +Index: b/docs/tworld.6 +=================================================================== +--- a/docs/tworld.6 ++++ b/docs/tworld.6 +@@ -1,5 +1,4 @@ + .TH tworld 6 "Mar 2006" "Tile World" +-.LO 1 + .SH NAME + tworld \- Tile World + .P +@@ -72,7 +71,7 @@ entering this tile will cause Chip to lo + collected. + .P + Dirt blocks are large, movable squares of dirt. Chip can push them +-about, and use them to wall off areas or to safely detontate bombs. ++about, and use them to wall off areas or to safely detonate bombs. + Furthermore, if a block is pushed into water, the tile will turn into + dirt (which will become normal flooring when Chip steps on it). + Finally, note that blocks can sometimes be resting on top of other +@@ -150,8 +149,8 @@ give you the opportunity to skip a level + .SH PASSWORDS + Every level has a four-letter password. The password for a level is + shown in the information display at the upper-right of the window. The +-obstensible purpose of passwords is to allow you to come back to a +-level. Howver, normally you will never need to remember passwords, as ++ostensible purpose of passwords is to allow you to come back to a ++level. However, normally you will never need to remember passwords, as + Tile World will automatically store the passwords for you. However, if + you somehow manage to learn the password of a level that you have yet + to achieve, you can use the password to gain early access to that +@@ -248,7 +247,7 @@ toggles between even-step and odd-step o + .TP + .B Ctrl-X + deprecates the best solution for that level. If the level is then +-succesfully completed again, the saved solution will be replaced with ++successfully completed again, the saved solution will be replaced with + the new one, whether or not it had a better time. + .TP + .B Shift-Ctrl-X +@@ -327,98 +326,98 @@ information on standard output actually + stdout.txt instead.) + .P + .TP +-.B -a ++.B \-a + Double the size of the audio buffer. This option can be repeated, so +-for example -aaa would increase the audio buffer size eightfold. ++for example \-aaa would increase the audio buffer size eightfold. + .TP +-.B -b ++.B \-b + Do a batch-mode verification of the existing solutions and exit. + Levels with invalid solutions are displayed on standard output. If +-used with -q, then nothing is displayed, and the program's exit code +-is the number of invalid solutions. Can also be used with -s or -t ++used with \-q, then nothing is displayed, and the program's exit code ++is the number of invalid solutions. Can also be used with \-s or \-t + to have solutions verified before the other option is applied. Note + that this options requires a level set file and/or a solution file be + named on the command line. + .TP +-.BI "-D\ " DIR ++.BI "\-D\ " DIR + Read level data files from + .I DIR + instead of the default directory. + .TP +-.B -d ++.B \-d + Display the default directories used by the program on standard + output, and exit. + .TP +-.B -F ++.B \-F + Run in full-screen mode. + .TP +-.B -H ++.B \-H + Upon exit, display a histogram of idle time on standard output. (This + option is used for evaluating optimization efforts.) + .TP +-.B -h ++.B \-h + Display a summary of the command-line syntax on standard output and + exit. + .TP +-.BI "-L\ " DIR ++.BI "\-L\ " DIR + Look for level sets in + .I DIR + instead of the default directory. + .TP +-.B -l ++.B \-l + Write a list of available level sets to standard output and exit. + .TP +-.BI "-n\ " N ++.BI "\-n\ " N + Set the initial volume level to + .IR "N" , + 0 being silence and 10 being + full volume. The default level is 10. + .TP +-.B -P ++.B \-P + Turn on pedantic mode, forcing the Lynx ruleset to emulate the + original game as closely as possible. (See the Tile World website for + more information on emulation of the Lynx ruleset.) + .TP +-.B -p ++.B \-p + Turn off all password-checking. This option allows the normal sequence + of levels to be bypassed. + .TP +-.B -q ++.B \-q + Run quietly. All sounds, including the ringing of the terminal bell, + are suppressed. + .TP +-.B -r ++.B \-r + Run in read-only mode. This guarantees that no changes will be made + to the solution files. + .TP +-.BI "-R\ " DIR ++.BI "\-R\ " DIR + Read resource data from + .I DIR + instead of the default directory. + .TP +-.BI "-S\ " DIR ++.BI "\-S\ " DIR + Read and write solution files under + .I DIR + instead of the default + directory. + .TP +-.B -s ++.B \-s + Display the current scores for the selected level set on standard + output and exit. A level set must be named on the command line. If +-used with -b, the solutions are verified beforehand, and invalid ++used with \-b, the solutions are verified beforehand, and invalid + solutions are indicated. + .TP +-.B -t ++.B \-t + Display the best times for the selected level set on standard output + and exit. A level set must be named on the command line. If used with +--b, the solutions are verified beforehand, and invalid solutions are ++\-b, the solutions are verified beforehand, and invalid solutions are + indicated. + .TP +-.B -V ++.B \-V + Display the program's version and license information on standard + output and exit. + .TP +-.B -v ++.B \-v + Display the program's version number on standard output and exit. + .P + Besides the above options, tworld can accept up to three --- tworld-1.3.0.orig/debian/patches/series +++ tworld-1.3.0/debian/patches/series @@ -0,0 +1,2 @@ +01_fix-manpage +02_integer-types