Enter the Dungeon 💀 ...if You Dare!

The Imaginary 3-D Dungeon Simulator of the Year, 1983...

NOW ON THE WEB!!

http://hostilefork.com/media/shared/replpad-js/?do=dungeon

Source code:

https://github.com/hostilefork/teenage-coding/blob/master/DUNGEON/dungeon.reb

Backstory

When I was a kid (circa 1983, I'd be about 8 or 9), I played the Intellivision game Advanced Dungeons and Dragons: Treasure of Tarmin. The 3-D graphics put you in a first-person-perspective point of view with shocking realism:

Shockingly Realistic 3-D Graphics

But a year or two later, I got a C-64. And I was able to draw on the 40x25 character grid by cursoring around the screen, setting the color with the Ctrl key and a digit, and putting symbols anywhere I wanted (why doesn't bash let me do that?). The character set had triangular components and solid block components.

So I was able to reason through how one might generate a rendering of one's perspective in a grid through that medium.

In 2013, I found the nearly-three-decades-old spec, in spiral-bound notebook paper, about "Dungeon Construction Set":

enter image description here

(Careful readers will notice that while it's almost right, this doesn't quite hold together on the slanted parts...)

Though Treasure of Tarmin was played on a grid, the walls existed only on the edges of grid squares. Having learned what bytes were, I realized that if I made the map out of bytes...then each square on the map could have four possible states for each of its edges:

  1. Unobstructed
  2. Wall
  3. Door
  4. Something Else?

The design allows for the opportunity that adjacent cells would "disagree"...e.g. have a wall on one side and a door on the other. I considered this a feature; these situations would give rise to one-way doors and walls that didn't appear until you passed through them and then turned around.

My idea was that a position on the map and a direction you were facing could "light up" regions on the screen...a bit like lighting up segments of a digit on an LCD.

But an implementation of the ideas eluded me. It was a trickier program than I had written at the time, made even trickier because I was trying to implement it in a "machine language monitor". I had no assembler, and the book I read didn't discuss them...just the instruction set and workings of the 6502 chip.

(The machine-language-monitor programs could encode single instructions into specific memory locations. But unlike an assembler, it had no labels or other abstractions...all addresses had to be kept track of on paper.)

By the time I knew enough about programming that I could do something like this in an evening, I no longer cared much about the idea. But better late than never, right?

Although Unicode/ASCII don't have good "seamless drawing" characters to do this like the C-64 could, it can still get the idea across.

Originally Wrote this in Red (2013)

I decided I'd use this relatively simple task as something where I would use Red the whole time--as opposed to writing it in Rebol2 or R3-Alpha and then porting the differences. So that let me get a sense of where the pain points were regarding things like syntax error reporting, that sort of stuff.

Anyway, about a decade later, it's now a Ren-C sample. Minor modifications were made, including taking advantage of the ReplPad's CLEAR-SCREEN ability (if running on the web).

3 Likes

It's probably worth mentioning what I had to change:

  • I put RETURN: at the beginning of the parameter lists.

    • This isn't yet a rule, but I think it might need to be. This is in flux.

    • One issue we have is how ENCLOSE'd functions might widen the return conventions. That might involve specializing out the old RETURN and augmenting with a new one.. which means the "active return" (the one typechecked for the enclose phase) might not be the 0th parameter.

  • Slashes changed to dots for member access. I am pretty gung ho about what differentiating refinements and member selection does for readability. And of course, it looks better when you're using division:

    start-pos: reduce [  ; red version
        1 + (display/screen-size/1 / 2) - (dims/1 / 2) + (dims/1 * x-offset)
        1 + (display/screen-size/2 / 2) - (dims/2 / 2)
    ]
    
    start-pos: reduce [  ; Ren-C version
        1 + (display.screen-size.1 / 2) - (dims.1 / 2) + (dims.1 * x-offset)
        1 + (display.screen-size.2 / 2) - (dims.2 / 2)
    ]
    
  • It uses fail msg instead of (print msg) quit

  • It uses ELSE with SWITCH instead of SWITCH/DEFAULT

  • The Rebol2/Red form of REPEAT takes a variable and a count, so repeat x 10 [...] would run the body 10 times for the values 1 to 10.

    • In Ren-C that is now for x 10 [...], where the idea is that FOR speaks in the "for dialect" by default (or can be passed a generator function). It's pretty wide open for what that will be able to be, but the base case of an integer is just to count from 1 to that.

      • for x [1 to 10] [...]
      • for x [1 thru 10] [...]
      • for x 1..10 [...]
      • for x each [a b c] [...] ; each makes a generator function
    • REPEAT is what was called LOOP (which saves LOOP for some great loop dialect yet to be made, but a place is held for it to be inspired by CommonLisp's LOOP)

It's a deliberately simple program, and it does not make use of PAIR! and its X and Y fields (possibly because Red didn't support it at the time? or maybe it was and I didn't use it).

It builds empty strings with loops instead of using APPEND/DUP or some similar construct (also, potentially because Red didn't have /DUP yet).

Future Features...?

I'm hesitant to go on any kind of enhancement kick for this. But it could mock up a more C-64 looking square-with-slants screen in the JavaScript canvas, and do the dark green/light green alternations of Treasure of Tarmin. Could add the blue doors...

Switching to a READ-KEY interface where you can use cursor keys and not get echo would mean adding the READ-KEY features to ReplPad. I like having the fallback so it runs in a stdio terminal that doesn't have anything but echoing input and irreversible printing. So it should do feature detection and work in both (kind of like how it does feature detection of CLEAR-SCREEN).

If there are any takers who want to improve it (or the ReplPad features to enable it), be my guest. :slight_smile:

1 Like

Wanted to see Wasm Ren-C in action.

All I get is "You are at location [1 1] facing north"

Fixed it. See also:

Visual PARSE on the Web... has arrived!

Ideally we'd have tests to make sure these stay running, but...

The bug here was a consequence of an obscure code trick that isotopic ~true~ and ~false~ broke.

There is a function in %dungeon.reb called IN-BOUNDS that clips the passed in position to a boundary, but also returns true or false as to whether the position needed clipping.

This gave rise to a strange REDUCE, whose purpose was to work around the short-circuiting nature of ALL:

all-inside: did all reduce [
    in-bounds start-pos [1 1] display.screen-size
    in-bounds end-pos [1 1] display.screen-size
]

By doing a REDUCE step to produce a BLOCK! of two LOGIC! and running the ALL on that, there's no short circuiting of the calls to IN-BOUNDS.

You can't do this under the isotopic definition of ~true~ and ~false~, because isotopes can't be put in blocks. You'd need to META them:

let all-inside: did all reduce [
    meta in-bounds start-pos [1 1] display.screen-size
    meta in-bounds end-pos [1 1] display.screen-size
]
...
return all-inside

This would give you quasiform ~true~ and ~false~ in the REDUCE step, which would then evaluate to the isotopic forms during the evaluation with the ALL.

(If words like "isotopic" and "quasiform" seem alien, please read this: A Justification of Generalized Isotopes)

Anyway, rather than just make a puzzling piece of code more puzzling, I just simplified it:

let start-inside: in-bounds start-pos [1 1] display.screen-size
let end-inside: in-bounds end-pos [1 1] display.screen-size
...
return start-inside and end-inside

(I'll mention that there is consideration of a non-short-circuit ALL called EVERY which receives the product of each evaluation and succeeds only if all of the steps do. I haven't really used it, and it is potentially on the chopping block under its current formulation.)