Here's a 3D renderer a-la Wolfenstein (without textures):
1 DEFINT a-z : REM Define the default type for all variables to be INT.
2 xm=8 : ym = 6 : REM maximum extents of the map we're prepared for.
3 DIM maze(xm,ym) : REM REM stands for "remark", i.e. a comment.
4 pi! = 3.141592653 : REM The "!" stands for explicitly FLOAT type variable.
10 REM Read the maze data.
11 y = 1
12 READ s$
20 IF s$ = "end" THEN 100
30 l=LEN(s$)
40 FOR x=1 TO l
50 IF MID$(s$, x, 1) = "1" THEN px!=x:py!=y:GOTO 70
60 IF MID$(s$, x, 1) = "X" THEN maze(x,y)=1
70 NEXT
80 y = y+1
90 GOTO 12
92 REM Maze data
93 DATA XXXXXXXX
94 DATA X X X
95 DATA X X XX X
96 DATA X X
97 DATA X X1 XXX
98 DATA XXXXXXXX,end
100 SCREEN 1 : REM Set 320x200 quadcolor mode
102 DIM visible(xm,ym)
103 DIM renderlistx(xm*ym), renderlisty(xm*ym)
103 DIM floorlistx(xm*ym), floorlisty(xm*ym)
104 REM Center the observer in the tile he's standing on
105 px! = px!+0.5 : py! = py!+0.5
107 look! = 0.1 : REM Camera angle
108 sinlook! = SIN(look!)
109 coslook! = COS(look!)
110 REM With raycasting, figure out which walls stand in our view
111 FOR x=1 TO xm: FOR y=1 TO ym: visible(x,y)=0 : NEXT y,x
112 rendercount = 0 : floorcount = 0
113 CLS:KEY OFF:PRINT "Tracing..."
120 FOR angle! = pi!*-.5 TO pi!*.5 STEP pi!*(2/180)
130 REM Calculate the unit vector pointing to this direction.
131 REM Scale by 0.3 to ensure we're seeing everything we should.
140 xo!= SIN(angle! + look!) * 0.1
141 yo!=-COS(angle! + look!) * 0.1
150 distance = 0
160 REM Find an obstacle. Since we're in a closed maze, this loop is finite.
170 distance = distance + 1
171 xposint = INT(px! + distance * xo!) : REM INT truncates.
172 yposint = INT(py! + distance * yo!)
173 IF maze(xposint,yposint) > 0 THEN 180
174 IF visible(xposint,yposint) > 0 THEN 160 : REM Only add to floor-list once
175 visible(xposint,yposint) = distance
176 floorcount = floorcount + 1
177 floorlistx(floorcount) = xposint
178 floorlisty(floorcount) = yposint
179 GOTO 160
180 IF visible(xposint,yposint) > 0 THEN 190 : REM Only add to wall-list once
185 visible(xposint,yposint) = distance
186 rendercount = rendercount + 1
187 renderlistx(rendercount) = xposint
188 renderlisty(rendercount) = yposint
190 NEXT
195 CLS
220 REM Render each seen floor tile.
230 FOR wallno = 1 TO floorcount : GOSUB 900 : NEXT
240 REM Render each seen wall.
250 FOR wallno = 1 TO rendercount : GOSUB 300 : NEXT
260 REM IF INKEY$ = "" THEN GOTO 110 : REM loop
270 a$=INPUT$(1)
280 GOTO 9999 : REM end
300 REM Render the wall.
303 blockx=renderlistx(wallno)
304 blocky=renderlisty(wallno)
310 facecx!(0)=blockx :facecy!(0)=blocky+.5 : REM west wall
311 facecx!(1)=blockx+ 1:facecy!(1)=blocky+.5 : REM east wall
312 facecx!(2)=blockx+.5:facecy!(2)=blocky : REM north wall
313 facecx!(3)=blockx+.5:facecy!(3)=blocky+1 : REM south wall
314 FOR n=0 TO 3: GOSUB 700: NEXT : REM Draw walls.
399 RETURN
700 REM Draw a wall. Determine the two XY coordinates
701 REM that define the wall edges on the 2D map.
702 REM Note: We use the difference between INT and CINT on 0.5
703 REM value for benefit. 0.5 was previously used to indicate
704 REM a midpoint in a wall (lines 310-313). Neat trick.
710 cx! = facecx!(n) : x1! = INT(cx!) : x2! = CINT(cx!)
711 cy! = facecy!(n) : y1! = INT(cy!) : y2! = CINT(cy!)
720 REM Translate and rotate the coordinates around the player.
721 x! = x1!-px! : y! = y1!-py! : GOSUB 820 : x1! = x! : y1! = y!
722 x! = x2!-px! : y! = y2!-py! : GOSUB 820 : x2! = x! : y2! = y!
730 REM Put object somewhat front of the camera and apply a zoom to
731 REM avoid ugly distortions with lines that cross the camera plane
732 y1! = (-y1! + 2) * 0.5
733 y2! = (-y2! + 2) * 0.5
740 REM Calculate the screen coordinates. Start with X.
741 REM Y is determined by ceiling and floor....
742 x1 = 160 * (1 + x1! / y1!)
743 x2 = 160 * (1 + x2! / y2!)
744 y1c = 100 * (1 - 0.9 / y1!)
745 y2c = 100 * (1 - 0.9 / y2!)
746 y1f = 100 * (1 + 0.8 / y1!)
747 y2f = 100 * (1 + 0.8 / y2!)
750 REM Orient the wall.
751 IF x1 > x2 THEN SWAP x1,x2 : SWAP y1c,y2c: SWAP y1f,y2f
760 REM Render the wall.
766 REM Bound the loop
767 x = x1 : GOSUB 830 : x1b = x
768 x = x2 : GOSUB 830 : x2b = x
769 IF x1=x2 THEN 799 : REM Avoid division by zero in the loop
770 FOR x = x1b TO x2b
775 p! = (x-x1) / (x2-x1)
780 y2 = y1f + p! * (y2f-y1f)
781 y1 = y1c + p! * (y2c-y1c)
782 y1clip = y1: IF y1clip < 0 THEN y1clip = 0
785 IF POINT(x,y1clip) >= 2 THEN 795 : REM Poor man's zbuffer
788 IF x = x1 OR x = x2 THEN LINE(x,y1)-(x,y2),3:GOTO 795
789 PSET (x,y1),3 : PSET (x,y2),3
790 LINE (x,y1+1)-(x,y2-1),2
795 NEXT
799 RETURN : REM Done rendering that wall
800 REM This helper function calculates the distance of the wall.
801 a = facecx!(n) - px!
802 b = facecy!(n) - py!
803 facedist!(n) = a*a + b*b
804 RETURN
810 REM This helper function swaps the two wall specifications.
811 SWAP facedist!(a), facedist!(b)
812 SWAP facecx!(a), facecx!(b)
813 SWAP facecy!(a), facecy!(b)
814 RETURN
820 REM Rotate the coordinates to accommodate the camera's angle.
821 n! = x!
822 x! = n! * coslook! - y! * sinlook!
823 y! = n! * sinlook! + y! * coslook!
824 RETURN
830 REM Clipping horizontal coordinate to the screen range.
831 IF x < 0 THEN x=0
832 IF x > 319 THEN x=319
833 RETURN
900 REM Render the floor tile.
905 REM Get the tile coordinate, translate and rotate it around the player.
906 x! = floorlistx(wallno) + 0.5 - px!
907 y! = floorlisty(wallno) + 0.5 - py!
908 GOSUB 820
910 y! = (-y! + 2) * 0.5 : REM Fixup the z coordinate
915 REM Generate screen coordinates for the ceiling and the floor
920 x = 160 * (1 + x! / y!)
921 yc = 100 * (1 - 0.9 / y!)
922 yf = 100 * (1 + 0.8 / y!)
925 zs! = 2.5/y!
930 REM In the ceiling, draw a lighting device.
932 CIRCLE(x,yc),7*zs!, 1, ,, 0.1
935 CIRCLE(x,yc),2*zs!, 3
940 REM In the floor, draw a spot.
945 CIRCLE(x,yf),7*zs!, 1, ,, 0.1
999 RETURN
9999 SCREEN 0 : WIDTH 80,25 : REM Restore text mode
10000 END
10001 System error! Crash! Bing, bang!
10002 Funny fact: GW-BASIC is interpreted as it goes. That means you
10003 can include quite blatant syntax errors on any lines that are
10004 not executed, and it will do no harm whatsoever. Same goes for
10005 BAT files in DOS. However, you must still put a line number on
10006 each line, because otherwise the interpreter will complain about
10007 direct statements in the file.
9 PRINT "Reading map data..."
10008 Funny fact 2: Line numbers decide the order of program
10009 lines, not their location in the source code file.
100 PRINT "Done." : SCREEN 1 : CLS
10010 You can ever replace the previous content of the line
10011 by specifying the same line number twice.
Screenshot:
Now, who can find the most annoying bug in it?
(And who can build a game around it? :P )
Edit: Oh, and yes, I just wrote it, to refresh my GW-BASIC skills; I didn't pick it from archives ― back then, I couldn't even have programmed something like this.