2024-10-29 23:44:41 +00:00
|
|
|
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)
|
2024-10-30 01:42:51 +00:00
|
|
|
(var current-cell (table.remove cell-stack))
|
2024-10-29 23:44:41 +00:00
|
|
|
(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 [])
|
2024-10-30 22:53:30 +00:00
|
|
|
(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))))
|
2024-10-29 23:44:41 +00:00
|
|
|
```
|
2024-10-30 22:53:30 +00:00
|
|
|
|
|
|
|
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.
|
2024-10-31 02:29:10 +00:00
|
|
|
[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.
|
2024-10-30 22:53:30 +00:00
|
|
|
2. Draw floors
|
2024-10-31 02:29:10 +00:00
|
|
|
3. Draw sky-boxen
|
2024-10-30 22:53:30 +00:00
|
|
|
4. Add monster mechanics
|
2024-11-02 21:09:38 +00:00
|
|
|
|
|
|
|
---
|
|
|
|
Thinking about a HUD. On the top can be a compass, and then on the bottom can be
|
|
|
|
a few additional things.
|
|
|
|
|
|
|
|
For the compass, I know the direction of the player. I've implemented the state
|
|
|
|
function again, and am writing that data to it and reading from it for the
|
|
|
|
overlay. When the `player.getX` is greater than 0, I'm "facing" north; when less
|
|
|
|
than 0, facing south. For the `player.getY`, greater than 0 is west; less than
|
|
|
|
is east. It's easy to just print the value, but it'd be really cool to instead
|
|
|
|
print one of those compass bars. Theoretically it's printing out the elements of
|
|
|
|
a list at set screen intervals. The list may look like:
|
|
|
|
|
|
|
|
```lisp
|
|
|
|
(var compass-bar [":" "|" "N" "|" ":" "|" "E" "|" ":" "|" "S" "|" ":" "|" "W" "|"])
|
|
|
|
```
|
|
|
|
|
|
|
|
Then it would print 5 elements, starting at x, going to x + 5, wrapping around.
|
|
|
|
`x` would be determined based on the two values of `player.getX` and
|
|
|
|
`player.getY`.
|
|
|
|
|
|
|
|
This will ensure we loop properly through the indexes, assuming `x` is always
|
|
|
|
larger than the array length.
|
|
|
|
```lisp
|
|
|
|
(fn fi [x] (set r x) (while (> r 5) (set r (- r 5))) r)
|
|
|
|
```
|
|
|
|
|
|
|
|
Beyond this, it may be good to pair this with logic that allows adding or
|
|
|
|
subtracting from the index and not escaping the list. This would basically check
|
|
|
|
if the new index will be either less-than 0 or greater than the list length, and
|
|
|
|
adjust accordingly.
|
|
|
|
|
|
|
|
Could be something like:
|
|
|
|
|
|
|
|
```
|
|
|
|
(if x > 0) // northern half
|
|
|
|
index is set to north
|
|
|
|
(adjust index by ratio of y to (-1 .. 1))
|
|
|
|
(else) // southern half
|
|
|
|
index is set to south
|
|
|
|
(adjust index by ratio y to (-1 .. 1))
|
|
|
|
```
|
|
|
|
|
|
|
|
The adjustment algorithm is the same, and it's either added or subtracted from
|
|
|
|
the index, based on which hemisphere, probably?
|
|
|
|
|
|
|
|
This value `(math.floor (+ 10 (* 10 (player.getDirX))))` yields 0-19 in both an
|
|
|
|
east and west facing arc. If I use east and west as a switch, then when I'm
|
|
|
|
facing east I can move the compass bar list in one direction, and the other when
|
|
|
|
facing west.
|
|
|
|
|
|
|
|
In order to do this I need a proper circular-list approach.
|
|
|
|
|
|
|
|
```lisp
|
|
|
|
(var compass [<elements>])
|
|
|
|
(fn circular-compass [c n]
|
|
|
|
(if (< (+ c n) 1) (length compass)
|
|
|
|
(> (length compass) (+ c n)) 0
|
|
|
|
(+ c n))
|
|
|
|
```
|
|
|
|
|
|
|
|
---
|
|
|
|
Compass bar "works", at least well enough for now. Time to move to some menus. I
|
|
|
|
think a cool aesthetic would be the start menu being like the inside of the
|
|
|
|
escape pod you start outside of. The background would be a texture of some kind
|
|
|
|
of metallic wall, and there could be a flashing light effect. The menu items
|
|
|
|
would be the configuration for the maze: size, number of survivors, etc.. There
|
|
|
|
would be a button to start, and then a button to quit. If I have time, there can
|
|
|
|
be a button to remap keys, and maybe turn sound on and off.
|
|
|
|
|
|
|
|
Focusing on the menu buttons themselves, each one has a shape and text.
|
|
|
|
Additionally, each one should have a hover effect. Each menu can have a
|
|
|
|
left-action, right-action, and select-action.
|
|
|
|
|
|
|
|
```lisp
|
|
|
|
(var menu-font (love.graphics.newFont 20))
|
|
|
|
(fn menu-button [x y text la ra sa]
|
|
|
|
(love.graphics.polygon ...)
|
|
|
|
(love.graphics.printf text menu-font x (+ y ...) (+ x ...))
|
|
|
|
(var (left-area right-area mid-area) (values ...))
|
|
|
|
(when (left-area) (la))
|
|
|
|
(when (right-area) (ra))
|
|
|
|
(when (mid-area) (sa))
|
|
|
|
```
|
|
|
|
|
|
|
|
There should also be a way to navigate with the keyboard. Using up and down
|
|
|
|
arrows or keys will move the "selected" button, left/right arrows/keys will
|
|
|
|
activate the left/right actions, and enter/use will activate the select action.
|
2024-11-03 23:08:43 +00:00
|
|
|
|
|
|
|
Start simple, make the polygon a square, make the areas square. Add buttons for
|
|
|
|
survivors and supplies and monster and stuff, even though it's not implemented
|
|
|
|
yet.
|