├── 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 | drawing 7 | 8 | ## Example Output (customized settings) 9 | drawing 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 | --------------------------------------------------------------------------------