Full Code of SiENcE/astray for AI

master ea48e9ac9731 cached
15 files
54.5 KB
15.1k tokens
1 requests
Download .txt
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.

<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] )
Download .txt
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[![License: Zlib](https://img.shields.io/badge/License-Zlib-brightgreen.svg)](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.

Copied to clipboard!