├── ExampleMain.lua
├── Images
├── .DS_Store
├── customizedEx.png
└── defaultEx.png
├── MainHelpFunc.lua
├── README.md
└── src
├── .DS_Store
├── Dungeon.lua
├── Level.lua
├── Room.lua
├── Tile.lua
└── helpFunctions.lua
/ExampleMain.lua:
--------------------------------------------------------------------------------
1 | local DungeonModule = require("src/Dungeon")
2 | local LevelModule = require("src/Level")
3 | local FuncModule = require("MainHelpFunc")
4 |
5 | -----------------------------------------------------------------------------------
6 | -- - - - - - - - - - - - - - - - Examples of main - - - - - - - - - - - - - - - - -
7 | -----------------------------------------------------------------------------------
8 |
9 | -- Example of generating a dungeon with:
10 | -- * default settings by using generateDungeon.
11 | -- * "Advanced" settings with max nr of rooms, room size and scattering factor.
12 |
13 | function main()
14 |
15 | -- Settings for level sizes and number of levels in dungeon.
16 | height=40
17 | width=60
18 | nrOfLevels=5
19 |
20 | dungeon = Dungeon:new(nrOfLevels, height, width)
21 |
22 | -- generate with default settings
23 | dungeon:generateDungeon()
24 |
25 | -- generate with advanced settings,
26 | -- params: (advanced, maxRooms, maxRoomSize, scatteringFactor)
27 | -- dungeon:generateDungeon(true, 30, 10, 30)
28 |
29 | -- inits a player in level 1, a boss in last level
30 | initPlayer(dungeon.levels[1])
31 | initBoss(dungeon.levels[#dungeon.levels])
32 |
33 | dungeon:printDungeon()
34 | end
35 |
36 | main()
37 |
38 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
39 |
40 | -- Example of generating one level with customized settings.
41 |
42 | -- Some targeted functions to play with:
43 | -- * setMaxRooms: maximum nr of rooms.
44 | -- * setMaxRoomSize: keep it lower than height/width-3 though!
45 | -- * setScatteringFactor: increases random scattering of tiles when forming corridors.
46 | -- * addCycles: adds random cycles between rooms up to parameter value.
47 |
48 | function mainCustomizedLevel()
49 | height = 40
50 | width = 60
51 | level = Level:new(height, width)
52 | level:setMaxRooms(30)
53 | level:setMaxRoomSize(5)
54 | level:setScatteringFactor(10)
55 | Level.veinSpawnRate=0.4
56 | level:initMap(level.height, level.width)
57 | level:generateRooms()
58 | root=level:getRoomTree()
59 | level:buildCorridors(root)
60 | level:buildCorridor(root, level:getEnd())
61 | level:addCycles(10)
62 | level:addStaircases(10)
63 | level:addDoors()
64 | initBoss(level)
65 | initPlayer(level)
66 | level:printLevel()
67 | end
68 |
69 | --mainCustomizedLevel()
70 |
--------------------------------------------------------------------------------
/Images/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vronc/Lua-Dungeon-Generator/c2d5ad1eafca2a221738546ac26a59bdbbd740bb/Images/.DS_Store
--------------------------------------------------------------------------------
/Images/customizedEx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vronc/Lua-Dungeon-Generator/c2d5ad1eafca2a221738546ac26a59bdbbd740bb/Images/customizedEx.png
--------------------------------------------------------------------------------
/Images/defaultEx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vronc/Lua-Dungeon-Generator/c2d5ad1eafca2a221738546ac26a59bdbbd740bb/Images/defaultEx.png
--------------------------------------------------------------------------------
/MainHelpFunc.lua:
--------------------------------------------------------------------------------
1 | -----------------------------------------------------------------------------------
2 | -- - - - - - - - - - - Help functions for main examples - - - - - - - - - - - - - -
3 | -----------------------------------------------------------------------------------
4 |
5 | function initPlayer(level)
6 | c=level:getRoot().center
7 | adj = getAdjacentPos(c[1], c[2])
8 | i=1
9 | repeat
10 | endr, endc = adj[i][1], adj[i][2]
11 | i=i+1
12 | until level:getTile(endr,endc).class == Tile.FLOOR
13 |
14 | level:getTile(endr,endc).class = Tile.PLAYER
15 | end
16 |
17 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
18 |
19 | function initBoss(level)
20 | c=level:getEnd().center
21 | adj = getAdjacentPos(c[1], c[2])
22 | i=1
23 | repeat
24 | endr, endc = adj[i][1], adj[i][2]
25 | i=i+1
26 | until level:getTile(endr,endc).class == Tile.FLOOR
27 |
28 | level:getTile(endr,endc).class = Tile.BOSS
29 | end
30 |
31 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Dungeon Generator
2 |
3 | This is a procedural dungeon generator written in Lua 5.3.5 for roguelike games.
4 |
5 | ## Example Output (default settings)
6 |
7 |
8 | ## Example Output (customized settings)
9 |
10 |
11 | ## How to use
12 |
13 | In the root folder there is a file [ExampleMain.lua](https://github.com/vronc/DungeonGen/blob/master/ExampleMain.lua) which
14 | includes two examples of how the generator can be used.
15 | - __main()__: Using the Dungeon class (which holds many levels) and default generation settings for levels.
16 | - __mainCustomizedLevel()__: Generating one level with customized settings.
17 |
18 | ## Default algorithm for levels
19 |
20 | - Creates matrix of given height/width
21 | - Places non-overlapping rooms of random sizes across matrix.
22 | - Creates minimal spanning tree of rooms using Prim's algorithm (to ensure whole dungeon is reachable).
23 | - Builds tile corridors between rooms by doing DFS through room tree.
24 | - Adds staircases and doors randomly but with certain constraints.
25 |
26 | ## Available functionalities
27 |
28 | ### Settings that can be modified:
29 |
30 | - Height/width
31 | - Maximum room size
32 | - Maximum number of rooms
33 | - ScatteringFactor: an attribute that changes randomness when building corridors between rooms.
34 | - Spawn rate of mineral veins "\*" / soil "%".
35 |
36 | ### Some useful functions to customize level:
37 |
38 | - __Level:buildCorridor(from, to)__: builds a corridor from one room to another.
39 | - __Level:addCycles()__: Builds cycles randomly between rooms.
40 | - __Level:buildRandomTiles(r,c)__: Builds random floor tiles around given tile.
41 | - __Level:getRandRoom()__: returns random room.
42 | - __Level:getRoot()__: returns root room.
43 | - __Level:getEnd()__: returns last leaf added to tree.
44 | - __Level:addDoors()__
45 | - __Level:addStaircases()/Level:getStaircases()__
46 |
47 |
--------------------------------------------------------------------------------
/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vronc/Lua-Dungeon-Generator/c2d5ad1eafca2a221738546ac26a59bdbbd740bb/src/.DS_Store
--------------------------------------------------------------------------------
/src/Dungeon.lua:
--------------------------------------------------------------------------------
1 | local LevelModule = require("src/Level")
2 |
3 | ---------------------------------------------------------------------------
4 | -- - - - - - - - - - - - - - - Dungeon object - - - - - - - - - - - - - - -
5 | ---------------------------------------------------------------------------
6 |
7 | -- Dungeon objects have several levels (consisting of Level objects) which
8 | -- together represent a whole dungeon.
9 |
10 | Dungeon = {nrOfLevels, height, width, levels}
11 | Dungeon.__index = Dungeon
12 |
13 | function Dungeon:new(nrOfLevels, height, width)
14 | local dungeon = {}
15 | dungeon.nrOfLevels = nrOfLevels
16 | dungeon.height = height
17 | dungeon.width = width
18 | dungeon.levels = {}
19 |
20 | setmetatable(dungeon, Dungeon)
21 | return dungeon
22 | end
23 |
24 | function Dungeon:generateDungeon(advanced, maxRooms, maxRoomSize, scatteringFactor)
25 | for i=1,self.nrOfLevels do
26 | newLevel = Level:new(self.height, self.width)
27 | if advanced then
28 | newLevel:setMaxRooms(maxRooms)
29 | newLevel:setMaxRoomSize(maxRoomSize)
30 | newLevel:setScatteringFactor(scatteringFactor)
31 | end
32 | newLevel:generateLevel()
33 | self.levels[i] = newLevel
34 | end
35 | end
36 |
37 | function Dungeon:printDungeon()
38 | for i=1,#dungeon.levels do
39 | local s = "L E V E L "..i
40 | local space=""
41 | for i=1, math.floor((self.width+2)/2-(string.len(s))/4) do
42 | space = space.." "
43 | end
44 | print(space..s..space)
45 | dungeon.levels[i]:printLevel()
46 | print()
47 | end
48 | end
--------------------------------------------------------------------------------
/src/Level.lua:
--------------------------------------------------------------------------------
1 | local FuncModule = require("src/helpFunctions")
2 | local TileModule = require("src/Tile")
3 | local RoomModule = require("src/Room")
4 |
5 | local random = math.random
6 | local floor = math.floor
7 | local ceil = math.ceil
8 | local min = math.min
9 | local max = math.max
10 | local insert = table.insert
11 |
12 | seed = os.time()
13 | math.randomseed(seed)
14 | -- print("seed: "..seed) -- for debugging
15 |
16 | ---------------------------------------------------------------------------------------
17 | -- - - - - - - - - - - - - - - - - - - Level object - - - - - - - - - - - - - - -- - --
18 | ---------------------------------------------------------------------------------------
19 |
20 | -- A Level object consist of several Tile objects which together make up
21 | -- one dungeon level.
22 |
23 | Level = {height, width, matrix, rooms, entrances, staircases}
24 | Level.__index = Level
25 |
26 | Level.MIN_ROOM_SIZE = 3
27 |
28 | Level.veinSpawnRate = 0.02
29 | Level.soilSpawnRate = 0.05
30 |
31 | function Level:new(height, width)
32 | if height < 10 or width < 10 then error("Level must have height>=10, width>=10") end
33 |
34 | local level = {
35 | height=height,
36 | width=width,
37 | matrix={},
38 | rooms = {},
39 | entrances = {},
40 | staircases = {},
41 | rootRoom=nil,
42 | endRoom=nil
43 | }
44 | level.maxRoomSize = ceil(min(height, width)/10)+5
45 | level.maxRooms = ceil(max(height, width)/Level.MIN_ROOM_SIZE)
46 | -- Determines amount of random tiles built when generating corridors:
47 | level.scatteringFactor = ceil(max(height,width)/level.maxRoomSize)
48 |
49 | setmetatable(level, Level)
50 | return level
51 | end
52 |
53 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
54 |
55 | function Level:generateLevel()
56 | -- A default generation of a level including rooms, corridors to each room forming
57 | -- an MSF, staircases and doors. addCycles can be uncommented to dig more corridors.
58 |
59 | self:initMap()
60 | self:generateRooms()
61 | root=self:getRoomTree()
62 | self:buildCorridors(root)
63 | -- self:addCycles(5)
64 | self:addStaircases()
65 | self:addDoors()
66 | end
67 |
68 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
69 |
70 | function Level:initMap()
71 |
72 | -- Create void
73 | for i=-1,self.height+1 do
74 | self.matrix[i] = {}
75 | for j=0,self.width+1 do
76 | self.matrix[i][j] = Tile:new(Tile.EMPTY)
77 | end
78 | end
79 |
80 | self:addWalls(0, 0, self.height+1, self.width+1)
81 | end
82 |
83 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
84 |
85 | function Level:printLevel ()
86 |
87 | for i=0,self.height+1 do
88 | local row=" "
89 | for j=0,self.width+1 do
90 | row=row..self.matrix[i][j].class..Tile.EMPTY
91 | --row=row..self.matrix[i][j].roomId..Tile.EMPTY -- for exposing room-ids
92 | end
93 | print(row)
94 | end
95 | end
96 |
97 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
98 |
99 | function Level:getRandRoom()
100 | -- return: Random room in level
101 | local i = random(1,#self.rooms)
102 | return self.rooms[i]
103 | end
104 |
105 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
106 |
107 | function Level:getRoot()
108 | -- return: Room that is root of room tree if such has been generated.
109 | return self.rootRoom
110 | end
111 |
112 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
113 |
114 | function Level:getEnd()
115 | -- return: Leaf room added last to tree if such has been generated.
116 | return self.endRoom
117 | end
118 |
119 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
120 |
121 | function Level:getStaircases()
122 | -- Index [1] for row, [2] for col on individual entry for individual staircase.
123 | return self.staircases
124 | end
125 |
126 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
127 |
128 | function Level:getTile(r, c)
129 | return self.matrix[r][c]
130 | end
131 |
132 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
133 |
134 | function Level:isRoom(row,col)
135 | return (self:getTile(row,col).roomId ~= 0)
136 | end
137 |
138 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
139 |
140 | function Level:setMaxRooms(m)
141 | self.maxRooms=m
142 | end
143 |
144 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
145 |
146 | function Level:setScatteringFactor(f)
147 | self.scatteringFactor=f
148 | end
149 |
150 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
151 |
152 | function Level:setMaxRoomSize(m)
153 | if m > min(self.height, self.width) or m < 3 then
154 | error("MaxRoomSize can't be bigger than height-3/width-3 or smaller than 3")
155 | end
156 | self.maxRoomSize=m
157 | end
158 |
159 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
160 |
161 | function Level:generateRooms()
162 | for i = 1,self.maxRooms do
163 | self:generateRoom()
164 | end
165 | end
166 |
167 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
168 |
169 | function Level:generateRoom()
170 | -- Will randomly place rooms across tiles (no overlapping).
171 |
172 | local startRow = random(1, self.height-self.maxRoomSize)
173 | local startCol = random(1, self.width-self.maxRoomSize)
174 |
175 | local height = random(Level.MIN_ROOM_SIZE, self.maxRoomSize)
176 | local width = random(Level.MIN_ROOM_SIZE, self.maxRoomSize)
177 |
178 | for i=startRow-1, startRow+height+1 do
179 | for j=startCol-1, startCol+width+1 do
180 |
181 | if (self:isRoom(i,j)) then
182 | return -- Room is overlapping other room->room is discarded
183 | end
184 | end
185 | end
186 | self:buildRoom(startRow, startCol, startRow+height, startCol+width)
187 | end
188 |
189 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
190 |
191 | function Level:getRoomTree()
192 | if #self.rooms < 1 then error("Can't generate room tree, no rooms exists") end
193 |
194 | local root, lastLeaf = prims(table.clone(self.rooms))
195 | self.rootRoom = root
196 | self.endRoom = lastLeaf
197 |
198 | return root
199 | end
200 |
201 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
202 |
203 | function Level:buildRoom(startR, startC, endR, endC)
204 | -- init room object and paint room onto tiles.
205 |
206 | local id = #self.rooms+1
207 | local room = Room:new(id)
208 | local r,c =endR-floor((endR-startR)/2), endC-floor((endC-startC)/2)
209 | room:setCenter(r,c)
210 | insert(self.rooms, room)
211 |
212 | for i=startR, endR do
213 | for j=startC, endC do
214 | local tile = self:getTile(i,j)
215 | tile.roomId, tile.class = id, Tile.FLOOR
216 | end
217 | end
218 | self:addWalls(startR-1, startC-1, endR+1, endC+1)
219 | end
220 |
221 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
222 |
223 | function Level:buildCorridors(root)
224 | -- Recursive DFS function for building corridors to every neighbour of a room (root)
225 |
226 | for i=1,#root.neighbours do
227 | local neigh = root.neighbours[i]
228 | self:buildCorridor(root, neigh)
229 | self:buildCorridors(neigh)
230 | end
231 | end
232 |
233 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
234 |
235 | function Level:buildCorridor(from, to)
236 | -- Parameters from and to are both Room-objects.
237 |
238 | local start, goal = from.center, to.center
239 | local nextTile = findNext(start, goal)
240 | repeat
241 | local row, col = nextTile[1], nextTile[2]
242 | self:buildTile(row, col)
243 |
244 | if random() < self.scatteringFactor*0.05 then
245 | self:buildRandomTiles(row,col) -- Makes the corridors a little more interesting
246 | end
247 | nextTile = findNext(nextTile, goal)
248 | until (self:getTile(nextTile[1], nextTile[2]).roomId == to.id)
249 |
250 | insert(self.entrances, {row,col})
251 | end
252 |
253 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
254 |
255 | function Level:buildTile(r, c)
256 | -- Builds floor tile surrounded by walls.
257 | -- Only floor and empty tiles around floor tiles turns to walls.
258 |
259 | local adj = getAdjacentPos(r,c)
260 | self:getTile(r, c).class = Tile.FLOOR
261 | for i=1,#adj do
262 | r, c = adj[i][1], adj[i][2]
263 | if not (self:getTile(r,c).class == Tile.FLOOR) then
264 | self:placeWall(r,c)
265 | end
266 | end
267 | end
268 |
269 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
270 |
271 | function Level:addDoors(maxDoors)
272 | -- Adds open or closed door randomly to entrance tiles.
273 | if #self.entrances == 0 or #self.rooms < 2 then return end
274 | if not maxDoors then maxDoors = #self.entrances end
275 |
276 | for i=1,maxDoors do
277 | e = self.entrances[i]
278 | if self:isValidEntrance(e[1], e[2]) then
279 | tile = self:getTile(e[1], e[2])
280 | if random() > 0.5 then
281 | tile.class = Tile.C_DOOR
282 | else
283 | tile.class = Tile.O_DOOR
284 | end
285 | end
286 | end
287 | end
288 |
289 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
290 |
291 | function Level:addStaircases(maxStaircases)
292 | -- Adds both descending and ascending staircases to random rooms.
293 | -- Number of staircases depend on number of rooms.
294 |
295 | if (not maxStaircases) or (maxStaircases > #self.rooms) then
296 | maxStaircases = ceil(#self.rooms-(#self.rooms/2))+1
297 | end
298 | local staircases = random(2,maxStaircases)
299 |
300 | repeat
301 | local room = self:getRandRoom()
302 | if not room.hasStaircase or #self.rooms == 1 then
303 | self:placeStaircase(room, staircases)
304 | staircases = staircases-1
305 | end
306 | until staircases==0
307 | end
308 |
309 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
310 |
311 | function Level:addWalls(startR, startC, endR, endC)
312 | -- Places walls on circumference of given rectangle.
313 |
314 | -- Upper and lower sides
315 | for j=startC,endC do
316 | self:placeWall(startR, j)
317 | self:placeWall(endR, j)
318 | end
319 |
320 | -- Left and right sides
321 | for i=startR,endR do
322 | self:placeWall(i, startC)
323 | self:placeWall(i, endC)
324 | end
325 | end
326 |
327 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
328 |
329 | function Level:placeWall(r,c)
330 | -- Places wall at given coordinate. Could either place
331 | -- regular wall, soil or mineral vein
332 |
333 | local tile = self:getTile(r,c)
334 |
335 | if random() <= Level.veinSpawnRate then
336 | tile.class = Tile.VEIN
337 | elseif random() <= Level.soilSpawnRate then
338 | tile.class = Tile.SOIL
339 | Level.soilSpawnRate = 0.6 -- for clustering
340 | else
341 | tile.class = Tile.WALL
342 | Level.soilSpawnRate = 0.05
343 | end
344 | end
345 |
346 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
347 |
348 | function Level:placeStaircase(room, staircases)
349 | -- Places staircase in given room.
350 | -- Position is random number of steps away from center.
351 |
352 | local steps = random(0, floor(self.maxRoomSize/2))
353 |
354 | local nrow, ncol = room.center[1], room.center[2]
355 | repeat
356 | row, col = nrow, ncol
357 | repeat
358 | nrow, ncol = getRandNeighbour(row, col)
359 | until self:getTile(nrow, ncol).class == Tile.FLOOR
360 | steps=steps-1
361 | until (self:getTile(nrow, ncol).roomId ~= room.id or steps <= 0)
362 |
363 | if staircases%2 == 0 then
364 | self:getTile(row, col).class=Tile.D_STAIRCASE
365 | else
366 | self:getTile(row, col).class=Tile.A_STAIRCASE
367 | end
368 | room.hasStaircase = true
369 | insert( self.staircases, { row, col } )
370 | end
371 |
372 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
373 |
374 | function Level:isValidEntrance(row, col)
375 | -- Tile is a valid entrance position if there is a wall above and below it or
376 | -- to the left and to the right of it.
377 | return (
378 | (self:getTile(row+1,col):isWall() and self:getTile(row-1,col):isWall()) or
379 | (self:getTile(row,col+1):isWall() and self:getTile(row,col-1):isWall())
380 | )
381 | end
382 |
383 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
384 |
385 | function Level:getAdjacentTiles(row, col)
386 | -- returns table containing all adjacent tiles to given position.
387 | -- Including self!
388 |
389 | local result={}
390 | local adj=getAdjacentPos(row,col)
391 | for i=1,#adj do
392 | local row, col = adj[i][1], adj[i][2]
393 | insert(result, self:getTile(row, col))
394 | end
395 | return result
396 | end
397 |
398 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
399 |
400 | function Level:buildRandomTiles(r,c)
401 | -- Creates random floor tiles starting from given tile.
402 |
403 | local rand = random(1,self.scatteringFactor)
404 | for i=1,rand do
405 | local nr,nc = getRandNeighbour(r,c, true)
406 |
407 | if (self:getTile(nr,nc).roomId==0 and
408 | withinBounds(nr,nc, self.height, self.width)) then
409 | self:buildTile(nr, nc)
410 | r,c=nr,nc
411 | end
412 | end
413 | end
414 |
415 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
416 |
417 | function Level:addCycles(maxCycles)
418 | -- Adds corridors between random rooms.
419 |
420 | for _=1,maxCycles do
421 | from = self:getRandRoom()
422 | to = self:getRandRoom()
423 | self:buildCorridor(from, to)
424 | end
425 | end
--------------------------------------------------------------------------------
/src/Room.lua:
--------------------------------------------------------------------------------
1 | local pow = math.pow
2 |
3 | ---------------------------------------------------------------------------
4 | -- - - - - - - - - - - - - - - - Room object - - - - - - - - - - - - - - --
5 | ---------------------------------------------------------------------------
6 |
7 | -- Room is a Node-like object.
8 |
9 | -- * Has unique id
10 | -- * Keeps track of neighbouring rooms by keeping boolean value
11 | -- * at index representing neighbours' id's
12 |
13 | Room = { id, neighbours, center, hasStaircase }
14 | Room.__index = Room
15 |
16 | function Room:new(id)
17 | local room = {
18 | id=id,
19 | neighbours = {},
20 | center = {},
21 | hasStaircase = false
22 | }
23 |
24 | setmetatable(room, Room)
25 |
26 | return room
27 | end
28 |
29 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
30 |
31 | function Room:addNeighbour(other)
32 | table.insert(self.neighbours, other)
33 | end
34 |
35 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
36 |
37 | function Room:hasNeighbours()
38 | return tablelength(self.neighbours)>1
39 | end
40 |
41 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
42 |
43 | function Room:setCenter(r,c)
44 | self.center = {r, c}
45 | end
46 |
47 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
48 |
49 | function Room:distanceTo(other)
50 | -- returns distance from self to other room's center.
51 |
52 | return math.sqrt(
53 | pow(math.abs(self.center[1]-other.center[1]),2)+
54 | pow(math.abs(self.center[2]-other.center[2]),2)
55 | )
56 | end
--------------------------------------------------------------------------------
/src/Tile.lua:
--------------------------------------------------------------------------------
1 | ---------------------------------------------------------------------------
2 | -- - - - - - - - - - - - - - - - Tile object - - - - - - - - - - - - - - --
3 | ---------------------------------------------------------------------------
4 |
5 | -- Tile objects:
6 |
7 | -- * Keeps track of room association, if not in room (default): roomId = 0
8 | -- * Has graphic symbol to represent what kind of tile this is in a level
9 | -- * " " for empty
10 | -- * "." for floor
11 | -- * "#" for wall
12 | -- * "<" for ascending staircase
13 | -- * ">" for descending staircase
14 | -- * "%" for soil
15 | -- * "*" for mineral vein
16 | -- * "'" for open door
17 | -- * "+" for closed door
18 |
19 | Tile = {class, roomId}
20 | Tile.__index = Tile
21 |
22 | Tile.EMPTY = " "
23 | Tile.FLOOR = "."
24 | Tile.WALL = "#"
25 | Tile.A_STAIRCASE = "<"
26 | Tile.D_STAIRCASE = ">"
27 | Tile.SOIL = "%"
28 | Tile.VEIN = "*"
29 | Tile.C_DOOR = "+"
30 | Tile.O_DOOR = "'"
31 |
32 | Tile.PLAYER = "@"
33 | Tile.BOSS = "B"
34 |
35 | function Tile:new(t)
36 | local tile = {}
37 | tile.class = t
38 | tile.roomId = 0
39 |
40 | setmetatable(tile, Tile)
41 |
42 | return tile
43 |
44 | end
45 |
46 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
47 |
48 | function Tile:isWall()
49 | return (
50 | self.class == Tile.WALL or
51 | self.class == Tile.SOIL or
52 | self.class == Tile.VEIN
53 | )
54 | end
--------------------------------------------------------------------------------
/src/helpFunctions.lua:
--------------------------------------------------------------------------------
1 |
2 | local random = math.random
3 | local insert = table.insert
4 | local remove = table.remove
5 |
6 | -----------------------------------------------------------------------------------
7 | -- - - - - - - - - - - - - - - Global Help Functions - - - - - - - - - - - - - - --
8 | -----------------------------------------------------------------------------------
9 |
10 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
11 |
12 | function getAdjacentPos(row, col)
13 | -- returns table containing all adjacent positions [1]:row [2]:col to given position
14 | -- INCLUDING SELF. to change this:
15 | -- add if (not (dx == 0 and dy == 0)) then ... end
16 |
17 | local result = {}
18 | for dx =-1,1 do
19 | for dy=-1,1 do
20 | result[#result+1] = { row+dy, col+dx }
21 | end
22 | end
23 | for i=1,#result do
24 | end
25 | return result
26 | end
27 |
28 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
29 |
30 | function findNext(start, goal)
31 | -- Finds next step from start position to goal position in a matrix
32 | if start == goal then return goal end
33 | row, col = start[1], start[2]
34 | local adj = getAdjacentPos(start[1], start[2])
35 | dist = getDist(start, goal)
36 |
37 | for i=1,#adj do
38 | local adjT = adj[i]
39 | if (getDist(adjT, goal) < dist) and
40 | i%2==0 -- not picking diagonals (would cause too narrow corridors)
41 | then
42 | nextPos = adjT
43 | dist = getDist(nextPos, goal)
44 | --break -- uncomment for more rectangular paths!
45 | end
46 | end
47 | return nextPos
48 | end
49 |
50 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
51 |
52 | function getRandNeighbour(row, col, notDiag)
53 | if notDiag then
54 | local f = {-1, 1}
55 | local d = f[random(1,2)]
56 | if random()>0.5 then
57 | return row+d, col
58 | else
59 | return row, col+d
60 | end
61 | else
62 | local dir={ random(-1,1), random(-1,1) }
63 | return row+dir[1], col+dir[2]
64 | end
65 | end
66 |
67 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
68 |
69 | function withinBounds(r, c, height, width)
70 | return (r0 and c0)
71 | end
72 |
73 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
74 |
75 | function prims(unvisited)
76 | len = #unvisited
77 | local root=remove(unvisited, 1)
78 | if #unvisited==0 then return root, root end
79 |
80 | local visited={}
81 | insert(visited, root)
82 | repeat
83 | local dist = 1e309 -- ~inf
84 | for i=1,#visited do
85 | for j=1,#unvisited do
86 | if (unvisited[j]:distanceTo(visited[i]) < dist) then
87 | dist = unvisited[j]:distanceTo(visited[i])
88 | v0 = visited[i]
89 | endIndex=j
90 | end
91 | end
92 | end
93 | v1 = remove(unvisited, endIndex)
94 | v0:addNeighbour(v1)
95 | insert(visited, v1)
96 | until #visited == len
97 |
98 | return visited[1], visited[#visited]
99 | end
100 |
101 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
102 |
103 | -- source: https://stackoverflow.com/questions/2705793/how-to-get-number-of-entries-in-a-lua-table
104 | function tablelength(T)
105 | local count = 0
106 | for _ in pairs(T) do count = count + 1 end
107 | return count
108 | end
109 |
110 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
111 |
112 | function table.clone(org)
113 | local newTable = {}
114 | for k,v in pairs(org) do
115 | newTable[k] = v
116 | end
117 | return newTable
118 | end
119 |
120 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
121 |
122 | function getDist(start, goal)
123 | return math.sqrt(
124 | math.pow(math.abs(goal[1]-start[1]),2)+
125 | math.pow(math.abs(goal[2]-start[2]),2)
126 | )
127 | end
128 |
129 | -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### -- ##### --
130 |
--------------------------------------------------------------------------------