townmaze part 04/04

Kent Paul Dolan xanthian at zorch.SF-Bay.ORG
Thu Apr 18 13:52:38 AEST 1991


Archive-name: townmaze/part04

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 4 (of 4)."
# Contents:  townpgmr.doc
# Wrapped by xanthian at zorch on Wed Apr 17 20:34:41 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'townpgmr.doc' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'townpgmr.doc'\"
else
echo shar: Extracting \"'townpgmr.doc'\" \(39151 characters\)
sed "s/^X//" >'townpgmr.doc' <<'END_OF_FILE'
XFile townpgmr.doc, the programmers' documentation for townmaze.
X
X
XThis file documents the C code; see file README for compile and
Xexecute instructions, copyright restrictions, etc.
X
XSince townmaze is meant to be a set of software for use by programmers,
Xthere is going to be a _lot_ of programmer documentation.  Each of the
Xfollowing source code files will be separately described.
X
X townmaze.h       townproto.h
X
X cleanupmap.c     closedoors.c       closegates.c       connectst.c
X filllist.c       fillmaze.c         finishalleys.c     freespace.c
X getargs.c        interiorcell.c     makecourts.c       makegates.c
X makestreet.c     makespace.c        makeunused.c       movefromto.c
X nhbrexists.c     nhbris.c           showdbmaze.c       showlistdet.c
X showlistsum.c    showmaze.c         townmain.c         usage.c
X
XBut first, an overview.
X
XTownmaze works conceptually on a structure of maze cells in a
Xfour-connected (each cell has four neighbors) grid with one of five
Xstatuses:
X
XISOLATED - no neighbor of this cell is a street.
X
XLIVE - some neighbor of this cell is a street, and some neighbor of
Xthis cell is an isolated cell. 
X
XDEAD - some neighbor of this cell is a street, but no neighbor of this
Xcell is an isolated cell. 
X
XSTREET - the passageways for the adventurers; this cell is connected
Xby a series of other street cells to some origin point for a street. 
X
XUNUSED - this cell is solid rock, it may neither become a room nor a
Xstreet, nor have a street cell as a neighbor. 
X
XThe goal is to build a town maze like the upper level of Bard's Tale I,
Xwith a street to every door, and a fairly "double row of brownstones" 
Xappearance, by looking two neighbor links away, to preserve where
Xpossible room cells in back to back rows.
X
XTo accomplish this, LIVE cells are used to indicate when at least one
Xdirection from the live cell, the next street is separated by an
XISOLATED cell and some other non-STREET cell on the far side.  This
Xworks fairly well when the streets run very parallel, not so well when
Xthey don't.  Better strategies may well exist, but this one does very
Xnice mazes for some parameter values. 
X
XThe basic plan of townmaze is to
X
X1) Accept a set of command line parameters.
X
X2) Allocate two major data structures of the appropriate size in
Xaccordance with those parameters, one a two dimensional array of
Xcharacters which will be used to build and display the completed maze,
Xthe other a one dimensional array of structures which is used as a
Xstatus list to store the status of each maze cell, and to link maze
Xcells together in sublists for speed of processing. 
X
X3) Fill in the maze as a solid array of rooms except the corners,
Xwhich are blocked off (they're too hard to use), each room without
Xdoors, and fill in the status list and link it together as a list of
XISOLATED cells, except again for the corners, which become UNUSED
Xcells. 
X
X4) Seed the maze with uniquely numbered street origins at border or
Xinterior cells, and obstacles at interior cells only.  Assure that
Xdifferent seed cells are far enough apart to allow successful maze
Xcreation.  Update display to match; rooms beside streets get doors
Xfacing the streets; the outside wall next to a street becomes a
X"gate".
X
X4a) Seed any UNUSED cells in the interior of the maze.
X
X4b) Seed any "gate" STREET cells along the border of the maze.
X
X4c) Seed any "courtyard" STREET cells in the interior of the maze.
X
X5) Connect the streets by extending them until all streets have the
Xsame street number; merge street numbers when two streets meet.  Again
Xupdate the display array to match; rooms are converted to streets by
Xremoving all doors.  Rooms newly adjacent to streets gain a new door
Xon that street. 
X
X6) When all streets are connected, continue to extend them as "alleys"
Xuntil no isolated room cells remain. 
X
X7) Close most of the doors, leaving one open door per room, by
Xreplacing doors by walls.
X
X8) Possibly close some or all gates by replacing them with walls.
X
X9) Clean up "pillars"; bits of wall that have been isolated by being
Xsurrounded by streets.
X
X10) Display the maze.
X
X11) Free the allocated space.
X
X12) Exit.
X
XDetailed source module descriptions.
X
X-----------------------------------------------------------------------
X| townmaze.h 
X-----------------------------------------------------------------------
X
XThis header file is where all the #ifdef controlling #defines are done
X(except MAINLINE, in townmain.c, and RAND48, in the make file), the
Xstructures are defined, the defined constants are declared, and the
Xexternal variables are declared or defined under #ifdef control. 
X
XIn particular, the cmaze two dimensional character array for maze
Xdisplay, the statlist one dimensional structure array for cell
Xstatuses, and the various cell type link list head pointers isolated,
Xlive, dead, street, and unused, and their corresponding cell counters
Xisolatedct, livect, deadct, streetct, and unusedct, are all defined
Xhere. As well, streetnumct, the count of how many different street
Xnumbers exist, is defined here.
X
XAs well, the global parameters mazeheight, mazewidth, mazegates,
Xleftgates, mazecourts, mazeunused, with their appropriate defaults
Xwhen not read in from the command line, and randomseed without a
Xdefault value, and the derived global parameter listsize, are declared
Xand defined here.
X
XIn addition, the integer constants ISOLATED, LIVE, DEAD, STREET, and
XUNUSED are defined, and the character constants WALL, VDOOR, HDOOR,
Xand BLANK.  Also defined here is NOPOINTER, which acts as a null
Xpointer in the statlist double linked lists, which use indices rather
Xthan pointers for links.
X
X
X-----------------------------------------------------------------------
X| townproto.h
X-----------------------------------------------------------------------
X
XThis header file contains all the function prototypes, since each
Xfunction is in its own separate source module, done in both ANSI C and
Xold K&R form, conditioned on __STDC__, the compiler defined constant
Xto identify ANSI C compilers.  The same conditioning is done in each
X*.c module for compatibility.
X
X
X-----------------------------------------------------------------------
X| cleanupmap.c -- cleanmap()
X-----------------------------------------------------------------------
X
XThis is the routine that accomplishes step 9) above.  It does this by
Xmoving to each intersection point in the cmaze display array interior
Xfor the horizontal and vertical walls (see fillmaze.c, below) of the
Xrooms, and looking at the north, south, east and west display
Xcharacters; if they are all blanks, then this is an isolated "pillar"
Xpiece of wall, so it is replaced by a blank.
X
X
X-----------------------------------------------------------------------
X| closedoors.c -- closedoors()
X-----------------------------------------------------------------------
X
XThis is the routine that accomplishes step 7) above.  It does this by
Xwalking the DEAD list from list head pointer dead until a NOPOINTER
Xnext pointer is found.  At each room on the dead list, it counts the
Xdoors by looking north, east, south, and west, then if more than one
Xexists, picks one at random to survive and replaces the others with
XWALL symbols.  Most of the nasty looking arithmetic is just the
Xtranslation from statlist indices to cmaze locations; all the code is
Xrife with them.
X
XThe necessity for this routine is that when the previous street
Xcreation routines are complete, most of the room cells have most walls
Xas doors, which doesn't make for much of a challenge, the adventurer
Xdoesn't have to work down the street, but can instead just walk
Xthrough the row of rooms to the street behind. 
X
XAlso, games like Bard's Tale are simpler to design if all normal rooms
Xhave only one exit, since then only one viewpoint and no choices need
Xbe presented to the player. 
X
X-----------------------------------------------------------------------
X| closegates.c -- closegates()
X-----------------------------------------------------------------------
X
XThis is the routine that accomplishes step 8) above.  It does this in
Xresponse to a comparision between the -g mazegates paramenter and the
X-l leftgates parameter; if more gates were seeded than were requested
Xin the final maze, then gates are closed at random until only
Xleftgates of them remain.
X
XHow many places along the maze periphery must be checked for gates is
Xprecomputed into possiblegates, which becomes the inner (for) loop
Xlimit.
X
XThe gate closing task is done by keeping track of how many gates are
Xleft with gatesleft, conditioning an outer while loop on gatesleft
Xbeing greater than leftgates, then withing the loop rolling a random
Xgate number into chosengate as a modulus of RANDOM() by gatesleft, and
Xthen walking the periphery of the maze in and inner for loop, looking
Xfor VDOOR or HDOOR elements in the outer wall, and counting how many
Xgates have been encountered in foundgate.
X
XWhen the gate encountered matches the number found, the gate is
Xclosed, the gatesleft decremented, and the inner loop broken.
X
XThe messy interior of the inner loop is unavoidable unless a link list
Xof gate cells is kept, which would have messed up statlist too much;
Xthe arithmetic to walk around the border of a rectangle when the
Xindices are in row major order instead of some nice inturning spiral
Xis just that ugly.
X
X
X-----------------------------------------------------------------------
X| connectst.c -- connectstreets()
X-----------------------------------------------------------------------
X
XThis is the routine that accomplishes step 5), above.  The goal is to
Xmake all the streets in the maze into one connected street, decreasing
Xstreetnumct to 1, and this is where townmaze spends most of its time,
Xsince adding new street cells is what makes the maze.
X
XThis is one of the biggest routines in townmaze, because it needs
Xthree separate strategies to connect streets.  First it tries to
Xconnect all the streets by only using cells from the LIVE list in
Xstatlist.  If all the LIVE cells are used up and there are still more
Xthan one street noted in streetnumct, then the second strategy is to
Xuse only DEAD cells that touch two different streets as new street
Xcells.  If worst comes to worst, if streetnumct is still greater than
Xone, then in the third strategy, the entire DEAD cell list is used as
Xtargets to connect the streets.
X
XThis routine runs a lot slower than first glance might suggest,
Xbecause called routine makestreet() can succeed as infrequently as
Xonce in a thousand calls, depending on the current configuration of
Xthe maze, and the mazestraightness parameter setting.
X
XThe first phase is done by picking a random LIVE list element, walking
Xthe live list to that element, and asking makestreet() to make a
Xstreet of it.  The outer loop doing this is conditioned on both
Xstreetnumct being greater than 1, and livect being greater than zero.
XThe fact that makestreet() might refuse to make a street on
Xstraightness criteria is hidden by just running the outer loop until
Xone of the conditions fails. 
X
XAfterthe outerloop selects a targetcell from the LIVE list based on a
Xmodulus of RANDOM() against the livect, the middle loop is used to
Xwalk livewalk down the LIVE list. This is where the maintenance of the
Xlinks lists pays off; without them, the walk to find the targetcell
Xamong the LIVE list cells would have to walk the whole statlist, a
Xmuch longer list. 
X
XIn the inner loop, nhbrexists is used, here as elsewhere, to avoid
Xindexing an invalid entry of statlist, while nhbris is used to
Xtranslate simply from a neighbor number 0-3 to a statlist cell index.
XIf you want to be nice about it you could call this data abstraction.
X
XThe call to makestreet() contains "-1" as the last parameter, which
Xenables the straightness checks there.
X
XThe second phase is done by walking the DEAD list, seeking DEAD cells
Xwhose neighbors include streets with two or more different numbers.
XThis could probably have been done more cleanly with the "for(i" loop
Xreplaced by a "while (deadwalk != NOPOINTER)" loop, but this one
Xworks.  Because the DEAD list is changing size while the list is
Xwalked, there are some hyper-defensive checks going on to make sure
Xthe DEAD list end isn't exceeded.  For the same reason, deadwalk has
Xto be moved past the current cell before it is possibly yanked out
Xfrom under by makestreet (which will move it from the DEAD list to
Xthe STREET list if called), so lastdead is used to access the current
Xcell and then not used after the makestreet call.
X
XAt the top of the loops, a check is made that this cell is interior;
Xthe goal is to avoid running streets along the city wall, a boring
Xkind of design; take this check out if you want the occasional wall
Xhugger.  The double loop on j and k is comparing neighbors, where they
Xexist, looking for streets with different numbers. 
X
XTo avoid trying to break out of a double loop, not one of C's strong
Xpoints, another trick was used.  The reason this loop doesn't keep
Xtrying to make a street out of the chosen cell after it has done so
Xonce is that makestreet() causes all the adjacent streets to be merged
Xand have the same street number, so that the inner if test always
Xfails after the first success. 
X
Xhe call to makestreet is with a forcing actual direction last
Xparameter (see below about cheapdir) so the street is always created
Xon the first try. Except, that if the street is a neighbor of an
XUNUSED cell, then the check at the very top of makestreet() means
Xintsead that it will never be made into a street. 
X
XThe double j, k loop can thus in either case safely be let run to
Xcompletion, since it will never ask makestreet() to make a street into
Xa street.  Letting it spin through to completion is no big deal since
Xphase two is a very minor part of the overall time in any case, doing
Xno random calls. 
X
XThe little trick down inside the loop with cheapdir is to adopt the
Xdirection of one of the streets whether this cell continues it or not,
Xand the second one if it happens to continue the street.  This is not
Xperfectly fair, but it doesn't seem to hurt anything, and with at
Xleast two sides already streets, this cell is less likely than many to
Xwant to continue its direction into a subsequently-started-here alley
Xor street.
X
XThe third phase is much like the first, except that now the DEAD list
Xis being randomly used to extend the streets, rather than the live
Xlist.  This tends to make the town maze more open, and to destroy the
Xback to back rooms that are the whole goal of townmaze, but it is
Xsometimes the only way to "make ends meet" and get all the streets
Xconnected, an absolute requirement. for townmaze.  The only difference
Xfrom the first phase is that, unlike LIVE cells, which are always
Xinterior (to keep the streets from running down the city wall again),
XDEAD cells may be border cells, and we want explicitly to exclude the
Xborder cells from consideration for extending the streets.
X
X
X-----------------------------------------------------------------------
X| filllist.c -- filllist()
X-----------------------------------------------------------------------
X
XThis routine does half of step 3), above.  This function takes the raw
Xspace allocated for statlist by makespace(), and turns it into an
Xarray which is also five doubly linked lists, all but the ISOLATED
Xlist empty and with list heads set to NOPOINTER, and list counts set
Xto zero, to start.  The ISOLATED list count is set to listsize, and
Xthe list header pointing to the zeroth element of statlist.  Just at
Xthe end it puts the four corner cells into the UNUSED list using
Xmovefromto(), and sets the streetnumct to zero (it needed to be done
Xsomewhere in setup, or static set there, and I prefer the self
Xdocumentation of an explicit action). 
X
X
X-----------------------------------------------------------------------
X| fillmaze.c -- fillmaze()
X-----------------------------------------------------------------------
X
X
XThis routine does the other half of step 3), above.  This function
Xmakes every even (zero origin, remember) row and column of the cmaze
Xcharacter array a WALL symbol, and the remaining doubly odd coordinate
Xinterstices BLANK symbols, and at the end fills in WALL symbols in the
Xfour corner cells' centers to make them UNUSED looking. 
X
X
X-----------------------------------------------------------------------
X| finishalleys.c -- finishalleys()
X-----------------------------------------------------------------------
X
XThis is the routine that accomplishes step 6), above.  After
Xconnectstreets() has made sure that all the streets in town are
Xinterconnected, there may still be ISOLATED (and thus LIVE) cells
Xremaining.  This routine continues to turn LIVE cells into streets,
Xand thus ISOLATED cells into LIVE or DEAD cells and possible
Xconsequently LIVE cells into DEAD cells (from lack of a neighboring
XISOLATED cell and more) until no LIVE or ISOLATED cells remain. 
X
XThis works just like the first phase of connectstreets(), except that
Xit is no longer conditioned on streetnumct > 1, just on livect > 0. 
X
X[By the way, there's no special significance to "alleys" as opposed to
X"streets", the name just seemed homeyer.]
X
X
X-----------------------------------------------------------------------
X| freespace.c -- freespace()
X-----------------------------------------------------------------------
X
XThis routine does step 11), above.  Sigh.  There really ought to be a
Xlibrary routine to allocate, and another to free, the ugly messes C
Xuses for multidimensional arrays.  So far as I can figure, you can
Xallocate a fixed size array without the intermediate pointer list only
Xat compile time, since there is no way to convince the compiled code
Xat run time that it knows the major dimension, so regular indexing
Xwon't work for an array dynamically allocated into an array declared
X"cmaze[][]", and the compiler complains if you try. 
X
XIn any case, this does the standard stuff to give back the storage for
Xstatlist and cmaze.  On a Unix box this is excessive neatness, but on
Xan Amiga that doesn't do resource tracking, this is mandatory to avoid
X"leaking" memory and forcing an eventual reboot. 
X
XError checking is done on the free() calls; I don't have any recourse
Xif an error occurs in any case, but at least I can complain before
Xdying. 
X
X
X-----------------------------------------------------------------------
X| getargs.c
X-----------------------------------------------------------------------
X
XThis routine accomplishes step 1) above.  Sigh again.  Lattice C for
Xthe Amiga doesn't have getopts() as a library routine, so I wrote this
Xspecial purpose beast.  To avoid intensely ugly code, the nice
Xgetopts() feature of accepting both spaces and no spaces between the
Xflag and the value is foregone here; I just don't have that much
Xpatience.  Leaving the space only option makes this code neat and
Xeasy. 
X
XThe routine starts by checking for any arguments, and if none just
Xgoes down to compute listsize and (compile time ooptionally) do a
Xheader and return.  If there are an odd number of argv entries (the
Xfunction name is counted in argc, so this means that flags and values
Xcome in pairs), the parameters are fetched in order into the
Xappropriate global parameter variables.  If an even number of argv
Xentries exist, the routine complains and exits.
X
XThe switch statement is pretty standard; the only interesting element
Xis the -r switch, which loads a long instead of an integer, and also
Xcalls SEEDRANDOM(randomseed) to override the previous (in main()) call
Xto SEEDRANDOM(time()).  This lets the random seed be forced, allowing
Xduplicate runs for debugging or for inclusion in a game where the code
Xand seeds are cheaper to store than the rooms. (Big game!)
X
XThe check below the switch is about half of the sanity in the whole
Xprogram:
X
X((mazewidth%2) != 1) -- The maze width must be odd.
X
X((mazeheight%2) != 1) -- The maze height must be odd.
X
X(mazewidth < 11) -- The maze must be at least five cells high to leave
Xroom for one interior row of buildings. 
X
X(mazeheight < 11) -- The maze must be at least five cells wide to
Xleave room for one interior row of buildings. 
X
X(mazegates < 0) -- There must be at least zero gates.
X
X(mazegates > (2*((mazeheight - 6)/7) + 2*((mazewidth- 6)/7))) -- There
Xcan be no more gates than one per three side cells between the unused
Xcorner cells, to assure that all gates will fit. 
X
X(leftgates < 0) -- There must be at least zero gates left at the end.
X
X(leftgates > mazegates) -- There cannot be more gates left than there
Xwere to begin. 
X
X(mazecourts < 0) -- There must be at least zero courtyards.
X
X(mazecourts > (((mazeheight - 5)/6)*((mazewidth - 5)/6))) --
XCourtyards must have room for at least two spaces between them,
Xotherwise they won't fit when makecourts() is run.
X
X((mazecourts + mazegates) < 1) -- There must be at least one street.
X
X(mazeunused > (((mazeheight - 1)/14)*((mazewidth - 1)/14))) -- Unused
Xcells must have room for at least three squares between them,
Xotherwise they can trap DEAD cells away from streets and won't fit when
Xmakeunused() is run.. 
X
X(mazestraightness < 0) -- Negative straightness makes no sense.  [Zero
Xmeans don't do straightening, and that's OK.]
X
X(mazestraightness > 998) -- There must be at least one chance per
Xthousand of accepting a bend in the street, or an infinite loop might
Xoccur. 999 is the highest value returned by the random roll modded
Xagainst 1000, so 998 is the largest accpetable straightness parameter. 
X
XIf all the parameters are right, the listsize (number of cell
Xstructures in statlist) is computed from the requested maze width and
Xheight. If the HEADER option is on (I always use it) a single line of
Xparameters is printed to standard out along with the eventual maze.
X[All other output goes to the standard error unit.]
X
X
X-----------------------------------------------------------------------
X| interiorcell.c -- interiorcell(cellid)
X-----------------------------------------------------------------------
X
XThis simple function just checks that the passed cell has all four
Xneighbors using nhbrexists().  Lots of stuff in townmaze specifies
Xinterior cells only. 
X
X
X-----------------------------------------------------------------------
X| makecourts.c -- makecourts()
X-----------------------------------------------------------------------
X
XThis routine does step 4c), above.  In response to the mazecourts
Xparameter value, this routine places "courtyards", STREET cells in the
Xinterior of the town maze, spaces them out by making sure before they
Xare placed that all neighbor cells and the randomly chosen cell are
XISOLATED cells, and uses a side effect of makestreet() to turn them
Xinto LIVE or DEAD cells as each courtyard is placed, effectively
Xfending off neighbors a bit. 
X
XBecause there are so many ISOLATED cells when this is done, it is
Xbetter to roll the randomizer agains the whole maze and accept the
Xmisses where a non-ISOLATED cell is chosen, than to roll against the
Xlength of the ISOLATED list and walk it from the end each time.  If
Xone habitually made the courtyard cells very high, it might be better
Xto do this the other way. 
X
XThe random roll could be improved, but for small mazes it doesn't
Xmatter much.  The bias of ignoring the mismatch between the modulus
Xand the length of the interval returned by RANDOM() in a maze 32K on a
Xside would probably be pretty obvious. 
X
XBecause it was easier than explicitly compensating for the
Xinterference from UNUSED cells, a MAXTRIES limit is enforced for
Xplacing each courtyard cell.  The limit is kept very high to prevent
Xinterference with the straightness parameter other places that it is
Xused, but with it in here, makecourts is guaranteed to terminate
Xeventually.  On the other hand, it is not guaranteed to place as many
Xcourtyards as the user specified, which is one reason for the sanity
Xcheck at the start of connectstreets(). 
X
X
X-----------------------------------------------------------------------
X| makegates.c
X-----------------------------------------------------------------------
X
XThis routine does step 4b), above.  In response to the makegates
Xparameter value, this routine places "gates", STREET cells along the
Xborder of the town maze, spacing them apart by insisting that all the
Xexisting neighbors when each gate cell is placed be ISOLATED cells,
Xincluding the chosen cell itself.  It uses makestreet() as each gate
Xcell is placed, to turn the chosen cell into a street, its border cell
Xneighbors into DEAD cells, and its interior neighbor into a LIVE or
XDEAD cell depending on that neighbor's neighbors. 
X
XIt would have been possible to just roll a randomizer against the
Xwhole statlist array, and then check the border and isolated
Xproperties, but for a very large town maze, this would have been
Xgrossly inefficient and slow, so the more complex method seen is used,
Xinstead.  The random roll is effectively done against the number of
Xcell wallss in the town maze border, the border is walked using the
Xlogic that comprises the whole middle of this routine, and if the
Xchosen wall meets the criteria of ISOLATED self and neighbors, it is
Xselected.  This can still be slow if gates are dense, but not nearly
Xas bad as scattershooting at the whole area. 
X
XAs explained above for closegates(), the ugly arithmetic in the middle
Xborder walking logic is fairly inevitable, though perhaps it should be
Xhidden like nhbrexists() hides similar arithmetic.  Maybe next time.
X
XNotice that, because we can't abstract away with interiorcell(), we
Xmust explicitly check nhbrexists() for each cell before using
Xnhbris() to index statlist.
X
XAlso, the test against MAXTRIES may not be needed here, but it was
Xeasier to put in the check than to do the analysis to prove it wasn't
Xneeded, and it makes future code changes like allowing non-corner
XUNUSED border cells more robust.
X
X
X-----------------------------------------------------------------------
X| makestreet.c -- makestreet(chosencell,streetid,streetpoints)
X-----------------------------------------------------------------------
X
XThis routine is charged with doing all the work to make a single cell
Xinto a street cell, including updating all the neighbor cells whose
Xstatus changes because this cell became a street cell, and doing all
Xthe corresponding architectural changes.  It is also tasked with
Xenforcing the straightness parameter, which means that it gets to say
X"no I won't" a lot when told to make a street, and all the routines
Xthat call it have to either override that option with a explicit
Xdirection in the "streetpoints" parameter, or survive not being
Xobeyed.  Not surprising that makestreet is a big routine.
X
XIt starts out by refusing to build a street alongside an UNUSED cell,
Xsince the point of an UNUSED cell is to have all neighbors be rooms.
XIt just returns immediately.
X
XNext, it checks the last parameter, and if it is "-1" (no street
Xdirection selected) enables the straightness check and does it by
Xmaking sure that the selected cell can continue some street's current
Xdirection.  If not, it returns immediately.
X
XNext, it moves the chosen cell to the STREET list using movefromto().
X
XNext, it copies the streetid parameter to the cells
Xstatlist[].streetnum field. 
X
XNext, it updates the cmaze display version of the town maze.  As a
Xfirst guess, it makes all four walls into doors. 
X
XNext, it starts working on the first order neighbors.  If the neighbor
Xis a STREET, then the door becomes a BLANK or passageway.  If the
Xchosen cell would continue the street direction, adopt that neighbor's
Xstreet direction for this cell; keep the last one thus adopted.  If it
Xwouldn't continue an existing street direction, defer this matter for
Xthe bottom of the routine.
X
XNext, a very important check is done to see if a detected neighbor
Xstreet has a different street number than this streets number (passed
Xin as streetdir).  If so, the higher number street is updated by a
Xwalk of the streetlist with the street number of the lower numbered
Xstreet, and the streetnumct is decremented.  The two driving goals of
Xthe whole townmaze routine are to make streetnumct 1 and isolatedct 0.
X
XIf the neighbor is LIVE, make it DEAD and then check its neighbors and
Xif one is still ISOLATED, make it LIVE again.
X
XIf the neighbor is ISOLATED, things get complicated.  This was hard
Xenough to see that it was the last big logic bug in the program.  If
Xthe neighbor is ISOLATED, it becomes LIVE or DEAD, as appropriate,
Xexcept that border cells always become dead to keep the roads off the
Xwalls as mentioned previously. 
X
XThis change, however, might mean that the formerly ISOLATED cells LIVE
Xneighbors, if any, no longer qualify as LIVE.  So, each of those
Xsecondary neighbors is checked, and if it is LIVE, made DEAD, and then
Xeach of its (now tertiary) neighbors is checked in turn, and if it is
XISOLATED, the DEAD cell is revived to LIVE again.  Whew! Making this
Xwork was what achieved back to back rows of rooms in the town maze
Xafter long programming effort.
X
XNext, the question of street direction for the new STREET cell is
Xrevived.  If some of the above overroad the "-1" that was installed by
Xfilllist(), that value is retained.  If not, and a valid (not -1)
Xdirection was passed in streetpoints, that value is adopted.  If not,
Xthe direction from a neighboring street is adopted, and this becomes a
Xturning point in the street.  Note that the effect of the whole
Xroutine is that if the straightness check near the top found a
Xdirection that this street could continue an existing street, then it
Xdoes continue some street, so we only get down here and turn the
Xstreet if the turn direction was passed in, or if the dice roll was
Xsuch as to allow a bend.
X
XAt the end, makestreet returns a True value; it several other places
Xreturns False; I'm not sure this is currently used anywhere, but if
Xdesired for your code, the return value tells whether a street cell
Xwas in fact created.
X
X
X-----------------------------------------------------------------------
X| makespace.c -- makespace()
X-----------------------------------------------------------------------
X
XThis routine does step 2) above.  The predecessor to freespace(), this
Xroutine uses malloc() to allocate space for the statlist and cmaze
Xarrays, using the usual grotesque C mechanisms.  It is very defensive
Xof freeing every bit of acquired memory if a malloc() call fails,
Xbecause on the Amiga, malloc()ed memory is lost if not free()d before
Xexit. 
X
X
X-----------------------------------------------------------------------
X| makeunused.c -- makeunused()
X-----------------------------------------------------------------------
X
XThis routine does step 4a), above.  In response to parameter
Xmazeunused, this routine makes some of the interior cells of the town
Xmaze have UNUSED status and a WALL symbol in the center.  It looks
Xgrotesque, but that's just because it checks the chosen cell and its
X48 closest neighbors to be ISOLATED before accepting selection of the
Xcell for the UNUSED list. 
X
XAttempts are controlled by MAXTRIES, and, since this is the hardest
Xseed item to place, it should be done first.  The reason for the wide
Xband of ISOLATED cells is to assure that UNUSED cells, which cannot
Xhave streets beside them, have at least three rows of cells between
Xthem, so street connection is not blocked. 
X
XAt the top of the routine, a choice existed to use simple math and
Xinefficient surplus random calls, or complex math so that only
Xinterior cells were eligible for the random call.  The simple,
Xwasteful method was chosen, since the mazeunused parameter is
Xcarefully limited to make sure the UNUSED cells will actually fit, and
Xthe number used is zero by default.  So totalcells is just the list
Xsize, and is used for the modulus against the RANDOM() call. 
X
XThe outer loop is pretty simple, it just counts the requested
Xmazeunused cells whose creation is attempted, attempts to find one to
Xcreate upto to MAXTRIES attempts, and if one is found, as opposed to
Xfalling through on MAXTRIES, moves it to the UNUSED list with
Xmovefromto(), and makes the center a WALL symbol in cmaze with the
Xusual ugly arithmetic to convert from statlist to cmaze indices. 
X
XThe inner loop is simpler yet, with just two statements, the random
Xcell choice to try, and a bump of tries to check each round against
XMAXTRIES. 
X
XThe scary looking part is the 59 guard conditions on those two
Xstatements, perhaps some minor record.  Dijkstra would be proud. There
Xis really a lot less than meets the eye.  First, the tries against
XMAXTRIES test is done.  Next, enough interiorcell() checks are done to
Xassure that all 49 cells exist, each check depending on some one
Xbefore for the existance of the next cell to check.  Last, all 49
Xcells are checked to be ISOLATED cells. The thing that makes it look
Xmessy is that the nbhris() method of finding a neighbor is used rather
Xthan defining several extra functions to accomplish the same thing
Xwith fewer lines of code.  Again, mainly defended by this being a low
Xcpu cost part of the code. 
X
X-----------------------------------------------------------------------
X| movefromto.c -- movefromto(&fromlist,&fromcount,&tolist,&tocount,
X|                            newstat,cellnum)
X-----------------------------------------------------------------------
X
XThis lovely little routine is used to hide almost all of the details
Xof fairly tricky double linked list maintenance from the rest of the
Xcode. 
X
XNothing terribly original is going on here; the cell pointers are
Xsaved, the cell is unlinked from the (middle of the) from list and
Xlinked into the head of the to list, the saved pointers are used to
Xrepair the rest of the from list, and the cell status is updated and
Xthe from and to counts corrected.. 
X
XThere is a little complexity because the list heads are "pointers"
X(indices really) rather than whole list elements, but that is one of
Xthe two standard approaches. 
X
X-----------------------------------------------------------------------
X| nhbrexists.c -- nhbrexists(cellid,nhbrid)
X-----------------------------------------------------------------------
X
XThis simple little routine just converts the statlist cell index to
Xcell row and cell column, something that corresponds to no existing
Xarray in the program, and then checks to see if the neighboring cell
Xin the nhbrid direction (0==north==up; 1==east==right; 2==south==down;
X3==west==left) falls inside the bounds of the cell row and column
Xlimits and returns true or false. 
X
X-----------------------------------------------------------------------
X| nhbris.c -- nhbris(cellid,nhbrid)
X-----------------------------------------------------------------------
X
XThis equally simple routine depends on the neighbor cell being known
Xto exist, and returns its statlist cell index given the current cell
Xindex and the neighbor direction.
X
X-----------------------------------------------------------------------
X| showdbmaze.c -- showdebugmaze()
X-----------------------------------------------------------------------
X
XThis routine is for debug, and is linked in but only called in error
Xconditions unless the SHOWSTART define is compiled set to 1.
X
XIt does give a very handy debugging display, though, with the cell
Xcenters of non-STREET cells replaced by letters showing the cell
Xstatus, I, L, D, U, or ? for bad status, and the centers of STREET
Xcells replaced by the direction of that street cell as ^ > v < or X
Xfor bad direction. 
X
XThis lets one dump (in one case where I used it) a running list of
Xmazes, with a single street cell more progress for each, and visualize
Xhow the algorithm is working. I used this display to find the problem
Xwith tertiary neighbors of ISOLATED cells mentioned above under
Xmakestreet(). 
X
XThe method is to walk the entire cmaze character array, dumping the
Xarray contents for all but center-of-cell cmazearray characters (the
Xones with both indices odd), and, using a big switch statement on cell
Xstatus and a nested smaller switch statement on street direction for
Xstatus STREET, to chose the status or direction symbol to print in
Xplace of the usual BLANK or WALL center symbol. 
X
XThe default: entries for the switch statements are bogosities, though
Xcute, but it's hard to put obsessively detailed debug statements into
Xwhat are already debug routines, so I didn't. 
X
X-----------------------------------------------------------------------
X| showlistdet.c -- showlistdetail()
X-----------------------------------------------------------------------
X
XAnother debug routine, calls to which are commented out in main().
XThis and showlistsummary() together dump a detailed printout of the
Xcontents of statlist.  Since mountains of data are useless for
Xdebugging, no attempt has been made to format this nicely for big
Xmazes; do your debugging with little ones. This part dumps the
Xcontents of each cell of the statlist, the prev, next, status,
Xstreetnum and streetdir fields. 
X
X-----------------------------------------------------------------------
X| showlistsum.c -- showlistsummary()
X-----------------------------------------------------------------------
X
XAnother debug routine, calls to which are commented out in main().
XThis and showlistdetail() together dump a detailed printout of the
Xcontents of statlist and the list counts and pointers.  This routine
Xdumps the list identifier integer value, the list header pointer
Xcontents, and the list count for each of the isolated, live, dead,
Xstreet, and unused lists, and with the street list items, the
Xstreetnumct global contents as well. 
X
X-----------------------------------------------------------------------
X| showmaze.c -- showmaze()
X-----------------------------------------------------------------------
X
XThe routine that does step 10), show the final product, is extremely
Xsimple, just a row by row dump of the cmaze array with trailing
Xnewlines.  The little #if condition on PROGRESS is so that the first
Xline of the maze won't get dumped starting in midline when the
Xoptional progress reports are turned on and showmaze() is being used
Xalong with showdbmaze() for debugging in mid run. 
X
X-----------------------------------------------------------------------
X| townmain.c -- main(argc,argv)
X-----------------------------------------------------------------------
X
XThis is the main routine for townmaze.  It just calls routines to do
Xthe steps listed at the top in order: seeds the random number
Xgenerator, gets the arguments from the command line, allocates array
Xspace, fills the cmaze array, fills the statlist array, seeds the
Xunused cells, seeds the gate cells, seeds the courtyard cells,
Xconnects the streets, runs alleys to the remaining isolated cells,
Xcloses off the extra doors open for each room, closes off any extra
Xopen gates, shows the maze, returns the allocated array space to the
Xfree list, and exits (step 12), uniquely a part of main()).
X
X-----------------------------------------------------------------------
X| usage.c -- usage()
X-----------------------------------------------------------------------
X
XThis routine is called by getargs if any of the command line arguments
Xare incorrect or unparsable, it just dumps a 22 or so line usage
Xmessage on the screen describing the parameters and their purpose
Xbriefly. 
X
END_OF_FILE
if test 39151 -ne `wc -c <'townpgmr.doc'`; then
    echo shar: \"'townpgmr.doc'\" unpacked with wrong size!
fi
# end of 'townpgmr.doc'
fi
echo shar: End of archive 4 \(of 4\).
cp /dev/null ark4isdone
MISSING=""
for I in 1 2 3 4 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 4 archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0



More information about the Alt.sources mailing list