Fog of War
Track which cells the player has seen with a visibility array — unseen areas stay hidden in darkness.
The viewport limits what the player sees at any moment, but it does not track what they have seen before. Without fog of war, every cell the player walks past is visible the moment it enters the viewport — and vanishes when it leaves. With fog of war, cells start hidden and are revealed permanently when the player moves close enough. The dungeon becomes a space to discover, not just traverse.
The Program
5 REM === DUNGEONS OF DORIN ===
10 BORDER 0: PAPER 0: INK 7: CLS
20 PRINT AT 3, 4; INK 7; BRIGHT 1; "DUNGEONS OF DORIN"
30 PRINT AT 6, 2; INK 6; "Descend ten floors of"
35 PRINT AT 7, 2; INK 6; "darkness. Find the Heart."
40 PRINT AT 10, 2; INK 7; "Q/A = up/down"
42 PRINT AT 11, 2; INK 7; "O/P = left/right"
44 PRINT AT 14, 2; INK 7; "Press any key to begin"
50 PAUSE 0
60 CLS
70 REM === define stats ===
80 LET h = 20: LET z = 20: LET a = 3: LET f = 2
90 REM === generate floor ===
92 DIM d(16,16): DIM v(16,16)
94 GO SUB 1000
96 LET x = u(1,1) + 1: LET y = u(1,2) + 1
98 GO SUB 380
100 GO SUB 300
102 REM === main loop ===
104 PAUSE 0: LET i$ = INKEY$
106 LET t = x: LET t2 = y
108 IF i$ = "q" OR i$ = "Q" THEN LET t2 = y - 1
110 IF i$ = "a" OR i$ = "A" THEN LET t2 = y + 1
112 IF i$ = "o" OR i$ = "O" THEN LET t = x - 1
114 IF i$ = "p" OR i$ = "P" THEN LET t = x + 1
116 IF t = x AND t2 = y THEN GO TO 102
118 IF t < 1 OR t > 16 OR t2 < 1 OR t2 > 16 THEN GO TO 102
120 IF d(t2,t) = 1 THEN GO TO 102
122 LET x = t: LET y = t2
124 GO SUB 380
126 GO SUB 300
128 GO TO 102
300 REM === draw viewport ===
302 FOR c = -3 TO 3
304 FOR o = -3 TO 3
306 LET t = x + o: LET t2 = y + c
308 LET i = 1: LET i2 = 0
310 IF t < 1 OR t > 16 OR t2 < 1 OR t2 > 16 THEN GO TO 340
312 IF v(t2,t) = 0 THEN GO TO 340
314 LET i = d(t2,t)
316 LET i2 = 7
318 IF i = 1 THEN LET i2 = 4
340 LET t3 = 3 + c: LET t4 = 11 + o
342 IF o = 0 AND c = 0 THEN LET i2 = 7: LET i = 6
344 IF i2 = 0 THEN PRINT AT t3, t4; " ": GO TO 370
346 IF i = 0 THEN PRINT AT t3, t4; INK i2; ".": GO TO 370
348 IF i = 1 THEN PRINT AT t3, t4; INK i2; CHR$ 143: GO TO 370
350 IF i = 6 THEN PRINT AT t3, t4; INK i2; "@": GO TO 370
352 PRINT AT t3, t4; INK i2; "?"
370 NEXT o: NEXT c
372 RETURN
380 REM === update visibility ===
382 FOR c = y - 2 TO y + 2
384 FOR o = x - 2 TO x + 2
386 IF c >= 1 AND c <= 16 AND o >= 1 AND o <= 16 THEN LET v(c,o) = 1
388 NEXT o: NEXT c
390 RETURN
1000 REM === generate floor subroutine ===
1002 FOR i = 1 TO 16: FOR o = 1 TO 16
1004 LET d(i,o) = 1: LET v(i,o) = 0
1006 NEXT o: NEXT i
1010 LET s = INT (RND * 3) + 3
1012 DIM u(5,4)
1014 FOR i = 1 TO s
1016 LET u(i,1) = INT (RND * 10) + 2
1018 LET u(i,2) = INT (RND * 10) + 2
1020 LET u(i,3) = INT (RND * 4) + 3
1022 LET u(i,4) = INT (RND * 4) + 3
1024 FOR c = u(i,2) TO u(i,2) + u(i,4) - 1
1026 FOR o = u(i,1) TO u(i,1) + u(i,3) - 1
1028 IF c >= 1 AND c <= 16 AND o >= 1 AND o <= 16 THEN LET d(c,o) = 0
1030 NEXT o: NEXT c
1032 NEXT i
1034 FOR i = 1 TO s - 1
1036 LET t = u(i,1) + INT (u(i,3) / 2)
1038 LET t2 = u(i,2) + INT (u(i,4) / 2)
1040 LET t3 = u(i+1,1) + INT (u(i+1,3) / 2)
1042 LET t4 = u(i+1,2) + INT (u(i+1,4) / 2)
1044 IF t <= t3 THEN FOR o = t TO t3: IF t2 >= 1 AND t2 <= 16 AND o >= 1 AND o <= 16 THEN LET d(t2,o) = 0
1046 IF t <= t3 THEN NEXT o
1048 IF t > t3 THEN FOR o = t3 TO t: IF t2 >= 1 AND t2 <= 16 AND o >= 1 AND o <= 16 THEN LET d(t2,o) = 0
1050 IF t > t3 THEN NEXT o
1052 IF t2 <= t4 THEN FOR c = t2 TO t4: IF c >= 1 AND c <= 16 AND t3 >= 1 AND t3 <= 16 THEN LET d(c,t3) = 0
1054 IF t2 <= t4 THEN NEXT c
1056 IF t2 > t4 THEN FOR c = t4 TO t2: IF c >= 1 AND c <= 16 AND t3 >= 1 AND t3 <= 16 THEN LET d(c,t3) = 0
1058 IF t2 > t4 THEN NEXT c
1060 NEXT i
1062 RETURN
How It Works
Line 20 dimensions the visibility array v(16,16) alongside the dungeon array. Every cell starts at 0 (unseen).
Line 44 resets visibility when a new floor is generated, setting every cell to 0.
Lines 380-390 are the visibility update subroutine. When called, it marks all cells within 2 steps of the player position as seen (value 1). The nested loops run from y-2 to y+2 and x-2 to x+2, covering a 5x5 area centred on the player.
Line 312 in the viewport renderer checks the visibility array before drawing a cell. If v(t2,t) is 0, the cell is unseen and renders as blank space regardless of what the dungeon array contains.
Two Arrays, One World
The dungeon now uses two parallel arrays. d(16,16) stores what each cell contains — walls, floors, stairs, chests. v(16,16) stores whether the player has discovered each cell. The rendering code checks both: first visibility (should this cell be drawn at all?), then content (what should it look like?).
This dual-array pattern is common in games with fog of war. The content array is the ground truth — it holds the complete state of the world. The visibility array is the player view — it controls what information is revealed. Separating knowledge from content lets the game hide things that exist in the world but have not yet been found.
Reveal Radius
The visibility update reveals a 5x5 area around the player. This is slightly smaller than the 7x7 viewport, which means the outer ring of the viewport shows unseen cells even after the player has stood in the centre. The player must move to the edge of their visible area to reveal what lies beyond.
A larger reveal radius would make exploration faster but less tense. A smaller one (3x3) would force the player to walk right up to every wall before seeing it. The 5x5 radius is a balance between pace and mystery.
Try This
Expand the reveal radius. Change the loops in lines 382-388 to run from -3 to +3 for a 7x7 reveal area that matches the viewport. How does this change the feel of exploration?
Add a “darkness” indicator. Count how many cells remain unseen and display the number. As the player explores, the count decreases — a measure of progress.