(* 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 db 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=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)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 ((X2x2) OR (Y2y2)) ) 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.