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 |
--------------------------------------------------------------------------------