Generate a maze and display it with the raycaster

This commit is contained in:
Bill Niblock 2024-10-30 18:53:30 -04:00
parent a5c2a4c825
commit d9f69ecbd6
3 changed files with 238 additions and 40 deletions

View file

@ -3,27 +3,105 @@
; grid. Each cell has a "cell-size" of walls ("wall-width") and hallways ; grid. Each cell has a "cell-size" of walls ("wall-width") and hallways
; ("hall-width"). ; ("hall-width").
(var cell-num 10) (var cell-num 10)
(var (hall-width wall-width) (values 2 1)) (var (hall-width wall-width) (values 1 1))
(var cell-size (+ hall-width wall-width)) (var cell-size (+ hall-width wall-width))
; Helper functions for dealing with the cell list
; Initialize the cell table (fn cell-row [i] (+ 1 (math.floor (/ (- i 1) cell-num))))
(var cells {})
(for [c 1 (* cell-num cell-num)]
(var cell [])
(for [i 1 cell-size]
(for [j 1 cell-size]
(var cell-index (+ j (* (- i 1) cell-size)))
(if (and (< i (+ hall-width 1)) (< j (+ hall-width 1)))
(tset cell cell-index 0)
(tset cell cell-index 1))))
(table.insert cells cell))
(fn cell-row [i] (+ 1 (// (- i 1) cell-num)))
(fn map-index [i] (fn map-index [i]
(var cell-col (if (= 0 (% i cell-num)) cell-num (% i cell-num))) (var cell-col (if (= 0 (% i cell-num)) cell-num (% i cell-num)))
(+ (* cell-size cell-size cell-num (- (cell-row i) 1)) (+ (* cell-size cell-size cell-num (- (cell-row i) 1))
(- (* cell-col cell-size) (- cell-size 1)))) (- (* cell-col cell-size) (- cell-size 1))))
; Meta-Cells is the data before the digits
(var spawn-cell (math.random 1 cell-num))
(var leave-cell (math.random (+ (* cell-num (- cell-num 1)) 1) (* cell-num cell-num)))
(var spawn-spot
{:x cell-size
:y (+ (- (* spawn-cell cell-size) (- cell-size 1)) (* 2 hall-width))})
(fn default-meta-cells []
(var meta-cells {})
(for [c 1 (* cell-num cell-num)]
(table.insert meta-cells {:e false :s false :v false}))
(tset meta-cells leave-cell :s true)
meta-cells)
; Cells is the 0's and 1's
(fn convert-cells [meta-cells leave-cell]
(var cells {})
(each [k v (ipairs meta-cells)]
(var cell [])
(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))
(if (= k leave-cell)
(tset cell cell-index 0))))
(table.insert cells cell))
cells)
(fn generate-maze [meta-cells spawn-cell]
; Maze Generation
; 1. Push the starting cell to the stack
; 2. Pop the top of the stack, mark that cell visited
; 3. If there are any unvisited neighbors:
; a. Push the current cell to the stack
; b. Choose a random un-visited neighbor
; c. Adjust walls accordingly
; d. Push chosen neighbor to the stack
(var cell-stack [spawn-cell])
(while (> (length cell-stack) 0)
(var current-cell (table.remove cell-stack))
(var next-cells {})
(tset meta-cells current-cell :v true)
; Check if any of the current cell's neighbors are viable
; North -
; if current-cell <= cell-num, then north is a map-side/barrier
(when (> current-cell cell-num)
(var n-cell (- current-cell cell-num))
(if (not (. meta-cells n-cell :v)) (table.insert next-cells {:c n-cell :d 1})))
; South -
; if current-cell > (- (* cell-num cell-num) cell-num),
; then south is a map-side/barrier
(when (< current-cell (- (* cell-num cell-num) cell-num))
(var s-cell (+ current-cell cell-num))
(if (not (. meta-cells s-cell :v)) (table.insert next-cells {:c s-cell :d 3})))
; East -
; if current-cell % cell-num = 0, then east is a map-side/barrier
(when (not (= 0 (% current-cell cell-num)))
(var e-cell (+ current-cell 1))
(if (not (. meta-cells e-cell :v)) (table.insert next-cells {:c e-cell :d 2})))
; West -
; if current-cell % cell-num = 1, then west is a map-side/barrier
(when (not (= 1 (% current-cell cell-num)))
(var w-cell (- current-cell 1))
(if (not (. meta-cells w-cell :v)) (table.insert next-cells {:c w-cell :d 4})))
; Randomly choose a viable neighbor, if possible
(var chosen-next-cell {})
(when (> (length next-cells) 0)
(table.insert cell-stack current-cell)
(set chosen-next-cell (. next-cells (math.random 1 (length next-cells))))
; Adjust walls accordingly
(case chosen-next-cell
{:d 1} (tset meta-cells (. chosen-next-cell :c) :s true)
{:d 2} (tset meta-cells current-cell :e true)
{:d 3} (tset meta-cells current-cell :s true)
{:d 4} (tset meta-cells (. chosen-next-cell :c) :e true))
(table.insert cell-stack (. chosen-next-cell :c)))
)
meta-cells
)
(fn generate_cell_map [cells] (fn generate_cell_map [cells]
(var cell_map []) (var cell_map [])
(for [c 1 (length cells)] (for [c 1 (length cells)]
@ -35,6 +113,48 @@
(tset cell_map new-map-index (. cells c new-cell-index))))) (tset cell_map new-map-index (. cells c new-cell-index)))))
cell_map) cell_map)
(fn generate-north-spawn [spawn-cell]
(var (map map-row) (values [] []))
(for [i 1 cell-size]
(set map-row [])
(for [j 1 (- (* (+ 2 cell-num) cell-size) hall-width)] (table.insert map-row 1))
(table.insert map map-row))
(var spawn (+ (- (* spawn-cell cell-size) (- cell-size 1)) cell-size))
(tset map cell-size spawn 0)
(tset map cell-size (+ spawn 1) 2)
(tset map cell-size (- spawn 1) 2)
(tset map (- cell-size 1) spawn 3)
map)
(fn generate-south-exit [cells leave-cell]
(var (map map-row) (values [] []))
(for [i 1 (- cell-size hall-width)]
(set map-row [])
(for [j 1 (- (* (+ 2 cell-num) cell-size) hall-width)] (table.insert map-row 1))
(table.insert map map-row))
(var escape (+ (* (- leave-cell (* cell-num (- cell-num 1))) cell-size) 1))
(for [i 1 cell-size]
(tset map 1 (+ escape (- i 1)) 3))
map)
(fn generate_map [cells spawn-cell leave-cell]
(var map [])
; Generate the northern feature - spawn point
(each [_ row (ipairs (generate-north-spawn spawn-cell))] (table.insert map row))
; Pad the east and west sides of the map
(for [c 1 (* cell-num cell-size)]
(var map-row [])
(for [i 1 cell-size] (table.insert map-row 1))
(table.move cells
(+ (* (- c 1) (* cell-num cell-size)) 1)
(* c (* cell-num cell-size))
(+ cell-size 1) map-row)
(for [i 1 (- cell-size hall-width)] (table.insert map-row 1))
(table.insert map map-row))
; Generate the northern feature - spawn point
(each [_ row (ipairs (generate-south-exit cells leave-cell))] (table.insert map row))
map)
(fn print_cell_map [cells] (fn print_cell_map [cells]
(var output "") (var output "")
(for [i 1 (length cells)] (for [i 1 (length cells)]
@ -42,4 +162,22 @@
(if (= 0 (% i (* cell-num cell-size))) (set output (.. output "\n")))) (if (= 0 (% i (* cell-num cell-size))) (set output (.. output "\n"))))
(print output)) (print output))
(print_cell_map (generate_cell_map cells)) (fn print_map [map]
(for [i 1 (length map)]
(var r "")
(for [j 1 (length (. map i))]
(set r (.. r " " (. map i j))))
(print r)))
(fn generate []
(var meta-maze (generate-maze (default-meta-cells) spawn-cell))
(var data-maze (convert-cells meta-maze leave-cell))
(var data-map (generate_cell_map data-maze))
(var map (generate_map data-map spawn-cell leave-cell))
(print_map map)
(values map spawn-spot))
; (print (.. "SPAWN: " spawn-cell "(" (. spawn-spot :x) "," (. spawn-spot :y) ")"))
; (print (.. "END: " leave-cell))
; (print (.. "Escape: " (- leave-cell (* cell-num (- cell-num 1)))))
{: generate}

View file

@ -118,10 +118,70 @@ Each cell is a combination of hallways and walls. For each cell, if the "row" or
```lisp ```lisp
(var cell []) (var cell [])
(for [i 1 cell-size] (each [k v (ipairs meta-cells)]
(for [j 1 cell-size] (var row-limit (if (. v :e) (+ hall-width wall-width) hall-width))
(var cell-index (+ j (* (- i 1) cell-size))) (var col-limit (if (. v :s) (+ hall-width wall-width) hall-width))
(if (and (< i (+ hall-width 1)) (< j (+ hall-width 1))) (for [i 1 cell-size]
(tset cell cell-index 0) (for [j 1 cell-size]
(tset cell cell-index 1)))) (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

View file

@ -1,27 +1,27 @@
(local state (require :state)) (local state (require :state))
;(local mapper (require :mapper)) (local mapper (require :mapper))
(local overlay (require :overlay)) (local overlay (require :overlay))
(local pi math.pi) (local pi math.pi)
; ### Screen Size ### ; ### Screen Size ###
(var (screen-width screen-height) (love.window.getMode)) (var (screen-width screen-height) (love.window.getMode))
; ### Map Information ### ; ### Map Information ###
; (var map (mapper.generate 15 15)) (var (map spawn) (mapper.generate))
(var map [[1 1 2 1 1 1 1 2 1 1 1 ] ; (var map [[1 1 2 1 1 1 1 2 1 1 1 ]
[1 0 0 0 0 0 0 0 0 0 1 ] ; [1 0 0 0 0 0 0 0 0 0 1 ]
[1 0 0 0 0 0 0 0 0 0 2 ] ; [1 0 0 0 0 0 0 0 0 0 2 ]
[2 0 0 0 0 0 0 0 0 0 2 ] ; [2 0 0 0 0 0 0 0 0 0 2 ]
[1 0 0 0 0 0 0 0 0 0 1 ] ; [1 0 0 0 0 0 0 0 0 0 1 ]
[1 0 0 0 0 0 0 0 0 0 1 ] ; [1 0 0 0 0 0 0 0 0 0 1 ]
[1 0 0 0 0 0 0 0 3 0 1 ] ; [1 0 0 0 0 0 0 0 3 0 1 ]
[1 0 0 0 0 0 0 3 3 0 1 ] ; [1 0 0 0 0 0 0 3 3 0 1 ]
[1 1 1 1 1 1 0 0 0 0 1 ] ; [1 1 1 1 1 1 0 0 0 0 1 ]
[1 0 0 0 0 0 0 1 1 1 1 ] ; [1 0 0 0 0 0 0 1 1 1 1 ]
[1 0 1 1 1 1 1 1 1 1 1 ] ; [1 0 1 1 1 1 1 1 1 1 1 ]
[1 0 1 1 1 1 0 0 0 0 1 ] ; [1 0 1 1 1 1 0 0 0 0 1 ]
[1 0 1 1 1 1 1 1 1 0 1 ] ; [1 0 1 1 1 1 1 1 1 0 1 ]
[1 0 0 0 0 0 0 0 0 0 1 ] ; [1 0 0 0 0 0 0 0 0 0 1 ]
[1 1 1 1 1 1 1 1 1 1 1 ]]) ; [1 1 1 1 1 1 1 1 1 1 1 ]])
; ### Texture Information ### ; ### Texture Information ###
(var walls []) (var walls [])
@ -46,7 +46,7 @@
(table.insert skybox skyb)) (table.insert skybox skyb))
; ### "Player" variables ### ; ### "Player" variables ###
(var (posx posy) (values 4.0 4.0)) ; Initial map position (var (posx posy) (values (+ (. spawn :x) 0.5) (+ (. spawn :y) 0.5)))
(var (dirx diry) (values -1.0 0.0)) ; Initial direction vector (var (dirx diry) (values -1.0 0.0)) ; Initial direction vector
(var (planex planey) (values 0 0.66)) ; Camera plane (var (planex planey) (values 0 0.66)) ; Camera plane