tracer/src/Floorplan.hs

483 lines
16 KiB
Haskell
Raw Normal View History

2018-02-13 23:54:40 +00:00
module Floorplan where
import Data.Matrix (Matrix(..))
import qualified Data.Matrix as M
2018-07-19 02:51:07 +00:00
import qualified Data.Text as T
2018-02-16 20:19:15 +00:00
import Data.Maybe
import Control.Monad (foldM)
import Control.Concurrent.MVar
2018-02-13 23:54:40 +00:00
import System.Random
import Types.Map
2018-02-13 23:54:40 +00:00
buildHallFloorIO
:: FloorConfig
2018-07-19 02:51:07 +00:00
-> MVar (Float, T.Text)
-> Float
-> IO (Matrix TileState, [Graph])
buildHallFloorIO fc progress increment = do
2018-02-13 23:54:40 +00:00
rand <- newStdGen
2018-07-19 02:51:07 +00:00
modifyMVar_ progress (return . (\(p, _) ->
( p + increment
, "New RNG"
)))
2018-02-15 18:42:07 +00:00
let empty = emptyFloor fc
2018-07-19 02:51:07 +00:00
modifyMVar_ progress (return . (\(p, _) ->
( p + increment
, "Built empty floor"
)))
let (g1, withElv) = buildElevator fc (placeHalls rand fc empty)
2018-07-19 02:51:07 +00:00
modifyMVar_ progress (return . (\(p, _) ->
( p + increment
, "Placed Elevator"
)))
let (g2, withIW) = buildInnerWalls g1 withElv
2018-07-19 02:51:07 +00:00
modifyMVar_ progress (return . (\(p, _) ->
( p + increment
, "Built inner walls"
)))
let withOW = buildOuterWalls withIW
2018-07-19 02:51:07 +00:00
modifyMVar_ progress (return . (\(p, _) ->
( p + increment
, "built outer walls"
)))
let closed = closeOffices withOW
2018-07-19 02:51:07 +00:00
modifyMVar_ progress (return . (\(p, _) ->
( p + increment
, "Closed offices"
)))
let doorgraph = buildDoorsGraph closed
2018-07-19 02:51:07 +00:00
modifyMVar_ progress (return . (\(p, _) ->
( p + increment
, "Doorgraph"
)))
2018-02-16 20:19:15 +00:00
doors <- buildDoors closed doorgraph
2018-07-19 02:51:07 +00:00
modifyMVar_ progress (return . (\(p, _) ->
( p + increment
, "Build doors"
)))
2018-02-18 02:11:41 +00:00
let (_, facils) = buildFacilities g2 fc doors
2018-07-19 02:51:07 +00:00
modifyMVar_ progress (return . (\(p, _) ->
( p + increment
, "Build facilities"
)))
2018-03-03 16:03:17 +00:00
return (facils, doorgraph)
2018-02-13 23:54:40 +00:00
emptyFloor :: FloorConfig -> Matrix TileState
emptyFloor fc =
2018-03-03 16:03:17 +00:00
let (rows, cols) = fcSize fc
2018-02-13 23:54:40 +00:00
in M.matrix rows cols (const Unde)
buildElevator
:: FloorConfig
-> (StdGen, Matrix TileState)
-> (StdGen, Matrix TileState)
2018-02-15 18:42:07 +00:00
buildElevator fc (gen, empty) =
2018-03-03 16:03:17 +00:00
let (row, col) = fcElevator fc
2018-02-13 23:54:40 +00:00
boxCoord x = (,) <$> [row - x .. row + x] <*> [col - x .. col + x]
buildShaft = foldl
(\acc coord -> M.setElem (replaceTile (acc M.! coord) Hall) coord acc)
2018-02-28 20:30:59 +00:00
(foldl (flip $ M.setElem Wall) empty (boxCoord 2))
2018-02-13 23:54:40 +00:00
(boxCoord 5)
elvDoor = M.setElem Door (row + 2, col) buildShaft
2018-02-28 20:30:59 +00:00
in (gen, foldl ( flip $ M.setElem Elev) elvDoor (boxCoord 1))
2018-02-13 23:54:40 +00:00
placeHalls
:: StdGen
-> FloorConfig
-> Matrix TileState
-> (StdGen, Matrix TileState)
placeHalls rng fc input =
doHalls rng
2018-02-28 20:30:59 +00:00
[Boundaries (1,1) (nrows input, ncols input)]
2018-03-03 16:03:17 +00:00
(fcElevator fc) 5 input
2018-02-13 23:54:40 +00:00
where
2018-07-03 14:19:27 +00:00
doHalls
:: StdGen
-> [Boundaries Int]
-> (Int , Int)
-> Int
-> Matrix TileState
-> (StdGen, Matrix TileState)
2018-02-18 02:11:41 +00:00
doHalls rand bs cross wmax mat =
2018-02-15 18:42:07 +00:00
foldl (\(agen, amat) b ->
2018-03-25 18:35:07 +00:00
let (row, g1) = randomR
(fst (matmin b) + 10, fst (matmax b) - 10) agen
(col, g2) = randomR
(snd (matmin b) + 10, snd (matmax b) - 10) g1
-- (nw, g3) = randomR (2, wmax) g2
(nbs, nmat) = buildHall cross 3 b amat
2018-02-15 18:42:07 +00:00
in
2018-03-25 18:35:07 +00:00
-- if hallRatio nmat < 0.33 && wmax - 1 >= 2
if wmax - 1 >= 3
then doHalls g2 nbs (row, col) (wmax -1) nmat
else (g2, nmat)
2018-02-18 02:11:41 +00:00
) (rand, mat) bs
2018-02-13 23:54:40 +00:00
2018-03-03 16:03:17 +00:00
boundSize :: Boundaries Int -> Int
2018-02-15 18:42:07 +00:00
boundSize (Boundaries mi ma) =
(fst ma - fst mi) * (snd ma - snd mi)
2018-02-13 23:54:40 +00:00
buildHall
:: (Int, Int)
-> Int
2018-03-03 16:03:17 +00:00
-> Boundaries Int
2018-02-13 23:54:40 +00:00
-> Matrix TileState
2018-03-03 16:03:17 +00:00
-> ([Boundaries Int], Matrix TileState)
2018-02-18 02:11:41 +00:00
buildHall coord@(row, col) width bs mat =
2018-02-28 20:30:59 +00:00
let vertHalls = foldl (flip (M.mapCol
2018-02-18 02:11:41 +00:00
(\r cur -> if r >= fst (matmin bs) && r <= fst (matmax bs)
2018-02-13 23:54:40 +00:00
then replaceTile cur Hall
else cur
2018-02-28 20:30:59 +00:00
)))
2018-02-13 23:54:40 +00:00
mat
[col - (width `div` 2) .. col + (width `div` 2)]
2018-02-28 20:30:59 +00:00
horzHalls = foldl (flip ( M.mapRow
2018-02-18 02:11:41 +00:00
(\c cur -> if c >= snd (matmin bs) && c <= snd (matmax bs)
2018-02-13 23:54:40 +00:00
then replaceTile cur Hall
else cur
2018-02-28 20:30:59 +00:00
)))
2018-02-13 23:54:40 +00:00
vertHalls
[row - (width `div` 2) .. row + (width `div` 2)]
2018-02-18 02:11:41 +00:00
in ( [ Boundaries (matmin bs) coord
, Boundaries (fst (matmin bs), col) (row, snd (matmax bs))
, Boundaries (row, snd (matmin bs)) (fst (matmax bs), col)
, Boundaries coord (matmax bs)
2018-02-13 23:54:40 +00:00
]
, horzHalls
)
replaceTile :: TileState -> TileState -> TileState
replaceTile cur new
| cur == Unde = new
| otherwise = cur
hallRatio :: Matrix TileState -> Double
hallRatio mat =
2018-02-15 18:42:07 +00:00
let hs = foldl (\acc a -> if a == Hall then acc + 1 else acc) 0 mat :: Int
2018-02-13 23:54:40 +00:00
in fromIntegral hs / fromIntegral (nrows mat * ncols mat)
buildInnerWalls :: StdGen -> Matrix TileState -> (StdGen, Matrix TileState)
buildInnerWalls rng input =
let floodSearchReplace
:: StdGen
-> (Int, Int)
-> Matrix TileState
-> (StdGen, Matrix TileState)
floodSearchReplace gn coord@(row, col) mat
| mat M.! coord == Unde =
let maxRow = doRow row
doRow r
| M.safeGet (r + 1) col mat == Just Unde = doRow (r + 1)
| otherwise = r
maxCol = doCol col
doCol c
| M.safeGet row (c + 1) mat == Just Unde = doCol (c + 1)
| otherwise = c
2018-02-14 15:45:12 +00:00
(cr, g1) = randomR
( if maxRow - 3 < row + 3
then (row + 2, row + 2)
else (row + 3, maxRow - 3)
) gn
(cc, g2) = randomR
( if maxCol - 3 < col + 3
then (col + 2, col + 2)
else (col + 3,maxCol - 3)
) g1
2018-02-14 17:32:05 +00:00
(nngen, nnmat) =
if (cr - 2 > row && cr + 2 < maxRow)
&& (cc - 2 > col && cc + 2 < maxCol)
then
doCross
g2
(cr, cc)
(Boundaries coord (maxRow, maxCol))
mat
else
let btups = (,)
2018-02-28 20:30:59 +00:00
<$> [fst coord .. maxRow]
<*> [snd coord .. maxCol]
2018-02-14 17:32:05 +00:00
in
( g2
, foldl
(\acc coords ->
M.setElem (replaceTile (acc M.! coords) Offi) coords acc
) mat btups
)
2018-02-13 23:54:40 +00:00
doCross
:: StdGen
-> (Int, Int)
2018-03-03 16:03:17 +00:00
-> Boundaries Int
2018-02-13 23:54:40 +00:00
-> Matrix TileState
-> (StdGen, Matrix TileState)
2018-02-15 18:42:07 +00:00
doCross gen cd@(xr, xc) b imat =
2018-02-28 20:30:59 +00:00
let nbs = map (uncurry Boundaries) bs
2018-02-18 02:11:41 +00:00
bs =
2018-02-15 18:42:07 +00:00
[ (matmin b, cd)
2018-02-13 23:54:40 +00:00
, ((fst (matmin b), col), (row, snd (matmax b)))
, ((row, snd (matmin b)), (fst (matmax b), col))
2018-02-15 18:42:07 +00:00
, (cd, matmax b)
2018-02-13 23:54:40 +00:00
]
(ngen, crosses) = foldl
(\(agen, acc) (minb, maxb) ->
2018-02-15 18:42:07 +00:00
let (fc, gg1) = randomR (fst minb, fst maxb) agen
(fr, gg2) = randomR (snd minb, snd maxb) gg1
in (gg2, acc ++ [(fc, fr)])
2018-02-18 02:11:41 +00:00
) (gen, []) bs
2018-02-13 23:54:40 +00:00
horz = M.mapRow (\icol cur ->
if icol >= snd (matmin b) && icol <= snd (matmax b)
then replaceTile cur Wall
else cur
) xr imat
vert = M.mapCol (\irow cur ->
if irow >= fst (matmin b) && irow <= fst (matmax b)
then replaceTile cur Wall
else cur
) xc horz
omat = foldl
(\acc coords ->
M.setElem (replaceTile (acc M.! coords) Offi) coords acc
) vert btups
btups = (,)
<$> [fst (matmin b) .. fst (matmax b)]
<*> [snd (matmin b) .. snd (matmax b)]
2018-02-14 17:32:05 +00:00
in if boundSize b >= 16
2018-02-13 23:54:40 +00:00
then foldl
2018-02-15 18:42:07 +00:00
(\(agen, amat) (acr, nb) -> doCross agen acr nb amat)
2018-02-13 23:54:40 +00:00
(ngen, omat)
(zip crosses nbs)
2018-02-14 17:32:05 +00:00
else
2018-02-13 23:54:40 +00:00
(gen, omat)
in (nngen, nnmat)
| otherwise = (gn, mat)
tups mat = (,) <$> [1 .. nrows mat] <*> [1 .. ncols mat]
in foldl (\(agen, amat) cds -> floodSearchReplace agen cds amat)
(rng, input) (tups input)
closeOffices :: Matrix TileState -> Matrix TileState
closeOffices input =
let tups mat = (,) <$> [2 .. nrows mat - 1] <*> [2 .. ncols mat - 1]
isNeighbor (row, col) =
let subm = M.submatrix (row -1) (row + 1) (col - 1) (col + 1) input
2018-02-28 20:30:59 +00:00
in Hall `elem` (M.toList subm)
2018-02-13 23:54:40 +00:00
in foldl (\acc coord ->
if input M.! coord == Offi && isNeighbor coord
then M.setElem Wall coord acc
else acc
) input (tups input)
buildOuterWalls :: Matrix TileState -> Matrix TileState
buildOuterWalls input =
let horz :: Matrix TileState
horz =
foldl
2018-02-28 20:30:59 +00:00
(flip $ M.mapRow (\_ _ -> Wall))
2018-02-13 23:54:40 +00:00
input
[ 1
, nrows input
]
vert =
foldl
2018-02-28 20:30:59 +00:00
(flip $ M.mapCol (\_ _ -> Wall))
2018-02-13 23:54:40 +00:00
horz
[ 1
, ncols horz
]
in vert
2018-02-15 02:50:51 +00:00
buildFacilities
:: StdGen
-> FloorConfig
-> Matrix TileState
-> (StdGen, Matrix TileState)
buildFacilities gen fc input =
let flood ts mat coords@(cr, cc) =
let cur = mat M.! coords
altered = M.setElem ts coords mat
in
if cur == ts || cur /= Offi
then mat
2018-02-28 20:30:59 +00:00
else foldl (flood ts)
2018-02-15 02:50:51 +00:00
altered
[ (cr + 1, cc)
, (cr - 1, cc)
, (cr, cc + 1)
, (cr, cc - 1)
]
2018-03-03 16:03:17 +00:00
nearests = map (findNearestOffice input) (fcFacilities fc)
2018-02-28 20:30:59 +00:00
in foldl (\(agen, acc) x ->
2018-02-15 02:50:51 +00:00
let (numfac, ngen) = randomR (0 :: Int, 1 :: Int) agen
facil = if numfac == 1 then Kitc else Toil
in (ngen, flood facil acc x)
2018-02-28 20:30:59 +00:00
) (gen, input) nearests
2018-02-15 02:50:51 +00:00
findNearestOffice
:: Matrix TileState
-> (Int, Int)
-> (Int, Int)
findNearestOffice mat (rrr, ccc) =
let matcoord = (,) <$> [1 .. nrows mat] <*> [1 .. ncols mat]
2018-02-15 18:42:07 +00:00
distance :: (Int, Int) -> Int
distance (ar, ac) = (ar - rrr) ^ (2 :: Int) + (ac - ccc) ^ (2 :: Int)
2018-02-15 02:50:51 +00:00
inlist = zip matcoord (M.toList mat)
2018-02-15 18:42:07 +00:00
in foldl (\acc (xc, ts) ->
2018-02-15 02:50:51 +00:00
if ts == Offi && distance acc > distance xc
then xc
else acc
) (fst $ head inlist) inlist
2018-02-15 18:42:07 +00:00
2018-02-16 20:19:15 +00:00
buildDoorsGraph :: Matrix TileState -> [Graph]
buildDoorsGraph mat =
2018-02-18 02:11:41 +00:00
let maxCol r c
2018-02-15 18:42:07 +00:00
| M.safeGet r (c + 1) mat == Just Offi = maxCol r (c + 1)
| otherwise = c
2018-02-16 20:19:15 +00:00
buildGraph :: Matrix TileState -> [Graph] -> (Int, Int) -> [Graph]
buildGraph amat root coord@(br, bc)
2018-02-15 18:42:07 +00:00
| bc > ncols amat - 1 =
2018-02-16 20:19:15 +00:00
buildGraph amat root (br + 1, 1)
2018-02-15 18:42:07 +00:00
| br > nrows amat - 1 =
2018-02-16 20:19:15 +00:00
root
2018-02-15 18:42:07 +00:00
| M.safeGet br bc amat == Just Offi =
2018-02-18 02:11:41 +00:00
let flood acc (fr, fc) =
2018-02-16 20:19:15 +00:00
let ncoords = [] ++
(if (fr + 1, fc) `notElem` acc &&
M.safeGet (fr + 1) fc amat == Just Offi
then [(fr + 1, fc)]
else []) ++
(if (fr - 1, fc) `notElem` acc &&
M.safeGet (fr - 1) fc amat == Just Offi
then [(fr - 1, fc)]
else []) ++
(if (fr, fc - 1) `notElem` acc &&
M.safeGet fr (fc - 1) amat == Just Offi
then [(fr, fc - 1)]
else []) ++
(if (fr, fc + 1) `notElem` acc &&
M.safeGet fr (fc + 1) amat == Just Offi
then [(fr, fc + 1)]
else [])
in foldl flood (acc ++ ncoords) ncoords
2018-02-24 21:24:48 +00:00
roomcoords = flood [coord] coord
2018-02-16 20:19:15 +00:00
b = Boundaries
(minimum (map fst roomcoords), minimum (map snd roomcoords))
(maximum (map fst roomcoords), maximum (map snd roomcoords))
2018-02-18 02:11:41 +00:00
neighs = map (\(a, bx) -> (a, fromJust bx)) (filter ((/=Nothing) . snd)
2018-02-16 20:19:15 +00:00
[ (North, M.safeGet (fst (matmin b) - 2) (snd (matmin b)) amat)
, (South, M.safeGet (fst (matmax b) + 2) (snd (matmin b)) amat)
, (East, M.safeGet (fst (matmin b)) (snd (matmin b) - 2) amat)
, (West, M.safeGet (fst (matmin b)) (snd (matmax b) + 2) amat)
])
in
if Hall `elem` map snd neighs
then
2018-02-16 21:16:28 +00:00
let nroot =
if GRoom neighs b `notElem` connects (head root)
then
GHall
{ connects = connects (head root) ++ [GRoom neighs b]
} : tail root
else root
2018-02-16 20:19:15 +00:00
in buildGraph amat nroot (br, 1 + snd (matmax b))
else
let nroot = root ++
2018-02-16 21:16:28 +00:00
if GRoom neighs b `elem` root
then []
else [GRoom neighs b]
2018-02-16 20:19:15 +00:00
in buildGraph amat nroot (br, 1 + snd (matmax b))
2018-02-15 18:42:07 +00:00
| otherwise =
2018-02-16 20:19:15 +00:00
buildGraph amat root (br, maxCol br (bc + 1))
in buildGraph mat [GHall []] (2, 2)
2018-02-15 18:42:07 +00:00
2018-02-16 20:19:15 +00:00
buildDoors :: Matrix TileState -> [Graph] -> IO (Matrix TileState)
2018-07-03 14:19:27 +00:00
buildDoors input graph =
2018-02-16 20:19:15 +00:00
foldM placeDoors input graph
where
placeDoors :: Matrix TileState -> Graph -> IO (Matrix TileState)
placeDoors amat (GHall conns) =
foldM placeDoors amat conns
2018-02-18 02:11:41 +00:00
placeDoors amat (GRoom neighs bs) =
2018-02-16 20:19:15 +00:00
if Hall `elem` map snd neighs
then do
let halls = filter ((== Hall) . snd) neighs
idx <- randomRIO (0, length halls - 1)
let (dir, _) = halls !! idx
case dir of
North ->
inRow
amat
2018-02-18 02:11:41 +00:00
(fst (matmin bs) - 1)
(snd (matmin bs), snd (matmax bs))
2018-02-16 20:19:15 +00:00
South ->
inRow
amat
2018-02-18 02:11:41 +00:00
(fst (matmax bs) + 1)
(snd (matmin bs), snd (matmax bs))
2018-02-16 20:19:15 +00:00
East ->
inCol
amat
2018-02-18 02:11:41 +00:00
(fst (matmin bs), fst (matmax bs))
(snd (matmin bs) - 1)
2018-02-16 20:19:15 +00:00
West ->
inCol
amat
2018-02-18 02:11:41 +00:00
(fst (matmin bs), fst (matmax bs))
(snd (matmax bs) + 1)
2018-02-16 20:19:15 +00:00
else do
idx <- randomRIO (0, length neighs - 1)
let (dir, _) = neighs !! idx
case dir of
North ->
inRow
amat
2018-02-18 02:11:41 +00:00
(fst (matmin bs) - 1)
(snd (matmin bs), snd (matmax bs))
2018-02-16 20:19:15 +00:00
South ->
inRow
amat
2018-02-18 02:11:41 +00:00
(fst (matmax bs) + 1)
(snd (matmin bs), snd (matmax bs))
2018-02-16 20:19:15 +00:00
East ->
inCol
amat
2018-02-18 02:11:41 +00:00
(fst (matmin bs), fst (matmax bs))
(snd (matmin bs) - 1)
2018-02-16 20:19:15 +00:00
West ->
inCol
amat
2018-02-18 02:11:41 +00:00
(fst (matmin bs), fst (matmax bs))
(snd (matmax bs) + 1)
2018-02-16 20:19:15 +00:00
inRow :: Matrix TileState -> Int -> (Int, Int) -> IO (Matrix TileState)
inRow mat row cols = do
col <- randomRIO cols
let tile = mat M.! (row, col)
if tile == Wall
&& length (filter
(== Wall)
(M.toList (M.submatrix (row - 1) (row + 1) col col mat)))
== 1
then
2018-07-03 14:19:27 +00:00
if Door `elem` M.toList (uncurry (M.submatrix row row) cols mat)
2018-02-16 20:19:15 +00:00
then return mat
2018-07-03 14:19:27 +00:00
else
2018-02-16 20:19:15 +00:00
return $ M.setElem Door (row, col) mat
else
inRow mat row cols
inCol :: Matrix TileState -> (Int, Int) -> Int -> IO (Matrix TileState)
inCol mat rows col = do
row <- randomRIO rows
let tile = mat M.! (row, col)
if tile == Wall
&& length (filter
(== Wall)
(M.toList (M.submatrix row row (col - 1) (col + 1) mat)))
== 1
then
2018-07-03 14:19:27 +00:00
if Door `elem` M.toList (uncurry M.submatrix rows col col mat)
2018-02-16 20:19:15 +00:00
then return mat
2018-07-03 14:19:27 +00:00
else
2018-02-16 20:19:15 +00:00
return $ M.setElem Door (row, col) mat
else
inCol mat rows col