'=========================================================================== ' Subject: GENERATE AND SOLVE MAZES Date: 04-22-98 (18:39) ' Author: D. J. Tuttle Code: QB, QBasic, PDS ' Origin: Dtuttle25@aol.com Packet: ALGOR.ABC '=========================================================================== 'MAZE25.BAS for QBasic by D. J. Tuttle 'generate and solve random traditional mazes 'This program generates and solves random traditional mazes. A traditional 'maze has the property that a unique path exists between any two rooms. 'Therefore the entrance and exit to the maze may be placed anywhere. There 'are no loops, islands or closed rooms in a traditional maze. 'maze generation algorithm 'from BYTE Magazine, Dec. 1981, pp. 190 - 196 'maze solution algorithm 'from STRUCTURED BASIC AND BEYOND by Wayne Amsbury, (Computer Science Press, 'Inc., 1980), pp. 214 - 217 'suggestions for programmers: ' ' 1. Adjust the size of the mazes produced. The constant MAXSIZE sets the ' upper limit for the number of rows and columns. For each maze a value, ' ranging between 1 and MAXSIZE, is selected at random for the number of ' rows; then another value, with the same range, is selected at random ' for the number of columns. ' The smallest acceptable maze is 1 row by 1 column (1 room). ' The effective maximum size for this maze program, as written, is a maze ' of 100 rows by 100 columns (10,000 rooms). A maze larger than this may ' cause an out-of-memory error or exceed the ability of the graphics to ' distinguish between individual rooms. ' To produce 10,000 room mazes set MAXSIZE equal to 100 and change the ' following two statements in the Process subprogram: ' from: RowSize = INT(RND * MAXSIZE) + 1 to: RowSize = MAXSIZE ' from: ColSize = INT(RND * MAXSIZE) + 1 to: ColSize = MAXSIZE ' ' 2. Take a closer look at how the program solves the mazes by adding a ' delay at an appropriate place. Use a call to the ZSUBDelay subprogram. ' for example: CALL ZSUBDelay(.2) ' ' 3. REM out (temporarily inactivate) the lines which create the exit to ' the maze. These are the last two statements in the ZSUBGenerateMaze ' subprogram. The program will then search the entire maze, display a ' message that "no solution exists" and end. ' ' 4. Test other maze solution methods. Substitute another algorithm for the ' one used in the ZSUBSolveMaze subprogram. 'Send comments and questions to this email address: dtuttle25@aol.com DECLARE SUB Initialization () DECLARE SUB Process () DECLARE SUB Summary () DECLARE FUNCTION ZFUNCTestBoundary% (Row AS INTEGER, Col AS INTEGER) DECLARE FUNCTION ZFUNCTestWall% (Value AS INTEGER, Dir AS INTEGER) DECLARE SUB ZSUBAddFrontier (Row AS INTEGER, Col AS INTEGER, Array1() AS INTEGER, Count AS INTEGER, Array2() AS INTEGER) DECLARE SUB ZSUBAddSpanning (Row AS INTEGER, Col AS INTEGER, Array1() AS INTEGER, Count AS INTEGER, Array2() AS INTEGER) DECLARE SUB ZSUBCheckEsc () DECLARE SUB ZSUBDelay (Value AS SINGLE) DECLARE SUB ZSUBDrawGrid () DECLARE SUB ZSUBDrawMouse (Row AS INTEGER, Col AS INTEGER, Dir AS INTEGER, Hue AS INTEGER) DECLARE SUB ZSUBEraseWall (Row AS INTEGER, Col AS INTEGER, Dir AS INTEGER) DECLARE SUB ZSUBGenerateMaze (Array() AS INTEGER) DECLARE SUB ZSUBSolveMaze (Array() AS INTEGER) DECLARE SUB ZSUBViewPort () CONST FALSE = 0, TRUE = NOT FALSE CONST VIEWPORTBG = 1, VIEWPORTBORDER = 15 CONST WALLON = 7, MOUSEON = 4, PATHON = 9 CONST MAXSIZE = 48 CONST H$ = "#####, \ \" CONST STARTDIR = 1 DIM SHARED RowSize AS INTEGER, ColSize AS INTEGER DIM SHARED StartRow AS INTEGER, StartCol AS INTEGER DIM SHARED FinishRow AS INTEGER, FinishCol AS INTEGER DIM SHARED ExitFlag AS INTEGER DIM SHARED Esc AS STRING * 1 DIM SHARED Index(1 TO 4, 1 TO 4) AS INTEGER CALL Initialization CALL Process CALL Summary SYSTEM 'END OF MAIN PROGRAM SUB Initialization STATIC RANDOMIZE TIMER ExitFlag = FALSE Esc$ = CHR$(27) '1 = north (up), 2 = east (right), 3 = west (left), 4 = south (down) 'binary weight associated with each direction Index(1, 1) = 1 Index(1, 2) = 2 Index(1, 3) = 4 Index(1, 4) = 8 'row offset associated with moving forward from each direction Index(2, 1) = -1 Index(2, 2) = 0 Index(2, 3) = 0 Index(2, 4) = 1 'col offset associated with moving forward from each direction Index(3, 1) = 0 Index(3, 2) = 1 Index(3, 3) = -1 Index(3, 4) = 0 'opposite direction associated with each direction Index(4, 1) = 4 Index(4, 2) = 3 Index(4, 3) = 2 Index(4, 4) = 1 SCREEN 12 COLOR 7 PRINT "MAZE25.BAS" LOCATE 3, 1 PRINT "generate and solve" PRINT "random traditional" PRINT "mazes" LOCATE 8, 1 PRINT " x" PRINT " --------" LOCATE 30, 1 PRINT "Press to exit"; END SUB SUB Process STATIC DIM RoomSize AS INTEGER DO RowSize = INT(RND * MAXSIZE) + 1 LOCATE 7, 5 IF RowSize = 1 THEN PRINT USING H$; RowSize; "row" ELSE PRINT USING H$; RowSize; "rows" ColSize = INT(RND * MAXSIZE) + 1 LOCATE 8, 5 IF ColSize = 1 THEN PRINT USING H$; ColSize; "column" ELSE PRINT USING H$; ColSize; "columns" RoomSize = RowSize * ColSize LOCATE 10, 5 IF RoomSize = 1 THEN PRINT USING H$; RoomSize; "room" ELSE PRINT USING H$; RoomSize; "rooms" CALL ZSUBViewPort CALL ZSUBDrawGrid CALL ZSUBCheckEsc IF ExitFlag THEN EXIT DO REDIM maze(1 TO RowSize + 1, 1 TO ColSize) AS INTEGER CALL ZSUBGenerateMaze(maze()) IF ExitFlag THEN EXIT DO CALL ZSUBSolveMaze(maze()) IF ExitFlag THEN EXIT DO CALL ZSUBDelay(1) IF ExitFlag THEN EXIT DO LOOP END SUB SUB Summary STATIC VIEW END SUB FUNCTION ZFUNCTestBoundary% (Row AS INTEGER, Col AS INTEGER) STATIC ZFUNCTestBoundary = TRUE IF (Row < 1) OR (Row > RowSize) OR (Col < 1) OR (Col > ColSize) THEN ZFUNCTestBoundary = FALSE END FUNCTION FUNCTION ZFUNCTestWall% (Value AS INTEGER, Dir AS INTEGER) STATIC ZFUNCTestWall = FALSE IF (Value AND Index(1, Dir)) = 0 THEN ZFUNCTestWall = TRUE END FUNCTION SUB ZSUBAddFrontier (Row AS INTEGER, Col AS INTEGER, Array1() AS INTEGER, Count AS INTEGER, Array2() AS INTEGER) STATIC DIM I AS INTEGER DIM OffsetRow AS INTEGER, OffsetCol AS INTEGER FOR I = 1 TO 4 OffsetRow = Row + Index(2, I) OffsetCol = Col + Index(3, I) IF ZFUNCTestBoundary(OffsetRow, OffsetCol) THEN IF Array1(OffsetRow, OffsetCol) = 0 THEN Count = Count + 1 Array2(Count, 1) = OffsetRow Array2(Count, 2) = OffsetCol Array1(OffsetRow, OffsetCol) = -1 END IF END IF NEXT I END SUB SUB ZSUBAddSpanning (Row AS INTEGER, Col AS INTEGER, Array1() AS INTEGER, Count AS INTEGER, Array2() AS INTEGER) STATIC DIM I AS INTEGER DIM OffsetRow AS INTEGER, OffsetCol AS INTEGER Count = 0 FOR I = 1 TO 4 OffsetRow = Row + Index(2, I) OffsetCol = Col + Index(3, I) IF ZFUNCTestBoundary(OffsetRow, OffsetCol) THEN IF Array1(OffsetRow, OffsetCol) > 0 THEN Count = Count + 1 Array2(Count) = I END IF END IF NEXT I END SUB SUB ZSUBCheckEsc STATIC SELECT CASE INKEY$ CASE Esc$ ExitFlag = TRUE CASE ELSE ' END SELECT END SUB SUB ZSUBDelay (Value AS SINGLE) STATIC 'seconds per day CONST SPD = 86400 DIM DelayValue AS SINGLE DIM TimeCheck1 AS SINGLE, TimeCheck2 AS SINGLE DelayValue = Value TimeCheck1 = TIMER WHILE DelayValue > 0 DO TimeCheck2 = TIMER LOOP WHILE TimeCheck1 = TimeCheck2 IF TimeCheck2 > TimeCheck1 THEN DelayValue = DelayValue - (TimeCheck2 - TimeCheck1) ELSE 'midnight DelayValue = DelayValue - (SPD - TimeCheck1) - TimeCheck2 END IF TimeCheck1 = TimeCheck2 CALL ZSUBCheckEsc IF ExitFlag THEN EXIT SUB WEND END SUB SUB ZSUBDrawGrid STATIC DIM I AS INTEGER LINE (1, 1)-(ColSize + 1, RowSize + 1), WALLON, B FOR I = 2 TO ColSize LINE (I, 1)-(I, RowSize + 1), WALLON NEXT I FOR I = 2 TO RowSize LINE (1, I)-(ColSize + 1, I), WALLON NEXT I END SUB SUB ZSUBDrawMouse (Row AS INTEGER, Col AS INTEGER, Dir AS INTEGER, Hue AS INTEGER) STATIC DIM Row1 AS INTEGER, Col1 AS INTEGER DIM Row2 AS INTEGER, Col2 AS INTEGER Row1 = PMAP(Row, 1) + 2 Col1 = PMAP(Col, 0) + 2 Row2 = PMAP(Row + 1, 1) - 2 Col2 = PMAP(Col + 1, 0) - 2 SELECT CASE Dir CASE 0 ' CASE 1 Row2 = Row2 + 3 CASE 2 Col1 = Col1 - 3 CASE 3 Col2 = Col2 + 3 CASE 4 Row1 = Row1 - 3 END SELECT LINE (PMAP(Col1, 2), PMAP(Row1, 3))-(PMAP(Col2, 2), PMAP(Row2, 3)), Hue, BF END SUB SUB ZSUBEraseWall (Row AS INTEGER, Col AS INTEGER, Dir AS INTEGER) STATIC DIM Row1 AS INTEGER, Col1 AS INTEGER DIM Row2 AS INTEGER, Col2 AS INTEGER SELECT CASE Dir CASE 1 Row1 = PMAP(Row, 1) Col1 = PMAP(Col, 0) + 1 Row2 = PMAP(Row, 1) Col2 = PMAP(Col + 1, 0) - 1 CASE 2 Row1 = PMAP(Row, 1) + 1 Col1 = PMAP(Col + 1, 0) Row2 = PMAP(Row + 1, 1) - 1 Col2 = PMAP(Col + 1, 0) CASE 3 Row1 = PMAP(Row, 1) + 1 Col1 = PMAP(Col, 0) Row2 = PMAP(Row + 1, 1) - 1 Col2 = PMAP(Col, 0) CASE 4 Row1 = PMAP(Row + 1, 1) Col1 = PMAP(Col, 0) + 1 Row2 = PMAP(Row + 1, 1) Col2 = PMAP(Col + 1, 0) - 1 END SELECT LINE (PMAP(Col1, 2), PMAP(Row1, 3))-(PMAP(Col2, 2), PMAP(Row2, 3)), VIEWPORTBG END SUB SUB ZSUBGenerateMaze (Array() AS INTEGER) STATIC DIM FirstRow AS INTEGER, FirstCol AS INTEGER DIM Row AS INTEGER, Col AS INTEGER DIM FrontierCount AS INTEGER DIM SpanningCount AS INTEGER DIM RndChoice AS INTEGER REDIM Frontier(1 TO RowSize * ColSize * 2 / 3, 1 TO 2) AS INTEGER FirstRow = INT(RND * RowSize) + 1 FirstCol = INT(RND * ColSize) + 1 Array(FirstRow, FirstCol) = 16 FrontierCount = 0 CALL ZSUBAddFrontier(FirstRow, FirstCol, Array(), FrontierCount, Frontier()) WHILE FrontierCount > 0 RndChoice = INT(RND * FrontierCount) + 1 Row = Frontier(RndChoice, 1) Col = Frontier(RndChoice, 2) Frontier(RndChoice, 1) = Frontier(FrontierCount, 1) Frontier(RndChoice, 2) = Frontier(FrontierCount, 2) FrontierCount = FrontierCount - 1 REDIM Spanning(1 TO 4) AS INTEGER CALL ZSUBAddSpanning(Row, Col, Array(), SpanningCount, Spanning()) RndChoice = Spanning(INT(RND * SpanningCount) + 1) CALL ZSUBEraseWall(Row, Col, RndChoice) Array(Row, Col) = Index(1, RndChoice) Array(Row + Index(2, RndChoice), Col + Index(3, RndChoice)) = Array(Row + Index(2, RndChoice), Col + Index(3, RndChoice)) + Index(1, Index(4, RndChoice)) CALL ZSUBAddFrontier(Row, Col, Array(), FrontierCount, Frontier()) CALL ZSUBCheckEsc IF ExitFlag THEN EXIT SUB WEND Array(FirstRow, FirstCol) = Array(FirstRow, FirstCol) - 16 StartRow = RowSize + 1 StartCol = INT(RND * ColSize) + 1 CALL ZSUBEraseWall(StartRow, StartCol, STARTDIR) Array(StartRow, StartCol) = Index(1, STARTDIR) Array(StartRow - 1, StartCol) = Array(StartRow - 1, StartCol) + Index(1, Index(4, STARTDIR)) FinishRow = 0 FinishCol = INT(RND * ColSize) + 1 CALL ZSUBEraseWall(FinishRow, FinishCol, Index(4, STARTDIR)) Array(1, FinishCol) = Array(1, FinishCol) + Index(1, STARTDIR) END SUB SUB ZSUBSolveMaze (Array() AS INTEGER) STATIC DIM I AS INTEGER DIM PathCount AS INTEGER DIM Flag AS INTEGER REDIM path(1 TO RowSize * ColSize + 2, 1 TO 2) AS INTEGER CALL ZSUBDrawMouse(StartRow, StartCol, 0, MOUSEON) path(1, 1) = StartRow path(1, 2) = StartCol PathCount = 1 WHILE NOT ((path(PathCount, 1) = FinishRow) AND (path(PathCount, 2) = FinishCol)) 'Flag holds two pieces of information: ' sign: negative = backtrack, zero = no move, positive = forward ' value: 1 to 4 = direction Flag = 0 FOR I = 1 TO 4 IF NOT ZFUNCTestWall(Array(path(PathCount, 1), path(PathCount, 2)), I) THEN Flag = I IF PathCount > 1 THEN IF (path(PathCount, 1) + Index(2, I) = path(PathCount - 1, 1)) AND (path(PathCount, 2) + Index(3, I) = path(PathCount - 1, 2)) THEN Flag = -Flag END IF IF Flag > 0 THEN EXIT FOR END IF NEXT I SELECT CASE SGN(Flag) CASE -1 CALL ZSUBDrawMouse(path(PathCount, 1), path(PathCount, 2), Index(4, -Flag), VIEWPORTBG) Array(path(PathCount, 1), path(PathCount, 2)) = Array(path(PathCount, 1), path(PathCount, 2)) - Index(1, -Flag) PathCount = PathCount - 1 Array(path(PathCount, 1), path(PathCount, 2)) = Array(path(PathCount, 1), path(PathCount, 2)) - Index(1, Index(4, -Flag)) CASE 0 BEEP LOCATE 12, 1 PRINT "no solution exists" ExitFlag = TRUE EXIT SUB CASE 1 PathCount = PathCount + 1 path(PathCount, 1) = path(PathCount - 1, 1) + Index(2, Flag) path(PathCount, 2) = path(PathCount - 1, 2) + Index(3, Flag) CALL ZSUBDrawMouse(path(PathCount, 1), path(PathCount, 2), Flag, MOUSEON) END SELECT CALL ZSUBCheckEsc IF ExitFlag THEN EXIT SUB WEND 'highlight solution PAINT (StartCol + .5, StartRow + .5), PATHON, VIEWPORTBG END SUB SUB ZSUBViewPort STATIC VIEW (161, 1)-(638, 478), VIEWPORTBG, VIEWPORTBORDER SELECT CASE RowSize CASE IS < ColSize WINDOW SCREEN (0, 0 - (ColSize - RowSize) / 2)-(ColSize + 2, ColSize + 2 - (ColSize - RowSize) / 2) CASE IS = ColSize WINDOW SCREEN (0, 0)-(ColSize + 2, RowSize + 2) CASE IS > ColSize WINDOW SCREEN (0 - (RowSize - ColSize) / 2, 0)-(RowSize + 2 - (RowSize - ColSize) / 2, RowSize + 2) END SELECT END SUB