www.jupiter-ace.co.uk

Previous Page > Listings Index > 3DMaze listing


3DMaze
by
Ricardo Fernandes Lopes
3D Maze download as a Tap, wav files and instruction can be found here
(      3D MAZE version 1.0 for Jupiter ACE       )
(     Copyright (c) 2007 by Ricardo F. Lopes     )
( under the GNU General Public License version 2 )

( Start the game with: )
( 3DMAZE )

( Game control keys:  )
(  I = Step forward   )
(  J = Turn left      )
(  L = Turn right     )
(  K = Step backward  )
(  H = 2D map look-up )
(  Q = Quit game      )

( ============= )
(  MISC. WORDS  )
( ============= )

16 BASE C!   ( Hex )

CREATE CMOVE ( src dst n -- : Move n bytes from src to dst )
 DF C,         ( RST 18    ; get 'n'               )
 42 C,         ( LD B,D    ; BC = 'n'              )
 4B C,         ( LD C,E                            )
 DF C,         ( RST 18    ; get 'dst'             )
 D5 C,         ( PUSH DE   ; save it               )
 DF C,         ( RST 18    ; get 'src'             )
 E1 C,         ( POP HL    ; HL = 'dst'            )
 EB C,         ( EX DE,HL  ; DE = 'dst' , HL='src' )
 ED C, B0 C,   ( LDIR      ; move 'n' bytes        )
 FD C, E9 C,   ( JP (IY)   ; end                   )
CMOVE DUP 2- ! ( Make CMOVE an executable word )

CREATE TOUPPER ( c -- C : convert a character to uppercase )
 DF C,             ( RST  18h   ; E = char to convert     )
 7B C,             ( LD   A,E   ; A = char to convert     )
 CD C, 07 C, 08 C, ( CALL 0807h ; to-upper ROM routine    )
 5F C,             ( LD   E,A   ; E = converted char      )
 D7 C,             ( RST  10h   ; Push char to Data Stack )
 FD C, E9 C,       ( JP   [IY]  ; end                     )
TOUPPER DUP 2- !   ( Make TOUPPER an executable word )

DECIMAL

: 2DUP ( x1 x2 -- x1 x2 x1 x2 )  OVER OVER ;
: 1+! ( adr -- )  DUP @ 1+ SWAP ! ;
: KEY ( -- c : wait for a keypress )
 BEGIN INKEY 0= UNTIL
 BEGIN INKEY ?DUP UNTIL
;

( Random Number Generator )
0 VARIABLE RND
: RANDOMIZE 15403 @ RND ! ;
: RANDOM RND @ 31421 * 6927 + DUP RND ! ;
: RAND RANDOM U* SWAP DROP ; ( n1 -- n2 : n2 = random from 0 to n1-1)

( ==================== )
(  GRAPHIC CHARACTERS  )
( ==================== )

CREATE GRAPH 272 ALLOT ( Graphic characters )

( Load graphic chars from files created with FontEd )
GRAPH       256 BLOAD GRAPH     ( ASCII 0 to 31 = graph  )
GRAPH 256 +   8 BLOAD SLASH     ( ASCII 47 = slash /     )
GRAPH 264 +   8 BLOAD BACKSLASH ( ASCII 92 = backslash \ )

: GR ( Set graphic characters to char generator mem )
 GRAPH       11264 256 CMOVE ( ASCII 0-31          )
 GRAPH 256 + 11640   8 CMOVE ( ASCII 47, slash     )
 GRAPH 264 + 12000   8 CMOVE ( ASCII 92, backslash )
;

( ====== )
(  MAZE  )
( ====== )

( Maximum Maze dimensions + margins )
32 CONSTANT MAXX  ( must be 32, the screen width )
22 CONSTANT MAXY

( Current maze dimensions )
10 VARIABLE XSIZE
 7 VARIABLE YSIZE

( Add to maze size )
: X+ ( n -- )  XSIZE @ + 3 MAX 30 MIN XSIZE ! ;
: Y+ ( n -- )  YSIZE @ + 3 MAX 20 MIN YSIZE ! ;

CREATE MAZE  MAXX MAXY * ALLOT ( Maze mem )

( Convert room xy coordinates to maze mem address )
: XY>ROOM ( x y -- room )  MAXX * + MAZE + ;

( Room walls as bit masks )
 1 CONSTANT WEST   ( 0001 West wall  )
 2 CONSTANT SOUTH  ( 0010 South wall )
 4 CONSTANT EAST   ( 0100 East wall  )
 8 CONSTANT NORTH  ( 1000 North wall )
15 CONSTANT ALL    ( 1111 All walls  )

2 CONSTANT RIGHT
4 CONSTANT BACK
8 CONSTANT LEFT
: TURN ( facing1 dir -- facing2 : Change Direction )
 OVER 16 * ROT OR ( NESW -> NESWNESW )
 SWAP /           ( Turn )
 15 AND           ( NESWNESW -> NESW )
;

( ============= )
(  DRAW 2D MAP  )
( ============= )

: _MAP ( Draw 2D maze map )
 YSIZE @ 2+ 0   ( line count including margins )
 DO
  0 I XY>ROOM   ( source = maze )
  9216 I 32 * + ( destination = screen )
  XSIZE @ 2+    ( column count, including margins )
  CMOVE         ( copy maze to screen )
 LOOP
;

: _ROOM ( room -- : Draw one maze room in 2D )
 DUP C@ SWAP   ( Stack: walls room )
 MAZE - 9216 + ( Stack: walls screen )
 C!            ( Set screen char )
;

: _POS ( room -- : Show player position in 2D map )
 DUP C@ 16 OR SWAP
 MAZE - 9216 + C!
;

( ================= )
(  MAZE GENERATION  )
( ================= )

: INIT ( Initialize maze before generate)
 YSIZE @ 2+ 0
 DO
  XSIZE @ 2+ 0
  DO
   ALL I J XY>ROOM C!                           ( Close rooms )
  LOOP
  0 I XY>ROOM DUP C@ EAST AND SWAP C!           ( West border )
  XSIZE @ 1+ I XY>ROOM DUP C@ WEST AND SWAP C!  ( East border )
 LOOP
 XSIZE @ 2+ 0
 DO
  I 0 XY>ROOM DUP C@ SOUTH AND SWAP C!          ( North border )
  I YSIZE @ 1+ XY>ROOM DUP C@ NORTH AND SWAP C! ( South border )
 LOOP
;

( Walking to next room )
CREATE WLK ( const table for WALK word )
0 , -1 , MAXX , 0 , 1 , 0 , 0 , 0 , MAXX NEGATE ,
: WALK ( room1 facing -- room2 facing : Walk to facing room )
 DUP 2 * WLK + @ ( displacement )
 ROT + SWAP
;

: REMOVE ( room facing -- room facing : Remove facing wall )
 2DUP -1 XOR  ( facing bit mask  )
 OVER C@ AND  ( clear facing bit )
 SWAP C!      ( store it back    )
 OVER _ROOM   ( update screen    )
;

: CUT ( room1 facing -- room2 : Open walls to next room )
 REMOVE     ( remove facing wall )
 WALK       ( go to next room    )
 BACK TURN  ( look back          )
 REMOVE     ( remove back wall   )
 DROP       ( discard facing     )
;

CREATE FACES 4 ALLOT ( direction buffer for neighbor check )

: CHECK ( n room facing -- n : Check & count if next room is closed )
 WALK              ( walk to next room    )
 SWAP C@ ALL =     ( is it a closed room? )
 IF
  OVER FACES + C!  ( save facing     )
  1+               ( count as closed )
 ELSE
  DROP             ( discard facing  )
 THEN
;

: CHECKAROUND ( room -- room n : Check around for closed rooms )
 0  ( start counting closed neighbors )
 OVER WEST  CHECK
 OVER NORTH CHECK
 OVER EAST  CHECK
 OVER SOUTH CHECK
;

( Select a random direction toward a closed room )
: RNDFACE ( n -- facing )  RAND FACES + C@ ;

0 VARIABLE REMAINING ( remaining rooms to visit )

: DFS ( room #rooms -- : Depth-First Search maze generation )
 1- REMAINING !      ( rooms to visit )
 0 SWAP              ( stack a marker )
 BEGIN
  REMAINING @        ( stop when all rooms visited )
 WHILE
  CHECKAROUND ?DUP   ( any closed neighbor? )
  IF
   OVER SWAP         ( stack current room             )
   RNDFACE CUT       ( cut a passage to a closed room )
   REMAINING @ 1-    ( count visited rooms            )
   REMAINING !
  ELSE
   DROP              ( backtrack last visited room )
  THEN
 REPEAT
 BEGIN 0= UNTIL      ( clear stack to marker )
;

: GENERATE ( Start maze generation )
 CLS INIT _MAP
 XSIZE @ 2 /  YSIZE @ 2 /      ( Start from maze center )
 XY>ROOM XSIZE @ YSIZE @ * DFS
;

: SETEXIT ( Set a random maze exit at North border )
 XSIZE @ RAND 1+ 0
 XY>ROOM SOUTH CUT DROP
;

: START ( -- room facing : Generate a random start position at South )
 XSIZE @ RAND 1+ YSIZE @
 XY>ROOM NORTH
;

( ============== )
(  DRAW 3D MAZE  )
( ============== )

0 VARIABLE LOOKUPS  ( Number of 2D map invoking )
0 VARIABLE STEPS    ( Number of steps )

: .SCORE ( Display score and controls )
  0 24 AT ." 3D MAZE"
  2 25 AT ." Steps"
  3 26 AT STEPS @ .
  5 25 AT ." Helps"
  6 26 AT LOOKUPS @ .
 15 25 AT ." I"
 16 24 AT ." JKL Move"
 18 25 AT ." H  Help"
 20 25 AT ." Q  Quit"
;

CREATE MRKS ( 3D drawing key screen coordinates )
( A     B )
  0 C, 22 C,
  1 C, 21 C,
  5 C, 17 C,
  8 C, 14 C,
 10 C, 12 C,
 11 C, 11 C,
: MARKS ( n -- A[n] B[n] )  2 * MRKS + DUP C@ SWAP 1+ C@ ;

( Draw Vertical edges: )
(  Left edge column = A[n+1]-1 )
( Right edge column = B[n+1]+1 )
(  1 past Last line = B[n+1]+1 )
(        First line = A[n+1]   )
: _EDGES ( n -- : Draw Vertical Edges )
 1+ MARKS 1+            ( A[n+1]   B[n+1]+1                 )
 OVER 1-                ( A[n+1]   B[n+1]+1 A[n+1]-1        )
 SWAP ROT               ( A[n+1]-1 B[n+1]+1 A[n+1]          )
 OVER SWAP              ( A[n+1]-1 B[n+1]+1 B[n+1]+1 A[n+1] )
 DO                     ( A[n+1]-1 B[n+1]+1                 )
  OVER I SWAP AT 4 EMIT ( Draw left edge  )
  I OVER AT 1 EMIT      ( Draw right edge )
 LOOP
 DROP DROP              ( discard left & right column coord )
;

( Draw Back Wall: )
(           Top line = A[n+1]-1 )
(        Bottom line = B[n+1]+1 )
( 1 past last column = B[n+1]+1 )
(       First column = A[n+1]   )
: _BACK ( n -- : Draw Back Wall )
 1+ MARKS 1+       ( A[n+1]   B[n+1]+1                 )
 OVER 1-           ( A[n+1]   B[n+1]+1 A[n+1]-1        )
 ROT ROT           ( A[n+1]-1 A[n+1]   B[n+1]+1        )
 DUP ROT           ( A[n+1]-1 B[n+1]+1 B[n+1]+1 A[n+1] )
 DO                ( A[n+1]-1 B[n+1]+1                 )
  OVER I AT 2 EMIT ( Draw top line    )
  DUP I AT 8 EMIT  ( Draw bottom line )
 LOOP
 DROP DROP         ( discard top & bottom line coord )
;

( Draw Left Wall: )
(        Bottom line = B[n]   )
(           Top line = A[n]   )
( 1 past last column = A[n+1] )
(       First column = A[n]   )
: _LWALL ( n -- : Draw Left wall )
 DUP MARKS SWAP    ( n    B[n]   A[n]   )
 ROT 1+ MARKS DROP ( B[n] A[n]   A[n+1] )
 SWAP              ( B[n] A[n+1] A[n]   )
 DO                ( B[n]               )
  I I AT 92 EMIT   ( Draw top line \          )
  DUP I AT 47 EMIT ( Draw bottom line /       )
  1-               ( update bottom line coord )
 LOOP
 DROP              ( discard bottom line coord )
;

( Draw Right Wall: )
(        Bottom line = B[n+1]+1   )
(           Top line = A[n+1]-1   )
( 1 past last column = B[n]+1     )
(       First column = B[n+1]+1   )
: _RWALL ( n -- : Draw Right Wall )
 DUP 1+ MARKS 1+ SWAP 1- ( n        B[n+1]+1 A[n+1]-1 )
 ROT MARKS SWAP DROP 1+  ( B[n+1]+1 A[n+1]-1 B[n]+1   )
 ROT                     ( A[n+1]-1 B[n]+1   B[n+1]+1 )
 DO                      ( A[n+1]-1                   )
  DUP I AT 47 EMIT       ( Draw top line /       )
  I I AT 92 EMIT         ( Draw bottom line \    )
  1-                     ( update top line coord )
 LOOP
 DROP                    ( discard top line coord )
;

( Draw Left Back Wall: )
(        Bottom line = B[n+1]+1 )
(           Top line = A[n+1]-1 )
( 1 past last column = A[n+1]   )
(       First column = A[n]     )
: _LBACK ( n -- : Draw Left Back Wall )
 DUP 1+ MARKS 1+   ( n        A[n+1]   B[n+1]+1        )
 ROT MARKS DROP    ( A[n+1]   B[n+1]+1 A[n]            )
 ROT DUP 1-        ( B[n+1]+1 A[n]     A[n+1] A[n+1]-1 )
 SWAP ROT          ( B[n+1]+1 A[n+1]-1 A[n+1] A[n]     )
 DO                ( B[n+a]+1 A[n+1]-1                 )
  DUP I AT 2 EMIT  ( Draw top line    )
  OVER I AT 8 EMIT ( Draw bottom line )
 LOOP
 DROP DROP         ( discard top & bottom line coord )
;

( Draw Right Back Wall: )
(        Bottom line = B[n+1]+1 )
(           Top line = A[n+1]-1 )
( 1 past last column = B[n]+1   )
(       First column = B[n+1]+1 )
: _RBACK ( n -- : Draw Right Back Wall )
 DUP 1+ MARKS 1+ SWAP 1-     ( n        B[n+1]+1 A[n+1]-1          )
 SWAP ROT MARKS SWAP DROP 1+ ( A[n+1]-1 B[n+1]+1 B[n]+1            )
 OVER                        ( A[n+1]-1 B[n+1]+1 B[n]+1   B[n+1]+1 )
 DO                          ( A[n+1]-1 B[n+1]+1                   )
  OVER I AT 2 EMIT           ( Draw top line    )
  DUP I AT 8 EMIT            ( Draw bottom line )
 LOOP
 DROP DROP                   ( discard top & bottom line coord )
;

( Check if facing wall exist )
: WALL? ( room facing -- flag )  SWAP C@ AND ;

: _3D ( room facing -- )
 CLS
 .SCORE
 5 0                    ( max View depth = 5 rooms )
 DO
  I _EDGES              ( Draw vertical edges )
  2DUP LEFT TURN WALL?  ( Is there a wall at left? )
  IF
   I _LWALL             ( draw left wall )
  ELSE
   2DUP LEFT TURN WALK  ( Go to left room )
   RIGHT TURN WALL?     ( Is there a left back wall?)
   IF
    I _LBACK            ( draw left back wall )
   THEN
  THEN
  2DUP RIGHT TURN WALL? ( Is there a wall at right? )
  IF
   I _RWALL             ( draw right wall )
  ELSE
   2DUP RIGHT TURN WALK ( Go to right room )
   LEFT TURN WALL?      ( Is there a right back wall? )
   IF
    I _RBACK            ( draw right back wall )
   THEN
  THEN
  2DUP WALL?            ( Facing a wall? )
  IF
   I _BACK LEAVE        ( Draw back wall and leave loop )
  ESLE
   WALK                 ( Move forward )
   OVER MAZE - 32 <     ( Maze exit? )
   IF
    LEAVE               ( Leave loop )
   THEN
  THEN
 LOOP
 DROP DROP              ( Discard room & facing )
;

( ============== )
(  GAME CONTROL  )
( ============== )

( Check if the exit was found )
: EXIT? ( room -- flag )  MAZE - 32 < ;

: .FACE ( facing -- : Print facing direction )
 DUP WEST  = IF ." West"  THEN
 DUP SOUTH = IF ." South" THEN
 DUP EAST  = IF ." East"  THEN
 NORTH     = IF ." North" THEN
;

: .INFO ( room facing -- : Print info bar at screen bottom )
 100 DUP BEEP
 22 0 AT ." Facing " .FACE
 ." , S:" STEPS @ .
 ." H:" LOOKUPS @ .
 EXIT?
 IF
  50 DUP BEEP 30 DUP BEEP
  ." EXIT"
 THEN
;

: _2D ( room facing -- : Draw 2D map and wait for a keypress )
 CLS _MAP OVER _POS .INFO
 KEY DROP                  ( Wait for a keypress )
;

: FORWARD ( room1 facing -- room2 facing : Step forward )
 2DUP WALL? 0=
 IF
  WALK STEPS 1+!
 THEN
;

: BACKWARD ( room1 facing -- room2 facing : Step backward )
 BACK TURN  FORWARD  BACK TURN
;

: PLAY
 0 STEPS !
 0 LOOKUPS !
 GENERATE SETEXIT START
 2DUP _2D
 BEGIN
  2DUP _3D
  KEY TOUPPER ROT ROT
  3 PICK ASCII I = IF FORWARD  THEN
  3 PICK ASCII K = IF BACKWARD  THEN
  3 PICK ASCII J = IF LEFT TURN  THEN
  3 PICK ASCII L = IF RIGHT TURN  THEN
  3 PICK ASCII H = IF 2DUP _2D  LOOKUPS 1+! THEN
  ROT ASCII Q = 3 PICK EXIT? OR
 UNTIL
 _2D
;

: .MENU
 CLS
 ." ____________3D_MAZE_______v 1.0_"
  4 11 AT ." Width:"
  5 10 AT ." Height:"
  7 11 AT ." I"
  8 10 AT ." JKL  Set Maze Size"
 10 11 AT ." P   Play"
 12 11 AT ." Q   Quit"
 20  3 AT ." c 2007 by Ricardo F. Lopes"
 21  3 AT ." General Public License v.2"
;

: .SIZE
 4 18 AT XSIZE @ .
 5 18 AT YSIZE @ .
;

: 3DMAZE ( Start the 3D game )
 GR RANDOMIZE .MENU
 BEGIN
  .SIZE
  KEY TOUPPER
  DUP ASCII I = IF  1 X+ THEN
  DUP ASCII K = IF -1 X+ THEN
  DUP ASCII J = IF -1 Y+ THEN
  DUP ASCII L = IF  1 Y+ THEN
  DUP ASCII P = IF PLAY .MENU THEN
  ASCII Q =
 UNTIL
 CLS
;