parabox/app/Main.hs

143 lines
4.8 KiB
Haskell
Raw Normal View History

2022-12-05 08:13:26 -08:00
module Main (main) where
2022-11-30 13:38:12 -08:00
2022-12-02 21:33:44 -08:00
import Control.Exception ( bracket )
2022-12-08 18:58:07 -08:00
import Data.Char (toUpper)
import Data.List (intersperse, isPrefixOf, sort)
import Data.List.NonEmpty (NonEmpty(..), (<|))
2022-11-30 13:38:12 -08:00
import Data.List.NonEmpty qualified as NonEmpty
2022-12-04 19:55:30 -08:00
import Data.Map (Map)
import Data.Map qualified as Map
2022-12-05 08:13:26 -08:00
import Graphics.Vty
2024-05-22 14:16:46 -07:00
import Graphics.Vty.CrossPlatform
2022-12-08 18:58:07 -08:00
import Data.Ord (clamp)
import System.Directory (listDirectory)
import System.Environment (getArgs)
import System.FilePath ((</>), takeBaseName, takeExtension)
2022-11-30 13:38:12 -08:00
2022-12-08 18:58:07 -08:00
import BigFont (bigText)
2022-12-02 10:54:31 -08:00
import Model
2022-12-04 16:45:15 -08:00
import Parser (parse)
2022-12-08 18:58:07 -08:00
import Rendering (render, wrapBox)
2022-11-30 16:44:30 -08:00
2022-12-03 11:50:06 -08:00
data Game = Game {
2022-12-04 19:55:30 -08:00
gameFlat :: Bool,
gameMode :: GameMode,
2022-12-08 18:58:07 -08:00
gameSelect :: Int,
gameError :: String
2022-12-03 11:50:06 -08:00
}
2022-11-30 13:38:12 -08:00
2022-12-04 19:55:30 -08:00
data GameMode
= MenuMode
| PlayMode (NonEmpty World)
2022-12-05 17:00:19 -08:00
getWorldList :: IO (Map String FilePath)
getWorldList =
2022-12-08 18:58:07 -08:00
do paths <- sort . filter isLevelName <$> listDirectory "levels"
2022-12-05 17:00:19 -08:00
pure (Map.fromList [(takeBaseName path, "levels" </> path) | path <- paths])
2022-12-08 18:58:07 -08:00
isLevelName :: FilePath -> Bool
isLevelName path = not ("." `isPrefixOf` path) && ".txt" == takeExtension path
defaultGame :: Game
defaultGame = Game {
gameMode = MenuMode,
gameFlat = True,
gameSelect = 0,
gameError = ""
}
2022-11-30 13:38:12 -08:00
main :: IO ()
main =
2022-12-02 20:53:45 -08:00
do args <- getArgs
2022-12-05 17:00:19 -08:00
worldList <- getWorldList
2022-12-04 19:55:30 -08:00
bracket (mkVty =<< userConfig) shutdown \vty ->
case args of
2022-12-05 17:00:19 -08:00
x:_ | Just path <- Map.lookup x worldList ->
2022-12-08 18:58:07 -08:00
loadLevelFile vty defaultGame path
_ -> loop vty defaultGame
2022-11-30 13:38:12 -08:00
2022-12-03 11:50:06 -08:00
loop :: Vty -> Game -> IO ()
2022-12-08 18:58:07 -08:00
loop vty game = do
bnds <- displayBounds (outputIface vty)
2022-12-04 19:55:30 -08:00
case gameMode game of
MenuMode ->
2022-12-05 17:00:19 -08:00
do worldList <- getWorldList
2022-12-08 18:58:07 -08:00
update vty (picForLayers $
[errorImage (gameError game) | not (null (gameError game))] ++
[renderMenu bnds (gameSelect game) worldList])
2022-12-04 19:55:30 -08:00
ev <- nextEvent vty
case ev of
EvKey key _modifier ->
case key of
2022-12-08 18:58:07 -08:00
KEsc -> escape vty game
KUp -> loop vty game{ gameSelect = max 0 (gameSelect game - 1) }
KPageUp -> loop vty game{ gameSelect = max 0 (gameSelect game - 10) }
KDown -> loop vty game{ gameSelect = clamp (0, Map.size worldList - 1) (gameSelect game + 1) }
KPageDown -> loop vty game{ gameSelect = clamp (0, Map.size worldList - 1) (gameSelect game + 10) }
KEnter
| 0 <= gameSelect game, gameSelect game < Map.size worldList
, (_, path) <- Map.elemAt (gameSelect game) worldList ->
loadLevelFile vty game path
2022-12-04 19:55:30 -08:00
_ -> loop vty game
_ -> loop vty game
PlayMode worlds ->
do let world = NonEmpty.head worlds
2022-12-08 18:58:07 -08:00
update vty (render bnds (gameFlat game) (NonEmpty.head worlds))
2022-12-04 19:55:30 -08:00
ev <- nextEvent vty
case ev of
EvKey key _modifier ->
let doMove m = game{ gameMode = PlayMode (move world m <| worlds) } in
2022-12-04 19:55:30 -08:00
case key of
KUp -> loop vty (doMove (-1, 0))
KDown -> loop vty (doMove ( 1, 0))
KLeft -> loop vty (doMove ( 0,-1))
KRight -> loop vty (doMove ( 0, 1))
2022-12-04 19:55:30 -08:00
KChar 'm' -> loop vty game { gameMode = MenuMode }
KChar 'r' -> loop vty game{ gameMode = PlayMode (pure (NonEmpty.last worlds)) }
KChar 'z'
| Just worlds' <- NonEmpty.nonEmpty (NonEmpty.tail worlds) ->
loop vty game{ gameMode = PlayMode worlds' }
2022-12-08 18:58:07 -08:00
KEsc -> escape vty game
2022-12-04 19:55:30 -08:00
KChar 'f' -> loop vty game{ gameFlat = not (gameFlat game) }
_ -> loop vty game
2022-12-03 11:50:06 -08:00
_ -> loop vty game
2022-12-04 19:55:30 -08:00
2022-12-08 18:58:07 -08:00
escape :: Vty -> Game -> IO ()
escape vty game
| null (gameError game) = pure ()
| otherwise = loop vty game{ gameError = "" }
loadLevelFile :: Vty -> Game -> FilePath -> IO ()
loadLevelFile vty game path =
do file <- readFile path
case parse file of
Left errorMessage -> loop vty game{ gameError = errorMessage }
Right world -> loop vty game{
gameMode = PlayMode (pure world),
gameError = ""
}
2022-12-04 19:55:30 -08:00
2022-12-08 18:58:07 -08:00
renderMenu :: DisplayRegion -> Int -> Map String a -> Image
renderMenu (w,h) sel list
| hpad >= 0 = pad wpad hpad 0 0 menu
| otherwise = pad wpad 0 0 0 (cropTop (imageHeight menu + hpad) menu)
2022-12-04 19:55:30 -08:00
where
hpad = h`div`2 - sel*6
wpad = max 0 (w - imageWidth menu) `div` 2
2022-12-04 19:55:30 -08:00
menu =
vertCat $
intersperse (char defAttr ' ') $
[ bigString (if sel == i then defAttr `withBackColor` cyan `withForeColor` white
else defAttr) k
| (i,k) <- zip [0..] (Map.keys list)
]
2022-12-07 10:11:06 -08:00
2022-12-08 18:58:07 -08:00
errorImage :: String -> Image
errorImage str = wrapBox a (bigString a str)
where
a = defAttr `withForeColor` red
2022-12-07 10:11:06 -08:00
bigString :: Attr -> String -> Image
bigString a = vertCat . map (string a) . bigText . map toUpper