sydney.lambda
2019-11-26 13:54:55

Hello :) I’m trying to work out how to code this routine (using the no-stack approach): https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker

does this look like I’m on the right track? I’d like to at least have something semi-decent before working on the printing code, as that itself could be a challenge for me. Makes testing mighty difficult, but hopefully someone more experienced can know if it’s close just by following the logic (the way I’ve been coding it):

(define (maze height width) (define grid (make-grid #:h height #:w width)) (define (forge-path grid point) (define (get-neighbors) (define y (point-y point)) (define x (point-x point)) (define (valid-neighbor? p) (and (> y -1) (< y height) (> x -1) (< x width))) (filter (conjoin (curry unvisited? grid) valid-neighbor?) (list (inc-y point) (dec-y point) (inc-x point) (dec-x point)))) (define (try-neighbors grid neighbors) (cond [(null? neighbors) (mark-visited grid point)] [else (try-neighbors (forge-path (mark-visited grid point) (random-neighbor neighbors)) (cdr neighbors))])) (try-neighbors grid (get-neighbors))) (forge-path grid (make-point #:y 0 #:x 0)))

thank you, hope my code was readable enough.


sydney.lambda
2019-11-26 14:09:56

The Grid is a struct containing: the grid itself, a vector of h*w, which contains: * Square structs, which have n s e w fields to denote which walls (paths) have been knocked down (forged) * a set containing coordinates of the squares which have been visited, stored as (y,x) Point structs.

I noticed that you only time you ever check the grid, is to see if a square has been visited or not. So, I figured it was better to just have a set of visited nodes and never actually index into the grid as such - just update it.


soegaard2
2019-11-26 14:11:33

I think you should make a printer for the grid - and use in, say, try-neighbors to see what is going on.


soegaard2
2019-11-26 14:11:57

Make it print the visited fields too.


sydney.lambda
2019-11-26 14:14:12

I couldn’t think of a way to do it in ascii, with each square being 1 tile (to save the upscaling in my head). Maybe I’ll just have to have it print the letter of the wall that was knocked down or something. Thanks :)


soegaard2
2019-11-26 14:15:13

Maybe use a terminal that supports colors?


sydney.lambda
2019-11-26 14:16:51

I’m using ncurses, but (not being an artist in the slightest) couldn’t think of a set of 4 tiles (indiscriminate of position on the grid) that could represent a square with either wall N,E,S,W knocked down, if that makes sense.


soegaard2
2019-11-26 14:28:31

soegaard2
2019-11-26 14:29:32

I think you can draw each box as four characters. It’s a bit tricky to pick the right characters though.


sydney.lambda
2019-11-26 14:30:51

Yeah, seems like using a single char per tile isn’t going to work. Just worried about all the moving parts involved that don’t involve the algorithm itself, but I’ll get there :)


soegaard2
2019-11-26 14:31:30

Hmm. I might be easier to draw the grid using pict.


sydney.lambda
2019-11-26 14:37:45

after struggling to come up with a set using only unicode characters combined with ncurses reverse attribute (and giving up at a square with only the side walls removed) I think you’re right ;)


sydney.lambda
2019-11-26 14:39:51

even the eight (unicode-supported!) trigrams of the i-ching seem to have been designed in a way so as to make this impossible. Just to spite me, naturally.


sydney.lambda
2019-11-26 15:27:49

for posterity, I found this ruby code online which takes a very nice approach:

puts " " + "_" * (width * 2 - 1) height.times do \|y\| print "\|" width.times do \|x\| print((grid[y][x] &amp; S != 0) ? " " : "_") if grid[y][x] &amp; E != 0 print(((grid[y][x] \| grid[y][x+1]) &amp; S != 0) ? " " : "_") else print "\|" end end puts end The bit-twiddling eludes me at the moment, but it appears that rather than thinking of each square as a… square, with potential missing lines, each square is thought of as either the left/right edge of another, or as the ceiling/floor of another:

___________________ \|___ \| \| ___ \| \| \| \| _\|___ \| \| \| \| \| \| \| \| \|___\|___\| \| \| _\|_\| \| \| ___ \| \| \|_______\| \|___ \|___\| \| _______\| \| \| \| \| \| _ \| _\| \|___\| \| \| \| \| _\| \| \|_ \| \| \|___\|_____\| \|___\| \| \|___________\|_______\|


mark.warren
2019-11-26 15:29:18

Nice idea


sydney.lambda
2019-11-26 15:31:13

It really is. I’ll save the “thinking outside the box” related puns for the Christmas crackers ;)



samdphillips
2019-11-26 18:17:00

I used pict for rendering


samdphillips
2019-11-26 18:19:16

IIRC that is the “Growing Tree” algorithm it can be configured a bit http://weblog.jamisbuck.org/2011/1/27/maze-generation-growing-tree-algorithm


samdphillips
2019-11-26 19:14:41

For drawing the maze I cheated and just draw the empty space not the walls.


sydney.lambda
2019-11-26 19:54:59

@samdphillips That’s great! I just finished translating that same version (https://gist.github.com/dys-bigwig/ee7e08ffff14416f280f121c130c238b), and was about to ask how I could make it more idiomatic and less of a naive procedural->functional conversion, but you beat me to it! Thank you :)


samdphillips
2019-11-26 19:56:37

There is a bit of imperative-ness in tracking visited cells, but it’s hidden inside of the generation function.


sydney.lambda
2019-11-26 20:00:36

It’ll probably take a while to wrap my head around your code, but I notice the use of streams. IIRC there’s a section in SICP (about the queens problem?) that uses streams as an alternative to backtracking.


samdphillips
2019-11-26 20:01:20

I don’t think it needs streams. I probably expecting a larger dataset for some reason.


samdphillips
2019-11-26 20:04:06

So I was probably trying to save some cons’ing by using a stream. But I think a stream is just about equivalent in that case (a short 4 element list/stream)


samdphillips
2019-11-26 20:04:29

It probably should be a sequence which might be better.


samdphillips
2019-11-26 20:06:02

Unless you’re generating very large mazes it probably doesn’t make a big difference.


sydney.lambda
2019-11-26 20:09:19

Just a very modest roguelike, so I shouldn’t think so.


samdphillips
2019-11-26 20:13:42

The backtracking that occurs is mostly a function of keeping a list of cells that need to be visited.


sydney.lambda
2019-11-26 20:17:17

That was what stumped me for some time. The link between in-place updating of the grid, and having the result of the recursive call on the neighbour be “available” to the initial call (ala a fold) in order to know which cells have been visited wasn’t something immediately obvious to me at all. Sorry, bit hard to put in words, hope it makes some sense.


sydney.lambda
2019-11-26 20:20:35

Despite usually finding mutation more confusing, I feel like in this sort of situation the imperative way matches how you imagine it “working” in your head. At least, for a non-mathematically-minded person such as myself ;)


sydney.lambda
2019-11-26 20:25:53

“Okay, cross this one off, move on to that one; I’ll keep a list of the ones I should come back to in the event I hit a dead-end” is a bit more natural in some ways than “right, before I finish this one, I’ll do the entire process first on this neighbor. But first, I’ll keep a list of remaining neighbors, remembering however to remember which of those I’ve crossed off when I come back after having done that neighbor I chose to finish first.” or…something like that.


samdphillips
2019-11-26 21:26:47

Yeah it’s a little easier if you’ve written a few different graph traversals over the years


sydney.lambda
2019-11-26 22:00:33

@samdphillips If you don’t mind me asking, any particular projects that spring to mind that you’ve used similar graph traversals for in the past?


samdphillips
2019-11-26 22:01:50

Garbage collectors generally use graph traversal


samdphillips
2019-11-26 22:02:53

Doing filesystem scans are technically a kind of graph traversal


sydney.lambda
2019-11-26 22:03:57

I suppose if you squint, the paths between grid squares could be seen as references to objects held by other objects? Or I’m completely off haha. Thanks either way, good to know that learning this could help with creating languages one day in the distant future :)


samdphillips
2019-11-26 22:06:12

Yes that’s it. Except you generally don’t pick a random path between nodes.