parabox/app/Rendering.hs

249 lines
8.5 KiB
Haskell
Raw Normal View History

2022-12-02 10:54:31 -08:00
module Rendering where
import Data.Array
import Data.Maybe
2022-12-02 10:54:31 -08:00
import Data.Map (Map)
import Data.Map qualified as Map
import Data.Set qualified as Set
2022-12-06 13:39:30 -08:00
import Data.List (intersperse, group)
2022-12-02 10:54:31 -08:00
import Graphics.Vty
2022-12-07 17:31:07 -08:00
import BigFont
2022-12-02 10:54:31 -08:00
import Model
2022-12-03 11:50:06 -08:00
border :: Int
border = 20
2022-12-06 13:39:30 -08:00
unit :: Attr -> Int -> Int -> Char -> Image
unit a h w c =
vertCat (replicate h (string a (replicate w c)))
2022-12-02 10:54:31 -08:00
2022-12-03 11:50:06 -08:00
drawBox :: Attr -> Int {- ^ width -} -> Int {- ^ height -} -> Image
2022-12-07 17:31:07 -08:00
drawBox a 1 w = string a ("[" ++ replicate (w-2) '-' ++ "]")
drawBox a h w = vertCat $
2022-12-03 11:50:06 -08:00
string a ('┌' : replicate (w-2) '─' ++ "") :
replicate (h-2) (string a ('│' : replicate (w-2) ' ' ++ "")) ++
[string a ('└' : replicate (w-2) '─' ++ "")]
2022-12-06 13:39:30 -08:00
button :: Attr -> Int -> Int -> Image
2022-12-07 17:31:07 -08:00
button a 1 w = string a ("[" ++ replicate (w-2) '-' ++ "]")
2022-12-06 13:39:30 -08:00
button a h w = vertCat $
string a ('┌' : replicate (w-2) '─' ++ "") :
replicate (h-2) (string a ('│' : replicate (w-2) '░' ++ "")) ++
[string a ('└' : replicate (w-2) '─' ++ "")]
home :: Attr -> Int -> Int -> Image
home a 1 w = string a ("<" ++ replicate (w-2) '=' ++ ">")
home a h w = vertCat $
string a ('╔' : replicate (w-2) '═' ++ "") :
replicate (h-2) (string a ('║' : replicate (w-2) '░' ++ "")) ++
[string a ('╚' : replicate (w-2) '═' ++ "")]
renderCell :: World -> Map Location Char -> Char -> Box -> Int -> Int -> Int -> Int -> Image
2022-12-07 17:31:07 -08:00
renderCell world locMap name box y x h w
| boxWalls world box ! (y,x) = unit (boxColor box) h w wallChar
| Just n <- Map.lookup (Location name' y x) locMap
, let box' = boxIx world n
= if h < boxSize world box'
then unit (boxColor box') h w n
else renderBox world locMap box' n h w
| Set.member loc (worldButtons world) = button (boxColor box) h w
| loc == worldHome world = home (boxColor box) h w
| otherwise = unit (boxColor box) h w floorChar
2022-12-02 15:28:05 -08:00
where
2022-12-02 20:53:45 -08:00
loc = Location name' y x
2022-12-04 17:16:43 -08:00
name' = contentName world name box
2022-12-02 20:53:45 -08:00
wallChar =
case boxType box of
2022-12-07 17:31:07 -08:00
Original {} -> '▓'
Link {} -> '▒'
2022-12-04 15:59:22 -08:00
Infinity {} -> '▓'
2022-12-07 17:31:07 -08:00
Epsilon {} -> '▓'
2022-12-04 15:59:22 -08:00
floorChar =
case boxType box of
2022-12-07 17:31:07 -08:00
Original {} -> '░'
Link {} -> '·'
2022-12-04 15:59:22 -08:00
Infinity {} -> '∞'
2022-12-07 17:31:07 -08:00
Epsilon {} -> 'ε'
2022-12-02 10:54:31 -08:00
2022-12-04 17:16:43 -08:00
contentName :: World -> Char -> Box -> Char
contentName world name box =
case boxType box of
Original{} -> name
2022-12-07 17:31:07 -08:00
Epsilon{} -> name
Link c -> c
Infinity c -> contentName world c (boxIx world c)
2022-12-04 17:16:43 -08:00
2022-12-06 13:39:30 -08:00
renderBox :: World -> Map Location Char -> Box -> Char -> Int -> Int -> Image
renderBox world locMap box name boxh boxw =
2022-12-02 21:33:44 -08:00
vertCat [
horizCat [
renderCell world locMap name box y x cellh cellw
2022-12-07 17:31:07 -08:00
| (x,cellw) <- zip [xlo .. xhi] xdivs ]
| (y,cellh) <- zip [ylo .. yhi] ydivs ]
2022-12-06 13:39:30 -08:00
where
2022-12-07 17:31:07 -08:00
ydivs = divisions boxHeight boxh
xdivs = divisions boxWidth boxw
2022-12-06 13:39:30 -08:00
((ylo,xlo),(yhi,xhi)) = bounds (boxWalls world box)
boxWidth = xhi - xlo + 1
boxHeight = yhi - ylo + 1
2022-12-03 13:33:02 -08:00
2022-12-03 11:50:06 -08:00
render ::
Bool {- ^ show flat overlay -} ->
World ->
Picture
render flat world = picForLayers $
[ pad 98 12 0 0 $
2022-12-02 21:33:44 -08:00
string defAttr "██╗ ██╗██╗███╗ ██╗███╗ ██╗███████╗██████╗ " <->
string defAttr "██║ ██║██║████╗ ██║████╗ ██║██╔════╝██╔══██╗" <->
string defAttr "██║ █╗ ██║██║██╔██╗ ██║██╔██╗ ██║█████╗ ██████╔╝" <->
string defAttr "██║███╗██║██║██║╚██╗██║██║╚██╗██║██╔══╝ ██╔══██╗" <->
string defAttr "╚███╔███╔╝██║██║ ╚████║██║ ╚████║███████╗██║ ██║" <->
string defAttr " ╚══╝╚══╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝"
| winCondition world ] ++
2022-12-07 17:31:07 -08:00
[ pad 94 7 0 0 $
vertCat (map (string defAttr) (bigText "VOIDED"))
| isNothing (boxLocation (boxIx world (worldMe world))) ] ++
2022-12-03 11:50:06 -08:00
(if flat then renderFlat locMap world else []) ++
[drawNestedWorld locMap world]
where
2022-12-07 17:31:07 -08:00
locMap = Map.fromList [(loc, n) | (n, box) <- Map.toList (worldBoxes world)
, loc <- maybeToList (boxLocation box)]
2022-12-03 11:50:06 -08:00
renderFlat :: Map Location Char -> World -> [Image]
renderFlat locMap world =
[ pad offset 0 0 0 baseImage
2022-12-07 17:31:07 -08:00
, pad offset 0 0 0 $ drawBox borderAttr (imageHeight baseImage) (imageWidth baseImage)
2022-12-03 11:50:06 -08:00
]
where
borderAttr = defAttr `withForeColor` white `withBackColor` black
2022-12-07 10:11:06 -08:00
offset = max 0 ( (worldWidth world + 2*(2*border)+2 - imageWidth baseImage) `div` 2)
2022-12-03 11:50:06 -08:00
baseImage =
pad 2 1 2 1 $
horizCat $
intersperse (char borderAttr ' ')
2022-12-06 13:39:30 -08:00
[renderBox world locMap b n 18 36
2022-12-03 11:50:06 -08:00
| (n,b) <- Map.assocs (worldBoxes world)
2022-12-03 13:33:02 -08:00
, not (boxBoring b)]
2022-12-02 10:54:31 -08:00
2022-12-03 11:50:06 -08:00
drawNestedWorld :: Map Location Char -> World -> Image
drawNestedWorld locMap world =
2022-12-07 10:11:06 -08:00
cropTop (h + 2*border) $
cropLeft (w + 4*border) $
cropBottom (2*h + border) $
cropRight (2*(w + border)) $
2022-12-02 21:33:44 -08:00
vertCat $
intersperse (char defAttr ' ')
[
horizCat $
intersperse (char defAttr ' ')
[
case myLocation world of
Nothing
2022-12-07 17:31:07 -08:00
| dx==0 && dy==0 -> renderBox world locMap (boxIx world (worldMe world)) (worldMe world) h w
| otherwise -> infinityImage
Just (Location name0 _ _) ->
2022-12-07 17:31:07 -08:00
case boxLocation (boxIx world name0) of
Nothing
2022-12-07 17:31:07 -08:00
| dx==0 && dy==0 -> renderBox world locMap (boxIx world name0) name0 h w
| otherwise -> infinityImage
Just (Location name1 y1 x1) ->
case stackedLoc world locMap (Location name1 (y1+dy) (x1+dx)) of
Nothing -> infinityImage
Just (Location n y x) ->
2022-12-07 17:31:07 -08:00
renderCell world locMap n (boxIx world n) y x h w
| dx <- [-1 .. 1]
2022-12-02 10:54:31 -08:00
]
| dy <- [-1 .. 1]
2022-12-02 10:54:31 -08:00
]
where
2022-12-07 17:31:07 -08:00
infinityImage = makeInfinity h w
2022-12-07 10:11:06 -08:00
h = worldHeight world
w = worldWidth world
2022-12-02 10:54:31 -08:00
2022-12-07 17:31:07 -08:00
makeInfinity :: Int -> Int -> Image
makeInfinity h w = result
where
attr = defAttr `withForeColor` black
single = vertCat (map (string attr) (bigText "?"))
rowN = (w+1) `div` (imageWidth single + 1)
rowTotalGap = w - rowN * imageWidth single
rowGaps = divisions (rowN-1) rowTotalGap
row = foldr mkRow single rowGaps
mkRow gap rest = single <|> charFill attr ' ' gap 1 <|> rest
colN = (h+1) `div` (imageHeight single + 1)
colTotalGap = h - colN * imageHeight single
colGaps = divisions (colN-1) colTotalGap
result = foldr mkCol row colGaps
mkCol gap rest = row <-> charFill attr ' ' 1 gap <-> rest
2022-12-02 19:33:27 -08:00
stackedLoc :: World -> Map Location Char -> Location -> Maybe Location
stackedLoc world locMap = go Set.empty
2022-12-02 10:54:31 -08:00
where
2022-12-04 15:59:22 -08:00
go visited loc@(Location b y x)
| Set.member loc visited =
do b' <- findInfinity world b
go visited (Location b' y x)
2022-12-02 10:54:31 -08:00
go visited loc@(Location b y x) =
do box <- Map.lookup b (worldBoxes world)
2022-12-02 15:28:05 -08:00
let bnds@((ylo,xlo),(yhi,xhi)) = bounds (boxWalls world box)
2022-12-02 10:54:31 -08:00
if inRange bnds (y, x)
then Just loc
else
do let dx = overflow (xlo,xhi) x
2022-12-02 10:54:31 -08:00
dy = overflow (ylo,yhi) y
Location parent py px <- boxLocation box
fixup world locMap dy dx y x <$> go (Set.insert loc visited) (Location parent (py+dy) (px+dx))
2022-12-02 10:54:31 -08:00
overflow :: (Int, Int) -> Int -> Int
overflow (lo,hi) x
| x < lo = x - lo
| x > hi = x - hi
| otherwise = 0
2022-12-02 19:33:27 -08:00
2022-12-07 17:31:07 -08:00
fixup :: World ->
Map Location Char ->
Int ->
Int ->
Int ->
Int ->
Location ->
2022-12-02 19:33:27 -08:00
Location
fixup world locMap dy dx py px loc =
case Map.lookup loc locMap of
Nothing -> loc
Just name -> Location name (fixup1 ylo yhi dy py) (fixup1 xlo xhi dx px)
where
2022-12-07 17:31:07 -08:00
((ylo,xlo),(yhi,xhi)) = bounds (boxWalls world (boxIx world name))
2022-12-02 19:33:27 -08:00
fixup1 :: Int -> Int -> Int -> Int -> Int
fixup1 _ _ 0 i = i
fixup1 _ hi (-1) _ = hi
fixup1 lo _ 1 _ = lo
2022-12-06 13:39:30 -08:00
fixup1 _ _ _ _ = error "fixup1: bad delta"
2022-12-07 17:31:07 -08:00
divisions ::
Int {- ^ result length -} ->
Int {- ^ result sum -} ->
[Int]
2022-12-06 13:39:30 -08:00
divisions divs size =
map length $ group
[ round (
(fromIntegral i + 1 / 2)
/ fromIntegral size
* fromIntegral divs
- 1/2 :: Rational
) :: Int
2022-12-06 13:39:30 -08:00
| i <- [0 ..size-1]
]