A random dungeon generator that fits on a business card
twitter.com
twitter.com
It reminds me that once upon a time the roguelike dev newsgroup had a "1k (source code) roguelike challenge".
The thread is here [1] and submissions are here in a slightly annoying format [2] but sadly I can't find the entry I remember, which I thought was in C and actually quite playable, if slightly at the mercy of the random number generator to make each new level solvable. Perhaps it was for a later iteration of the same challenge.
[1] https://rec.games.roguelike.development.narkive.com/3tm7xGpn... [2] https://sites.google.com/site/1024brl/
https://gist.github.com/femto113/28f69626acddc70a002ecead0b3...
Just expands the macros/typedefs and applies standard indenting, code is otherwise same as the original.
https://gist.github.com/munificent/ce8f7a9e6b09938ca8d2d43fa...
(defn maze [n]
(->> (repeatedly (* n n) #(rand-nth "╱╲"))
(partition n)
(transduce
(comp
(map #(apply str %))
(interpose "\n"))
str)))
Simpler output but related and also quite interesting!Here's some modified code that runs on my machine if pasted directly into bash:
gcc -x c - <<EOF
#include <time.h> // Robert Nystrom
#include <stdio.h> // @munificentbob
#include <stdlib.h> // for Ginny
#define r return // 2008-2019
#define l(a, b, c, d) for (i y=a;y\
<b; y++) for (int x = c; x < d; x++)
typedef int i;const i H=40;const i W
=80;i m[40][80];i g(i x) {r rand()%x;}
void cave(i s){i w=g(10)+5;i h=g(6)+
3;i t=g(W-w-2)+1;i u=g(H-h-2)+1;l(u-
1,u+h+2,t-1, t+w+2)if(m[y
][x]=='.') r;i d=0;
i e,f; if(!s){l(u -1,u+h
+2,t-1 ,t+w+2){i s=x<t|| x>t+
w; i t=y<u|| y> u+h;
if(s ^t&& m[ y]
[x ]=='#' ){d++; if(g (d
) ==0) e=x,f=y; }}if (d
== 0)r; }l(u-1,u +h+2 ,t
-1 ,t+w +2){i s= x< t ||
x> t+w; i t= y<u ||y> u+
h; m[y] [x]= s &&t? '!'
:s^t ?'#' :'.'
;}if (d>0)m [f][
e]=g(2 )?'\'':'+';for(i j=0;j<(s?
1:g(6) +1);j++)m[g(h)+u][g(w)
+t]=s?'@' :g(4) ==0?
'$':65+g(62) ;}i main(i
argc, const char* argv[]) {srand((i)
time(NULL));l(0, H, 0,W)m[y][x]=' ';
for(i j=0;j<1000;j++)cave(j==0);l(0,
H,0,W) {i c=m[y][x]; putchar(c=='!'?
'#':c);if(x==W-1)printf("\n");}r 0;}
EOF./a.out
###############
#.......^...$.#
################### ############## #.....Z.......#
#..$...#..........# #............# #.....e.......#
#..a...#........$.# #...$....$...# ############.............#
#......#.}........# ###########....y.......# #.........#######+########
#......#..........# #........##............# #.........##....~.#
#......#..........# #........########'###### #....d....##......#
#......+..........# #H....^$.#.....$...# #.........##......#
###########'####### #........+c......O.# #.........##......#
#...........# ##########.........# #...$.....##.z$...#
#.....x.$...# #.........# #.........##......########
#...........# #.....Q...# #.........##Q.....+......#
#...........# ########### #.........# #.........##......#......#
######'######### #.........# #.........# ###########'###+######......#
#........`.# #.....H...# #.........# #...............# #.C....#
#..R.......# #.........####+############~........P.....# #..d...#
#..........# #..{......#...............#...............# #......#
#..$.......# #.........#...............#....t..........# #......#
#..........# #.........#...............#...............# #...`V.#
#######+###### #.........+...............#...............# #......#
#....$...# #.........#...............+....v..........# ########
#fT......# ###########....@..........#...............#
#........# #...............#########'#####'############
#...H....# #...............# #......#..............#
#........# ########+#######'####### #..d.b.#..............#
#........# #...X...s.# #....$.# #.J....#..............#
#######'#'##############..|....E.# #...]a.# #......#..............#
#.......#..............+$........# #......# ########..............#
#.......#..............#.........# #......# #..............#
#.......#......~.......#########'# #......# #$.............#
#.......#..v...........# #...\...# #......# #..............#
#n......#........}.....# #.......# #......# #..............#
#....$..#......`.......# #....u$.# #......# ################
#...r...#..............# #.......# #......#
#.......#..............# ######### ########
#########..............#
################Online version: https://jsfiddle.net/dfu7ws69/
To try and understand it I reformated it to make it somewhat readable:
https://gist.github.com/femto113/28f69626acddc70a002ecead0b3...
>// Probably plus means a locked door, and single quote means unlocked, or the other way around.
http://angband.oook.cz/stuff/manual.txt gives
' An open /broken door
+ A closed door
$ Gold or gems
A Angelic being
~ Light sources, Tools, Chests, Junk, Sticks, Skeletons, etc
https://gist.github.com/ctsrc/fef3006e1d728bb7271cff0656eb02...
Meanwhile others have posted fully deobfuscated versions that the original author and someone else has published in the past. Oh well :P
The link above goes to the first revision.
Here is a link to the most recent revision at all times:
https://gist.github.com/ctsrc/fef3006e1d728bb7271cff0656eb02...
I think at this point most of it is pretty understandable.
https://gist.github.com/ctsrc/fef3006e1d728bb7271cff0656eb02...
Description of bug, which I also posted as a comment on the OP gist:
> Player and other entities will never be placed on the rightmost column of the room floor, nor on the bottom-most row of the room floor. See https://gist.github.com/ctsrc/fef3006e1d728bb7271cff0656eb02... [...]. In my refactored version of your code the bug is explained at the line I linked to in this comment.
https://gist.github.com/munificent/b1bcd969063da3e6c298be070...
https://gist.github.com/airstrike/66e0152e75c3a81fd1496bfbf5...
This one is great: https://gist.github.com/airstrike/66e0152e75c3a81fd1496bfbf5...
A couple of remarks:
// The door should not be created in the cave's corner or over
// another door, or in another cave's corner. It's impossible
// to make a cave without a door, because randInt(1) always
// returns 0.
if (atWallButNotAtCorner && FIELD[y][x] == TILE_WALL) {
doorCounter++;
if (randInt(doorCounter) == 0) {
doorX = x;
doorY = y;
}
}
The randInt() part here is pretty confusing. Here's the intent of the code. It picks a random boundary for the new room. Then it walks over the edges and finds every tile where the room's wall overlaps the wall of an existing room. Those are candidates where a door can be placed to connect this room to the existing one.If no candidates are found, the room is discarded. This ensures the dungeon is always connected.
If there are multiple candidates, we only need to pick one. We want to pick one randomly because otherwise you'd get obviously biased choices where the door always appeared at the left-most edge between two rooms or something. The obvious way to do that is to build a list of the candidate coordinates and then choose a random element from the list.
But that's a lot of code. Instead, I use Algorithm R [0]. It's a streaming algorithm for selecting a random item as you walk the set of items being sampled. The idea is that you keep a running winner. Each new element, you have a random chance of replacing the winner. As the number of elements visited increases, the chances of replacing the winner decreases. So the first element has a 1/1 chance of being the winner. The second has a 1/2 chance of replacing the winner, the third 1/3, etc.
// If the cave's walls were made completely out of corners
// and doors, don't make such a cave
if (doorCounter == 0) { return; }
This case actually means the new room didn't share a wall with any existing room. // We need to somehow record corners of all caves to check
// for intersections later, so we use a special tile for it
FIELD[y][x] = atCorner
? TILE_CORNER
: (atWallButNotAtCorner ? TILE_WALL : TILE_FLOOR);
For a room to connect to an existing one, they need to share a tile on their actual sides, like: #####
#...#
#...#####
#...X...#
#####...#
#...#
#####
That's leaves at least one tile where we can place a door. If only the corners overlap, there may not be enough room to connect them: #####
#...#
#...#
#...#####
#####...# ???
#...#
#...#
#####
#####
#...#
#...#
#...#
######### ???
#...#
#...#
#...#
#####
So, during room placement, the corners are not treated as part of the room: ###
#...#
#...####
#...X...#
####...#
#...#
###
###
#...#
#...#
#...####
####...# ???
#...#
#...#
###
###
#...#
#...#
#...#
### ### ???
#...#
#...#
#...#
###
But, when rendering the rooms, I want them to look rectangular. So the special "!" means "render like a wall, but don't act like a wall". // 1d6 of entities total, 25% chance of gold, 75% of a mob.
// Mob letters range from 'A' to '~', inclusive
Technically, the "mob" case includes all of the non-"$" treasure too. Punctuation characters are in that character range as well and represent items.Otherwise, the comments are all spot on. It's really gratifying seeing people figure this all out.
[0]: https://en.wikipedia.org/wiki/Reservoir_sampling#Algorithm_R
I want to see someone try to rewrite C++'s entire syntax in the form of another language (I just have to assume it has already been done.)
#define r return
#define l(a, b, c, d) for (i y = a; y < b; y++) for (int x = c; x < d; x++)
typedef int i;The "l" macro cut the code down significantly and is one of the tricks I'm most proud of.
"r" and "i" aren't that impactful in terms of length. Their main benefit is that they make it easier to split the code where I need to in order to render the big ASCII art "@" sign. That's a lot harder when you have multiple-letter identifiers that can't be split in the middle.
C is used rather than C++, because C++ is a car crash before you even start trying to obfuscate.
https://github.com/samboy/rg32hash/blob/master/C/tinyrg32.c
The file has test cases, documentation:
https://github.com/samboy/rg32hash/blob/master/C/tinyrg32.md
and I even have a 12-page de-obfuscation of an earlier version of this program:
https://github.com/samboy/rg32hash/blob/master/C/nanorg32.md
(I have since then come up more size optimizations)
https://gist.github.com/donarb/1502a12cb16fa74f4fd2e295867da...
It's nice as it is: just someone having fun coding with self-imposed constraints (in this case: doing something interesting with a really small card/code size).
Same as the business card raytracer :)
import random as r
print(''.join([r.choice(["/", "\\"]) for _ in range(9999)]))
Output: //\\/\//\\/\\\\/\\\/\//\\\\/\\/\\/\///\\\\///\\/\//\\\\/\\/\
/\\\\/////\\\\\/\\\\////\/\//////\////\\\//////\\\\\/\//\\\/
//\\\\\\/\/\\//\/\\\\///\\/\\//\\///\\/\/\\//\/\\/\///\\\\//
\\/\//\\\//////\/\/\/\\/\\//\/\///\\///\/\////\//\/\\\/\/\//
\\\\\\\\\/\//\\\//\\\/\\/\\\\\/\\\\\///\/\\/\\\\//\\/\\/\///
//\\/\//////////\\//\\\////\\/////\/\/\\\\\\\\///\/\\/\///\/
\///\//\\//\\\/\/\\\\\//\\\\\\//\/\\//\\\/\/\/////\/\//\/\//
/\\\\//\\\\/\\///\\\\/\\\/////\\\\\\//\///\//\\////\//\/\\\/
/\\/\\\//\/\/\//\//\//\\\//\\\\\\\/\\\/\\\\/\\///\/\/\//\///
\\/\\/////\/\\\///\\\/\\/\//\/\/\/\\\//\\\\\\\\/////\\/\\/\/
//\\\\\\\\/\\/////\\/\\\\\///\/\\/\\\/\\//\\\\\\/\\/\\\/////
\\//\\\\/\\/\\///\///\/\\\/\/\/////\//\\//\/\/\/\/\/\/\\/\//
\//\\\\\\///\/\\/\\\\/\\\/\/\/\/\//\////\/////\\\/\/\\/\\\\/
//\\\/\\//\\/\\/\/\\////\/\\//\/\/\///\//////\\///////\/\\//
\/\////\/\//\\\\//\\/\\/\\\\/\/////\///\/\\/\///\\\\/\\\//\/
\///\\/\\/\\\\\\/\\\\\\\/\\\///\\\/\\///\\\\//\/\/\/\\//\/\\
/\\\////\/\//\/////\\///\\//\/\\\/\/\\\/\/\\\\\////\\/\\////(Note that this version of the original is half the size of this Python adaptation!)
import random
print(''.join(random.choice("/\\") for _ in "x"*9999))
I was surprised to discover that "import random\nrandom.choice", "import random as r\nrandom.choice", and "__import__("random").choice" are all exactly 27 bytes, so no byte count is saved by preferring any of these forms over another!This is kind of a pet peeve of mine, but if you post simple, standalone code you really ought to post the compiled executable(s) as well. I don't currently have a C compiler installed on my home Windows box.
But more seriously, this is a toy implementation and the value is in reading it to see how it works, not in seeing its output
But you have to use the modified code in the comments above or you will get a : main.c:8:7: error: variably modified ‘m’ at file scope =80;i m[H][W];i g(i x) {r rand()%x;}
I swear the URL wasn't chosen by me.
If it did require specific hardware or software, that would be different, which is why I specifically said "simple, standalone code."