{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE TypeSynonymInstances #-} -- instance MapTile View {-# LANGUAGE FlexibleInstances #-} -- instance MapTile View module GridProto.Internal.Core where import Prelude hiding (lookup) import GHC.Generics (Generic) import Control.Applicative ((<|>)) import Control.Monad.IO.Class (MonadIO(..)) import Control.Monad (when) import Data.Traversable (forM) import Data.Function (fix) import Data.Foldable (forM_) import Data.IORef (IORef, newIORef, readIORef, modifyIORef) import Data.Map (Map, fromList, (!), delete, alter, insert, filterWithKey, member, notMember, toList) import Data.Maybe (catMaybes, fromMaybe) import Data.Monoid (Monoid(..)) import Data.Semigroup (Semigroup(..)) import Data.Text (pack) import Data.Word (Word8, Word32) import Data.Int (Int16) import Linear.V2 (V2(..)) import Linear.V4 (V4(..)) import SDL.Input.Keyboard.Codes import SDL.Input.GameController (ControllerButton(..), ControllerButtonState(..), ControllerDeviceConnection(..)) import GridProto.Internal.Font import qualified Data.Map as Map import qualified Data.List as L import qualified Data.Vector.Storable as VS import qualified SDL import qualified SDL.Raw.Event as Raw import qualified SDL.Font as Font import qualified SDL.Primitive as Gfx import qualified SDL.Mixer as Mixer data Color = Red0 | Red1 | Red2 | Orange0 | Orange1 | Orange2 | Yellow0 | Yellow1 | Yellow2 | Chartreuse0 | Chartreuse1 | Chartreuse2 | Green0 | Green1 | Green2 | Spring0 | Spring1 | Spring2 | Cyan0 | Cyan1 | Cyan2 | Azure0 | Azure1 | Azure2 | Blue0 | Blue1 | Blue2 | Violet0 | Violet1 | Violet2 | Magenta0 | Magenta1 | Magenta2 | Rose0 | Rose1 | Rose2 | Brown0 | Brown1 | Brown2 | Gray0 | Gray1 | Gray2 | White0 | White1 | White2 | Black0 | Black1 | Black2 deriving (Enum, Eq, Bounded, Ord, Show, Generic) data Shape = Circle | FillCircle | Triangle | FillTriangle | Square | FillSquare | Plus | Dash | Bar | Cross deriving (Enum, Eq, Bounded, Show, Generic) data Mouse = Mouse { mousePosition :: (Int, Int) , mouseButton :: KeyState } deriving (Show, Eq, Generic) data Key = Char Char | UpArrow | DownArrow | LeftArrow | RightArrow | Enter | Escape | LeftShift | RightShift | LeftControl | RightControl | LeftAlt | RightAlt | Tab | Backspace | Meta deriving (Eq, Show, Ord, Generic) data KeyState = Pressed | Held | Released | Untouched deriving (Enum, Eq, Bounded, Show, Generic) newtype Keys = Keys { unKeys :: Map Key KeyState } deriving (Show, Eq, Generic) data Axis = Axis { xAxis :: Float , yAxis :: Float } deriving (Show, Eq, Generic) data Controller = Controller { isConnected :: Bool , startButton :: KeyState , backButton :: KeyState , dpadUp :: KeyState , dpadDown :: KeyState , dpadLeft :: KeyState , dpadRight :: KeyState , aButton :: KeyState , bButton :: KeyState , xButton :: KeyState , yButton :: KeyState , leftStick :: KeyState , rightStick :: KeyState , leftShoulder :: KeyState , rightShoulder :: KeyState , leftAxis :: Axis , rightAxis :: Axis } deriving (Show, Eq, Generic) initController :: Controller initController = Controller False Untouched Untouched Untouched Untouched Untouched Untouched Untouched Untouched Untouched Untouched Untouched Untouched Untouched Untouched (Axis 0 0) (Axis 0 0) data Input = Input { mouse :: Mouse , keys :: Keys , controller1 :: Controller , controller2 :: Controller , controller3 :: Controller , controller4 :: Controller } deriving (Show, Eq, Generic) data Tile = Tile { symbol :: Maybe (Char, Color) , shape :: Maybe (Shape, Color) , fill :: Maybe Color } deriving (Show, Eq, Generic) instance Semigroup Tile where (<>) (Tile aSymbol aShape aFill) (Tile bSymbol bShape bFill) = case bFill of Nothing -> Tile (bSymbol <|> aSymbol) (bShape <|> aShape) aFill Just _ -> Tile bSymbol bShape bFill instance Monoid Tile where mempty = Tile Nothing Nothing Nothing type View = Map (Int, Int) Tile data Viewport = Viewport { vpView :: View , vpXY :: (Int, Int) , vpDim :: (Int, Int) } deriving (Show, Eq, Generic) type Viewports = [Viewport] class MapTile a where mapTile :: ((Char, Color) -> (Char, Color)) -> ((Shape, Color) -> (Shape, Color)) -> (Color -> Color) -> a -> a -- mapSymbol :: ((Char, Color) -> (Char, Color)) -> a -> a mapSymbol f = mapTile f id id -- mapShape :: ((Shape, Color) -> (Shape, Color)) -> a -> a mapShape f = mapTile id f id -- mapFill :: (Color -> Color) -> a -> a mapFill f = mapTile id id f instance MapTile Tile where mapTile symbolFn shapeFn fillFn tile = Tile (symbolFn <$> symbol tile) (shapeFn <$> shape tile) (fillFn <$> fill tile) emptyView :: View emptyView = Map.fromList [] instance MapTile View where mapTile symbolFn shapeFn fillFn = fmap (mapTile symbolFn shapeFn fillFn) instance MapTile Viewport where mapTile symbolFn shapeFn fillFn vp = vp { vpView = mapTile symbolFn shapeFn fillFn (vpView vp) } lookupMap :: Ord k => k -> Map k a -> Maybe a lookupMap = Map.lookup num :: (Integral a, Num b) => a -> b num = fromIntegral lookupKey :: Keys -> Key -> KeyState lookupKey (Keys m) k = fromMaybe Untouched (Map.lookup k m) makeInput :: Input -> Maybe (Int, Int) -> Bool -> [SDL.EventPayload] -> Input makeInput Input{mouse,keys,controller1,controller2,controller3,controller4} mpos' mclick eventPayloads = Input m (Keys $ nextKeys $ unKeys keys) controller1' controller2' controller3' controller4' where mpos = fromMaybe (mousePosition mouse) mpos' mbutton | mclick && mouseButton mouse == Untouched = Pressed | mclick && mouseButton mouse == Pressed = Held | mclick && mouseButton mouse == Held = Held | not mclick && mouseButton mouse == Held = Released | otherwise = Untouched m = Mouse mpos mbutton keyChanges = Map.fromList . catMaybes $ map keyChange eventPayloads removeReleased = Map.filter (/= Released) pressedToHeld = Map.map stepKeyState nextKeys = Map.union keyChanges . pressedToHeld . removeReleased controller1' = foldr (applyControllerChange 0) (stepController controller1) eventPayloads controller2' = foldr (applyControllerChange 1) (stepController controller2) eventPayloads controller3' = foldr (applyControllerChange 2) (stepController controller3) eventPayloads controller4' = foldr (applyControllerChange 3) (stepController controller4) eventPayloads normalizeInt16 :: Int16 -> Float normalizeInt16 w = let f = fromIntegral w / (fromIntegral (maxBound :: Int16)) deadzone x = if x < 0.05 && x > -0.05 then 0 else x clamp x = if x > 1 then 1 else (if x < -1 then -1 else x) in clamp $ deadzone f applyControllerChange :: Int -> SDL.EventPayload -> Controller -> Controller applyControllerChange idx event c = case event of SDL.ControllerDeviceEvent (SDL.ControllerDeviceEventData ControllerDeviceRemoved j) -> if j == jId then c { isConnected = False } else c SDL.ControllerButtonEvent (SDL.ControllerButtonEventData 0 button buttonState) -> fromMaybe c (update button <$> toKeyState buttonState) SDL.ControllerAxisEvent (SDL.ControllerAxisEventData j 0 i) -> if j == jId then c { leftAxis = (leftAxis c) { xAxis = normalizeInt16 i } } else c SDL.ControllerAxisEvent (SDL.ControllerAxisEventData j 1 i) -> if j == jId then c { leftAxis = (leftAxis c) { yAxis = normalizeInt16 i } } else c SDL.ControllerAxisEvent (SDL.ControllerAxisEventData j 2 i) -> if j == jId then c { rightAxis = (rightAxis c) { xAxis = normalizeInt16 i } } else c SDL.ControllerAxisEvent (SDL.ControllerAxisEventData j 3 i) -> if j == jId then c { rightAxis = (rightAxis c) { yAxis = normalizeInt16 i } } else c -- SDL.JoyAxisEvent (SDL.JoyAxisEventData j 0 i) -> if j == jId then c { leftAxis = (leftAxis c) { xAxis = normalizeInt16 i } } else c SDL.JoyAxisEvent (SDL.JoyAxisEventData j 1 i) -> if j == jId then c { leftAxis = (leftAxis c) { yAxis = normalizeInt16 i } } else c SDL.JoyAxisEvent (SDL.JoyAxisEventData j 2 i) -> if j == jId then c { rightAxis = (rightAxis c) { xAxis = normalizeInt16 i } } else c SDL.JoyAxisEvent (SDL.JoyAxisEventData j 3 i) -> if j == jId then c { rightAxis = (rightAxis c) { yAxis = normalizeInt16 i } } else c _ -> c where toKeyState buttonState = case buttonState of ControllerButtonPressed -> Just Pressed ControllerButtonReleased -> Just Released _ -> Nothing update button v = case button of ControllerButtonStart -> c { startButton = v } ControllerButtonBack -> c { backButton = v } ControllerButtonDpadUp -> c { dpadUp = v } ControllerButtonDpadDown -> c { dpadDown = v } ControllerButtonDpadLeft -> c { dpadLeft = v } ControllerButtonDpadRight -> c { dpadRight = v } ControllerButtonA -> c { aButton = v } ControllerButtonB -> c { bButton = v } ControllerButtonX -> c { xButton = v } ControllerButtonY -> c { yButton = v } ControllerButtonLeftStick -> c { leftStick = v } ControllerButtonRightStick -> c { rightStick = v } ControllerButtonLeftShoulder -> c { leftShoulder = v } ControllerButtonRightShoulder -> c { rightShoulder = v } _ -> c jId = fromIntegral idx stepController :: Controller -> Controller stepController c = c { startButton = stepKeyState $ startButton c , backButton = stepKeyState $ backButton c , dpadUp = stepKeyState $ dpadUp c , dpadDown = stepKeyState $ dpadDown c , dpadLeft = stepKeyState $ dpadLeft c , dpadRight = stepKeyState $ dpadRight c , aButton = stepKeyState $ aButton c , bButton = stepKeyState $ bButton c , xButton = stepKeyState $ xButton c , yButton = stepKeyState $ yButton c , leftStick = stepKeyState $ leftStick c , rightStick = stepKeyState $ rightStick c , leftShoulder = stepKeyState $ leftShoulder c , rightShoulder = stepKeyState $ rightShoulder c } stepKeyState :: KeyState -> KeyState stepKeyState ks = case ks of Pressed -> Held Held -> Held Released -> Untouched Untouched -> Untouched keyFromKeyCode :: SDL.Keycode -> Maybe Key keyFromKeyCode = \case KeycodeLeft -> Just LeftArrow KeycodeDown -> Just DownArrow KeycodeUp -> Just UpArrow KeycodeRight -> Just RightArrow KeycodeReturn -> Just Enter KeycodeEscape -> Just Escape KeycodeLShift -> Just LeftShift KeycodeRShift -> Just RightShift KeycodeLCtrl -> Just LeftControl KeycodeRCtrl -> Just RightControl KeycodeLAlt -> Just LeftAlt KeycodeRAlt -> Just RightAlt KeycodeTab -> Just Tab KeycodeBackspace -> Just Backspace KeycodeLGUI -> Just Meta KeycodeRGUI -> Just Meta -- KeycodeA -> Just $ Char 'a' KeycodeB -> Just $ Char 'b' KeycodeC -> Just $ Char 'c' KeycodeD -> Just $ Char 'd' KeycodeE -> Just $ Char 'e' KeycodeF -> Just $ Char 'f' KeycodeG -> Just $ Char 'g' KeycodeH -> Just $ Char 'h' KeycodeI -> Just $ Char 'i' KeycodeJ -> Just $ Char 'j' KeycodeK -> Just $ Char 'k' KeycodeL -> Just $ Char 'l' KeycodeM -> Just $ Char 'm' KeycodeN -> Just $ Char 'n' KeycodeO -> Just $ Char 'o' KeycodeP -> Just $ Char 'p' KeycodeQ -> Just $ Char 'q' KeycodeR -> Just $ Char 'r' KeycodeS -> Just $ Char 's' KeycodeT -> Just $ Char 't' KeycodeU -> Just $ Char 'u' KeycodeV -> Just $ Char 'v' KeycodeW -> Just $ Char 'w' KeycodeX -> Just $ Char 'x' KeycodeY -> Just $ Char 'y' KeycodeZ -> Just $ Char 'z' -- Keycode0 -> Just $ Char '0' Keycode1 -> Just $ Char '1' Keycode2 -> Just $ Char '2' Keycode3 -> Just $ Char '3' Keycode4 -> Just $ Char '4' Keycode5 -> Just $ Char '5' Keycode6 -> Just $ Char '6' Keycode7 -> Just $ Char '7' Keycode8 -> Just $ Char '8' Keycode9 -> Just $ Char '9' -- KeycodeBackquote -> Just $ Char '`' KeycodeMinus -> Just $ Char '-' KeycodeEquals -> Just $ Char '=' KeycodeLeftBracket -> Just $ Char '[' KeycodeRightBracket -> Just $ Char ']' KeycodeBackslash -> Just $ Char '\\' KeycodeSemicolon -> Just $ Char ';' KeycodeQuote -> Just $ Char '\'' KeycodeComma -> Just $ Char ',' KeycodePeriod -> Just $ Char '.' KeycodeSlash -> Just $ Char '/' KeycodeSpace -> Just $ Char ' ' -- _ -> Nothing keyChange :: SDL.EventPayload -> Maybe (Key, KeyState) keyChange event = case event of SDL.KeyboardEvent SDL.KeyboardEventData{SDL.keyboardEventKeysym = SDL.Keysym{SDL.keysymKeycode = code}, SDL.keyboardEventKeyMotion = motion, SDL.keyboardEventRepeat } -> if not keyboardEventRepeat then case motion of SDL.Released -> (\k -> (k, Released)) <$> keyFromKeyCode code SDL.Pressed -> (\k -> (k, Pressed)) <$> keyFromKeyCode code else Nothing _ -> Nothing drawTileMap :: Color -> SDL.Renderer -> Int -> (Color -> Char -> IO (Maybe (SDL.Texture, Int, Int))) -> View -> IO () drawTileMap bgColor renderer tileSize fontMap m = forM_ (toList m) $ \((x,y), Tile{symbol,shape,fill}) -> do drawFill renderer tileSize (x,y) fill case shape of Nothing -> return () Just shape' -> drawShape (fromMaybe bgColor fill) renderer tileSize (x,y) shape' case symbol of Nothing -> return () Just (symbol', color) -> drawSymbol renderer fontMap symbol' color tileSize (x,y) drawFill :: SDL.Renderer -> Int -> (Int, Int) -> Maybe Color -> IO () drawFill _ _ _ Nothing = return () drawFill renderer tileSize (x,y) (Just color) = do let fx0 = x * tileSize fx1 = (x + 1) * tileSize fy0 = y * tileSize fy1 = (y + 1) * tileSize Gfx.fillRectangle renderer (V2 (num fx0) (num fy0)) (V2 (num fx1) (num fy1)) (colorPixel color) drawShape :: Color -> SDL.Renderer -> Int -> (Int, Int) -> (Shape, Color) -> IO () drawShape bgColor renderer tileSize (x,y) (shape,color) = case shape of -- Circle -> do Gfx.fillCircle renderer center radius color' Gfx.fillCircle renderer center (radius - thickness') (colorPixel bgColor) -- FillCircle -> Gfx.fillCircle renderer center radius color' -- Triangle -> do let drawTri a b c co = do let (dax, day) = a (dbx, dby) = b (dcx, dcy) = c ax = x * tileSize + dax ay = y * tileSize + day bx = x * tileSize + dbx by = y * tileSize + dby cx = x * tileSize + dcx cy = y * tileSize + dcy Gfx.fillTriangle renderer (V2 (num ax) (num ay)) (V2 (num bx) (num by)) (V2 (num cx) (num cy)) (colorPixel co) drawTri triDA triDB triDC color drawTri triDA' triDB' triDC' bgColor -- FillTriangle -> do let (dax, day) = triDA (dbx, dby) = triDB (dcx, dcy) = triDC ax = x * tileSize + dax ay = y * tileSize + day bx = x * tileSize + dbx by = y * tileSize + dby cx = x * tileSize + dcx cy = y * tileSize + dcy Gfx.fillTriangle renderer (V2 (num ax) (num ay)) (V2 (num bx) (num by)) (V2 (num cx) (num cy)) (colorPixel color) -- Square -> do let drawSquare thk co = do let fx0 = x * tileSize + thk fx1 = (x + 1) * tileSize - thk fy0 = y * tileSize + thk fy1 = (y + 1) * tileSize - thk Gfx.fillRectangle renderer (V2 (num fx0) (num fy0)) (V2 (num fx1) (num fy1)) (colorPixel co) drawSquare thickness color drawSquare (thickness * 2) bgColor -- FillSquare -> do let fx0 = x * tileSize + thickness fx1 = (x + 1) * tileSize - thickness fy0 = y * tileSize + thickness fy1 = (y + 1) * tileSize - thickness Gfx.fillRectangle renderer (V2 (num fx0) (num fy0)) (V2 (num fx1) (num fy1)) (colorPixel color) -- Plus -> do let x' = x * tileSize + halfTile' y' = y * tileSize + halfTile' a = num <$> V2 x' (y * tileSize + thickness') b = num <$> V2 x' ((y + 1) * tileSize - thickness') c = num <$> V2 (x * tileSize + thickness') y' d = num <$> V2 ((x + 1) * tileSize - thickness') y' Gfx.thickLine renderer a b thickness' color' Gfx.thickLine renderer c d thickness' color' -- Dash -> do let y' = y * tileSize + halfTile' a = num <$> V2 (x * tileSize + thickness') y' b = num <$> V2 ((x + 1) * tileSize - thickness') y' Gfx.thickLine renderer a b thickness' color' Bar -> do let x' = x * tileSize + halfTile' a = num <$> V2 x' (y * tileSize + thickness') b = num <$> V2 x' ((y + 1) * tileSize - thickness') Gfx.thickLine renderer a b thickness' color' -- Cross -> do let diff = halfTile' - thickness left = x * tileSize + halfTile' - diff right = x * tileSize + halfTile' + diff top = y * tileSize + halfTile' - diff bottom = y * tileSize + halfTile' + diff a = num <$> V2 left top b = num <$> V2 right bottom c = num <$> V2 right top d = num <$> V2 left bottom Gfx.thickLine renderer a b thickness' color' Gfx.thickLine renderer c d thickness' color' where thickness' :: Num a => a thickness' = num thickness thickness :: Int thickness = max (tileSize `div` 12) 1 -- triAAngle = pi / 2 triBAngle = 2 * pi / 3 + pi / 2 triCAngle = 2 * 2 * pi / 3 + pi / 2 halfTile = fromIntegral tileSize / 2 halfTile' = tileSize `div` 2 triCorner angle radius = ( floor $ (radius * cos angle) + halfTile , floor $ negate (radius * sin angle) + halfTile + fromIntegral tileSize * 0.1 ) -- triDA = triCorner triAAngle halfTile triDB = triCorner triBAngle halfTile triDC = triCorner triCAngle halfTile -- innerHalf = halfTile - fromIntegral thickness * 2 triDA' = triCorner triAAngle innerHalf triDB' = triCorner triBAngle innerHalf triDC' = triCorner triCAngle innerHalf -- center = (\n -> floor (num (n * tileSize) + halfTile)) <$> V2 x y radius = floor $ halfTile * 0.8 color' = colorPixel color drawSymbol :: SDL.Renderer -> (Color -> Char -> IO (Maybe (SDL.Texture, Int, Int))) -> Char -> Color -> Int -> (Int, Int) -> IO () drawSymbol renderer fontMap ch color tileSize (x,y) = do m <- fontMap color ch case m of Nothing -> return () Just (tex, offsetX, offsetWidth) -> do SDL.TextureInfo{SDL.textureWidth=_texWidth,SDL.textureHeight=texHeight} <- SDL.queryTexture tex let wh = V2 (fromIntegral offsetWidth) texHeight let wh2 = V2 (div (fromIntegral offsetWidth) 2) (div texHeight 2) let xy' = xy + center - wh2 SDL.copy renderer tex (Just $ SDL.Rectangle (SDL.P (fromIntegral <$> V2 offsetX 0)) (V2 (fromIntegral offsetWidth) texHeight)) (Just $ SDL.Rectangle (SDL.P xy') wh) where xy = fromIntegral <$> V2 (tileSize * x) (tileSize * y) center = fromIntegral <$> V2 (tileSize `div` 2) (tileSize `div` 2) colorPixel :: Color -> Gfx.Color colorPixel c = bgr (colorValue c) sdlColor :: Color -> Gfx.Color sdlColor = bgr . colorValue bgr :: (Word8, Word8, Word8) -> Gfx.Color bgr (r,g,b) = V4 (num r) (num g) (num b) 0xff colorValue :: Integral a => Color -> (a, a, a) colorValue Red0 = (0xff, 0x44, 0x44) colorValue Red1 = (0xff, 0x00, 0x00) colorValue Red2 = (0xaa, 0x00, 0x00) colorValue Orange0 = (0xff, 0xaf, 0x33) colorValue Orange1 = (0xff, 0x7f, 0x00) colorValue Orange2 = (0xcf, 0x4f, 0x00) colorValue Yellow0 = (0xff, 0xff, 0x44) colorValue Yellow1 = (0xff, 0xff, 0x00) colorValue Yellow2 = (0xaa, 0xaa, 0x00) colorValue Chartreuse0 = (0xb2, 0xff, 0x66) colorValue Chartreuse1 = (0x7f, 0xff, 0x00) colorValue Chartreuse2 = (0x58, 0xb2, 0x00) colorValue Green0 = (0x44, 0xff, 0x44) colorValue Green1 = (0x00, 0xff, 0x00) colorValue Green2 = (0x00, 0xaa, 0x00) colorValue Spring0 = (0x66, 0xff, 0xb2) colorValue Spring1 = (0x00, 0xff, 0x7f) colorValue Spring2 = (0x00, 0x99, 0x4c) colorValue Cyan0 = (0xa0, 0xff, 0xff) colorValue Cyan1 = (0x00, 0xff, 0xff) colorValue Cyan2 = (0x00, 0x8b, 0x8b) colorValue Azure0 = (0x00, 0x7f, 0xff) colorValue Azure1 = (0x33, 0x66, 0x99) colorValue Azure2 = (0x00, 0x33, 0x66) colorValue Blue0 = (0x44, 0x44, 0xff) colorValue Blue1 = (0x00, 0x00, 0xff) colorValue Blue2 = (0x00, 0x00, 0xaa) colorValue Violet0 = (0x93, 0x70, 0xdb) colorValue Violet1 = (0x94, 0x00, 0xd3) colorValue Violet2 = (0x80, 0x00, 0x80) colorValue Magenta0 = (0xff, 0x9e, 0xff) colorValue Magenta1 = (0xff, 0x00, 0xff) colorValue Magenta2 = (0x8b, 0x00, 0x8b) colorValue Rose0 = (0xff, 0x99, 0xcc) colorValue Rose1 = (0xff, 0x66, 0x99) colorValue Rose2 = (0xaa, 0x22, 0x44) colorValue Brown0 = (0xaa, 0x77, 0x44) colorValue Brown1 = (0x88, 0x44, 0x00) colorValue Brown2 = (0x55, 0x22, 0x00) colorValue Gray0 = (0xd3, 0xd3, 0xd3) colorValue Gray1 = (0x80, 0x80, 0x80) colorValue Gray2 = (0xa9, 0xa9, 0xa9) colorValue White0 = (0xff, 0xff, 0xf0) colorValue White1 = (0xff, 0xff, 0xff) colorValue White2 = (0xf0, 0xff, 0xff) colorValue Black0 = (0x10, 0x00, 0x00) colorValue Black1 = (0x00, 0x00, 0x00) colorValue Black2 = (0x00, 0x10, 0x10) shade :: Color -> Color shade c = case c of Red0 -> Red1 Red1 -> Red2 Red2 -> Red2 Orange0 -> Orange1 Orange1 -> Orange2 Orange2 -> Orange2 Yellow0 -> Yellow1 Yellow1 -> Yellow2 Yellow2 -> Yellow2 Chartreuse0 -> Chartreuse1 Chartreuse1 -> Chartreuse2 Chartreuse2 -> Chartreuse2 Green0 -> Green1 Green1 -> Green2 Green2 -> Green2 Spring0 -> Spring1 Spring1 -> Spring2 Spring2 -> Spring2 Cyan0 -> Cyan1 Cyan1 -> Cyan2 Cyan2 -> Cyan2 Azure0 -> Azure1 Azure1 -> Azure2 Azure2 -> Azure2 Blue0 -> Blue1 Blue1 -> Blue2 Blue2 -> Blue2 Violet0 -> Violet1 Violet1 -> Violet2 Violet2 -> Violet2 Magenta0 -> Magenta1 Magenta1 -> Magenta2 Magenta2 -> Magenta2 Rose0 -> Rose1 Rose1 -> Rose2 Rose2 -> Rose2 Brown0 -> Brown1 Brown1 -> Brown2 Brown2 -> Brown2 Gray0 -> Gray1 Gray1 -> Gray2 Gray2 -> Gray2 White0 -> White1 White1 -> White2 White2 -> White2 Black0 -> Black1 Black1 -> Black2 Black2 -> Black2 tint :: Color -> Color tint c = case c of Red0 -> Red0 Red1 -> Red0 Red2 -> Red1 Orange0 -> Orange0 Orange1 -> Orange0 Orange2 -> Orange1 Yellow0 -> Yellow0 Yellow1 -> Yellow0 Yellow2 -> Yellow1 Chartreuse0 -> Chartreuse0 Chartreuse1 -> Chartreuse0 Chartreuse2 -> Chartreuse1 Green0 -> Green0 Green1 -> Green0 Green2 -> Green1 Spring0 -> Spring0 Spring1 -> Spring0 Spring2 -> Spring1 Cyan0 -> Cyan0 Cyan1 -> Cyan0 Cyan2 -> Cyan1 Azure0 -> Azure0 Azure1 -> Azure0 Azure2 -> Azure1 Blue0 -> Blue0 Blue1 -> Blue0 Blue2 -> Blue1 Violet0 -> Violet0 Violet1 -> Violet0 Violet2 -> Violet1 Magenta0 -> Magenta0 Magenta1 -> Magenta0 Magenta2 -> Magenta1 Rose0 -> Rose0 Rose1 -> Rose0 Rose2 -> Rose1 Brown0 -> Brown0 Brown1 -> Brown0 Brown2 -> Brown1 Gray0 -> Gray0 Gray1 -> Gray0 Gray2 -> Gray1 White0 -> White0 White1 -> White0 White2 -> White1 Black0 -> Black0 Black1 -> Black0 Black2 -> Black1 rd0, rd1, rd2, or0, or1, or2, yw0, yw1, yw2, ch0, ch1, ch2, gn0, gn1, gn2, sp0, sp1, sp2, cn0, cn1, cn2, az0, az1, az2, bu0, bu1, bu2, vt0, vt1, vt2, mg0, mg1, mg2, rs0, rs1, rs2, br0, br1, br2, gy0, gy1, gy2, wh0, wh1, wh2, bk0, bk1, bk2 :: Color (rd0, rd1, rd2) = (Red0, Red1, Red2) (or0, or1, or2) = (Orange0, Orange1, Orange2) (yw0, yw1, yw2) = (Yellow0, Yellow1, Yellow2) (ch0, ch1, ch2) = (Chartreuse0, Chartreuse1, Chartreuse2) (gn0, gn1, gn2) = (Green0, Green1, Green2) (sp0, sp1, sp2) = (Spring0, Spring1, Spring2) (cn0, cn1, cn2) = (Cyan0, Cyan1, Cyan2) (az0, az1, az2) = (Azure0, Azure1, Azure2) (bu0, bu1, bu2) = (Blue0, Blue1, Blue2) (vt0, vt1, vt2) = (Violet0, Violet1, Violet2) (mg0, mg1, mg2) = (Magenta0, Magenta1, Magenta2) (rs0, rs1, rs2) = (Rose0, Rose1, Rose2) (br0, br1, br2) = (Brown0, Brown1, Brown2) (gy0, gy1, gy2) = (Gray0, Gray1, Gray2) (wh0, wh1, wh2) = (White0, White1, White2) (bk0, bk1, bk2) = (Black0, Black1, Black2) rainbow :: [Color] rainbow = [rd1, or1, yw1, ch1, gn1, sp1, cn1, az1, bu1, vt1, mg1, rs1] warms :: [Color] warms = [rd1, or1, yw1, rs1] cools :: [Color] cools = [ch1, gn1, sp1, cn1, az1, bu1, vt1, mg1] tileByMousePosition :: Int -> (Int, Int) -> (Int, Int) -> Maybe (Int, Int) tileByMousePosition tileSize (mx,my) (r,c) | mx < 0 || my < 0 || mx >= tileSize * c || my >= tileSize * r = Nothing | otherwise = Just (mx `div` tileSize, my `div` tileSize) symbolList :: [Char] symbolList = "`1234567890-=~!@#$%^&*()_+qwertyuiop[]\\QWERTYUIOP{}|asdfghjkl;'ASDFGHJKL:\"zxcvbnm,./ZXCVBNM<>?" toTexture :: SDL.Renderer -> SDL.Surface -> IO SDL.Texture toTexture renderer surface = do texture <- SDL.createTextureFromSurface renderer surface SDL.freeSurface surface return texture placeTile :: (Int, Int) -> Tile -> View -> View placeTile xy tile m = Map.insertWith (flip (<>)) xy tile m placeTilesAt :: View -- | Base tiles -> (Int, Int) -- | Offset -> View -- | Tiles to be placed -> View placeTilesAt old (x,y) new = foldr (\((x',y'), tile) m' -> placeTile (x+x', y+y') tile m') old (Map.toList new) mergeTiles :: View -- | Base tiles -> View -- | Tiles to be placed -> View mergeTiles old new = placeTilesAt old (0,0) new mergeViewport :: View -> Viewport -> View mergeViewport old vp = placeTilesAt old (vpXY vp) (Map.filterWithKey (\(x,y) _ -> x < w && y < h) (vpView vp)) where (w,h) = vpDim vp mergeViewports :: View -> Viewports -> View mergeViewports = L.foldl' mergeViewport loadFont :: SDL.Renderer -> Int -> IO (Font.Font, Int) loadFont renderer tileSize = (,) <$> Font.decode fontData size <*> pure size where size = tileSize `div` 2 newFontColorMap :: IO (IORef (Map Color SDL.Texture)) newFontColorMap = newIORef Map.empty loadSymbols :: SDL.Renderer -> Font.Font -> Color -> IO SDL.Texture loadSymbols renderer font color = do symSurface <- Font.solid font (colorPixel color) (pack symbolList) toTexture renderer symSurface findSymbols :: SDL.Renderer -> Font.Font -> Int -> IORef (Map Color SDL.Texture) -> Color -> Char -> IO (Maybe (SDL.Texture, Int, Int)) findSymbols renderer font width ref color ch = do let width' = width `div` 2 fontMap <- readIORef ref case lookupMap color fontMap of Just tex -> case lookupMap ch offsets of Nothing -> return Nothing Just off -> return $ Just (tex, off * width', width') Nothing -> case lookupMap ch offsets of Nothing -> return Nothing Just off -> do sym <- loadSymbols renderer font color modifyIORef ref (insert color sym) return $ Just (sym, off * width', width') where offsets = Map.fromList $ zip symbolList [0..] colorWheel0 :: [Color] colorWheel0 = [Red0, Orange0, Yellow0, Chartreuse0, Green0, Spring0, Cyan0, Azure0, Blue0, Violet0, Magenta0, Rose0] colorWheel1 :: [Color] colorWheel1 = [Red1, Orange1, Yellow1, Chartreuse1, Green1, Spring1, Cyan1, Azure1, Blue1, Violet1, Magenta1, Rose1] colorWheel2 :: [Color] colorWheel2 = [Red2, Orange2, Yellow2, Chartreuse2, Green2, Spring2, Cyan2, Azure2, Blue2, Violet2, Magenta2, Rose2] class Monad m => FPS m where startFrame :: m Word32 default startFrame :: MonadIO m => m Word32 startFrame = liftIO SDL.ticks endFrame :: Int -> Word32 -> m () default endFrame :: MonadIO m => Int -> Word32 -> m () endFrame = endFrame' instance FPS IO -- | `endFrame`'s default definition endFrame' :: MonadIO m => Int -> Word32 -> m () endFrame' fps startTicks = liftIO $ do endTicks <- SDL.ticks let diff = (endTicks - startTicks) * fps' when (msps > diff) $ do let ms = (msps - diff) `div` fps' SDL.delay (fromIntegral ms) where fps' = fromIntegral fps -- | Same as default definition and prints fps and delay endFrameDebug :: MonadIO m => Int -> Word32 -> m () endFrameDebug fps startTicks = liftIO $ do endTicks <- SDL.ticks let diff = (endTicks - startTicks) * fps' let ms = (msps - diff) `div` fps' when (msps > diff) $ SDL.delay (fromIntegral ms) putStrLn $ show fps ++ " fps - before delay " ++ show (endTicks - startTicks) ++ " ms" ++ " with expected max " ++ show (msps `div` fps') ++ " ms" where fps' = fromIntegral fps -- | Milliseconds per second msps :: Word32 msps = 1000