parabox/app/Rendering.hs

179 lines
6.4 KiB
Haskell
Raw Normal View History

2022-12-02 10:54:31 -08:00
module Rendering where
import Data.Array
import Data.Map (Map)
import Data.Map qualified as Map
import Data.Set qualified as Set
2022-12-02 21:33:44 -08:00
import Data.List (intersperse)
2022-12-02 10:54:31 -08:00
import Graphics.Vty
import Model
2022-12-03 11:50:06 -08:00
border :: Int
border = 20
2022-12-02 11:48:03 -08:00
unit :: Attr -> Int -> Char -> Image
unit a scale x =
vertCat (replicate scale (string a (replicate (2*scale) x)))
2022-12-02 10:54:31 -08:00
2022-12-03 11:50:06 -08:00
drawBox :: Attr -> Int {- ^ width -} -> Int {- ^ height -} -> Image
drawBox a _ 1 = string a "[]"
drawBox a w h = vertCat $
string a ('┌' : replicate (w-2) '─' ++ "") :
replicate (h-2) (string a ('│' : replicate (w-2) ' ' ++ "")) ++
[string a ('└' : replicate (w-2) '─' ++ "")]
2022-12-02 20:53:45 -08:00
button :: Attr -> Int -> Image
button a 1 = string a "[]"
button a n = vertCat $
string a ('┌' : replicate (2*n-2) '─' ++ "") :
replicate (n-2) (string a ('│' : replicate (2*n-2) '░' ++ "")) ++
[string a ('└' : replicate (2*n-2) '─' ++ "")]
home :: Attr -> Int -> Image
home a 1 = string a "<>"
home a n = vertCat $
string a ('╔' : replicate (2*n-2) '═' ++ "") :
replicate (n-2) (string a ('║' : replicate (2*n-2) '░' ++ "")) ++
[string a ('╚' : replicate (2*n-2) '═' ++ "")]
2022-12-02 11:48:03 -08:00
renderCell :: World -> Map Location Char -> Char -> Box -> Int -> Int -> Int -> Image
renderCell world locMap name box y x scale =
2022-12-02 15:28:05 -08:00
if boxWalls world box ! (y,x) then unit (boxColor box) scale wallChar
else case Map.lookup (Location name' y x) locMap of
2022-12-02 11:48:03 -08:00
Just n ->
2022-12-03 11:54:06 -08:00
if scale < 9
2022-12-02 11:48:03 -08:00
then unit (boxColor (worldBoxes world Map.! n)) scale n
else renderBox world locMap (worldBoxes world Map.! n) n
2022-12-02 15:28:05 -08:00
(scale `div` boxSize world box)
2022-12-02 20:53:45 -08:00
Nothing
| Set.member loc (worldButtons world) -> button (boxColor box) scale
| loc == worldHome world -> home (boxColor box) scale
| otherwise -> unit (boxColor box) scale '░'
2022-12-02 15:28:05 -08:00
where
2022-12-02 20:53:45 -08:00
loc = Location name' y x
name' =
case boxType box of
Original{} -> name
Link c -> c
wallChar =
case boxType box of
Original{} -> '▓'
Link{} -> '▒'
2022-12-02 10:54:31 -08:00
2022-12-02 11:48:03 -08:00
renderBox :: World -> Map Location Char -> Box -> Char -> Int -> Image
renderBox world locMap box name scale =
2022-12-02 21:33:44 -08:00
vertCat [
horizCat [
renderCell world locMap name box y x scale
| x <- [xlo .. xhi]
2022-12-02 10:54:31 -08:00
]
2022-12-02 15:28:05 -08:00
| let ((ylo,xlo),(yhi,xhi)) = bounds (boxWalls world box)
2022-12-02 10:54:31 -08:00
, y <- [ylo .. yhi]
]
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-03 11:50:06 -08:00
(if flat then renderFlat locMap world else []) ++
[drawNestedWorld locMap world]
where
locMap = Map.fromList [(boxLocation loc, n) | (n, loc) <- Map.toList (worldBoxes world)]
renderFlat :: Map Location Char -> World -> [Image]
renderFlat locMap world =
[ pad offset 0 0 0 baseImage
, pad offset 0 0 0 $ drawBox borderAttr (imageWidth baseImage) (imageHeight baseImage)
]
where
borderAttr = defAttr `withForeColor` white `withBackColor` black
offset = max 0 ( (2*(81+2*border)+2 - imageWidth baseImage) `div` 2)
baseImage =
pad 2 1 2 1 $
horizCat $
intersperse (char borderAttr ' ')
2022-12-03 11:54:06 -08:00
[renderBox world locMap b n 2
2022-12-03 11:50:06 -08:00
| (n,b) <- Map.assocs (worldBoxes world)
, Original{} <- [boxType 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-02 15:46:42 -08:00
-- (3*81) + 81 + (3*81)
cropTop (81 + 2*border) $
cropLeft (2*(81 + 2*border)) $
cropBottom (2*81 + border) $
cropRight (2*(2*81 + border)) $
2022-12-02 21:33:44 -08:00
vertCat $
intersperse (char defAttr ' ')
[
horizCat $
intersperse (char defAttr ' ')
[
2022-12-02 19:33:27 -08:00
case stackedLoc world locMap (Location name1 y_ x_) of
2022-12-02 15:46:42 -08:00
Nothing -> unit (withForeColor defAttr black) 81 '?'
2022-12-02 10:54:31 -08:00
Just (Location n y x) ->
2022-12-02 11:48:03 -08:00
let box = worldBoxes world Map.! n in
2022-12-02 15:46:42 -08:00
renderCell world locMap n box y x 81
2022-12-02 11:48:03 -08:00
| x_ <- [x1-1 .. x1+1]
2022-12-02 10:54:31 -08:00
]
2022-12-02 11:48:03 -08:00
| y_ <- [y1-1 .. y1+1]
2022-12-02 10:54:31 -08:00
]
where
-- name1 is the box the player is standing in
2022-12-03 11:50:06 -08:00
Location name0 _ _ = myLocation world
2022-12-02 10:54:31 -08:00
Location name1 y1 x1 = boxLocation (worldBoxes world Map.! name0)
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
go visited loc | Set.member loc visited = Nothing
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
let dx = overflow (xlo,xhi) x
dy = overflow (ylo,yhi) y
Location parent py px = boxLocation box
2022-12-02 19:33:27 -08:00
in 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
fixup :: World ->
Map Location Char ->
Int ->
Int ->
Int ->
Int ->
Location ->
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
((ylo,xlo),(yhi,xhi)) = bounds (boxWalls world (worldBoxes world Map.! name))
fixup1 :: Int -> Int -> Int -> Int -> Int
fixup1 _ _ 0 i = i
fixup1 _ hi (-1) _ = hi
fixup1 lo _ 1 _ = lo
fixup1 _ _ _ _ = error "fixup1: bad delta"