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
[](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.
<p align="center">
<a href="https://raw.githubusercontent.com/SiENcE/astray/master/sample.png">
<img border="0" src="https://raw.githubusercontent.com/SiENcE/astray/master/sample.png">
</a>
</p>
## 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<Point>
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] )
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
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (62K chars).
[
{
"path": "LICENSE",
"chars": 880,
"preview": "Copyright (c) <''2014''> <''Florian Fischer''>\n\nThis software is provided 'as-is', without any express or implied\nwarran"
},
{
"path": "README.md",
"chars": 6013,
"preview": "# Astray\n\n[](https://opensource.org/licenses/"
},
{
"path": "astray/MiddleClass.lua",
"chars": 6058,
"preview": "local middleclass = {\n _VERSION = 'middleclass v3.0.0',\n _DESCRIPTION = 'Object Orientation for Lua',\n _LICENSE "
},
{
"path": "astray/astray.lua",
"chars": 9446,
"preview": "--[[\nCopyright (c) <''2024''> <''Florian Fischer''>\n\nThis software is provided 'as-is', without any express or implied\nw"
},
{
"path": "astray/cell.lua",
"chars": 3111,
"preview": "--[[\nCopyright (c) <''2014''> <''Florian Fischer''>\n\nThis software is provided 'as-is', without any express or implied\nw"
},
{
"path": "astray/directionpicker.lua",
"chars": 3036,
"preview": "--[[\nCopyright (c) <''2014''> <''Florian Fischer''>\n\nThis software is provided 'as-is', without any express or implied\nw"
},
{
"path": "astray/dungeon.lua",
"chars": 6211,
"preview": "--[[\nCopyright (c) <''2014''> <''Florian Fischer''>\n\nThis software is provided 'as-is', without any express or implied\nw"
},
{
"path": "astray/init.lua",
"chars": 132,
"preview": "local BASE = (...) .. '.'\n\nreturn {\n\tAstray = require(BASE .. 'astray'),\n\tRoomGenerator = require(BASE .. 'roomgen"
},
{
"path": "astray/map.lua",
"chars": 3417,
"preview": "--[[\nCopyright (c) <''2014''> <''Florian Fischer''>\n\nThis software is provided 'as-is', without any express or implied\nw"
},
{
"path": "astray/point.lua",
"chars": 1093,
"preview": "--[[\nCopyright (c) <''2014''> <''Florian Fischer''>\n\nThis software is provided 'as-is', without any express or implied\nw"
},
{
"path": "astray/room.lua",
"chars": 2045,
"preview": "--[[\nCopyright (c) <''2014''> <''Florian Fischer''>\n\nThis software is provided 'as-is', without any express or implied\nw"
},
{
"path": "astray/roomgenerator.lua",
"chars": 8713,
"preview": "--[[\nCopyright (c) <''2014''> <''Florian Fischer''>\n\nThis software is provided 'as-is', without any express or implied\nw"
},
{
"path": "astray/util.lua",
"chars": 2050,
"preview": "--[[\nCopyright (c) <''2014''> <''Florian Fischer''>\n\nThis software is provided 'as-is', without any express or implied\nw"
},
{
"path": "conf.lua",
"chars": 1476,
"preview": "function love.conf(t)\n t.title = \"Astray - random dungeon generation library\"\t\t-- The title of the window the game is"
},
{
"path": "main.lua",
"chars": 2173,
"preview": "--[[\nCopyright (c) <''2024''> <''Florian Fischer''>\n]]--\n\nlocal astray = require('astray')\n\nlocal symbols = {Wall='#', E"
}
]
About this extraction
This page contains the full source code of the SiENcE/astray GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (54.5 KB), approximately 15.1k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.