Repository: SiENcE/astray Branch: master Commit: ea48e9ac9731 Files: 15 Total size: 54.5 KB Directory structure: gitextract_yev__z04/ ├── LICENSE ├── README.md ├── astray/ │ ├── MiddleClass.lua │ ├── astray.lua │ ├── cell.lua │ ├── directionpicker.lua │ ├── dungeon.lua │ ├── init.lua │ ├── map.lua │ ├── point.lua │ ├── room.lua │ ├── roomgenerator.lua │ └── util.lua ├── conf.lua └── main.lua ================================================ FILE CONTENTS ================================================ ================================================ FILE: LICENSE ================================================ Copyright (c) <''2014''> <''Florian Fischer''> This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ================================================ FILE: README.md ================================================ # Astray [![License: Zlib](https://img.shields.io/badge/License-Zlib-brightgreen.svg)](https://opensource.org/licenses/Zlib) Astray is a robust Lua library for procedural generation of mazes, rooms, and dungeons. It provides a flexible system for creating diverse layouts suitable for dungeon crawlers, roguelikes, and other games requiring procedural map generation.

## Features - Procedural maze generation using modified depth-first search - Customizable room placement and connection - Configurable parameters for dungeon characteristics: - Corridor density and sparseness - Room size and quantity - Dead end frequency - Direction change probability - ASCII map output with customizable tiles - Support for different cell types (corridors, rooms, doors) ## Installation 1. Copy the Astray folder to your project: ```bash git clone https://github.com/SiENcE/astray.git ``` 2. Include the library in your Lua code: ```lua local astray = require('astray') ``` ## Quick Start ```lua local astray = require('astray') -- Initialize generator with desired parameters -- Note: Astray generates uneven-sized maps (e.g., 39x39 from 40x40 input) local height, width = 40, 40 local generator = astray.Astray:new( width/2-1, -- Map width height/2-1, -- Map height 30, -- Change direction modifier (1-30) 70, -- Sparseness modifier (25-70) 50, -- Dead end removal modifier (50-99) astray.RoomGenerator:new( -- Room generator configuration 4, -- Number of rooms 2, 4, -- Min/Max room width 2, 4 -- Min/Max room height ) ) -- Generate dungeon local dungeon = generator:Generate() -- Convert to ASCII tiles local tiles = generator:CellToTiles(dungeon) -- Print the dungeon for y = 0, #tiles[1] do local line = '' for x = 0, #tiles do line = line .. tiles[x][y] end print(line) end ``` ## Configuration ### Astray Constructor Parameters ```lua Astray:new(width, height, changeDirectionMod, sparsenessMod, deadEndRemovalMod, roomGenerator) ``` | Parameter | Range | Description | |-----------|-------|-------------| | width | > 0 | Width of the dungeon in even numbers (map will be uneven) | | height | > 0 | Height of the dungeon in even numbers (map will be uneven) | | changeDirectionMod | 1-30 | Higher values create more winding corridors | | sparsenessMod | 25-70 | Higher values create more open layouts | | deadEndRemovalMod | 50-99 | Higher values remove more dead ends | ### Room Generator Parameters ```lua RoomGenerator:new(rooms, minWidth, maxWidth, minHeight, maxHeight) ``` | Parameter | Description | |-----------|-------------| | rooms | Number of rooms to generate | | minWidth | Minimum room width | | maxWidth | Maximum room width | | minHeight | Minimum room height | | maxHeight | Maximum room height | ## Customizing Tiles You can customize the appearance of generated dungeons by providing a tile mapping: ```lua local symbols = { Wall = '#', Empty = ' ', DoorN = '|', DoorS = '|', DoorE = '-', DoorW = '-' } local tiles = generator:CellToTiles(dungeon, symbols) ``` ## Example Output ``` Map size= 39 39 ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓-▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓ | ▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓-▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓ | | ▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓ ▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓ ▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓ ▓ ▓ ▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓ ▓ ▓ ▓-▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓ ▓ ▓ | ▓▓▓▓▓ ▓▓▓ ▓ ▓ ▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓ ▓▓▓ ▓ ▓ ▓▓▓▓▓▓▓▓▓ ▓ ▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓ ▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓ ▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓ ▓ ▓ ▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓ ▓▓▓▓▓ ▓ ▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓-▓▓▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓ ▓▓▓ ▓▓▓▓▓▓▓▓▓ | | ▓▓▓ ▓▓▓▓▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓ ▓▓▓ ▓▓▓▓▓▓▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓ ▓▓▓ ▓▓▓▓▓▓▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓ ▓▓▓ ▓▓▓▓▓▓▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓ ▓▓▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓-▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ``` ## License Distributed under the [zlib/libpng License](https://opensource.org/licenses/Zlib). See `LICENSE` for more information. ## Contributing 1. Fork the repository 2. Create a feature branch (`git checkout -b feature/amazing-feature`) 3. Commit your changes (`git commit -m 'Add amazing feature'`) 4. Push to the branch (`git push origin feature/amazing-feature`) 5. Open a Pull Request ## Acknowledgments This work is based on various dungeon generation techniques and algorithms: - [Dirkkok's Random Dungeon Generation](http://dirkkok.wordpress.com/2007/11/21/generating-random-dungeons-part-1/) - [Myth-Weavers Dungeon Generator](http://www.myth-weavers.com/generate_dungeon.php) - [Thomas Bowker's Dungeon Generation](http://thomasbowker.com/2013/08/02/generating-a-dungeon/) ## Support For issues, questions, or contributions, please visit the [GitHub repository](https://github.com/SiENcE/astray). ## Author Florian Fischer ================================================ FILE: astray/MiddleClass.lua ================================================ local middleclass = { _VERSION = 'middleclass v3.0.0', _DESCRIPTION = 'Object Orientation for Lua', _LICENSE = [[ MIT LICENSE Copyright (c) 2011 Enrique García Cota Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]] } local function _setClassDictionariesMetatables(aClass) local dict = aClass.__instanceDict dict.__index = dict local super = aClass.super if super then local superStatic = super.static setmetatable(dict, super.__instanceDict) setmetatable(aClass.static, { __index = function(_,k) return dict[k] or superStatic[k] end }) else setmetatable(aClass.static, { __index = function(_,k) return dict[k] end }) end end local function _setClassMetatable(aClass) setmetatable(aClass, { __tostring = function() return "class " .. aClass.name end, __index = aClass.static, __newindex = aClass.__instanceDict, __call = function(self, ...) return self:new(...) end }) end local function _createClass(name, super) local aClass = { name = name, super = super, static = {}, __mixins = {}, __instanceDict={} } aClass.subclasses = setmetatable({}, {__mode = "k"}) _setClassDictionariesMetatables(aClass) _setClassMetatable(aClass) return aClass end local function _createLookupMetamethod(aClass, name) return function(...) local method = aClass.super[name] assert( type(method)=='function', tostring(aClass) .. " doesn't implement metamethod '" .. name .. "'" ) return method(...) end end local function _setClassMetamethods(aClass) for _,m in ipairs(aClass.__metamethods) do aClass[m]= _createLookupMetamethod(aClass, m) end end local function _setDefaultInitializeMethod(aClass, super) aClass.initialize = function(instance, ...) return super.initialize(instance, ...) end end local function _includeMixin(aClass, mixin) assert(type(mixin)=='table', "mixin must be a table") for name,method in pairs(mixin) do if name ~= "included" and name ~= "static" then aClass[name] = method end end if mixin.static then for name,method in pairs(mixin.static) do aClass.static[name] = method end end if type(mixin.included)=="function" then mixin:included(aClass) end aClass.__mixins[mixin] = true end local Object = _createClass("Object", nil) Object.static.__metamethods = { '__add', '__call', '__concat', '__div', '__ipairs', '__le', '__len', '__lt', '__mod', '__mul', '__pairs', '__pow', '__sub', '__tostring', '__unm'} function Object.static:allocate() assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'") return setmetatable({ class = self }, self.__instanceDict) end function Object.static:new(...) local instance = self:allocate() instance:initialize(...) return instance end function Object.static:subclass(name) assert(type(self) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'") assert(type(name) == "string", "You must provide a name(string) for your class") local subclass = _createClass(name, self) _setClassMetamethods(subclass) _setDefaultInitializeMethod(subclass, self) self.subclasses[subclass] = true self:subclassed(subclass) return subclass end function Object.static:subclassed(other) end function Object.static:isSubclassOf(other) return type(other) == 'table' and type(self) == 'table' and type(self.super) == 'table' and ( self.super == other or type(self.super.isSubclassOf) == 'function' and self.super:isSubclassOf(other) ) end function Object.static:include( ... ) assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'") for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end return self end function Object.static:includes(mixin) return type(mixin) == 'table' and type(self) == 'table' and type(self.__mixins) == 'table' and ( self.__mixins[mixin] or type(self.super) == 'table' and type(self.super.includes) == 'function' and self.super:includes(mixin) ) end function Object:initialize() end function Object:__tostring() return "instance of " .. tostring(self.class) end function Object:isInstanceOf(aClass) return type(self) == 'table' and type(self.class) == 'table' and type(aClass) == 'table' and ( aClass == self.class or type(aClass.isSubclassOf) == 'function' and self.class:isSubclassOf(aClass) ) end function middleclass.class(name, super, ...) super = super or Object return super:subclass(name, ...) end middleclass.Object = Object setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end }) return middleclass ================================================ FILE: astray/astray.lua ================================================ --[[ Copyright (c) <''2024''> <''Florian Fischer''> This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ]]-- -- Globals DirectionType = { North = 0, South = 1, East = 2, West = 3 } SideType = { Empty=1, Wall=2, Door=3 } MaxValue = 65535 local PATH = (...):match("(.-)[^%.]+$") local class = require(PATH .. 'MiddleClass') local Point = require(PATH .. 'point') local RoomGenerator = require(PATH .. 'roomgenerator') local Dungeon = require(PATH .. 'dungeon') local DirectionPicker = require(PATH .. 'directionpicker') -- Class local Astray = class("Astray") function Astray:initialize( width, height, changeDirectionModifier, sparsenessModifier, deadEndRemovalModifier, roomGenerator) -- print('Astray:initialize') math.randomseed(os.time()) math.random(); math.random(); math.random() self.width = width or 25 self.height = height or 25 self.changeDirectionModifier = changeDirectionModifier or 30 self.sparsenessModifier = sparsenessModifier or 70 self.deadEndRemovalModifier = deadEndRemovalModifier or 50 self.roomGenerator = roomGenerator or RoomGenerator:new(10, 1, 5, 1, 5) end function Astray:Generate() local dungeon = Dungeon:new(self.width, self.height) dungeon:FlagAllCellsAsUnvisited() self:CreateDenseMaze(dungeon) self:SparsifyMaze(dungeon) self:RemoveDeadEnds(dungeon) self.roomGenerator:PlaceRooms(dungeon) self.roomGenerator:PlaceDoors(dungeon) return dungeon end function Astray:GenerateDungeon() local dungeon = Dungeon:new(self.width, self.height) dungeon:FlagAllCellsAsUnvisited() self:CreateDenseMaze(dungeon) return dungeon end function Astray:GeneratePlaceRooms(dungeon) self.roomGenerator:PlaceRooms(dungeon) end function Astray:GeneratePlaceDoors(dungeon) self.roomGenerator:PlaceDoors(dungeon) end function Astray:CreateDenseMaze( dungeon ) local currentLocation = dungeon:PickRandomCellAndFlagItAsVisited() local previousDirection = DirectionType.North while (not dungeon:AllCellsAreVisited() ) do local directionPicker = DirectionPicker:new(previousDirection, self.changeDirectionModifier) local direction = directionPicker:GetNextDirection() while (not dungeon:HasAdjacentCellInDirection(currentLocation, direction)) or dungeon:AdjacentCellInDirectionIsVisited(currentLocation, direction) do if directionPicker:HasNextDirection() then direction = directionPicker:GetNextDirection() else currentLocation = dungeon:GetRandomVisitedCell(currentLocation) -- Get a new previously visited location directionPicker = DirectionPicker:new(previousDirection, self.changeDirectionModifier) -- Reset the direction picker direction = directionPicker:GetNextDirection() -- Get a new direction end end currentLocation = dungeon:CreateCorridor(currentLocation, direction) dungeon:FlagCellAsVisited(currentLocation) previousDirection = direction end end function Astray:SparsifyMaze(dungeon) -- Calculate the target number of cells to remove local noOfDeadEndCellsToRemove = math.ceil((self.sparsenessModifier / 100) * (dungeon:getWidth() * dungeon:getHeight())) -- Initialize counters local cellsRemoved = 0 local maxIterations = dungeon:getWidth() * dungeon:getHeight() -- Maximum possible iterations local iterations = 0 while cellsRemoved < noOfDeadEndCellsToRemove do -- Get current dead end cells local deadEndCellList = dungeon:DeadEndCellLocations() -- Break if no more dead ends or max iterations reached if #deadEndCellList < 2 or iterations >= maxIterations then break end -- Process each dead end for _, point in pairs(deadEndCellList) do local cell = dungeon:getCell(point) local direction = cell:CalculateDeadEndCorridorDirection() -- Remove the dead end dungeon:CreateWall(point, direction) dungeon:getCell(point):setIsCorridor(false) -- Update counter cellsRemoved = cellsRemoved + 1 -- Check if we've reached our target if cellsRemoved >= noOfDeadEndCellsToRemove then break end end -- Increment iteration counter iterations = iterations + 1 end -- Log completion statistics if needed if iterations >= maxIterations then print(string.format("WARNING: SparsifyMaze reached maximum iterations. Removed %d of %d planned cells.", cellsRemoved, noOfDeadEndCellsToRemove)) end end function Astray:RemoveDeadEnds( dungeon ) local deadEndCellList = dungeon:DeadEndCellLocations() for key,deadEndLocation in pairs( deadEndCellList ) do if self:ShouldRemoveDeadend() then local currentLocation = deadEndLocation repeat local directionPicker = DirectionPicker:new( dungeon:getCell(currentLocation):CalculateDeadEndCorridorDirection(), 100) local direction = directionPicker:GetNextDirection() while (not dungeon:HasAdjacentCellInDirection(currentLocation, direction)) do if directionPicker:HasNextDirection() then direction = directionPicker:GetNextDirection() else print("ERROR: This should not happen") end end -- Create a corridor in the selected direction currentLocation = dungeon:CreateCorridor(currentLocation, direction) until (not dungeon:getCell(currentLocation):getIsDeadEnd()) -- Stop when you intersect an existing corridor. end end end function Astray:ShouldRemoveDeadend() return math.random(1, 99) < self.deadEndRemovalModifier end function Astray:CellToTiles( dungeon, tiles ) local tile = tiles if not tile then tile = {} tile.Wall = '' tile.Empty = ' ' tile.DoorN = '|' tile.DoorS = '|' tile.DoorE = '-' tile.DoorW = '-' end local expanded = {} for x = 0, dungeon:getWidth()*2 do expanded[x] = {} for y = 0, dungeon:getHeight()*2 do expanded[x][y] = tile.Wall end end local minPoint = nil local maxPoint = nil for key,room in pairs(dungeon.rooms) do -- Get the room min and max location in tile coordinates minPoint = Point:new(room:getBounds().X * 2 + 1, room:getBounds().Y * 2 + 1) maxPoint = Point:new( (room:getBounds().X+room:getBounds().Width) * 2, (room:getBounds().Y+room:getBounds().Height) * 2 ) -- Fill the room in tile space with an empty value for i = minPoint.X, maxPoint.X-1 do for j = minPoint.Y, maxPoint.Y-1 do expanded[i][j] = tile.Empty end end end local target = nil for key,location in pairs( dungeon:CorridorCellLocations() ) do target = Point:new(location.X*2+1, location.Y*2+1) expanded[target.X][target.Y] = tile.Empty if dungeon:getCell(location):getNorthSide() == SideType.Empty then expanded[target.X][target.Y-1] = tile.Empty end if dungeon:getCell(location):getNorthSide() == SideType.Door then expanded[target.X][target.Y-1] = tile.DoorN end if dungeon:getCell(location):getSouthSide() == SideType.Empty then expanded[target.X][target.Y+1] = tile.Empty end if dungeon:getCell(location):getSouthSide() == SideType.Door then expanded[target.X][target.Y+1] = tile.DoorS end if dungeon:getCell(location):getEastSide() == SideType.Empty then expanded[target.X+1][target.Y] = tile.Empty end if dungeon:getCell(location):getEastSide() == SideType.Door then expanded[target.X+1][target.Y] = tile.DoorE end if dungeon:getCell(location):getWestSide() == SideType.Empty then expanded[target.X-1][target.Y] = tile.Empty end if dungeon:getCell(location):getWestSide() == SideType.Door then expanded[target.X-1][target.Y] = tile.DoorW end end return expanded end ------------------------------------------------------ -- helper functions ------------------------------------------------------ function Astray:getWidth() return self.width end function Astray:setWidth( width ) self.width = width end function Astray:getHeight() return self.height end function Astray:setHeight( height ) self.height = height end function Astray:getChangeDirectionModifier() return self.changeDirectionModifier end function Astray:setChangeDirectionModifier( changeDirectionModifier ) self.changeDirectionModifier = changeDirectionModifier end function Astray:getSparsenessModifier() return self.sparsenessModifier end function Astray:setSparsenessModifier( sparsenessModifier ) self.sparsenessModifier = sparsenessModifier end function Astray:getDeadEndRemovalModifier() return self.deadEndRemovalModifier end function Astray:setDeadEndRemovalModifier( deadEndRemovalModifier ) self.deadEndRemovalModifier = deadEndRemovalModifier end return Astray ================================================ FILE: astray/cell.lua ================================================ --[[ Copyright (c) <''2014''> <''Florian Fischer''> This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ]]-- local PATH = (...):match("(.-)[^%.]+$") local class = require(PATH .. 'MiddleClass') -- Class local Cell = class("Cell") function Cell:initialize() self.eastSide = SideType.Wall self.northSide = SideType.Wall self.southSide = SideType.Wall self.westSide = SideType.Wall self.visited = false self.isCorridor = false end function Cell:CalculateDeadEndCorridorDirection() if (not self:getIsDeadEnd()) then print('ERROR: InvalidOperationException (Cell:CalculateDeadEndCorridorDirection): not getIsDeadEnd()') end if (self.northSide == SideType.Empty) then return DirectionType.North end if (self.southSide == SideType.Empty) then return DirectionType.South end if (self.westSide == SideType.Empty) then return DirectionType.West end if (self.eastSide == SideType.Empty) then return DirectionType.East end print('ERROR: InvalidOperationException (Cell:CalculateDeadEndCorridorDirection)') end ------------------------------------------------------ -- helper functions ------------------------------------------------------ function Cell:getVisited() return self.visited end function Cell:setVisited( visited ) self.visited = visited end function Cell:getNorthSide() return self.northSide end function Cell:setNorthSide( northSide ) self.northSide = northSide end function Cell:getSouthSide() return self.southSide end function Cell:setSouthSide( southSide ) self.southSide = southSide end function Cell:getEastSide() return self.eastSide end function Cell:setEastSide( eastSide ) self.eastSide = eastSide end function Cell:getWestSide() return self.westSide end function Cell:setWestSide( westSide ) self.westSide = westSide end function Cell:getIsDeadEnd() return self:getWallCount() == 3 end function Cell:getIsCorridor() return self.isCorridor end function Cell:setIsCorridor( isCorridor ) self.isCorridor = isCorridor end function Cell:getWallCount() local wallCount = 0 if (self.northSide == SideType.Wall) then wallCount=wallCount+1 end if (self.southSide == SideType.Wall) then wallCount=wallCount+1 end if (self.westSide == SideType.Wall) then wallCount=wallCount+1 end if (self.eastSide == SideType.Wall) then wallCount=wallCount+1 end return wallCount end return Cell ================================================ FILE: astray/directionpicker.lua ================================================ --[[ Copyright (c) <''2014''> <''Florian Fischer''> This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ]]-- local PATH = (...):match("(.-)[^%.]+$") local class = require(PATH .. 'MiddleClass') local Util = require(PATH .. 'util') -- Class local DirectionPicker = class("DirectionPicker") function DirectionPicker:initialize(previousDirection, changeDirectionModifier) -- Initialize all available directions self.availableDirections = { DirectionType.North, DirectionType.South, DirectionType.East, DirectionType.West } self.previousDirection = previousDirection self.changeDirectionModifier = changeDirectionModifier self.directionsPicked = {} end function DirectionPicker:PickDifferentDirection() -- Filter out previously picked directions and the current direction local validDirections = {} for _, dir in ipairs(self.availableDirections) do if dir ~= self.previousDirection and not Util:tablecontains(self.directionsPicked, dir) then table.insert(validDirections, dir) end end -- If no valid directions, return any unpicked direction if #validDirections == 0 then for _, dir in ipairs(self.availableDirections) do if not Util:tablecontains(self.directionsPicked, dir) then return dir end end -- If all directions are picked, return the previous direction as last resort return self.previousDirection end -- Return random valid direction return validDirections[math.random(1, #validDirections)] end function DirectionPicker:GetNextDirection() if not self:HasNextDirection() then return nil end local directionPicked if self:MustChangeDirection() then directionPicked = self:PickDifferentDirection() else directionPicked = self.previousDirection end table.insert(self.directionsPicked, directionPicked) return directionPicked end function DirectionPicker:HasNextDirection() return #self.directionsPicked < 4 end function DirectionPicker:MustChangeDirection() return (#self.directionsPicked > 0) or (self.changeDirectionModifier > math.random(0, 99)) end return DirectionPicker ================================================ FILE: astray/dungeon.lua ================================================ --[[ Copyright (c) <''2014''> <''Florian Fischer''> This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ]]-- local PATH = (...):match("(.-)[^%.]+$") local class = require(PATH .. 'MiddleClass') local Util = require(PATH .. 'util') local Point = require(PATH .. 'point') local Map = require(PATH .. 'map') -- Class local Dungeon = class("Dungeon", Map) function Dungeon:initialize( width, height ) Map.initialize(self, width, height) -- invoking the superclass' initializer self.visitedCells = {} self.rooms = {} end function Dungeon:AddRoom( room ) table.insert(self.rooms, room ) end function Dungeon:FlagAllCellsAsUnvisited() for key,location in pairs( self:getCellLocations() ) do self:getCell(location):setVisited(false) end end -- return point function Dungeon:PickRandomCellAndFlagItAsVisited() local randomLocation = Point:new(math.random(0,self:getWidth() - 1), math.random(0,self:getHeight() - 1)) self:FlagCellAsVisited(randomLocation) return randomLocation end -- return boolean function Dungeon:AdjacentCellInDirectionIsVisited( location, direction ) local target = self:GetTargetLocation(location, direction) if target == nil then return false end if direction == DirectionType.North then return self:getCell(target):getVisited() elseif direction == DirectionType.West then return self:getCell(target):getVisited() elseif direction == DirectionType.South then return self:getCell(target):getVisited() elseif direction == DirectionType.East then return self:getCell(target):getVisited() else print('ERROR: InvalidOperationException (Dungeon:AdjacentCellInDirectionIsVisited)') return nil end end -- return boolean function Dungeon:AdjacentCellInDirectionIsCorridor( location, direction ) local target = self:GetTargetLocation(location, direction) if target == nil then return false end if direction == DirectionType.North then return self:getCell(target):getIsCorridor() elseif direction == DirectionType.West then return self:getCell(target):getIsCorridor() elseif direction == DirectionType.South then return self:getCell(target):getIsCorridor() elseif direction == DirectionType.East then return self:getCell(target):getIsCorridor() else print('ERROR: InvalidOperationException (Dungeon:AdjacentCellInDirectionIsCorridor)') return false end end function Dungeon:FlagCellAsVisited( location ) if not Util:rectbound(location, self:getBounds() ) then print('ERROR: Location is outside of Dungeon bounds') end if self:getCell(location):getVisited() then print('ERROR: Location is already visited') end self:getCell(location):setVisited(true) table.insert( self.visitedCells, location ) end -- return point function Dungeon:GetRandomVisitedCell( location ) if (#self.visitedCells == 0) then print("ERROR: There are no visited cells to return.") return nil end local index = math.random(#self.visitedCells-1) -- Loop while the current cell is the visited cell while (self.visitedCells[index].X == location.X and self.visitedCells[index].Y == location.Y) do index = math.random(#self.visitedCells - 1) end return self.visitedCells[index] end -- return point function Dungeon:CreateCorridor( location, direction ) local targetLocation = self:CreateSide(location, direction, SideType.Empty) self:getCell(location):setIsCorridor(true) -- Set current location to corridor self:getCell(targetLocation):setIsCorridor(true) --Set target location to corridor return targetLocation end -- return point function Dungeon:CreateWall( location, direction ) return self:CreateSide(location, direction, SideType.Wall) end -- return point function Dungeon:CreateDoor( location, direction ) return self:CreateSide(location, direction, SideType.Door) end -- return point function Dungeon:CreateSide( location, direction, sideType ) local target = self:GetTargetLocation(location, direction) if (target == nil) then print('ERROR: ArgumentException("There is no adjacent cell in the given direction", "location")') end if direction == DirectionType.North then self:getCell(location):setNorthSide( sideType ) self:getCell(target):setSouthSide( sideType ) elseif direction == DirectionType.South then self:getCell(location):setSouthSide( sideType ) self:getCell(target):setNorthSide( sideType ) elseif direction == DirectionType.West then self:getCell(location):setWestSide( sideType ) self:getCell(target):setEastSide( sideType ) elseif direction == DirectionType.East then self:getCell(location):setEastSide( sideType ) self:getCell(target):setWestSide( sideType ) end return target end ------------------------------------------------------ -- helper functions ------------------------------------------------------ -- return boolean function Dungeon:AllCellsAreVisited() return #self.visitedCells == ( self:getWidth() * self:getHeight() ) end -- IEnumerable function Dungeon:DeadEndCellLocations() local deadEndpointList = {} for key,point in pairs( self:getCellLocations() ) do if self:getCell(point):getIsDeadEnd() then table.insert(deadEndpointList, point) end end return deadEndpointList end function Dungeon:CorridorCellLocations() local corridorPointList = {} for key,point in pairs( self:getCellLocations() ) do if self:getCell(point):getIsCorridor() then table.insert(corridorPointList, point) end end return corridorPointList end return Dungeon ================================================ FILE: astray/init.lua ================================================ local BASE = (...) .. '.' return { Astray = require(BASE .. 'astray'), RoomGenerator = require(BASE .. 'roomgenerator'), } ================================================ FILE: astray/map.lua ================================================ --[[ Copyright (c) <''2014''> <''Florian Fischer''> This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ]]-- local PATH = (...):match("(.-)[^%.]+$") local class = require(PATH .. 'MiddleClass') local Util = require(PATH .. 'util') local Point = require(PATH .. 'point') local Cell = require(PATH .. 'cell') -- Class local Map = class("Map") function Map:initialize( width, height ) self.cells = {} self.bounds = { X=0, Y=0, Width=width, Height=height } self.cellLocations = {} -- Add cache -- Initialize cells and cache locations for x = 0, self.bounds.Width-1 do self.cells[x] = {} for y = 0, self.bounds.Height-1 do self.cells[x][y] = Cell:new() table.insert(self.cellLocations, Point:new(x,y)) end end end function Map:HasAdjacentCellInDirection( location, direction) -- Check that the location falls within the bounds of the map if not Util:rectbound(location, self.bounds) then print('ERROR: Map:HasAdjacentCellInDirection: not rectbound!!') return false end -- Check if there is an adjacent cell in the direction if direction == DirectionType.North then return location.Y > 0 elseif direction == DirectionType.South then return location.Y < (self:getHeight() - 1) elseif direction == DirectionType.West then return location.X > 0 elseif direction == DirectionType.East then return location.X < (self:getWidth() - 1) else print('ERROR: Map:HasAdjacentCellInDirection') return false end end -- return point function Map:GetTargetLocation( location, direction) if not self:HasAdjacentCellInDirection(location, direction) then return nil end if direction == DirectionType.North then return Point:new(location.X, location.Y - 1) elseif direction == DirectionType.West then return Point:new(location.X - 1, location.Y) elseif direction == DirectionType.South then return Point:new(location.X, location.Y + 1) elseif direction == DirectionType.East then return Point:new(location.X + 1, location.Y) else print('ERROR: InvalidOperationException (Map:GetTargetLocation)') return nil end end ------------------------------------------------------ -- helper functions ------------------------------------------------------ function Map:getBounds() return self.bounds end function Map:getCell( point ) return self.cells[point.X][point.Y] end function Map:setCell( point, value ) self.cells[point.X][point.Y] = value end function Map:getWidth() return self.bounds.Width end function Map:getHeight() return self.bounds.Height end function Map:getCellLocations() return self.cellLocations end return Map ================================================ FILE: astray/point.lua ================================================ --[[ Copyright (c) <''2014''> <''Florian Fischer''> This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ]]-- local PATH = (...):match("(.-)[^%.]+$") local class = require(PATH .. 'MiddleClass') -- Class local Point = class("Point") function Point:initialize( x, y ) self.X = x self.Y = y end return Point ================================================ FILE: astray/room.lua ================================================ --[[ Copyright (c) <''2014''> <''Florian Fischer''> This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ]]-- local PATH = (...):match("(.-)[^%.]+$") local class = require(PATH .. 'MiddleClass') local Cell = require(PATH .. 'cell') local Map = require(PATH .. 'map') -- Class local Room = class("Room", Map) function Room:initialize( width, height ) Map.initialize(self, width, height) -- invoking the superclass' initializer end function Room:InitializeRoomCells() for key,location in pairs( self:getCellLocations() ) do local cell = Cell:new() if (location.X == self.bounds.X) then cell.WestSide = SideType.Wall else cell.WestSide = SideType.Empty end if (location.X == self.bounds.Width - 1) then cell.EastSide = SideType.Wall else cell.EastSide = SideType.Empty end if (location.Y == self.bounds.Y) then cell.NorthSide = SideType.Wall else cell.NorthSide = SideType.Empty end if (location.Y == self.bounds.Height - 1) then cell.SouthSide = SideType.Wall else cell.SouthSide = SideType.Empty end self:setCell( location, cell ) end end function Room:SetLocation( location ) self.bounds = { X=location.X, Y=location.Y, Width=self.bounds.Width, Height=self.bounds.Height } end return Room ================================================ FILE: astray/roomgenerator.lua ================================================ --[[ Copyright (c) <''2014''> <''Florian Fischer''> This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ]]-- local PATH = (...):match("(.-)[^%.]+$") local class = require(PATH .. 'MiddleClass') local Util = require(PATH .. 'util') local Point = require(PATH .. 'point') local Room = require(PATH .. 'room') -- Class local RoomGenerator = class("RoomGenerator") function RoomGenerator:initialize(noOfRoomsToPlace, minRoomWidth, maxRoomWidth, minRoomHeight, maxRoomHeight) self.noOfRoomsToPlace = noOfRoomsToPlace or 10 self.minRoomWidth = minRoomWidth or 1 self.maxRoomWidth = maxRoomWidth or 6 self.minRoomHeight = minRoomHeight or 1 self.maxRoomHeight = maxRoomHeight or 6 end function RoomGenerator:CreateRoom() local room = Room:new( math.random(self.minRoomWidth, self.maxRoomWidth), math.random(self.minRoomHeight, self.maxRoomHeight) ) room:InitializeRoomCells() return room end function RoomGenerator:PlaceRooms( dungeon ) -- Loop for the amount of rooms to place for roomCounter = 0, self.noOfRoomsToPlace-1 do local room = self:CreateRoom() local bestRoomPlacementScore = MaxValue local bestRoomPlacementLocation = nil for key,currentRoomPlacementLocation in pairs( dungeon:CorridorCellLocations() ) do local currentRoomPlacementScore = self:CalculateRoomPlacementScore( currentRoomPlacementLocation, room, dungeon ) if (currentRoomPlacementScore < bestRoomPlacementScore) then bestRoomPlacementScore = currentRoomPlacementScore bestRoomPlacementLocation = currentRoomPlacementLocation end end -- Create room at best room placement cell if bestRoomPlacementLocation ~= nil then self:PlaceRoom( bestRoomPlacementLocation, room, dungeon ) end end end function RoomGenerator:CalculateRoomPlacementScore( location, room, dungeon ) -- Check if the room at the given location will fit inside the bounds of the map if Util:rectbound( {X=location.X, Y=location.Y, Width=room:getWidth(), Height=room:getHeight()}, dungeon:getBounds() ) then local roomPlacementScore = 0 -- Loop for each cell in the room for key,roomLocation in pairs( room:getCellLocations() ) do --Translate the room cell location to its location in the dungeon local dungeonLocation = Point:new(location.X + roomLocation.X, location.Y + roomLocation.Y); -- Add 1 point for each adjacent corridor to the cell if dungeon:AdjacentCellInDirectionIsCorridor(dungeonLocation, DirectionType.North) then roomPlacementScore = roomPlacementScore + 1 end if dungeon:AdjacentCellInDirectionIsCorridor(dungeonLocation, DirectionType.South) then roomPlacementScore = roomPlacementScore + 1 end if dungeon:AdjacentCellInDirectionIsCorridor(dungeonLocation, DirectionType.West) then roomPlacementScore = roomPlacementScore + 1 end if dungeon:AdjacentCellInDirectionIsCorridor(dungeonLocation, DirectionType.East) then roomPlacementScore = roomPlacementScore + 1 end -- Add 3 points if the cell overlaps an existing corridor if (dungeon:getCell(dungeonLocation):getIsCorridor()) then roomPlacementScore = roomPlacementScore + 3 end -- Add 100 points if the cell overlaps any existing room cells for key,dungeonRoom in pairs(dungeon.rooms) do if Util:rectbound( dungeonLocation, dungeonRoom:getBounds() ) then roomPlacementScore = roomPlacementScore + 100 end end end return roomPlacementScore else return MaxValue end end function RoomGenerator:PlaceRoom( location, room, dungeon ) -- Offset the room origin to the new location room:SetLocation(location) -- Loop for each cell in the room for key,roomLocation in pairs( room:getCellLocations() ) do -- Translate the room cell location to its location in the dungeon local dungeonLocation = Point:new(location.X + roomLocation.X, location.Y + roomLocation.Y) dungeon:getCell(dungeonLocation):setNorthSide( room:getCell(roomLocation):getNorthSide() ) dungeon:getCell(dungeonLocation):setSouthSide( room:getCell(roomLocation):getSouthSide() ) dungeon:getCell(dungeonLocation):setWestSide( room:getCell(roomLocation):getWestSide() ) dungeon:getCell(dungeonLocation):setEastSide( room:getCell(roomLocation):getEastSide() ) -- Create room walls on map (either side of the wall) if (roomLocation.X == 0) and (dungeon:HasAdjacentCellInDirection(dungeonLocation, DirectionType.West)) then dungeon:CreateWall( dungeonLocation, DirectionType.West ) end if (roomLocation.X == room:getWidth() - 1) and (dungeon:HasAdjacentCellInDirection(dungeonLocation, DirectionType.East)) then dungeon:CreateWall( dungeonLocation, DirectionType.East ) end if (roomLocation.Y == 0) and (dungeon:HasAdjacentCellInDirection(dungeonLocation, DirectionType.North)) then dungeon:CreateWall( dungeonLocation, DirectionType.North ) end if (roomLocation.Y == room:getHeight() - 1) and (dungeon:HasAdjacentCellInDirection(dungeonLocation, DirectionType.South)) then dungeon:CreateWall( dungeonLocation, DirectionType.South ) end end dungeon:AddRoom(room) end -- TODO: check if Door has adjancent Walls beside! function RoomGenerator:PlaceDoors( dungeon ) for key,room in pairs(dungeon.rooms) do local hasNorthDoor = false local hasSouthDoor = false local hasWestDoor = false local hasEastDoor = false for key,cellLocation in pairs( room:getCellLocations() ) do -- Translate the room cell location to its location in the dungeon local dungeonLocation = Point:new(room:getBounds().X + cellLocation.X, room:getBounds().Y + cellLocation.Y) -- Check if we are on the west boundary of our room -- and if there is a corridor to the west if (cellLocation.X == 0) and (dungeon:AdjacentCellInDirectionIsCorridor(dungeonLocation, DirectionType.West)) and (not hasWestDoor) then dungeon:CreateDoor(dungeonLocation, DirectionType.West) hasWestDoor = true end -- Check if we are on the east boundary of our room -- and if there is a corridor to the east if (cellLocation.X == room:getWidth() - 1) and (dungeon:AdjacentCellInDirectionIsCorridor(dungeonLocation, DirectionType.East)) and (not hasEastDoor) then dungeon:CreateDoor(dungeonLocation, DirectionType.East) hasEastDoor = true end -- Check if we are on the north boundary of our room -- and if there is a corridor to the north if (cellLocation.Y == 0) and (dungeon:AdjacentCellInDirectionIsCorridor(dungeonLocation, DirectionType.North)) and (not hasNorthDoor) then dungeon:CreateDoor(dungeonLocation, DirectionType.North) hasNorthDoor = true end -- Check if we are on the south boundary of our room -- and if there is a corridor to the south if (cellLocation.Y == room:getHeight() - 1) and (dungeon:AdjacentCellInDirectionIsCorridor(dungeonLocation, DirectionType.South)) and (not hasSouthDoor) then dungeon:CreateDoor(dungeonLocation, DirectionType.South) hasSouthDoor = true end end end end ------------------------------------------------------ -- helper functions ------------------------------------------------------ function RoomGenerator:getNoOfRoomsToPlace() return self.noOfRoomsToPlace end function RoomGenerator:setNoOfRoomsToPlace( noOfRoomsToPlace ) self.noOfRoomsToPlace = noOfRoomsToPlace end function RoomGenerator:MinRoomWidth() return self.minRoomWidth end function RoomGenerator:MinRoomWidth( minRoomWidth ) self.minRoomWidth = minRoomWidth end function RoomGenerator:MaxRoomWidth() return self.maxRoomWidth end function RoomGenerator:MaxRoomWidth( maxRoomWidth ) self.maxRoomWidth = maxRoomWidth end function RoomGenerator:MinRoomHeight() return self.minRoomHeight end function RoomGenerator:MinRoomHeight( minRoomHeight ) self.minRoomHeight = minRoomHeight end function RoomGenerator:MaxRoomHeight() return self.maxRoomHeight end function RoomGenerator:MaxRoomHeight( maxRoomHeight ) self.maxRoomHeight = maxRoomHeight end return RoomGenerator ================================================ FILE: astray/util.lua ================================================ --[[ Copyright (c) <''2014''> <''Florian Fischer''> This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ]]-- local PATH = (...):match("(.-)[^%.]+$") local class = require(PATH .. 'MiddleClass') -- Class local Util = class("Util") -- option1: check if point is in rect bound -- option2: check if bound is in rect bound function Util:rectbound( bounds1, bounds2) if bounds1.Width and bounds1.Height then local inbound = false local x1,y1 = bounds1.X, bounds1.Y local width1, height1 = bounds1.Width, bounds1.Height local x2,y2 = bounds2.X, bounds2.Y local width2, height2 = bounds2.Width, bounds2.Height if x1 >= x2 and x1 <= x2+width2 and x1+width1 <= x2+width2 and y1 >= y2 and y1 <= y2+height2 and y1+height1 <= y2+height2 then inbound = true end return inbound else local inbound = false local x,y = bounds1.X, bounds1.Y local x2 = bounds2.X local y2 = bounds2.Y local width2, height2 = bounds2.Width, bounds2.Height if x >= x2 and x <= x2+width2 and y >= y2 and y <= y2+height2 then inbound = true end return inbound end end function Util:tablecontains(table, element) for _, value in pairs(table) do if value == element then return true end end return false end return Util:new() ================================================ FILE: conf.lua ================================================ function love.conf(t) t.title = "Astray - random dungeon generation library" -- The title of the window the game is in (string) t.author = "Florian Fischer^SiENcE" -- The author of the game (string) t.identity = "astray" -- The name of the save directory (string) -- t.version = 0 -- The LÖVE version this game was made for (number) t.console = true -- Attach a console (boolean, Windows only) t.screen = {} t.screen.width = 800 -- The window width (number) t.screen.height = 600 -- The window height (number) t.screen.fullscreen = false -- Enable fullscreen (boolean) t.screen.vsync = false -- Enable vertical sync (boolean) t.screen.fsaa = 0 -- The number of FSAA-buffers (number) t.modules.joystick = false -- Enable the joystick module (boolean) t.modules.audio = true -- Enable the audio module (boolean) t.modules.keyboard = true -- Enable the keyboard module (boolean) t.modules.event = true -- Enable the event module (boolean) t.modules.image = true -- Enable the image module (boolean) t.modules.graphics = true -- Enable the graphics module (boolean) t.modules.timer = true -- Enable the timer module (boolean) t.modules.mouse = true -- Enable the mouse module (boolean) t.modules.sound = true -- Enable the sound module (boolean) t.modules.physics = false -- Enable the physics module (boolean) end ================================================ FILE: main.lua ================================================ --[[ Copyright (c) <''2024''> <''Florian Fischer''> ]]-- local astray = require('astray') local symbols = {Wall='#', Empty=' ', DoorN='|', DoorS='|', DoorE='-', DoorW='-'} function drawdungeon(tiles, startx, starty, width, height) -- we have to add +1 for the 0 rows print("Map size=", #tiles+1-startx, #tiles[1]+1-starty ) for y = startx, height do local line = '' for x = starty, width do line = line .. tiles[y][x] end print(line) end print('') end function updatewalls(tiles, width, height) local block = false for y = 0, height do for x = 0, width do if tiles[x][y] == '#' then if block then tiles[x][y] = '#' else tiles[x][y] = 'O' end end block = not block end end return tiles end function fixTiles( tiles, width, height ) local fixed_tiles = {} for y = 0, height do for x = 0, width do if not fixed_tiles[y+1] then fixed_tiles[y+1] = {} end fixed_tiles[y+1][x+1] = tiles[y][x] end end return fixed_tiles end print('Astay Sample\n') print('Automatic\n------------------------------------------\n') -- NOTE: This maze generator only accepts even number and can only generate uneven maps! -- So when you input 40, 40 you get a -> 39,39 map. local height, width = 40, 40 -- Function Header: Astray:new(width/2-1, height/2-1, changeDirectionModifier (1-30), sparsenessModifier (25-70), deadEndRemovalModifier (70-99) ) | RoomGenerator:new(rooms, minWidth, maxWidth, minHeight, maxHeight) local generator = astray.Astray:new( height/2-1, width/2-1, 30, 70, 50, astray.RoomGenerator:new(4, 2, 4, 2, 4) ) -- original setup --local generator = astray.Astray:new( 25, 25, 30, 70, 50, astray.RoomGenerator:new(10,1,5,1,5) ) local dungeon = generator:Generate() --local tiles = generator:CellToTiles(dungeon, symbols) local tiles = generator:CellToTiles(dungeon) -- to alternate between two wall-types updatewalls(tiles, #tiles, #tiles[1] ) -- draw on console drawdungeon(tiles, 0, 0, #tiles, #tiles[1] ) -- fix array index to 1 (instead of 0) local fixed_tiles = fixTiles( tiles, #tiles, #tiles[1] ) -- draw on console drawdungeon(fixed_tiles, 1, 1, #fixed_tiles, #fixed_tiles[1] )