lispjam-autumn-2024/notes.md

187 lines
6.9 KiB
Markdown

First, establish some of the cell data
```lisp
; 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.
```lisp
(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.
```lisp
(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`.
```lisp
(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:
```lisp
(fn row [i] (+ 1 (// i num-cells)))
```
This gets the "upper-left" for the map array, given a cell index.
```lisp
(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".
```lisp
(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`.
```lisp
(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.
[ ] Could do an eastern and western spot too
[ ] Modify the map wall values to account for random wall heights.
2. Draw floors
3. Draw skyboxen
4. Add monster mechanics