├── .gitattributes ├── .gitignore ├── LICENSE.txt ├── README.md ├── conf.lua ├── doc ├── examples │ ├── action.lua.html │ ├── arena.lua.html │ ├── astar.lua.html │ ├── bresenham.lua.html │ ├── brogue.lua.html │ ├── broguecavegeneration.lua.html │ ├── cellular.lua.html │ ├── characters.lua.html │ ├── color.lua.html │ ├── dice.lua.html │ ├── dice_advanced_uses.lua.html │ ├── dice_basic.lua.html │ ├── dice_boundry_behaviour.lua.html │ ├── dice_metamethods.lua.html │ ├── dice_minimum.lua.html │ ├── dice_valid_notation.lua.html │ ├── dice_weapon_samples.lua.html │ ├── digger.lua.html │ ├── dijkstra.lua.html │ ├── dijkstramap.lua.html │ ├── dividedmaze.lua.html │ ├── drawtextdisplay.lua.html │ ├── drawtexttextdisplay.lua.html │ ├── ellermaze.lua.html │ ├── engine.lua.html │ ├── event.lua.html │ ├── iceymaze.lua.html │ ├── lighting.lua.html │ ├── precise.lua.html │ ├── precisewithmovingplayer.lua.html │ ├── rng.lua.html │ ├── rogue.lua.html │ ├── simple.lua.html │ ├── simplex.lua.html │ ├── speed.lua.html │ ├── stringgen.lua.html │ ├── textdisplay.lua.html │ └── uniform.lua.html ├── index.html ├── ldoc.css └── modules │ ├── ROT.Color.html │ ├── ROT.Dice.html │ ├── ROT.DijkstraMap.html │ ├── ROT.Display.html │ ├── ROT.EventQueue.html │ ├── ROT.FOV.Bresenham.html │ ├── ROT.FOV.Precise.html │ ├── ROT.FOV.Recursive.html │ ├── ROT.Lighting.html │ ├── ROT.Map.Arena.html │ ├── ROT.Map.Brogue.html │ ├── ROT.Map.BrogueRoom.html │ ├── ROT.Map.Cellular.html │ ├── ROT.Map.Corridor.html │ ├── ROT.Map.Digger.html │ ├── ROT.Map.DividedMaze.html │ ├── ROT.Map.Dungeon.html │ ├── ROT.Map.EllerMaze.html │ ├── ROT.Map.IceyMaze.html │ ├── ROT.Map.Rogue.html │ ├── ROT.Map.Room.html │ ├── ROT.Map.Uniform.html │ ├── ROT.Noise.Simplex.html │ ├── ROT.Path.AStar.html │ ├── ROT.Path.Dijkstra.html │ ├── ROT.RNG.html │ ├── ROT.Scheduler.Action.html │ ├── ROT.Scheduler.Simple.html │ ├── ROT.Scheduler.Speed.html │ ├── ROT.Scheduler.html │ ├── ROT.StringGenerator.html │ ├── ROT.Text.html │ └── ROT.TextDisplay.html ├── examples ├── action.lua ├── arena.lua ├── astar.lua ├── bresenham.lua ├── brogue.lua ├── brogueCaveGeneration.lua ├── cellular.lua ├── characters.lua ├── color.lua ├── dice.lua ├── dice_advanced_uses.lua ├── dice_basic.lua ├── dice_boundry_behaviour.lua ├── dice_metamethods.lua ├── dice_minimum.lua ├── dice_valid_notation.lua ├── dice_weapon_samples.lua ├── digger.lua ├── dijkstra.lua ├── dijkstraMap.lua ├── dividedMaze.lua ├── drawtextDisplay.lua ├── drawtextTextDisplay.lua ├── ellerMaze.lua ├── engine.lua ├── event.lua ├── iceyMaze.lua ├── lighting.lua ├── precise.lua ├── preciseWithMovingPlayer.lua ├── rng.lua ├── rogue.lua ├── simple.lua ├── simplex.lua ├── speed.lua ├── stringGen.lua ├── textDisplay.lua └── uniform.lua ├── main.lua ├── src ├── img │ └── cp437.png ├── rot.lua └── rot │ ├── class.lua │ ├── color.lua │ ├── dice.lua │ ├── display.lua │ ├── engine.lua │ ├── eventQueue.lua │ ├── fov.lua │ ├── fov │ ├── bresenham.lua │ ├── precise.lua │ └── recursive.lua │ ├── lighting.lua │ ├── map.lua │ ├── map │ ├── arena.lua │ ├── brogue.lua │ ├── brogueRoom.lua │ ├── cellular.lua │ ├── corridor.lua │ ├── digger.lua │ ├── dividedMaze.lua │ ├── dungeon.lua │ ├── ellerMaze.lua │ ├── feature.lua │ ├── iceyMaze.lua │ ├── rogue.lua │ ├── room.lua │ └── uniform.lua │ ├── newFuncs.lua │ ├── noise.lua │ ├── noise │ └── simplex.lua │ ├── path.lua │ ├── path │ ├── astar.lua │ ├── dijkstra.lua │ └── dijkstraMap.lua │ ├── rng.lua │ ├── scheduler.lua │ ├── scheduler │ ├── action.lua │ ├── simple.lua │ └── speed.lua │ ├── stringGenerator.lua │ ├── text.lua │ ├── textDisplay.lua │ └── type │ ├── grid.lua │ └── pointSet.lua └── tests ├── expect.lua ├── helper.lua ├── readme └── spec ├── color.lua ├── dungeon.lua ├── engine.lua ├── eventQueue.lua ├── fov.lua ├── path.lua ├── rng.lua ├── scheduler.lua └── text.lua /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Demos/ 2 | *.js 3 | config.ld 4 | ################# 5 | ## Eclipse 6 | ################# 7 | 8 | *.pydevproject 9 | .project 10 | .metadata 11 | bin/ 12 | tmp/ 13 | *.tmp 14 | *.bak 15 | *.swp 16 | *~.nib 17 | local.properties 18 | .classpath 19 | .settings/ 20 | .loadpath 21 | 22 | # External tool builders 23 | .externalToolBuilders/ 24 | 25 | # Locally stored "Eclipse launch configurations" 26 | *.launch 27 | 28 | # CDT-specific 29 | .cproject 30 | 31 | # PDT-specific 32 | .buildpath 33 | 34 | 35 | ################# 36 | ## Visual Studio 37 | ################# 38 | 39 | ## Ignore Visual Studio temporary files, build results, and 40 | ## files generated by popular Visual Studio add-ons. 41 | 42 | # User-specific files 43 | *.suo 44 | *.user 45 | *.sln.docstates 46 | 47 | # Build results 48 | [Dd]ebug/ 49 | [Rr]elease/ 50 | *_i.c 51 | *_p.c 52 | *.ilk 53 | *.meta 54 | *.obj 55 | *.pch 56 | *.pdb 57 | *.pgc 58 | *.pgd 59 | *.rsp 60 | *.sbr 61 | *.tlb 62 | *.tli 63 | *.tlh 64 | *.tmp 65 | *.vspscc 66 | .builds 67 | *.dotCover 68 | 69 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 70 | #packages/ 71 | 72 | # Visual C++ cache files 73 | ipch/ 74 | *.aps 75 | *.ncb 76 | *.opensdf 77 | *.sdf 78 | 79 | # Visual Studio profiler 80 | *.psess 81 | *.vsp 82 | 83 | # ReSharper is a .NET coding add-in 84 | _ReSharper* 85 | 86 | # Installshield output folder 87 | [Ee]xpress 88 | 89 | # DocProject is a documentation generator add-in 90 | DocProject/buildhelp/ 91 | DocProject/Help/*.HxT 92 | DocProject/Help/*.HxC 93 | DocProject/Help/*.hhc 94 | DocProject/Help/*.hhk 95 | DocProject/Help/*.hhp 96 | DocProject/Help/Html2 97 | DocProject/Help/html 98 | 99 | # Click-Once directory 100 | publish 101 | 102 | # Others 103 | [Bb]in 104 | [Oo]bj 105 | sql 106 | TestResults 107 | *.Cache 108 | ClientBin 109 | stylecop.* 110 | ~$* 111 | *.dbmdl 112 | Generated_Code #added for RIA/Silverlight projects 113 | 114 | # Backup & report files from converting an old project file to a newer 115 | # Visual Studio version. Backup files are not needed, because we have git ;-) 116 | _UpgradeReport_Files/ 117 | Backup*/ 118 | UpgradeLog*.XML 119 | 120 | 121 | 122 | ############ 123 | ## Windows 124 | ############ 125 | 126 | # Windows image file caches 127 | Thumbs.db 128 | 129 | # Folder config file 130 | Desktop.ini 131 | 132 | 133 | ############# 134 | ## Python 135 | ############# 136 | 137 | *.py[co] 138 | 139 | # Packages 140 | *.egg 141 | *.egg-info 142 | dist 143 | build 144 | eggs 145 | parts 146 | bin 147 | var 148 | sdist 149 | develop-eggs 150 | .installed.cfg 151 | 152 | # Installer logs 153 | pip-log.txt 154 | 155 | # Unit test / coverage reports 156 | .coverage 157 | .tox 158 | 159 | #Translations 160 | *.mo 161 | 162 | #Mr Developer 163 | .mr.developer.cfg 164 | 165 | # Mac crap 166 | .DS_Store 167 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | -- ROT.LOVE -- License 2 | --[[ 3 | rotLove is Copyright (c) 2013 Paul Lewis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | --]] 24 | 25 | -- ROT.JS -- License 26 | 27 | -- While not verbatim, MUCH (most (the vast majority)) of the code involved in this project is derived from rot.js 28 | -- rot.js is a wonderful javascript-based imagining of the libtcod library that many popular 29 | -- roguelike developers use to make their lives easier 30 | -- Keeping in mind the aforementioned derivation, I feel it necessary to include the rot.js license. 31 | --[[-------------------------------------------------------------------------------------- 32 | rot.js is Copyright (c) 2012-now(), Ondrej Zara 33 | All rights reserved. 34 | 35 | Redistribution and use in source and binary forms, with or without modification, 36 | are permitted provided that the following conditions are met: 37 | 38 | * Redistributions of source code must retain the above copyright notice, 39 | this list of conditions and the following disclaimer. 40 | * Redistributions in binary form must reproduce the above copyright notice, 41 | this list of conditions and the following disclaimer in the documentation 42 | and/or other materials provided with the distribution. 43 | * Neither the name of Ondrej Zara nor the names of its contributors may be used 44 | to endorse or promote products derived from this software without specific 45 | prior written permission. 46 | 47 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 48 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 49 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 50 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 51 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 52 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 53 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 54 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 55 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 56 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 57 | --]]-------------------------------------------------------------------------------------- 58 | 59 | -- Damn near all of this would be laughably disfunctional if not for 30log. 60 | -- 30log is a OOP solution for lua, created by Roland Yonaba. It's excellent for this sort of thing. 61 | --[[--------------------------------------------------------------------------- 62 | 30log is Copyright (c) 2012 Roland Yonaba 63 | 64 | Permission is hereby granted, free of charge, to any person obtaining a 65 | copy of this software and associated documentation files (the 66 | "Software"), to deal in the Software without restriction, including 67 | without limitation the rights to use, copy, modify, merge, publish, 68 | distribute, sublicense, and/or sell copies of the Software, and to 69 | permit persons to whom the Software is furnished to do so, subject to 70 | the following conditions: 71 | 72 | The above copyright notice and this permission notice shall be included 73 | in all copies or substantial portions of the Software. 74 | 75 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 76 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 77 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 78 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 79 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 80 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 81 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 82 | --]]--------------------------------------------------------------------------- 83 | 84 | -- ROT.RNG includes code from RandomLua (that is, it's basically RandomLua plus some stuff I added.) 85 | -- Following is the RandomLua license 86 | --[[------------------------------------ 87 | RandomLua v0.3.1 88 | Pure Lua Pseudo-Random Numbers Generator 89 | Under the MIT license. 90 | copyright(c) 2011 linux-man 91 | --]]------------------------------------ 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RogueLike Toolkit in Love 2 | ========= 3 | Bringing [rot.js](http://ondras.github.io/rot.js/hp/) functionality to Love2D. The only modules that require Love2D are the display modules. 4 | 5 | See [this page](http://paulofmandown.github.io/rotLove/) for a quick and dirty run down of all the functionality provided. 6 | 7 | Included: 8 | 9 | * Display - via [rlLove](https://github.com/paulofmandown/rlLove), only supports cp437 emulation 10 | rather than full font support. 11 | * TextDisplay - Text based display, accepts supplied fonts 12 | * RNG - via [RandomLua](http://love2d.org/forums/viewtopic.php?f=5&t=3424). 13 | Multiply With Carry, Linear congruential generator, and Mersenne Twister. 14 | Extended with set/getState methods. 15 | * StringGenerator - Direct Port from [rot.js](http://ondras.github.io/rot.js/hp/) 16 | * Map - Arena, Divided/Icey/Eller Maze, Digger/Uniform/Rogue* Dungeons. 17 | Ported from [rot.js](http://ondras.github.io/rot.js/hp/). 18 | * Noise Generator - Simplex Noise 19 | * FOV - Bresenham Line based Ray Casting, Precise Shadow Casting 20 | * Color - 147 Predefined colors; generate valid colors from string; add, multiply, or interpolate colors; 21 | generate a random color from a reference and set of standard deviations. 22 | (straight port from [rot.js](http://ondras.github.io/rot.js/hp/)) 23 | * Path Finding - Dijkstra and AStar pathfinding ported from [rot.js](http://ondras.github.io/rot.js/hp/). 24 | * Lighting - compute light emission and blending, ported from [rot.js](http://ondras.github.io/rot.js/hp/). 25 | * Dice - Roguelike based dice module ported from [RL-Dice](https://github.com/timothymtorres/RL-Dice). 26 | 27 | Getting started 28 | ========== 29 | `git clone git://github.com/paulofmandown/rotLove.git` 30 | 31 | Add the contents of the src directory to lib/rotLove in your project and require the rot file. 32 | ```lua 33 | ROT=require 'lib/rotLove/rot' 34 | function love.load() 35 | f=ROT.Display() 36 | f:writeCenter('You did it!', math.floor(f:getHeight()/2)) 37 | end 38 | function love.draw() f:draw() end 39 | ``` 40 | 41 | Examples 42 | ========== 43 | rotLove has a number of demo projects in `examples/` that you can use to 44 | get a feel for each API. To see a demo in action, run 45 | ```shell 46 | love . my-demo 47 | ``` 48 | from your shell. 49 | -------------------------------------------------------------------------------- /conf.lua: -------------------------------------------------------------------------------- 1 | function love.conf(t) 2 | t.window = nil -- setmode is in love.load 3 | end 4 | -------------------------------------------------------------------------------- /doc/examples/arena.lua.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 114 | 115 |
116 | 117 |

arena.lua

118 |
119 | --[[ Arena ]]--
120 | ROT=require 'src.rot'
121 | function love.load()
122 |     f=ROT.Display:new(80,24)
123 |     m=ROT.Map.Arena:new(f:getWidth(), f:getHeight())
124 |     function callbak(x,y,val)
125 |         f:write(val == 1 and '#' or '.', x, y)
126 |     end
127 |     m:create(callbak)
128 | end
129 | function love.draw() f:draw() end
130 | --]]
131 | 132 | 133 |
134 |
135 |
136 | generated by LDoc 1.4.6 137 | Last updated 2017-07-19 18:43:03 138 |
139 |
140 | 141 | 142 | -------------------------------------------------------------------------------- /doc/examples/drawtextdisplay.lua.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 114 | 115 |
116 | 117 |

drawtextdisplay.lua

118 |
119 | --[[ Event Queue ]]--
120 | ROT=require 'src.rot'
121 | function love.load()
122 |     f=ROT.Display(80,24)
123 |     f:drawText(30, 11, "%c{brown}Everything is all right%c{}, just relax.")
124 | end
125 | function love.draw() f:draw() end
126 | --]]
127 | 128 | 129 |
130 |
131 |
132 | generated by LDoc 1.4.6 133 | Last updated 2017-07-19 18:43:03 134 |
135 |
136 | 137 | 138 | -------------------------------------------------------------------------------- /doc/examples/drawtexttextdisplay.lua.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Reference 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 114 | 115 |
116 | 117 |

drawtexttextdisplay.lua

118 |
119 | --[[ Event Queue ]]--
120 | ROT=require 'src.rot'
121 | function love.load()
122 |     f=ROT.Display(80,24)
123 |     f:drawText(30, 11, "%c{brown}Everything is all right%c{}, just relax.")
124 | end
125 | function love.draw() f:draw() end
126 | --]]
127 | 128 | 129 |
130 |
131 |
132 | generated by LDoc 1.4.6 133 | Last updated 2017-07-19 18:43:03 134 |
135 |
136 | 137 | 138 | -------------------------------------------------------------------------------- /examples/action.lua: -------------------------------------------------------------------------------- 1 | --[[ ActionScheduler ]]-- 2 | ROT=require 'src.rot' 3 | 4 | function love.load() 5 | s =ROT.Scheduler.Action:new() 6 | f =ROT.Display(80,24) 7 | for i=1,4 do s:add(i,true,i-1) end 8 | end 9 | y=1 10 | function love.update() 11 | love.timer.sleep(.5) 12 | c =s:next() 13 | dur=ROT.RNG:random(1,20) 14 | s:setDuration(dur) 15 | f:writeCenter('TURN: '..c..' for '..dur..' units of time', y) 16 | y=y<24 and y+1 or 1 17 | end 18 | function love.draw() 19 | f:draw() 20 | end 21 | --]] 22 | -------------------------------------------------------------------------------- /examples/arena.lua: -------------------------------------------------------------------------------- 1 | --[[ Arena ]]-- 2 | ROT=require 'src.rot' 3 | function love.load() 4 | f=ROT.Display:new(80,24) 5 | m=ROT.Map.Arena:new(f:getWidth(), f:getHeight()) 6 | function callbak(x,y,val) 7 | f:write(val == 1 and '#' or '.', x, y) 8 | end 9 | m:create(callbak) 10 | end 11 | function love.draw() f:draw() end 12 | --]] 13 | -------------------------------------------------------------------------------- /examples/astar.lua: -------------------------------------------------------------------------------- 1 | ROT=require 'src.rot' 2 | 3 | data={} 4 | 5 | function love.load() 6 | --f=ROT.Display:new() 7 | 8 | -- use this to stress out the map creation and path finding 9 | -- should take about a second to do one demo with this 10 | f=ROT.Display:new(256, 100, .275) 11 | 12 | map=ROT.Map.Uniform(f:getWidth(), f:getHeight()) 13 | doTheThing() 14 | end 15 | 16 | function love.draw() f:draw() end 17 | 18 | update=false 19 | function love.update() 20 | if update then 21 | update=false 22 | doTheThing() 23 | end 24 | end 25 | function love.keypressed() update=true end 26 | 27 | function doTheThing() 28 | local start=os.clock() 29 | map:create(mapCallback) 30 | p1=getRandomFloor(data) 31 | p2=getRandomFloor(data) 32 | p3=getRandomFloor(data) 33 | start=os.clock() 34 | astar=ROT.Path.AStar(p1[1], p1[2], passableCallback) 35 | start=os.clock() 36 | astar:compute(p2[1], p2[2], astarCallback) 37 | start=os.clock() 38 | astar:compute(p3[1], p3[2], astarCallback) 39 | 40 | f:write('S', tonumber(p1[1]), tonumber(p1[2]), nil, { 0, 0, 1, 255 }) 41 | f:write('E', tonumber(p2[1]), tonumber(p2[2]), nil, { 0, 0, 1, 255 }) 42 | f:write('E', tonumber(p3[1]), tonumber(p3[2]), nil, { 0, 0, 1, 255 }) 43 | 44 | end 45 | 46 | function astarCallback(x, y) 47 | f:write('.', x, y, nil, { 136, 0, 0, 255 }) 48 | end 49 | 50 | function passableCallback(x, y) return data[x..','..y]==0 end 51 | 52 | function getRandomFloor(data) 53 | local key=nil 54 | while true do 55 | key=ROT.RNG:random(1,f:getWidth())..','.. 56 | ROT.RNG:random(1,f:getHeight()) 57 | if data[key]==0 then 58 | return key:split(',') 59 | end 60 | end 61 | end 62 | 63 | function mapCallback(x, y, val) 64 | data[x..','..y]=val 65 | f:write(val==0 and '.' or '#', x, y) 66 | end 67 | -------------------------------------------------------------------------------- /examples/bresenham.lua: -------------------------------------------------------------------------------- 1 | --[[ Bresenham Line of Sight ]]-- 2 | ROT=require 'src.rot' 3 | 4 | function calbak(x, y, val) 5 | map[x..','..y]=val 6 | f:write(val==1 and '#' or '.', x, y, { .4, .4, .4, 255 }, { 0, 0, 0, 255 }) 7 | end 8 | 9 | function lightCalbak(fov, x, y) 10 | local key=x..','..y 11 | if map[key] then 12 | return map[key]==0 13 | end 14 | return false 15 | end 16 | 17 | function computeCalbak(x, y, r, v) 18 | local key =x..','..y 19 | if not map[key] then return end 20 | local color= { .5, .5, 0, 255 } 21 | f:write(r>0 and f:getCharacter(x, y) or '@', x, y, nil, color) 22 | end 23 | local player={x=1, y=1} 24 | function placePlayer() 25 | local key =nil 26 | local char='#' 27 | while true do 28 | key=ROT.RNG:random(1,f:getWidth())..','.. 29 | ROT.RNG:random(1,f:getHeight()) 30 | if map[key]==0 then 31 | pos = key:split(',') 32 | player.x, player.y=tonumber(pos[1]), tonumber(pos[2]) 33 | f:write('@', player.x, player.y) 34 | break 35 | end 36 | end 37 | end 38 | 39 | function love.load() 40 | f =ROT.Display(40, 25) 41 | map={} 42 | doTheThing() 43 | end 44 | function doTheThing() 45 | mapgen=ROT.Map.Arena:new(f:getWidth(), f:getHeight()) 46 | mapgen:create(calbak) 47 | fov=ROT.FOV.Bresenham:new(lightCalbak, {useDiamond=true}) 48 | placePlayer() 49 | fov:compute(player.x, player.y, 10, computeCalbak) 50 | end 51 | local update=false 52 | function love.update() 53 | if update then 54 | update=false 55 | doTheThing() 56 | end 57 | end 58 | function love.keypressed() update=true end 59 | function love.draw() f:draw() end 60 | -------------------------------------------------------------------------------- /examples/brogue.lua: -------------------------------------------------------------------------------- 1 | --[[ Brogue ]] 2 | ROT=require 'src.rot' 3 | 4 | function love.load() 5 | f =ROT.Display(80, 30) 6 | brg=ROT.Map.Brogue(f:getWidth(), f:getHeight()) 7 | brg:create(calbak,true) 8 | for _, room in ipairs(brg:getRooms()) do 9 | room:getDoors(function(x, y) f:write('+', x, y) end) 10 | end 11 | end 12 | function love.draw() f:draw() end 13 | function calbak(x, y, val) 14 | f:write(val==1 and '#' or '.', x, y) 15 | end 16 | local update=false 17 | function love.update() 18 | if update then 19 | update=false 20 | brg:create(calbak) 21 | for _, room in ipairs(brg:getRooms()) do 22 | room:getDoors(function(x, y) f:write('+', x, y) end) 23 | end 24 | end 25 | end 26 | function love.keypressed(key) update=true end 27 | -------------------------------------------------------------------------------- /examples/brogueCaveGeneration.lua: -------------------------------------------------------------------------------- 1 | ROT=require 'src.rot' 2 | function love.load() 3 | f =ROT.Display(79,29) 4 | cl=ROT.Map.Cellular:new(f:getWidth(), f:getHeight()) 5 | cl:randomize(.55) 6 | cl:create(calbak) 7 | end 8 | function love.draw() f:draw() end 9 | 10 | wait=false 11 | id=2 12 | largest=2 13 | largestCount=0 14 | function love.update() 15 | local start=os.clock() 16 | cl:randomize(.55) 17 | if wait then return end 18 | local cellStart=os.clock() 19 | for i=1,5 do cl:create(calbak) end 20 | for x=1,f:getWidth() do 21 | for y=1,f:getHeight() do 22 | if cl._map[x][y]==1 then 23 | local count=fillBlob(x,y,cl._map, id) 24 | if count>largestCount then 25 | largest=id 26 | largestCount=count 27 | end 28 | id=id+1 29 | end 30 | end 31 | end 32 | writeMap() 33 | largest=2 34 | id=2 35 | largestCount=0 36 | wait=true 37 | i=0 38 | end 39 | function love.keypressed() wait=false end 40 | function writeMap() 41 | for x=1,f:getWidth() do 42 | for y=1,f:getHeight() do 43 | f:write(cl._map[x][y]==largest and '.' or '#', x, y) 44 | end 45 | end 46 | end 47 | 48 | function calbak(x, y, val) 49 | f:write(val==1 and '#' or '.', x, y) 50 | end 51 | 52 | function fillBlob(x,y,m,id) 53 | m[x][y]=id 54 | local todo={{x,y}} 55 | local dirs=ROT.DIRS.EIGHT 56 | local size=1 57 | repeat 58 | local pos=table.remove(todo, 1) 59 | for i=1,#dirs do 60 | local rx=pos[1]+dirs[i][1] 61 | local ry=pos[2]+dirs[i][2] 62 | if rx<1 or rx>f:getWidth() or ry<1 or ry>f:getHeight() then 63 | 64 | elseif m[rx][ry]==1 then 65 | m[rx][ry]=id 66 | table.insert(todo,{ rx, ry }) 67 | size=size+1 68 | end 69 | end 70 | until #todo==0 71 | return size 72 | end 73 | -------------------------------------------------------------------------------- /examples/cellular.lua: -------------------------------------------------------------------------------- 1 | --[[ Cellular ]]-- 2 | ROT=require 'src.rot' 3 | function love.load() 4 | f =ROT.Display(80,24) 5 | cl=ROT.Map.Cellular:new(f:getWidth(), f:getHeight()) 6 | cl:randomize(.5) 7 | cl:create(calbak) 8 | end 9 | function love.draw() f:draw() end 10 | function love.update() 11 | love.timer.sleep(.5) 12 | if cl:create(calbak).changed then 13 | f:write('changed @ '..os.clock(), 1, 1) 14 | else 15 | f:write("didn't change @ "..os.clock(), 1, 1) 16 | end 17 | end 18 | function calbak(x, y, val) 19 | f:write(val==1 and '#' or '.', x, y) 20 | end 21 | --]] 22 | -------------------------------------------------------------------------------- /examples/characters.lua: -------------------------------------------------------------------------------- 1 | ROT=require 'src.rot' 2 | DSP=ROT.Display:new() 3 | function love.load() 4 | local y=1 5 | local x=3 6 | for i=1,255 do 7 | local str=tostring(i):lpad('0', 3)..' '..string.char(i) 8 | DSP:write(str, x, y) 9 | y=y1 then 14 | f:clear() 15 | f:writeCenter("ROLL instance with rng: "..d_with_rng:roll(), 1) 16 | f:writeCenter("ROLL instance with rng: "..d_with_rng:roll(), 2) 17 | f:writeCenter("ROLL instance with rng: "..d_with_rng:roll(), 3) 18 | f:writeCenter("ROLL instance with rng: "..d_with_rng:roll(), 4) 19 | 20 | f:writeCenter("ROLL instance without rng: "..d_without_rng:roll(), 6) 21 | f:writeCenter("ROLL instance without rng: "..d_without_rng:roll(), 7) 22 | f:writeCenter("ROLL instance without rng: "..d_without_rng:roll(), 8) 23 | f:writeCenter("ROLL instance without rng: "..d_without_rng:roll(), 9) 24 | 25 | f:writeCenter("ROLL ROT.Dice with rng: "..ROT.Dice.roll('3d6', 1, lcg), 11) 26 | f:writeCenter("ROLL ROT.Dice with rng: "..ROT.Dice.roll('3d6', 1, lcg), 12) 27 | f:writeCenter("ROLL ROT.Dice with rng: "..ROT.Dice.roll('3d6', 1, lcg), 13) 28 | f:writeCenter("ROLL ROT.Dice with rng: "..ROT.Dice.roll('3d6', 1, lcg), 14) 29 | 30 | f:writeCenter("ROLL ROT.Dice without rng: "..ROT.Dice.roll('3d6', 1), 16) 31 | f:writeCenter("ROLL ROT.Dice without rng: "..ROT.Dice.roll('3d6', 1), 17) 32 | f:writeCenter("ROLL ROT.Dice without rng: "..ROT.Dice.roll('3d6', 1), 18) 33 | f:writeCenter("ROLL ROT.Dice without rng: "..ROT.Dice.roll('3d6', 1), 19) 34 | 35 | f:writeCenter("Rolling 3d6's", 23) 36 | t=0 37 | end 38 | t=t+dt 39 | end 40 | -------------------------------------------------------------------------------- /examples/dice_advanced_uses.lua: -------------------------------------------------------------------------------- 1 | dice = require('dice') 2 | math.randomseed(os.time()) 3 | 4 | -- Some special ways the user may choose to utilize the dice module 5 | -- although there are endless creative possiblities 6 | 7 | -- DUAL WIELDING -- 8 | l_hand_weap, r_hand_weap = dice:new('1d6'), dice:new('2d4') 9 | attacks = {l_hand_weap:roll(), r_hand_weap:roll()} 10 | 11 | for _, attack in ipairs(attacks) do 12 | -- apply armor protection 13 | -- take damage 14 | -- ... 15 | end 16 | 17 | -- STATUS EFFECTS -- 18 | sleep_duration = dice.roll('1d100+50') 19 | confusion_duration = dice.roll('1d10') 20 | poison_damage = dice.roll('1d3') 21 | 22 | -- CURSED/BLESSED ITEMS -- 23 | basic_weapon = dice:new('1d4') 24 | 25 | if basic_weapon:isCursed() then -- Add a reroll that removes the highest number 26 | basic_weapon = basic_weapon ^ -1 27 | elseif basic_weapon:isBlessed() then -- Add a reroll that removes the lowest number 28 | basic_weapon = basic_weapon ^ 1 29 | end 30 | 31 | -- SKILL MASTERY -- 32 | if player:hasMasteredSkill() then 33 | -- apply a positive reroll when doing something 34 | end 35 | 36 | -- AIMING AND TO-HIT -- 37 | weapon_to_hit = dice:new('1d20') 38 | 39 | if weapon_to_hit:roll() > enemy_def then 40 | -- attack is successful 41 | -- apply damage 42 | end 43 | 44 | -- BURSTFIRE -- 45 | gattling_gun = dice:new('(1d5)x6') 46 | 47 | for _, damage in ipairs( {gattling_gun:roll()} ) do 48 | if to_hit:roll() > enemy_dodge then 49 | -- attack succeeds 50 | -- apply protections 51 | -- apply damage 52 | else 53 | -- missed 54 | -- skip damage calculations 55 | -- include miss message 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /examples/dice_basic.lua: -------------------------------------------------------------------------------- 1 | dice = require('dice') 2 | math.randomseed(os.time()) 3 | 4 | -- some basic dice you see in boardgames 5 | 6 | single_die = dice:new(6) -- is the same as dice:new('1d6') 7 | die_result = single_die:roll() 8 | 9 | monopoly_dice = dice:new('2d6') 10 | monopoly_turn = monopoly_dice:roll() 11 | 12 | D&D_dice = dice:new('1d20') -- we could also use dice:new(20) 13 | roll_to_hit = D&D_dice:roll() 14 | 15 | risk_attackers_dice = dice:new('(1d6)x3') 16 | risk_defenders_dice = dice:new('(1d6)x2') 17 | 18 | attack_1, attack_2, attack_3 = risk_attackers_dice:roll() 19 | defend_1, defend_2 = risk_defenders_dice:roll() 20 | 21 | --alternatively we could just put the risk roll results in a table 22 | attackers_result = { risk_attackers_dice:roll() } 23 | defenders_result = { risk_defenders_dice:roll() } 24 | 25 | 26 | -- Another method to roll dice is instead of using the roguelike dice notation, we can just feed the roll function a direct number 27 | 28 | die_result = dice.roll(6) 29 | monopoly_turn = dice.roll(6) + dice.roll(6) 30 | roll_to_hit = dice.roll(20) 31 | attack_1, attack_2, attack_3 = dice.roll(6), dice.roll(6), dice.roll(6) 32 | defend_1, defend_2 = dice.roll(6), dice.roll(6) 33 | 34 | -- Or we can roll the dice just using the notation alone without having to use dice:new() 35 | 36 | die_result = dice.roll('1d6') 37 | monopoly_turn = dice.roll('2d6') 38 | roll_to_hit = dice.roll('1d20') 39 | attack_1, attack_2, attack_3 = dice.roll('(1d6)x3') 40 | defend_1, defend_2 = dice.roll('(1d6)x2') 41 | 42 | -- notice we omitted the colon from the dice roll function because a dice instance is not neccessary although 43 | -- every roll will initialize the dice table, return the result, and then discard the dice table 44 | -- which means if you will be using dice continously it will be more efficent to use dice:new() 45 | -------------------------------------------------------------------------------- /examples/dice_boundry_behaviour.lua: -------------------------------------------------------------------------------- 1 | dice = require('dice') 2 | math.randomseed(os.time()) 3 | 4 | -- Demonstration of the what-ifs of this dice module when the user attempts unusual behavior 5 | 6 | test_dice = dice:new('3d6') -- initializes our dice instance 7 | 8 | -- Let's try to do something crazy! 9 | test_dice = test_dice / -100 -- Attempt to make the dice faces a negative number 10 | test_dice = test_dice * -10 -- Annnnd negative number of dice 11 | test_dice = test_dice % -1 -- Annnnnnnnd 0 dice sets 12 | 13 | -- Instead of the dice module crashing, these three fields have a boundry of math.max(value, 1) 14 | -- So that even if the numbers go into negative, the dice will still roll properly 15 | 16 | print(test_dice) -- 1d1 17 | test_dice:roll() -- RESULT = 1 18 | 19 | -- If we check the dice variables we get... 20 | 21 | test_dice:getFaces() -- -94 22 | test_dice:getNum() -- -7 23 | test_dice:getSets() -- 0 24 | 25 | -- The variables are still being tracked properly. Good to know! 26 | -------------------------------------------------------------------------------- /examples/dice_metamethods.lua: -------------------------------------------------------------------------------- 1 | dice = require('dice') 2 | math.randomseed(os.time()) 3 | 4 | weapon = dice:new('1d6') -- 1d6 5 | 6 | -- Subtraction or addition modifies the bonus of the rolls 7 | weapon = weapon + 4 -- 1d6+4 8 | weapon = weapon - 2 -- 1d6+2 9 | 10 | -- Multiplcation modifies the number of dice 11 | weapon = weapon * 2 -- 3d6+2 12 | 13 | -- Division modifies the number of faces on the dice 14 | weapon = weapon / -2 -- 3d4+2 15 | 16 | -- Exponential modifies the rerolls (positive number removes low rolls, negative number removes high rolls) 17 | weapon = weapon ^ 1 -- 3d4+2^+1 18 | 19 | -- Modulo division modifies the dice sets (returns multiple results) 20 | weapon = weapon % 2 --(3d4+1^+1)x3 21 | 22 | -- To string operations returns a dice notation string 23 | print(weapon) --(3d4+2^+1)x3 24 | 25 | -- Concat operations is a tricky concept to explain. Concating the dice with the following strings 26 | -- '++', '--', '^^', '+', '-', '^' or a combination of both disables or enables plurality of bonus/rerolls 27 | -- if a double operation sign is used, then the effect will be MULTIPLIED TO ALL dice 28 | -- if a single operation sign is used, then the effect will apply as normal 29 | 30 | -- Let us create a new weapon to demonstrate this with bonuses 31 | weapon = dice:new('3d1+2') 32 | 33 | -- Time to show how it is calculated 34 | print(weapon:roll()) -- 1 + 1 + (1+2) RESULT=5 35 | 36 | -- Enable plurality for bonus 37 | weapon = weapon .. '++' -- 3d1++2 38 | 39 | -- Calculation is much different now! 40 | print(weapon:roll()) -- (1+2) + (1+2) + (1+2) RESULT=9 41 | 42 | -- Reset back to normal 43 | weapon = weapon .. '+' --Plurality is now disabled for bonus 44 | 45 | -- Alternatively instead of '++' and '+' you may opt to use '--' and '-' instead. 46 | -- Both signs enable/disable plurality for bonus 47 | 48 | -- Another new weapon to demonstrate plurality for rerolls 49 | weapon = dice:new('2d6^+1') 50 | 51 | -- Rolls 2 dice and one extra 52 | print(weapon:roll()) -- (5) (3) (1) -- Out of the 3 dice, remove the lowest roll -> (1) RESULT=8 53 | 54 | -- Enable plurality for rerolls 55 | weapon = weapon .. '^^' 56 | 57 | -- Now rolls 2 dice and two extra 58 | print(weapon:roll()) -- (2) (6) (4) (2) -- Out of the 4 dice, remove the two lowest rolls -> (2) (2) RESULT=10 59 | -------------------------------------------------------------------------------- /examples/dice_minimum.lua: -------------------------------------------------------------------------------- 1 | dice = require('dice') 2 | math.randomseed(os.time()) 3 | 4 | -- Due to the possiblity of negative rolls and whether the user wishes for this type of behavior, a dice 5 | -- minimum is factored into rolls to enable or prevent this from happening. By default the minimum is set to 1 6 | 7 | -- Roll a die with a negative bonus 8 | dice.roll('1d1-100') -- RESULT = 1 9 | 10 | -- Disable the minimum 11 | dice:setMinimum(nil) 12 | 13 | -- Try this again 14 | dice.roll('1d1-100') -- RESULT = -99 15 | 16 | -- The second argument in dice.roll allows a shortcut to set the minimum 17 | dice.roll('1d1-100', 0) -- RESULT = 0 18 | 19 | -- Another handy feature allows us to place a minimum on each individual dice instance 20 | 21 | test_dice = dice:new('1d1-100', 1) -- Yet again a second argument in dice:new is a shortcut to set the minimum 22 | test_dice:roll() -- RESULT = 1 23 | test_dice:setMinimum(nil) 24 | test_dice:roll() -- RESULT = 0 (dice class minimum used instead) 25 | 26 | -- Notice how the dice instance minimum has precedence over dice class minimum although 27 | -- if a dice instance minimum is not set, then by default the dice class minimum will be used 28 | -------------------------------------------------------------------------------- /examples/dice_valid_notation.lua: -------------------------------------------------------------------------------- 1 | -- The dice must follow a certain string format when creating a new dice object or it will raise an error. 2 | 3 | dice_str = '1d5' -- valid 4 | dice_str = '3d5' -- valid 5 | dice_str = '(1d3)x1' -- valid 6 | dice_str = '1d2+1' -- valid 7 | dice_str = '1d10^+1' -- valid 8 | dice_str = '1d5+1^-2' -- valid 9 | dice_str = '(1d3+8^+3)x3' -- valid 10 | dice_str = ' 1d5' -- not valid (space in front of string) 11 | dice_str = '+10d5' -- not valid (cannot have a + or - sign in front of dice number) 12 | dice_str = '5d+5' -- not valid (cannot have a + or - sign in front of dice faces either) 13 | dice_str = '3d4+^1' -- not valid (there is no number for the bonus?!) 14 | dice_str = '(1d3)x1+5' -- not valid (bonuses and rerolls have to be inside the sets parenthesis!) 15 | dice_str = '3d4^3' -- not valid (reroll needs a + or - sign in front of it) 16 | -------------------------------------------------------------------------------- /examples/dice_weapon_samples.lua: -------------------------------------------------------------------------------- 1 | -- Here is a list of weapons that I have made for some of my games and how they have been 2 | -- used with the dice module 3 | 4 | 5 | -- *Note that every weapon that is initalized has a condition variable between: 6 | -- weapon.condition = {0=ruined, 1=worn, 2=average, 3=pristine} 7 | -- This will effect the dice string or accuracy in a positive or negative way for each weapon group differently 8 | 9 | weapon = {} 10 | -- weapon.dice = Damage upon a successful attack 11 | -- weapon.accuracy = To-hit chance 12 | -- weapon.durability = Chance for condition to degrade after use (higher number results in longer use) 13 | 14 | --[[ 15 | --- BRUTE --- 16 | ----- This weapon group uses large dice face numbers that excel against armored opponets 17 | ----- High amount of durability since attacking with dull melee weapons 18 | ----- Low accuracy since swinging bulky melee weapons is diffcult 19 | 20 | ----- Condition Modifiers ----- 21 | ----- dice = dice / -4 (ruined) 22 | ----- dice = dice / -2 (worn) 23 | ----- dice = dice (average) 24 | ----- dice = dice / 2 (pristine) 25 | --]] 26 | 27 | weapon.bat = {} 28 | weapon.bat.full_name = 'baseball bat' 29 | weapon.bat.dice = '1d4' 30 | weapon.bat.accuracy = 4 31 | weapon.bat.durability = 12 32 | 33 | weapon.crowbar = {} 34 | weapon.crowbar.full_name = 'crowbar' 35 | weapon.crowbar.dice = '1d5' 36 | weapon.crowbar.accuracy = 4 37 | weapon.crowbar.durability = 10 38 | 39 | weapon.sledge = {} 40 | weapon.sledge.full_name = 'sledgehammer' 41 | weapon.sledge.dice = '1d8' 42 | weapon.sledge.accuracy = 3 43 | weapon.sledge.durability = 10 44 | 45 | --[[ 46 | --- BLADE --- 47 | ----- This weapon group uses dice bonuses that makes for high average damage output 48 | ----- Low durability since bladed weapons dull quickly 49 | ----- Medium accuracy since bladed weapons are light 50 | 51 | ----- Condition Modifiers ----- 52 | ----- dice = dice - 2 (ruined) 53 | ----- dice = dice - 1 (worn) 54 | ----- dice = dice (average) 55 | ----- dice = dice + 1 (pristine) 56 | --]] 57 | 58 | weapon.knife = {} 59 | weapon.knife.full_name = 'knife' 60 | weapon.knife.dice = '1d2+1' 61 | weapon.knife.accuracy = 4 62 | weapon.knife.durability = 3 63 | 64 | weapon.katanna = {} 65 | weapon.katanna.full_name = 'katanna' 66 | weapon.katanna.dice = '1d4+2' 67 | weapon.katanna.accuracy = 5 68 | weapon.katanna.durability = 4 69 | 70 | --[[ 71 | --- PROJECTILE --- 72 | ----- This weapon group uses high damage, but at the cost of ammo 73 | ----- Medium durability 74 | ----- High accuracy 75 | 76 | ----- Condition Modifiers ----- 77 | ----- accuracy = accuracy - 2 (ruined) 78 | ----- accuracy = accuracy - 1 (worn) 79 | ----- accuracy = accuracy (average) 80 | ----- accuracy = accuracy + 1 (pristine) 81 | 82 | ----- Notice this isn't affecting the damage, but merely the accuracy! 83 | ----- Also we can conclude that whenever a ranged weapon is fired, it uses 84 | ----- up durability regardless of hit or miss, whereas a melee weapon does 85 | ----- not use durability on a miss 86 | --]] 87 | 88 | weapon.pistol = {} 89 | weapon.pistol.full_name = 'pistol' 90 | weapon.pistol.dice = '1d6+2' 91 | weapon.pistol.accuracy = 6 92 | weapon.pistol.durability = 7 93 | 94 | weapon.magnum = {} 95 | weapon.magnum.full_name = 'magnum' 96 | weapon.magnum.dice = '1d9+4' 97 | weapon.magnum.accuracy = 6 98 | weapon.magnum.durability = 8 99 | 100 | weapon.shotgun = {} 101 | weapon.shotgun.full_name = 'shotgun' 102 | weapon.shotgun.dice = '3d3++1' 103 | weapon.shotgun.accuracy = 6 104 | weapon.shotgun.durability = 8 105 | 106 | weapon.rifle = {} 107 | weapon.rifle.full_name = 'assualt rifle' 108 | weapon.rifle.dice = '(3d2)x3' 109 | weapon.rifle.accuracy = 7 110 | weapon.rifle.durability = 8 111 | 112 | 113 | --[[ 114 | --- BURN/EXPLOSIVES --- 115 | ----- This weapon group uses large number of dice 116 | ----- These weapons are single use 117 | ----- Low accuracy 118 | ----- Unpredictable damage, usually high 119 | 120 | ----- Condition Modifiers ----- 121 | ----- dice = dice * -2 (ruined) 122 | ----- dice = dice * -1 (worn) 123 | ----- dice = dice (average) 124 | ----- dice = dice * 1 (pristine) 125 | --]] 126 | 127 | weapon.molotov = {} 128 | weapon.molotov.full_name = 'molotov cocktail' 129 | weapon.molotov.dice = '5d2' 130 | weapon.molotov.accuracy = 3 131 | weapon.molotov.durability = 'one_use' 132 | 133 | weapon.flare = {} 134 | weapon.flare.full_name = 'flare gun' 135 | weapon.flare.dice = '5d3' 136 | weapon.flare.accuracy = 3 137 | weapon.flare.durability = 'one_use' 138 | 139 | weapon.missile = {} 140 | weapon.missile.full_name = 'missile launcher' 141 | weapon.missile.dice = '5d8' 142 | weapon.missile.accuracy = 3 143 | weapon.missile.durability = 'one_use' 144 | 145 | return weapon 146 | -------------------------------------------------------------------------------- /examples/digger.lua: -------------------------------------------------------------------------------- 1 | --[[ Digger ]] 2 | ROT=require 'src.rot' 3 | 4 | local update=false 5 | function love.load() 6 | f =ROT.Display(80, 24) 7 | dgr=ROT.Map.Digger(f:getWidth(), f:getHeight()) 8 | update=true 9 | end 10 | function love.draw() f:draw() end 11 | function calbak(x, y, val) f:write(val==1 and '#' or '.', x, y) end 12 | function love.update() 13 | if update then 14 | update=false 15 | dgr:create(calbak) 16 | local doors=dgr:getDoors() 17 | for k,v in pairs(doors) do 18 | f:write('+', v.x, v.y) 19 | end 20 | end 21 | end 22 | function love.keypressed(key) 23 | ROT.RNG:setSeed(key) 24 | update=true 25 | end 26 | -------------------------------------------------------------------------------- /examples/dijkstra.lua: -------------------------------------------------------------------------------- 1 | ROT=require 'src.rot' 2 | 3 | data={} 4 | 5 | function love.load() 6 | --f=ROT.Display:new() 7 | 8 | -- use this to stress out the map creation and path finding 9 | -- should take about a second to do one demo with this 10 | f=ROT.Display:new(256, 100, .275) 11 | 12 | map=ROT.Map.Uniform(f:getWidth(), f:getHeight(), {dugPercentage=.7}) 13 | doTheThing() 14 | end 15 | 16 | function love.draw() f:draw() end 17 | 18 | update=false 19 | function love.update() 20 | if update then 21 | update=false 22 | doTheThing() 23 | end 24 | end 25 | function love.keypressed() update=true end 26 | 27 | function doTheThing() 28 | local start=os.clock() 29 | map:create(mapCallback) 30 | p1=getRandomFloor(data) 31 | p2=getRandomFloor(data) 32 | p3=getRandomFloor(data) 33 | 34 | start=os.clock() 35 | dijkstra=ROT.Path.Dijkstra(p1[1], p1[2], passableCallback) 36 | start=os.clock() 37 | dijkstra:compute(p2[1], p2[2], dijkstraCallback) 38 | start=os.clock() 39 | dijkstra:compute(p3[1], p3[2], dijkstraCallback) 40 | 41 | f:write('S', tonumber(p1[1]), tonumber(p1[2]), nil, { 0, 0, 1, 1 }) 42 | f:write('E', tonumber(p2[1]), tonumber(p2[2]), nil, { 0, 0, 1, 1 }) 43 | f:write('E', tonumber(p3[1]), tonumber(p3[2]), nil, { 0, 0, 1, 1 }) 44 | 45 | end 46 | 47 | function dijkstraCallback(x, y) 48 | f:write('.', x, y, nil, { 0.533, 0.0, 0.0, 1.0 }) 49 | end 50 | 51 | function passableCallback(x, y) return data[x..','..y]==0 end 52 | 53 | function getRandomFloor(data) 54 | local key=nil 55 | while true do 56 | key=ROT.RNG:random(1,f:getWidth())..','.. 57 | ROT.RNG:random(1,f:getHeight()) 58 | if data[key]==0 then 59 | return key:split(',') 60 | end 61 | end 62 | end 63 | 64 | function mapCallback(x, y, val) 65 | data[x..','..y]=val 66 | f:write(val==0 and '.' or '#', x, y) 67 | end 68 | -------------------------------------------------------------------------------- /examples/dijkstraMap.lua: -------------------------------------------------------------------------------- 1 | --[[ Rogue ]] 2 | ROT=require 'src.rot' 3 | movers={} 4 | colors={} 5 | table.insert(colors, ROT.Color.fromString('blue')) 6 | table.insert(colors, ROT.Color.fromString('red')) 7 | table.insert(colors, ROT.Color.fromString('green')) 8 | table.insert(colors, ROT.Color.fromString('yellow')) 9 | function love.load() 10 | f =ROT.Display() 11 | maps={ 12 | "DividedMaze", 13 | "IceyMaze", 14 | "EllerMaze", 15 | } 16 | dothething() 17 | end 18 | function love.keypressed() 19 | dothething() 20 | end 21 | tsl=0 22 | tbf=1/30 23 | function love.update(dt) 24 | --tsl=tsl+dt 25 | if true then --tsl>tbf then 26 | tsl=tsl-tbf 27 | for _,mover in pairs(movers) do 28 | local dir={dijkMap:dirTowardsGoal(mover.x, mover.y)} 29 | if dir[1] and dir[2] and mover.x and mover.y then 30 | f:write(map[mover.x][mover.y], mover.x, mover.y, nil, ROT.Color.interpolate(mover.color, mover.oc)) 31 | mover.x=mover.x+dir[1] 32 | mover.y=mover.y+dir[2] 33 | local oc=f:getBackgroundColor(mover.x, mover.y) 34 | mover.oc=oc==f:getDefaultBackgroundColor() and ROT.Color.fromString('dimgrey') or oc 35 | f:write('@', mover.x, mover.y, nil, mover.color) 36 | end 37 | end 38 | end 39 | end 40 | 41 | function dothething() 42 | mapType=maps[ROT.RNG:random(1,#maps)] 43 | rog= ROT.Map[mapType]:new(f:getWidth(), f:getHeight()) 44 | map={} 45 | for i=1,f:getWidth() do map[i]={} end 46 | if rog.randomize then 47 | floorValue=1 48 | rog:randomize(.5) 49 | for i=1,5 do 50 | rog:create(calbak) 51 | end 52 | else 53 | floorValue=0 54 | rog:create(calbak) 55 | end 56 | --rog:randomize(.5) 57 | --while rog:create(calbak) do end 58 | rog:create(calbak) 59 | while true do 60 | local x=math.random(1,f:getWidth()) 61 | local y=math.random(1,f:getHeight()) 62 | 63 | if map[x][y]=='.' then 64 | dijkMap=ROT.Path.DijkstraMap(x,y,dijkCalbak) 65 | break 66 | end 67 | end 68 | dijkMap:compute() 69 | movers={} 70 | while #movers<40 do 71 | local x=math.random(1,f:getWidth()) 72 | local y=math.random(1,f:getHeight()) 73 | 74 | if map[x][y]=='.' then 75 | table.insert(movers, {x=x,y=y,color=getRandomColor(),oc=f:getDefaultBackgroundColor()}) 76 | end 77 | end 78 | 79 | --[[while true do 80 | local dir=dijkMap:dirTowardsGoal(mover.x, mover.y) 81 | if not dir then break end 82 | mover.x=mover.x+dir[1] 83 | mover.y=mover.y+dir[2] 84 | local x=mover.x 85 | local y=mover.y 86 | f:write(map[x][y], x, y, nil, { 125, 15, 15, 255 }) 87 | end--]] 88 | end 89 | 90 | 91 | function love.draw() f:draw() end 92 | function calbak(x, y, val) 93 | map[x][y]=val==floorValue and '.' or '#' 94 | f:write(map[x][y], x, y) 95 | end 96 | function dijkCalbak(x,y) return map[x][y]=='.' end 97 | 98 | function getRandomColor() 99 | return { (ROT.RNG:random(0,1)), 100 | (ROT.RNG:random(0,1)), 101 | (ROT.RNG:random(0,1)), 102 | 1.0} 103 | end 104 | -------------------------------------------------------------------------------- /examples/dividedMaze.lua: -------------------------------------------------------------------------------- 1 | --[[ Divided Maze ]] 2 | ROT=require 'src.rot' 3 | function love.load() 4 | f =ROT.Display(80,24) 5 | dm=ROT.Map.DividedMaze:new(f:getWidth(), f:getHeight()) 6 | dm:create(calbak) 7 | end 8 | function love.draw() f:draw() end 9 | local update=false 10 | function love.update() 11 | if update then 12 | update=false 13 | dm:create(calbak) 14 | end 15 | end 16 | function calbak(x,y,val) 17 | f:write(val==1 and '#' or '.', x, y) 18 | end 19 | function love.keypressed(key) update=true end 20 | -------------------------------------------------------------------------------- /examples/drawtextDisplay.lua: -------------------------------------------------------------------------------- 1 | --[[ Event Queue ]]-- 2 | ROT=require 'src.rot' 3 | function love.load() 4 | f=ROT.Display(80,24) 5 | f:drawText(30, 11, "%c{brown}Everything is all right%c{}, just relax.") 6 | end 7 | function love.draw() f:draw() end 8 | --]] 9 | -------------------------------------------------------------------------------- /examples/drawtextTextDisplay.lua: -------------------------------------------------------------------------------- 1 | --[[ Event Queue ]]-- 2 | ROT=require 'src.rot' 3 | function love.load() 4 | f=ROT.Display(80,24) 5 | f:drawText(30, 11, "%c{brown}Everything is all right%c{}, just relax.") 6 | end 7 | function love.draw() f:draw() end 8 | --]] 9 | -------------------------------------------------------------------------------- /examples/ellerMaze.lua: -------------------------------------------------------------------------------- 1 | ROT=require 'src.rot' 2 | function love.load() 3 | f =ROT.Display(211, 75) 4 | em=ROT.Map.EllerMaze:new(f:getWidth(), f:getHeight()) 5 | em:create(calbak) 6 | end 7 | function love.draw() f:draw() end 8 | ellerStr='' 9 | function calbak(x,y,val) 10 | f:write(' ', x, y, nil, val==0 and { 125, 125, 125, 255 } or nil) 11 | end 12 | local update=false 13 | function love.update() 14 | if update then 15 | update=false 16 | em:create(calbak) 17 | end 18 | end 19 | function love.keypressed(key) update=true end 20 | -------------------------------------------------------------------------------- /examples/engine.lua: -------------------------------------------------------------------------------- 1 | --[[ Engine ]]-- 2 | ROT=require 'src.rot' 3 | 4 | a1=ROT.Class:extend("ActorOne", {lives=3}) 5 | 6 | function a1:act() 7 | f:write('.'..','..os.clock(), 1, self.lives) 8 | self.lives=self.lives-1 9 | if self.lives<1 then 10 | s:remove(self) 11 | e:lock() 12 | love.timer.sleep(.5) 13 | unlock() 14 | end 15 | end 16 | 17 | a2=ROT.Class:extend("ActorTwo") 18 | 19 | function a2:act() 20 | f:write('@'..','..os.clock(), 1, 4) 21 | s:remove(self) 22 | end 23 | 24 | function unlock() 25 | s:add(a2, false) 26 | e:unlock() 27 | end 28 | 29 | function love.load() 30 | f=ROT.Display(80,24) 31 | s=ROT.Scheduler.Simple:new() 32 | e=ROT.Engine:new(s) 33 | s:add(a1, true) 34 | e:start() 35 | end 36 | 37 | function love.draw() 38 | f:draw() 39 | end 40 | -------------------------------------------------------------------------------- /examples/event.lua: -------------------------------------------------------------------------------- 1 | --[[ Event Queue ]]-- 2 | ROT=require 'src.rot' 3 | function love.load() 4 | f=ROT.Display(80,24) 5 | q=ROT.EventQueue() 6 | q:add('e1', 100) 7 | q:add('e2', 50) 8 | q:add('e3', 10) 9 | q:remove('e2') 10 | f:writeCenter(tostring(q:get()), 11) 11 | f:writeCenter(tostring(q:get()), 12) 12 | f:writeCenter(tostring(q:getTime()), 13) 13 | end 14 | function love.draw() f:draw() end 15 | --]] 16 | -------------------------------------------------------------------------------- /examples/iceyMaze.lua: -------------------------------------------------------------------------------- 1 | --[[ Divided Maze ]] 2 | ROT=require 'src.rot' 3 | function love.load() 4 | -- Icey works better with odd width/height 5 | f =ROT.Display:new(81,25) 6 | im=ROT.Map.IceyMaze:new(f:getWidth(), f:getHeight()) 7 | im:create(calbak) 8 | end 9 | local update=false 10 | function love.update() 11 | if update then 12 | update=false 13 | im=ROT.Map.IceyMaze:new(f:getWidth(), f:getHeight()) 14 | im:create(calbak) 15 | end 16 | end 17 | function love.draw() f:draw() end 18 | function calbak(x,y,val) 19 | f:write(val==1 and '#' or '.', x, y) 20 | end 21 | function love.keypressed(key) update=true end 22 | -------------------------------------------------------------------------------- /examples/lighting.lua: -------------------------------------------------------------------------------- 1 | ROT=require 'src.rot' 2 | 3 | function love.load() 4 | f=ROT.Display(80, 24) 5 | maps={ 6 | "Arena", 7 | "DividedMaze", 8 | "IceyMaze", 9 | "EllerMaze", 10 | "Cellular", 11 | "Digger", 12 | "Uniform", 13 | "Rogue", 14 | "Brogue", 15 | } 16 | doTheThing() 17 | end 18 | 19 | function love.draw() f:draw() end 20 | update=false 21 | function love.update() 22 | if update then 23 | update=false 24 | doTheThing() 25 | end 26 | end 27 | function love.keypressed() update=true end 28 | 29 | function doTheThing() 30 | f:clear() 31 | mapData={} 32 | lightData={} 33 | -- Map type defaults to random or you can hard-code it here 34 | mapType=maps[ROT.RNG:random(1,#maps)] 35 | map= ROT.Map[mapType]:new(f:getWidth(), f:getHeight()) 36 | if map.randomize then 37 | floorValue=1 38 | map:randomize(.5) 39 | for i=1,5 do 40 | map:create(mapCallback) 41 | end 42 | else 43 | floorValue=0 44 | map:create(mapCallback) 45 | end 46 | fov=ROT.FOV.Precise:new(lightPasses, {topology=4}) 47 | lighting=ROT.Lighting(reflectivityCB, {range=12, passes=2}) 48 | lighting:setFOV(fov) 49 | for i=1,10 do 50 | local point=getRandomFloor() 51 | f:write('*',tonumber(point[1]),tonumber(point[2])) 52 | lighting:setLight(tonumber(point[1]),tonumber(point[2]), getRandomColor()) 53 | end 54 | lighting:compute(lightingCallback) 55 | local ambientLight={ 0.0, 0.0, 0.0, 1.0 } 56 | for k,_ in pairs(mapData) do 57 | local parts=k:split(',') 58 | local x =tonumber(parts[1]) 59 | local y =tonumber(parts[2]) 60 | local baseColor=mapData[k]==floorValue and { 0.4, 0.4, 0.4, 1.0 } or { 0.2, 0.2, 0.2, 1.0 } 61 | local light=ambientLight 62 | local char=f:getCharacter(x, y) 63 | if lightData[k] then 64 | light=ROT.Color.add(light, lightData[k]) 65 | end 66 | local finalColor=ROT.Color.multiply(baseColor, light) 67 | char=not lightData[k] and ' ' or char~=' ' and char or mapData[x..','..y]~=floorValue and '#' or ' ' 68 | 69 | f:write(char, x, y, light, finalColor) 70 | end 71 | mapData=nil 72 | lightData=nil 73 | map=nil 74 | lighting=nil 75 | fov=nil 76 | end 77 | 78 | function lightingCallback(x, y, color) 79 | local key=x..','..y 80 | lightData[x..','..y]=color 81 | end 82 | 83 | function getRandomColor() 84 | return { (ROT.RNG:random(0.4, 0.8)), 85 | (ROT.RNG:random(0.4, 0.8)), 86 | (ROT.RNG:random(0.4, 0.8)), 87 | 1.0} 88 | end 89 | 90 | function getRandomFloor() 91 | local key=nil 92 | while true do 93 | key=ROT.RNG:random(1,f:getWidth())..','..ROT.RNG:random(1,f:getHeight()) 94 | if mapData[key]==floorValue then 95 | return key:split(',') 96 | end 97 | end 98 | end 99 | 100 | function reflectivityCB(lighting, x, y) 101 | local key=x..','..y 102 | return mapData[key]==floorValue and .3 or 0 103 | end 104 | 105 | function lightPasses(fov, x, y) 106 | return mapData[x..','..y]==floorValue 107 | end 108 | 109 | function mapCallback(x, y, val) 110 | mapData[x..','..y]=val 111 | end 112 | -------------------------------------------------------------------------------- /examples/precise.lua: -------------------------------------------------------------------------------- 1 | --[[ Precise Shadowcasting ]]-- 2 | ROT=require 'src.rot' 3 | 4 | function calbak(x, y, val) 5 | map[x..','..y]=val 6 | f:write(val==1 and '#' or '.', x, y) 7 | end 8 | 9 | function lightCalbak(fov, x, y) 10 | local key=x..','..y 11 | if map[key] then 12 | return map[key]==0 13 | end 14 | return false 15 | end 16 | 17 | function computeCalbak(x, y, r, v) 18 | local key =x..','..y 19 | if not map[key] then return end 20 | local color= { 0.4, 0.4, 0.0, 1.0 } 21 | f:write(r>0 and f:getCharacter(x, y) or '@', x, y, nil, color) 22 | end 23 | local player={x=1, y=1} 24 | function placePlayer() 25 | local key =nil 26 | local char='#' 27 | while true do 28 | key=ROT.RNG:random(1,f:getWidth())..','.. 29 | ROT.RNG:random(1,f:getHeight()) 30 | if map[key]==0 then 31 | pos = key:split(',') 32 | player.x, player.y=tonumber(pos[1]), tonumber(pos[2]) 33 | f:write('@', player.x, player.y) 34 | break 35 | end 36 | end 37 | end 38 | 39 | function love.load() 40 | f =ROT.Display(80, 24) 41 | map={} 42 | doTheThing() 43 | end 44 | function doTheThing() 45 | uni=ROT.Map.Uniform:new(f:getWidth(), f:getHeight()) 46 | uni:create(calbak) 47 | fov=ROT.FOV.Precise:new(lightCalbak) 48 | placePlayer() 49 | fov:compute(player.x, player.y, 10, computeCalbak) 50 | end 51 | local update=false 52 | function love.update() 53 | if update then 54 | update=false 55 | doTheThing() 56 | end 57 | end 58 | function love.keypressed() update=true end 59 | function love.draw() f:draw() end 60 | -------------------------------------------------------------------------------- /examples/preciseWithMovingPlayer.lua: -------------------------------------------------------------------------------- 1 | --[[ Precise Shadowcasting ]]-- 2 | 3 | setmetatable(_G, { __newindex = function (k, v) error('global ' .. v, 2) end }) 4 | local ROT=require 'src.rot' 5 | setmetatable(_G, nil) 6 | 7 | function calbak(x, y, val) 8 | map[x..','..y]=val 9 | end 10 | 11 | function lightCalbak(fov, x, y) 12 | local key=x..','..y 13 | if map[key] then 14 | return map[key]==0 15 | end 16 | return false 17 | end 18 | 19 | function computeCalbak(x, y, r, v) 20 | local key =x..','..y 21 | if not map[key] then return end 22 | field[key]=1 23 | seen[key]=1 24 | end 25 | 26 | function placePlayer() 27 | local key =nil 28 | local char='#' 29 | while true do 30 | key=ROT.RNG:random(1,f:getWidth())..','.. 31 | ROT.RNG:random(1,f:getHeight()) 32 | if map[key]==0 then 33 | pos = key:split(',') 34 | player.x, player.y=tonumber(pos[1]), tonumber(pos[2]) 35 | f:write('@', player.x, player.y) 36 | break 37 | end 38 | end 39 | end 40 | 41 | function love.load() 42 | f =ROT.Display(80, 24) 43 | map={} 44 | field={} 45 | seen={} 46 | seenColor={ 0.4, 0.4, 0.4, 1.0 } 47 | fieldColor={ 1.0, 1.0, 1.0, 1.0 } 48 | fieldbg={ 0.2, 0.2, 0.2, 1.0 } 49 | update=false 50 | player={x=1, y=1} 51 | doTheThing() 52 | end 53 | 54 | function doTheThing() 55 | uni=ROT.Map.Uniform:new(f:getWidth(), f:getHeight()) 56 | uni:create(calbak) 57 | fov=ROT.FOV.Precise:new(lightCalbak)--, {topology=4}) 58 | placePlayer() 59 | fov:compute(player.x, player.y, 10, computeCalbak) 60 | end 61 | 62 | function love.update() 63 | if update then 64 | update=false 65 | seen={} 66 | doTheThing() 67 | end 68 | f:clear() 69 | for x=1,f:getWidth() do 70 | for y=1,f:getHeight() do 71 | local key=x..','..y 72 | if seen[key] then 73 | char=key==player.x..','..player.y and '@' or map[key]==0 and '.' or map[key]==1 and '#' 74 | f:write(char, x, y, field[key] and fieldColor or seenColor, field[key] and fieldbg or nil) 75 | end 76 | end 77 | end 78 | local s='Use numpad/vimkeys to move!' 79 | f:write(s, f:getWidth()-#s, f:getHeight()) 80 | end 81 | function love.keypressed(key) 82 | local newPos={0,0} 83 | if key=='kp1' then newPos={-1, 1} 84 | elseif key=='kp2' or key=='j' then newPos={ 0, 1} 85 | elseif key=='kp3' then newPos={ 1, 1} 86 | elseif key=='kp4' or key=='h' then newPos={-1, 0} 87 | elseif key=='kp5' then newPos={ 0, 0} 88 | elseif key=='kp6' or key=='l' then newPos={ 1, 0} 89 | elseif key=='kp7' then newPos={-1,-1} 90 | elseif key=='kp8' or key=='k' then newPos={ 0,-1} 91 | elseif key=='kp9' then newPos={ 1,-1} 92 | else 93 | update=true 94 | end 95 | if newPos~={0,0} then 96 | local newx = player.x+newPos[1] 97 | local newy = player.y+newPos[2] 98 | if map[newx..','..newy]==0 then 99 | field={} 100 | player.x=newx 101 | player.y=newy 102 | fov:compute(player.x, player.y, 10, computeCalbak) 103 | end 104 | end 105 | 106 | end 107 | function love.draw() f:draw() end 108 | -------------------------------------------------------------------------------- /examples/rng.lua: -------------------------------------------------------------------------------- 1 | ROT=require 'src.rot' 2 | function love.load() 3 | f=ROT.Display:new() 4 | doTheThing() 5 | end 6 | 7 | function doTheThing() 8 | local rng = ROT.RNG 9 | 10 | f:write(tostring(rng), 1, 1) 11 | 12 | rng:randomseed() 13 | local state=rng:getState() 14 | for i=2,f:getHeight()/2 do 15 | f:writeCenter(tostring(rng:random()), i) 16 | end 17 | rng:setState(state) 18 | for i=f:getHeight()/2+2,f:getHeight() do 19 | f:writeCenter(tostring(rng:random()), i) 20 | end 21 | end 22 | 23 | function love:draw() f:draw() end 24 | 25 | update=false 26 | function love.update() 27 | if update then 28 | update=false 29 | f:clear() 30 | doTheThing() 31 | end 32 | end 33 | 34 | function love.keypressed() update=true end 35 | 36 | -------------------------------------------------------------------------------- /examples/rogue.lua: -------------------------------------------------------------------------------- 1 | --[[ Rogue ]] 2 | ROT=require 'src.rot' 3 | 4 | function love.load() 5 | f =ROT.Display(80, 24) 6 | rog=ROT.Map.Rogue(f:getWidth(), f:getHeight()) 7 | rog:create(calbak) 8 | end 9 | function love.draw() f:draw() end 10 | function calbak(x, y, val) f:write(val==1 and '#' or '.', x, y) end 11 | update=false 12 | function love.update() 13 | if update then 14 | update=false 15 | rog:create(calbak) 16 | end 17 | end 18 | function love.keypressed(key) update=true end 19 | -------------------------------------------------------------------------------- /examples/simple.lua: -------------------------------------------------------------------------------- 1 | --[[ SimpleScheduler ]]-- 2 | ROT=require 'src.rot' 3 | function love.load() 4 | f=ROT.Display(80, 24) 5 | s=ROT.Scheduler.Simple:new() 6 | for i=1,3 do s:add(i, true) end 7 | end 8 | function love.update() 9 | love.timer.sleep(.5) 10 | f:writeCenter('TURN: '..s:next()) 11 | end 12 | function love.draw() f:draw() end 13 | --]] 14 | 15 | -------------------------------------------------------------------------------- /examples/simplex.lua: -------------------------------------------------------------------------------- 1 | --[[ Simplex Noise ]]-- 2 | ROT=require 'src.rot' 3 | function generateNoise() 4 | sim=ROT.Noise.Simplex() 5 | for j=1,f:getHeight() do 6 | for i=1,f:getWidth() do 7 | local val=sim:get(i/20, j/20) 8 | red =(val>0 and val or 0) 9 | green=(val<0 and -val or 0) 10 | 11 | f:write(' ', i, j, nil, { red, green, 0, 1.0 }) 12 | end 13 | end 14 | end 15 | function love.load() 16 | f =ROT.Display(120, 50) 17 | generateNoise() 18 | end 19 | update=false 20 | function love.update() 21 | if update then 22 | update=false 23 | generateNoise() 24 | end 25 | end 26 | function love.keypressed(key) update=true end 27 | function love.draw() 28 | f:draw() 29 | end 30 | -------------------------------------------------------------------------------- /examples/speed.lua: -------------------------------------------------------------------------------- 1 | --[[ SpeedScheduler ]]-- 2 | ROT=require 'src.rot' 3 | 4 | actor=ROT.Class:extend("actor", {speed, number}) 5 | function actor:init(speed, number) 6 | self.speed=speed 7 | self.number=number 8 | end 9 | function actor:getSpeed() return self.speed end 10 | 11 | function love.load() 12 | f=ROT.Display(80, 24) 13 | s=ROT.Scheduler.Speed:new() 14 | for i=1,4 do 15 | a=actor:new(ROT.RNG:random(1,100), i) 16 | s:add(a, true) 17 | f:writeCenter('Added '..i..', with speed: '..a:getSpeed(), i) 18 | end 19 | end 20 | y=5 21 | function love.update() 22 | love.timer.sleep(.5) 23 | f:writeCenter('TURN: '..s:next().number, y) 24 | y=y<24 and y+1 or 5 25 | end 26 | function love.draw() f:draw() end 27 | --]] 28 | -------------------------------------------------------------------------------- /examples/textDisplay.lua: -------------------------------------------------------------------------------- 1 | --[[ Display, RNG ]]-- 2 | ROT=require 'src.rot' 3 | function love.load() 4 | frame=ROT.TextDisplay() 5 | end 6 | function love.draw() 7 | frame:draw() 8 | end 9 | 10 | x,y,i=1,1,64 11 | 12 | function love.update() 13 | if x<80 then x=x+1 14 | else x,y=1,y<24 and y+1 or 1 15 | end 16 | i = i<120 and i+1 or 64 17 | frame:write(string.char(i), x, y, getRandomColor(), getRandomColor()) 18 | end 19 | 20 | function getRandomColor() 21 | return { (ROT.RNG:random(0,1.0)), 22 | (ROT.RNG:random(0,1.0)), 23 | (ROT.RNG:random(0,1.0)), 24 | 1.0} 25 | end 26 | --]] 27 | -------------------------------------------------------------------------------- /examples/uniform.lua: -------------------------------------------------------------------------------- 1 | --[[ Uniform ]] 2 | ROT=require 'src.rot' 3 | 4 | update=false 5 | function love.load() 6 | f =ROT.Display(80, 24) 7 | uni=ROT.Map.Uniform(f:getWidth(), f:getHeight()) 8 | update=true 9 | end 10 | function love.draw() f:draw() end 11 | function calbak(x, y, val) f:write(val==1 and '#' or '.', x, y) end 12 | function love.update() 13 | if update then 14 | update=false 15 | uni:create(calbak) 16 | local rooms=uni:getDoors() 17 | for k,v in pairs(rooms) do 18 | f:write('+', v.x, v.y) 19 | end 20 | end 21 | end 22 | function love.keypressed(key) update=true end 23 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | --- rotLove Demo runner 2 | -- specify which example you'd like to see on the command line 3 | -- 4 | -- to list all available examples, do: 5 | -- love . list 6 | 7 | if arg[2] == "rtd" then 8 | local mod = love.filesystem.getDirectoryItems("examples") 9 | for i=#mod, 1, -1 do 10 | if not mod[i]:match("%.lua$") then 11 | table.remove(mod, i) 12 | else 13 | mod[i] = mod[i]:gsub("%.lua$", "") 14 | end 15 | end 16 | math.randomseed(os.time()) 17 | arg[2] = mod[math.random(1, #mod)] 18 | print("running " .. arg[2]) 19 | end 20 | 21 | if arg[2] then 22 | require("examples.".. arg[2]) 23 | else 24 | io.write([[ 25 | ERROR: Please specify a demo, for example: 26 | love . preciseWithMovingPlayer 27 | you can get a random one using 28 | love . rtd 29 | ]]) 30 | 31 | love.event.push('quit') 32 | end 33 | -------------------------------------------------------------------------------- /src/img/cp437.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paulofmandown/rotLove/4e0d9bfcb5bbcd47bc9ac0b93acf17a62f33b70f/src/img/cp437.png -------------------------------------------------------------------------------- /src/rot.lua: -------------------------------------------------------------------------------- 1 | local ROTLOVE_PATH = (...) .. '.' 2 | local Class = require (ROTLOVE_PATH .. 'class') 3 | 4 | local ROT = Class:extend('ROT', { 5 | DEFAULT_WIDTH =80, 6 | DEFAULT_HEIGHT=24, 7 | 8 | DIRS= {FOUR={ 9 | { 0,-1}, 10 | { 1, 0}, 11 | { 0, 1}, 12 | {-1, 0} 13 | }, 14 | EIGHT={ 15 | { 0,-1}, 16 | { 1,-1}, 17 | { 1, 0}, 18 | { 1, 1}, 19 | { 0, 1}, 20 | {-1, 1}, 21 | {-1, 0}, 22 | {-1,-1} 23 | } 24 | } 25 | }) 26 | package.loaded[...] = ROT 27 | 28 | -- Concatenating assert function 29 | -- see http://lua.space/general/assert-usage-caveat 30 | function ROT.assert(pass, ...) 31 | if pass then 32 | return pass, ... 33 | elseif select('#', ...) > 0 then 34 | error(table.concat({...}), 2) 35 | else 36 | error('assertion failed!', 2) 37 | end 38 | end 39 | 40 | ROT.Class = Class 41 | 42 | ROT.RNG = require (ROTLOVE_PATH .. 'rng') 43 | 44 | -- bind a function to a class instance 45 | function Class:bind (func) 46 | return function (...) return func(self, ...) end 47 | end 48 | 49 | -- get/set RNG instance for a class 50 | -- used by maps, noise, dice, etc. 51 | Class._rng = ROT.RNG 52 | function Class:getRNG() 53 | return self._rng 54 | end 55 | function Class:setRNG(rng) 56 | self._rng = rng or ROT.RNG 57 | return self 58 | end 59 | 60 | require (ROTLOVE_PATH .. 'newFuncs') 61 | 62 | ROT.Type = {} -- collection types tuned for various use cases 63 | ROT.Type.PointSet = require (ROTLOVE_PATH .. 'type.pointSet') 64 | ROT.Type.Grid = require (ROTLOVE_PATH .. 'type.grid') 65 | 66 | ROT.Dice = require (ROTLOVE_PATH .. 'dice') 67 | ROT.Display = require (ROTLOVE_PATH .. 'display') 68 | ROT.TextDisplay = require (ROTLOVE_PATH .. 'textDisplay') 69 | ROT.StringGenerator = require (ROTLOVE_PATH .. 'stringGenerator') 70 | ROT.EventQueue = require (ROTLOVE_PATH .. 'eventQueue') 71 | ROT.Scheduler = require (ROTLOVE_PATH .. 'scheduler') 72 | ROT.Scheduler.Simple = require (ROTLOVE_PATH .. 'scheduler.simple') 73 | ROT.Scheduler.Speed = require (ROTLOVE_PATH .. 'scheduler.speed') 74 | ROT.Scheduler.Action = require (ROTLOVE_PATH .. 'scheduler.action') 75 | ROT.Engine = require (ROTLOVE_PATH .. 'engine') 76 | ROT.Map = require (ROTLOVE_PATH .. 'map') 77 | ROT.Map.Arena = require (ROTLOVE_PATH .. 'map.arena') 78 | ROT.Map.DividedMaze = require (ROTLOVE_PATH .. 'map.dividedMaze') 79 | ROT.Map.IceyMaze = require (ROTLOVE_PATH .. 'map.iceyMaze') 80 | ROT.Map.EllerMaze = require (ROTLOVE_PATH .. 'map.ellerMaze') 81 | ROT.Map.Cellular = require (ROTLOVE_PATH .. 'map.cellular') 82 | ROT.Map.Dungeon = require (ROTLOVE_PATH .. 'map.dungeon') 83 | ROT.Map.Feature = require (ROTLOVE_PATH .. 'map.feature') 84 | ROT.Map.Room = require (ROTLOVE_PATH .. 'map.room') 85 | ROT.Map.Corridor = require (ROTLOVE_PATH .. 'map.corridor') 86 | ROT.Map.Digger = require (ROTLOVE_PATH .. 'map.digger') 87 | ROT.Map.Uniform = require (ROTLOVE_PATH .. 'map.uniform') 88 | ROT.Map.Rogue = require (ROTLOVE_PATH .. 'map.rogue') 89 | ROT.Map.BrogueRoom = require (ROTLOVE_PATH .. 'map.brogueRoom') 90 | ROT.Map.Brogue = require (ROTLOVE_PATH .. 'map.brogue') 91 | ROT.Noise = require (ROTLOVE_PATH .. 'noise') 92 | ROT.Noise.Simplex = require (ROTLOVE_PATH .. 'noise.simplex') 93 | ROT.FOV = require (ROTLOVE_PATH .. 'fov') 94 | ROT.FOV.Precise = require (ROTLOVE_PATH .. 'fov.precise') 95 | ROT.FOV.Bresenham = require (ROTLOVE_PATH .. 'fov.bresenham') 96 | ROT.FOV.Recursive = require (ROTLOVE_PATH .. 'fov.recursive') 97 | ROT.Color = require (ROTLOVE_PATH .. 'color') 98 | ROT.Lighting = require (ROTLOVE_PATH .. 'lighting') 99 | ROT.Path = require (ROTLOVE_PATH .. 'path') 100 | ROT.Path.Dijkstra = require (ROTLOVE_PATH .. 'path.dijkstra') 101 | ROT.Path.DijkstraMap = require (ROTLOVE_PATH .. 'path.dijkstraMap') 102 | ROT.Path.AStar = require (ROTLOVE_PATH .. 'path.astar') 103 | ROT.Text = require (ROTLOVE_PATH .. 'text') 104 | 105 | return ROT 106 | 107 | -------------------------------------------------------------------------------- /src/rot/class.lua: -------------------------------------------------------------------------------- 1 | local BaseClass = {} 2 | 3 | function BaseClass:new(...) 4 | local t = setmetatable({}, self) 5 | t:init(...) 6 | return t 7 | end 8 | 9 | function BaseClass:extend(name, t) 10 | t = t or {} 11 | t.__index = t 12 | t.super = self 13 | return setmetatable(t, { __call = self.new, __index = self }) 14 | end 15 | 16 | function BaseClass:init() 17 | end 18 | 19 | return BaseClass 20 | 21 | -------------------------------------------------------------------------------- /src/rot/engine.lua: -------------------------------------------------------------------------------- 1 | local ROT = require((...):gsub(('.[^./\\]*'):rep(1) .. '$', '')) 2 | local Engine = ROT.Class:extend("Engine") 3 | 4 | function Engine:init(scheduler) 5 | self._scheduler=scheduler 6 | self._lock =1 7 | end 8 | 9 | function Engine:start() 10 | return self:unlock() 11 | end 12 | 13 | function Engine:lock() 14 | self._lock=self._lock+1 15 | end 16 | 17 | function Engine:unlock() 18 | assert(self._lock>0, 'Cannot unlock unlocked Engine') 19 | self._lock=self._lock-1 20 | while self._lock<1 do 21 | local actor=self._scheduler:next() 22 | if not actor then return self:lock() end 23 | actor:act() 24 | end 25 | return self 26 | end 27 | 28 | return Engine 29 | -------------------------------------------------------------------------------- /src/rot/eventQueue.lua: -------------------------------------------------------------------------------- 1 | --- Stores and retrieves events based on time. 2 | -- @module ROT.EventQueue 3 | local ROT = require((...):gsub(('.[^./\\]*'):rep(1) .. '$', '')) 4 | local EventQueue = ROT.Class:extend("EventQueue") 5 | 6 | function EventQueue:init() 7 | self._time = 0 8 | self._events = {} 9 | self._eventTimes = {} 10 | end 11 | 12 | --- Get Time. 13 | -- Get time counted since start 14 | -- @treturn int elapsed time 15 | function EventQueue:getTime() 16 | return self._time 17 | end 18 | 19 | --- Clear. 20 | -- Remove all events from queue 21 | -- @treturn ROT.EventQueue self 22 | function EventQueue:clear() 23 | self._events ={} 24 | self._eventTimes={} 25 | return self 26 | end 27 | 28 | --- Add. 29 | -- Add an event 30 | -- @tparam any event Any object 31 | -- @tparam int time The number of time units that will elapse before this event is returned 32 | function EventQueue:add(event, time) 33 | local index= 1 34 | if self._eventTimes then 35 | for i=1,#self._eventTimes do 36 | if self._eventTimes[i]>time then 37 | index=i 38 | break 39 | end 40 | index=i+1 41 | end 42 | end 43 | table.insert(self._events, index, event) 44 | table.insert(self._eventTimes, index, time) 45 | end 46 | 47 | --- Get. 48 | -- Get the next event from the queue and advance the appropriate amount time 49 | -- @treturn event|nil The event previously added by .add() or nil if none are queued 50 | function EventQueue:get() 51 | if #self._events<1 then return nil end 52 | local time = table.remove(self._eventTimes, 1) 53 | if time>0 then 54 | self._time=self._time+time 55 | for i=1,#self._eventTimes do 56 | self._eventTimes[i]=self._eventTimes[i]-time 57 | end 58 | end 59 | return table.remove(self._events, 1) 60 | end 61 | 62 | --- Get event time. 63 | -- Get the time associated with the given event 64 | -- @tparam any event 65 | -- @treturn number time 66 | function EventQueue:getEventTime(event) 67 | local index=table.indexOf(self._events, event) 68 | if index==0 then return nil end 69 | return self._eventTimes[index] 70 | end 71 | 72 | --- Remove. 73 | -- Find and remove an event from the queue 74 | -- @tparam any event The previously added event to be removed 75 | -- @treturn boolean true if an event was removed from the queue 76 | function EventQueue:remove(event) 77 | local index=table.indexOf(self._events, event) 78 | if index==0 then return false end 79 | self:_remove(index) 80 | return true 81 | end 82 | 83 | function EventQueue:_remove(index) 84 | table.remove(self._events, index) 85 | table.remove(self._eventTimes, index) 86 | end 87 | 88 | return EventQueue 89 | -------------------------------------------------------------------------------- /src/rot/fov.lua: -------------------------------------------------------------------------------- 1 | local ROT = require((...):gsub(('.[^./\\]*'):rep(1) .. '$', '')) 2 | local FOV = ROT.Class:extend("FOV") 3 | 4 | function FOV:init(lightPassesCallback, options) 5 | self._lightPasses=lightPassesCallback 6 | self._options={topology=8} 7 | if options then for k,_ in pairs(options) do self._options[k]=options[k] end end 8 | end 9 | 10 | function FOV:compute() end 11 | 12 | function FOV:_getCircle(cx, cy, r) 13 | local result={} 14 | local dirs, countFactor, startOffset 15 | local topo=self._options.topology 16 | if topo==4 then 17 | countFactor=1 18 | startOffset={0,1} 19 | dirs={ 20 | ROT.DIRS.EIGHT[8], 21 | ROT.DIRS.EIGHT[2], 22 | ROT.DIRS.EIGHT[4], 23 | ROT.DIRS.EIGHT[6] 24 | } 25 | elseif topo==8 then 26 | dirs=ROT.DIRS.FOUR 27 | countFactor=2 28 | startOffset={-1,1} 29 | end 30 | 31 | local x=cx+startOffset[1]*r 32 | local y=cy+startOffset[2]*r 33 | 34 | for i=1,#dirs do 35 | for _=1,r*countFactor do 36 | table.insert(result, {x, y}) 37 | x=x+dirs[i][1] 38 | y=y+dirs[i][2] 39 | end 40 | end 41 | return result 42 | end 43 | 44 | function FOV:_getRealCircle(cx, cy, r) 45 | local i=0 46 | local result={} 47 | while i<2*math.pi do 48 | i=i+0.05 49 | local x = cx + r * math.cos(i) 50 | local y = cy + r * math.sin(i) 51 | table.insert(result, {x,y}) 52 | end 53 | return result 54 | end 55 | 56 | return FOV 57 | -------------------------------------------------------------------------------- /src/rot/fov/precise.lua: -------------------------------------------------------------------------------- 1 | --- Precise Shadowcasting Field of View calculator. 2 | -- The Precise shadow casting algorithm developed by Ondřej Žára for rot.js. 3 | -- See http://roguebasin.roguelikedevelopment.org/index.php?title=Precise_Shadowcasting_in_JavaScript 4 | -- @module ROT.FOV.Precise 5 | local ROT = require((...):gsub(('.[^./\\]*'):rep(2) .. '$', '')) 6 | local Precise=ROT.FOV:extend("Precise") 7 | --- Constructor. 8 | -- Called with ROT.FOV.Precise:new() 9 | -- @tparam function lightPassesCallback A function with two parameters (x, y) that returns true if a map cell will allow light to pass through 10 | -- @tparam table options Options 11 | -- @tparam int options.topology Direction for light movement Accepted values: (4 or 8) 12 | function Precise:init(lightPassesCallback, options) 13 | Precise.super.init(self, lightPassesCallback, options) 14 | end 15 | 16 | --- Compute. 17 | -- Get visibility from a given point 18 | -- @tparam int x x-position of center of FOV 19 | -- @tparam int y y-position of center of FOV 20 | -- @tparam int R radius of FOV (i.e.: At most, I can see for R cells) 21 | -- @tparam function callback A function that is called for every cell in view. Must accept four parameters. 22 | -- @tparam int callback.x x-position of cell that is in view 23 | -- @tparam int callback.y y-position of cell that is in view 24 | -- @tparam int callback.r The cell's distance from center of FOV 25 | -- @tparam number callback.visibility The cell's visibility rating (from 0-1). How well can you see this cell? 26 | function Precise:compute(x, y, R, callback) 27 | callback(x, y, 0, 1) 28 | local SHADOWS={} 29 | 30 | local blocks, A1, A2, visibility 31 | 32 | for r=1,R do 33 | local neighbors=self:_getCircle(x, y, r) 34 | local neighborCount=#neighbors 35 | 36 | for i=0,neighborCount-1 do 37 | local cx=neighbors[i+1][1] 38 | local cy=neighbors[i+1][2] 39 | A1={i>0 and 2*i-1 or 2*neighborCount-1, 2*neighborCount} 40 | A2={2*i+1, 2*neighborCount} 41 | 42 | blocks =not self:_lightPasses(cx, cy) 43 | visibility=self:_checkVisibility(A1, A2, blocks, SHADOWS) 44 | if visibility>0 then callback(cx, cy, r, visibility) end 45 | if #SHADOWS==2 and SHADOWS[1][1]==0 and SHADOWS[2][1]==SHADOWS[2][2] then 46 | break 47 | end 48 | end 49 | end 50 | end 51 | 52 | local function splice(t, i, rn, it) -- table, index, numberToRemove, insertTable 53 | if rn>0 then 54 | for _=1,rn do 55 | table.remove(t, i) 56 | end 57 | end 58 | if it and #it>0 then 59 | for idx=i,i+#it-1 do 60 | local el=table.remove(it, 1) 61 | if el then table.insert(t, idx, el) end 62 | end 63 | end 64 | end 65 | 66 | function Precise:_checkVisibility(A1, A2, blocks, SHADOWS) 67 | if A1[1]>A2[1] then 68 | local v1=self:_checkVisibility(A1, {A1[2], A1[2]}, blocks, SHADOWS) 69 | local v2=self:_checkVisibility({0, 1}, A2, blocks, SHADOWS) 70 | return (v1+v2)/2 71 | end 72 | local index1=1 73 | local edge1 =false 74 | while index1<=#SHADOWS do 75 | local old =SHADOWS[index1] 76 | local diff=old[1]*A1[2] - A1[1]*old[2] 77 | if diff>=0 then 78 | if diff==0 and (index1)%2==1 then edge1=true end 79 | break 80 | end 81 | index1=index1+1 82 | end 83 | 84 | local index2=#SHADOWS 85 | local edge2=false 86 | while index2>0 do 87 | local old =SHADOWS[index2] 88 | local diff=A2[1]*old[2] - old[1]*A2[2] 89 | if diff >= 0 then 90 | if diff==0 and (index2)%2==0 then edge2=true end 91 | break 92 | end 93 | index2=index2-1 94 | end 95 | local visible=true 96 | if index1==index2 and (edge1 or edge2) then 97 | visible=false 98 | elseif edge1 and edge2 and index1+1==index2 and (index2)%2==0 then 99 | visible=false 100 | elseif index1>index2 and (index1)%2==0 then 101 | visible=false 102 | end 103 | if not visible then return 0 end 104 | local visibleLength=0 105 | local remove=index2-index1+1 106 | if remove%2==1 then 107 | if (index1)%2==0 then 108 | if #SHADOWS>0 then 109 | local P=SHADOWS[index1] 110 | visibleLength=(A2[1]*P[2] - P[1]*A2[2]) / (P[2]*A2[2]) 111 | end 112 | if blocks then splice(SHADOWS, index1, remove, {A2}) end 113 | else 114 | if #SHADOWS>0 then 115 | local P=SHADOWS[index2] 116 | visibleLength=(P[1]*A1[2] - A1[1]*P[2]) / (A1[2]*P[2]) 117 | end 118 | if blocks then splice(SHADOWS, index1, remove, {A1}) end 119 | end 120 | else 121 | if (index1)%2==0 then 122 | if #SHADOWS>0 then 123 | local P1=SHADOWS[index1] 124 | local P2=SHADOWS[index2] 125 | visibleLength=(P2[1]*P1[2] - P1[1]*P2[2]) / (P1[2]*P2[2]) 126 | end 127 | if blocks then splice(SHADOWS, index1, remove) end 128 | else 129 | if blocks then splice(SHADOWS, index1, remove, {A1, A2}) end 130 | return 1 131 | end 132 | end 133 | 134 | local arcLength=(A2[1]*A1[2] - A1[1]*A2[2]) / (A1[2]*A2[2]) 135 | return visibleLength/arcLength 136 | end 137 | 138 | return Precise 139 | -------------------------------------------------------------------------------- /src/rot/fov/recursive.lua: -------------------------------------------------------------------------------- 1 | --- Recursive Shadowcasting Field of View calculator. 2 | -- The Recursive shadow casting algorithm developed by Ondřej Žára for rot.js. 3 | -- See http://roguebasin.roguelikedevelopment.org/index.php?title=Recursive_Shadowcasting_in_JavaScript 4 | -- @module ROT.FOV.Recursive 5 | local ROT = require((...):gsub(('.[^./\\]*'):rep(2) .. '$', '')) 6 | local Recursive=ROT.FOV:extend("Recursive") 7 | --- Constructor. 8 | -- Called with ROT.FOV.Recursive:new() 9 | -- @tparam function lightPassesCallback A function with two parameters (x, y) that returns true if a map cell will allow light to pass through 10 | -- @tparam table options Options 11 | -- @tparam int options.topology Direction for light movement Accepted values: (4 or 8) 12 | function Recursive:init(lightPassesCallback, options) 13 | Recursive.super.init(self, lightPassesCallback, options) 14 | end 15 | 16 | Recursive._octants = { 17 | {-1, 0, 0, 1}, 18 | { 0, -1, 1, 0}, 19 | { 0, -1, -1, 0}, 20 | {-1, 0, 0, -1}, 21 | { 1, 0, 0, -1}, 22 | { 0, 1, -1, 0}, 23 | { 0, 1, 1, 0}, 24 | { 1, 0, 0, 1} 25 | } 26 | 27 | --- Compute. 28 | -- Get visibility from a given point 29 | -- @tparam int x x-position of center of FOV 30 | -- @tparam int y y-position of center of FOV 31 | -- @tparam int R radius of FOV (i.e.: At most, I can see for R cells) 32 | -- @tparam function callback A function that is called for every cell in view. Must accept four parameters. 33 | -- @tparam int callback.x x-position of cell that is in view 34 | -- @tparam int callback.y y-position of cell that is in view 35 | -- @tparam int callback.r The cell's distance from center of FOV 36 | -- @tparam boolean callback.visibility Indicates if the cell is seen 37 | function Recursive:compute(x, y, R, callback) 38 | callback(x, y, 0, true) 39 | for i=1,#self._octants do 40 | self:_renderOctant(x,y,self._octants[i], R, callback) 41 | end 42 | end 43 | 44 | --- Compute 180. 45 | -- Get visibility from a given point for a 180 degree arc 46 | -- @tparam int x x-position of center of FOV 47 | -- @tparam int y y-position of center of FOV 48 | -- @tparam int R radius of FOV (i.e.: At most, I can see for R cells) 49 | -- @tparam int dir viewing direction (use ROT.DIR index for values) 50 | -- @tparam function callback A function that is called for every cell in view. Must accept four parameters. 51 | -- @tparam int callback.x x-position of cell that is in view 52 | -- @tparam int callback.y y-position of cell that is in view 53 | -- @tparam int callback.r The cell's distance from center of FOV 54 | -- @tparam boolean callback.visibility Indicates if the cell is seen 55 | function Recursive:compute180(x, y, R, dir, callback) 56 | callback(x, y, 0, true) 57 | local prev=((dir-2+8)%8)+1 58 | local nPre=((dir-3+8)%8)+1 59 | local next=((dir +8)%8)+1 60 | 61 | self:_renderOctant(x, y, self._octants[nPre], R, callback) 62 | self:_renderOctant(x, y, self._octants[prev], R, callback) 63 | self:_renderOctant(x, y, self._octants[dir ], R, callback) 64 | self:_renderOctant(x, y, self._octants[next], R, callback) 65 | end 66 | --- Compute 90. 67 | -- Get visibility from a given point for a 90 degree arc 68 | -- @tparam int x x-position of center of FOV 69 | -- @tparam int y y-position of center of FOV 70 | -- @tparam int R radius of FOV (i.e.: At most, I can see for R cells) 71 | -- @tparam int dir viewing direction (use ROT.DIR index for values) 72 | -- @tparam function callback A function that is called for every cell in view. Must accept four parameters. 73 | -- @tparam int callback.x x-position of cell that is in view 74 | -- @tparam int callback.y y-position of cell that is in view 75 | -- @tparam int callback.r The cell's distance from center of FOV 76 | -- @tparam boolean callback.visibility Indicates if the cell is seen 77 | function Recursive:compute90(x, y, R, dir, callback) 78 | callback(x, y, 0, true) 79 | local prev=((dir-2+8)%8)+1 80 | 81 | self:_renderOctant(x, y, self._octants[dir ], R, callback) 82 | self:_renderOctant(x, y, self._octants[prev], R, callback) 83 | end 84 | 85 | function Recursive:_renderOctant(x, y, octant, R, callback) 86 | self:_castVisibility(x, y, 1, 1.0, 0.0, R + 1, octant[1], octant[2], octant[3], octant[4], callback) 87 | end 88 | 89 | function Recursive:_castVisibility(startX, startY, row, visSlopeStart, visSlopeEnd, radius, xx, xy, yx, yy, callback) 90 | if visSlopeStart1 means a *highly simplified* radiosity-like algorithm. 16 | -- @tparam[opt=100] int options.emissionThreshold Cells with emissivity > threshold will be treated as light source in the next pass. 17 | -- @tparam[opt=10] int options.range Max light range 18 | function Lighting:init(reflectivityCallback, options) 19 | self._reflectivityCallback=reflectivityCallback 20 | self._options={passes=1, emissionThreshold=100, range=10} 21 | self._fov=nil 22 | self._lights = Grid() 23 | self._reflectivityCache = Grid() 24 | self._fovCache = Grid() 25 | 26 | if options then for k,_ in pairs(options) do self._options[k]=options[k] end end 27 | end 28 | 29 | --- Set FOV 30 | -- Set the Field of View algorithm used to calculate light emission 31 | -- @tparam userdata fov Class/Module used to calculate fov Must have compute(x, y, range, cb) method. Typically you would supply ROT.FOV.Precise:new() here. 32 | -- @treturn ROT.Lighting self 33 | -- @see ROT.FOV.Precise 34 | -- @see ROT.FOV.Bresenham 35 | function Lighting:setFOV(fov) 36 | self._fov=fov 37 | self._fovCache = Grid() 38 | return self 39 | end 40 | 41 | --- Add or remove a light source 42 | -- @tparam int x x-position of light source 43 | -- @tparam int y y-position of light source 44 | -- @tparam nil|string|table color An string accepted by Color:fromString(str) or a color table. A nil value here will remove the light source at x, y 45 | -- @treturn ROT.Lighting self 46 | -- @see ROT.Color 47 | function Lighting:setLight(x, y, color) 48 | self._lights:setCell(x, y, 49 | type(color)=='string' and ROT.Color.fromString(color) or color or nil) 50 | return self 51 | end 52 | 53 | --- Compute. 54 | -- Compute the light sources and lit cells 55 | -- @tparam function lightingCallback Will be called with (x, y, color) for every lit cell 56 | -- @treturn ROT.Lighting self 57 | function Lighting:compute(lightingCallback) 58 | local doneCells = PointSet() 59 | local emittingCells = Grid() 60 | local litCells = Grid() 61 | 62 | for _, x, y, light in self._lights:each() do 63 | local emitted = emittingCells:getCell(x, y) 64 | if not emitted then 65 | emitted = { 0, 0, 0 } 66 | emittingCells:setCell(x, y, emitted) 67 | end 68 | ROT.Color.add_(emitted, light) 69 | end 70 | 71 | for i=1,self._options.passes do 72 | self:_emitLight(emittingCells, litCells, doneCells) 73 | if i0 then 107 | local emission ={} 108 | local intensity=0 109 | for l, c in ipairs(color) do 110 | if l < 4 then 111 | local part=c*reflectivity 112 | emission[l]=part 113 | intensity=intensity+part 114 | end 115 | end 116 | if intensity>self._options.emissionThreshold then 117 | result:setCell(x, y, emission) 118 | end 119 | end 120 | end 121 | end 122 | 123 | return result 124 | end 125 | 126 | function Lighting:_emitLightFromCell(x, y, color, litCells) 127 | local fov = self._fovCache:getCell(x, y) or self:_updateFOV(x, y) 128 | for _, x, y, formFactor in fov:each() do 129 | local cellColor = litCells:getCell(x, y) 130 | if not cellColor then 131 | cellColor = { 0, 0, 0 } 132 | litCells:setCell(x, y, cellColor) 133 | end 134 | for l = 1, 3 do 135 | cellColor[l] = cellColor[l] + (color[l]*formFactor) 136 | end 137 | end 138 | return self 139 | end 140 | 141 | function Lighting:_updateFOV(x, y) 142 | local cache = Grid() 143 | self._fovCache:setCell(x, y, cache) 144 | local range=self._options.range 145 | local function cb(x, y, r, vis) 146 | local formFactor=vis*(1-r/range) 147 | if formFactor==0 then return end 148 | cache:setCell(x, y, formFactor) 149 | end 150 | self._fov:compute(x, y, range, cb) 151 | 152 | return cache 153 | end 154 | 155 | return Lighting 156 | 157 | -------------------------------------------------------------------------------- /src/rot/map.lua: -------------------------------------------------------------------------------- 1 | local ROT = require((...):gsub(('.[^./\\]*'):rep(1) .. '$', '')) 2 | local Map = ROT.Class:extend("Map") 3 | 4 | function Map:init(width, height) 5 | self._width = width or ROT.DEFAULT_WIDTH 6 | self._height = height or ROT.DEFAULT_HEIGHT 7 | end 8 | 9 | function Map:create() end 10 | 11 | function Map:_fillMap(value) 12 | local map = {} 13 | for x = 1, self._width do 14 | map[x] = {} 15 | for y = 1, self._height do 16 | map[x][y] = value 17 | end 18 | end 19 | return map 20 | end 21 | 22 | return Map 23 | 24 | -------------------------------------------------------------------------------- /src/rot/map/arena.lua: -------------------------------------------------------------------------------- 1 | --- The Arena map generator. 2 | -- Generates an arena style map. All cells except for the extreme borders are floors. The borders are walls. 3 | -- @module ROT.Map.Arena 4 | local ROT = require((...):gsub(('.[^./\\]*'):rep(2) .. '$', '')) 5 | local Arena = ROT.Map:extend("Arena") 6 | --- Constructor. 7 | -- Called with ROT.Map.Arena:new(width, height) 8 | -- @tparam int width Width in cells of the map 9 | -- @tparam int height Height in cells of the map 10 | function Arena:init(width, height) 11 | Arena.super.init(self, width, height) 12 | end 13 | 14 | --- Create. 15 | -- Creates a map. 16 | -- @tparam function callback This function will be called for every cell. It must accept the following parameters: 17 | -- @tparam int callback.x The x-position of a cell in the map 18 | -- @tparam int callback.y The y-position of a cell in the map 19 | -- @tparam int callback.value A value representing the cell-type. 0==floor, 1==wall 20 | -- @treturn ROT.Map.Arena self 21 | function Arena:create(callback) 22 | local w, h = self._width, self._height 23 | if not callback then return self end 24 | for y = 1, h do 25 | for x = 1, w do 26 | callback(x, y, x>1 and y>1 and xself._options.timeLimit then break end 69 | 70 | local wall=self:_findWall() 71 | if not wall then break end 72 | 73 | local x = wall.x 74 | local y = wall.y 75 | local dir =self:_getDiggingDirection(x, y) 76 | if dir then 77 | local featureAttempts=0 78 | repeat 79 | featureAttempts=featureAttempts+1 80 | if self:_tryFeature(x, y, dir[1], dir[2]) then 81 | self:_removeSurroundingWalls(x, y) 82 | self:_removeSurroundingWalls(x-dir[1], y-dir[2]) 83 | break 84 | end 85 | until featureAttempts>=self._featureAttempts 86 | priorityWalls=0 87 | for i = 1, #self._walls do 88 | local wall = self._walls[i] 89 | if wall.value > 1 then 90 | priorityWalls=priorityWalls+1 91 | end 92 | end 93 | end 94 | until self._dug/area > self._options.dugPercentage and priorityWalls<1 95 | 96 | self:_addDoors() 97 | 98 | if not callback then return self end 99 | for y = 1, self._height do 100 | for x = 1, self._width do 101 | callback(x, y, self._map[x][y]) 102 | end 103 | end 104 | return self 105 | end 106 | 107 | function Digger:_digCallback(x, y, value) 108 | if value==0 or value==2 then 109 | self._map[x][y]=0 110 | self._dug=self._dug+1 111 | else 112 | self:setWall(x, y, 1) 113 | end 114 | end 115 | 116 | function Digger:_isWallCallback(x, y) 117 | if x<1 or y<1 or x>self._width or y>self._height then return false end 118 | return self._map[x][y]==1 119 | end 120 | 121 | function Digger:_canBeDugCallback(x, y) 122 | if x<2 or y<2 or x>=self._width or y>=self._height then return false end 123 | return self._map[x][y]==1 124 | end 125 | 126 | function Digger:_priorityWallCallback(x, y) 127 | self:setWall(x, y, 2) 128 | end 129 | 130 | function Digger:_firstRoom() 131 | local cx =math.floor(self._width/2) 132 | local cy =math.floor(self._height/2) 133 | local room=ROT.Map.Room:new():createRandomCenter(cx, cy, self._options, self._rng) 134 | table.insert(self._rooms, room) 135 | room:create(self._digCallback) 136 | end 137 | 138 | function Digger:_findWall() 139 | local prio1={} 140 | local prio2={} 141 | for i = 1, #self._walls do 142 | local wall = self._walls[i] 143 | if wall.value > 1 then prio2[#prio2 + 1] = wall 144 | else prio1[#prio1 + 1] = wall end 145 | end 146 | local arr=#prio2>0 and prio2 or prio1 147 | if #arr<1 then return nil end 148 | local wall = table.random(arr) 149 | self:setWall(wall.x, wall.y, nil) 150 | return wall 151 | end 152 | 153 | function Digger:_tryFeature(x, y, dx, dy) 154 | local type=self._rng:getWeightedValue(self._features) 155 | local feature=ROT.Map[type]:createRandomAt(x,y,dx,dy,self._options,self._rng) 156 | 157 | if not feature:isValid(self._isWallCallback, self._canBeDugCallback) then 158 | return false 159 | end 160 | 161 | feature:create(self._digCallback) 162 | 163 | if type=='Room' then 164 | table.insert(self._rooms, feature) 165 | elseif type=='Corridor' then 166 | feature:createPriorityWalls(self._priorityWallCallback) 167 | table.insert(self._corridors, feature) 168 | end 169 | 170 | return true 171 | end 172 | 173 | function Digger:_removeSurroundingWalls(cx, cy) 174 | local deltas=ROT.DIRS.FOUR 175 | for i=1,#deltas do 176 | local delta=deltas[i] 177 | local x =cx+delta[1] 178 | local y =cy+delta[2] 179 | self:setWall(x, y, nil) 180 | x=2*delta[1] 181 | y=2*delta[2] 182 | self:setWall(x, y, nil) 183 | end 184 | end 185 | 186 | function Digger:_getDiggingDirection(cx, cy) 187 | if cx<2 or cy<2 or cx>self._width-1 or cy>self._height-1 then return nil end 188 | local deltas=ROT.DIRS.FOUR 189 | local result=nil 190 | 191 | for i=1,#deltas do 192 | local delta=deltas[i] 193 | local x =cx+delta[1] 194 | local y =cy+delta[2] 195 | if self._map[x][y]==0 then 196 | if result then return nil end 197 | result=delta 198 | end 199 | end 200 | if not result then return nil end 201 | 202 | return {-result[1], -result[2]} 203 | end 204 | 205 | function Digger:_addDoors() 206 | for i=1,#self._rooms do 207 | local room=self._rooms[i] 208 | room:clearDoors() 209 | room:addDoors(self._isWallCallback) 210 | end 211 | end 212 | 213 | return Digger 214 | -------------------------------------------------------------------------------- /src/rot/map/dividedMaze.lua: -------------------------------------------------------------------------------- 1 | --- The Divided Maze Map Generator. 2 | -- Recursively divided maze, http://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_division_method 3 | -- @module ROT.Map.DividedMaze 4 | local ROT = require((...):gsub(('.[^./\\]*'):rep(2) .. '$', '')) 5 | local DividedMaze = ROT.Map:extend("DividedMaze") 6 | --- Constructor. 7 | -- Called with ROT.Map.DividedMaze:new(width, height) 8 | -- @tparam int width Width in cells of the map 9 | -- @tparam int height Height in cells of the map 10 | function DividedMaze:init(width, height) 11 | DividedMaze.super.init(self, width, height) 12 | end 13 | 14 | --- Create. 15 | -- Creates a map. 16 | -- @tparam function callback This function will be called for every cell. It must accept the following parameters: 17 | -- @tparam int callback.x The x-position of a cell in the map 18 | -- @tparam int callback.y The y-position of a cell in the map 19 | -- @tparam int callback.value A value representing the cell-type. 0==floor, 1==wall 20 | -- @treturn ROT.Map.DividedMaze self 21 | function DividedMaze:create(callback) 22 | local w=self._width 23 | local h=self._height 24 | self._map = {} 25 | 26 | for i=1,w do 27 | table.insert(self._map, {}) 28 | for j=1,h do 29 | local border= i==1 or j==1 or i==w or j==h 30 | table.insert(self._map[i], border and 1 or 0) 31 | end 32 | end 33 | self._stack = { {2,2,w-1,h-1} } 34 | self:_process() 35 | if not callback then return self end 36 | for y = 1, h do 37 | for x = 1, w do 38 | callback(x, y, self._map[x][y]) 39 | end 40 | end 41 | return self 42 | end 43 | 44 | function DividedMaze:_process() 45 | while #self._stack>0 do 46 | local room=table.remove(self._stack, 1) 47 | self:_partitionRoom(room) 48 | end 49 | end 50 | 51 | function DividedMaze:_partitionRoom(room) 52 | local availX={} 53 | local availY={} 54 | 55 | for i=room[1]+1,room[3]-1 do 56 | local top =self._map[i][room[2]-1] 57 | local bottom=self._map[i][room[4]+1] 58 | if top>0 and bottom>0 and i%2==0 then table.insert(availX, i) end 59 | end 60 | 61 | for j=room[2]+1,room[4]-1 do 62 | local left =self._map[room[1]-1][j] 63 | local right=self._map[room[3]+1][j] 64 | if left>0 and right>0 and j%2==0 then table.insert(availY, j) end 65 | end 66 | 67 | if #availX==0 or #availY==0 then return end 68 | 69 | local x=table.random(availX) 70 | local y=table.random(availY) 71 | 72 | self._map[x][y]=1 73 | 74 | local walls={} 75 | 76 | table.insert(walls, {}) 77 | for i=room[1],x-1,1 do 78 | self._map[i][y]=1 79 | table.insert(walls[#walls], {i,y}) 80 | end 81 | 82 | table.insert(walls, {}) 83 | for i=x+1,room[3],1 do 84 | self._map[i][y]=1 85 | table.insert(walls[#walls],{i,y}) 86 | end 87 | 88 | table.insert(walls, {}) 89 | for j=room[2],y-1,1 do 90 | self._map[x][j]=1 91 | table.insert(walls[#walls],{x,j}) 92 | end 93 | 94 | table.insert(walls, {}) 95 | for j=y+1,room[4] do 96 | self._map[x][j]=1 97 | table.insert(walls[#walls],{x,j}) 98 | end 99 | 100 | local solid= table.random(walls) 101 | for i=1,#walls do 102 | local w=walls[i] 103 | if w~=solid then 104 | local hole=table.random(w) 105 | self._map[hole[1]][hole[2]]=0 106 | end 107 | end 108 | table.insert(self._stack, {room[1], room[2], x-1, y-1}) 109 | table.insert(self._stack, {x+1, room[2], room[3], y-1}) 110 | table.insert(self._stack, {room[1], y+1, x-1, room[4]}) 111 | table.insert(self._stack, {x+1, y+1, room[3], room[4]}) 112 | 113 | end 114 | 115 | return DividedMaze 116 | -------------------------------------------------------------------------------- /src/rot/map/dungeon.lua: -------------------------------------------------------------------------------- 1 | --- The Dungeon-style map Prototype. 2 | -- This class is extended by ROT.Map.Digger and ROT.Map.Uniform 3 | -- @module ROT.Map.Dungeon 4 | local ROT = require((...):gsub(('.[^./\\]*'):rep(2) .. '$', '')) 5 | local Dungeon = ROT.Map:extend("Dungeon") 6 | --- Constructor. 7 | -- Called with ROT.Map.Cellular:new() 8 | -- @tparam int width Width in cells of the map 9 | -- @tparam int height Height in cells of the map 10 | function Dungeon:init(width, height) 11 | Dungeon.super.init(self, width, height) 12 | self._rooms ={} 13 | self._corridors={} 14 | end 15 | 16 | --- Get rooms 17 | -- Get a table of rooms on the map 18 | -- @treturn table A table containing objects of the type ROT.Map.Room 19 | function Dungeon:getRooms() return self._rooms end 20 | 21 | --- Get doors 22 | -- Get a table of doors on the map 23 | -- @treturn table A table {{x=int, y=int},...} for doors. 24 | 25 | -- FIXME: This could be problematic; it accesses an internal member of another 26 | -- class (room._doors). Will break if underlying implementation changes. 27 | -- Should probably take a callback instead like Room:getDoors(). 28 | 29 | function Dungeon:getDoors() 30 | local result={} 31 | for _, room in ipairs(self._rooms) do 32 | for _, x, y in room._doors:each() do 33 | result[#result + 1] = { x = x, y = y } 34 | end 35 | end 36 | return result 37 | end 38 | 39 | --- Get corridors 40 | -- Get a table of corridors on the map 41 | -- @treturn table A table containing objects of the type ROT.Map.Corridor 42 | function Dungeon:getCorridors() return self._corridors end 43 | 44 | function Dungeon:_getDetail(name, x, y) 45 | local t = self[name] 46 | for i = 1, #t do 47 | if t[i].x == x and t[i].y == y then return t[i], i end 48 | end 49 | end 50 | 51 | function Dungeon:_setDetail(name, x, y, value) 52 | local detail, i = self:_getDetail(name, x, y) 53 | if detail then 54 | if value then 55 | detail.value = value 56 | else 57 | table.remove(self[name], i) 58 | end 59 | elseif value then 60 | local t = self[name] 61 | detail = { x = x, y = y, value = value } 62 | t[#t + 1] = detail 63 | end 64 | return detail 65 | end 66 | 67 | function Dungeon:getWall(x, y) 68 | return self:_getDetail('_walls', x, y) 69 | end 70 | 71 | function Dungeon:setWall(x, y, value) 72 | return self:_setDetail('_walls', x, y, value) 73 | end 74 | 75 | return Dungeon 76 | -------------------------------------------------------------------------------- /src/rot/map/ellerMaze.lua: -------------------------------------------------------------------------------- 1 | --- The Eller Maze Map Generator. 2 | -- See http://homepages.cwi.nl/~tromp/maze.html for explanation 3 | -- @module ROT.Map.EllerMaze 4 | local ROT = require((...):gsub(('.[^./\\]*'):rep(2) .. '$', '')) 5 | local EllerMaze = ROT.Map:extend("EllerMaze") 6 | 7 | --- Constructor. 8 | -- Called with ROT.Map.EllerMaze:new(width, height) 9 | -- @tparam int width Width in cells of the map 10 | -- @tparam int height Height in cells of the map 11 | function EllerMaze:init(width, height) 12 | EllerMaze.super.init(self, width, height) 13 | end 14 | 15 | --- Create. 16 | -- Creates a map. 17 | -- @tparam function callback This function will be called for every cell. It must accept the following parameters: 18 | -- @tparam int callback.x The x-position of a cell in the map 19 | -- @tparam int callback.y The y-position of a cell in the map 20 | -- @tparam int callback.value A value representing the cell-type. 0==floor, 1==wall 21 | -- @treturn ROT.Map.EllerMaze self 22 | function EllerMaze:create(callback) 23 | local map =ROT.Type.Grid() 24 | local w =math.ceil((self._width-2)/2) 25 | local rand=9/24 26 | local L ={} 27 | local R ={} 28 | 29 | for i=1,w do 30 | table.insert(L,i) 31 | table.insert(R,i) 32 | end 33 | table.insert(L,w) 34 | local j=2 35 | while jrand then 42 | self:_addToList(i, L, R) 43 | map:setCell(x + 1, y, 0) 44 | end 45 | 46 | if i~=L[i] and self._rng:random()>rand then 47 | self:_removeFromList(i, L, R) 48 | else 49 | map:setCell(x, y + 1, 0) 50 | end 51 | end 52 | j=j+2 53 | end 54 | --j=self._height%2==1 and self._height-2 or self._height-3 55 | for i=1,w do 56 | local x=2*i 57 | local y=j 58 | map:setCell(x, y, 0) 59 | 60 | if i~=L[i+1] and (i==L[i] or self._rng:random()>rand) then 61 | self:_addToList(i, L, R) 62 | map:setCell(x + 1, y, 0) 63 | end 64 | 65 | self:_removeFromList(i, L, R) 66 | end 67 | 68 | if not callback then return self end 69 | 70 | for y = 1, self._height do 71 | for x = 1, self._width do 72 | callback(x, y, map:getCell(x, y) or 1) 73 | end 74 | end 75 | 76 | return self 77 | end 78 | 79 | function EllerMaze:_removeFromList(i, L, R) 80 | R[L[i]]=R[i] 81 | L[R[i]]=L[i] 82 | R[i] =i 83 | L[i] =i 84 | end 85 | 86 | function EllerMaze:_addToList(i, L, R) 87 | R[L[i+1]]=R[i] 88 | L[R[i]] =L[i+1] 89 | R[i] =i+1 90 | L[i+1] =i 91 | end 92 | 93 | return EllerMaze 94 | -------------------------------------------------------------------------------- /src/rot/map/feature.lua: -------------------------------------------------------------------------------- 1 | local ROT = require((...):gsub(('.[^./\\]*'):rep(2) .. '$', '')) 2 | local Feature = ROT.Class:extend("Feature") 3 | 4 | function Feature:isValid() end 5 | function Feature:create() end 6 | function Feature:debug() end 7 | function Feature:createRandomAt() end 8 | return Feature 9 | -------------------------------------------------------------------------------- /src/rot/map/iceyMaze.lua: -------------------------------------------------------------------------------- 1 | --- The Icey Maze Map Generator. 2 | -- See http://www.roguebasin.roguelikedevelopment.org/index.php?title=Simple_maze for explanation 3 | -- @module ROT.Map.IceyMaze 4 | local ROT = require((...):gsub(('.[^./\\]*'):rep(2) .. '$', '')) 5 | local IceyMaze = ROT.Map:extend("IceyMaze") 6 | --- Constructor. 7 | -- Called with ROT.Map.IceyMaze:new(width, height, regularity) 8 | -- @tparam int width Width in cells of the map 9 | -- @tparam int height Height in cells of the map 10 | -- @tparam int[opt=0] regularity A value used to determine the 'randomness' of the map, 0= more random 11 | function IceyMaze:init(width, height, regularity) 12 | IceyMaze.super.init(self, width, height) 13 | self._regularity= regularity and regularity or 0 14 | end 15 | 16 | --- Create. 17 | -- Creates a map. 18 | -- @tparam function callback This function will be called for every cell. It must accept the following parameters: 19 | -- @tparam int callback.x The x-position of a cell in the map 20 | -- @tparam int callback.y The y-position of a cell in the map 21 | -- @tparam int callback.value A value representing the cell-type. 0==floor, 1==wall 22 | -- @treturn ROT.Map.IceyMaze self 23 | function IceyMaze:create(callback) 24 | local w=self._width 25 | local h=self._height 26 | local map=self:_fillMap(1) 27 | w= w%2==1 and w-1 or w-2 28 | h= h%2==1 and h-1 or h-2 29 | 30 | local cx, cy, nx, ny = 1, 1, 1, 1 31 | local done =0 32 | local blocked=false 33 | local dirs={ 34 | {0,0}, 35 | {0,0}, 36 | {0,0}, 37 | {0,0} 38 | } 39 | repeat 40 | cx=2+2*math.floor(self._rng:random()*(w-1)/2) 41 | cy=2+2*math.floor(self._rng:random()*(h-1)/2) 42 | if done==0 then map[cx][cy]=0 end 43 | if map[cx][cy]==0 then 44 | self:_randomize(dirs) 45 | repeat 46 | if math.floor(self._rng:random()*(self._regularity+1))==0 then self:_randomize(dirs) end 47 | blocked=true 48 | for i=1,4 do 49 | nx=cx+dirs[i][1]*2 50 | ny=cy+dirs[i][2]*2 51 | if self:_isFree(map, nx, ny, w, h) then 52 | map[nx][ny]=0 53 | map[cx+dirs[i][1]][cy+dirs[i][2]]=0 54 | 55 | cx=nx 56 | cy=ny 57 | blocked=false 58 | done=done+1 59 | break 60 | end 61 | end 62 | until blocked 63 | end 64 | until done+1>=w*h/4 65 | 66 | if not callback then return self end 67 | for y = 1, self._height do 68 | for x = 1, self._width do 69 | callback(x, y, map[x][y]) 70 | end 71 | end 72 | return self 73 | end 74 | 75 | function IceyMaze:_randomize(dirs) 76 | for i=1,4 do 77 | dirs[i][1]=0 78 | dirs[i][2]=0 79 | end 80 | local rand=math.floor(self._rng:random()*4) 81 | if rand==0 then 82 | dirs[1][1]=-1 83 | dirs[3][2]=-1 84 | dirs[2][1]= 1 85 | dirs[4][2]= 1 86 | elseif rand==1 then 87 | dirs[4][1]=-1 88 | dirs[2][2]=-1 89 | dirs[3][1]= 1 90 | dirs[1][2]= 1 91 | elseif rand==2 then 92 | dirs[3][1]=-1 93 | dirs[1][2]=-1 94 | dirs[4][1]= 1 95 | dirs[2][2]= 1 96 | elseif rand==3 then 97 | dirs[2][1]=-1 98 | dirs[4][2]=-1 99 | dirs[1][1]= 1 100 | dirs[3][2]= 1 101 | end 102 | end 103 | 104 | function IceyMaze:_isFree(map, x, y, w, h) 105 | if x<2 or y<2 or x>w or y>h then return false end 106 | return map[x][y]~=0 107 | end 108 | 109 | return IceyMaze 110 | -------------------------------------------------------------------------------- /src/rot/newFuncs.lua: -------------------------------------------------------------------------------- 1 | local ROT = require((...):gsub(('.[^./\\]*'):rep(1) .. '$', '')) 2 | 3 | -- asserts the type of 'theTable' is table 4 | local function isATable(theTable) 5 | ROT.assert(type(theTable)=='table', "bad argument #1 to 'random' (table expected got ",type(theTable),")") 6 | end 7 | 8 | -- returns string of length n consisting of only char c 9 | local function charNTimes(c, n) 10 | ROT.assert(#c==1, 'character must be a string of length 1') 11 | local s='' 12 | for _=1,n and n or 2 do 13 | s=s..c 14 | end 15 | return s 16 | end 17 | 18 | -- New Table Functions 19 | -- returns random table element, nil if length is 0 20 | function table.random(theTable) 21 | isATable(theTable) 22 | if #theTable==0 then return nil end 23 | return theTable[math.floor(ROT.RNG:random(#theTable))] 24 | end 25 | -- returns random valid index, nil if length is 0 26 | function table.randomi(theTable) 27 | isATable(theTable) 28 | if #theTable==0 then return nil end 29 | return math.floor(ROT.RNG:random(#theTable)) 30 | end 31 | -- randomly reorders the elements of the provided table and returns the result 32 | function table.randomize(theTable) 33 | isATable(theTable) 34 | local result={} 35 | while #theTable>0 do 36 | table.insert(result, table.remove(theTable, table.randomi(theTable))) 37 | end 38 | return result 39 | end 40 | -- add js slice function 41 | function table.slice (values,i1,i2) 42 | local res = {} 43 | local n = #values 44 | -- default values for range 45 | i1 = i1 or 1 46 | i2 = i2 or n 47 | if i2 < 0 then 48 | i2 = n + i2 + 1 49 | elseif i2 > n then 50 | i2 = n 51 | end 52 | if i1 < 1 or i1 > n then 53 | return {} 54 | end 55 | local k = 1 56 | for i = i1,i2 do 57 | res[k] = values[i] 58 | k = k + 1 59 | end 60 | return res 61 | end 62 | -- add js indexOf function 63 | function table.indexOf(values,value) 64 | if values then 65 | for i=1,#values do 66 | if values[i] == value then return i end 67 | end 68 | end 69 | if type(value)=='table' then return table.indexOfTable(values, value) end 70 | return 0 71 | end 72 | 73 | -- extended for use with tables of tables 74 | function table.indexOfTable(values, value) 75 | if type(value)~='table' then return 0 end 76 | for k,v in ipairs(values) do 77 | if #v==#value then 78 | local match=true 79 | for i=1,#v do 80 | if v[i]~=value[i] then match=false end 81 | end 82 | if match then return k end 83 | end 84 | end 85 | return 0 86 | end 87 | 88 | -- New String functions 89 | -- first letter capitalized 90 | function string:capitalize() 91 | return self:sub(1,1):upper() .. self:sub(2) 92 | end 93 | -- left pad with c char, repeated n times 94 | function string:lpad(c, n) 95 | c=c and c or '0' 96 | n=n and n or 2 97 | local s='' 98 | while #s < n-#self do s=s..c end 99 | return s..self 100 | end 101 | -- right pad with c char, repeated n times 102 | function string:rpad(c, n) 103 | c=c and c or '0' 104 | n=n and n or 2 105 | local s='' 106 | while #s < n-#self do s=s..c end 107 | return self..s 108 | end 109 | -- add js split function 110 | function string:split(delim, maxNb) 111 | -- Eliminate bad cases... 112 | if string.find(self, delim) == nil then 113 | return { self } 114 | end 115 | local result = {} 116 | if delim == '' or not delim then 117 | for i=1,#self do 118 | result[i]=self:sub(i,i) 119 | end 120 | return result 121 | end 122 | if maxNb == nil or maxNb < 1 then 123 | maxNb = 0 -- No limit 124 | end 125 | local pat = "(.-)" .. delim .. "()" 126 | local nb = 0 127 | local lastPos 128 | for part, pos in string.gmatch(self, pat) do 129 | nb = nb + 1 130 | result[nb] = part 131 | lastPos = pos 132 | if nb == maxNb then break end 133 | end 134 | -- Handle the last field 135 | if nb ~= maxNb then 136 | result[nb + 1] = string.sub(self, lastPos) 137 | end 138 | return result 139 | end 140 | 141 | function math.round(n, mult) 142 | mult = mult or 1 143 | return math.floor((n + mult/2)/mult) * mult 144 | end 145 | 146 | -------------------------------------------------------------------------------- /src/rot/noise.lua: -------------------------------------------------------------------------------- 1 | local ROT = require((...):gsub(('.[^./\\]*'):rep(1) .. '$', '')) 2 | local Noise = ROT.Class:extend("Noise") 3 | 4 | function Noise:get() end 5 | 6 | return Noise 7 | -------------------------------------------------------------------------------- /src/rot/noise/simplex.lua: -------------------------------------------------------------------------------- 1 | --- Simplex Noise Generator. 2 | -- Based on a simple 2d implementation of simplex noise by Ondrej Zara 3 | -- Which is based on a speed-improved simplex noise algorithm for 2D, 3D and 4D in Java. 4 | -- Which is based on example code by Stefan Gustavson (stegu@itn.liu.se). 5 | -- With Optimisations by Peter Eastman (peastman@drizzle.stanford.edu). 6 | -- Better rank ordering method by Stefan Gustavson in 2012. 7 | -- @module ROT.Noise.Simplex 8 | local ROT = require((...):gsub(('.[^./\\]*'):rep(2) .. '$', '')) 9 | local Simplex=ROT.Noise:extend("Simplex") 10 | --- Constructor. 11 | -- 2D simplex noise generator. 12 | -- @tparam int gradients The random values for the noise. 13 | function Simplex:init(gradients) 14 | self._F2=.5*(math.sqrt(3)-1) 15 | self._G2=(3-math.sqrt(3))/6 16 | 17 | self._gradients={ 18 | { 0,-1}, 19 | { 1,-1}, 20 | { 1, 0}, 21 | { 1, 1}, 22 | { 0, 1}, 23 | {-1, 1}, 24 | {-1, 0}, 25 | {-1,-1} 26 | } 27 | local permutations={} 28 | local count =gradients and gradients or 256 29 | for i=1,count do 30 | table.insert(permutations, i) 31 | end 32 | 33 | permutations=table.randomize(permutations) 34 | 35 | self._perms ={} 36 | self._indexes={} 37 | 38 | for i=1,2*count do 39 | table.insert(self._perms, permutations[i%count+1]) 40 | table.insert(self._indexes, self._perms[i] % #self._gradients +1) 41 | end 42 | end 43 | 44 | --- Get noise for a cell 45 | -- Iterate over this function to retrieve noise values 46 | -- @tparam int xin x-position of noise value 47 | -- @tparam int yin y-position of noise value 48 | function Simplex:get(xin, yin) 49 | local perms =self._perms 50 | local indexes=self._indexes 51 | local count =#perms/2 52 | local G2 =self._G2 53 | 54 | local n0, n1, n2, gi=0, 0, 0 55 | 56 | local s =(xin+yin)*self._F2 57 | local i =math.floor(xin+s) 58 | local j =math.floor(yin+s) 59 | local t =(i+j)*G2 60 | local X0=i-t 61 | local Y0=j-t 62 | local x0=xin-X0 63 | local y0=yin-Y0 64 | 65 | local i1, j1 66 | if x0>y0 then 67 | i1=1 68 | j1=0 69 | else 70 | i1=0 71 | j1=1 72 | end 73 | 74 | local x1=x0-i1+G2 75 | local y1=y0-j1+G2 76 | local x2=x0-1+2*G2 77 | local y2=y0-1+2*G2 78 | 79 | local ii=i%count+1 80 | local jj=j%count+1 81 | 82 | local t0=.5- x0*x0 - y0*y0 83 | if t0>=0 then 84 | t0=t0*t0 85 | gi=indexes[ii+perms[jj]] 86 | local grad=self._gradients[gi] 87 | n0=t0*t0*(grad[1]*x0+grad[2]*y0) 88 | end 89 | 90 | local t1=.5- x1*x1 - y1*y1 91 | if t1>=0 then 92 | t1=t1*t1 93 | gi=indexes[ii+i1+perms[jj+j1]] 94 | local grad=self._gradients[gi] 95 | n1=t1*t1*(grad[1]*x1+grad[2]*y1) 96 | end 97 | 98 | local t2=.5- x2*x2 - y2*y2 99 | if t2>=0 then 100 | t2=t2*t2 101 | gi=indexes[ii+1+perms[jj+1]] 102 | local grad=self._gradients[gi] 103 | n2=t2*t2*(grad[1]*x2+grad[2]*y2) 104 | end 105 | return 70*(n0+n1+n2) 106 | end 107 | 108 | return Simplex 109 | -------------------------------------------------------------------------------- /src/rot/path.lua: -------------------------------------------------------------------------------- 1 | local ROT = require((...):gsub(('.[^./\\]*'):rep(1) .. '$', '')) 2 | local Path = ROT.Class:extend("Path") 3 | 4 | function Path:init(toX, toY, passableCallback, options) 5 | self._toX =toX 6 | self._toY =toY 7 | self._fromX=nil 8 | self._fromY=nil 9 | self._passableCallback=passableCallback 10 | self._options= { topology=8 } 11 | 12 | if options then for k,_ in pairs(options) do self._options[k]=options[k] end end 13 | 14 | self._dirs= self._options.topology==8 and ROT.DIRS.EIGHT or ROT.DIRS.FOUR 15 | if self._options.topology==8 then 16 | self._dirs ={self._dirs[1], 17 | self._dirs[3], 18 | self._dirs[5], 19 | self._dirs[7], 20 | self._dirs[2], 21 | self._dirs[4], 22 | self._dirs[6], 23 | self._dirs[8] } 24 | end 25 | end 26 | 27 | function Path:compute() end 28 | 29 | function Path:_getNeighbors(cx, cy) 30 | local result={} 31 | for i=1,#self._dirs do 32 | local dir=self._dirs[i] 33 | local x=cx+dir[1] 34 | local y=cy+dir[2] 35 | if self._passableCallback(x, y) then 36 | table.insert(result, {x, y}) 37 | end 38 | end 39 | return result 40 | end 41 | 42 | return Path 43 | -------------------------------------------------------------------------------- /src/rot/path/astar.lua: -------------------------------------------------------------------------------- 1 | --- A* Pathfinding. 2 | -- Simplified A* algorithm: all edges have a value of 1 3 | -- @module ROT.Path.AStar 4 | local ROT = require((...):gsub(('.[^./\\]*'):rep(2) .. '$', '')) 5 | local AStar=ROT.Path:extend("AStar") 6 | --- Constructor. 7 | -- @tparam int toX x-position of destination cell 8 | -- @tparam int toY y-position of destination cell 9 | -- @tparam function passableCallback Function with two parameters (x, y) that returns true if the cell at x,y is able to be crossed 10 | -- @tparam table options Options 11 | -- @tparam[opt=8] int options.topology Directions for movement Accepted values (4 or 8) 12 | function AStar:init(toX, toY, passableCallback, options) 13 | AStar.super.init(self, toX, toY, passableCallback, options) 14 | self._todo={} 15 | self._done={} 16 | self._fromX=nil 17 | self._fromY=nil 18 | end 19 | 20 | --- Compute the path from a starting point 21 | -- @tparam int fromX x-position of starting point 22 | -- @tparam int fromY y-position of starting point 23 | -- @tparam function callback Will be called for every path item with arguments "x" and "y" 24 | function AStar:compute(fromX, fromY, callback) 25 | self._todo={} 26 | self._done={} 27 | self._fromX=tonumber(fromX) 28 | self._fromY=tonumber(fromY) 29 | self._done[self._toX]={} 30 | self:_add(self._toX, self._toY, nil) 31 | 32 | while #self._todo>0 do 33 | local item=table.remove(self._todo, 1) 34 | if item.x == fromX and item.y == fromY then break end 35 | local neighbors=self:_getNeighbors(item.x, item.y) 36 | 37 | for i=1,#neighbors do 38 | local x = neighbors[i][1] 39 | local y = neighbors[i][2] 40 | if not self._done[x] then self._done[x]={} end 41 | if not self._done[x][y] then 42 | self:_add(x, y, item) 43 | end 44 | end 45 | end 46 | 47 | local item=self._done[self._fromX] and self._done[self._fromX][self._fromY] or nil 48 | if not item then return end 49 | 50 | while item do 51 | callback(tonumber(item.x), tonumber(item.y)) 52 | item=item.prev 53 | end 54 | end 55 | 56 | function AStar:_add(x, y, prev) 57 | local h = self:_distance(x, y) 58 | local obj={} 59 | obj.x =x 60 | obj.y =y 61 | obj.prev=prev 62 | obj.g =prev and prev.g+1 or 0 63 | obj.h =h 64 | self._done[x][y]=obj 65 | 66 | local f=obj.g+obj.h 67 | 68 | for i=1,#self._todo do 69 | local item=self._todo[i] 70 | local itemF = item.g + item.h; 71 | if f < itemF or (f == itemF and h < item.h) then 72 | table.insert(self._todo, i, obj) 73 | return 74 | end 75 | end 76 | 77 | table.insert(self._todo, obj) 78 | end 79 | 80 | function AStar:_distance(x, y) 81 | if self._options.topology==4 then 82 | return math.abs(x-self._fromX)+math.abs(y-self._fromY) 83 | elseif self._options.topology==8 then 84 | return math.max(math.abs(x-self._fromX), math.abs(y-self._fromY)) 85 | end 86 | end 87 | 88 | return AStar 89 | -------------------------------------------------------------------------------- /src/rot/path/dijkstra.lua: -------------------------------------------------------------------------------- 1 | --- Dijkstra Pathfinding. 2 | -- Simplified Dijkstra's algorithm: all edges have a value of 1 3 | -- @module ROT.Path.Dijkstra 4 | local ROT = require((...):gsub(('.[^./\\]*'):rep(2) .. '$', '')) 5 | local Dijkstra=ROT.Path:extend("Dijkstra") 6 | 7 | local Grid = ROT.Type.Grid 8 | 9 | --- Constructor. 10 | -- @tparam int toX x-position of destination cell 11 | -- @tparam int toY y-position of destination cell 12 | -- @tparam function passableCallback Function with two parameters (x, y) that returns true if the cell at x,y is able to be crossed 13 | -- @tparam table options Options 14 | -- @tparam[opt=8] int options.topology Directions for movement Accepted values (4 or 8) 15 | function Dijkstra:init(toX, toY, passableCallback, options) 16 | toX, toY = tonumber(toX), tonumber(toY) 17 | Dijkstra.super.init(self, toX, toY, passableCallback, options) 18 | 19 | self._computed=Grid() 20 | self._todo ={} 21 | 22 | self:_add(toX, toY) 23 | end 24 | 25 | --- Compute the path from a starting point 26 | -- @tparam int fromX x-position of starting point 27 | -- @tparam int fromY y-position of starting point 28 | -- @tparam function callback Will be called for every path item with arguments "x" and "y" 29 | function Dijkstra:compute(fromX, fromY, callback) 30 | fromX, fromY = tonumber(fromX), tonumber(fromY) 31 | 32 | local item = self._computed:getCell(fromX, fromY) 33 | or self:_compute(fromX, fromY) 34 | 35 | while item do 36 | callback(item.x, item.y) 37 | item=item.prev 38 | end 39 | end 40 | 41 | function Dijkstra:_compute(fromX, fromY) 42 | while #self._todo>0 do 43 | local item=table.remove(self._todo, 1) 44 | if item.x == fromX and item.y == fromY then return item end 45 | 46 | local neighbors=self:_getNeighbors(item.x, item.y) 47 | 48 | for i=1,#neighbors do 49 | local x=neighbors[i][1] 50 | local y=neighbors[i][2] 51 | if not self._computed:getCell(x, y) then 52 | self:_add(x, y, item) 53 | end 54 | end 55 | end 56 | end 57 | 58 | function Dijkstra:_add(x, y, prev) 59 | local obj = { x = x, y = y, prev = prev } 60 | 61 | self._computed:setCell(x, y, obj) 62 | table.insert(self._todo, obj) 63 | end 64 | 65 | return Dijkstra 66 | 67 | -------------------------------------------------------------------------------- /src/rot/path/dijkstraMap.lua: -------------------------------------------------------------------------------- 1 | --- DijkstraMap Pathfinding. 2 | -- Based on the DijkstraMap Article on RogueBasin, http://roguebasin.roguelikedevelopment.org/index.php?title=The_Incredible_Power_of_Dijkstra_Maps 3 | -- @module ROT.DijkstraMap 4 | local ROT = require((...):gsub(('.[^./\\]*'):rep(2) .. '$', '')) 5 | local DijkstraMap = ROT.Path:extend("DijkstraMap") 6 | 7 | local PointSet = ROT.Type.PointSet 8 | local Grid = ROT.Type.Grid 9 | 10 | --- Constructor. 11 | -- @tparam int goalX x-position of cell that map will 'roll down' to 12 | -- @tparam int goalY y-position of cell that map will 'roll down' to 13 | -- @tparam function passableCallback a function with two parameters (x, y) that returns true if a map cell is passable 14 | -- @tparam table options Options 15 | -- @tparam[opt=8] int options.topology Directions for movement Accepted values (4 or 8) 16 | function DijkstraMap:init(goalX, goalY, passableCallback, options) 17 | DijkstraMap.super.init(self, goalX, goalY, passableCallback, options) 18 | self._map = Grid() 19 | self._goals = PointSet() 20 | self._dirty = true 21 | if goalX and goalY then 22 | self:addGoal(goalX, goalY) 23 | end 24 | end 25 | 26 | --- Establish values for all cells in map. 27 | -- call after ROT.DijkstraMap:new(goalX, goalY, passableCallback) 28 | function DijkstraMap:compute(x, y, callback, topology) 29 | self:_rebuild() 30 | local dx, dy = self:dirTowardsGoal(x, y, topology) 31 | if dx then 32 | callback(x, y) 33 | end 34 | while dx do 35 | x, y = x + dx, y + dy 36 | callback(x, y) 37 | dx, dy = self:dirTowardsGoal(x, y, topology) 38 | end 39 | end 40 | 41 | --- Run a callback function on every cell in the map 42 | -- @tparam function callback A function with x and y parameters that will be run on every cell in the map 43 | function DijkstraMap:create(callback) 44 | self:_rebuild() 45 | for _, x, y, v in self._map:each() do 46 | callback(x, y, v) 47 | end 48 | end 49 | 50 | --- Check if a goal exists at a position. 51 | -- @tparam int x the x-value to check 52 | -- @tparam int y the y-value to check 53 | function DijkstraMap:hasGoal(x, y) 54 | return not not self._goals:find(x, y) 55 | end 56 | 57 | --- Add new goal. 58 | -- @tparam int x the x-value of the new goal cell 59 | -- @tparam int y the y-value of the new goal cell 60 | function DijkstraMap:addGoal(x, y) 61 | if self._goals:push(x, y) then 62 | self._dirty = true 63 | end 64 | return self 65 | end 66 | 67 | --- Remove a goal. 68 | -- @tparam int gx the x-value of the goal cell 69 | -- @tparam int gy the y-value of the goal cell 70 | function DijkstraMap:removeGoal(x, y) 71 | if self._goals:prune(x, y) then 72 | self._dirty = true 73 | end 74 | return self 75 | end 76 | 77 | --- Remove all goals. 78 | function DijkstraMap:clearGoals() 79 | self._goals = PointSet() 80 | self._dirty = true 81 | return self 82 | end 83 | 84 | --- Get the direction of the goal from a given position 85 | -- @tparam int x x-value of current position 86 | -- @tparam int y y-value of current position 87 | -- @treturn int xDir X-Direction towards goal. Either -1, 0, or 1 88 | -- @treturn int yDir Y-Direction towards goal. Either -1, 0, or 1 89 | function DijkstraMap:dirTowardsGoal(x, y, topology) 90 | local low = self._map:getCell(x, y) 91 | if not low or low == 0 or low == math.huge then return end 92 | local dir=nil 93 | for i = 1, topology or self._options.topology do 94 | local v = ROT.DIRS.FOUR[i] or ROT.DIRS.EIGHT[(i - 4) * 2] 95 | local tx=(x+v[1]) 96 | local ty=(y+v[2]) 97 | local val = self._map:getCell(tx, ty) 98 | if val and i < 5 and val <= low or val < low then 99 | low=val 100 | dir=v 101 | end 102 | end 103 | if dir then return dir[1],dir[2] end 104 | end 105 | 106 | --- Output map values to console. 107 | -- For debugging, will send a comma separated output of cell values to the console. 108 | -- @tparam boolean[opt=false] returnString Will return the output in addition to sending it to console if true. 109 | function DijkstraMap:debug(returnString) 110 | self:_rebuild() 111 | local ls 112 | 113 | if returnString then ls='' end 114 | for y=1,self._dimensions.h do 115 | local s='' 116 | for x=1,self._dimensions.w do 117 | s=s..self._map:getCell(x, y)..',' 118 | end 119 | io.write(s); io.flush() 120 | if returnString then ls=ls..s..'\n' end 121 | end 122 | if returnString then return ls end 123 | end 124 | 125 | function DijkstraMap:_addCell(x, y, value) 126 | self._nextCells:push(x, y) 127 | self._map:setCell(x, y, value) 128 | return value 129 | end 130 | 131 | function DijkstraMap:_visitAdjacent(x, y) 132 | if not self._passableCallback(x, y) then return end 133 | 134 | local low = math.huge 135 | 136 | for i = 1, #self._dirs do 137 | local tx = x + self._dirs[i][1] 138 | local ty = y + self._dirs[i][2] 139 | local value = self._map:getCell(tx, ty) 140 | or self:_addCell(tx, ty, math.huge) 141 | 142 | low = math.min(low, value) 143 | end 144 | 145 | if self._map:getCell(x, y) > low + 2 then 146 | self._map:setCell(x, y, low + 1) 147 | end 148 | end 149 | 150 | function DijkstraMap:_rebuild(callback) 151 | if not self._dirty then return end 152 | self._dirty = false 153 | 154 | self._nextCells = PointSet() 155 | self._map = Grid() 156 | 157 | for _, x, y in self._goals:each() do 158 | self:_addCell(x, y, 0) 159 | end 160 | 161 | while #self._nextCells > 0 do 162 | for i in self._nextCells:each() do 163 | self:_visitAdjacent(self._nextCells:pluck(i)) 164 | end 165 | end 166 | end 167 | 168 | return DijkstraMap 169 | 170 | -------------------------------------------------------------------------------- /src/rot/rng.lua: -------------------------------------------------------------------------------- 1 | --- The RNG Class. 2 | -- A Lua port of Johannes Baagøe's Alea 3 | -- From http://baagoe.com/en/RandomMusings/javascript/ 4 | -- Johannes Baagøe , 2010 5 | -- Mirrored at: 6 | -- https://github.com/nquinlan/better-random-numbers-for-javascript-mirror 7 | -- @module ROT.RNG 8 | local ROT = require((...):gsub(('.[^./\\]*'):rep(1) .. '$', '')) 9 | local RNG = ROT.Class:extend("RNG") 10 | 11 | 12 | local function Mash () 13 | local n = 0xefc8249d 14 | 15 | local function mash (data) 16 | data = tostring(data) 17 | 18 | for i = 1, data:len() do 19 | n = n + data:byte(i) 20 | local h = 0.02519603282416938 * n 21 | n = math.floor(h) 22 | h = h - n 23 | h = h * n 24 | n = math.floor(h) 25 | h = h - n 26 | n = n + h * 0x100000000 -- 2^32 27 | end 28 | return math.floor(n) * 2.3283064365386963e-10 -- 2^-32 29 | end 30 | 31 | return mash 32 | end 33 | 34 | function RNG:init(seed) 35 | self.s0 = 0 36 | self.s1 = 0 37 | self.s2 = 0 38 | self.c = 1 39 | self:setSeed(seed) 40 | end 41 | 42 | --- Seed. 43 | -- seed the rng 44 | -- @tparam[opt=os.clock()] number s A number to base the rng from 45 | function RNG:getSeed() 46 | return self.seed 47 | end 48 | 49 | --- Seed. 50 | -- seed the rng 51 | -- @tparam[opt=os.clock()] number s A number to base the rng from 52 | function RNG:setSeed(seed) 53 | self.c = 1 54 | self.seed = seed or os.time() 55 | 56 | local mash = Mash() 57 | self.s0 = mash(' ') 58 | self.s1 = mash(' ') 59 | self.s2 = mash(' ') 60 | 61 | self.s0 = self.s0 - mash(self.seed) 62 | if self.s0 < 0 then 63 | self.s0 = self.s0 + 1 64 | end 65 | self.s1 = self.s1 - mash(self.seed) 66 | if self.s1 < 0 then 67 | self.s1 = self.s1 + 1 68 | end 69 | self.s2 = self.s2 - mash(self.seed) 70 | if self.s2 < 0 then 71 | self.s2 = self.s2 + 1 72 | end 73 | mash = nil 74 | end 75 | 76 | function RNG:getUniform() 77 | -- return self.implementation() 78 | local t = 2091639 * self.s0 + self.c * 2.3283064365386963e-10 -- 2^-32 79 | self.s0 = self.s1 80 | self.s1 = self.s2 81 | self.c = math.floor(t) 82 | self.s2 = t - self.c 83 | return self.s2 84 | end 85 | 86 | function RNG:getUniformInt(lowerBound, upperBound) 87 | local max = math.max(lowerBound, upperBound) 88 | local min = math.min(lowerBound, upperBound) 89 | return math.floor(self:getUniform() * (max - min + 1)) + min 90 | end 91 | 92 | function RNG:getNormal(mean, stddev) 93 | repeat 94 | local u = 2*self:getUniform()-1 95 | local v = 2*self:getUniform()-1 96 | local r = u*u + v*v 97 | until r > 1 or r == 0 98 | 99 | local gauss = u * math.sqrt(-2*math.log(r)/r) 100 | return (mean or 0) + gauss*(stddev or 1) 101 | end 102 | 103 | function RNG:getPercentage() 104 | return 1 + math.floor(self:getUniform()*100) 105 | end 106 | 107 | function RNG:getWeightedValue(tbl) 108 | local total=0 109 | for _,v in pairs(tbl) do 110 | total=total+v 111 | end 112 | local rand=self:getUniform()*total 113 | local part=0 114 | for k,v in pairs(tbl) do 115 | part=part+v 116 | if rand self._options.order then 139 | while #ctx > self._options.order do table.remove(ctx, 1) end 140 | elseif #ctx < self._options.order then 141 | while #ctx < self._options.order do table.insert(ctx,1,self._boundary) end 142 | end 143 | while not self._data[self:_join(ctx)] and #ctx>0 do 144 | ctx=table.slice(ctx, 2) 145 | end 146 | 147 | return ctx 148 | end 149 | 150 | function StringGenerator:_pickRandom(data) 151 | local total =0 152 | for k,_ in pairs(data) do 153 | total=total+data[k] 154 | end 155 | local rand=self._rng:random()*total 156 | local i=0 157 | for k,_ in pairs(data) do 158 | i=i+data[k] 159 | if (rand 0 then 63 | result[#result + 1] = { 64 | type = Text.TYPE_TEXT, 65 | value = part 66 | } 67 | end 68 | 69 | return (Text._breakLines(result, maxWidth)) 70 | end 71 | 72 | -- insert line breaks into first-pass tokenized data 73 | function Text._breakLines(tokens, maxWidth) 74 | maxWidth = maxWidth or math.huge 75 | 76 | local i = 1 77 | local lineLength = 0 78 | local lastTokenWithSpace 79 | 80 | -- This contraption makes `break` work like `continue`. 81 | -- A `break` in the `repeat` loop will continue the outer loop. 82 | while i <= #tokens do repeat 83 | -- take all text tokens, remove space, apply linebreaks 84 | local token = tokens[i] 85 | if token.type == Text.TYPE_NEWLINE then -- reset 86 | lineLength = 0 87 | lastTokenWithSpace = nil 88 | end 89 | if token.type ~= Text.TYPE_TEXT then -- skip non-text tokens 90 | i = i + 1 91 | break -- continue 92 | end 93 | 94 | -- remove spaces at the beginning of line 95 | if lineLength == 0 then 96 | token.value = token.value:gsub("^ +", "") 97 | end 98 | 99 | -- forced newline? insert two new tokens after this one 100 | local index = token.value:find("\n") 101 | if index then 102 | token.value = Text._breakInsideToken(tokens, i, index, true) 103 | 104 | -- if there are spaces at the end, we must remove them 105 | -- (we do not want the line too long) 106 | token.value = token.value:gsub(" +$", "") 107 | end 108 | 109 | -- token degenerated? 110 | if token.value == "" then 111 | table.remove(tokens, i) 112 | break -- continue 113 | end 114 | 115 | if lineLength + #token.value > maxWidth then 116 | -- line too long, find a suitable breaking spot 117 | 118 | -- is it possible to break within this token? 119 | local index = 0 120 | while 1 do 121 | local nextIndex = token.value:find(" ", index+1) 122 | if not nextIndex then break end 123 | if lineLength + nextIndex > maxWidth then break end 124 | index = nextIndex 125 | end 126 | 127 | if index > 0 then -- break at space within this one 128 | token.value = Text._breakInsideToken(tokens, i, index, true) 129 | elseif lastTokenWithSpace then 130 | -- is there a previous token where a break can occur? 131 | local token = tokens[lastTokenWithSpace] 132 | local breakIndex = token.value:find(" [^ ]-$") 133 | token.value = Text._breakInsideToken( 134 | tokens, lastTokenWithSpace, breakIndex, true) 135 | i = lastTokenWithSpace 136 | else -- force break in this token 137 | token.value = Text._breakInsideToken( 138 | tokens, i, maxWidth-lineLength+1, false) 139 | end 140 | 141 | else -- line not long, continue 142 | lineLength = lineLength + #token.value 143 | if token.value:find(" ") then lastTokenWithSpace = i end 144 | end 145 | 146 | i = i + 1 -- advance to next token 147 | until true end 148 | -- end of "continue contraption" 149 | 150 | -- insert fake newline to fix the last text line 151 | tokens[#tokens + 1] = { type = Text.TYPE_NEWLINE } 152 | 153 | -- remove trailing space from text tokens before newlines 154 | local lastTextToken 155 | for i = 1, #tokens do 156 | local token = tokens[i] 157 | if token.type == Text.TYPE_TEXT then 158 | lastTextToken = token 159 | elseif token.type == Text.TYPE_NEWLINE then 160 | if lastTextToken then -- remove trailing space 161 | lastTextToken.value = lastTextToken.value:gsub(" +$", "") 162 | end 163 | lastTextToken = nil 164 | end 165 | end 166 | 167 | tokens[#tokens] = nil -- remove fake token 168 | 169 | return tokens 170 | end 171 | 172 | --- Create new tokens and insert them into the stream 173 | -- @tparam table tokens 174 | -- @tparam number tokenIndex Token being processed 175 | -- @tparam number breakIndex Index within current token's value 176 | -- @tparam boolean removeBreakChar Do we want to remove the breaking character? 177 | -- @treturn string remaining unbroken token value 178 | function Text._breakInsideToken(tokens, tokenIndex, breakIndex, removeBreakChar) 179 | local newBreakToken = { 180 | type = Text.TYPE_NEWLINE, 181 | } 182 | local newTextToken = { 183 | type = Text.TYPE_TEXT, 184 | value = tokens[tokenIndex].value:sub( 185 | breakIndex + (removeBreakChar and 1 or 0)) 186 | } 187 | 188 | table.insert(tokens, tokenIndex + 1, newTextToken) 189 | table.insert(tokens, tokenIndex + 1, newBreakToken) 190 | 191 | return tokens[tokenIndex].value:sub(1, breakIndex - 1) 192 | end 193 | 194 | return Text 195 | 196 | -------------------------------------------------------------------------------- /src/rot/type/grid.lua: -------------------------------------------------------------------------------- 1 | --- Grid. 2 | -- @module ROT.Type.Grid 3 | local ROT = require((...):gsub(('.[^./\\]*'):rep(2) .. '$', '')) 4 | local Grid = ROT.Class:extend("Grid") 5 | 6 | -- Grid class 7 | 8 | function Grid:init() 9 | self:clear() 10 | end 11 | 12 | function Grid:clear() 13 | self._points = ROT.Type.PointSet() 14 | self._values = {} 15 | end 16 | 17 | function Grid:removeCell(x, y) 18 | local i = self._points:find(x, y) 19 | if not i then return end 20 | local n = #self._points - 1 21 | local oldValue = self._values[i] 22 | self._points:pluck(i) 23 | self._values[i] = self._values[n] 24 | self._values[n] = nil 25 | return oldValue 26 | end 27 | 28 | function Grid:setCell(x, y, value) 29 | if value == nil then return self:removeCell(x, y) end 30 | local i, j = self._points:push(x, y) 31 | local oldValue = j and self._values[j] 32 | self._values[i or j] = value 33 | return oldValue 34 | end 35 | 36 | function Grid:getCell(x, y) 37 | local i = self._points:find(x, y) 38 | return i and self._values[i] 39 | end 40 | 41 | local function iterate(self, i) 42 | i = i - 2 43 | if i > 0 then 44 | local x, y = self._points:peek(i) 45 | return i, x, y, self._values[i] 46 | end 47 | end 48 | 49 | function Grid:each() 50 | return iterate, self, #self._points + 1 51 | end 52 | 53 | function Grid:getRandom() 54 | local x, y = self._points:getRandom() 55 | return x, y, self:getCell(x, y) 56 | end 57 | 58 | return Grid 59 | 60 | -------------------------------------------------------------------------------- /src/rot/type/pointSet.lua: -------------------------------------------------------------------------------- 1 | --- Pair set. 2 | -- An unordered collection of unique value-pairs. 3 | -- @module ROT.Type.PointSet 4 | local ROT = require((...):gsub(('.[^./\\]*'):rep(2) .. '$', '')) 5 | local PointSet = ROT.Class:extend("PointSet") 6 | 7 | function PointSet:init() 8 | self._index = {} 9 | end 10 | 11 | local function hash(x, y) 12 | return x and y * 0x4000000 + x or false -- 26-bit x and y 13 | end 14 | 15 | function PointSet:find(x, y) 16 | return self._index[hash(x, y)] 17 | end 18 | 19 | function PointSet:peek(i) 20 | return self[i], self[i + 1] 21 | end 22 | 23 | function PointSet:poke(i, x, y) 24 | self._index[hash(self:peek(i))] = nil 25 | self._index[hash(x, y)] = i 26 | self._index[false] = nil 27 | self[i], self[i + 1] = x, y 28 | return self 29 | end 30 | 31 | function PointSet:push(x, y) 32 | local key = hash(x, y) 33 | local i = self._index[key] 34 | if i then return nil, i end 35 | i = #self + 1 36 | self:poke(i, x, y) 37 | self._index[key] = i 38 | self._index[false] = nil 39 | return i 40 | end 41 | 42 | function PointSet:pluck(i) 43 | local last, x, y = #self - 1, self:peek(i) 44 | self:poke(i, self:peek(last)):poke(last) 45 | self._index[hash(x, y)] = nil 46 | self._index[hash(self:peek(i))] = i 47 | self._index[false] = nil 48 | return x, y 49 | end 50 | 51 | function PointSet:prune(x, y) 52 | local i = self:find(x, y) 53 | return i and self:pluck(i) 54 | end 55 | 56 | local function iterate(self, i) 57 | i = i - 2 58 | if i > 0 then 59 | return i, self:peek(i) 60 | end 61 | end 62 | 63 | function PointSet:each() 64 | return iterate, self, #self + 1 65 | end 66 | 67 | function PointSet:getRandom() 68 | local i = self._rng:random(1, #self / 2) * 2 - 1 69 | return self:peek(i) 70 | end 71 | 72 | return PointSet 73 | 74 | -------------------------------------------------------------------------------- /tests/expect.lua: -------------------------------------------------------------------------------- 1 | -- Some of Jasmine's API mapped onto Busted. Makes tests easier to port. 2 | 3 | 4 | local function gt (_, args) return args[1] > args[2] end 5 | local function lt (_, args) return args[1] < args[2] end 6 | local function undef (_, args) return args[1] == nil end 7 | 8 | return function (assert) 9 | assert:register("assertion", "gt", gt, 10 | "assertion.gt.positive", "assertion.gt.negative") 11 | assert:register("assertion", "lt", lt, 12 | "assertion.lt.positive", "assertion.lt.negative") 13 | assert:register("assertion", "undef", undef, 14 | "assertion.undef.positive", "assertion.undef.negative") 15 | return function (a) 16 | return { 17 | toEqual = function (b) return assert.is.same(b, a) end, 18 | toBe = function (b) return assert.is.equal(b, a) end, 19 | toBeGreaterThan = function (b) return assert.gt(a, b) end, 20 | toBeLessThan = function (b) return assert.lt(a, b) end, 21 | toBeUndefined = function () return assert.undef(a) end, 22 | NOT = { 23 | toEqual = function (b) return assert.Not.same(b, a) end, 24 | toBe = function (b) return assert.Not.equal(b, a) end, 25 | toBeGreaterThan = function (b) return assert.Not.gt(a, b) end, 26 | toBeLessThan = function (b) return assert.Not.lt(a, b) end, 27 | toBeUndefined = function () return assert.Not.undef(a) end, 28 | } 29 | } 30 | end 31 | end 32 | 33 | -------------------------------------------------------------------------------- /tests/helper.lua: -------------------------------------------------------------------------------- 1 | local say = require 'say' 2 | 3 | say:set("assertion.gt.positive", "Expected %s to be greater than %s") 4 | say:set("assertion.gt.negative", "Expected %s not to be greater than %s") 5 | say:set("assertion.lt.positive", "Expected %s to be less than %s") 6 | say:set("assertion.lt.negative", "Expected %s not to be less than %s") 7 | say:set("assertion.undef.positive", "Expected %s to be nil") 8 | say:set("assertion.undef.negative", "Expected %s not to be nil") 9 | 10 | -------------------------------------------------------------------------------- /tests/readme: -------------------------------------------------------------------------------- 1 | Run tests with Busted from the project root like this: 2 | 3 | busted --helper tests/helper.lua tests/spec/*.lua 4 | 5 | Busted is available in LuaRocks: 6 | 7 | luarocks install busted 8 | 9 | -------------------------------------------------------------------------------- /tests/spec/color.lua: -------------------------------------------------------------------------------- 1 | local ROT = require 'src.rot' 2 | local expect = require 'tests.expect' (assert) 3 | 4 | describe("Color", function() 5 | describe("add", function() 6 | it("should add two colors", function() 7 | expect(ROT.Color.add({1,2,3}, {3,4,5})).toEqual({4,6,8}) 8 | end) 9 | it("should add three colors", function() 10 | expect(ROT.Color.add({1,2,3}, {3,4,5}, {100,200,300})).toEqual({104,206,308}) 11 | end) 12 | it("should add one color (noop)", function() 13 | expect(ROT.Color.add({1,2,3})).toEqual({1,2,3}) 14 | end) 15 | 16 | it("should not modify first argument values", function() 17 | local c1 = {1,2,3} 18 | local c2 = {3,4,5} 19 | ROT.Color.add(c1, c2) 20 | expect(c1).toEqual({1,2,3}) 21 | end) 22 | end) 23 | 24 | describe("add_", function() 25 | it("should add two colors", function() 26 | expect(ROT.Color.add_({1,2,3}, {3,4,5})).toEqual({4,6,8}) 27 | end) 28 | it("should add three colors", function() 29 | expect(ROT.Color.add_({1,2,3}, {3,4,5}, {100,200,300})).toEqual({104,206,308}) 30 | end) 31 | it("should add one color (noop)", function() 32 | expect(ROT.Color.add_({1,2,3})).toEqual({1,2,3}) 33 | end) 34 | 35 | it("should modify first argument values", function() 36 | local c1 = {1,2,3} 37 | local c2 = {3,4,5} 38 | ROT.Color.add_(c1, c2) 39 | expect(c1).toEqual({4,6,8}) 40 | end) 41 | it("should return first argument", function() 42 | local c1 = {1,2,3} 43 | local c2 = {3,4,5} 44 | local c3 = ROT.Color.add_(c1, c2) 45 | expect(c1).toBe(c3) 46 | end) 47 | end) 48 | 49 | describe("multiply", function() 50 | it("should multiply two colors", function() 51 | expect(ROT.Color.multiply({100,200,300}, {51,51,51})).toEqual({20,40,60}) 52 | end) 53 | it("should multiply three colors", function() 54 | expect(ROT.Color.multiply({100,200,300}, {51,51,51}, {510,510,510})).toEqual({40,80,120}) 55 | end) 56 | it("should multiply one color (noop)", function() 57 | expect(ROT.Color.multiply({1,2,3})).toEqual({1,2,3}) 58 | end) 59 | it("should not modify first argument values", function() 60 | local c1 = {1,2,3} 61 | local c2 = {3,4,5} 62 | ROT.Color.multiply(c1, c2) 63 | expect(c1).toEqual({1,2,3}) 64 | end) 65 | it("should round values", function() 66 | expect(ROT.Color.multiply({100,200,300}, {10, 10, 10})).toEqual({4,8,12}) 67 | end) 68 | end) 69 | 70 | describe("multiply_", function() 71 | it("should multiply two colors", function() 72 | expect(ROT.Color.multiply_({100,200,300}, {51,51,51})).toEqual({20,40,60}) 73 | end) 74 | it("should multiply three colors", function() 75 | expect(ROT.Color.multiply_({100,200,300}, {51,51,51}, {510,510,510})).toEqual({40,80,120}) 76 | end) 77 | it("should multiply one color (noop)", function() 78 | expect(ROT.Color.multiply_({1,2,3})).toEqual({1,2,3}) 79 | end) 80 | it("should modify first argument values", function() 81 | local c1 = {100,200,300} 82 | local c2 = {51,51,51} 83 | ROT.Color.multiply_(c1, c2) 84 | expect(c1).toEqual({20,40,60}) 85 | end) 86 | it("should round values", function() 87 | expect(ROT.Color.multiply_({100,200,300}, {10, 10, 10})).toEqual({4,8,12}) 88 | end) 89 | it("should return first argument", function() 90 | local c1 = {1,2,3} 91 | local c2 = {3,4,5} 92 | local c3 = ROT.Color.multiply_(c1, c2) 93 | expect(c1).toBe(c3) 94 | end) 95 | end) 96 | 97 | describe("fromString", function() 98 | it("should handle rgb() colors", function() 99 | expect(ROT.Color.fromString("rgb(10, 20, 33)")).toEqual({10, 20, 33}) 100 | end) 101 | it("should handle #abcdef colors", function() 102 | expect(ROT.Color.fromString("#1a2f3c")).toEqual({26, 47, 60}) 103 | end) 104 | it("should handle #abc colors", function() 105 | expect(ROT.Color.fromString("#ca8")).toEqual({204, 170, 136}) 106 | end) 107 | it("should handle named colors", function() 108 | expect(ROT.Color.fromString("red")).toEqual({255, 0, 0}) 109 | end) 110 | it("should not handle nonexistant colors", function() 111 | expect(ROT.Color.fromString("lol")).toEqual({0, 0, 0}) 112 | end) 113 | end) 114 | 115 | describe("toRGB", function() 116 | it("should serialize to rgb", function() 117 | expect(ROT.Color.toRGB({10, 20, 30})).toEqual("rgb(10,20,30)") 118 | end) 119 | it("should clamp values to 0..255", function() 120 | expect(ROT.Color.toRGB({-100, 20, 2000})).toEqual("rgb(0,20,255)") 121 | end) 122 | end) 123 | 124 | describe("toHex", function() 125 | it("should serialize to hex", function() 126 | expect(ROT.Color.toHex({10, 20, 40})).toEqual("#0a1428") 127 | end) 128 | it("should clamp values to 0..255", function() 129 | expect(ROT.Color.toHex({-100, 20, 2000})).toEqual("#0014ff") 130 | end) 131 | end) 132 | 133 | describe("interpolate", function() 134 | it("should intepolate two colors", function() 135 | expect(ROT.Color.interpolate({10, 20, 40}, {100, 200, 300}, 0.1)).toEqual({19, 38, 66}) 136 | end) 137 | it("should round values", function() 138 | expect(ROT.Color.interpolate({10, 20, 40}, {15, 30, 53}, 0.5)).toEqual({13, 25, 47}) 139 | end) 140 | it("should default to 0.5 factor", function() 141 | expect(ROT.Color.interpolate({10, 20, 40}, {20, 30, 40})).toEqual({15, 25, 40}) 142 | end) 143 | end) 144 | 145 | describe("interpolateHSL", function() 146 | it("should intepolate two colors", function() 147 | expect(ROT.Color.interpolateHSL({10, 20, 40}, {100, 200, 300}, 0.1)).toEqual({12, 33, 73}) 148 | end) 149 | end) 150 | 151 | describe("randomize", function() 152 | it("should maintain constant diff when a number is used", function() 153 | local c = ROT.Color.randomize({100, 100, 100}, 100) 154 | expect(c[1]).toBe(c[2]) 155 | expect(c[2]).toBe(c[3]) 156 | end) 157 | end) 158 | 159 | describe("rgb2hsl and hsl2rgb", function() 160 | it("should correctly convert to HSL and back", function() 161 | local rgb = { 162 | {255, 255, 255}, 163 | {0, 0, 0}, 164 | {255, 0, 0}, 165 | {30, 30, 30}, 166 | {100, 120, 140} 167 | } 168 | 169 | while (rgb.length) do 170 | local color = rgb.pop() 171 | local hsl = ROT.Color.rgb2hsl(color) 172 | local rgb2 = ROT.Color.hsl2rgb(hsl) 173 | expect(rgb2).toEqual(color) 174 | end 175 | end) 176 | 177 | it("should round converted values", function() 178 | local hsl = {0.5, 0, 0.3} 179 | local rgb = ROT.Color.hsl2rgb(hsl) 180 | for i=1, #rgb do 181 | expect(math.floor(rgb[i] + 0.5)).toEqual(rgb[i]) 182 | end 183 | end) 184 | end) 185 | end) 186 | 187 | -------------------------------------------------------------------------------- /tests/spec/dungeon.lua: -------------------------------------------------------------------------------- 1 | local ROT = require 'src.rot' 2 | local expect = require 'tests.expect' (assert) 3 | 4 | describe("Map.Dungeon", function() 5 | local names = { "Digger", "Uniform", "Brogue" } 6 | 7 | local buildDungeonTests = function(name) 8 | local ctor = ROT.Map[name] 9 | ROT.RNG:setSeed(123456) 10 | local map = ctor() 11 | map:create() 12 | local rooms = map:getRooms() 13 | local corridors = map:getCorridors() 14 | 15 | describe(name, function() 16 | it("should generate >0 rooms", function() 17 | expect(#rooms).toBeGreaterThan(0) 18 | end) 19 | 20 | it("all rooms should have at least one door", function() 21 | for i = 1, #rooms do 22 | local room = rooms[i] 23 | local doorCount = 0 24 | room:create(function(x, y, value) 25 | if value == 2 then doorCount = doorCount + 1 end 26 | end) 27 | expect(doorCount).toBeGreaterThan(0) 28 | end 29 | end) 30 | 31 | it("all rooms should have at least one wall", function() 32 | for i = 1, #rooms do 33 | local room = rooms[i] 34 | local wallCount = 0 35 | room:create(function(x, y, value) 36 | if value == 1 then wallCount = wallCount + 1 end 37 | end) 38 | expect(wallCount).toBeGreaterThan(0) 39 | end 40 | end) 41 | 42 | it("all rooms should have at least one empty cell", function() 43 | for i = 1, #rooms do 44 | local room = rooms[i] 45 | local emptyCount = 0 46 | room:create(function(x, y, value) 47 | if value == 0 then emptyCount = emptyCount + 1 end 48 | end) 49 | expect(emptyCount).toBeGreaterThan(0) 50 | end 51 | end) 52 | 53 | it("should generate >0 corridors", function() 54 | expect(#corridors).toBeGreaterThan(0) 55 | end) 56 | 57 | it("all corridors should have at least one empty cell", function() 58 | for i = 1, #corridors do 59 | local corridor = corridors[i] 60 | local emptyCount = 0 61 | corridor:create(function(x, y, value) 62 | if value == 0 then emptyCount = emptyCount + 1 end 63 | end) 64 | expect(emptyCount).toBeGreaterThan(0) 65 | end 66 | end) 67 | end) 68 | 69 | end 70 | 71 | while (#names > 0) do 72 | local name = names[#names] 73 | names[#names] = nil 74 | buildDungeonTests(name) 75 | end 76 | end) 77 | 78 | -------------------------------------------------------------------------------- /tests/spec/engine.lua: -------------------------------------------------------------------------------- 1 | local ROT = require 'src.rot' 2 | local expect = require 'tests.expect' (assert) 3 | 4 | describe("Engine", function() 5 | local RESULT = 0 6 | local E = nil 7 | local S = nil 8 | local A50 = { 9 | getSpeed = function() return 50 end, 10 | act = function() RESULT = RESULT + 1 end, 11 | } 12 | local A100 = { 13 | getSpeed = function() return 100 end, 14 | act = function() E:lock() end, 15 | } 16 | local A70 = { 17 | getSpeed = function() return 70 end, 18 | act = function() RESULT = RESULT + 1; S:add(A100) end, 19 | } 20 | 21 | before_each(function() 22 | RESULT = 0 23 | S = ROT.Scheduler.Speed() 24 | E = ROT.Engine(S) 25 | end) 26 | 27 | it("should stop when locked", function() 28 | S:add(A50, true) 29 | S:add(A100, true) 30 | 31 | E:start() 32 | expect(RESULT).toEqual(0) 33 | end) 34 | 35 | it("should run until locked", function() 36 | S:add(A50, true) 37 | S:add(A70, true) 38 | E:start() 39 | expect(RESULT).toEqual(2) 40 | end) 41 | 42 | it("should run only when unlocked", function() 43 | S:add(A70, true) 44 | 45 | E:lock() 46 | E:start() 47 | expect(RESULT).toEqual(0) 48 | E:start() 49 | expect(RESULT).toEqual(1) 50 | end) 51 | end) 52 | 53 | -------------------------------------------------------------------------------- /tests/spec/eventQueue.lua: -------------------------------------------------------------------------------- 1 | local ROT = require 'src.rot' 2 | local expect = require 'tests.expect' (assert) 3 | 4 | describe("EventQueue", function() 5 | it("should return added event", function() 6 | local q = ROT.EventQueue() 7 | q:add("a", 100) 8 | expect(q:get()).toEqual("a") 9 | end) 10 | 11 | it("should return nil when no events are available", function() 12 | local q = ROT.EventQueue() 13 | expect(q:get()).toEqual(nil) 14 | end) 15 | 16 | it("should remove returned events", function() 17 | local q = ROT.EventQueue() 18 | q:add(0, 0) 19 | q:get() 20 | expect(q:get()).toEqual(nil) 21 | end) 22 | 23 | it("should look up time of events", function() 24 | local q = ROT.EventQueue() 25 | q:add(123, 187) 26 | q:add(456, 42) 27 | expect(q:getEventTime(123)).toEqual(187) 28 | expect(q:getEventTime(456)).toEqual(42) 29 | end) 30 | 31 | it("should look up correct times after events removed", function() 32 | local q = ROT.EventQueue() 33 | q:add(123, 187) 34 | q:add(456, 42) 35 | q:add(789, 411) 36 | q:get() 37 | expect(q:getEventTime(456)).toBeUndefined() 38 | expect(q:getEventTime(123)).toEqual(187 - 42) 39 | expect(q:getEventTime(789)).toEqual(411 - 42) 40 | end) 41 | 42 | it("should remove events", function() 43 | local q = ROT.EventQueue() 44 | q:add(123, 0) 45 | q:add(456, 0) 46 | local result = q:remove(123) 47 | expect(result).toEqual(true) 48 | expect(q:get()).toEqual(456) 49 | end) 50 | 51 | it("should survive removal of non-existant events", function() 52 | local q = ROT.EventQueue() 53 | q:add(0, 0) 54 | local result = q:remove(1) 55 | expect(result).toEqual(false) 56 | expect(q:get()).toEqual(0) 57 | end) 58 | 59 | it("should return events sorted", function() 60 | local q = ROT.EventQueue() 61 | q:add(456, 10) 62 | q:add(123, 5) 63 | q:add(789, 15) 64 | expect(q:get()).toEqual(123) 65 | expect(q:get()).toEqual(456) 66 | expect(q:get()).toEqual(789) 67 | end) 68 | 69 | it("should compute elapsed time", function() 70 | local q = ROT.EventQueue() 71 | q:add(456, 10) 72 | q:add(123, 5) 73 | q:add(789, 15) 74 | q:get() 75 | q:get() 76 | q:get() 77 | expect(q:getTime()).toEqual(15) 78 | end) 79 | 80 | it("should maintain event order for same timestamps", function() 81 | local q = ROT.EventQueue() 82 | q:add(456, 10) 83 | q:add(123, 10) 84 | q:add(789, 10) 85 | expect(q:get()).toEqual(456) 86 | expect(q:get()).toEqual(123) 87 | expect(q:get()).toEqual(789) 88 | expect(q:getTime()).toEqual(10) 89 | end) 90 | end) 91 | 92 | -------------------------------------------------------------------------------- /tests/spec/rng.lua: -------------------------------------------------------------------------------- 1 | local ROT = require 'src.rot' 2 | local expect = require 'tests.expect' (assert) 3 | 4 | describe("RNG", function() 5 | describe("getUniform", function() 6 | local value = ROT.RNG:getUniform() 7 | it("should return a number", function() 8 | expect(type(value)).toEqual("number") 9 | end) 10 | it("should return a number 0..1", function() 11 | expect(value).toBeGreaterThan(0) 12 | expect(value).toBeLessThan(1) 13 | end) 14 | end) 15 | 16 | describe("getUniformInt", function() 17 | local lowerBound = 5 18 | local upperBound = 10 19 | it("should return a number", function() 20 | local value = ROT.RNG:getUniformInt(lowerBound, upperBound) 21 | expect(type(value)).toEqual("number") 22 | end) 23 | it("should not care which number is larger in the arguments", function() 24 | local seed = math.floor(math.random()*1000000) 25 | ROT.RNG:setSeed(seed) 26 | local val1 = ROT.RNG:getUniformInt(lowerBound, upperBound) 27 | ROT.RNG:setSeed(seed) 28 | local val2 = ROT.RNG:getUniformInt(upperBound, lowerBound) 29 | expect(val1).toEqual(val2) 30 | end) 31 | it("should only return a number in the desired range", function() 32 | local value = ROT.RNG:getUniformInt(lowerBound, upperBound) 33 | local value2 = ROT.RNG:getUniformInt(upperBound, lowerBound) 34 | expect(value).NOT.toBeGreaterThan(upperBound) 35 | expect(value).NOT.toBeLessThan(lowerBound) 36 | expect(value2).NOT.toBeGreaterThan(upperBound) 37 | expect(value2).NOT.toBeLessThan(lowerBound) 38 | end) 39 | end) 40 | 41 | describe("seeding", function() 42 | it("should return a seed number", function() 43 | expect(type(ROT.RNG:getSeed())).toEqual("number") 44 | end) 45 | 46 | it("should return the same value for a given seed", function() 47 | local seed = math.floor(os.time()*1000000) 48 | ROT.RNG:setSeed(seed) 49 | local val1 = ROT.RNG:getUniform() 50 | ROT.RNG:setSeed(seed) 51 | local val2 = ROT.RNG:getUniform() 52 | expect(val1).toEqual(val2) 53 | end) 54 | 55 | it("should return a precomputed value for a given seed", function() 56 | ROT.RNG:setSeed(12345) 57 | local val = ROT.RNG:getUniform() 58 | -- expect(val).toEqual(0.01198604702949524) 59 | -- We expect the same value the original Alea/Mash would give 60 | expect(val).toEqual(0.27138191112317144871) 61 | 62 | end) 63 | end) 64 | 65 | describe("state manipulation", function() 66 | it("should return identical values after setting identical states", function() 67 | ROT.RNG:getUniform() 68 | 69 | local state = ROT.RNG:getState() 70 | local val1 = ROT.RNG:getUniform() 71 | ROT.RNG:setState(state) 72 | local val2 = ROT.RNG:getUniform() 73 | 74 | expect(val1).toEqual(val2) 75 | end) 76 | end) 77 | 78 | describe("cloning", function() 79 | it("should be able to clone a RNG", function() 80 | local clone = ROT.RNG:clone() 81 | expect(type(clone)).toEqual("table") 82 | end) 83 | 84 | it("should clone a working RNG", function() 85 | local clone = ROT.RNG:clone() 86 | local num = clone:getUniform() 87 | expect(type(num)).toEqual("number") 88 | end) 89 | 90 | it("should clone maintaining its state", function() 91 | local clone = ROT.RNG:clone() 92 | local num1 = ROT.RNG:getUniform() 93 | local num2 = clone:getUniform() 94 | expect(num1).toEqual(num2) 95 | end) 96 | end) 97 | 98 | end) 99 | 100 | -------------------------------------------------------------------------------- /tests/spec/scheduler.lua: -------------------------------------------------------------------------------- 1 | local ROT = require 'src.rot' 2 | local expect = require 'tests.expect' (assert) 3 | 4 | describe("Scheduler", function() 5 | 6 | describe("Simple", function() 7 | local S = ROT.Scheduler.Simple() 8 | local A1 = "A1" 9 | local A2 = "A2" 10 | local A3 = "A3" 11 | before_each(function() S:clear() end) 12 | 13 | it("should schedule actors evenly", function() 14 | S:add(A1, true) 15 | S:add(A2, true) 16 | S:add(A3, true) 17 | local result = {} 18 | for i = 1, 6 do table.insert(result, S:next()) end 19 | expect(result).toEqual({A1, A2, A3, A1, A2, A3}) 20 | end) 21 | 22 | it("should schedule one-time events", function() 23 | S:add(A1, false) 24 | S:add(A2, true) 25 | local result = {} 26 | for i = 1, 4 do table.insert(result, S:next()) end 27 | expect(result).toEqual({A1, A2, A2, A2}) 28 | end) 29 | 30 | it("should remove repeated events", function() 31 | S:add(A1, false) 32 | S:add(A2, true) 33 | S:add(A3, true) 34 | S:remove(A2) 35 | local result = {} 36 | for i = 1, 4 do table.insert(result, S:next()) end 37 | expect(result).toEqual({A1, A3, A3, A3}) 38 | end) 39 | 40 | it("should remove one-time events", function() 41 | S:add(A1, false) 42 | S:add(A2, false) 43 | S:add(A3, true) 44 | S:remove(A2) 45 | local result = {} 46 | for i = 1, 4 do table.insert(result, S:next()) end 47 | expect(result).toEqual({A1, A3, A3, A3}) 48 | end) 49 | 50 | end) 51 | 52 | describe("Speed", function() 53 | local S = ROT.Scheduler.Speed() 54 | local A = { getSpeed = function(self) return self.speed end } 55 | local A50 = setmetatable({}, { __index = A }) A50.speed = 50 56 | local A100a = setmetatable({}, { __index = A }) A100a.speed = 100 57 | local A100b = setmetatable({}, { __index = A }) A100b.speed = 100 58 | local A200 = setmetatable({}, { __index = A }) A200.speed = 200 59 | 60 | before_each(function() S:clear() end) 61 | 62 | it("should schedule same speed evenly", function() 63 | S:add(A100a, true) 64 | S:add(A100b, true) 65 | local result = {} 66 | for i = 1, 4 do table.insert(result, S:next()) end 67 | 68 | expect(result).toEqual({A100a, A100b, A100a, A100b}) 69 | end) 70 | 71 | it("should schedule different speeds properly", function() 72 | S:add(A50, true) 73 | S:add(A100a, true) 74 | S:add(A200, true) 75 | local result = {} 76 | for i = 1, 7 do table.insert(result, S:next()) end 77 | expect(result).toEqual({A200, A100a, A200, A200, A50, A100a, A200}) 78 | end) 79 | 80 | it("should schedule with initial offsets", function() 81 | S:add(A50, true, 1/300) 82 | S:add(A100a, true, 0) 83 | S:add(A200, true) 84 | local result = {} 85 | for i = 1, 9 do table.insert(result, S:next()) end 86 | expect(result).toEqual({A100a, A50, A200, A100a, A200, A200, A100a, A200, A50}) 87 | end) 88 | 89 | it("should look up the time of an event", function() 90 | S:add(A100a, true) 91 | S:add(A50, true, 1/200) 92 | expect(S:getTimeOf(A50)).toEqual(1/200) 93 | expect(S:getTimeOf(A100a)).toEqual(1/100) 94 | end) 95 | 96 | end) 97 | 98 | describe("Action", function() 99 | local S = null 100 | local A1 = "A1" 101 | local A2 = "A2" 102 | local A3 = "A3" 103 | before_each(function() S = ROT.Scheduler.Action() end) 104 | 105 | it("should schedule evenly by default", function() 106 | S:add(A1, true) 107 | S:add(A2, true) 108 | S:add(A3, true) 109 | local result = {} 110 | for i = 1, 6 do table.insert(result, S:next()) end 111 | expect(result).toEqual({A1, A2, A3, A1, A2, A3}) 112 | end) 113 | 114 | it("should schedule with respect to extra argument", function() 115 | S:add(A1, true) 116 | S:add(A2, true, 2) 117 | S:add(A3, true) 118 | local result = {} 119 | for i = 1, 6 do table.insert(result, S:next()) end 120 | expect(result).toEqual({A1, A3, A2, A1, A3, A2}) 121 | end) 122 | 123 | it("should schedule with respect to action duration", function() 124 | S:add(A1, true) 125 | S:add(A2, true) 126 | S:add(A3, true) 127 | local result = {} 128 | 129 | table.insert(result, S:next()) 130 | S:setDuration(10) 131 | 132 | table.insert(result, S:next()) 133 | S:setDuration(5) 134 | 135 | table.insert(result, S:next()) 136 | S:setDuration(1) 137 | expect(S:getTime()).toEqual(1) 138 | 139 | for i = 1, 3 do 140 | table.insert(result, S:next()) 141 | S:setDuration(100) -- somewhere in the future 142 | end 143 | 144 | expect(result).toEqual({A1, A2, A3, A3, A2, A1}) 145 | end) 146 | end) 147 | end) 148 | 149 | -------------------------------------------------------------------------------- /tests/spec/text.lua: -------------------------------------------------------------------------------- 1 | local ROT = require 'src.rot' 2 | local expect = require 'tests.expect' (assert) 3 | 4 | describe("Text", function() 5 | describe("line breaking", function() 6 | local A100 = ("A"):rep(100) 7 | local B100 = ("B"):rep(100) 8 | 9 | it("should not break when not requested", function() 10 | local width, height = ROT.Text.measure(A100) 11 | expect(width).toEqual(#A100) 12 | expect(height).toEqual(1) 13 | end) 14 | 15 | it("should break when max length requested", function() 16 | local width, height = ROT.Text.measure(A100, 30) 17 | expect(height).toEqual(4) 18 | end) 19 | 20 | it("should break at explicit newlines", function() 21 | local width, height = ROT.Text.measure("a\nb\nc") 22 | expect(height).toEqual(3) 23 | end) 24 | 25 | it("should break at explicit newlines AND max length", function() 26 | local width, height = ROT.Text.measure(A100 .. B100, 30) 27 | expect(height).toEqual(7) 28 | 29 | local width, height = ROT.Text.measure(A100 .. "\n" .. B100, 30) 30 | expect(height).toEqual(8) 31 | end) 32 | 33 | it("should break at space", function() 34 | local width, height = ROT.Text.measure(A100 .. " " .. B100, 30) 35 | expect(height).toEqual(8) 36 | end) 37 | 38 | it("should not break at nbsp", function() 39 | local width, height = ROT.Text.measure(A100 .. '\160' .. B100, 30) 40 | expect(height).toEqual(7) 41 | end) 42 | 43 | it("should not break when text is short", function() 44 | local width, height = ROT.Text.measure("aaa bbb", 7) 45 | expect(width).toEqual(7) 46 | expect(height).toEqual(1) 47 | end) 48 | 49 | it("should adjust resulting width", function() 50 | local width, height = ROT.Text.measure("aaa bbb", 6) 51 | expect(width).toEqual(3) 52 | expect(height).toEqual(2) 53 | end) 54 | 55 | it("should adjust resulting width even without breaks", function() 56 | local width, height = ROT.Text.measure("aaa ", 6) 57 | expect(width).toEqual(3) 58 | expect(height).toEqual(1) 59 | end) 60 | 61 | it("should remove unnecessary spaces around newlines", function() 62 | local width, height = ROT.Text.measure("aaa \n bbb") 63 | expect(width).toEqual(3) 64 | expect(height).toEqual(2) 65 | end) 66 | 67 | it("should remove unnecessary spaces at the beginning", function() 68 | local width, height = ROT.Text.measure(" aaa bbb", 3) 69 | expect(width).toEqual(3) 70 | expect(height).toEqual(2) 71 | end) 72 | 73 | it("should remove unnecessary spaces at the end", function() 74 | local width, height = ROT.Text.measure("aaa \nbbb", 3) 75 | expect(width).toEqual(3) 76 | expect(height).toEqual(2) 77 | end) 78 | end) 79 | 80 | describe("color formatting", function() 81 | it("should not break with formatting part", function() 82 | local width, height = ROT.Text.measure("aaa%c{x}bbb") 83 | expect(height).toEqual(1) 84 | end) 85 | 86 | it("should correctly remove formatting", function() 87 | local width, height = ROT.Text.measure("aaa%c{x}bbb") 88 | expect(width).toEqual(6) 89 | end) 90 | 91 | it("should break independently on formatting - forced break", function() 92 | local width, height = ROT.Text.measure("aaa%c{x}bbb", 3) 93 | expect(width).toEqual(3) 94 | expect(height).toEqual(2) 95 | end) 96 | 97 | it("should break independently on formatting - forward break", function() 98 | local width, height = ROT.Text.measure("aaa%c{x}b bb", 5) 99 | expect(width).toEqual(4) 100 | expect(height).toEqual(2) 101 | end) 102 | 103 | it("should break independently on formatting - backward break", function() 104 | local width, height = ROT.Text.measure("aa a%c{x}bbb", 5) 105 | expect(width).toEqual(4) 106 | expect(height).toEqual(2) 107 | end) 108 | end) 109 | end) 110 | 111 | --------------------------------------------------------------------------------