{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE RecordWildCards #-} module Command.Betris (command, Options(..), betris) where import Control.Concurrent (forkIO, threadDelay) import Control.Concurrent.STM.TChan import Control.Lens hiding (argument) import Control.Monad (forever) import Control.Monad.STM (atomically) import Data.Char (chr) import Data.Foldable (for_) import Data.IORef import qualified Data.Map as Map import Data.Semigroup ((<>)) import Data.Time.Units import Data.Version (showVersion) import Game.Tetris import Graphics.Vty hiding (Event) import qualified Graphics.Vty as Vty (Event) import Linear.V2 (V2(..)) import Paths_betris (version) import Prelude hiding (Left, Right) import Options.Applicative hiding (command, (<|>)) command :: Parser (IO ()) command = betris <$> (versionOption <*> programOptions) data Options = Options { initialDelay :: Millisecond } deriving (Eq, Show) programOptions :: Parser Options programOptions = Options <$> initialDelayOption data Event e = Tick | Ev e deriving (Eq, Read, Show, Functor) betris :: Options -> IO () betris Options{..} = do vty <- mkVty =<< userConfig chan <- newTChanIO game <- initGame 0 speed <- newIORef $ fromIntegral $ toMicroseconds initialDelay forkIO $ forever $ nextEvent vty >>= atomically . writeTChan chan . Ev forkIO $ forever $ do readIORef speed >>= threadDelay modifyIORef speed ((-) 500) atomically $ writeTChan chan Tick _ <- play vty chan game shutdown vty putStrLn "" play :: Vty -> TChan (Event Vty.Event) -> Game -> IO Game play vty chan tetris | isGameOver tetris = pure tetris | otherwise = do update vty $ picForImage $ string defAttr (emboss tetris) <|> string defAttr (show $ tetris ^. score) e <- atomically $ readTChan chan case e of Tick -> play vty chan =<< timeStep tetris Ev (EvKey KLeft []) -> play vty chan $ hardDrop tetris Ev (EvKey KUp []) -> play vty chan $ Left `shift` tetris Ev (EvKey KDown []) -> play vty chan $ Right `shift` tetris Ev (EvKey KEnter []) -> play vty chan $ rotate tetris Ev (EvKey KEsc []) -> pure tetris _ -> play vty chan tetris emboss :: Game -> String emboss g = map go [1, 3 .. boardHeight] where go y = chr $ foldr (f y) 0x2800 [((0,0),1), ((1,0), 2), ((2,0), 4) ,((0,1),8), ((1,1),16), ((2,1),32) ,((3,0),64),((3,1),128)] f y ((x',y'), v) a = case Map.lookup (V2 (x+x') (y+y')) fullBoard of Just _ -> a + v _ -> a x = minimum $ (boardWidth - 3) : map (\(V2 x _) -> x) (coords $ g ^. block) fullBoard = g ^. board <> blk (g ^. block) blk b = Map.fromList $ map (, b ^. shape) $ coords b initialDelayOption :: Parser Millisecond initialDelayOption = option auto $ long "initial-delay" <> short 'i' <> metavar "DURATION" <> value (fromMicroseconds 1000000) <> showDefault <> help "Initial delay" versionOption :: Parser (a -> a) versionOption = infoOption (showVersion version) $ long "version" <> help "Show version"