lispjam-autumn-2024/notes.md
2024-10-30 22:29:10 -04:00

6.9 KiB

First, establish some of the cell data

; This will result in a "2x2" cell, of [0 1 1 1], with 0 representing an empty
; space, and 1 representing a wall.
(var (hall-width wall-width) (values 1 1))

; This sets the "map width". This means 10 cells across and down. With each cell
; being 2x2, this results in a 20x20 square map. All maps will be square (for
; now).
(var num-cells 10)

; Explicitly setting cell-size for convenience. All cells are squares
(var cell-size (+ hall-width wall-width))

Next, generate the initial array of cells to work with.

(var cells {})
(for [i 1 (* num-cells num-cells)]
    (table.insert cells {
        :n false :s false :e false :w false 
        :v false 
        :o false
        }))
`:n`, `:s`, `:e`, `:w` - if cell has a directional connection
`:v` - has the cell been visited
`:o` - does the cell contain an obstacle

With the cells established, can use a recursive depth-first search with a stack to generate the maze.

For ease, we'll always start along the top. Eventually, want to ensure the solution is along the sides or bottom.

(var cell-stack [(math.random 1 num-cells)])

(while (> 0 (length cell-stack)
    (var current-cell (table.remove cell-stack))
    (tset cells current-cell :v true)

    ; Check if any of the current cell's neighbors are
    ;   1. unvisited
    ;   2. a side/barrier wall
    (var next-cells {})

    ; North - 
    ;   if current-cell <= num-cells, then north is a map-side/barrier
    (when (> num-cells current-cell)
        (var n-cell (- current-cell num-cells))
        (if (not (. cells n-cell :v)) (table.insert next-cells n-cell)))

    ; South - 
    ;   if current-cell > (- (* num-cells num-cells) num-cells), 
    ;       then south is a map-side/barrier
    (when (< current-cell (- (* num-cells num-cells) num-cells))
        (var s-cell (- (* num-cells num-cells) num-cells))
        (if (not (. cells s-cell :v)) (table.insert next-cells s-cell)))

    ; East -
    ;   if current-cell % num-cells = 0, then east is a map-side/barrier
    (when (not (= 0 (% current-cell num-cells)))
        (var e-cell (+ current-cell 1))
        (if (not (. cells e-cell :v)) (table.insert next-cells e-cell)))

    ; West -
    ;   if current-cell % num-cells = 1, then west is a map-side/barrier
    (when (not (= 1 (% current-cell num-cells)))
        (var w-cell (- current-cell 1))
        (if (not (. cells e-cell :v)) (table.insert next-cells w-cell)))


For output:

Each cell is then used to generate values in the map array. Each cell will have either a hallway or a wall at each position, for cell-size positions.

Iterate through each cell. Cells 1 through num-cells are the first row, then (1 + num-cells) through (num-cells * 2), and so on. This can be generalized and extrapolated to (x + (num-cells * (y - 1))) through (num-cells * y), for x and y loops from 1 to num-cells.

(for [i 1 num-cells]
    (for [j 1 num-cells]

Each cell is of uniform size, and the map is a square that divides evenly by that size. We established the width, and that value squared is the map. Every cell is numbered from 1 to (* num-cells num-cells) (ie., 1 to 100). The index of the cell divided by num-cells, plus 1, gets the row:

(fn row [i] (+ 1 (// i num-cells)))

This gets the "upper-left" for the map array, given a cell index.

(fn c [i] (+ (- (* i cell-size) 1) (* cell-size cell-num (- (row i) 1))))

Using that index, iterate through the cell based on cell-size twice, using each loop to add either (- i 1) and (* (- j 1) cell-num) to the value from c above, and insert it into the map array.


For cell generation:

Each cell is a combination of hallways and walls. For each cell, if the "row" or "column" is less than the hall-width, enter a "0", otherwise enter a "1".

(var cell [])
(each [k v (ipairs meta-cells)]
    (var row-limit (if (. v :e) (+ hall-width wall-width) hall-width))
    (var col-limit (if (. v :s) (+ hall-width wall-width) hall-width))
    (for [i 1 cell-size]
        (for [j 1 cell-size]
            (var cell-index (+ j (* (- i 1) cell-size)))
            (if (and (< i (+ col-limit 1)) (< j (+ row-limit 1)))
                    (tset cell cell-index 0)
                    (tset cell cell-index 1))
            (if (and (> i hall-width) (> j hall-width))
                    (tset cell cell-index 1))))

This becomes a bit more challenging with considering connections. It is likely a matter of modifying the conditional such that: when there is a horizontal connection, check for i to be less than the entire cell width (hall-width + wall-width); and then similar for j with vertical connections. However, this will then remove the corner wall when there are both vertical and horizontal connections. This could be solved by checking if both i and j are beyond hall-width, which would represent the always corner.

The above code should translate from a meta-cells list of meta-data to a cells list of 0's and 1's.


With a bit of modification, the logic for the maze generation from above appears to work. There is one challenge which remains, which is updating the walls. A way around this is to make the data about the next cell more verbose: include not only the cell number, but also the direction. Then, use a case statement to update accordingly.

The last remaining tasks are to buffer the entire maze, such that there are two cells worth of walls around it; and to establish starting and ending squares.

Creating the buffer means adding two cells worth of walls to the north and west walls, and 1 cell worth of walls to the south and east walls. This should theoretically be very easy using table.insert, which automatically modifies all further table values. However, will need to be mindful when updating east and west. Updating north is just (table.insert 1 1) for (* cell-num cell-size cell-size) times twice. Similar for south, except (table.insert 1) for (* cell-num cell-size cell-size) once. East and west seem more challenging. Though, thinking about it for a moment, I could essentially migrate the generated array one "row" at a time into a new array, padding it on either side as I do so. I believe this may be the way. I can use table.move to accomplish this.

As part of the padding, I also need to translate the map from a single list into a list of lists. Again, this can be done using the table.move function and table.insert.

(var map [])
(var map-row [])
(table.move cell_map x y 1 map-row)
(table.insert map map-row)

Remaining tasks:

  1. Finalize map generation: [X] Establish starting spot, and add the northern feature spawn point. [X] Establish ending spot, and add the southern feature finale point. [O] Modify the map wall values to account for random wall heights. - This "works", but the walls are all only the front of the square, so it looks odd.
  2. Draw floors
  3. Draw sky-boxen
  4. Add monster mechanics