(* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010
Free Software Foundation, Inc. *)
(* This file is part of GNU Modula-2.
GNU Modula-2 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 3, or (at your option) any later
version.
GNU Modula-2 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 gm2; see the file COPYING. If not, write to the Free Software
Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *)
IMPLEMENTATION MODULE BoxMap ;
(*
Title : MakeMap
Author : Gaius Mulley
Date : 18/7/88
LastEdit : 18/7/88
System : LOGITECH MODULA-2/86
Description: Generates a simple random box map for Dungeon
*)
IMPORT Break ;
FROM StdIO IMPORT Write, Read ;
FROM StrIO IMPORT WriteString, WriteLn ;
FROM NumberIO IMPORT WriteCard, ReadCard ;
FROM Assertion IMPORT Assert ;
FROM Geometry IMPORT IsSubLine, IsSubRange, IsIntersectingRange,
IntersectionLength, IsPointOnLine, Abs, Min, Max ;
FROM MakeBoxes IMPORT InitBoxes, KillBoxes,
AddBoxes, GetAndDeleteRandomBox ;
FROM StoreCoords IMPORT InitCoords, KillCoords,
GetAndDeleteRandomCoord, AddCoord, CoordsExist ;
FROM Chance IMPORT InitRandom, KillRandom,
GetAndDeleteRandom, AddRandom,
GetRand ;
CONST
MaxCard = 65535 ;
MaxStack = 500 ;
TYPE
Square = RECORD
Contents : (Empty, Secret, Door, Wall, Treasure) ;
RoomOfSquare: CARDINAL ;
END ;
Map = ARRAY [1..MaxX], [1..MaxY] OF Square ;
StackEntity = RECORD
PerimeterIndex : CARDINAL ; (* Untried Coords *)
BoxIndex : CARDINAL ; (* Untried boxes *)
OrientationIndex: CARDINAL ; (* Untried orient's *)
END ;
VAR
CurrentMap : Map ;
Stack : ARRAY [1..MaxStack] OF StackEntity ;
StackPtr : CARDINAL ;
(*
InitializeMap - Initializes CurrentMap.
CurrentMap has its boarder set to a Wall and middle
is set to Empty.
*)
PROCEDURE InitializeMap ;
VAR
i, j: CARDINAL ;
BEGIN
FOR i := 1 TO MaxX DO
FOR j := 1 TO MaxY DO
WITH CurrentMap[i, j] DO
Contents := Empty ;
RoomOfSquare := 0
END
END
END
END InitializeMap ;
(*
Init - Initialize the module and start the generation of a map.
*)
PROCEDURE Init ;
BEGIN
NoOfBoxes := 0 ;
(* Initialize box 0 the edge of the map *)
WITH Boxes[0] DO
x1 := 1 ;
x2 := MaxX ;
y1 := 1 ;
y2 := MaxY
END ;
StackPtr := 0
END Init ;
(*
CreateBoxMap - builds a map with central corridors and ajoining rooms.
*)
PROCEDURE CreateBoxMap ;
BEGIN
Init ;
CorridorMap ;
RoomMap
END CreateBoxMap ;
(*
CorridorMap - makes a map based arround central corridors.
*)
PROCEDURE CorridorMap ;
BEGIN
CreateCorridors ;
NoOfCorridorBoxes := NoOfBoxes
END CorridorMap ;
(*
CleanUpStack - cleans up the temporary stack where alternative rooms were
stored but are no longer needed.
*)
PROCEDURE CleanUpStack ;
BEGIN
WHILE StackPtr>0 DO
WITH Stack[StackPtr] DO
KillBoxes(BoxIndex) ;
KillCoords(PerimeterIndex) ;
KillRandom(OrientationIndex)
END ;
DEC(StackPtr)
END
END CleanUpStack ;
(*
RoomMap - creates the rooms on the map which fill in space left by the
corridors.
*)
PROCEDURE RoomMap ;
BEGIN
WriteString('Starting Room building') ; WriteLn ;
CreateRooms
END RoomMap ;
(*
CreateCorridors - creates a length of corridor on the map.
*)
PROCEDURE CreateCorridors ;
VAR
Length,
LengthLeft: CARDINAL ;
BEGIN
LengthLeft := TotalCorridorLength ;
InitBoxCorridor ; (* Place new Box on the stack *)
REPEAT
IF MakeCorridor()
THEN
WITH Boxes[NoOfBoxes] DO
Length := Max(Abs(x1, x2), Abs(y1, y2))
END ;
IF LengthLeft>Length
THEN
DEC(LengthLeft, Length) ;
InitBoxCorridor (* Place new corridors on the stack *)
ELSE
LengthLeft := 0 (* All done *)
END
ELSE
IF StackPtr>0
THEN
(* Retract last corridor and try another *)
WriteString('Backtracking') ; WriteLn ;
WriteString('HALTing - quicker than backtracking') ; WriteLn ;
HALT ;
WITH Boxes[NoOfBoxes] DO
INC(LengthLeft, Max(Abs(x1, x2), Abs(y1, y2)))
END ;
KillBox ;
UnMakeBox
ELSE
WriteString('Run out of ideas! MaxCorridorLength too large!') ;
WriteLn ;
LengthLeft := 0 (* Fail safe exit *)
END
END
UNTIL LengthLeft=0
END CreateCorridors ;
(*
CreateRooms - places rooms inbetween the corridors on the map.
*)
PROCEDURE CreateRooms ;
VAR
Finished: BOOLEAN ;
BEGIN
InitBoxRoom ;
Finished := FALSE ;
REPEAT
IF MakeRoom()
THEN
InitBoxRoom ;
Finished := NOT CoordsExist(Stack[StackPtr].PerimeterIndex)
ELSE
Finished := TRUE ;
(*
IF StackPtr>0
THEN
(* Retract last room and try another *)
WriteString('Backtracking room') ; WriteLn ;
KillBox ;
UnMakeBox
ELSE
WriteString('Run out of ideas! Trying to create rooms!') ;
WriteLn ;
END
*)
END
UNTIL Finished ;
END CreateRooms ;
(*
MakeCorridor - returns true if a corridor was legally placed
onto the map.
*)
PROCEDURE MakeCorridor () : BOOLEAN ;
VAR
Success : BOOLEAN ;
x, y : CARDINAL ;
BEGIN
WITH Stack[StackPtr] DO
(*
Perimeter has been previously pushed.
We now try to place a piece of corridor
on a selected perimeter coordinate.
*)
Success := FALSE ;
REPEAT
GetAndDeleteRandomCoord(PerimeterIndex, x, y) ;
x := Min(x, MaxX) ;
y := Min(y, MaxY) ;
IF x#0 (* x=0 means no more coordinates to fetch *)
THEN
Success := PutCorridorOntoMap(x, y)
END
UNTIL Success OR (x=0) (* x=0 and y=0 means no coordinates left *)
(* when x=0 y is also 0. *)
END ;
RETURN( Success )
END MakeCorridor ;
(*
MakeRoom - returns true if a room was legally placed
onto the map.
*)
PROCEDURE MakeRoom () : BOOLEAN ;
VAR
Success : BOOLEAN ;
x, y : CARDINAL ;
BEGIN
WITH Stack[StackPtr] DO
(*
Perimeter has been previously pushed.
We now try to place a piece of corridor
on a selected perimeter coordinate.
*)
Success := FALSE ;
REPEAT
GetAndDeleteRandomCoord(PerimeterIndex, x, y) ;
IF x#0 (* x=0 means no more coordinates to fetch *)
THEN
Success := PutRoomOntoMap(x, y)
END
UNTIL Success OR (x=0) (* x=0 and y=0 means no coordinates left *)
(* when x=0 y is also 0. *)
END ;
RETURN( Success )
END MakeRoom ;
(*
UnMakeBox - deletes the last box placed in the Box list.
*)
PROCEDURE UnMakeBox ;
BEGIN
(*
IF NoOfCorridorBoxes>0
THEN
FindSpaceNextToRoom
END ;
*)
DEC(NoOfBoxes)
END UnMakeBox ;
(*
KillSurroundingBoxes - finds a pocket of space on the map and deletes
all neighbouring boxes.
*)
(*
PROCEDURE KillSurroundingBoxes ;
VAR
x, y,
i, j,
Swap, b: CARDINAL ;
BEGIN
GetFreeSpace(x, y) ;
i := x ;
j := y ;
Swap := NoOfBoxes ;
REPEAT
b := 1 ;
WHILE b<=Swap DO
IF IsPointOnBox(b, i, j)
THEN
SwapBox(b, Swap) ;
DEC(Swap)
END ;
INC(b)
END ;
WalkClockWise(i, j)
UNTIL (x=i) AND (y=j) ;
RenewBoxes(Swap, Swap)
END KillSurroundingBoxes ;
*)
(*
SwapBox - swaps two boxes, i and j, arround on the stack.
*)
PROCEDURE SwapBox (i, j: CARDINAL) ;
VAR
s: StackEntity ;
b: Box ;
BEGIN
b := Boxes[i] ;
Boxes[i] := Boxes[j] ;
Boxes[j] := b ;
s := Stack[i] ;
Stack[i] := Stack[j] ;
Stack[j] := s
END SwapBox ;
(*
FindSpaceNextToRoom - finds a pocket of space on the map and places
a room near this onto the top of the box stack.
*)
PROCEDURE FindSpaceNextToRoom ;
VAR
t: Box ;
x, y, b, d,
Nearest,
Distance : CARDINAL ;
BEGIN
GetSpaceCoord(x, y) ;
Nearest := 1 ;
Distance := DistanceAppartPoint(1, x, y) ;
b := NoOfBoxes-1 ;
WHILE b>1 DO
d := DistanceAppartPoint(b, x, y) ;
IF d<Distance
THEN
Distance := d ;
Nearest := b
END ;
DEC(b)
END ;
SwapBox(Nearest, NoOfBoxes)
END FindSpaceNextToRoom ;
(*
GetSpaceCoord - Sets x and y to a coordinate which has no room on it.
*)
PROCEDURE GetSpaceCoord (VAR x, y: CARDINAL) ;
VAR
Space: BOOLEAN ;
BEGIN
Space := FALSE ;
x := 1 ;
WHILE (NOT Space) AND (x<=MaxX) DO
y := 1 ;
WHILE (NOT Space) AND (y<=MaxY) DO
IF NOT IsSpace(x, y)
THEN
INC(y)
ELSE
Space := TRUE
END
END ;
IF NOT Space
THEN
INC(x)
END
END
END GetSpaceCoord ;
(*
Reschedule - reorders boxes on the stack, all boxes that touch the
top box are placed n-1 n-2 etc on the stack,
efficient recursive backtracking!
*)
PROCEDURE Reschedule (Lowest: CARDINAL) ;
VAR
b,
Swap: CARDINAL ;
t : Box ;
BEGIN
Swap := NoOfBoxes-1 ;
b := Lowest+1 ;
WITH Boxes[NoOfBoxes] DO
WHILE Swap>b DO
IF IsTouchingBox(b, x1, y1, x2, y2)
THEN
SwapBox(b, Swap) ;
INC(b)
END ;
DEC(Swap)
END
END
END Reschedule ;
(*
InitBoxCorridor - initializes a new corridor on the Stack,
the perimeter of the map is also pushed.
*)
PROCEDURE InitBoxCorridor ;
BEGIN
INC(StackPtr) ;
WITH Stack[StackPtr] DO
PerimeterIndex := InitCoords() ;
PushPerimeterOfBoxes(PerimeterIndex, FALSE) ;
OrientationIndex := 0 ;
BoxIndex := 0
END
END InitBoxCorridor ;
(*
InitBoxRoom - initializes a new corridor on the Stack,
the perimeter of the map is also pushed.
*)
PROCEDURE InitBoxRoom ;
BEGIN
(*
This is a really nasty kludge - because of memory space limitations
the StoreCoords module is pushed for space when creating large size
maps.
The kludge to get arround this is to kill all perimeter coordinates of the
previous box. This can be done since we never invoke backtracking
when creating boxrooms - but we may when we come up with a suitable
reliable algorithm, however, until then we can take advantage of
no backtracking and delete all perimeter coords of the last box.
*)
IF StackPtr>1
THEN
(* Ok delete perimeter coord *)
KillCoords(Stack[StackPtr].PerimeterIndex) ;
KillBoxes(Stack[StackPtr].BoxIndex)
END ;
(* All done - kludge over *)
INC(StackPtr) ;
WITH Stack[StackPtr] DO
PerimeterIndex := InitCoords() ;
PushPerimeterOfBoxes(PerimeterIndex, TRUE) ;
OrientationIndex := 0 ;
BoxIndex := 0
END
END InitBoxRoom ;
(*
KillBox - pops the last Box from the stack.
*)
PROCEDURE KillBox ;
BEGIN
WITH Stack[StackPtr] DO
KillCoords(PerimeterIndex)
END ;
DEC(StackPtr)
END KillBox ;
(*
PutCorridorOntoMap - returns true if it has placed a corridor
onto a map.
Otherwise no corridor has been placed onto
this map.
*)
PROCEDURE PutCorridorOntoMap (x, y: CARDINAL) : BOOLEAN ;
VAR
LenX,
LenY : CARDINAL ;
Success : BOOLEAN ;
BEGIN
CheckInitBoxCorridorIndex ;
WITH Stack[StackPtr] DO
Success := FALSE ;
REPEAT
IF GetBox(LenX, LenY)
THEN
Success := PlaceCorridorBox(x, y, LenX-1, LenY-1)
END
UNTIL Success OR (LenX=0) ;
END ;
CheckKillBoxIndex(LenX=0) ;
RETURN( Success )
END PutCorridorOntoMap ;
(*
PutRoomOntoMap - returns true if it has placed a room
onto a map.
Otherwise no room has been placed onto
this map.
*)
PROCEDURE PutRoomOntoMap (x, y: CARDINAL) : BOOLEAN ;
VAR
LenX,
LenY : CARDINAL ;
Success : BOOLEAN ;
BEGIN
CheckInitBoxRoomIndex ;
WITH Stack[StackPtr] DO
Success := FALSE ;
REPEAT
IF GetBox(LenX, LenY)
THEN
Success := PlaceRoomBox(x, y, LenX-1, LenY-1)
END
UNTIL Success OR (LenX=0) ;
END ;
CheckKillBoxIndex(LenX=0) ;
RETURN( Success )
END PutRoomOntoMap ;
(*
GetBox - returns true if a box can be returned.
It chooses one box from the box index,
from the stack.
The lengths of the Box are returned
in LengthX and LengthY.
*)
PROCEDURE GetBox (VAR LengthX, LengthY: CARDINAL) : BOOLEAN ;
BEGIN
WITH Stack[StackPtr] DO
GetAndDeleteRandomBox(BoxIndex, LengthX, LengthY)
END ;
RETURN(LengthX#0) (* LengthX#0 means found legal size box *)
END GetBox ;
(*
CheckInitBoxCorridorIndex - checks to see whether the current
stacked box needs
a list of legal corridor sizes stacked.
*)
PROCEDURE CheckInitBoxCorridorIndex ;
BEGIN
WITH Stack[StackPtr] DO
IF BoxIndex=0
THEN
(* Without stacked box list of legal sized corridors *)
BoxIndex := InitBoxes() ;
AddBoxes(BoxIndex, MinCorridorLength, CorridorWidth,
MaxCorridorLength, CorridorWidth) ;
AddBoxes(BoxIndex, CorridorWidth, MinCorridorLength,
CorridorWidth, MaxCorridorLength)
END
END
END CheckInitBoxCorridorIndex ;
(*
CheckInitBoxRoomIndex - checks to see whether the current stack box
needs a list of legal corridor sizes stacked.
*)
PROCEDURE CheckInitBoxRoomIndex ;
BEGIN
WITH Stack[StackPtr] DO
IF BoxIndex=0
THEN
(* Without stacked box list of legal sized rooms *)
BoxIndex := InitBoxes() ;
AddBoxes(BoxIndex, MinRoomLength, MinRoomLength,
MaxRoomLength, MaxRoomLength)
END
END
END CheckInitBoxRoomIndex ;
(*
CheckKillBoxIndex - if NeedToKill is set then the list of boxes
on the stack is killed.
Ideally this procedure should be a macro.
*)
PROCEDURE CheckKillBoxIndex (NeedToKill: BOOLEAN) ;
BEGIN
IF NeedToKill
THEN
WITH Stack[StackPtr] DO
KillBoxes(BoxIndex) ;
BoxIndex := 0
END
END
END CheckKillBoxIndex ;
(*
PlaceCorridorBox - returns true if a box can make a corridor at
position x, y.
All 4 orientations are tried.
2 1
4 3
Ie 1: (x, y) (x+LenX, y+LenY)
2: (x, y) (x-LenX, y+LenY)
3: (x, y) (x+LenX, y-LenY)
4: (x, y) (x-LenX, y-LenY)
*)
PROCEDURE PlaceCorridorBox (x, y: CARDINAL; LenX, LenY: CARDINAL) : BOOLEAN ;
VAR
Success: BOOLEAN ;
i : CARDINAL ;
BEGIN
CheckInitOrientationIndex ;
WITH Stack[StackPtr] DO
Success := FALSE ;
REPEAT
i := GetAndDeleteRandom(OrientationIndex) ;
CASE i OF
1: Success := AttemptToPlaceCorridor(x, y, x+LenX, y+LenY) |
2: IF x>LenX
THEN
Success := AttemptToPlaceCorridor(x-LenX, y, x, y+LenY)
END |
3: IF y>LenY
THEN
Success := AttemptToPlaceCorridor(x, y-LenY, x+LenX, y)
END |
4: IF (x>LenX) AND (y>LenY)
THEN
Success := AttemptToPlaceCorridor(x-LenX, y-LenY, x, y)
END
ELSE
END
UNTIL Success OR (i=0) ;
END ;
CheckKillOrientationIndex(i=0) ;
RETURN( Success )
END PlaceCorridorBox ;
(*
PlaceRoomBox - returns true if a box can make a corridor at
position x, y.
All 4 orientations are tried.
2 1
4 3
Ie 1: (x, y) (x+LenX, y+LenY)
2: (x, y) (x-LenX, y+LenY)
3: (x, y) (x+LenX, y-LenY)
4: (x, y) (x-LenX, y-LenY)
*)
PROCEDURE PlaceRoomBox (x, y: CARDINAL; LenX, LenY: CARDINAL) : BOOLEAN ;
VAR
Success: BOOLEAN ;
i : CARDINAL ;
BEGIN
CheckInitOrientationIndex ;
WITH Stack[StackPtr] DO
Success := FALSE ;
REPEAT
i := GetAndDeleteRandom(OrientationIndex) ;
CASE i OF
1: Success := AttemptToPlaceRoom(x, y, x+LenX, y+LenY) |
2: IF x>LenX
THEN
Success := AttemptToPlaceRoom(x-LenX, y, x, y+LenY)
END |
3: IF y>LenY
THEN
Success := AttemptToPlaceRoom(x, y-LenY, x+LenX, y)
END |
4: IF (x>LenX) AND (y>LenY)
THEN
Success := AttemptToPlaceRoom(x-LenX, y-LenY, x, y)
END
ELSE
END
UNTIL Success OR (i=0) ;
END ;
CheckKillOrientationIndex(i=0) ;
RETURN( Success )
END PlaceRoomBox ;
(*
CheckInitOrientationIndex - checks to see whether the current stacked
entity needs a new orientation index to also
be stacked.
*)
PROCEDURE CheckInitOrientationIndex ;
BEGIN
WITH Stack[StackPtr] DO
IF OrientationIndex=0
THEN
OrientationIndex := InitRandom() ;
AddRandom(OrientationIndex, 4)
END
END
END CheckInitOrientationIndex ;
(*
CheckKillOrientationIndex - checks to see whether the current stacked
entities orientation index needs to be
deleted.
This procedure ideally should be a macro..
*)
PROCEDURE CheckKillOrientationIndex (NeedToKill: BOOLEAN) ;
BEGIN
IF NeedToKill
THEN
WITH Stack[StackPtr] DO
KillRandom(OrientationIndex) ;
OrientationIndex := 0
END
END
END CheckKillOrientationIndex ;
(*
PushPerimeterOfBoxes - pushes all the current perimeter of the box map onto
the perimeter stack.
*)
PROCEDURE PushPerimeterOfBoxes (CoordIndex: CARDINAL; NoOpt: BOOLEAN) ;
VAR
i: CARDINAL ;
BEGIN
IF NoOfBoxes=0
THEN
(* Perimeter is center square in map *)
AddCoord(CoordIndex, MaxX DIV 2, MaxY DIV 2)
ELSE
i := 1 ;
WHILE i<=NoOfBoxes DO
PushPerimeterOfWalls(CoordIndex, i, NoOpt) ;
INC(i)
END
END
END PushPerimeterOfBoxes ;
(*
PushPerimeterOfWalls - pushes all coordinates of a box wall which are
external to the group of boxes.
Ie any wall which does is not shared by an
adjacent box MUST be external.
NoOpt determines whether optimization should be
applied to the restricting of perimeter coords.
Optimiztion tests for the minimum size of a room
to any wall, if this fails the coord is not added
to the perimeter list.
However this should not be used when pushing the
room perimeter since optimization is too restrictive.
(Corridor restrictions etc).
*)
PROCEDURE PushPerimeterOfWalls (CoordIndex: CARDINAL; b: CARDINAL;
NoOpt: BOOLEAN) ;
VAR
i, j: CARDINAL ;
BEGIN
WITH Boxes[b] DO
FOR i := x1 TO x2 DO
IF IsExternalHorizWallPerimeter(b, i, y1) AND
(NoOpt OR IsEnoughSpacePointToBox(i, y1))
THEN
AddCoord(CoordIndex, i, y1)
END ;
IF IsExternalHorizWallPerimeter(b, i, y2) AND
(NoOpt OR IsEnoughSpacePointToBox(i, y2))
THEN
AddCoord(CoordIndex, i, y2)
END
END ;
FOR j := y1 TO y2 DO
IF IsExternalVertWallPerimeter(b, x1, j) AND
(NoOpt OR IsEnoughSpacePointToBox(x1, j))
THEN
AddCoord(CoordIndex, x1, j)
END ;
IF IsExternalVertWallPerimeter(b, x2, j) AND
(NoOpt OR IsEnoughSpacePointToBox(x2, j))
THEN
AddCoord(CoordIndex, x2, j)
END
END
END
END PushPerimeterOfWalls ;
(*
IsExternalHorizWallPerimeter - returns true if coordinates,
x and y are not on any Horiz
wall of any box except b.
This routine allows point z, y to be
on a Vertical wall, but NOT on another
Horizontal wall.
*)
PROCEDURE IsExternalHorizWallPerimeter (b: CARDINAL;
x, y: CARDINAL) : BOOLEAN ;
VAR
i : CARDINAL ;
Found: BOOLEAN ;
BEGIN
Found := FALSE ;
IF NOT IsCornerPerimeter(b, x, y)
THEN
i := 0 ;
WHILE (i<=NoOfBoxes) AND (NOT Found) DO
IF i#b
THEN
WITH Boxes[i] DO
IF IsPointOnLine(x, y, x1, y1, x2, y1)
THEN
Found := TRUE
ELSIF IsPointOnLine(x, y, x1, y2, x2, y2)
THEN
Found := TRUE
END
END
END ;
INC(i)
END
END ;
RETURN( NOT Found )
END IsExternalHorizWallPerimeter ;
(*
IsExternalVertWallPerimeter - returns true if coordinates,
x and y are not on any Vertical
wall of any box except b.
This routine allows point z, y to be
on a Horizontal wall, but NOT on another
Vertical wall.
*)
PROCEDURE IsExternalVertWallPerimeter (b: CARDINAL;
x, y: CARDINAL) : BOOLEAN ;
VAR
i : CARDINAL ;
Found: BOOLEAN ;
BEGIN
Found := FALSE ;
IF NOT IsCornerPerimeter(b, x, y)
THEN
i := 0 ;
WHILE (i<=NoOfBoxes) AND (NOT Found) DO
IF i#b
THEN
WITH Boxes[i] DO
IF IsPointOnLine(x, y, x1, y1, x1, y2)
THEN
Found := TRUE
ELSIF IsPointOnLine(x, y, x2, y1, x2, y2)
THEN
Found := TRUE
END
END
END ;
INC(i)
END
END ;
RETURN( NOT Found )
END IsExternalVertWallPerimeter ;
(*
AttemptToPlaceCorridor - attempts to place a corridor x1, y1 x2, y2
onto the map.
If it succeeds it returns true
otherwise false
*)
PROCEDURE AttemptToPlaceCorridor (x1, y1, x2, y2: CARDINAL) : BOOLEAN ;
VAR
Success: BOOLEAN ;
BEGIN
IF IsCorridorSatisfied(x1, y1, x2, y2)
THEN
AddBox(x1, y1, x2, y2) ;
Success := TRUE
ELSE
Success := FALSE
END ;
RETURN( Success )
END AttemptToPlaceCorridor ;
(*
AttemptToPlaceRoom - attempts to place a room x1, y1 x2, y2
onto the map.
If it succeeds it returns true
otherwise false
*)
PROCEDURE AttemptToPlaceRoom (x1, y1, x2, y2: CARDINAL) : BOOLEAN ;
VAR
Success: BOOLEAN ;
BEGIN
IF IsRoomSatisfied(x1, y1, x2, y2)
THEN
AddBox(x1, y1, x2, y2) ;
Success := TRUE
ELSE
Success := FALSE
END ;
RETURN( Success )
END AttemptToPlaceRoom ;
(*
IsCorridorSatisfied - returns true if a Corridor x1, y1 x2, y2
may be placed onto the map without
contraveining the various rules.
*)
PROCEDURE IsCorridorSatisfied (x1, y1, x2, y2: CARDINAL) : BOOLEAN ;
VAR
Success: BOOLEAN ;
BEGIN
(* Put(x1, y1, x2, y2) ; *)
IF (x2>MaxX) OR (y2>MaxY)
THEN
(* WriteString('Failed SIZE') ; WriteLn *)
Success := FALSE
ELSIF NOT DistanceAppartEdge(x1, y1, x2, y2)
THEN
Success := FALSE
ELSIF IsOverLappingBox(x1, y1, x2, y2)
THEN
(* WriteString('Failed OVERLAP') ; *)
Success := FALSE
ELSIF NOT IsCorridorJoin(x1, y1, x2, y2)
THEN
(* WriteString('Failed CORRIDOR JOIN') ; *)
Success := FALSE
ELSIF NOT IsEnoughSpaceBetweenCorridors(x1, y1, x2, y2)
THEN
(* WriteString('Failed SPACE') ; *)
Success := FALSE
ELSE
Success := TRUE
END ;
RETURN( Success )
END IsCorridorSatisfied ;
(*
IsRoomSatisfied - returns true if a box x1, y1 x2, y2
may be placed onto the map without
contraveining the various rules.
*)
PROCEDURE IsRoomSatisfied (x1, y1, x2, y2: CARDINAL) : BOOLEAN ;
VAR
Success: BOOLEAN ;
BEGIN
(* Put(x1, y1, x2, y2) ; *)
IF (x2>MaxX) OR (y2>MaxY)
THEN
(* WriteString('Failed SIZE') ; WriteLn ; *)
Success := FALSE
ELSIF IsOverLappingBox(x1, y1, x2, y2)
THEN
(* WriteString('Failed OVERLAP') ; *)
Success := FALSE
ELSIF NOT DistanceAppartEdge(x1, y1, x2, y2)
THEN
Success := FALSE
ELSIF NOT IsBoxRoomLegal(x1, y1, x2, y2)
THEN
(* WriteString('Failed Legal') ; *)
Success := FALSE
ELSIF NOT IsRoomJoin(x1, y1, x2, y2)
THEN
(* WriteString('Failed ROOM JOIN') ; *)
Success := FALSE
ELSIF NOT IsEnoughSpaceBetweenRooms(x1, y1, x2, y2)
THEN
(* WriteString('Failed SPACE') ; *)
Success := FALSE
ELSE
Success := TRUE
(* ; WriteString('SUCCESS') ; *)
END ;
RETURN( Success )
END IsRoomSatisfied ;
(*
IsEnoughSpacePointToBox - returns true if there is enough space
between a point, x, y and all the boxes.
This routine is called before perimeter
coordinates are pushed, therefore coordinates
pushed are not doomed to failure due to lack
of space.
This routine consists of a reduced
IsEnoughSpaceBetweenBoxes procedure.
*)
PROCEDURE IsEnoughSpacePointToBox (x, y: CARDINAL) : BOOLEAN ;
VAR
ok : BOOLEAN ;
i : CARDINAL ;
Distance: CARDINAL ;
BEGIN
ok := TRUE ;
i := 0 ; (* 0 = Perimeter of map *)
WHILE ok AND (i<=NoOfBoxes) DO
Distance := DistanceAppartPoint(i, x, y) ;
IF Distance#0
THEN
ok := (Distance>=MinDistanceBetweenRooms)
END ;
INC(i)
END ;
RETURN( ok )
END IsEnoughSpacePointToBox ;
(*
IsEnoughSpaceBetweenCorridors - returns true if there is enough
space between box x1, y1 x2, y2
and the other boxes.
Also tests for right angle connection.
*)
PROCEDURE IsEnoughSpaceBetweenCorridors (x1, y1, x2, y2: CARDINAL) : BOOLEAN ;
VAR
ok : BOOLEAN ;
i : CARDINAL ;
Distance: CARDINAL ;
BEGIN
ok := TRUE ;
i := 1 ;
WHILE ok AND (i<=NoOfBoxes) DO
IF IsTouchingBox(i, x1, y1, x2, y2)
THEN
(* Check for a box that is not at right angles to new box. *)
(* We are only allowed to touch a box at right angles. *)
IF NOT IsDifferentOrientationBox(i, x1, y1, x2, y2)
THEN
(* touching a box which is not at right angles *)
ok := FALSE
END
ELSIF FreeSpace(i, x1, y1, x2, y2)
THEN
Distance := DistanceAppartBox(i, x1, y1, x2, y2) ;
(*
Distance := Min(
DistanceAppartBox(i, x1, y1, x2, y2),
DistanceAppartDiagonal(i, x1, y1, x2, y2)
) ;
*)
IF Distance=0
THEN
ELSE
ok := (Distance>=MinDistanceBetweenCorridors)
END
END ;
INC(i)
END ;
RETURN( ok )
END IsEnoughSpaceBetweenCorridors ;
(*
IsBoxRoomLegal - returns true if a box x1, y1, x2, y2 does not
have a wall which is next to but not sharing
another wall.
*)
PROCEDURE IsBoxRoomLegal (x1, y1, x2, y2: CARDINAL) : BOOLEAN ;
VAR
ok : BOOLEAN ;
i : CARDINAL ;
Distance: CARDINAL ;
CoveredN,
CoveredS,
CoveredE,
CoveredW: BOOLEAN ;
BEGIN
CoveredN := IsFullyCovered(x1, y2, x2, y2) ;
CoveredS := IsFullyCovered(x1, y1, x2, y1) ;
CoveredE := IsFullyCovered(x2, y1, x2, y2) ;
CoveredW := IsFullyCovered(x1, y1, x1, y2) ;
ok := TRUE ;
i := 1 ;
WHILE ok AND (i<=NoOfBoxes) DO
IF NOT IsTouchingBox(i, x1, y1, x2, y2)
THEN
IF (x1>1) AND (NOT CoveredW)
THEN
ok := NOT IsTouchingBox(i, x1-1, y1, x2, y2)
END ;
IF ok AND (y1>1) AND (NOT CoveredS)
THEN
ok := NOT IsTouchingBox(i, x1, y1-1, x2, y2)
END ;
IF ok AND (x2<MaxX) AND (NOT CoveredE)
THEN
ok := NOT IsTouchingBox(i, x1, y1, x2+1, y2)
END ;
IF ok AND (y2<MaxY) AND (NOT CoveredN)
THEN
ok := NOT IsTouchingBox(i, x1, y1, x2, y2+1)
END
END ;
INC(i)
END ;
RETURN( ok )
END IsBoxRoomLegal ;
(*
IsFullyCovered - returns true if every point on the line
x1, y1, x2, y2 is covered. The line must
either be horizontal or vertical.
*)
PROCEDURE IsFullyCovered (x1, y1, x2, y2: CARDINAL) : BOOLEAN ;
VAR
i : CARDINAL ;
Covered: BOOLEAN ;
BEGIN
Covered := TRUE ;
IF x1=x2
THEN
(* Vertical *)
i := y1 ;
WHILE Covered AND (i<=y2) DO
Covered := IsSpace(x1, i) ;
INC(i)
END
ELSIF y1=y2
THEN
(* Horizontal *)
i := x1 ;
WHILE Covered AND (i<=x2) DO
Covered := IsSpace(i, y1) ;
INC(i)
END
END ;
RETURN( Covered )
END IsFullyCovered ;
(*
IsEnoughSpaceBetweenRooms - returns true if there is enough
space between box x1, y1 x2, y2
and the other boxes.
*)
PROCEDURE IsEnoughSpaceBetweenRooms (x1, y1, x2, y2: CARDINAL) : BOOLEAN ;
VAR
ok : BOOLEAN ;
i : CARDINAL ;
Distance: CARDINAL ;
BEGIN
ok := TRUE ;
i := 1 ;
WHILE ok AND (i<=NoOfBoxes) DO
IF NOT IsTouchingBox(i, x1, y1, x2, y2)
THEN
(* Dont test the walls of the box for contraveining the space rule *)
(*
IF (x1+1<x2-1) AND (y1+1<y2-1) AND FreeSpace(i, x1+1, y1+1, x2-1, y2-1)
THEN
Distance := DistanceAppartDiagonal(i, x1+1, y1+1, x2-1, y2-1) ;
*)
IF FreeSpace(i, x1, y1, x2, y2)
THEN
(*
Distance := DistanceAppartDiagonal(i, x1, y1, x2, y2) ;
*)
Distance := Max( DistanceAppartDiagonal(i, x1, y1, x2, y2),
DistanceAppartBox(i, x1, y1, x2, y2) ) ;
(* WriteString('Dist') ; WriteCard(Distance, 6) ; WriteLn ; *)
IF Distance=0
THEN
(* touching a box *)
ELSE
Assert(NOT IsTouchingBox(i, x1, y1, x2, y2)) ;
ok := (Distance>=MinDistanceBetweenRooms)
END
END
END ;
INC(i)
END ;
RETURN( ok )
END IsEnoughSpaceBetweenRooms ;
(*
FreeSpace - returns true if there exists free space between box
X1, Y1, X2, Y2 and box b.
Should not be called if box b touches X1, Y1, X2, Y2.
*)
PROCEDURE FreeSpace (b: CARDINAL; X1, Y1, X2, Y2: CARDINAL) : BOOLEAN ;
VAR
Free : BOOLEAN ;
xs, xe,
ys, ye,
i, j : CARDINAL ;
BEGIN
WITH Boxes[b] DO
IF Abs(X1, x2)<Abs(X2, x1)
THEN
xs := Min(X1, x2) ;
xe := Max(X1, x2)
ELSE
xs := Min(X2, x1) ;
xe := Max(X2, x1)
END ;
IF Abs(Y1, y2)<Abs(Y2, y1)
THEN
ys := Min(Y1, y2) ;
ye := Max(Y1, y2)
ELSE
ys := Min(Y2, y1) ;
ye := Max(Y2, y1)
END ;
Free := FALSE ;
i := xs ;
WHILE (NOT Free) AND (i<=xe) DO
j := ys ;
WHILE (NOT Free) AND (j<=ye) DO
Free := IsSpace(i, j) ;
INC(j)
END ;
INC(i)
END
END ;
(* IF Free THEN WriteString('FREE') END ; *)
RETURN( Free )
END FreeSpace ;
(*
IsSpace - returns true if point x, y is not in any box.
A wall is counted as in the box.
*)
PROCEDURE IsSpace (x, y: CARDINAL) : BOOLEAN ;
VAR
b : CARDINAL ;
InBox: BOOLEAN ;
BEGIN
InBox := FALSE ;
b := 1 ; (* Not zero of course !! *)
WHILE (NOT InBox) AND (b<=NoOfBoxes) DO
WITH Boxes[b] DO
InBox := IsSubRange(x1, x2, x) AND IsSubRange(y1, y2, y)
END ;
INC(b)
END ;
RETURN( NOT InBox )
END IsSpace ;
(*
DistanceAppartEdge - returns true if the box, x1, y1, x2, y2, is a
required distance away from the edge of the
map.
Cannot use room zero for this test as we are inside
it and may touch one wall but be too near another!
*)
PROCEDURE DistanceAppartEdge (x1, y1, x2, y2: CARDINAL) : BOOLEAN ;
VAR
ok : BOOLEAN ;
Distance: CARDINAL ;
BEGIN
ok := TRUE ;
Distance := Abs(x1, 1) ;
IF (Distance>0) AND ok
THEN
ok := (Distance>=MinDistanceBetweenRooms)
END ;
Distance := Abs(x2, MaxX) ;
IF (Distance>0) AND ok
THEN
ok := (Distance>=MinDistanceBetweenRooms)
END ;
Distance := Abs(y1, 1) ;
IF (Distance>0) AND ok
THEN
ok := (Distance>=MinDistanceBetweenRooms)
END ;
Distance := Abs(y2, MaxY) ;
IF (Distance>0) AND ok
THEN
ok := (Distance>=MinDistanceBetweenRooms)
END ;
RETURN( ok )
END DistanceAppartEdge ;
(*
DistanceAppartPoint - returns the distance appart between box, b,
and point X, Y.
*)
PROCEDURE DistanceAppartPoint (b: CARDINAL; X, Y: CARDINAL) : CARDINAL ;
VAR
Xmin,
Ymin: CARDINAL ;
BEGIN
WITH Boxes[b] DO
IF IsSubRange(x1, x2, X)
THEN
Ymin := Min( Abs(y1, Y), Abs(y2, Y) )
ELSE
Ymin := MaxCard
END ;
IF IsSubRange(y1, y2, Y)
THEN
Xmin := Min( Abs(x1, X), Abs(x2, X) )
ELSE
Xmin := MaxCard
END
END ;
RETURN( Min(Xmin, Ymin) )
END DistanceAppartPoint ;
(*
DistanceAppartBox - returns the distance appart between box, b,
and box X1, Y1, X2, Y2
*)
PROCEDURE DistanceAppartBox (b: CARDINAL; X1, Y1, X2, Y2: CARDINAL) : CARDINAL ;
VAR
Xmin,
Ymin: CARDINAL ;
BEGIN
WITH Boxes[b] DO
IF IsIntersectingRange(x1, x2, X1, X2)
THEN
Ymin := Min(
Min( Abs(y1, Y1), Abs(y2, Y2) ),
Min( Abs(y1, Y2), Abs(Y1, y2) )
)
ELSE
Ymin := MaxCard
END ;
IF IsIntersectingRange(y1, y2, Y1, Y2)
THEN
Xmin := Min(
Min( Abs(x1, X1), Abs(x2, X2) ),
Min( Abs(x1, X2), Abs(X1, x2) )
)
ELSE
Xmin := MaxCard
END
END ;
RETURN( Min(Xmin, Ymin) )
END DistanceAppartBox ;
(*
DistanceAppartDiagonal - returns the diagonal
distance appart between X1, Y1, X2, Y2
and box b.
*)
PROCEDURE DistanceAppartDiagonal (b: CARDINAL;
X1, Y1, X2, Y2: CARDINAL) : CARDINAL ;
BEGIN
WITH Boxes[b] DO
RETURN(
Min(
Min( Abs(x1, X2), Abs(x2, X1) ),
Min( Abs(y1, Y2), Abs(y2, Y1) )
)
)
END
END DistanceAppartDiagonal ;
(*
IsCorridorJoin - returns true if a box corridor x1, y1 x2, y2
joins another corridor at right angles without
cutting off the potential corridor door.
A corridor is thought of as
##########################
| |
| |
##########################
and may only be placed together in a way such that
they meet -| or - etc
|
False is returned if this box corridor does not
correctly form a T junction with another.
*)
PROCEDURE IsCorridorJoin (x1, y1, x2, y2: CARDINAL) : BOOLEAN ;
VAR
ok,
DoorFound: BOOLEAN ;
b : CARDINAL ;
BEGIN
ok := TRUE ;
IF NoOfBoxes=0
THEN
DoorFound := TRUE
ELSE
DoorFound := FALSE ;
b := 1 ;
WHILE ok AND (b<=NoOfBoxes) DO
(* WriteString('Box') ; WriteCard(b, 2) ; *)
IF IsTouchingBox(b, x1, y1, x2, y2)
THEN
(* WriteString('TouchingBox') ; *)
IF IsDifferentOrientationBox(b, x1, y1, x2, y2)
THEN
(* WriteString('Different Orientation') ; *)
IF NOT DoorFound
THEN
DoorFound := IsCorridorWallJoinBox(b, x1, y1, x2, y2)
END
(* ; IF ok THEN WriteString('WallJoin') END ; *)
ELSE
ok := FALSE (* Dont allow parallel corridors to touch *)
END
END ;
INC(b)
END
END ;
RETURN( ok AND DoorFound )
END IsCorridorJoin ;
(*
IsRoomJoin - returns true if a box room x1, y1 x2, y2
joins another room with enough space for a door.
*)
PROCEDURE IsRoomJoin (x1, y1, x2, y2: CARDINAL) : BOOLEAN ;
VAR
DoorFound: BOOLEAN ;
b : CARDINAL ;
BEGIN
IF NoOfBoxes=0
THEN
DoorFound := TRUE
ELSE
DoorFound := FALSE ;
b := 1 ;
WHILE (NOT DoorFound) AND (b<=NoOfBoxes) DO
(* WriteString('Box') ; WriteCard(b, 2) ; *)
IF IsTouchingBox(b, x1, y1, x2, y2)
THEN
IF NOT DoorFound
THEN
DoorFound := IsRoomWallJoinBox(b, x1, y1, x2, y2)
END
END ;
INC(b)
END
END ;
RETURN( DoorFound )
END IsRoomJoin ;
(*
IsCorridorWallJoinBox - returns true if box, b, and box X1, Y1 X2, Y2
form a correct join ie covering the potential
door.
*)
PROCEDURE IsCorridorWallJoinBox (b: CARDINAL;
X1, Y1, X2, Y2: CARDINAL) : BOOLEAN ;
VAR
Success: BOOLEAN ;
BEGIN
WITH Boxes[b] DO
Success := (((X1=x1) OR (X1=x2) OR (X2=x1) OR (X2=x2))
AND IsSubLine(Y1, Y2, y1, y2)) OR
(((Y1=y1) OR (Y1=y2) OR (Y2=y1) OR (Y2=y2))
AND IsSubLine(X1, X2, x1, x2)) ;
RETURN( Success )
END
END IsCorridorWallJoinBox ;
(*
IsRoomWallJoinBox - returns true if box, b, and box X1, Y1 X2, Y2
form a correct join ie covering the potential
door.
*)
PROCEDURE IsRoomWallJoinBox (b: CARDINAL;
X1, Y1, X2, Y2: CARDINAL) : BOOLEAN ;
VAR
DoorWidth: CARDINAL ;
BEGIN
DoorWidth := 0 ;
WITH Boxes[b] DO
IF (X1=x2) OR (x1=X2)
THEN
DoorWidth := IntersectionLength(Y1, Y2, y1, y2) ;
IF (IsSubRange(Y1, Y2, y1) OR IsSubRange(y1, y2, Y1)) AND (DoorWidth>1)
THEN
DEC(DoorWidth)
END ;
IF (IsSubRange(Y1, Y2, y2) OR IsSubRange(y1, y2, Y2)) AND (DoorWidth>1)
THEN
DEC(DoorWidth)
END
ELSIF (Y1=y2) OR (y1=Y2)
THEN
DoorWidth := IntersectionLength(X1, X2, x1, x2) ;
IF (IsSubRange(X1, X2, x1) OR IsSubRange(x1, x2, X1)) AND (DoorWidth>1)
THEN
DEC(DoorWidth)
END ;
IF (IsSubRange(X1, X2, x2) OR IsSubRange(x1, x2, X2)) AND (DoorWidth>1)
THEN
DEC(DoorWidth)
END
END ;
RETURN( DoorWidth>=MinDoorLength )
END
END IsRoomWallJoinBox ;
(*
IsDifferentOrientationBox - returns true if box b has a different
orientation to box X1, Y1 X2, Y2.
*)
PROCEDURE IsDifferentOrientationBox (b: CARDINAL;
X1, Y1, X2, Y2: CARDINAL) : BOOLEAN ;
BEGIN
WITH Boxes[b] DO
IF Abs(X1, X2) = Abs(Y1, Y2)
THEN
RETURN( TRUE ) (* Square X1, Y1, X2, Y2 *)
ELSIF Abs(X1, X2) > Abs(Y1, Y2)
THEN
RETURN( Abs(x1, x2) <= Abs(y1, y2) )
ELSE
RETURN( Abs(x1, x2) >= Abs(y1, y2) )
END
END
END IsDifferentOrientationBox ;
(*
IsTouchingBox - returns true if a box X1, Y1 X2, Y2 touches box b
or if it intersects with this box.
*)
PROCEDURE IsTouchingBox (b: CARDINAL; X1, Y1, X2, Y2: CARDINAL) : BOOLEAN ;
BEGIN
WITH Boxes[b] DO
RETURN( NOT ((X2<x1) OR (X1>x2) OR (Y2<y1) OR (Y1>y2)) )
END
END IsTouchingBox ;
(*
IsCornerPerimeter - returns true if box, b, has a corner x, y which
is a perimeter.
*)
PROCEDURE IsCornerPerimeter (b: CARDINAL; x, y: CARDINAL) : BOOLEAN ;
VAR
Perimeter: BOOLEAN ;
i, j : CARDINAL ;
BEGIN
IF IsCorner(b, x, y)
THEN
Perimeter := FALSE ;
i := x-1 ;
j := y-1 ;
WHILE (NOT Perimeter) AND (i<=x+1) DO
j := y-1 ;
WHILE (NOT Perimeter) AND (j<=y+1) DO
IF IsSubRange(1, MaxX, i) AND IsSubRange(1, MaxY, j)
THEN
Perimeter := IsSpace(i, j)
END ;
INC(j, 2)
END ;
INC(i, 2)
END ;
RETURN( Perimeter )
ELSE
RETURN( FALSE )
END
END IsCornerPerimeter ;
(*
IsCorner - returns true if box, b, has a corner x, y.
*)
PROCEDURE IsCorner (b: CARDINAL; x, y: CARDINAL) : BOOLEAN ;
BEGIN
WITH Boxes[b] DO
RETURN( ((x1=x) OR (x2=x)) AND ((y1=y) OR (y2=y)) )
END
END IsCorner ;
(*
IsOverLappingBox - returns true if box X1, Y1 X2, Y2 overlaps
with another box NOT including edges touching.
*)
PROCEDURE IsOverLappingBox (X1, Y1, X2, Y2: CARDINAL) : BOOLEAN ;
VAR
b : CARDINAL ;
Found: BOOLEAN ;
BEGIN
Found := FALSE ;
b := 1 ;
WHILE (NOT Found) AND (b<=NoOfBoxes) DO
WITH Boxes[b] DO
Found := IsIntersection(x1, y1, x2, y2, X1, Y1, X2, Y2)
END ;
INC(b)
END ;
RETURN( Found )
END IsOverLappingBox ;
(*
IsIntersection - returns true if two boxes x1, y1 x2, y2 intersects
with X1, Y1 X2, Y2. Wall touching is allowed.
*)
PROCEDURE IsIntersection (x1, y1, x2, y2,
X1, Y1, X2, Y2: CARDINAL) : BOOLEAN ;
BEGIN
RETURN( NOT ( (x2<=X1) OR (x1>=X2) OR (y2<=Y1) OR (y1>=Y2) ) )
END IsIntersection ;
(*
AddBox - adds a box to the list of boxes and
adds a box to the Map.
*)
PROCEDURE AddBox (X1, Y1, X2, Y2: CARDINAL) ;
BEGIN
IF NoOfBoxes=MaxBoxes
THEN
WriteString('Too many boxes in Module MakeMap') ; WriteLn ;
HALT
ELSE
INC(NoOfBoxes) ;
WITH Boxes[NoOfBoxes] DO
x1 := X1 ;
y1 := Y1 ;
x2 := X2 ;
y2 := Y2
END
END
END AddBox ;
(*
GetCh - waits for a character to be pressed.
*)
PROCEDURE GetCh ;
VAR
ch: CHAR ;
BEGIN
Read(ch)
END GetCh ;
BEGIN
Init
END BoxMap.