From d9f69ecbd674a3a1b5b0a8989afa0cc2b967057b Mon Sep 17 00:00:00 2001 From: Bill Niblock Date: Wed, 30 Oct 2024 18:53:30 -0400 Subject: [PATCH] Generate a maze and display it with the raycaster --- mapper.fnl | 170 +++++++++++++++++++++++++++++++++++++++++++++----- notes.md | 72 +++++++++++++++++++-- raycaster.fnl | 36 +++++------ 3 files changed, 238 insertions(+), 40 deletions(-) diff --git a/mapper.fnl b/mapper.fnl index 8f9e627..08441b8 100644 --- a/mapper.fnl +++ b/mapper.fnl @@ -3,27 +3,105 @@ ; grid. Each cell has a "cell-size" of walls ("wall-width") and hallways ; ("hall-width"). (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)) - -; Initialize the cell table -(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))) +; Helper functions for dealing with the cell list +(fn cell-row [i] (+ 1 (math.floor (/ (- i 1) cell-num)))) (fn map-index [i] (var cell-col (if (= 0 (% i cell-num)) cell-num (% i cell-num))) (+ (* cell-size cell-size cell-num (- (cell-row i) 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] (var cell_map []) (for [c 1 (length cells)] @@ -35,6 +113,48 @@ (tset cell_map new-map-index (. cells c new-cell-index))))) 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] (var output "") (for [i 1 (length cells)] @@ -42,4 +162,22 @@ (if (= 0 (% i (* cell-num cell-size))) (set output (.. output "\n")))) (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} diff --git a/notes.md b/notes.md index b168ed6..f1e9b7a 100644 --- a/notes.md +++ b/notes.md @@ -118,10 +118,70 @@ Each cell is a combination of hallways and walls. For each cell, if the "row" or ```lisp (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)))) +(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 diff --git a/raycaster.fnl b/raycaster.fnl index fffe845..60c8b28 100644 --- a/raycaster.fnl +++ b/raycaster.fnl @@ -1,27 +1,27 @@ (local state (require :state)) -;(local mapper (require :mapper)) +(local mapper (require :mapper)) (local overlay (require :overlay)) (local pi math.pi) ; ### Screen Size ### (var (screen-width screen-height) (love.window.getMode)) ; ### Map Information ### -; (var map (mapper.generate 15 15)) -(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 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 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 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 0 0 0 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 1 1 1 1 1 1 1 1 1 1 ]]) +(var (map spawn) (mapper.generate)) +; (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 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 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 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 0 0 0 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 1 1 1 1 1 1 1 1 1 1 ]]) ; ### Texture Information ### (var walls []) @@ -46,7 +46,7 @@ (table.insert skybox skyb)) ; ### "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 (planex planey) (values 0 0.66)) ; Camera plane